C语言文件操作

C语言文件操作

先唠点嗑。

学习函数,要学习函数的名字、功能、各个参数、不同情况下的返回值,最好敲一段代码,由体会,到记忆,再到理解。

如同许多要学习的事物一样,入门编程,并不是像局外人印象中,随随便便敲一个烟花代码,的帅气与洒脱。而是,蹒跚学步的艰难,与无所适从。往往,自主解决一个,很简单的问题,都需要试很多次,甚至出不来。

不说什么鼓励的话,只要你能明确自己的目标,并且每天一定,一定,一定要做点事,做一点能靠近目标的事。

今天做实验割到手了,出了很多血。所以做事要小心。

1、文件的由来

我们打印在屏幕(也叫标准信息流)上的信息,在退出程序之后,就会被清除。也就是说,打印在屏幕上的信息,是临时的。

如何持久保存呢?这就涉及到了文件。

2、文件的分类

一般文件,分两类:程序文件、数据文件。

程序文件,有源程序文件.c、目标文件Windows: .obj、可执行程序文件Windows: .exe

数据文件,顾名思义,就是存放数据的文件。

3、数据文件

数据文件,可被分为:文本文件、二进制文件。

文本文件,可以被理解为存放字符ASCII值的文件,在文件中,每个字符被翻译为ASCII数值,并存下来。

二进制文件,可以被理解为,将数据转化为二进制序列,并存在文件中。

比如,存数据10000

存文本文件,分别将1 (ASCII码值为48) 0 (ASCII码值为49) 0 (ASCII码值为49) 0 (ASCII码值为49) 0 (ASCII码值为49)存入文件。

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("data003.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("10000", fp);
	fclose(fp);
	fp = NULL;
	
	return 0;
}


存二进制文件,将10000翻译为:
00000000000000000010011100010000 00000000000000000010011100010000 00000000000000000010011100010000

再存入文件。

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("data004.txt", "wb");
	if (fp == NULL)
	{
		perror("fopen");
	}
	int a = 10000;
	fwrite(&a, sizeof(int), 1, fp);
	fclose(fp);
	fp = NULL;
	
	return 0;
}

10 27 00 00,翻译一下,注意大小端,就是10000。

4、文件的打开与关闭

文件的类型,与其包含的内容,多种多样。为了方便对文件的操作,我们抽象出流的概念。

可以想象下面这样一种场景:

许多条不同的河流,汇入同一条河道,又分离到不同的河道之中。

上面说的同一条河道,类似于流。我们可以在文件中,输入信息,然后打印在屏幕上;可以在屏幕上,输入信息,然后输出到文件中。

4.1、标准流

C语言程序启动时,自动打开了3种流:

  1. stdin: 标准输入流,可以在屏幕上输入信息。
  2. stdout: 标准输出流,可以在屏幕上输出信息。
  3. stderr: 标准错误流。

这三个标准流的类型,都是FILE*

那什么又是FILE*

4.2、FILE*

FILE*,称为文件类型指针,简称文件指针。

在vs 2022中,FILE*本质上,是一种结构体类型。

我们不讨论那么多,只需要知道,文件类型变量创建方式:

c 复制代码
#include<stdio.h>

int main()
{
	FILE* fp;
	
	return 0;
}

通过指针fp,我们就可以找到,其指向的文件。

4.3、打开和关闭的基本操作

关闭函数fclose()

需要头文件<stdio.h>

声明:int fclose(FILE* stream);

  1. 功能是关闭文件(的流)。
  2. stream指向要关闭的流。
  3. 成功关闭,返回0;失败,返回EOF

打开函数fopen()

需要头文件<stdio.h>

声明:FILE* fclose(FILE* stream, const char* mode);

  1. 功能是打开文件(的流)。
  2. stream指向要打开的流,mode指向打开的方式。
  3. 成功关闭,返回该文件的指针;失败,返回NULL
  4. 注意,创建了文件,最好判断一下,其可用性。

