C语言文件读写——文本文件读操作

最近和几个初学C语言的朋友讨论文件读写,发现他们在使用C语言文件读写功能的时候遇到了不少问题,不是文件打开方式有问题,就是文件读写有问题,总是得不到自己想要的结果。

C语言文件读写操作,既简单又复杂,要熟练使用每一项功能,也确实不易,既有文本文件操作,也有二进制文件的读写。本文先比较详细地介绍C语言的文本文件读操作,下一篇会详细介绍文本文件的写使用。

什么是文本文件

文本文件是由一行一行的字符的有序序列组成的。一行是由0个或者若干个字符加上一个行结束符'\n'(换行符)组成的,但是最后一行的最后一个字符是否是'\n'可能在不同的平台的实现不太一样,但是由于是最后一行的最后一个字符,所以并不会影响正常读写,但是我们在写文本文件的时候,最好也保证最有一个字符是行结束符'\n'。

在Windows上,为了方便人阅读文本文档,在写入'\n'的时候,系统会自动将'\n'转换为'\r\n'(回车换行),在读取的时候会自动将'\r\n'转换为'\n'。在Linux上,并不会有这种转换,所以Windows上的文本文件在Linux上比较低的版本上进行读写的时候有时候需要进行一下转换,但是现在的Linux系统基本上已经能够正常处理'\r\n'了。

为了能够保证文本文件的正常读写,要求:

  • 写入的字符是可打印字符,或者一些特殊的控制符,比如'\t'(TAB键),'\n'。
  • 不要在空格字符后紧跟'\n'字符,否则可能在读取的时候空格会丢失。
  • 最后一个字符是'\n'。

读文本文件示例

说了这么多,我们先直接来一个读取文本文件内容的例子,然后再做一下具体分析,这个文本文件的内容就是本文档的部分内容,我们把它读取出来,然后在控制台显示,我们以Windows系统为例,Linux下面的操作方式是类似的,读取文本文件的示例代码如下:

