C语言——文件操作

目录

1 文件概述

2 文件的打开和关闭

2.1 文件指针

2.2 文件的打开与关闭

2.3 文件的顺序读写

2.3.1 顺序读写函数

2.3.2 fputc() & fgetc() 

 2.3.3 fputs() & fgets()

 2.3.4 fscanf() & fprintf()

2.4 文件的随机读写

2.4.1 fseek

2.4.2 ftell

2.4.3 rewind

2.5 文件读取结束的判定

2.5.1 文件流的“状态”

2.5.2 文件读取结束的判定

2.6 文件缓冲区


1 文件概述

【文件分类】

从文件功能角度对文件进行分类,通常分为:程序文件、数据文件。

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

数据文件:数据文件不是代码文件,而是包含程序运行时需要处理或存储的数据的文件。比如程序运行需要从中读取数据的文件,或者输出内容的文件。

【文件标识】

文件名包含3部分:文件路径+文件名主干+文件后缀。例如:c:\code\test.txt

文件标识通常被称为文件名。

2 文件的打开和关闭

2.1 文件指针

        每个被使用的文件都在内存中开辟了一个相应的文件信息区,用于存放文件的相关信息(比如:文件的名字、状态及当前位置)。这些信息保存在一个结构体变量中。该结构体由系统声明,名为FILE

FILE* pf;   
//pf是一个指向FILE类型数据的指针变量

        每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构变量,并填充其中的信息。但使用者不需要关心细节,只需要通过FILE指针维护该结构体变量

        上述代码中,pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。

2.2 文件的打开与关闭

//打开文件
FILE* fopen(const char* filename, const char* mode);
//                       文件名              打开方式
//返回类型是FILE*,要用指针接收 
                          
//关闭文件
int fclose(FILE* stream);  //成功时返回 0,失败时返回 EOF(通常是 -1)

【常用打开方式】

文件使用方式含义if 目标文件不存在
"r"      只读出错
"w"    只写创建一个新文件
"a"      再文本文件末尾追加创建一个新文件

【文件操作示例】 

int main() {
	FILE* pf = fopen("test.txt", "w");  //位置:当前文件所在目录内
	if (pf == NULL) {   //打开失败会返回空指针
		perror("fopen");
		return 1;
	}
	//文件相关操作
	// ......
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

2.3 文件的顺序读写

2.3.1 顺序读写函数

功能函数名适用范围
字符输入函数fgetc所有输入流
字符输出函数fputc

所有输出流

文本行输入函数fgets所有输入流
文本行输出函数fputs

所有输出流

格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件
二进制输出fwrite文件

输入流】:数据从外部流入程序(如:键盘输入等)

输出流】:数据从程序流向外部(如:数据打印在显示器等)

2.3.2 fputc() & fgetc() 

fputc() 和 fgetc() 是C标准库中的两个文件操作函数,用于处理文件中的单个字符读写操作。

【fputc()函数】:用于将单个字符写入指定文件。 

int fputc(int character, FILE *stream);
  • character:要写入的字符(实际是unsigned char类型值,这里转换成int表示);
  • stream:文件指针,指向被操作的文件;
  • 返回值:成功——返回写入的字符;失败:返回EOF。
int main() {
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL) {
		perror("Error opening file");
		return 1;
	}

	for (int i = 0; i < 26; i++) {
		fputc('a' + i, pf);
		fputc(' ', pf);
	}

	fclose(pf);
	pf = NULL;

	return 0;
}

 程序运行结果:

 【fgetc()函数】:从指定文件读取单个字符

int fgetc(FILE *stream);
  • stream:文件指针,指向代操作文件;
  • 返回值:成功——返回读取的字符,失败——返回EOF。
int main() {
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL) {
		perror("Error opening file");
		return 1;
	}

	int ch = 0;  //定义变量接收读取到的字符
	while ((ch = fgetc(pf)) != EOF) {
    //fgetc读取成功后,文件指针会向后移动,指向下一个字符
		printf("%c", ch);
	}


	fclose(pf);
	pf = NULL;

	return 0;
}

 程序运行结果:a b c d e f g h i j k l m n o p q r s t u v w x y z

 2.3.3 fputs() & fgets()