例如,我们以只写的形式,打开文件"myfile.txt"

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("myfile.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ret = fputs("Hello world", fp);
	if (ret == EOF)
	{
		printf(""\n);
		if (feof(fp))
			printf("文件遇到末尾\n");
		else if (ferror(fp))
			printf("文件读写发生错误\n");			
	}
	fclose(fp);
	fp = NULL;
	
	return 0;
}

下面是一些常见的文件打开方式:

打开方式 含义 如果指定文件不存在
r 为了输入数据,打开一个已经存在的文件 出错
w 为了输出数据,打开一个文件 创建新的文件
a 为了追加数据,打开一个文件 创建新的文件

5、文件的顺序读写

5.1、fputc()

c 复制代码
int fputc(int character, FILE* stream);
  1. 功能:向所有输出流中输出信息。
  2. character为要输出字符的ASCII码值,stream指向输出流
  3. 函数返回成功,返回要输出字符的ASCII码值;返回失败,返回EOF,可由函数ferror()检查。

将信息输出到屏幕:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	fputc('a', stdout);
	printf("\n");

	return 0;
}

输出到指定文件:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("file001.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//这里我们可以借助循环,输出信息到文件
	for (int i = 'a'; i <= 'z'; i++)
	{
		fputc(i, fp);
	}
	fclose(fp);
	fp = NULL;
	
	return 0;
}

5.2、fgetc()

c 复制代码
int fgetc(FILE* stream);
  1. 功能:在流中读取字符。
  2. stream指向流。
  3. 返回成功,返回读取字符的ASCII码值;返回失败,有两种情况:
    • 若已处于文件末尾,可用feof()检查。
    • 若发生读写错误,可用ferror()检查。

在标准流中读取字符并打印:

c 复制代码
#include<stdio.h>

int main()
{
	int ret = fgetc(stdin);
	fputc(ret, stdout);
	printf("\n");
	
	return 0;
}

在上面创建的file001.txt文件中,读取前十个字符:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("file001.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		int ret = fgetc(fp);
		putchar(ret);
	}
	fclose(fp);
	fp = NULL;
	
	return 0;
}

5.3、feof()

c 复制代码
int feof(FILE* stream);

文件读取遇到末尾时,函数会在对应流上,放置文件结束的指示符,feof()的作用就是检测这个指示符,也就是检测读取文件是否遇到结尾。

如果检测到指示符,feof()返回非0值,否则返回0。

测试feof()

c 复制代码
#include<stdio.h>
#include<errno.h>

//假设一个文件中有6个字符,但我们硬是要读取10个字符
int main()
{
	FILE* fp = fopen("file002.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		int ret = fgetc(fp);
		putchar(ret);
		
		if (ret == EOF)
		{
			printf("\n");
			if (feof(fp))
				printf("文件读取遇到末尾\n");
			else if (ferror(fp))
				printf("文件读取出错\n");
		}
	}
	printf("\n");
	fclose(fp);
	fp = NULL;
	
	return 0;
}

5.4、ferror()

c 复制代码
int ferror(FILE* stream);

如果文件读写发生错误,函数会在对应流上,放置文件结束的指示符,ferror()的作用是检测这个指示符,也就是检测文件读写是否遇到错误。

如果检测到发生错误,ferror()返回非0值,否则返回0。

测试ferror()

c 复制代码
#include<stdio.h>
#include<errno.h>

