【C语言】文件的输入与输出

在此之前,我极少使用C语言处理文件。因为我认为使用Python、matlab处理文件是及其方便的。

事实果真如此吗?

文章目录

  • 一、与文件进行通信
    • 1.1 文件的定义
    • 1.2 文本文件和二进制文件
    • 1.3 底层 I/O 和 标准I/O
    • 1.4 标准文件
    • 1.5 标准 I/O
  • 二、文件的打开和关闭:fopen()、fclose
    • 2.1 打开一个文件:fopen()函数
    • 2.2 getc()函数和putc函数(文件的字符处理)
    • 2.3 文件结尾:EOF
    • 2.4 关闭一个打开的文件:fclose()函数
    • 2.5 指向标准文件的指针
    • 2.6 例程
  • 三、文件I/O:fprintf()、fscanf()、fgets()、fputs()
    • 3.1 fscanf()和fprintf()函数
    • 3.2 fgets()和fputs()
  • 四、定位函数fseek()和ftell()
  • 五、标准I/O的原理

一、与文件进行通信

1.1 文件的定义

文件(file)通常是在磁盘或固态硬盘上的一段已命名的存储区。对我们而言,stdio.h就是一个文件的名称,该文件中包含一些有用的信息。然而,对操作系统而言,文件更复杂一些。例如,大型文件会被分开储存,或者包含一些额外的数据,方便操作系统确定文件的种类,这都是操作系统所关心的,程序员关心的是C程序如何处理文件。

C把文件看作是一系列连续的字节,每个字节都能被单独读取。这与UNIX环境中的文件结构相对应。由于其他环境中可能无法完全对应这个模型,C提供两种文件模式:文本模式和二进制模式

1.2 文本文件和二进制文件

需要区分:文本内容和二进制内容文本文件格式和二进制文件格式以及文件的文本模式和二进制模式

所有文件的内容都以二进制形式(0或1)储存(即在物理层面上,文本文件和二进制文件没有区别)。

如果文件最初使用二进制编码的字符(例如,ASCII或Unicode)表示文本(就像C字符串那样),该文件就是文本文件,其中包含文本内容。

如果文件中的二进制值代表机器语言代码或数值数据(使用相同的内部表示,假设,用于long或double类型的值)或图片或音乐编码,该文件就是二进制文件,其中包含二进制内容。

比如 bmp文件,它开始的部分是文件头信息,前2个字节表示文件格式为BMP格式,接着的 8个字节表示文件的长度,再接着的4个字节表示 bmp文件头的长度。从中可以看出,解析bmp文件时是不定长度的,2、4、8字节长度的都有。因此bmp文件是二进制文件。

不同系统在文件处理方面有着不同的标准(如换行符在不同系统中可以是:\n,\r,\r\n),为了规范文本文件的处理,C提供两种访问文件的途径:二进制模式和文本模式。

  • 在二进制模式中,程序可以访问文件的每个字节。

  • 在文本模式中,程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时,本地环境表示的行末尾或文件结尾映射为C模式

例如:

  • C程序在旧式Macintosh中以文本模式读取文件时,把文件中的\r转换成\n;以文本模式写入文件时,把\n转换成\r。

  • 或者,C文本模式程序在MS-DOS平台读取文件时,把\r\n转换成\n;写入文件时,把\n转换成\r\n。在其他环境中编写的文本模式程序也会做类似的转换。

  • 除了以文本模式读写文本文件,还能以二进制模式读写文本文件。如果读写一个旧式MS-DOS文本文件,程序会看到文件中的\r和\n字符,不会发生映射。如果要编写旧式Mac格式、MS-DOS格式或UNIX/Linux格式的文件模式程序,应该使用二进制模式,这样程序才能确定实际的文件内容并执行相应的动作。

虽然C提供了二进制模式和文本模式,但是这两种模式的实现可以相同。前面提到过,因为UNIX使用一种文件格式,这两种模式对于UNIX实现而言完全相同。Linux也是如此。

1.3 底层 I/O 和 标准I/O

除了选择文件的模式,大多数情况下,还可以选择I/O的两个级别(即处理文件访问的两个级别)。

底层I/O(low-level I/O)使用操作系统提供的基本I/O服务

