C语言文件读写教程:详解文本文件读操作及常见问题解决方案
C语言文件读写——文本文件读取操作

最近和几个刚接触C语言的朋友讨论文件读写。我发现他们在使用C语言文件读写功能时遇到了很多问题。要么是文件打开方式有问题,要么是文件读写有问题。他们总是失败。得到你想要的结果。
C语言的文件读写操作既简单又复杂。熟练使用每一个功能确实不容易,包括文本文件操作和二进制文件读写。本文首先比较详细地介绍C语言中的文本文件读取操作。下一篇文章将详细介绍文本文件的编写和使用。
什么是文本文件
文本文件由逐行有序的字符序列组成。一行由 0 个或多个字符加上行终止符“\n”(换行符)组成。但最后一行的最后一个字符是否为'\n'在不同的平台上可能有不同的实现。不过,由于是最后一行的最后一个字符,因此不会影响正常的读写。不过,当我们写入文本文件时,最好保证最后一个字符是行终止符'\n'。
在Windows上,为了方便人们阅读文本文档,在写入'\n'时,系统会自动将'\n'转换为'\r\n'(回车换行),而在读取时,它会自动将 '\r\n' 转换为 '\n'。在Linux上则没有这样的转换,所以在较低版本的Linux上读写Windows上的文本文件时,有时需要进行转换,但目前的Linux系统基本可以正常处理。 \r\n'。
为了保证文本文件的正常读写,需要:
读取文本文件示例
说到这里,我们先以读取文本文件内容为例,然后再做详细分析。该文本文件的内容是本文档内容的一部分。我们读出它然后将其显示在控制台上。我们以Windows系统为例。 Linux下的操作方法类似。读取文本文件的示例代码如下:
#include
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,则表示打开文件失败。
让我们重点关注模式,即打开文件的方式。常用的模式主要有:
模式
意义
阐明
如果文件存在
如果文件不存在
“r”
读
以阅读模式打开文件。打开后只能读取。
打开成功并开始读取文件
打开失败
“w”
写
创建一个用于写入的文件
文件内容将被清除
创建一个新文件
“一个”
附加
将内容追加到文件末尾
将文件内容附加到文件末尾
创建一个新文件
“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 *流);
从文件中一次读取一个字符,直到文件末尾。如果到达文件末尾,则返回 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 *限制 str, int count, FILE *限制流);
(自 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);
}
然后运行一下,结果如下图。
从图中可以看到,显示结果是一团糟。为什么?因为我们的缓冲区大小是 20,所以我们一次只能读取 19 个字符。有时我们只得到半个汉字,显示很乱。
因此,如果我们想要一次完整地读取一行数据,这个缓冲缓冲区就必须足够大,至少要超过文件中最长的一行字符的长度。我们来修改一下程序。我们知道,我们的test.txt文件中的每一行数据行都不会太长,所以我们将缓冲区大小设置为128,看看效果,如下图所示。
因此,我们在阅读无格式文本的时候,一定要选择合适的函数来阅读。如果我们想逐行读取文本文件的内容,最好使用fgets并提供足够大的缓冲区。
格式化文本文件读取
读取格式化文本文件主要使用函数fscanf和fscanf_s。
我们先看一下fscanf函数的原型。
int fscanf( FILE *限制流, const char *限制格式, ... );
(自 C99 起)
和scanf一样,fscanf有一个格式字符串格式参数,所以在读取文本内容时,会严格按照这个格式来读取。因此,我们读取结构化数据是非常方便的。以我们的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 *限制流,const char *限制格式,...);
(自 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 );
(自 C95)
wchar_t *fgetws( wchar_t * 限制 str, int 计数, FILE * 限制流);
(自 C99 起)
int fwscanf(FILE *限制流,
const wchar_t *限制格式,...);
(自 C99 起)
int fwscanf_s( FILE *限制流,
const wchar_t *限制格式,...);
(5)
(自 C11 起)
它们的用法与狭义字完全相同。唯一的区别是,它们都要求读取的文件内容为UNICODE,即宽字符编码。
这里没有实验。如果有朋友感兴趣的话,可以自己尝试一下,看看效果。
其他
如果你想学习VC++调试相关技术,可以学习
如果你想学习Linux gdb C/C++调试相关技术,可以学习
“超过”