C语言文件操作详解(上)

目录

一、文件简介

1.数据文件

2.文件名

二、文件指针

三、文件的打开与关闭

1. fopen()函数

函数原型

参数与返回值

2. fclose()函数

函数原型

参数与返回值

3. fopen()与fclose()的使用示例

(1)使用相对路径打开文件

(2)使用绝对路径打开文件

四、文件的顺序读写

1. fgetc()    字符输入函数

函数原型

参数与返回值

2. fputc()    字符输出函数

函数原型

参数与返回值

3. fgetc() 与 fputc() 的使用

(1)fputc()写入单个字符

(2)在文件中fputc()入26个字符 

(3)用fgetc()从文件中读入26个字母

4.fgets()    文本行输入函数 

函数原型

参数和返回值

5.fgets()函数的使用

(1)fgets()从文件中读取num-1个字符的情况

6.fputs()    文本行输出函数 

函数原型

参数和返回值

 

7.fputs()的使用

8.fscanf()    格式化输入函数

函数原型

返回值

9.fprintf()    格式化输出函数

函数原型

10.fscanf()与fprintf()函数的使用

(1)读写一个结构体中的数据

(2)读取一个结构体的数据

11.fread()    二进制输入

函数原型

参数

12.fwrite()   二进制输出

函数原型

参数

13.fread()与fwrite()的使用

(1)二进制的写文件

(2)二进制的读文件

五、输入输出流的补充


一、文件简介

1.数据文件

利用文件存储数据是实现数据持久化方式的一种。文件可以分为以下两类:

  • 程序文件:包括源程序文件(.c),目标文件(windows环境下后缀为.obj),可执行程序(windows环境下后缀为.exe)
  • 数据文件:文件的内容不一定是程序,而是程序运行时读写的数据。比如程序运行需要从中读取数据的文件或输出内容的文件。

以往许多情况下,我们C程序的输入输出是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。

而有时我们也会把信息输出到磁盘上,需要时再把磁盘上的数据读入到内存中,这时我们就需要处理磁盘上的数据文件


2.文件名

文件名是一个文件唯一的文件标识,包含三部分:

文件路径、文件名主干、文件后缀

如        E:\Assignment\C++\cpp_practice\test.txt

C语言中我们经常要用到文件名来对文件进行操作。 


二、文件指针

可以简单的理解为,C语言代码中,用文件指针来对文件进行维护。

每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的相关信息。如文件名、文件状态以及文件的当前位置等等。而文件信息区实际上是一个结构体变量。该结构体类型由系统自动声明,每当打开一个文件时,系统会根据文件的情况自动创建一个FILE结构的变量并填充其中的信息,这个结构体变量取名为FILE

一般通过一个FILE指针来维护FILE结构体变量,这个指针就是文件指针。通过文件信息区中的信息就能访问该文件,因此通过文件指针变量,能够找到与它相关联的文件。

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


三、文件的打开与关闭

文件在读写之前,应先打开文件,在使用结束之后应关闭文件。就像一个装东西的瓶子,使用前必须先打开瓶盖,用完之后要关上瓶盖,否则无法对瓶内的东西进行操作。

编写程序时,在打开文件的同时会返回一个FILE*的指针变量指向该文件,相当于建立了指针和文件的关系。

使用fopen()函数来打开文件。

1. fopen()函数

fopen()函数的API文档:fopen - C++ Reference

函数原型

FILE * fopen ( const char * filename, const char * mode );

C 库函数 FILE *fopen(const char *filename, const char *mode) 使用给定的模式 mode 打开 filename 所指向的文件。 

参数与返回值

  • filename -- 字符串,表示要打开的文件名称或路径。

直接写文件名称,其实就是文件的相对路径,默认在工程文件同一文件夹下创建文件。如:fopen("myFile.txt","w"); 若"myFile"文件不存在,则运行后会在该项目工程文件夹下自动创建该文件。

也可以写绝对路径:fopen("c:\\code\\myFile.txt","w");  注意文件名字符串中的双斜杠\\,第一个 \ 是转义字符的作用。

此外,文件名中不一定要包含后缀,可以写"test"也可以写"test.txt"。且有一些字符是文件名中禁止使用的,包括\/:*?"<>|

  • mode -- 字符串,表示文件的访问模式,可以是以下表格中的值(注意一定要用双引号):

注意:若以"w"的模式打开一个已经写过的文件进行写入,原内容会被清空。而用追加"a"可以避免这个问题。 

  • 返回值:如果文件成功打开,则该函数返回一个指向该文件的文件信息区的 FILE 指针;否则则返回 NULL。因此,在每一次打开文件后,我们都需要对fopen()的返回值进行检查,判断它是否为NULL。 

2. fclose()函数

fclose()函数的API文档:fclose - C++ Reference

函数原型

int fclose(FILE *stream);

C 库函数 int fclose(FILE *stream) 关闭流 stream。刷新所有的缓冲区。

参数与返回值

  • 参数即指向要关闭文件的文件指针,类型为FILE *。
  • 返回值:如果文件流被成功关闭,则返回0;如果失败,则返回EOF。

