C文件读写
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C文件读写相关的知识,希望对你有一定的参考价值。
可以将程序中的数据保存为一个文件。待下次重新启动程序时,从之前保存的文件中提取数据。这样,程序就不会在重启后失忆了。
创建并写入文件
#include <stdio.h>
int main()
// 创建一个名为data.txt的文件
FILE* pFile = fopen("data.txt", "w");
if (pFile == NULL)
// 文件创建失败
return -1;
// 文件创建成功
int n = 123;
double f = 3.1415;
char ch = A;
// fprintf第一个参数为文件结构指针,其后参数与printf一致
fprintf(pFile, "%d\\n", n);
fprintf(pFile, "%f\\n", f);
fprintf(pFile, "%c\\n", ch);
// 关闭文件
fclose(pFile);
return 0;
打开文件data.txt
,我们可以发现,里面有刚刚写入的三个变量的值,并且每打印一个变量换行一次。
123
3.141500
A
为了操作文件,我们需要借助几个在头文件stdio.h
中声明的库函数。
创建或打开文件fopen函数。
FILE *fopen (const char * filename, const char * mode);
输入:
const char * filename
文件路径,可以使用相对路径或绝对路径。
const char * mode
操作模式
输出:
如果文件创建或打开成功,则返回一个指针。这个指针指向一个记录文件信息的结构FILE
。其他各种文件操作函数,需要这个结构指针才能对fopen
打开或创建的文件进行操作。我们无需过多地关注这个结构的具体组成,仅需要将这个结构指针传递给各种文件操作函数即可。
例如,我们使用相对路径data.txt
,将在当前目录下,创建一个名为data.txt
的文件。
也可以在windows上使用形如F:/projects/data.txt
的绝对路径,在F盘下的project文件夹中,创建data.txt文件。
函数 fopen 的第一个参数为字符串,内容为需要操作的文件路径,第二个参数也为字符串,内容为文件的操作模式。
操作模式
读、写模式w、r
- “r” 模式,读模式,取自read的首字母。对文件进行读取操作。
- “w” 模式,写模式,取自write的首字母。对文件进行写入操作。如果文件存在,清空原文件内容,不存在则创建一个新文件。
追加模式a
如果,现在想在第一行后,再增加更多的HelloWorld
,若函数fopen
使用的是w
写入模式,文件将清空原内容再写入。现在,我们需要保留原有内容,继续在文件尾部添加新内容。这时候,需要使用追加模式a
。字符a
为单词追加append
的首字母。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "a"); // 追加模式
if (pFile == NULL)
return -1;
char str[] = "HelloWorld\\n";
char* p = str;
while (*p != \\0)
fputc(*p, pFile);
p++;
fclose(pFile);
return 0;
多运行几次,可以发现,文件中有了多行HelloWorld
了。
注意,代码从未将\\0
写入过文件,文件中的每一行都是由换行分隔。且\\0
也不标记文件结尾。文件是否结尾可以通过文件操作函数返回值和feof
函数的返回值判断。
可读可写模式
可以使用+
将r
和w
模式从单一的模式,升级为读写均可模式。
- “w+” 模式,更新模式,可读可写。但是,会清空文件原有内容。
- “r+” 模式,更新模式,可读可写。
对于以更新模式 + 打开的文件,这里有一个必须要注意的地方:
- 文件从写操作转换为读操作前,必须使用
fflush
,fseek
,rewind
其中一个函数。 - 文件从读操作转换为写操作前,必须使用
fseek
,rewind
其中一个函数。
字符串输出到文件内fprintf
int fprintf (FILE * stream, const char * format, ...);
若需要将字符串输出到文件内,有一个非常类似于printf
的函数fprintf
。它就相当于在函数printf
第一个参数前,加了一个文件结构指针参数,用于指明操作哪个文件。其他的使用方法和printf
几乎一致。
字符输出到文件内fputc
fputc()
函数用于向文件中写入一个字符。
fputc 的函数原型:
int fputc(int character, FILE* stream);
输入:
int character
写入文件的字符
FILE* stream
文件结构指针
输出 :
如果写入成功,返回刚刚写入的字符。如果文件结尾或失败,则返回EOF
。并且ferror
可以检测到文件读写出错。
使用指针p
的移动遍历"HelloWorld\\n"
字符串,直到指针指向字符为\\0
为止。遍历结束前的字符,均被fputc
函数写入到文件当中。
请注意,目前函数fopen
使用的是w
写入模式。因此,文件将清空原内容再写入。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "w"); // 写模式
if (pFile == NULL)
return -1;
char str[] = "HelloWorld\\n";
char* p = str;
while (*p != \\0)
// 向文件中写入一个字符
fputc(*p, pFile);
p++;
fclose(pFile);
return 0;
程序运行完成后,将会在文件中看到一串字符HelloWorld
并换行。
关闭文件fclose(pFile);
虽然程序结束会为我们自动关闭文件。如果在程序运行期间,不需要再次操作文件了,可以调用函数fclose
关闭文件。并且,关闭所有资源再结束程序是一个良好的编程习惯。
文本模式与二进制模式
使用十六进制查看器,打开这个文件
很显然,这个文件里面记录了刚刚写入字符的ASCII码。
十六进制0A
,换行符,转义序列为\\n
。
十六进制0D
,回车,转义序列为\\r
。
为什么会出现回车和换行两个字符
在早期的电传打字机上,有一个部件叫“字车”,类似于打印机的喷头。“字车”从最左端开始,每打一个字符,“字车”就向右移动一格。当打满一行字后,“字车”需要回到最左端。这个动作被称作“回车”(return carriage)。
但是,仅仅做了“回车”还不够,我们还需要将纸张上移一行,让“字车”对准新的空白一行。否则,两行字将被重叠打印在一起。这个动作被称作“换行”。
随着时代的发展,字符不仅仅只打印在纸上。例如,在屏幕上打印字符时,无需“字车”。
所以,当人们将开始新的一行引入到计算机上时,分成了两种惯例:
- 沿用这两个动作,回车加换行
\\r
、\\n
。 - 简化为仅换行
\\n
。
两类具有代表性的系统分别使用了其中一种惯例:
- Windows系统使用
\\r
加\\n
。 - Linux系统使用
\\n
。
C语言本身采取了第二种惯例,仅使用一个字符\\n
。但是,为了适配各系统下的惯例,C语言写入、读取文件时,若系统惯例与C语言使用的不一致,则会自动进行转换。
Linux系统和C语言采用同一种惯例\\n
,无需转换。
C语言在Windows系统上写入文件时,会将\\n
写入为\\r
、\\n
。而读取文件时,会将\\r
、\\n
读取为\\n
。
如果在windows系统上运行刚刚的代码,文件内换行将是\\r
、\\n
两个字符。
如果在linux系统上运行刚刚的代码,文件内换行将是\\n
一个字符。
正是因为C语言把对文件输入输出的数据当做一行行的文本来处理,才会有这种换行时的自动转换的现象。这种文件操作模式被称作文本模式。
二进制模式
如果,不希望C语言把对文件输入输出的数据当做文本,不进行换行时的自动转换。可以在打开文件时使用二进制模式。在函数fopen
的第二个参数的字符串中添加字符b
,代表二进制binary
。
FILE *pFile = fopen("data.txt", "wb"); // 二进制写模式
FILE *pFile = fopen("data.txt", "rb"); // 二进制读模式
读取文件
fscanf函数
fscanf
相当于在函数scanf
第一个参数前,加了一个文件结构指针参数,用于指明操作哪个文件。其他的使用方法和scanf
几乎一致。
fscanf
的函数原型:
int fscanf(FILE* stream, const char* format, ...);
现在需要从文件中读取数据,所以使用只读r
模式打开文件。
#include <stdio.h>
int main()
// 读取一个名为data.txt的文件
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
// 文件打开失败
return -1;
// 文件打开成功
int n;
double f;
char ch;
// fscanf第一个参数为文件结构指针,其后参数与fscanf一致
fscanf(pFile, "%d", &n);
fscanf(pFile, "%lf", &f);
fscanf(pFile, "%c", &ch);
printf("%d\\n", n);
printf("%f\\n", f);
printf("%c\\n", ch);
// 关闭文件
fclose(pFile);
return 0;
函数fscanf
成功地从文件中读取出了前两个数据,第三个数据读取失败了。这是因为第三个fscanf
的%c
占位符期望获取一个字符。而上一行末尾中,刚好有一个\\n
。因此,第三个fscanf
读取了\\n
并赋值给了变量ch
。
可以使用类似于getchar()
函数的fgetc
,从文件中读取一个字符,吸收这个\\n
。
fgetc函数
int fgetc(FILE* stream);
输入:
FILE * stream
文件结构指针
输出:
如果读取成功,返回读取到的字符。如果文件结尾或失败,则返回EOF
。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
return -1;
int n;
double f;
char ch;
fscanf(pFile, "%d", &n);
fscanf(pFile, "%lf", &f);
// 吸收上一行末尾的\\n
fgetc(pFile);
fscanf(pFile, "%c", &ch);
printf("%d\\n", n);
printf("%f\\n", f);
printf("%c\\n", ch);
fclose(pFile);
return 0;
fgets函数
char* fgets(char* str, int num, FILE* stream);
输入:
-
str
将读取的一行字符串存储在 str 为首地址的空间中。 -
num
最大的读取字符数,包括 ‘\\n’ 在内。 -
stream
文件结构指针
例如,我们先声明100个字节的 char 类型的数组,数组名为 str ,用于放置从文件中读取的一行字符串。若文件中有一行超过100个字符,将这一行字符串放置到str
数组中,将导致越界。因此,我们可以使用第二个参数num
来限制最大读取的字符数。第三个参数则是文件结构指针。
char buffer[100];
fgets(buffer, 100, pFile);
输出:
- 如果读取成功,函数返回
str
。 - 如果遇到文件结尾,已读取到部分数据,那么返回
str
。 - 如果遇到文件结尾,未读取到任何数据,那么返回
NULL
。 - 如果遇到文件读取错误,返回
NULL
。str
中有可能有部分已读取数据。
根据返回值规则,若读取一行字符成功将返回str
,即可再次读取下一行字符。若返回NULL
,则结束读取。
在运行程序前,别忘记刚刚文件已经被清空了。先向文件写入些内容再运行程序。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
return -1;
char buffer[100];
while (fgets(buffer, 100, pFile) != NULL)
printf("%s", buffer);
fclose(pFile);
return 0;
正常输出
123
3.141500
A
EOF
EOF
,是文件结尾,End Of File
的首字符缩写。为头文件stdio.h
中定义的一个宏,通常定义为:
#define EOF (-1)
它被用于头文件stdio.h
中一些函数的返回值,用于指示文件结尾或者是一些其他错误。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
return -1;
char ch;
while (1)
ch = fgetc(pFile);
if (ch == EOF)
// 文件结尾或者是一些其他错误
break;
putchar(ch);
fclose(pFile);
return 0;
文件状态判断
假设文件data.txt
内容为
123
3.141500
A
feof
用于测试是否文件结尾。
ferror
用于测试文件是否读写出错。
feof函数原型
int feof(FILE* stream);
输入:
FILE * stream
文件结构指针
输出:
如果文件结尾,返回值为非0。否则,返回值为0。
ferror函数原型
int ferror(FILE* stream);
输入:
FILE * stream
文件结构指针
输出:
如果文件读写出错,返回值为非0。否则,返回值为0。
我们可以在fgetc
函数返回EOF
后,再次根据上述两个函数,判断究竟是文件结尾了,还是遇到了错误。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
return -1;
char ch;
while (1)
ch = fgetc(pFile);
if (ch == EOF)
// 文件结尾或者是一些其他错误
if (feof(pFile) != 0) // 测试文件是否结尾
printf("end of file\\n");
else if (ferror(pFile) != 0) // 测试文件是否读写出错
printf("file access error\\n");
break;
putchar(ch);
fclose(pFile);
return 0;
正常结尾
123
3.141500
A
end of file
如果把文件打开模式换成w
写模式。那么,文件将无法被读取,尝试读取文件将产生读写错误。并且,由于**w**
写模式会将已有文件清空,所以现在文件内容为空。
// 改为"w"写模式
FILE* pFile = fopen("data.txt", "w");
读写错误
file access error
文件缓存
fputs函数
fputs()
函数用于向文件中写入一串字符串。
int fputs(const char* str, FILE* stream);
输入:
const char* str
待写入文件的字符串
FILE* stream
文件结构指针
输出 :
如果写入成功,返回一个非负值。如果写入失败,则返回EOF
。并且,ferror
可以检测到文件读写出错。
由于用fopen
函数打开文件时,使用了w
写模式。因此,文件原内容将清空,写入5行Have a good time\\n
。
#include <stdio.h>
#include <stdlib.h>
int main()
FILE* pFile = fopen("data.txt", "w"); // 写模式
if (pFile == NULL)
return -1;
char str[] = "Have a good time\\n";
for (int i = 0; i < 5; i++)
fputs(str, pFile);
// 关闭文件前,先暂停一下
system("pause");
fclose(pFile);
return 0;
虽然在运行到暂停时,向文件中写入数据的fputs(str, pFile)
语句已经运行过了。但是,现在打开文件,文件内没有任何内容。
让暂停继续。程序结束后,文件内出现了内容。
fflush函数
C语言中提供的文件操作函数是带有缓存的,数据会先写入到缓存中。待缓存中的数据积累到一定数量时,再一起写入文件。因此,刚刚暂停时,数据还在缓存区内,未写入到文件当中。
只有将缓存区的数据写入文件,数据才真正保存在了文件中。此时缓存区的数据无需保留将被清空。这个动作被称之为刷新缓存。
而文件关闭fclose
或程序结束会刷新缓存。所以,关闭文件fclose
后,文件内出现了内容。
除此之外,还可以主动调用fflush
函数,主动刷新文件缓存。
int fflush(FILE* stream);
输入:
FILE * stream
文件结构指针
输出
刷新缓存区成功返回0,否则返回EOF
,并且ferror
可以检测到文件读写出错。
现在,稍微改一点代码。在程序暂停前刷新缓存区。
#include <stdio.h>
#include <stdlib.h>
int main()
FILE* pFile = fopen("data.txt", "w"); // 写模式
if (pFile == NULL)
return -1;
char str[] = "Have a good time\\n";
for (int i = 0; i < 5; i++)
fputs(str, pFile);
// 刷新文件缓存区后暂停程序
fflush(pFile);
system("pause");
fclose(pFile);
return 0;
现在,即使未运行到fclose
及程序关闭,文件中也已经有内容了。
Have a good time
Have a good time
Have a good time
Have a good time
Have a good time
文件偏移
假设现在文件data.txt
内容为
Have a good time
Have a good time
Have a good time
Have a good time
Have a good time
#include<stdio.h>
void fileEofOrError(FILE* pFile)
if (feof(pFile) != 0) // 测试文件是否结尾
printf("end of file\\n");
else if (ferror(pFile) != 0) // 测试文件是否读写出错
printf("file access error\\n");
int main()
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
return -1;
char ch;
while (1)
ch = fgetc(pFile);
if (ch == EOF)
fileEofOrError(pFile);
break;
putchar(ch);
fclose(pFile);
return 0;
输出结果
Have a good time
Have a good time
Have a good time
Have a good time
Have a good time
end of file
为什么每一次的 fgetc 函数能顺序获取到文件中的字符呢?
文件指针
文件结构pFile
中,保存了一个当前文件读写位置的指针。文件由fopen
函数打开后,这个指针指向文件中第一个字节。当任意文件操作函数读写相应长度的字节后,指针也会偏移相应的长度。
fgetc
函数每次获取一个字节。因此,文件指针向后移动一个字节。所以,重复调用fgetc
函数可以逐个读取文件内的字符。
fgets
函数每次获取一行字符。因此,文件指针向后移动到下一行开始。所以,重复调用fgets
函数可以逐行读取文件内的字符。
文件指针移动函数fseek
int fseek(FILE* stream, long offset, int origin);
输入:
FILE* stream
文件结构指针
long offset
文件指针偏移量
origin
从什么位置开始偏移。
其中origin
可以使用以下3种宏定义作为参数:
-
SEEK_SET
文件开头(文件第一个字节) -
SEEK_CUR
当前文件位置 -
SEEK_END
文件结尾(文件最后一个字节后)
输出 :
如果成功,返回0。否则,则返回一个非零值。并且,ferror
可以检测到文件读写出错。
从文件开头偏移5个字节,文件指针将指向
a
。
fseek(pFile, 5, SEEK_SET);
从文件结尾偏移-5个字节,文件指针将指向
i
。
fseek(pFile, -5, SEEK_END);
ftell函数获取当前文件指针位置
ftell 的函数原型:
long ftell (FILE * stream);
输入:
FILE * stream
文件结构指针
输出:
如果成功,则返回当前文件指针位置。如果失败,则返回-1。
获取文件大小
如果将文件指针先偏移到末尾,再获取文件指针当前的位置,就能知道该文件内有多少个字节。即该文件的大小。
#include <stdio.h>
int main()
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
return -1;
char ch;
// 偏移到文件结尾
fseek(pFile, 0, SEEK_END);
// 获取当前文件指针位置
long length = ftell(pFile);
printf("size of file %ld\\n", length);
fclose(pFile);
return 0;
输出文件大小
函数rewind,将文件指针回到文件最开始。
如果想让文件指针回到最开始,从文件开头偏移0个字节。
fseek(pFile, 0, SEEK_SET);
也可以使用函数rewind
,将文件指针回到文件最开始。
rewind
的函数原型:
void rewind(FILE * stream);
输入:
FILE * stream
文件结构指针
输出:
无
更新文件
假设现在data.txt
文件内容为:
Hello world
Hello world
Hello world
Hello world
Hello world
现在要将H
全部改为h
为了满足需求,我们选用保留原文件内容的r+
更新模式。
代码中使用fgetc
读取文件中的每个字符,若读到字符H
,则把这个字符使用fputc
修改为h
。fgetc
读取到字符H
后,文件指针已经指向了下一个字符。所以,若读取到字符H
,需要将文件指针向前移动一个字节,再进行修改。
对于以更新模式
+
开的文件,这里有一个必须要注意的地方:
- 文件从写操作转换为读操作前,必须使用
fflush
,fseek
,rewind
其中一个函数。 - 文件从读操作转换为写操作前,必须使用
fseek
,rewind
其中一个函数。
在代码中读写操作转换的地方加入必要函数。如果仅需要读写操作转换,但无需变动文件指针。可以在当前位置处偏移0字节。
fseek(pFile, 0, SEEK_CUR);
#include <stdio.h>
void fileEofOrError(FILE* pFile)
if (feof(pFile) != 0) // 测试文件是否结尾
printf("end of file\\n");
else if (ferror(pFile) != 0) // 测试文件是否读写出错
printf("file access error\\n");
int main()
FILE* pFile = fopen("data.txt", "r+");
if (pFile == NULL)
return -1;
char ch;
while (1)
ch = fgetc(pFile);
if (ch == EOF)
fileEofOrError(pFile);
break;
if (ch == H)
// 读转写
fseek(pFile, -1, SEEK_CUR);
ch = fputc(h, pFile);
if (ch == EOF)
fileEofOrError(pFile);
break;
// 写转读
fflush(pFile);
fclose(pFile);
return 0;
读转写时已经调用过fseek
函数了。写转读时,可以使用fflush
或fseek
偏移0字节。
运行后,文件中的字符H
已修改为小写的h
。
读写字符串
将数值转为字符串保存
#include <stdio.h>
int main()
// 创建一个名为data.txt的文件
FILE* pFile = fopen("data.txt", "w");
if (pFile == NULL)
// 文件创建失败
return -1;
// 装有数值的数组
int numbers[8] = 1, 12, 123, 1234, 12345, 10, 123456, 1234567 ;
for (int i = 0; i < 8; i++)
// 将数值打印至文件,每行一个数值
fprintf(pFile, "%d\\n", numbers[i]);
// 关闭文件
fclose(pFile);
return 0;
编译并运行后,使用文本编译器打开文件data.txt
可以发现,数值已经被转为换行分隔的字符串并保存在文件中了。
若数值的十进制位数越多,字符串的字符也就越多,需要占用的空间也越大。
例如:
“1” 有1个十进制位,需要1个字节。
“12345” 有5个十进制位,需要5个字节。
“1234567” 有7个十进制位,需要7个字节。
读取字符串转为数值
#include <stdio.h>
void fileEofOrError(FILE* pFile)
if (feof(pFile) != 0) // 测试文件是否结尾
printf("end of file\\n");
else if (ferror(pFile) != 0) // 测试文件是否读写出错
printf("file access error\\n");
int main()
// 创建一个名为data.txt的文件
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
// 文件创建失败
return -1;
int numbers[8] = 0 ;
int count = 0;
while (1)
// 如果数组已经填满8个元素,则不继续读取
if (count >= 8)
printf("numbers is full\\n");
break;
int get = fscanf(pFile, "%d", &numbers[count]);
printf("%d,", get);
if (get == EOF)
fileEofOrError(pFile);
break;
count++;
putchar(\\n);
// 打印数组中的数值
for (int i = 0; i < 8; i++)
printf("%d\\n", numbers[i]);
// 关闭文件
fclose(pFile);
return 0;
判断是否读完
除了使用固定长度的循环,还可以通过函数fscanf
的返回值判断是否已经读完文件。
函数fscanf
的返回值的意义为:参数列表中成功填充的参数个数。若文件读取失败或文件结尾,将返回EOF
。
若返回EOF
,此时可以通过feof
以及ferror
函数查询具体的原因。
防止数组越界
若文件中的字符串小于8个:数组numbers
未填满,但文件已经结尾。那么fscanf
将返回EOF
指示文件结尾,并终止读取文件内容。
若文件中的字符串大于等于8个:数组numbers
已填满,但文件内还有内容,这时没有地方再放置读取上来的数据了。也必须终止读取文件内容。
输出结果
1,1,1,1,1,1,1,1,-1,end of file
1
12
123
1234
12345
10
123456
1234567
以二进制形式读写
将数值以二进制形式保存
除了将数值转为字符串保存,数值还能不经过任何处理,直接以二进制形式保存成文件。下面介绍一个新函数fwrite
,用于将数据直接写入
以上是关于C文件读写的主要内容,如果未能解决你的问题,请参考以下文章