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

简介: 【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包除了可移植以外还有两个好处。


第一,标准I/O有许多专门的函数简化了处理不同I/O的问题。例如,printf()把不同形式的数据转换成与终端相适应的字符串输出。

第二,输入和输出都是缓冲的。(一次转移一大块信息而不是一字节信息(通常至少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<stdio.h>
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<stdio.h>
#include<stdlib.h>
#include<string.h>
#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!");
}

效果:

fc2ff869e18c7dd4cd3eb7c6380e888d_20f89de2073e4b16964b2b42bca2829b.png

注:

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。输出函数以类似的方式把数据写


相关文章
|
27天前
|
存储 编译器 C语言
如何在 C 语言中判断文件缓冲区是否需要刷新?
在C语言中,可以通过检查文件流的内部状态或使用`fflush`函数尝试刷新缓冲区来判断文件缓冲区是否需要刷新。通常,当缓冲区满、遇到换行符或显式调用`fflush`时,缓冲区会自动刷新。
|
27天前
|
存储 编译器 C语言
C语言:文件缓冲区刷新方式有几种
C语言中文件缓冲区的刷新方式主要包括三种:自动刷新(如遇到换行符或缓冲区满)、显式调用 fflush() 函数强制刷新、以及关闭文件时自动刷新。这些方法确保数据及时写入文件。
|
1月前
|
C语言
【C语言】探索文件读写函数的全貌(三)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 C语言
【C语言】探索文件读写函数的全貌(二)
【C语言】探索文件读写函数的全貌
|
1月前
|
C语言
【C语言】探索文件读写函数的全貌(一)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 文件存储 C语言
【C语言】深入了解文件:简明指南
【C语言】深入了解文件:简明指南
|
2月前
|
Linux C语言
C语言 文件IO (系统调用)
本文介绍了Linux系统调用中的文件I/O操作,包括文件描述符、`open`、`read`、`write`、`lseek`、`close`、`dup`、`dup2`等函数,以及如何获取文件属性信息(`stat`)、用户信息(`getpwuid`)和组信息(`getgrgid`)。此外还介绍了目录操作函数如`opendir`、`readdir`、`rewinddir`和`closedir`,并提供了相关示例代码。系统调用直接与内核交互,没有缓冲机制,效率相对较低,但实时性更高。
|
3月前
|
存储 C语言
【C语言】C语言-学生成绩管理系统(源码+数据文件+课程论文)【独一无二】
【C语言】C语言-学生成绩管理系统(源码+数据文件+课程论文)【独一无二】
53 15
|
3月前
|
存储 C语言
【c语言】职工信息管理系统 包含读取写入txt文件,职工信息的增删改查
【c语言】职工信息管理系统 包含读取写入txt文件,职工信息的增删改查
|
3月前
|
存储 自然语言处理 程序员
【C语言】文件的编译链接和预处理
【C语言】文件的编译链接和预处理