可以将程序中的数据保存为一个文件。待下次重新启动程序时,从之前保存的文件中提取数据。这样,程序就不会在重启后失忆了。
#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
如果,现在想在第一行后,再增加更多的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
模式从单一的模式,升级为读写均可模式。
对于以更新模式 + 打开的文件,这里有一个必须要注意的地方:
fflush
,fseek
,rewind
其中一个函数。fseek
,rewind
其中一个函数。fprintf
int fprintf (FILE * stream, const char * format, ...);
若需要将字符串输出到文件内,有一个非常类似于printf
的函数fprintf
。它就相当于在函数printf
第一个参数前,加了一个文件结构指针参数,用于指明操作哪个文件。其他的使用方法和printf
几乎一致。
putc
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
。两类具有代表性的系统分别使用了其中一种惯例:
\r
加\n
。\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
相当于在函数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
。
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;
}
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
,是文件结尾,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()
函数用于向文件中写入一串字符串。
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)
语句已经运行过了。但是,现在打开文件,文件内没有任何内容。
让暂停继续。程序结束后,文件内出现了内容。
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
,用于将数据直接写入到文件。
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
输入:
const void *buffer
待写入文件数据的首地址
size_t size
每一块数据的大小
size_t count
一共有多少块数据
FILE *stream
文件结构指针
返回值:
size_t
成功写入多少块数据。
第一个参数 buffer 为待写入文件的数据的首地址。数组 numbers 出现在表达式中将会转为首元素指针,指向第一个int
元素,类型为int *
,其内部保存了数组的首地址。函数参数buffer
为void *
类型的指针,而void *
类型的指针可以接收任何类型的指针。int *
类型的指针在传递给void *
类型的指针时,指针类型信息将丢失,仅留下首地址信息。
fwrite
会把待写入数据分为count
块,每一块size
个字节。例如:
numbers
分为1块,每一块sizeof(numbers)
大小。numbers
分为8块,每一块sizeof(int)
大小。两种方式都能将整个数组写入文件,以下是对应的代码。
// 将数组numbers分为1块,每一块`sizeof(numbers)`大小
fwrite(numbers, sizeof(numbers), 1, pFile);
// 将数组numbers分为8块,每一块sizeof(int)大小
fwrite(numbers, sizeof(int), 8, pFile);
而参数类型size_t
为sizeof
关键词返回值的类型,通常是unsigned int
类型的别名。
参数stream
为使用fopen
函数打开文件时返回的文件结构指针。
fwrite
将返回成功写入文件的数据块的数量。
numbers
分为1块,写入成功将返回1,写入失败将返回0。numbers
分为8块,写入成功将返回8,部分成功将返回小于8大于0的数值,写入失败将返回0。
字节0A
是数值int
类型的数值0A 00 00 00
的前1个字节,刚好为\n
的ASCII码。在文本模式下,字符\n
将会被自动替换为\n\r
,再输出到文件中。其ASCII码为十六进制0D 0A
。因此,数据0A 00 00 00
前会出现一个OD
。很显然,这里的字节0A
并不代表换行,而是与其他3个十六进制字节一起表示一个int
类型的数据。因此,以二进制形式存储为文件并不需要做这个转换。
默认情况下,文件是以文本模式打开的,文本模式下会做换行符的转换。而在函数fopen
的第二个参数中,添加字符b
。以二进制模式打开文件,二进制模式不进行换行符的转换。
FILE* pFile = fopen("data.txt", "w");
修改为
FILE* pFile = fopen("data.txt", "wb");
现在,字节0A
前不会再自动添加0D
了。
与之前讨论的直接将数据写入文件的fwrite
函数对应,fread
函数可以将文件中的数据直接读取到内存当中。由于现在需要读取文件,函数fopen
的第二个参数,文件打开模式改为r
。
size_t fread(void* buffer, size_t size, size_t count, FILE* stream);
输入:
const void* buffer
接收数据的首地址
size_t size
每一块数据的大小
size_t count
一共有多少块数据
FILE* stream
文件结构指针
返回值 :
size_t
成功读取多少块数据。
函数fread
的各个参数用法类似于fwrite
函数,不同的是将写入换成了读取。它将从文件中读取count
块数据,每一块数据size
大小,读取出来的数据存放到buffer
为首地址的空间中。返回值为成功读取的块的数量。
#include <stdio.h>
int main()
{
// 创建一个名为data.txt的文件
FILE* pFile = fopen("data.txt", "r");
if (pFile == NULL)
{
// 文件创建失败
return -1;
}
// 接收数值的数组
int numbers[8] = { 0 };
// 每块读取sizeof(numbers)字节,一共读取1块
fread(numbers, sizeof(numbers), 1, pFile);
for (int i = 0; i < 8; i++)
printf("%d\n", numbers[i]);
// 关闭文件
fclose(pFile);
return 0;
}
除了读取固定大小的数据,我们也能让fread
每次读取一字节数据,直到文件结尾或接收的空间存满为止。
#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 };
// 接收数据的首地址
char* p = (char*)(numbers);
// 已读取的字节
int count = 0;
while (1)
{
// 如果数组已经填满8个元素,则不继续读取
if (count >= sizeof(numbers))
{
printf("numbers is full\n");
break;
}
// 每块读取1字节,一共读取1块
int get = fread(p, 1, 1, pFile);
if (get == EOF)
{
fileEofOrError(pFile);
break;
}
p++;
count++;
}
for (int i = 0; i < 8; i++)
printf("%d\n", numbers[i]);
// 关闭文件
fclose(pFile);
return 0;
}
由于fread
函数每次读取1字节并存放到第一个参数指示的地址当中。因此,在下一次读取前,需要将接收数据的地址向后移动一字节。我们将数组首地址存放到一个char *
类型的指针p
当中。fread
函数将读取到的1字节数据,存放到指针 p 中保存的地址当中。在下一次读取开始前,让指针p++
,使得指针中保存的地址向后移动1字节。
注意,文件中的数据可能超过numbers
数组的长度,因此,需要在程序中判断已读取到的数据大小。若数组已经装满,也不应该继续读取了,否则会造成数组越界。代码中使用count
记录已经读取到的数据大小,当count
大于数组长度sizeof(numbers)
时,读取应当停止。