#include <stdio.h>
void read_text(const char* file_name)
{char line[1024]={0};FILE *file = fopen(file_name,"rt");if(!file)return;while(1){//文件读取结束if(EOF == fscanf(file,"%s",line))break;printf("%s\n",line);}fclose(file);}
int main(int argc, char* argv[])
{read_text("test.txt");return 0;
}

然后运行程序,看效果是否是我们期望的那样,一行一行地显示在控制台,如下图所示,上边是程序的输出,下边是test.txt文件的内容。

读文本文件详解

C语言中打开文件,主要有两个函数,fopen和fopen_s,fopen_s是C11标准中新引入的,它们的函数申明是这样的:

     (1)
FILE *fopen( const char *filename, const char *mode );  (until C99)
FILE *fopen( const char *restrict filename, const char *restrict mode );(since C99)(2)
errno_t fopen_s(FILE *restrict *restrict streamptr, const char *restrict filename,  const char *restrict mode);(since C11)

fopen_s会做一些额外的检查,更“安全”一些,C11标准后,有很多这样的_s的函数,都是为了更加安全,会做一些安全性的检查,比如函数传入的指针是否为空,传入的缓冲区是否会溢出等等。

返回值FILE*就代表了一个文件流对象,如果为NULL,则表示打开文件失败。

重点看一下mode,即打开文件的方式,常用的mode主要有:

mode 含义 说明 如果文件存在 如果文件不存在
“r” 以读的方式打开文件,打开以后只能读 成功打开,并从文件开始读 打开失败
"w" 创建一个文件进行写 文件内容会被清空 创建一个新文件
"a" 追加 追加内容到文件末尾 将文件内容追加到文件末尾 创建一个新文件
"r+" 扩展读 打开文件进行读写,可读可写 从文件开始读 打开失败
"w+" 扩展写 创建一个文件进行读写 文件内容会被清空 创建一个新文件
"a+" 扩展追加 打开文件进行读写 追加内容到文件末尾 创建一个新文件

11啊

除了这些基本的打开模式之外,还可以附加一些别的模式,比如“t”,“b”等,例如“rb”,表示以二进制方式打开文件,"wt"以文本文件的方式创建文件,默认是文本文件方式,所以“t”一般可以省略。

从上面的表格我们可以简单总结出来,只要是有“r”标志,文件就要求存在,否则就会打开失败,其他的打开模式都不需要文件一定存在,如果文件不存在,则会创建一个新文件。所以,在进行文件读写之前,一定要明确自己的目的,是读文件,还是写文件,是从文件开始写,还是追加内容到文件末尾。

本节的主题是读文本文件,所以我们在使用fopen的时候,就使用"r"或者"rt"标志,不要使用别的模式。

说到读文本文件内容,不是一件简单事情,因为读文本文件内容也涉及到好些函数,如果选择不当,得到的结果也会不是我们期望的。

其实文本文件的内容虽然在前面介绍过,要作为文本文件是有要求的,比如前面提到的每行要求以'\n'结束等。但是文本文件内容本身又可以分为有格式的和没有格式的,比如我们前面的test.txt文件,就是没有格式的,就是纯文本文件,因为除了换行之外,没有任何别的格式,信息是杂乱的。如果我们查看一下一个普通的Excel文件,里面的数据就很有格式,一行一行,一列一列,非常整齐,这就是有格式的文本文件样子,我们看一个有格式的文本文件,这是一个简易的学生名单文件student.txt,每一行包含的内容为学号,姓名,所属学院,成绩,如下图所示。

这个student.txt文件内容就是格式化的,即每一行的数据格式都是一致的,包含的信息都是类似的,即都包含学号,姓名,学院和分数,而且它们之间是用空格隔开的,这就是格式化的文件数据。

在读取非格式化和格式化数据的时候,需要用到的函数是不一样的,否则,用非格式化数据读取函数去读取格式化的数据,读取到的内容我们也不好利用,同样,用格式化数据读取函数去读取非格式化的数据,有时候也得不到正确结果。

非格式化文本文件读取

非格式文本文件内容读取主要有两个函数,fgetc和fgets,fgetc的函数原型为:

int fgetc( FILE *stream );

 

每次从文件中读取一个字符,直到文件结尾。如果读到文件结尾,返回EOF。

fgetc不会对文件中的内容做任何处理,一次就读一个字符,'\n'也会想普通字符一样读取,我们来看一下使用fgetc读取我们前面提到的text.txt文件的效果,代码如下:

void read_text_by_getc(const char* file_name)
{int ch = 0;FILE *file = fopen(file_name,"rt");if(!file)return;while(EOF != (ch = fgetc(file))){//在屏幕上输出读到的每一个字符putchar(ch);}fclose(file);
}

则执行效果下图所示。

fgetc对于读取非格式化的文本内容,就是最真实地反应了文本文件中的内容。

但是fgetc读取容易,处理起来却很麻烦,如果只是在屏幕上显示文件内容的话,没有问题,如果还要想对读取的内容进行处理的话,就麻烦一些。

我们再来看一下fgets函数。

fgets的函数原型为:

char *fgets( char *restrict str, int count, FILE *restrict stream );

 

(since C99)

每次从文件中读取指定长度的内容,读取的长度最多为count-1个字符。读到'\n'的时候,这一次读取就结束了,即使还没有读到count-1个字符。如果读取失败,返回NULL。

我们先看一下使用fgets读取test.txt文本文件的效果,代码如下:

void read_text_by_gets(const char* file_name)
{char buffer[20]={0};FILE *file = fopen(file_name,"rt");if(!file)return;while(NULL != (fgets(buffer,sizeof(buffer),file))){//显示每一次读取到的内容printf("%s",buffer);}fclose(file);
}

然后运行,结果如下所示。

从图中可以看到,显示结果一塌糊涂,为什么呢?因为我们的缓冲区buffer大小是20,所以每次只读19个字符,有时候刚好得到半个汉字,就显示很凌乱了。

因此,如果我们想完整的一次读取一行的数据,这个缓冲区buffer必须足够大,至少要超过文件中最长的一行字符的长度,我们修改一下程序,我们知道我们的test.txt文件中每行都不会太长,所以我们把buffer的大小设置成128,看看效果如何,如下图所示。

因此,我们在对于非格式化文本进行读取的时候,要选择合适的函数来读取,如果想一行一行的读取文本文件中的内容,最好使用fgets,并且提供足够大的缓冲区。

格式化文本文件读取

格式化文本文件的读取主要用到函数fscanf和fscanf_s。

先看一下fscanf函数的原型。

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

(since C99)

fscanf和scanf一样,有一个格式化字符串format参数,因此在读取文本内容的时候会严格按照这个format格式去读,因此,对于我们的读取结构化的数据就非常方便,以我们的student.txt为例,我们再来看一下student.txt文件的内容。

1001 张三 计算机学院 90.0
1002 李四 计算机学院 98.7
1003 王五 软件学院 88
1004 黄渤海 人文学院 97

每一行代表一个学生,包含学号,姓名,所属学院以及成绩。

当使用fscanf读取的时候,同样,遇到'\n',一次读取就结束了,假设我们有四个变量ID,Name,College,Score,我们可以用scanf一次把一行的数据都读取到这四个变量中,我们看一下代码:

void read_formated_text(const char* file_name)
{int ID;char Name[32]={0};char College[128]={0};float Score = 0.0;FILE *file = fopen(file_name,"rt");if(!file)return;printf("学生名单为:\n");while(1){//文件读取结束if(EOF == fscanf(file,"%d %s %s %f",&ID,Name,College,&Score))break;printf("学号:%d 姓名:%s 学院:%s 分数:%.2f\n",ID,Name,College,Score);      }fclose(file);
}

运行结果下图所示。

如果我们有一个结构体Student的话,也可以通过这种方式把一行的数据直接读取到一个结构体中,代码如下:

void read_formated_text_by_struct(const char* file_name)
{struct Student stu;FILE *file = fopen(file_name,"rt");if(!file)return;printf("学生名单为:\n");while(1){//文件读取结束if(EOF == fscanf(file,"%d %s %s %f",&stu.ID,stu.Name,stu.College,&stu.Score))break;printf("学号:%d 姓名:%s 学院:%s 分数:%.2f\n",stu.ID,stu.Name,stu.College,stu.Score);       }fclose(file);
}

结果是一样的。

现在简单介绍一下fscanf_s,其函数原型为:

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

  (since C11)

对于这个函数的使用,牢记住一点就好了,如果读取的是字符串类型,需要在字符串变量后面在跟一个字符串长度的值来指明字符串变量或者数组能够容纳多少个字符,比如我们前面的读取student.txt文件的代码就可以改为:

void read_formated_text_by_struct(const char* file_name)
{struct Student stu;FILE *file = fopen(file_name,"rt");if(!file)return;printf("学生名单为:\n");while(1){//文件读取结束if(EOF == fscanf_s(file,"%d %s %s %f",&stu.ID,stu.Name,sizeof(stu.Name)-1,stu.College,sizeof(stu.College)-1,&stu.Score))break;printf("学号:%d 姓名:%s 学院:%s 分数:%.2f\n",stu.ID,stu.Name,stu.College,stu.Score);        }fclose(file);
}

因为Name和College都是字符串类型,所以在它们后面都跟了一个长度。执行结果仍然一样。

到了这里,大家以为文本文件的读取内容应该已经结束了,其实不是,如果你是C语言的初级选手或者刚学计算机不太久,后面的内容可以暂时跳过。

窄字符与宽字符

窄字符和宽字符都是指对字符的编码,窄字符就是一个字符占有的空间只有一个字节,宽字符通常一个字符占用两个字节的空间,我们也常常说宽字符为UNICODE编码字符。

我们前面所讲的读取文件,这些函数其实都是针对窄字符的,因为这些文件内容的编码都是窄字符编码,一个英文是一个字符,一个汉字是两个字符,它们都是一个字符占用一个字节。

如果是宽字符的话,一个字符占用两个字节,每一个英文字符还是一个字符,一个中文汉字通常也是一个字符,但是它们都占用两个字节。我们把test.txt文件另存一下,存为UNICODE编码,用记事本的另存功能,选择UNICODE,如图所示。

如果我们此时还是用前面的代码来读取test_unicode.txt文件,效果会是什么样呢?我们以read_text_by_getc来验证一下,结果如图所示。

可以再次用一塌糊涂来形容,什么也不是了。

这就涉及到另外的读取函数了,是专门针对宽字符的,主要也有这几个函数,与窄字符对应的函数分别为:

wint_t fgetwc( FILE *stream );

  (since C95)

wchar_t *fgetws( wchar_t * restrict str, int count, FILE * restrict stream );

  (since C99)

int fwscanf( FILE *restrict stream,
             const wchar_t *restrict format, ... );

(since C99)

int fwscanf_s( FILE *restrict stream,
               const wchar_t *restrict format, ...);

(5) (since C11)

它们的用法和窄字符的用法完全一样,唯一的不同是它们都要求被读取的文件的内容是UNICODE即宽字符编码的。

这里不做试验了,有的朋友如果有兴趣的话,自己可以试一试,看看效果。

其它

如果要学习VC++调试相关技术,可以学习

https://edu.csdn.net/course/detail/28915

如果要学习Linux gdb C/C++调试相关技术,可以学习

https://edu.csdn.net/course/detail/28981

《完》

C语言文件读写(1)-文本文件读操作相关推荐

  1. C语言文件读写(2)-文本文件写操作

    C语言文件读写-文本文件写操作 在上一篇中介绍了如何读取文本文件 https://blog.csdn.net/zhanghaiyang9999/article/details/107032563 这一 ...

  2. C语言文件读写(3)-二进制文件读写操作

    C语言文件读写-二进制文件读写操作 先说说什么是二进制文件,二进制文件是相对于文本文件而言的,文本文件是由一行一行的字符的有序序列组成的.二进制文件就没有行的概念了,也是由有序的字符组成的,但是在写入 ...

  3. c语言中读和写的作用,c语言文件读写

    c语言文件读写 从文件编码的方式来看,文件可分为ASCII码文件和二进制码文件两种. ASCII文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码.ASCII码 ...

  4. C语言 文件读写 EOF - C语言零基础入门教程

    目录 一.EOF 简介 二.EOF 实战 三.猜你喜欢 零基础 C/C++ 学习路线推荐 : C/C++ 学习目录 >> C 语言基础入门 一.EOF 简介 EOF 是 End Of Fi ...

  5. vs调用css写的c语言程序,c语言文件读写实例

    #include"ccn.h" main(){FILE *in,*out; char ch,infile[10],outfile[10文件使用方式 意义 "rt" ...

  6. c语言文件读写ppt,c语言文件读写.ppt

    c语言文件读写.ppt 一.文件操作概述 所谓文件,是指存放在外部存储介质(可以是磁盘.光盘.磁带等等)上数据的集合. 要想读取外部存储介质中的数据,必须先按照文件名找到相应的文件,然后再从文件中读取 ...

  7. c语言创建一个文件函数,c语言文件读写函数 用C语言的函数创建、打开和读写文件...

    C语言文件读写结构体里面的数据怎样存到磁盘文件上 c语言对同一个文件进行读写(r+) 编写程序,将文本文件c.txt中的所有小写字母转换成相应的大写字母,其他一.标准文件的读写 1.文件的打开 fop ...

  8. C语言文件读写(5)-文件位置相关

    C语言文件读写(5)-文件位置相关 当打开一个文件以后,无论是读还是写,都有一个指示文件位置的这么一个东西,我们可以称之为文件的位置指示器或者文件位置指针,文件位置指示器指明了当前文件读或者写的位置信 ...

  9. C语言文件读写(4)-判断文件是否结束

    C语言文件读写-判断文件是否结束 在读文件的时候,很重要的一个条件是:文件是否结束,因为文件已经结束了,就不用继续读文件了. 判断文件结束,有许多方式,比如读取文本文件和二进制文件就可以使用不同的方式 ...

最新文章

  1. 小程序加载大图片 使用widthFix时,图片先拉伸然后才显示完全
  2. c语言求解热传导方程,二维稳态导热问题的数值解法.docx
  3. 轻量级ORM框架Dapper应用四:使用Dapper返回多个结果集
  4. Python案例:计算softmax函数值
  5. 元胞自动机交通流模型c++_MATLAB——含出入匝道的交织区快速路元胞自动机模型...
  6. 计算机网络 故障处理,计算机网络通讯技术故障分析与处理
  7. 召唤新一代超参调优开源新神器,集十大主流模块于一身
  8. ARM DS-5 Flex网络许可证编译错误“Error: C9933W: Waiting for license...”
  9. 使用APICloud AVM多端组件快速实现app中的搜索功能
  10. 【poj3311】Hie with the Pie
  11. 【英文】Node.js Streams: Everything you need to know //转载
  12. 木棍游戏(深搜 模板
  13. 高中数学基础08:关系、概率与统计
  14. UI设计色彩趋势总结
  15. 图形图形处理方面的一位微软专家的主页,
  16. 嵌入式linux 从u盘启动,嵌入式 linux中dd命令[用于制作U盘启动盘的神奇的命令]
  17. 超详细!Vue-coderwhy个人学习笔记(二)(Day3)
  18. 汽车UDS诊断之通过标识符写入数据服务(0x2E)深度剖析
  19. 将文件从 Linux 传输到 Windows
  20. A-level Computer Science 计算机科学学习笔记/经验分享/教学 (1):考试流程和大纲

热门文章

  1. ZZULIOJ:1071: 分解质因子
  2. 超详细的Linux安装Redis单机版教程
  3. win10下的msys2开发环境搭建
  4. 数学中说的线性关系线性是什么意思?
  5. 洛谷P1262 间谍网络题解
  6. Python自动化测试学习3
  7. 安装x86版 OS X的系统要求
  8. layui制作二维码
  9. 软件测试能干多久?测试员能干到多大年龄?
  10. 什么是字节码?采用字节码的好处是什么?