fputs() 和 fgets() 函数用于处理文件中的字符串(写入 or 读取)。

int main() {
	FILE* writef = fopen("test2.txt", "w");
	if (writef == NULL) {
		perror("Error opening file");
		return 1;
	}

	fputs("HELLO WORLD\n", writef);
	fputs("HELLO C\n", writef);

	fclose(writef);
	writef = NULL;

	//读取文件
	FILE* readf = fopen("test2.txt", "r");
	if (readf == NULL) {
		perror("Error opening file");
		return 1;
	}

	char arr[100];
	while (fgets(arr, 100, readf) != NULL) {
		//一次读取100个字符,直到文件结束
		printf("%s", arr);
	}

	fclose(readf);
	readf = NULL;

	return 0;
}

 程序运行结果:

fputs() 和 fgets() 与 fputc() 和 fgetc() 功能类似,但有以下几点区别:

  • fputs() 和 fgets()处理字符串;
  • fgets() 读取失败或读取完成返回NULL,而非EOF;
  • fgets() 如果一次读取的内容长度小于文件内容长度,会在读取结束位置补上'\0'。(如:fgets(arr, 10, readf);只会读取到前9个字符,最后一个补上'\0')

 2.3.4 fscanf() & fprintf()

【fscanf()函数】:从指定文件流中读取数据,并按照指定格式进行解析,然后存储到相应变量中。

int fscanf(FILE *stream, const char *format, ...);
  • stream:代操作文件指针
  • format:指定格式
  • 返回值:操作成功——返回输入项数量,操作失败/错误/文件结束——返回EOF

 【fprintf()函数】:将格式化数据写入到文件中。

int fprintf(FILE *stream, const char *format, ...);
  • stream:代操作文件指针
  • format:指定格式
  • 返回值:操作成功——返回写入的字符输出,操作失败——返回负值
struct Stu
{
	char name[20];
	int age;
	float score;
};

int main() {
	struct Stu s1 = { "haha",18,99.92 };
	struct Stu s2 = { "xixi",20,96.57 };

	FILE* pf = fopen("test3.txt", "w");
	if (pf == NULL) {
		perror("Error opening file");
		return 1;
	}

	//格式化数据写入文件
	fprintf(pf, "%s %d %f\n", s1.name, s1.age, s1.score);
	fprintf(pf, "%s %d %f\n", s2.name, s2.age, s2.score);

	rewind(pf);  //重置文件指针到文件开头

	//格式化数据读取
	fscanf(pf, "%s %d %f\n", s1.name, &(s1.age), &(s1.score));
	printf("姓名:%s 年龄:%d 分数:%.2f\n", s1.name, s1.age, s1.score);
	fscanf(pf, "%s %d %f\n", s2.name, &(s2.age), &(s2.score));
	printf("姓名:%s 年龄:%d 分数:%.2f\n", s2.name, s2.age, s2.score);

	fclose(pf);
	pf = NULL;

	return 0;
}

程序运行结果: 

2.4 文件的随机读写

2.4.1 fseek

用于在文件中移动文件指针的位置

int fseek(FILE *stream, long offset, int origin);
  •  stream:要操作的文件指针
  • offset:相对位置的偏移量,以字节为单位
  • origin:起始位置
  • fseek() 执行成功返回0,失败返回-1

关于origin的取值:

SEEK_SET:从文件开头位置开始计算偏移量

SEEK_CUR:从当前位置开始

SEEK_END:从文件末尾开始

