IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言文件操作 -> 正文阅读

[C++知识库]C语言文件操作


1.什么是文件?

磁盘上的文件都是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件数据文件(从文件功能的角度来分类的)。

1.1程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

1.2数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件

本篇博客重点讨论数据文件
在一千各章所处理数据的输入输出都是终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

1.3文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3个部分:文件路径+文件名主干+文件后缀
例如:c:\code\test.txt
文件路径为:c:\code\
文件名主干:test
文件后缀: .txt

2 文件的打开和关闭

2.1文件指针

缓冲文件系统中,关键的概念是:文件类型指针,简称:文件指针
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的相关信息(比如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明,取名FILE
例如:VS2013编译环境提供的stdio.h头文件中有以下的文件类型申明:

struct _iobuf
{
	char* _ptr;
	int _cnt;
	char* _base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char* _tmpfname;
};
typedef struct _iobuf FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的内容,使用者不必关心细节。
一都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

创建一个FILE* 的指针变量:

FILE* pf;//文件指针变量

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

2.2文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件
在编写程序的时候,再打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSI C规定使用fopen函数来打开文件,fclose来关闭文件。

//打开文件
FILE* fopen(const char* filename,const char* mode);
//关闭文件
int fclose(FILE* stream);
文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建立一个新的文件出错
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建立一个新的二进制文件建议一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

实现代码:

#include<stdio.h>
int main()
{
	FILE* pFile;
	//打开文件
	pFile = fpopen("myfile.txt","w");
	//文件操作
	if(pFile != NULL)
	{
		fputs("fopen example",pFile);
		//关闭文件
		fclose(pFile);
	}
	return 0;
}

例子:

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//读文件
	
	//关闭文件
	fclose(pf);
	return 0;
}

如果该路径下没有test.txt文件,则会提示打开失败;
在这里插入图片描述
我们在程序运行前,创建好该文件,就可以正常运行!如下:
在这里插入图片描述
在这里插入图片描述
如果想要打开存在于桌面上的文件,则需要加上绝对路径

