C语言-文件操作

一、文件的打开和关闭

1.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指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件
在这里插入图片描述

1.2 文件的打开和关闭

  文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
  在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
  ANSIC 规定使用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+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件
int main()
{
	//相对路径
	FILE* pf1 = fopen("..\\data1.txt", "w");
	//绝对路径
	//FILE* pf2 = fopen("E:\\bitcode\\c_code\\Git\\进阶C语言\\进阶c语言第13课\\data2.txt", "w");
	//FILE *pf=fopen("data.txt","r");
	if (pf1 == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	/*fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);*/
	/*int ch = fgetc(pf);
	printf("%c\n", ch)*/;


	//读文件
	int ch = fgetc(stdin);
	printf("%c\n", ch);
	ch = fgetc(stdin);
	printf("%c\n", ch);
	ch = fgetc(stdin);
	printf("%c\n", ch);
	ch = fgetc(stdin);
	printf("%c\n", ch);

	//关闭文件

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

二、文件的顺序读写

C语言程序,只要运行起来,默认就打开3个流
1.标准输入流stdin FILE*
2.标准输出流stdout FILE*
3.标准错误流stderr FILE*

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件
二进制输出fwrite文件
//用于从指定的文件流中读取一个字符
int fgetc(FILE *stream);
//fgetc(fp)
//stream:一个指向FILE对象的指针,该对象标识了要从中读取字符的流。这通常是一个由fopen函数打开的文件。

//如果读取成功,fgetc会返回读取到的字符的ASCII值(作为int类型返回,以便能够表示EOF)。
//如果到达文件末尾(EOF),fgetc会返回特殊的值EOF(在stdio.h中定义,通常是-1)。
//如果发生读取错误,fgetc也可能返回EOF,但此时你可以使用ferror函数来检查是否发生了错误。
//用于将一个字符写入指定的文件流中
int fputc(int char, FILE *stream);
//int fputc(int c, FILE *fp);
//char:要写入文件的字符。虽然函数接受一个 int 类型的参数,但通常只使用它的低八位来表示字符。
//stream:一个指向 FILE 对象的指针,该对象标识了要写入字符的流。这通常是一个由 fopen 函数打开的文件。

//如果写入成功,fputc 会返回写入的字符的 ASCII 码值(作为 int 类型返回)。
//如果发生错误,fputc 会返回 EOF(End Of File,通常在 stdio.h 中定义为 -1)。
//用于从指定的流(stream)中读取一行数据,并保存到一个字符串中
char *fgets(char *str, int n, FILE *stream);
//fgets(str, n, fp)
//str:这是一个指向字符数组的指针,fgets 会把读取到的数据(包括换行符,如果有的话)保存到这个数组中。
//n:这是要读取的最大字符数(包括结尾的空字符)。通常,为了安全起见,你会把这个值设置为数组的大小减一,以便为 '\0' 留出空间。
//stream:这是要从中读取的流的指针。这通常是一个由 fopen 打开的文件,但也可以是其他类型的流,如 stdin(标准输入)。

//如果成功读取了一行,fgets 会返回一个指向 str 的指针。
//如果到达文件末尾或发生错误,fgets 会返回 NULL。
//用于将一个字符串写入指定的文件流中
int fputs(const char *str, FILE *stream);
//fputs(str, fp)
//str:一个指向字符数组的指针,该数组包含了要写入文件的以空字符(\0)结尾的字符串。注意,fputs 不会写入空字符(\0)到文件中。
//stream:一个指向 FILE 对象的指针,该对象标识了要写入字符串的流。这通常是一个由 fopen 函数打开的文件。

//如果写入成功,fputs 会返回一个非负值(在某些实现中可能是写入的字符数,但在其他实现中可能总是返回非零值)。
//如果发生错误,fputs 会返回 EOF(End Of File,通常在 stdio.h 中定义为 -1)。
//用于从指定的文件流中读取格式化输入
int fscanf(FILE *stream, const char *format, ...);
//char str[50];     fscanf(pf, "%s",str);     std::cout << str << std::endl;
//fscanf(fp, "%d %f %s", &num, &value, str)
//stream:一个指向 FILE 对象的指针,该对象标识了要从中读取的流。这通常是一个由 fopen 打开的文件。
//format:一个格式字符串,指定了要读取的数据类型及其格式。格式字符串与 scanf 使用的格式字符串相同。
//...:可变数量的参数,对应于 format 字符串中指定的数据。

//如果成功读取并赋值了参数,fscanf 返回成功匹配和赋值的输入项数。
//如果到达文件末尾或发生读取错误,或者输入项不匹配指定的格式,fscanf 返回一个小于 format 字符串中指定的变量数的值。
//如果在调用 fscanf 时已经到达文件末尾,则返回 EOF。
//用于将格式化的数据写入到指定的文件流中
int fprintf(FILE *stream, const char *format, ...);
//fprintf(pf, "hello world");
//fprintf(file, "Integer: %d, Float: %.2f\n", a, b); // 写入格式化数据到文件 
//stream:这是一个指向 FILE 对象的指针,表示要写入数据的文件流。例如,你可以使用 stdout(表示标准输出,通常是屏幕)或者一个通过 fopen 打开的文件流。
//format:这是一个 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的格式标签,这些标签会被随后的附加参数中指定的值所替换,并按需求进行格式化。
//...:这是一个可变参数列表,表示要被插入到 format 字符串中的值。
//主要用于从二进制文件中读取数据。
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
//fread(buffer, sizeof(char), sizeof(buffer), fp) // 读取数据到缓冲区
//ptr:这是一个指向要读取数据的缓冲区的指针,即数据将被读取到这块内存中。
//size:每个数据项的字节数。
//count:要读取的数据项数目。
//stream:指向文件流的指针,通常是通过 fopen 函数打开的文件。
//主要用于向指定的文件中写入数据块
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
//fwrite(str, sizeof(char), n, fp)
//ptr:这是一个指向要写入文件的数据的指针。这通常是一个指向要写入文件的数组或结构体等的指针。
//size:这是每个数据项的大小(以字节为单位)。这决定了从 ptr 指向的内存块中每次读取多少个字节写入文件。
//nmemb:这是要写入的数据项的数量。这决定了从 ptr 指向的内存块中读取多少次数据并写入文件。
//stream:这是一个指向 FILE 对象的指针,该 FILE 对象指定了要写入数据的文件流。这个文件流通常是通过 fopen 函数打开的。

scanf/fscanf/sscanf
printf/fprintf/sprintf

scanf   从标准输入流读取格式化的数据
printf   向标准输出流写格式化的数据

fscanf    适用于所有输入流的格式化输入函数
fprintf    适用于所有输出流的格式化输出函数

sscanf   从字符串中读取格式化的数据
sprintf   将格式化的数据,转换成字符串

//用于从字符串中读取格式化的输入
int sscanf(const char *str, const char *format, ...);
//sscanf(str, "Age: %d, Salary: %f", &age, &salary)
//str:要从中读取数据的字符串。
//format:格式控制字符串,定义了如何解析输入字符串中的数据。它包含了一个或多个转换说明符,每个转换说明符都指定了一种类型的输入数据,并定义了如何从输入字符串中读取和转换这些数据。
//...:这是一个可变参数列表,对应于 format 字符串中的转换说明符,提供了用于存储从字符串中读取的数据的变量的地址。

//sscanf 函数返回成功读取并赋值的变量数。如果遇到文件结束符或读取错误,则返回 EOF。

int main() {
    char str[] = "123 4.567 hello";
    int x;
    float y;
    char z[20];
    sscanf(str, "%d %f %s", &x, &y, z);
    printf("x = %d, y = %f, z = %s\n", x, y, z);
    return 0;
}
//x = 123, y = 4.567000, z = hello

int main() {
	char str[] = "123 4.567 hello";
	int x;
	float y;
	char z[20];
	sscanf(str, "%*d %f %s", &y, z);
	std::cout << y << std::endl;
	std::cout << z << std::endl;
// y = 4.567, z = "hello"
}
//*的含义是指定该参数不进行解析存储,也就是说,只会跳过指定的字符串,而不会去解析并保存
//用于将格式化的数据写入字符串
int sprintf(char *str, const char *format, ...);
//sprintf(buffer, "整数: %d, 浮点数: %.2f", a, b)
//str:这是一个指向字符数组的指针,即目标字符串,sprintf 将把格式化的结果存储在这个字符串中。这个数组应该足够大,以容纳整个格式化的字符串,包括终止的空字符(\0)。
//format:这是一个 C 字符串,包含了要被写入到 str 的文本。它可以包含嵌入的格式标签,这些标签会被随后的附加参数中指定的值替换,并按需求进行格式化。
//...(可变参数):根据 format 字符串中的格式标签,sprintf 需要可变数量的附加参数,每个参数都包含了一个要被插入的值。

//sprintf 返回写入的字符数(不包括终止的空字符)。如果发生错误,返回值是一个负数。

int main() {
    char buffer[100]; // 创建一个足够大的缓冲区来存储格式化后的字符串
    int num = 42;
    double pi = 3.14159;
    const char* name = "Alice";

    // 使用sprintf将不同类型的变量格式化后写入缓冲区
    int len = sprintf(buffer, "Number: %d, PI: %.2f, Name: %s", num, pi, name);

    // 输出缓冲区内容
    printf("Formatted string: %s\n", buffer);
    printf("Characters written: %d\n", len); // len返回写入缓冲区的字符数,不包括'\0'

    return 0;
}

三、文件的随机读写

3.1 fseek

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

//用于重新定位文件内部位置指针。它允许你将文件指针移动到文件的任意位置,以便进行读取或写入操作。
int fseek ( FILE * stream, long int offset, int origin );
//fseek(file, 0, SEEK_END)
//stream:这是一个指向 FILE 对象的指针,表示要操作的文件。通常,这个指针是通过 fopen 函数获得的。
//offset:这是一个长整型值,表示相对于 origin 指定的位置的偏移量(以字节为单位)。正数表示向后移动,负数表示向前移动。
//origin:这是一个整型值,指定了从哪里开始计算偏移量。它可以是以下三个常量之一:
//SEEK_SET:文件开头(偏移量从文件开头计算)。
//SEEK_CUR:当前位置(偏移量从当前位置计算)。
//SEEK_END:文件末尾(偏移量从文件末尾计算)。

//如果函数执行成功,返回值为 0。如果执行失败(例如,文件未打开、偏移量超出文件大小等),返回值为非 0 值,并设置全局变量 errno 以指示错误原因。

3.2 ftell

返回文件指针相对于起始位置的偏移量

//用于获取当前文件指针的位置。它返回文件位置指针当前位置相对于文件首的偏移字节数。这个偏移量以长整型(long)的形式表示,通常用于在随机方式存取文件时确定文件的当前位置。
long int ftell ( FILE * stream );
//ftell(fp)
//stream:这是一个指向 FILE 对象的指针,表示要操作的文件。这个指针通常是通过 fopen、freopen 或其他文件打开函数获得的。

//如果函数执行成功,ftell 返回一个长整型值,表示当前文件指针相对于文件开头的偏移量(以字节为单位)。
//如果出现错误(例如,文件未打开或无法确定文件位置),ftell 返回 -1L,并设置全局变量 errno 以指示错误原因。

3.3 rewind

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

//用于将文件内部的位置指针重新指向文件的开头。这通常在你需要再次从头开始读取或写入文件时使用。
void rewind ( FILE * stream );
//rewind(fp)
//stream:这是一个指向 FILE 对象的指针,表示要操作的文件。这个指针通常是通过 fopen、freopen 或其他文件打开函数获得的。

//rewind 函数没有返回值(即返回类型为 void)。

四、文本文件和二进制文件

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

五、文件读取结束的判定

5.1 被错误使用的feof

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

  1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
  2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数(fread)

六、文件缓冲区

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

在这里插入图片描述
  因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。

  • 13
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值