【C语言】探索文件读写函数的全貌

发布于:2024-07-05 ⋅ 阅读:(15) ⋅ 点赞:(0)

请添加图片描述

请添加图片描述
Alt

🌈个人主页:是店小二呀
🌈C语言笔记专栏:C语言笔记
🌈C++笔记专栏: C++笔记

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
请添加图片描述
🔥引言

本章将介绍文件读取函数的相关知识和展示使用场景,通过这些函数就可以打开文件与我们之间的桥梁。


一、文件的打开和关闭

如果需要对文件进行一些读写操作,那么首先就需要先打开文件,在使用完以后关闭文件,所以最基本是打开文件和关闭文件

ANSIC规定使用fopen函数来打开文件,fclose来关闭文件

打开文件
    FILE *fopen(const char *filename,const char *mode);
关闭文件
    FILE *fclose(FILE *stream);

二、文件的打开模式

mode表示文件的打开模式,下面都是文件的打开模式

⽂件使⽤⽅式 含义 如果指定⽂件不存在
“r”(只读) 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 出错
“w”(只写) 为了输出数据,打开⼀个⽂本⽂件 建⽴⼀个新的⽂件
“a”(追加) 向⽂本⽂件尾添加数据 建⽴⼀个新的⽂件
“rb”(只读) 为了输⼊数据,打开⼀个⼆进制⽂件 出错
“wb”(只写) 为了输出数据,打开⼀个⼆进制⽂件 建⽴⼀个新的⽂件
“ab”(追加) 向⼀个⼆进制⽂件尾添加数据 建⽴⼀个新的⽂件
“r+”(读写) 为了读和写,打开⼀个⽂本⽂件 出错
“w+”(读写) 为了读和写,建议⼀个新的⽂件 建⽴⼀个新的⽂件
“a+”(读写) 打开⼀个⽂件,在⽂件尾进⾏读写 建⽴⼀个新的⽂件
“rb+”(读写) 为了读和写打开⼀个⼆进制⽂件 出错
“wb+”(读写) 为了读和写,新建⼀个新的⼆进制⽂件 建⽴⼀个新的⽂件
“ab+”(读写) 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 建⽴⼀个新的⽂件

三、文件路径

在打开和关闭文件一般直接输入指定的文件名,当然也可以根据文件的路径对该文件进行操作。

路径分为:

  • 绝对路径
  • 相对路径
  • . 表示当前路径
  • … 上一级路径
对此当前文件test1.txt在D:\code\2024\2024\Document(绝对路径)
FILE* pf = fopen("C:\\Users\\zpeng\\Desktop\\test.txt", "w");   

当test1.txt在其他文件夹中,可以通过上面表示的关系寻找
FILE* pf = fopen(".\\..\\..\\hehe\\test.txt", "w");

四、文件打开和关闭函数

4.1 fopen()

FILE *fopen(const char * filename, const char * mode)

说明】:

  • 功能:打开其名称在参数 filename 中指定的文件,并将其与流相关联,该流可在将来的操作中通过返回的 FILE指针进行标识。
  • 返回值:如果文件成功打开,该函数将返回指FILE对象的指针,该对象可用于在将来的操作中标识流,如果文件没有成功打开,则将返回NULL。
  • 允许对流执行的操作以及如何执行这些操作由 mode 参数定义

4.2 fclose()

int fclose(FLIE *stream)

说明】:

  • 功能:关闭与流关联的文件并取消关联
  • 与流关联的所有内部缓冲区都将与其解除关联并刷新:写入任何未写入的输出缓冲区的内容,并丢弃任何未写入输入缓冲区的内容。(更新缓冲区)
  • 返回值:流关闭成功,则返回0。关闭失败,返回EOF

五、顺序读写函数

这里所有的输入流、输出流分别指stdin(标准输入流)、stdout(标准输出流)和文件流

5.1 fputc()

请添加图片描述

函数部分说明】:

功能上:

将字符写入流并推进位置指示器,然后自动前进1。适用于所有输入流(字符输入到流中)

参数部分上:

character

  • 将要写入的字符的整型提升
  • 写入时,该值在内部转换为无符号字符

stream:

  • 指向标识输出流的FILE对象指针

返回值:

  • 成功,将返回写入的字符。发生写入错误,则返回EOF并设置错误指示符(ferror)
