C语言学习笔记(十)文件操作

发布时间 2023-08-12 17:44:28作者: Arongsec

十、文件操作

  • 程序文件

  • 数据文件

本章学习的是数据文件

文件名

包含三部分:

文件路径 + 文件名主干 + 文件后缀

c:\code\test.php

文件类型

  • 文本文件:肉眼就能看懂
  • 二进制文件:数据在内存中以二进制的形式存储,若不加转换就输出到外存,就是二进制文件

字符一律以ASCII码形式存储

数值可以用ASCII形式存储,也可以使用二进制形式存储

如 整数 10000;若以ASCII码形式输出,占5个字节(每个字符一个字节 ’1‘ ’0‘ ’0‘ ’0‘ ’0‘ );

​ 若以二进制形式输出,占4个字节(00000000 00000000 00100111 00010000)

文件缓冲区

​ ANSIC标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等]。缓冲区的大小根据C编译系统决定的。

输出缓冲区

程序缓冲区 磁盘

输入缓冲区

文件指针

​ 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的,该结构体类型是有系统声明的,取名FILE

FILE f;

一般通过FILE的指针来维护这个FILE结构的变量

创建FILE*的指针变量

FILE* pf;

定义pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件

文件的打开和关闭

在读写文件之前要先打开文件

在使用结束之后应该关闭文件

编写程序在打开文件的同时,会返回一个FILE* 的指针变量指向该文件,也相当于建立了指针和文件的关系

用fopen函数来打开文件,fclose来关闭文件

打开方式

//打开文件test.php
FILE* pf = fopen("test.php","r");//以只读模式打开,且test.php就在当前.c文件的路径下
//相对路径
FILE* pf = fopen("../../test.php","r");// ..表示上一级路径,.表示当前路径,注意后面使用单斜杠/
//绝对路径:
FILE* pf = fopen("C:\\code\\test.php","r");//打开绝对路径下的文件,注意有两个斜杠,避免转义

若打开失败,会给 pf 返回一个空指针

#include<errno.h>
if (pf == NULL)//pf指针为空,打开失败
{
	printf("%s",strerror(errno));//打印错误信息
    return 0;
}
//打开成功后关闭文件
fclose(pf);
pf = NULL;//将pf指针置空

注意 当读写的文件不存在时,是否会创建一个新文件

“w”模式下,不管文件是否存在,都会新建一个新的文件把原来的覆盖掉

文件的顺序读写

写入

fputc():一次只能写入一个字符,如果不小心写成字符串,则会逐字覆盖,最后的结果是仅剩最后一个字符

注意:读取一次,文件指针就向后偏移一位