int main()
{
	FILE* pf = fopen("C:\\Users\\15508\\Desktop\\test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//读文件

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

在这里插入图片描述
警告:
绝对路径中出现 \ 需要替换为 \ \ 防止转换为转义字符

3.文件的顺序读写

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件
二进制输出fwrite文件

fputc

将一个字符写入指定输入流中

int fputc(int ch,FILE* stream);
//第一个参数为字符的ASCII码
//第二个参数为指定流

代码实现:

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 'a'; i <= 'z'; i++)
	{
		//依次将a -> z写入文件中
		fputc(i, pf);
	}
	
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

fgetc

去指定流里读取字符

int fgetc(FILE* stream);
//第一个参数为指定流
//返回读取的字符

代码实现:

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	
	int ch = fgetc(pf);
	printf("%c ", ch);
	ch = fgetc(pf);
	printf("%c ", ch);
	ch = fgetc(pf);
	printf("%c ", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述有没有一种可能,读取文件会失败呢?

If a read error occurs, the function returns EOF and sets the error indicator for the stream (ferror).
如果读取文件失败,则返回EOF;
所以,我们需要在读取文件后,进行判断,如果函数返回值等于EOF则停止读取,否则就可以继续;

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	
	//int ch = fgetc(pf);
	//printf("%c ", ch);
	//ch = fgetc(pf);
	//printf("%c ", ch);
	//ch = fgetc(pf);
	//printf("%c ", ch);
	//printf("\n");
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ", ch);
	}
	printf("\n");
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述

fputs

写一行字符串进指定流中

int fputs(const char* str,FILE* stream);

代码实现:

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	fputs("hello world", pf);
	
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述
不知道你们发现没有,之前写进文件中的a->z的字符去哪里了?
其实,在进行文件写操作的时候,原本文件中的内容会被全部清空,再重新写进去新的数据。
如果你要在文件原有内容的基础在后面进行写操作,就需要用到"a"追加操作;

在这里插入图片描述
如果需要换行,只需要在程序中"hello world"后添加即可;

fgets

从输入流中读取字符串,读到的字符串放到指定数组中

char* fgets(char* str, int num, FILE* stream);

代码实现:

int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	char arr[20] = { 0 };
	fgets(arr, 5, pf);
	printf("%s\n", arr);

	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述
注意:
这里最多读5个字符,但实际上读到的只有4个,它会在最后一个字符处放一个’\0’,所以有效数据只有4个

fgets如果读取成功,返回存放字符串的地址;如果读取失败,则返回NULL指针

perror

打印错误信息,可以代替printf(“%s\n”,strerror(errno));
但该函数一定会进行打印错误信息;
如果你只想获得错误信息,则使用strerror(errno)
如果你需要打印错误信息,则使用perror();
代码实现:

int main()
{
	FILE* pf = fopen("C:\\Users\\15508\\Desktop\\test.txt", "r");
	if (pf == NULL)
	{
		//printf("%s\n", strerror(errno));
		perror("fopen::");
		return 1;
	}
	//读文件

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

上述函数只能操作于字符串和字符,下面我们介绍能够作用于格式化输出/入的函数

fprintf

按指定格式将数据写入文件中

代码实现:

struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	struct Stu s = { "zhangsan", 14, 98.5 };
	fprintf(pf, "%s %d %f", s.name, s.age, s.score);
	
	fclose(pf);
	pf = NULL;
	
	return 0;
}

在这里插入图片描述

fscanf

按指定格式从文件中读取数据

代码实现:

int main()
{
	struct Stu s = { 0 };
	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\n", s.name, s.age, s.score);

	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述
注意:
任意一个C语言,只要运行起来都会默认打开3个流:

stdin //标准输入流(键盘)
stdout //标准输出流(屏幕)
stderr //标准错误流(屏幕)

用fprintf将信息写到屏幕上去
在这里插入图片描述

fwrite

以二进制的形式将信息写到文件中

size_t fwrite(const void* ptr,size_t size,size_t count,FILE* stream);
//ptr指向要写的数据
//size表示要写的每个数据的大小
//count表示要写的数据的数量
//函数返回成功写进去的数据的数量

代码实现:

int main()
{
	FILE* pf = fopen("test.txt", "wb");//以二进制形式写
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	struct Stu s = { "zhangsan", 14, 98.5 };
	fwrite(&s, sizeof(struct Stu), 1, pf);
	
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们发现,文本中有部分我能看得懂,有部分却看不懂,这是为什么呢?
因为:这是以二进制的形式写进文件中的;
但为什么zhangsan我们能看得懂?这是因为zhangsan以文本的形式写进文件和以二进制形式写进文件是一样的;

我们可以在VS下以二进制形式打开文本文件,查看其中内容
在这里插入图片描述

fread

以二进制的形式将文件中的数据读入指定地点

size_t fread(void* ptr,size_t size,size_t count,FILE* stream);

代码使用:

int main()
{
	FILE* pf = fopen("test.txt", "rb");//以二进制形式写
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	struct Stu s = { 0 };

	fread(&s, sizeof(struct Stu), 1, pf);
	printf("%s %d %f", s.name, s.age, s.score);
	printf("\n");
	
	fclose(pf);
	pf = NULL;
	
	return 0;
}

在这里插入图片描述

3.1对比一组函数

scanf / fscanf / sscanf
printf / fprintf / sprintf

前两对我们已经很熟悉了,只有后两对有点问题

scanf 是针对标准输入的格式化输入语句
printf是针对标准输出的格式化输出语句
fscanf是针对所有输入流的格式化输入语句
fprintf是针对所有输出流的格式化输出语句

sprintf

将一个格式化的数据写入到字符串中,本质上就是将格式化的数据转换为字符串
在这里插入图片描述

代码使用:

struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = { "zhangsan",20,55.5f };
	char buf[100] = { 0 };
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);

	printf("%s\n", buf);

	return 0;
}

在这里插入图片描述

sscanf

从字符串中读一个格式化的数据,本质上就是从一个字符串中转换出一个格式化的数据
在这里插入图片描述

代码使用:

int main()
{
	struct Stu s = { "zhangsan",20,55.5f };
	char buf[100] = { 0 };
	//把s中的格式化数据转换为字符串放到buf中
	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
	
	struct Stu tmp = { 0 };
	//从字符串buf中获取一个格式化数据到tmp中
	sscanf(buf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
	printf("%s %d %f", tmp.name, tmp.age, tmp.score);
	printf("\n");
	return 0;
}  

在这里插入图片描述

4.文件的随机读写

4.1 fseek

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

int fseek(FILE* stream,long int offset,int origin);
//stream//流
//offset//偏移量
//origin//文件指针位置
//如果函数成功,则返回0
//否则返回非0值

origin有三种取值
分别为:
SEEK_SET – 文件起始位置
SEEK_CUR – 当前文件的位置
SEEK_END – 文件末尾
在这里插入图片描述

先进行写文件

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	//写文件
	for (int i = 'a'; i <= 'z'; i++)
	{
		fputc(i, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

利用fseek读取你所需的数据

int main()
{
	//以读的形式打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	//从文件起始位置读取'c'
	fseek(pf, 2, SEEK_SET);//文件指针指向偏移量为2的地方
	int ch = fgetc(pf);//读取成功后,当前文件指针指向偏移量为3的地方
	printf("%c\n", ch);

	//从当前位置向后读取f
	fseek(pf, 2, SEEK_CUR);//文件指针指向偏移量 3+2的地方
	ch = fgetc(pf);//读取成功后,文件指针指向偏移量为6的地方
	printf("%c\n", ch);

	//读取末尾z
	fseek(pf, -1, SEEK_END);//偏移量指向末尾-1的地方
	ch = fgetc(pf);
	printf("%c\n", ch);
	
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

4.2 ftell

返回文件指针相对于文件的偏移量

long int ftell(FILE* stream);

代码使用:

int main()
{
	//以读的形式打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	//从文件起始位置读取'c'
	fseek(pf, 2, SEEK_SET);//文件指针指向偏移量为2的地方
	int ch = fgetc(pf);//读取成功后,文件指针指向偏移量为3的地方
	printf("%c\n", ch);
	printf("%d\n", ftell(pf));

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

在这里插入图片描述

4.3 rewind

让文件指针的位置回到文件的起始位置

void rewind(FILE* stream);

代码使用:

int main()
{
	//以读的形式打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	//从文件起始位置读取'c'
	fseek(pf, 2, SEEK_SET);//文件指针指向偏移量为2的地方
	int ch = fgetc(pf);//读取成功后,文件指针指向偏移量为3的地方
	printf("%c\n", ch);
	printf("%d\n", ftell(pf));
	
	rewind(pf);//让文件指针回到文件起始位置
	ch = fgetc(pf);
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述

5. 文本文件和二进制文件

根据数据的组织形式,数据文件被称为文本文件二进制文件
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件是文本文件
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据可以用ASCII形式存储,也可以用二进制形式存储。
例如:有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘占用5个字节(每个字符占1个字节),而二进制形式输出,则在磁盘上只占4个字节

代码演示:

以二进制形式进行写并查看内容:

int main()
{
	int a = 10000;
	//以二进制形式进行写操作
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	fwrite(&a, sizeof(int), 1, pf);
	
	fclose(pf);
	pf = NULL;

	return 0;
}

由于直接打开文件,会看不懂里面的内容;我们先将文件拖到VS中,再利用VS的二进制编辑器查看文件中的内容;
在这里插入图片描述
以文本文件形式进行写并查看内容:

int main()
{
	int a = 10000;
	//以二进制形式进行写操作
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen::");
		return 1;
	}
	fprintf(pf, "%d", a);
	
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述
在这里插入图片描述

6.文件读取结束的判定

6.1被错误使用的feof

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应该用于当文件读取结束的时候,判断文件结束的原因:是读取失败结束,还是遇到文件尾结束(feof)。
1.文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)
例如:
fgetc判断是否为EOF
fgets判断返回值是否为NULL
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。

文本文件例子:

int main()
{
	FILE* pf = fopen("test.txt","r");
	if (pf == NULL)
	{
		perror("file opening failed");
		return 1;
	}
	int c = 0;
	//fgetc当遇到文件读取失败或者遇到文件末尾都会返回EOF
	while ((c = fgetc(pf)) != EOF)
	{
		putchar(c);
	}
	if (ferror(pf))
	{
		puts("I/0 error when reading");
	}
	else if (feof(pf))
	{
		printf("End of file reached successfully");
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

二进制形式例子:

enum
{
	SIZE = 5
};

int main()
{
	double a[SIZE] = { 1.0,2.0,3.0,4.0,5.0 };
	FILE* pf = fopen("test.txt", "wb");//二进制形式写
	fwrite(a, sizeof(a[0]), SIZE, pf);
	fclose(pf);

	double b[SIZE];
	pf = fopen("test.txt", "rb");
	size_t ret_code = fread(b, sizeof(b[0]), SIZE, pf);
	if (ret_code == SIZE)
	{
		puts("Array read successfully,contents: ");
		for (int n = 0; n < SIZE; ++n)
			printf("%f ", b[n]);
		putchar("\n");
	}
	else
	{
		if (feof(pf))
		{
			printf("Error reading test.txt: unexpected end of file\n");
		}
		else if (ferror(pf))
		{
			perror("Error reading test.txt");
		}
	}

	close(pf);

	return 0;
}

7.文件缓冲区

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

int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//将代码放在输出缓冲区
	printf("睡眠20秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(20000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	printf("在睡眠20秒-此时,再次打开test.txt文件,文件有内容了\n");
	Sleep(20000);
	fclose(pf);
	//fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;

	return 0;
}

在这里插入图片描述
在这里插入图片描述
这里可以得出一个结论:
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束
的时候关闭文件。
如果不做,可能导致读写文件的问题。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:34:21  更:2022-07-20 18:36:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 10:21:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码