int main()
{
	FILE* pf = fopen("test1.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fail!!!");
			return;
	}
	fputc('d', pf);
	fputc('e', pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

使用说明】:

  • 首次调用fputc函数将字符‘d’写入到文件当中,位置指示器向前+1就会指向‘d’的下一个位置,第二次fputc函数时,字符‘e’在这个位置输入进去。
  • 注意这里对流执行写操作"w"模式
  • 对于stream可以是文件流也可以是标准流

练习】:使用fputc将A ~Z个字符输入到指定文件和显示器中

int main()
{
	FILE* pf = fopen("test1.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fail!!!");
		return;
	}
	for (char i = 'A'; i <= 'Z'; i++)
	{
	fputc(i, pf);//输入到文件中
	fputc(i, stdout);//输入到显示器中
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

5.2 fgetc()

在这里插入图片描述

函数部分说明】:

  • 功能:字符输入函数,从流中获得字符,返回指定流的内部文件位置指示符当前指向的字符内部文件位置指示器将前进到下一个字符
  • 返回值:成功后,将返回字符读取(进行整型提升)。如果失败则,返回EOF
int main()
{
    FILE* pf = fopen("test1.txt", "r");
    if (pf == NULL)
    {
        perror("fopen fail!!!");
        return;
    }	
    char ch;
    //ch用于接收fgetc从流中获得的字符
    ch = fgetc(pf);
    printf("%c", ch);
    
    ch = fgetc(pf);
    printf("%c", ch);

    fclose(pf);
    pf = NULL;
    return 0;
}

使用说明】:

  • 这里对流的操作采用mode是"r"模式
  • 此时在指定文件提前写入数据,使用fgetc读取该文中的数据,第一次使用时读取‘h’字符,指示器++1,指向‘e’字符位置,在第二次使用读取该字符。
  • 当然也可以直接从键盘读取数据(使用标准输入流stdin)–>改为ch = fgetc(stdin);

5.3 实现文件拷贝

通过以上两个函数,我们可以用来实现文件拷贝的场景

int main()
{
    FILE* pfread = fopen("data1.txt", "r");
    if (pfread == NULL)
    {
        perror("fopen fail!!!");
        return;
    }	
    FILE* pfwrite = fopen("data2.txt", "w");
    if (pfwrite == NULL)
    {
        fclose(pfread);//读文件操作没有必要
        pfread=NULL;
        perror("fopen fail!!!");
        return;
    }
    
    char ch;
    while ((ch = fgetc(pfread)) != EOF)
    {
        fputc(ch, pfwrite);
    }
    fclose(pfread);
    fclose(pfwrite);
    return 0;
}

在这里插入图片描述

使用说明】:

  1. 这里先提前准备好两个文件,并且在data1.txt中提前输入数据
  2. 这里需要对两个不同文件使用不同mode打开,对data1.txt使用读的形式,而data2.txt是写的形式打开
  3. 在while循环中一个从data1.txt读取字符,同时一个向data2.txt输入字符,直到读取完成或者发生错误

5.4 fputs()

在这里插入图片描述

函数部分说明】:

  • 功能:文本行输入函数,将str指向的字符串写入流中,从指定的地址开始复制,直到结束标志\0(\0’字符不会复制到流中
  • 返回值:成功,返回一个非负值,失败,则返回EOF
int main()
{
    FILE * pf = fopen("test1.txt", "w");
    if (pf == NULL)
    {
        perror("fopen fail!!!");
        return;
    }	
    //fputs("hellow world", pf); -- 将字符串输入到文件流中
    fputs("hellow world", stdout); -- 将字符串输入到输出流中(打印到屏幕上)

    fclose(pf);
    pf=NULL;
    return 0;
}

5.5 fgets()

在这里插入图片描述

函数部分说明】:

功能上:

  • 文本行输入函数,从流中获得字符串,直到读取到num-1个字符或者在达到换行符或文件末尾(前者为准)
  • 换行符(\n)会使fgets停止,但函数将其视为有效字符,并包含在复制到str的字符串中,在将字符复制到str之后,会自动添加\0,导致只能读取num-1字符

参数部分上:

str

  • 指向复制字符串读取到char数组的指针(这里提前开辟好数组)

num:

  • 复制到str中的最大字符数(包括‘\0’字符-字符串)

返回值:

  • 成功,返回str指针,失败,返回EOF
int main()
{
    FILE* pf = fopen("test1.txt", "r");
    if (pf == NULL)
    {
        perror("fopen fail!!!");
        return;
    }
    char ch[1000]="xxxxxxx";
    fgets(ch, 7, pf);
    printf("%s", ch);
    fclose(pf);
    pf = NULL;
    return 0;
}

在这里插入图片描述

说明】:给\0留了一个位置


5.6 fscanf和fprintf函数

fprintfscanf属于格式化输入输出函数,将不同数据类型放在结构体中进行统一管理

struct S
{
    char a[1000];
    int p;
    float pa;
};

5.7 fprintf()

在这里插入图片描述

函数部分说明】:

功能上

  • 按格式化数据写入流,如果format包含格式说明符(%开头的子序列),则格式化format后面的其他参数并将其插入到生成的字符串中,代替其各自的说明符.
  • 根据格式说明符顺序,匹配不同类型对应的数据,输入到text1.txt中去。

返回值:

  • 成功,返回写入字符总数,失败,则返回负数
int main()
{
    struct S s={"zhangsan",100,4.3};
    FILE* pf = fopen("test1.txt", "w");
	if (pf == NULL)
	{
		perror("fopen fail!!!");
		return;
	}
	fprintf(pf,"%s %d %f", s.a, s.p, s.pa);
		fclose(pf);
		pf = NULL;
	return 0;
}

5.8 fscanf()

在这里插入图片描述

函数部分说明】:

功能:

  • 从流中读取数据,并根据格式将其存储到其他参数指向的位置。
  • 该对象由格式化字符串的相应格式说明符指示。
  • 根据格式说明符顺序,匹配text1.txt对应的数据,输入到对应的数据变量中

返回值:

成功后,该函数返回已成功填充的参数列表的项数,失败,EOF

int main()
{
	struct S s;//这里没有初始化,目的是通过fscanf赋值
	FILE* pf = fopen("test1.txt", "r");
	if (pf == NULL)
	{
		perror("fopen fail!!!");
		return;
	}	
	fscanf(pf, "%s %d %f", s.a, &(s.p), &(s.pa));
	printf("%s %d %f", s.a, s.p, s.pa);
    
		fclose(pf);
		pf = NULL;
	return 0;
}

5.9 sprintf和sscanf函数

5.9.1 sprintf()

在这里插入图片描述

函数部分说明】:

功能:

从字符串中读取格式化数据(将字符数组中的字符串按照格式说明符转换为对应的格式化数据)

5.9.2 sscanf

在这里插入图片描述

函数部分说明】:

功能:

将格式化数据写入字符串(格式化数据转换为字符串存放在字符数组中)

基于以上两个函数使用场景:

int main()
{
	//将格式化的数据转换为字符串存放在p数组中
	struct S s = { "zhangsan",100,4.3 };
	char p[1000] = { 0 };
	sprintf(p, "%s %d %f", s.a, s.p, s.pa);
	printf("%s\n", p);

	//从p这个字符串中提取格式化的数据(用p数组中的数据,为结构体t成员赋值)
	struct S t;
	sscanf(p, "%s %d %f", t.a, &(t.p), &(t.pa));
	printf("%s %d %lf", s.a, s.p, s.pa);

	return 0;
}

小总结

在这里插入图片描述


六、二进制的方式读写函数

6.1 fwrite()

在这里插入图片描述

函数部分说明】:
请添加图片描述

功能:

  • 二进制输出,将 count 元素数组(每个元素的大小为 size bytes)ptr 指向的内存块写入中的当前位置

返回值:

  • 成功将返回写入元素个数
  • 如果此数字与 count 参数不同,则写入错误会阻止函数完成
  • 如果 sizecount 为零,则函数返回零,错误指示器保持不变。
int main()
{
	FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
	if (pf == NULL)
	{
		perror("fopen fail!!!");
		return;
	}
	int nums[] = { 1,2,3,4,5,6,7 };
	fwrite(nums, sizeof(int), 7, pf);
    fclose(pf);
	pf = NULL;
	return 0;
}

注意】:这里是通过二进制的形式写入文本文件中,如果想要看懂输入的数据,可以在打开方式选择二进制编辑器或者使用fread函数读取出来


6.2 fread()

在这里插入图片描述

函数部分说明】:

  • 功能:
  • 二进制输入,从流中读取 count 元素的数组,每个元素的大小为 bytes,并将它们存储在 ptr 指定的内存块中。

返回值:

  • 成功将返回写入实际元素个数
  • 如果此数字与 count 参数不同,则表示读取时发生读取错误或达到文件末尾
  • 如果 sizecount 为零,则该函数返回零, ptr 指向的流状态和内容保持不变

比如:如果实际文件中只有7个字符,count是14,那么读到7给字符就结束(返回值 < count就返回)

int main()
{
    FILE* pf = fopen("test1.txt", "rb");//打开一个二级制文件
    if (pf == NULL)
    {
        perror("fopen fail!!!");
        return;
    }
    //int nums[] = { 1,2,3,4,5,6,7 };
    //fwrite(nums, sizeof(int), 7, pf);
    //读取二进制存放的信息
    
    int nums[10] = { 0 };
    fread(nums, sizeof(int), 10, pf);
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", nums[i]);
    }
    
    fclose(pf);
    pf = NULL;
    return 0;
}

在这里插入图片描述


七、文件的随机读写

由于文本文件和二进制文件使用方面存在差异,下面三个函数将采用二进制文件

7.1 fseek()

在这里插入图片描述

函数部分说明】:

功能上:

  • 重新定位流位置指示器,根据文件指针的位置和偏移量定位文件指针

返回值:

  • 成功,返回零,失败,返回非零值

注意】:

  • 二进制文件:要从原点偏移的字节数
  • 文本文件:零或 ftell 返回的值

【原点】

特别注意的是起源(从哪个位置),用作偏移参考位置

  • SEEK_SET :文件开头
  • SEEK_CUR :文件指针的当前位置
  • SEEK_END: 文件末尾
int main()
{
	FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
	if (pf == NULL)
	{
		perror("fopen fail!!!");
		return;
	}
	fputs("This is an apple", pf);
	fseek(pf, 3, SEEK_SET);
	fputs("sam", pf);
    
	fseek(pf, -3, SEEK_END);//文件指针往前走
	fputs("xxx", pf);
    fclose
	return 0;
}

在这里插入图片描述

7.2 ftell()

在这里插入图片描述

函数部分说明】:

功能上:

  • 返回流的位置指示器的当前值(返回文件指针相对于起始位置的偏移量 )

返回值:

  • 对于二进制流,这是从文件开头开始的字节
  • 对于文本流,数值可能没有意义
int main()
{
    FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
    if (pf == NULL)
    {
        perror("fopen fail!!!");
        return;
    }
    fputs("This is an apple", pf);

    fseek(pf, 0, SEEK_END);
    int size = ftell(pf);
    printf("%d", size);//size==16,源头到参考位置的字节数
    fclose(pf);
    pf = NULL;
    return 0;
}

7.3 rewind()

image-20240429202328302

函数部分说明】:

  • 功能:让文件指针的位置回到文件的起始位置
int main()
{
    FILE* pf = fopen("test1.txt", "wb");//打开一个二级制文件
    if (pf == NULL)
    {
        perror("fopen fail!!!");
        return;
    }
    fputs("This is an apple", pf);

    fseek(pf, 0, SEEK_END);
    rewind(pf);
    int size = ftell(pf);
    printf("%d", size);
    fclose(pf);
    pf = NULL;
    return 0;
}

请添加图片描述


八、文件读取结束的判断

牢记】:在文件读取过程中,不能用feof函数的返回值直接判断文件是否结束

8.1 ferror()

在这里插入图片描述

函数部分说明】:

  • 功能:检查错误指示器
  • 返回值:检查是否设置了与关联的错误指示器,如果设置了,则返回一个不同于零的值

8.2 feof()

在这里插入图片描述

函数部分说明】:

  • 功能:检查文件结束指示器

  • 返回值:**如果设置了与流关联的文件结束指示符,则返回非零值。**否则,返回零。

小总结

文本文件读取是否结束:判断返回值是否为EOF(fgetc)或者NULL(fgets)

二进制文件读取是否结束:判断返回值是否小于实际要读的个数。


检查文件读取结束场景

while ((c = fgetc(fp)) != EOF) // 标准C I/O读取
{
	putchar(c);
}

//判断是什么原因结束的
	if (ferror(fp))
puts("I/O error when reading");
	else if (feof(fp))
puts("End of file reached successfully");

九、文件缓冲区

ANSIC标准采⽤“缓冲文件系统”处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的.上述过程转换为下图展示。

验证】:

int main()
{
FILE*pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
//注:fflush 在⾼版本的VS上不能使⽤了
printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭⽂件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}

目的】:

程序延迟执行时间内,检查文本编辑器是否存在数据,等待程序睡眠时间过去,再次检查文本编辑器是否存在数据,验证内存和外存之间存在缓冲区

请添加图片描述
请添加图片描述

结论】:

因为有缓冲区的存在,C语⾔在操作文件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭文件。如果不做,可能导致读写⽂件的问题


请添加图片描述

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二C语言笔记,希望对你在学习C语言中有所帮助!


网站公告

今日签到

点亮在社区的每一天
去签到