//如果以只写的方式打开文件,再去读文件,会出错
int main()
{
	FILE* fp = fopen("file003.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ret = fgetc(fp);
	if (ret == EOF)
	{
		printf("\n");
		if (feof(fp))
			printf("文件读取遇到末尾\n");
		if (ferror(fp))
			printf("文件读取出错\n");
	}
	fclose(fp);
	fp = NULL;
	
	return 0;
}

5.5、fputs()

c 复制代码
int fputs(const char* str, FILE* stream);
  1. 功能:将指定字符串,输出到指定流中。字符串必须包含\0,同时读到\0停止。
  2. stream指向输出到的流。
  3. 成功,返回非0值;失败,返回EOF,可被检测到。

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("file004.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("abc\0def\n", fp);
	fputs("xxxxx", fp);
	fclose(fp);
	fp = NULL;
	
	return 0;
}

5.6、fgets()

c 复制代码
char* fgets(char* str, int num, FILE* stream);
  1. 功能:读取流中指定长度的字符串,并保存至str中。
  2. str指向要保存到的字符串的空间。
  3. num表示要读取字符的个数。由于fputs()会将字符串末尾的\0一起读取,实际读取的最大字符数,就为num - 1
  4. 成功,返回str;失败,返回NULL,存在两种情况:
    • 若已处于文件末尾,可用feof()检查。
    • 若发生读写错误,可用ferror()检查。

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("file002.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}

	char str1[] = "********";
	fgets(str1, sizeof(str1), fp);

	return 0;
}

调试下:

注意两点:

1、对num - 1的理解。

比如我们想在文件内容为abcdef中,读取前3个字符。但是监视窗口中,str只有ab2个,和\0

2、被读取文件内容中,出现换行的情况。

这时,在换行处,写入\n,然后以\0结尾:

5.7、fprintf()

fprintf(),与printf()的功能类似,但是前者已经不限于标准流,而是任何文件流。

c 复制代码
int fprintf(FILE* stream, const char* format, ...);
  1. 功能就是打印信息。
  2. stream指向打印到的文件流,剩下的参数与printf()是类似的。
  3. 成功时,fprintf()返回写入字符的总数;失败时,返回EOF,可用ferror()检测。

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

typedef struct stu
{
	char name[30];
	int age;
	char sex[10];
}stu;

//创建一个结构体,然后打印到文件中
int main()
{
	stu t0 = { "zhangsan", 18, "male" };
	FILE* fp = fopen("file005.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ret = fprintf(fp, "%s %d %s", t0.name, t0.age, t0.sex);
	printf("%d\n", ret);
	fclose(fp);
	fp = NULL;
	
	return 0;
}

5.8、fscanf()

scanf()类似。

c 复制代码
int fscanf(FILE* fp, const char* format, ...)
  1. 功能是输入信息到流,与fprintf()对应。
  2. 参数依次是指向的流...
  3. 函数返回成功输入数据的个数。若到达结尾,返回EOF;若输入失败,返回EOF。可检查错误。

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

typedef struct stu
{
	char name[30];
	int age;
	char sex[10];
}stu;

//创建一个结构体,然后打印到文件中
int main()
{
	stu t0 = { 0 };
	FILE* fp = fopen("file005.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ret1 = fscanf(fp, "%s %d %s", t0.name, &t0.age, t0.sex);
	int ret2 = fprintf(stdout, "%s %d %s", t0.name, t0.age, t0.sex);
	printf("\n");
	printf("%d %d\n", ret1, ret2);
	fclose(fp);
	fp = NULL;

	return 0;
}

5.9、fwrite()

c 复制代码
size_t fwrite(char* str, size_t size, size_t count, FILE* stream);

功能:将str指定的数据块内容,以二进制的形式,写入stream指定的流中。

参数:

  1. str:指向被写入的空间。
  2. size:表示每个写入数据的大小(字节)。
  3. count:表示写入数据的个数。
  4. stream:指向要写入的文件流。

返回值:如果成功,返回count;如果发生错误,返回值可能小于count

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

typedef struct std
{
	char name[30];
	int age;
	float score;
}std;

int main()
{
	std s1 = { "lisi", 18, 95.5f };
	FILE* fp = fopen("file006.txt", "wb");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	if (fwrite(&s1, sizeof(std), 1, fp) != 1)
	{
		perror("fwrite");
		return -1;
	}
	fclose(fp);
	fp = NULL;
	
	return 0;
}

用自带笔记本打开,自然是一堆乱码。

用vs 2022自带的二进制编辑器打开,是这个样子:

依旧看不懂。

既然用二进制形式写入,那么可以读取出来吗?用什么函数?

答案是fread()

5.10、fread()

c 复制代码
size_t fread(char* str, size_t size, size_t count, FILE* stream);

功能:将stream指向文件流中二进制形式存储的内容,读取并存放到str指向的空间。

参数:

  1. str:指向读取到的空间。
  2. size:表示每个读取数据的大小(字节)。
  3. count:表示读取数据的个数。
  4. stream:指向被读取的文件流。

返回值:如果成功,返回count;如果发生错误,返回值可能小于count

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

typedef struct std
{
	char name[30];
	int age;
	float score;
}std;

//输入到文件流,再输出出来
int main()
{
	//std s1 = { "lisi", 18, 95.5f };
	FILE* fp = fopen("file006.txt", "rb");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	//if (fwrite(&s1, sizeof(std), 1, fp) != 1)
	//{
	//	perror("fwrite");
	//	return -1;
	//}
	std s2 = { 0 };
	if (fread(&s2, sizeof(std), 1, fp) != 1)
	{
		perror("fread");
		return -1;
	}
	printf("%s %d %f", s2.name, s2.age, s2.score);
	fclose(fp);
	fp = NULL;

	return 0;
}

6、对比一组函数

我们在前面就知道:

  1. scanf()/printf():对于标准输入/输出流的格式化数据的输入函数/输出函数。
  2. fscanf()/fprintf():对于所有输入/输出流的格式化数据的输入函数/输出函数。

我们再来补充一组:sscanf()/sprintf()

6.1、sprintf()

c 复制代码
int sprintf(void* ptr, const char* format, ...);

功能:将格式化数据,写入到ptr指向的空间中。

参数:

  1. ptr:指向要输出到的空间。
  2. format:格式化字符串,如%d %s %f
  3. ...:可变参数列表。

返回值:返回成功输出的字符数。

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

typedef struct std
{
	char name[100];
	int age;
	float score;
}std;

int main()
{
	//将结构体内容,改写为字符串
	std s1 = { "lisi", 18, 95.5f };
	char str[100] = { 0 };
	sprintf(str, "%s %d %f", s1.name, s1.age, s1.score);
	printf("%s\n", str);
	
	return 0;
}

输是输进字符数组了,怎么从字符数组输出呢?

sscanf()

6.2、sscanf()

c 复制代码
int sscanf(void* ptr, const char* format, ...);

功能:读取ptr指向的空间中内容,以格式化数据的形式。

参数:

  1. ptr:指向被读取到的空间。
  2. format:格式化字符串,如%d %s %f
  3. ...:可变参数列表。

返回值:返回成功输出的数据个数,若失败,返回EOF

函数测试:

c 复制代码
#include<stdio.h>
#include<errno.h>

typedef struct std
{
	char name[100];
	int age;
	float score;
}std;

int main()
{
	//将字符串内容,改写为结构体
	std s2 = { 0 };
	char str[100] = "zhangsan 20 85.5";
	sscanf(str, "%s %d %f", s2.name, &s2.age, &s2.score);
	printf("%s %d %f\n", s2.name, s2.age, s2.score);
	
	return 0;
}

总结:

函数 区别
printf() 针对标准输出流的格式化的输出函数
scanf() 针对标准输入流的格式化的输入函数
fprintf() 针对所有输出流的格式化的输出函数
fscanf() 针对所有输入流的格式化的输入函数
sprintf() 将格式化数据转化为字符串
sscanf() 在字符串中提取格式化数据

7、随机读写

上面提到的,都是按顺序来的,那有没有想读哪就读哪的方法?

7.1、fseek()

c 复制代码
int fseek(FILE* stream, long int offset, int origin);

功能:移动光标到指定位置。

参数:

  1. stream:文件流。
  2. offset:偏移量。
  3. origin:指定光标的位置,有起始位置SEEK_SET、当前位置SEEK_CUR、结束位置SEEK_END

例如,向已输入一串字母文件中,读取特定字母:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("text001.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	int ret1 = fgetc(fp);
	putchar(ret1);
	printf("\n");
	fseek(fp, 3, SEEK_CUR);
	int ret2 = fgetc(fp);
	putchar(ret2);
	printf("\n");
	fclose(fp);
	fp = NULL;

	return 0;
}

7.2、ftell()

c 复制代码
long int ftell(FILE* stream);

功能:返回文件光标相对于起始位置的偏移量。

例如,可以用此计算一串字母中字符的个数:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("text002.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	fseek(fp, 0, SEEK_END);
	long int ret = ftell(fp);
	printf("%ld\n", ret);
	fclose(fp);
	fp = NULL;

	return 0;
}

7.3、rewind()

c 复制代码
void rewind(FILE* stream);

功能:将文件内容中的光标移到起始位置。

例如,向文件输入26个英文字母,再输入到字符串中:

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("text002.txt", "w+");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	for (int i = 'a'; i <= 'z'; i++)
	{
		fputc(i, fp);
	}
	rewind(fp);
	fflush(fp);
	char buffer[27];
	fread(buffer, sizeof(char), 26, fp);
	buffer[26] = '\0';
	printf(buffer);
	fclose(fp);
	fp = NULL;

	return 0;
}

8、文件缓冲

数据从程序输入到硬盘,或从硬盘输入到程序,其实不是一下子就搞定的,而是中间经过了文件缓冲区。

c 复制代码
int fflush(FILE* stream);

功能:强制刷新文件缓冲区。

  1. 对输出流,将缓冲区的数据全部写入文件。
  2. 对输入流,该行为不是C语言标准行为。
  3. 参数为NULL时,刷新所有打开的输出流。

例如,编写一段代码,佐证文件缓冲区的存在:

c 复制代码
#include<stdio.h>
#include<errno.h>
#include<windows.h>

int main()
{
	FILE* fp = fopen("text003.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return -1;
	}
	fputs("abcdef", fp);
	printf("缓冲10秒,此时文件无内容。\n");
	Sleep(10000);
	printf("刷新文件\n");
	fflush(fp);
	printf("缓冲10秒,此时文件有内容。\n");
	fclose(fp);
	fp = NULL;
	
	return 0;
}

8、更新文件

打开文件的方式,其实还有"r+ w+ a+"

其实它们都有同时读写的含义,但是使用时,必须注意两点:

  1. 写入数据后,一定用fflush()进行刷新。
  2. 重新写数据时,一定用fseek() rewind()重新定光标的位置。

例如,我们写上字符串abcdefghi,并找到字符c,然后写入hello

c 复制代码
#include<stdio.h>
#include<errno.h>

int main()
{
	FILE* fp = fopen("text004.txt", "w+");
	if (fp == NULL)
	{
		perror("fopen\n");
		return -1;
	}
	fputs("abcdefghi", fp);
	fflush(fp);
	rewind(fp);
	fseek(fp, 2, SEEK_CUR);
	int ret = fgetc(fp);
	printf("%c\n", ret);
	fseek(fp, -1, SEEK_CUR);
	fputs("hello", fp);
	fclose(fp);
	fp = NULL;
	
	return 0;
}
相关推荐
熙xi.5 小时前
Linux I²C 总线驱动开发:从架构到实战的完整指南
linux·c语言·驱动开发
二进制coder6 小时前
深入浅出:I²C多路复用器PCA9546详解 - 解决地址冲突,扩展你的I²C总线
c语言·开发语言·单片机
彷徨而立8 小时前
【C/C++】只知道窗口句柄,如何擦除窗口内容,清理窗口?
c语言·c++·windows
云知谷8 小时前
【经典书籍】C++ Primer 第14类虚函数与多态精华讲解
c语言·开发语言·c++·软件工程·团队开发
傻童:CPU9 小时前
C语言需要掌握的基础知识点之递归
c语言·开发语言
laocooon52385788610 小时前
一个适合新手的训练C题
c语言·开发语言
坚持编程的菜鸟11 小时前
LeetCode每日一题——缀点成线
c语言·算法·leetcode
degen_11 小时前
PEIM安装PPI和调用其他PPI的相关函数
c语言·笔记
啊森要自信12 小时前
【MySQL 数据库】使用C语言操作MySQL
linux·c语言·开发语言·数据库·mysql