注意:fclose()调用后,文件指针pFile不会被置为NULL!此时pFile是一个野指针。要避免野指针错误,需手动在关闭文件流后置空文件指针:pFile = NULL;

虽然在进程结束后,文件流也会自动关闭。但fopen()与fclose()仍应该成对出现,在打开文件后要有关闭文件的操作,并在fopen()调用后对其返回值进行NULL检查。这一点与动态内存管理函数malloc等非常相似。 


3. fopen()与fclose()的使用示例

(1)使用相对路径打开文件

示例代码如下。首先,在代码运行前,我的工程文件夹下"test_tjfz.txt"文件并不存在。

运行该代码,由于我选择的打开方式为"w",因此当目录下文件不存在时,系统将会自动建立一个名为"test_tjfz.txt"的新文件。

#include<stdio.h>

//文件操作
int main()
{
	FILE* pf = NULL;
	//"w" 写
	pf = fopen("test_tjfz.txt", "w");
    //对文件指针进行检查
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

    //写文件...

    //关闭文件
	fclose(pf);
	pf = NULL;	//pf置空,避免野指针错误

	return 0;
}

 此时,文件夹下出现了"test_tjfz.txt"文件:

(2)使用绝对路径打开文件

int main()
{
	//打开文件
	//相对路径
	//FILE* pf = fopen("test.txt", "w");

	//绝对路径
	FILE* pf = fopen("E:\\code\\test.txt", "w");

	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}

	//写文件...

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

	return 0;
}

代码运行前后效果如下: 


四、文件的顺序读写

文件的读写分为顺序读写与随机读写,这里先介绍顺序读写的几种读写函数。

1. fgetc()    字符输入函数

fgetc()函数的API文档:fgetc - C++ Reference

函数原型

int fgetc ( FILE * stream );

C 库函数 int fgetc(FILE *stream) 从指定的文件流 stream 中获取下一个字符(一个无符号字符),并把位置标识符往前移动。 

参数与返回值

  • stream -- 一个指向 FILE 对象的指针,标识了要从哪个文件流向内存输入字符。
  • 返回值:如果成功读入,则该函数返回所读取的字符值(以无符号 char 强制转换为 int 的形式返回读取的字符);如果到达文件末尾或发生读错误,则返回 EOF。

2. fputc()    字符输出函数

fputc()函数的API文档:fputc - C++ Reference

函数原型

int fputc ( int character, FILE * stream );

C 库函数 int fputc(int character, FILE *stream) 把参数 character 指定的字符(一个无符号字符)写入到指定的文件流 stream 中,并把位置标识符往前移动。 

参数与返回值

  • character -- 一个要被写入的字符。该字符以其对应的 int 值进行传递。
  • stream -- 指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的文件流。
  • 返回值:如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

3. fgetc() 与 fputc() 的使用

(1)fputc()写入单个字符

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写入字符'a'
	fputc('a', pf);

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

结果如下,在相应的文件中,字符'a'被成功写入。

如果此时我们用一个变量接收fputc()函数的返回值,则可以发现其返回值正是被写入字符的ASCII码值(int类型):


(2)在文件中fputc()入26个字符 

int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

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

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

 效果如下:


(3)用fgetc()从文件中读入26个字母

可以采用循环的方式进行读取。

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		int ch = fgetc(pf);
		printf("%c ", ch);
	}

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

	return 0;
}

但若不知道一共要从文件中读取打印多少个字符,怎么办呢?

从fgetc()函数的返回值可知,当到达文件末尾时,将返回EOF。因此可以用EOF作读取结束标志,当读取到的字符等于EOF时,才打印:

int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
	printf("%c ", ch);
}

4.fgets()    文本行输入函数 

fgets()函数的API文档:fgets - C++ Reference

函数原型

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

C 库函数 char *fgets(char *str, int num, FILE *stream) 从指定的文件流 stream 读取一行,并把它存储在 str 所指向的字符串内。

当读取 (num-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。 

  • 当要读取的字符个数num比文件流中一行总字符数小或相等时,读取的是文件流该行中的(num-1)个字符。
  • 当要读取的字符个数num比文件流中一行总字符大时,读取的是文件流该行中的全部字符。

参数和返回值

  • str -- 指向一个字符数组的指针,该数组存储了要读取的字符串。
  • num -- 要读取的最大字符数(包括最后的空字符'\0')。通常是使用以 str 传递的数组长度。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的文件流。

5.fgets()函数的使用

(1)fgets()从文件中读取num-1个字符的情况

下列代码中,arr数组用于接收读到的字符。为了观察更清晰,我们预先在其中放入10个#号。

fgets(arr,5,pf);语句意思是从pf指向的文件中读取5个字符,存入arr中。新存入的字符会覆盖arr中原有的字符。 

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}

	//读文件-一行一行读
	//10个#
	char arr[] = "##########";
	fgets(arr, 5, pf);
	printf("%s", arr);
	
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