标准高级I/O(standardhigh-level I/O)使用C库的标准包和stdio.h头文件定义。因为无法保证所有的操作系统都使用相同的底层I/O模型,C标准只支持标准I/O包。有些实现会提供底层库,但是C标准建立了可移植的I/O模型,本文主要讨论这些I/O。

1.4 标准文件

C程序会自动打开3个文件,它们被称为标准输入(standard input)、标准输出(standard output)和标准错误输出(standard error output)

  • 在默认情况下,标准输入是系统的普通输入设备,通常为键盘;

  • 标准输出和标准错误输出是系统的普通输出设备,通常为显示屏。

  • 通常,标准输入为程序提供输入,它是getchar()和scanf()使用的文件。

  • 程序通常输出到标准输出,它是putchar()、puts()和printf()使用的文件。

  • 重定向把其他文件视为标准输入或标准输出

标准错误输出提供了一个逻辑上不同的地方来发送错误消息。例如,如果使用重定向把输出发送给文件而不是屏幕,那么发送至标准错误输出的内容仍然会被发送到屏幕上。这样很好,因为如果把错误消息发送至文件,就只能打开文件才能看到。

1.5 标准 I/O

与底层I/O相比,标准I/O包除了可移植以外还有两个好处。

  1. 第一,标准I/O有许多专门的函数简化了处理不同I/O的问题。例如,printf()把不同形式的数据转换成与终端相适应的字符串输出。
  2. 第二,输入和输出都是缓冲的。(一次转移一大块信息而不是一字节信息(通常至少512字节))

例如,当程序读取文件时,一块数据被拷贝到缓冲区(一块中介存储区域)。这种缓冲极大地提高了数据传输速率。程序可以检查
缓冲区中的字节。缓冲在后台处理,所以让人有逐字符访问的错觉(如果使用底层I/O,要自己完成大部分工作)

二、文件的打开和关闭:fopen()、fclose

2.1 打开一个文件:fopen()函数

