C语言之文件操作(2)

目录

1.文件的顺序读写 ​

[1.1 fputc​](#1.1 fputc)

[1.2 fgetc ​](#1.2 fgetc)

[1.3 fputs](#1.3 fputs)

[1.4 fgets](#1.4 fgets)

[1.5 fprintf](#1.5 fprintf)

[1.6 fscanf​](#1.6 fscanf)

[1.7 fwrite](#1.7 fwrite)

[1.8 fread](#1.8 fread)

[2.sscanf 和 sprintf](#2.sscanf 和 sprintf)

3.文件的随机读写

[3.1 fseek](#3.1 fseek)

[3.2 ftell](#3.2 ftell)

[3.3 rewind](#3.3 rewind)

4.文件缓冲区


1.文件的顺序读写

上面表格适用于所有输入流一般指适用于标准输入流和其他输入流(如文件输入流);所有输出流一般指适用于标准输出流和其他输出流(如文件输出流)。

1.1 fputc

向流中写入字符,写入一个字符,光标向后移动一位

返回值:成功时,返回写入的字符。如果发生写入错误,则返回EOF并设置错误指示符(ferror)

#include <stdio.h>
int main()
{
	//打开文件
	FILE*pf=fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	char ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


1.2 fgetc

如果成功读取字符,返回字符的ASCII值,理论上用char类型接收返回值从技术层面看,char类型实际上存储的是整数而不是字符 ),int类型也可以为了兼容两者,返回值用int接收。

返回值:**成功时,返回读取的字符 ,如果位置指示符在文件的末尾**,函数返回EOF 并设置流的EOF指示符(feof)。如果发生其他读取错误,该函数也会返回EOF ,但会设置其错误指示器(ferror)

#include <stdio.h>
int main()
{
	//打开文件
	FILE*pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch=fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:

代码改进:

#include <stdio.h>
int main()
{
	//打开文件
	FILE*pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ",ch);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


1.3 fputs

该函数从指定的地址(str)开始复制,直到到达终止空字符(' \0 ')。此终止空字符不会复制到流中。

返回值:如果成功,将返回一个非负值。出错时,该函数返回EOF并设置错误指示器(ferror)。

#include <stdio.h>
int main()
{
	//打开文件
	FILE*pf=fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("hello zhangsan\n", pf);
	fputs("hello lisi\n", pf);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


1.4 fgets

从流中读取字符,并将它们作为C字符串存储到str中,直到读取了(num-1 )个字符,或者到达了换行符文件结尾 ,无论哪种情况先发生。换行符使fgets停止读取,但它被函数认为是有效字符,并包含在复制到str的字符串中。在复制到str的字符后会自动追加一个终止空字符

返回值:如果读取成功,该函数将返回str。如果在试图读取字符时遇到文件结尾或者遇到错误导致读取失败就会返回NULL

test.txt文件内容:

#include <stdio.h>
int main()
{
	//打开文件
	FILE*pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char arr[10] = { 0 };
	fgets(arr, 10, pf);//读取9个字符,最后一个空间留给\0
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

如果文件第一行不够10个字符,该怎么读取呢?

test.txt文件内容:

连续读几次,查看运行结果 ,例如:

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char arr[15] = "xxxxxxxxxxxxxxx";
	fgets(arr, 10, pf);
	printf("%s\n", arr);

	fgets(arr, 10, pf);
	printf("%s\n", arr);

	fgets(arr, 10, pf);
	printf("%s\n", arr);

	fgets(arr, 10, pf);
	printf("%s\n", arr);


	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:

不加换行符(\n),不会换行,下一次接着上次光标停留处读取,再打印,无论字符串有多长,都可以把文件信息原模原样打印出来 !例如:

将打印部分冗余的代码采用while循环改进:

#include <stdio.h>
int main()
{
	//打开文件
	FILE*pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char arr[15] = {0};
	while (fgets(arr, 10, pf) != NULL)
	{
		printf("%s", arr);
	}

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


1.5 fprintf

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "张三",20,45.5f };
	//想把s中的数据存放在文件中
	FILE* pf=fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件-是以文本的形式写进去的
	fprintf(pf,"%s %d %f", s.name, s.age, s.score);	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:

1.6 fscanf

test.txt文件内容:

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { 0 };
	//想从test.txt文件中读取数据放在s中
	FILE* pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	fscanf(pf,"%s %d %f", s.name, &(s.age), &(s.score));
	//打印在屏幕上
	printf("%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:

使用fprintf函数和标准输出流stdout把数据打印在屏幕上:

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { 0 };
	//想从test.txt文件中读取数据放在s中
	FILE* pf=fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	fscanf(pf,"%s %d %f", s.name, &(s.age), &(s.score));
	//打印在屏幕上
	fprintf(stdout,"%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


使用fputc函数和标准输出流stdout把数据打印在屏幕上:

#include <stdio.h>
int main()
{
	fputc('a', stdout);
	return 0;
}

输出结果:

1.7 fwrite

向流中写入ptr指向的内存块count个元素的数组,每个元素的大小为size个字节。

函数返回成功写入的元素的总个数

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	FILE* pf=fopen("test.txt", "wb");//以二进制形式写进文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写数据
	fwrite(arr, sizeof(arr[0]), sz, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文本编辑器打开如下:


1.8 fread

从流中读取count个元素的数组,每个元素的大小为size个字节,并将它们存储在ptr指定的内存块中 。

函数返回成功读取的元素总个数

我们把上次以二进制形式写进文件中的数据以二进制形式读取出来:

#include <stdio.h>
int main()
{
	int arr[5] = { 0 };
	FILE* pf = fopen("test.txt", "rb");//以二进制形式读取文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取数据
	fread(arr, sizeof(arr[0]), 5, pf);//从pf指向的文件中读取数据放到arr数组中
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

当我们不知道test.txt文件中到底存放多少个数据时,size_t count该怎么传参呢?

改进代码:

#include <stdio.h>
int main()
{
	int arr[5] = { 0 };
	FILE* pf = fopen("test.txt", "rb");//以二进制形式读取文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取数据
	//从pf指向的文件中读取数据放到arr数组中
	int i = 0;
	while (fread(&arr[i], sizeof(int), 1, pf))//一次读一个,每次返回值都是1,当函数没有读到数据,返回0,停止循环
	{
		printf("%d ", arr[i]);
		i++;
	}
	printf("\n");
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


2.sscanfsprintf

对比区分下列函数:

  • scanf------从标准输入流(键盘...)上读取格式化的数据。
  • fscanf------从所有输入流(包括标准输入流、文件流)上读取格式化的数据。
  • sscanf------从字符串中读取格式化的数据
    printf------将数据以格式化的形式打印在标准输出流(屏幕...)上。

fprintf------将数据以格式化的形式打印在所有输出流(包括标准输出流、文件流)上。

sprintf ------将格式化的数据输出到字符串中

sscanf函数和sprintf函数的使用:

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	char buf[200] = { 0 };
	struct S s = { "张三",20,45.5f };

	sprintf(buf,"%s %d %f", s.name, s.age, s.score);
	printf("以字符串的形式打印:%s\n", buf);

	struct S t = { 0 };//此时buf中存储的信息:张三 20 45.500000
	sscanf(buf, "%s %d %f", t.name, &(t.age), &(t.score));//从buf数组中读取结构体数据放到t中
	printf("按照格式化打印:%s %d %f\n", t.name, t.age, t.score);
	return 0;
}

运行结果:


3.文件的随机读写

3.1fseek

根据文件指针的位置偏移量来定位文件指针。(文件内容的光标)

新建一个test.txt文件,文件中存储:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);//a

	fseek(pf,4,SEEK_CUR); //fseek(pf,5,SEEK_SET); //fseek(pf,-4 , SEEK_END);
	
	ch = fgetc(pf);
	printf("%c\n", ch);//f
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


3.2 ftell

返回文件指针相对于起始位置的偏移量。
新建一个test.txt文件,文件中存储:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件

	fseek(pf, 0, SEEK_END);//文件末尾
	long int ret=ftell(pf);//文件末尾相对于文件起始位置的偏移量,等同于知道了文件的长度
	
	printf("%d\n", ret);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


3.3rewind

让文件指针的位置回到文件的起始位置。
新建一个test.txt文件,文件中存储:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	
	fseek(pf,2,SEEK_CUR);
	
	int ch = fgetc(pf);
	printf("%c\n", ch);//c

	rewind(pf);//文件指针回到起始位置

	ch = fgetc(pf);
	printf("%c\n", ch);//a

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

运行结果:


4.文件缓冲区

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

//VS2019 WIN11环境测试
#include <stdio.h>
#include <windows.h>
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;
}

10秒内打开文件:

10~20秒打开文件:

这里可以得出一个结论

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


相关推荐
hong1616881 小时前
VSCode中配置C/C++环境
c语言·c++·vscode
Crossoads2 小时前
【数据结构】排序算法---快速排序
c语言·开发语言·数据结构·算法·排序算法
小立爱学习2 小时前
Linux 给 vmlinux 添加符号
linux·c语言
Crossoads3 小时前
【数据结构】排序算法---基数排序
c语言·开发语言·数据结构·算法·排序算法
老薛爱吃大西瓜3 小时前
DAY15:链表实现学生信息管理系统
c语言·数据结构·学习·链表
guoguoqiang.3 小时前
我与Linux的爱恋:命令行参数|环境变量
linux·运维·服务器·c语言·学习
浅陌pa5 小时前
24:RTC实时时钟
c语言·stm32·单片机·嵌入式硬件
Reese_Cool5 小时前
【二级C语言考试】自定义数据类型
c语言·开发语言
我命由我123455 小时前
GPIO 理解(基本功能、模拟案例)
linux·运维·服务器·c语言·c++·嵌入式硬件·c#
只对您心动7 小时前
【C高级】有关shell脚本的一些练习
linux·c语言·shell·脚本