在代码运行前,test.txt文件中的内容如下:

abcdefg\nhijklmnopqrstuvwxyz

运行后,屏幕上显示arr数组内的内容:

如图,虽然我们要输入的num为5,但实际只显示出4个字符。通过调试我们可以发现,是因为系统在读入的时候,自动将在第4个字符后添加了一个'\0':

第5个字符为'\0'

因此,当num小于等于该行字符数时,实际存入arr的是该行前(num-1)个字符,最后一个字符为系统自动追加的'\0'。 而若令要存入的num数大于该行字符总数,则该行字符会被全部读取,包括末尾的'\n'。并且在读完后,还是会在末尾追加'\0'

一行共8个字符,但会另外追加一个'\0'在arr数组中

6.fputs()    文本行输出函数 

fputs()函数的API文档:fputs - C++ Reference

函数原型

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

C 库函数 int fputs(const char *str, FILE *stream) 把字符串写入到指定的文件流 stream 中,但不包括空字符。

参数和返回值

  • str -- 一个数组,包含了要写入的以空字符终止的字符序列。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的文件流。
  • 返回值 -- 若成功,返回一个非负值;若发生错误,则返回 EOF。

7.fputs()的使用

打印两行,注意要在字符串中加入换行符'\n',否则并不能实现在文件中显示两行的效果。 

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}

	//写文件-一行一行写
	fputs("hello\n", pf);
	fputs("world!!!!\n", pf);

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

	return 0;
}

若去除'\n',就不分行了

  


8.fscanf()    格式化输入函数

fscanf()函数的API文档:fscanf - C++ Reference

函数原型

int fscanf ( FILE * stream, const char * format, ... );

C 库函数 int fscanf(FILE *stream, const char *format, ...) 从文件流 stream 读取格式化输入。 

返回值

  • 如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

该函数的用法与scanf函数一致,只是fscanf()函数在参数部分多了一个文件指针用于标识要输入的文件流。


9.fprintf()    格式化输出函数

fprintf()函数的API文档:fprintf - C++ Reference

函数原型

int fprintf ( FILE * stream, const char * format, ... );

该函数的用法与printf函数也一致,只是fprintf函数在参数部分多了一个文件指针用于标识要输出到的文件流。


10.fscanf()与fprintf()函数的使用

(1)读写一个结构体中的数据

 先创建一个结构体的数据:

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

把结构体中的一些数据写到文件中: 

int main()
{
	struct S s = { "zhangsan", 20, 95.5f };
	//把s中的数据写到文件中
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}

	//写文件
	fprintf(pf, "%s %d %.1f", s.name, s.age, s.score);

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

运行后效果如下:


(2)读取一个结构体的数据

 依旧是上述结构体,在写入数据后,我们再将它读取到内存中:

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

int main()
{
	struct S s = {0};
	//把s中的数据写到文件中
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		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;
}

此时在控制台中也能成功打印出文件内容: 


11.fread()    二进制输入

fread()函数的API文档:fread - C++ Reference

函数原型

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

从给定文件流 stream 读取数据到 ptr 所指向的数组中。 

参数

  • ptr -- 指向带有最小尺寸 size*nmemb 字节内存块的指针。
  • size -- 要读取的每个元素的大小,以字节为单位。
  • nmemb -- 元素个数,每个元素的大小为 size 字节。
  • stream -- 指向 FILE 对象的指针,该 FILE 对象指定了一个输入文件流。

12.fwrite()   二进制输出

fwrite()函数的API文档:fwrite - C++ Reference

函数原型

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

把 ptr 所指向的数组中的数据写入到给定文件流 stream 中。 

参数

  • ptr -- 这是指向要被写入的元素数组的指针。
  • size -- 这是要被写入的每个元素的大小,以字节为单位。
  • nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出文件流。

13.fread()与fwrite()的使用

(1)二进制的写文件

写一个结构体数据到文件中。注意文件打开模式为"wb"。

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

int main()
{
	struct S s = { "zhangsan", 20, 95.5f };
	//把s中的数据写到文件中
	FILE*pf = fopen("test.txt", "wb");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	//二进制的写文件
	fwrite(&s, sizeof(s), 1, pf);

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

(2)二进制的读文件

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

int main()
{
	struct S s = {0};
	//把s中的数据写到文件中
	FILE* pf = fopen("test.txt", "rb");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	//二进制的读文件
	fread(&s, sizeof(s), 1, pf);

	printf("%s %d %f\n", s.name, s.age, s.score);

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

五、输入输出流的补充

对于任何一个C程序,只要运行起来就默认打开三个流:

stdin -- 标准输入流 -- 键盘

stdout -- 标准输出流 -- 屏幕

stderr -- 标准错误流 -- 屏幕

上面介绍到,除了fread()与fwrite()适用于文件流以外,其余的顺序读写函数适用于所有输入流。因此,它们也可以起到scanf()与printf()的作用,如:

fgetc(stdin);
fputc(ch,stdout);

在需要填入一个文件指针的地方,将参数换为stdin或stdout即可。

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值