C语言:文件操作

1、为什么使用文件?什么是文件?

        如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以是使用文件。在计算机中,文件是指计算机存储设备(如硬盘、U盘等)上存储的数据集合,可以是文本、图像、音频、视频等各种形式的数据。文件通常由一个文件名和扩展名组成,文件名是文件的主要标识符,扩展名则标识文件的类型。文件可以被打开、编辑、保存、删除等操作。

1.1程序文件

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

1.2数据文件

        数据文件是一种存储结构化或非结构化数据的文件,用于在计算机系统中存储和处理数据。数据文件可以包含各种类型的数据,如文本、数字、图像、音频、视频等。数据文件通常由特定的文件格式或者数据库系统定义的数据结构来组织和存储数据。

        数据文件的后缀可以根据不同的文件格式和用途而有所不同。以下是一些常见的数据文件后缀及其对应的文件格式:

●  .txt:文本文件,通常包含纯文本数据。
●  .csv:逗号分隔值文件,用于存储表格数据,每个数据项由逗号分隔。
●  .xls/.xlsx:Excel文件,用于存储电子表格数据。
●  .json:JSON文件,用于存储结构化数据,以键值对的形式表示。
●  .xml:XML文件,用于存储和传输结构化数据,具有自定义的标签和属性。
●  .sql:SQL文件,用于存储和执行SQL语句,通常用于数据库操作。
●  .db/.sqlite/.mdb:数据库文件,用于存储结构化数据,可通过数据库管理系统进行读写操作。
●  .jpg/.png/.gif:图像文件,用于存储图像数据。
●  .mp3/.wav/.flac:音频文件,用于存储音频数据。
●  .mp4/.avi/.mov:视频文件,用于存储视频数据。

2、二进制文件与文本文件

        根据数据的组织形式,数据文件被称为文本文件或者二进制文件。数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。

2.1数据在内存中的存储

        字符⼀律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。     

测试代码:

#include <stdio.h>

int main()
{
    int a = 10000;
    FILE* fp = fopen("test.txt", "wb"); // 打开名为test.txt的文件,以二进制写入模式打开
    fwrite(&a, sizeof(int), 1, fp); // 将变量a的值写入文件
    fclose(fp); // 关闭文件
    fp = NULL; // 将文件指针置为空
    return 0; // 返回0表示程序执行成功
}

代码执行后我们会在当前文件路下观察到生成的test.txt文件:

我们用记事本(文本形式)Visual Studio 2022(二进制形式)打开test.txt文件

                 

3、文件的打开和关闭

3.1 流和标准流
3.1.1 流

        程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序针对文件、画面、键盘等的数据输入输出操作都是同流操作的。一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。

3.1.2 标准流

        那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?那是因为C语言程序在启动的时候,默认打开了3个流:
• stdin -标准输入流,在大多数的环境中从键盘输入。
• stdout -标准输出流,大多数的环境中输出至显示器界面。
• stderr -标准错误流,大多数环境中输出到显示器界面。
这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作的。
stdin、stdout、stderr三个流的类型是: FILE* ,通常称为文件指针。
C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。

3.2 文件指针

        缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”

        每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名 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* pf; //⽂件指针变量

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

3.3 文件的打开和关闭

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

        ANSIC 规定使用 fopen 函数来打开文件, fclose 来关闭文件。

//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );

mode表示文件的打开模式,下⾯都是文件的打开模式:

举个例子:

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

4、文件的顺序读写

4.1 顺序读写函数介绍

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

举个例子:

5、文件的随机读写

5.1 fseek 

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

int fseek ( FILE * stream, long int offset, int origin );

举个例子:

/* fseek example */
#include <stdio.h>
int main()
{
	FILE* pFile;
		pFile = fopen("example.txt", "wb");
	fputs("This is an apple.", pFile);
	fseek(pFile, 9, SEEK_SET);
	fputs(" sam", pFile);
	fclose(pFile);
	return 0;
}

5.2 ftell

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

long int ftell ( FILE * stream );

举个例子:

/* ftell example : getting size of a file */
#include <stdio.h>
int main ()
{
FILE * pFile;
long size;
pFile = fopen ("myfile.txt","rb");
if (pFile==NULL)
perror ("Error opening file");
else
{
fseek (pFile, 0, SEEK_END); // non-portable
size=ftell (pFile);
fclose (pFile);
printf ("Size of myfile.txt: %ld bytes.\n",size);
}
return 0;
}

我们可以看到,ftell函数返回的值是17。这是因为example.txt文件中有13个字符(包括空格和逗号),加上文本文件的换行符(在Windows中为"\r\n",在Unix和Linux中为"\n"),以及文件结束符(EOF),总共为17个字符。因此,ftell函数返回的值是文件指针的当前位置,即文件的大小。

5.3 rewind

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

void rewind ( FILE * stream );

举个例子:

/* rewind example */
#include <stdio.h>
int main ()
{
    int n;
    FILE * pFile;
    char buffer [27];
    pFile = fopen ("myfile.txt","w+");
    for ( n='A' ; n<='Z' ; n++)
        fputc ( n, pFile);
    rewind (pFile);
    fread (buffer,1,26,pFile);
    fclose (pFile);
    buffer[26]='\0';
    printf(buffer);
    return 0;
}

这段代码会创建一个名为`myfile.txt`的文件,并将字母'A'到'Z'写入该文件。然后,它使用`rewind`函数将文件指针重新定位到文件开头,并使用`fread`函数从文件中读取26个字节到`buffer`数组中。最后,它关闭文件,将`buffer`数组的最后一个元素设置为`\0`,并使用`printf`函数打印`buffer`数组中的内容。

6、文件读取结束的判定

6.1 被错误使用的 feof

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

1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:

        • fgetc 判断是否为 EOF

        • fgets 判断返回值是否为 NULL .

2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

例如:
        • fread判断返回值是否小于实际要读的个数。

6.2 ferrorfeof函数

ferror是一个C标准库函数,用于检查文件操作是否发生了错误。它的函数原型如下:

int ferror(FILE *stream);

参数stream是一个指向FILE对象的指针,它表示要检查错误的文件流。

ferror函数返回一个非零值(通常是1)表示发生了错误,返回0表示没有发生错误。

      

feof是一个C标准库函数,用于检查文件流是否已经到达文件末尾。它的函数原型如下:

int feof(FILE *stream);

参数stream是一个指向FILE对象的指针,它表示要检查文件流的末尾。

feof函数返回一个非零值(通常是1)表示文件流已经到达了文件末尾,返回0表示文件流还没有到达文件末尾。

举例:

文本文件的例子

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

二进制文件的例子:

#include <stdio.h>
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);
}

7、文件缓冲区

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

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

8、结语

        通过本篇文章的学习,你已经掌握了如何在C语言中进行文件操作,包括文件的创建、打开、读写、关闭、删除和重命名等操作。文件操作是编程中非常常用的一项功能,可以帮助我们将数据保存到文件或从文件中读取数据,方便数据的持久化存储和共享。

在实际应用中,我们需要注意以下几点:

  1. 文件操作时需要保证文件存在并且有正确的访问权限,否则会出现错误。
  2. 在进行文件读写操作时,需要注意文件指针的位置,否则数据可能会被覆盖或读取错误。
  3. 在进行文件操作时,需要及时关闭文件,避免文件句柄被占用,导致其他进程无法访问文件。

希望本篇文章对你有所帮助,如有不足之处,希望大家能在评论区给博主指正。

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值