函数原型:

 FILE* fopen(char const* _FileName,char const* _Mode
  • 第一个参数:文件名;
  • 第二个参数:打开的模式;
  • 返回文件指针,即指向FILE的指针,FILE是一个定义在stdio.h中的派生类型。文件指针fp并不指向实际的文件,它指向一个包含文件信息的数据对象,其中包含操作文件的I/O函数所用的缓冲区信息。

第二个参数值指定以什么模式打开文件,是一个字符串,可取的值有:

模式字符串 含义
“r” 以读模式打开文件
“w” 以写模式打开文件,把现有文件的长度截为0,如果文件不存在,则创建一个新文件
“a” 以写模式打开文件,在现有文件末尾添加内容,如果文件不存在,则创建一个新文件
“r+” 以更新模式打开文件(即可以读写文件)
“w+” 以更新模式打开文件(即,读和写),如果文件存在,则将其长度截为0;如果文件不存在,则创建一个新文件
“a+” 以更新模式打开文件(即,读和写),在现有文件的末尾添加内容,如果文件不存在则创建一个新文件;可以读整个文件,但是只能从末尾添加内容
“rb”、“wb”、“ab”、“rb+”、“r+b”、“wb+”、“w+b”、“ab+”、“a+b” 与上一个模式类似,但是以二进制模式而不是文本模式打开文件
“wx”、“wbx”、“w+x”、“wb+x"或"w+bx” (C11)类似非x模式,但是如果文件已存在或以独占模式打开文件,则打开文件失败

像UNIX和Linux这样只有一种文件类型的系统,带b字母的模式和不带b字母的模式相同。

上面的“将长度截为0”意思是删除现有文件已有的内容。使用任何一种不带X的“w”模式,都会这样。

示例:

FILE* fp = fopen("test.txt","r");

2.2 getc()函数和putc函数(文件的字符处理)

这两个函数用于从文件中获取一个字符和将一个字符写入文件,每次执行后,位置都会自动向后移动一个字符。

和getchar()、putchar()类似。

示例:

ch = getc();
putc(ch,fp);

2.3 文件结尾:EOF

getc()函数在读取一个字符时发现是文件结尾,它将返回一个特殊值EOF。

它定义在stdio.h中:

#define EOF    (-1)

现在就可以使用C语言来读取一个文本文件了:

#include

int main()
{
	FILE* fp = fopen("origin.txt","r");
	int ch;
	while ((ch=getc(fp))!=EOF)
	{
		putchar(ch);

	}
	fclose(fp);

	return 0;
}

输出:

Hello,CSDN.

2.4 关闭一个打开的文件:fclose()函数

fclose(fp)函数关闭fp指定的文件,必要时刷新缓冲区。对于较正式的程序,应该检查是否成功关闭文件。如果成功关闭,fclose()函数返回0
,否则返回EOF:

if (fclose(fp) != 0)
    printf("Error in closing file %s\n", argv[1]);

如果磁盘已满、移动硬盘被移除或出现I/O错误,都会导致调用fclose()函数失败。

2.5 指向标准文件的指针

  • 标准输入:stdin
  • 标准输出:stdout
  • 标准错误:stderr

都是指向文件的指针。

2.6 例程

写一个程序,读取一个文件,并copy内容到新文件中。

#include
#include
#include

#define LEN 30

void file_copy(const char *file_in);


int main()
{
	file_copy("origin.txt");

	return 0;
}


void file_copy(const char *file_in)
{
	FILE *in, *out;
	char name[LEN];
	int ch;

	// 检查文件名称
	if ((in = fopen(file_in, "r")) == NULL)
	{
		fprintf(stderr,"The file: %s is not exit.\n",file_in);
		exit(EXIT_FAILURE);
	}


	strncpy(name, file_in, LEN - 9);
	name[LEN - 5] = '\0';
	strcat(name,".new.txt");

	if ((out = fopen(name, "w+")) == NULL)
	{
		fprintf(stderr, "Can't creat output file.\n");
		exit(EXIT_FAILURE);
	}



	while ((ch = getc(in)) != EOF)
	{
		putc(ch, out);
	}

	if (fclose(in) != 0 || fclose(out) != 0)
	{
		fprintf(stderr, "Error with closing files.\n");
		exit(EXIT_FAILURE);
	}

	puts("Processing success!");

}

效果:
【C语言】文件的输入与输出_第1张图片

注:

exit()函数关闭所有打开的文件并结束程序。exit(EXIT_FAILURE);表示技术程序失败,并将标准错误(stderr)输出到屏幕,exit(EXIT_SUCCESS);即exit(0);在main()函数中和return效果一样。

三、文件I/O:fprintf()、fscanf()、fgets()、fputs()

3.1 fscanf()和fprintf()函数

文件I/O函数fprintf()和fscanf()函数的工作方式与printf()和scanf()类似,区别在于前者需要用第1个参数指定待处理的文件。

例:

void fp_s()
{
	FILE* fp;
	char words[30];

	if ((fp = fopen("word.txt", "a+")) == NULL)
	{
		fprintf(stderr, "打开文件失败。\n");
		exit(EXIT_FAILURE);
	}

	puts("输入单词,q表示结束\n");
	while ((fscanf(stdin, "%s", words) == 1) && words[0] != 'q')
	{
		fprintf(fp, "%s\n", words);
	}

	rewind(fp); //将指针移到开头

	puts("你输入了以下单词:");

	while ((fscanf(fp, "%s\n", words)) == 1)
		puts(words);

	if ((fclose(fp)) != NULL)
		fprintf(stderr,"关闭文件失败。\n");
}

输出:

输入单词,q表示结束

dog
cat
Jay Chou
你好
123
q
你输入了以下单词:
dog
cat
Jay
Chou
你好
123

3.2 fgets()和fputs()

fgets()函数的第1个参数和gets()函数一样,也是表示储存输入位置的地址(char * 类型);第2个参数是一个整数,表示待输入字符串的大小(不是长度);最后一个参数是文件指针,指定待读取的文件。下面是一个调用该函数的例子:

fgets(buf, STLEN, fp);

这里,buf是char类型数组的名称,STLEN是字符串的大小,fp是指向FILE的指针。fgets()函数读取输入直到第1个换行符的后面,或读到文件结尾,或者读取STLEN-1个字符(以上面的fgets()为例)。然后,fgets()在末尾添加一个空字符使之成为一个字符串。字符串的大小是其字符数加上一个空字符。如果fgets()在读到字符上限之前已读完一整行,它会把表示行结尾的换行符放在空字符前面。fgets()函数在遇到EOF时将返回NULL值,可以利用这一机制检查是否到达文件结尾;如果未遇到EOF则返回之前传给它的第一个参数地址。

fputs()函数接受两个参数:第1个是字符串的地址;第2个是文件指针。该函数根据传入地址找到的字符串写入指定的文件中。和puts()函数不同,fputs()在打印字符串时不会在其末尾添加换行符。下面是一个调用该函数的例子:

fputs(buf, fp);

这里,buf是字符串的地址,fp用于指定目标文件。由于fgets()保留了换行符,fputs()就不会再添加换行符。

四、定位函数fseek()和ftell()

fseek()函数原型:

 int  fseek(  FILE* _Stream,long  _Offset, int _Origin);

该函数的功能是修改当前位置。

  • 第一个参数是文件指针(已经打开的文件);
  • 第二个参数是偏移的字节数,long型;
  • 第三个参数是气势位置:SEEK_SET:文件开始处,SEEK_END:文件结尾,SEEK_CUR:当前位置。

成功返回0,出错返回-1。

ftell()函数的返回类型是long,就一个参数:文件指针,它返回的是参数指向文件的当前位置距文件开始处的字节数。

编写一个函数将文件中的内容逆序添加到文件结尾,原内容:

ABCDEFGHIJK

运行一次:

ABCDEFGHIJK

KJIHGFEDCBA

代码:

void fseek_tell()
{
	FILE* pt;
	long count;
	int ch;

	pt = fopen("sort.txt","a+");

	fseek(pt,0L,SEEK_END);
	count = ftell(pt);

	fprintf(pt,"%s","\n"); //换行

	while (count != -1)
	{
		fseek(pt,count,SEEK_SET);
		ch = getc(pt);

		count--;

		fseek(pt, 0L, SEEK_END);
		fprintf(pt, "%c", ch);
	}

	fclose(pt);
}

fseek()和ftell()潜在的问题是,它们都把文件大小限制在long类型能表示的范围内。也许20亿字节看起来相当大,但是随着存储设备的容量迅猛增长,文件也越来越大。鉴于此,ANSI C新增了两个处理较大文件的新定位函数:fgetpos()和fsetpos().

五、标准I/O的原理

通常,使用标准I/O的第1步是调用fopen()打开文件(前面介绍过,C程序会自动打开3种标准文件)。

fopen()函数不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构

另外,fopen()返回一个指向该结构的指针,以便其他函数知道如何找到该结构。假设把该指针赋给一个指针变量fp,我们说fopen()函数“打开一个”。如果以文本模式打开该文件,就获得一个文本流;如果以二进制模式打开该文件,就获得一个二进制流。

这个结构通常包含一个指定流中当前位置的文件位置指示器。除此之外,它还包含错误和文件结尾的指示器、一个指向缓冲区开始处的指针、一个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。

通常,使用标准I/O的第2步是调用一个定义在stdio.h中的输入函数,如fscanf()、getc()或fgets()。一调用这些函数,文件中的缓冲大小数据块就被拷贝到缓冲区中。缓冲区的大小因实现而异,一般是512字节或是它的倍数,如4096或16384。最初调用函数,除了填充缓冲区外,还要设置fp所指向的结构中的值。尤其要设置流中的当前位置和拷贝进缓冲区的字节数。通常,当前位置从字节0开始。

在初始化结构和缓冲区后,输入函数按要求从缓冲区中读取数据。在它读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。由于stdio.h系列的所有输入函数都使用相同的缓冲区,所以调用任何一个函数都将从上一次函数停止调用的位置开始。

当输入函数发现已读完缓冲区中的所有字符时,会请求把下一个缓冲大小的数据块从文件拷贝到该缓冲区中。以这种方式,输入函数可以读取文件中的所有内容,直到文件结尾。函数在读取缓冲区中的最后一个字符后,把结尾指示器设置为真。于是,下一次被调用的输入函数将返回EOF。输出函数以类似的方式把数据写

你可能感兴趣的:(C语言,c语言,unix)