int main() {
    FILE* pFile = fopen("example.txt", "r");  // 以只读模式打开文件
    if (pFile == NULL) {
        perror("Error opening file");
        return 1;
    }

    // 将文件指针移动到第 9 个字符位置
    fseek(pFile, 9, SEEK_SET);
    int ch = fgetc(pFile);  // 读取该位置的字符
    printf("Character at position 9: %c\n", ch);  // 输出 'n'

    // 将文件指针移动到文件末尾的第一个字符
    fseek(pFile, -1, SEEK_END);
    ch = fgetc(pFile);  // 读取文件的最后一个字符
    printf("Last character in file: %c\n", ch);  // 输出 '.'

    // 将文件指针移动到当前位置的前 3 个字符
    fseek(pFile, -3, SEEK_CUR);
    ch = fgetc(pFile);
    printf("Character at current-3: %c\n", ch);  // 输出 'l'

    fclose(pFile);
    return 0;
}

2.4.2 ftell

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

long int ftell ( FILE * stream );
int main()
{
	FILE* pFile;
	pFile = fopen("example.txt", "w");
	if (pFile == NULL) {
		perror("Error opening file");
		return 1;
	}
	fputs("This is an apple.", pFile);  //T是第0个字符
	fseek(pFile, 9, SEEK_SET);
	printf("%d\n", ftell(pFile));   //9
	fclose(pFile);
	pFile = NULL;
	return 0;
}

2.4.3 rewind

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

void rewind ( FILE * stream );

2.5 文件读取结束的判定

2.5.1 文件流的“状态”

FILE类型指针不仅仅指向文件,还维护文件操作的相关信息,包括文件状态标志

这些标志记录了文件流当前的状态:是否与到文件末尾(EOF)、是否出现错误(如硬盘故障等)。

文件操作相关函数的返回值中会记录不同的文件状态

2.5.2 文件读取结束的判定

因此,在文件读取的过程中,直接使用 feof() 函数判断是否结束并不可靠,更好的方法时检查文件操作函数(如:fgetc()、fgets()等)的返回值,区分具体状态。

feof() 用于确认读取是否因为文件结束而停止

ferror() 用于确认读取是否因为出现错误而停止

【文本文件读取结束判定】

int main(void)
{
    int c; // 注意:int,非char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if (!fp) {
        perror("File opening failed");
        return 1;
    }
    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) 
    {
        putchar(c);
    }
    //判断是什么原因结束的
    if (ferror(fp))
        puts("I/O error when reading");
    else if (feof(fp))
        puts("End of file reached successfully");
    fclose(fp);
}

 【二进制文件读取结束判定】

enum { SIZE = 5 };
int main(void)
{
    double a[SIZE] = { 1.,2.,3.,4.,5. };
    FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
    fclose(fp);
    double b[SIZE];
    fp = fopen("test.bin", "rb");
    size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
    if (ret_code == SIZE) {
        puts("Array read successfully, contents: ");
        for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
        putchar('\n');
    }
    else { // error handling
        if (feof(fp))
            printf("Error reading test.bin: unexpected end of file\n");
        else if (ferror(fp)) {
            perror("Error reading test.bin");
        }
    }
    fclose(fp);
}
  1.  通过fread()返回值与期望读取的数目对比判断是否达读取成功
  2. 结合feof()和ferror()进一步确认状态

2.6 文件缓冲区

【缓冲机制】

  • 写操作:当你向文件中写入数据时,这些数据首先会被写入到内存中的缓冲区,而不是立即写入磁盘。这是为了提高性能,避免频繁的磁盘操作。
  • 读操作:在从文件读取数据时,系统会先将数据从磁盘读取到内存中的缓冲区,然后再从缓冲区传输到程序使用的变量中。
  • 缓冲区的大小根据C编译系统决定的。

【缓冲区优点】

  • 提升性能: 通过减少与磁盘的直接交互,缓冲机制显著提高了文件读写的效率。
  • 减少I/O操作:在很多情况下,程序会一次性读取或写入大量数据,而不是逐字节或逐行进行操作。

 【刷新缓冲区】

  • fclose() :关闭文件时,fclose()  也会自动刷新缓冲区。这意味着在关闭文件之前,所有在缓冲区中的数据都会被写入到磁盘。

因此: 由于缓冲机制,数据并不总是立即写入文件,因此在处理文件时要小心。如果你的程序在写入数据后没有刷新缓冲区或关闭文件,可能导致数据丢失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值