#include<errno.h>
#include<stdio.h>
int main()
{
	FILE* pfWrite = fopen("test.txt","w");//不要忘记等号
	if (pfWrite == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
	//写文件
	fputc('a',pfWrite);//fputc()一次只能写入一个字符,如果不小心写成字符串,则会逐字覆盖,最后的结果是仅剩最后一个字符
	//关闭文件
	fclose(pfWrite);
	pfWrite = NULL;
	return 0;
}

fputs():一次可以输入一行字符串

#include<errno.h>
#include<stdio.h>
int main()
{
	FILE* pfWrite = fopen("test.txt","w");//不要忘记等号
	if (pfWrite == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
	//写文件
	fputs('hello',pfWrite);
    fputs('hello',pfWrite);//注意:如果没有写入换行符,则默认两行是首尾拼接的
	//关闭文件
	fclose(pfWrite);
	pfWrite = NULL;
	return 0;
}
fwrite(&s,sizeof(struct S),1,pf);//将变量s中的内容写到pf指向的文件中,每个元素的字节大小是struct S的大小,元素的个数为1

读取

fgetc():一次只读取一个字符

#include<errno.h>
#include<stdio.h>
int main()
{
	FILE* pfRead = fopen("test.txt","r");
	if (pfRead == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
	//读文件
	printf("%c ",fgetc(pfRead));
	//关闭文件
	fclose(pfRead);
	pfRead = NULL;
	return 0;
}

fgets():一次读取一行字符

#include<errno.h>
#include<stdio.h>
int main()
{
	char buf[1024] = {0};
	FILE* pfRead = fopen("test.txt","r");
	if (pfRead == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
	//读文件
	printf("%s",fgetc(buf,1024,pfRead));//将读取到的内容存到buf中,一次最多读取1024个字节 
	//关闭文件
	fclose(pfRead);
	pfRead = NULL;
	return 0;
}
/*得到的结果为:
bit

以上内容之所以有两行,是因为buf自带一个换行符,即buf存储了所要读取的文件中末尾本身自带的换行符*/

注意:输出函数puts()不管原文件是否有换行,都会自动换行

二进制形式读文件:fread()

fread(&tmp,sizeof(struct S),1,pf);//将pf所指向文件中的内容读取出来,存到tmp中

标准输入/输出流

char buf[1024] = {0};
fgets(buf,1024,stdin);//从标准输入流读取
fputs(buf,stdout);//从标准输出流输出
//以上两行等价于
//gets(buf);
//puts(buf);

若要输入/输出处了字符串以外的其他格式的信息

fprintf():格式化输出数据,并将结果写入文件

struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = {100,3.14,"abc"};
	FILE* pf = fopen("text.txt","w");
	if (pf == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
    //格式化输出
	fprintf(pf,"%d %f %s",s.n,s.score,s.arr);//将输出的数据打印在pf指向的文件中
    
	fclose(pf);
	pf = NULL;
	return 0;
}

fscanf():格式化输入数据,即将文件读取到的信息放到某个变量里

struct S
{
	int n;
	float score;
	char arr[10];
};
int main()
{
	struct S s = {0};
	FILE* pf = fopen("text.txt","r");
	if (pf == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
    //格式化输入
	fscanf(pf,"%d %f %s",&(s.n),&(s.score),s.arr)// s.arr不需要使用取地址符号
	fclose(pf);
	pf = NULL;
	return 0;
}

  • scanf() 和 printf() 其实就是标准输入流 / 标准输出流的 fscanf() 和 fprintf() ,即后面这组针对的是所有输入流 / 输出流

  • sscanf() 和 sprintf():从字符串中格式化输入 / 输出,即可以将文件中的字符串转换为原有的类型并存储在变量中,也可以将变量中其他类型转化为字符串存储在文件中

文件的随机读写

fseek()

根据文件指针的位置和偏移量来定位文件指针

int main()
{
	FILE* pf = fopen("text.txt","r");
	if (pf == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
    //1.定位文件指针
    fseek(pf,2,SEEK_CUR);//从文件指针当前位置开始(默认是起始位置),向后(右)偏移2个字节
    //2.读取文件
    int ch = fgetc(pf);
    printf("%c ",ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

注意:如果是向前(左)偏移,文件的末尾位置应该是最后一位的后面

​ 如:abcdef ,末尾位置应是 f 后面一位,如果要移动到e,则偏移量应为-2

ftell()

计算当前文件指针相比于初始位置的偏移量

int main()
{
	FILE* pf = fopen("text.txt","r");
	if (pf == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
    fseek(pf,2,SEEK_CUR);
	int pos = ftell(pf);
    printf("%d\n",pos);
	fclose(pf);
	pf = NULL;
	return 0;
}

rewind():让文件指针的位置回到起始位置

int main()
{
	FILE* pf = fopen("text.txt","r");
	if (pf == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
	}
  	fgetc(pf);//读完以后文件指针向后偏移一位
    rewind(pf);//使pf回到起始位置
	int pos = ftell(pf);
    printf("%d\n",pos);
	fclose(pf);
	pf = NULL;
	return 0;
}

文件结束判定

feof

  • 不是用来判断文件读取是否结束的
  • 是当文件读取已经结束时,用来判断是读取失败,还是遇到文件尾(EOF)结束,即是用来判断文件读取结束的原因

EOF(-1)

文件结束标志

文本文件读取是否结束

判断返回值是否为EOF(fgetc()),或者NULL(fgets())

二进制文件读取是否结束

判断返回值是否小于实际要读取的个数

案例

int main()
{
	FILE* pf = fopen("text.txt","r");
	if (pf == NULL)
	{
		printf("%s",strerror(errno));
		return 0;
    }
    int ch = 0;
    while ((ch=fget(pf)) != EOF)
    {
        putc(ch);
    }
    if(ferror(pf))//遇到错误时,ferror返回一个非零值
    {
        printf("error!\n")
    }
    else if(feof(pf))//feof函数如果是遇到EOF结束的,返回非零值
    {
        printf("end of this file!");
    }
	fclose(pf);
	pf = NULL;
	return 0;
}