解读文件流
C语言对文件的写入和读取涉及到流的概念,写入为输出流,读取为输入流。如何理解流的概念呢?可以把流看成流动的自来水,打开水龙头,自来水就会通过自来水管从水源流到用户家中,同样的道理,水库中的水也会通过管道流入到水源。从水源流出到用户住家为自来水的输出流,从水库流入到水源为自来水的输入流,只有这样,自来水才能源源不断地输送到用户家中。

如果把水源看成文件,用户住家为读取文件的函数,水库为写入文件的函数,就很容易理解C语言的输入与输出流了。当C语言的写入函数(水库)需要将数据写入到文件(水源)时,需要建立一条从写入函数(水库)到文件(水源)的通道,这个通道就是输入流;当C语言的读取函数(用户住家)需要读取文件(水源)时,也需要建立一条从文件(水源)到读取函数(用户住家)的通道,这个通道就是输出流。
C语言的读写函数在读写文件时,并不会直接把数据写入到文件,或直接读取到程序接收文件数据的变量,它会建立一个文件缓冲区用来存放读写的数据。当进行文件读取时,读取函数先打开数据流,将磁盘上的文件信息拷贝到缓冲区内,然后程序再从缓冲区中读取所需数据。当写入函数将数据写入文件时,并不会马上写入磁盘中,而是先写入缓冲区,只有在缓冲区已满或“关闭文件”时,才会将数据写入磁盘文件。

在C语言中,文件流是通过FILE结构体来表示的。FILE结构体在C标准库中定义,包含了文件操作的所有信息,如文件指针、缓冲区等。程序通过文件指针来访问和操作文件流。
将文本数据写入文件
文本数据写入文本文件的流程:调用fopen()函数打开或创建一个新的文件,调用fputc()函数或fputs()函数进行单字符写入或文本行写入,最后调用fclose()函数关闭文件。
函数声明
int fputc ( int character, FILE * stream );
函数将字符character写入到stream指向的文件缓冲区。若写入成功返回写入的字符,否则返回EOF。
函数声明
int fputs ( const char * str, FILE * stream );
函数将str字符串写入到stream指向的文件缓冲区,字符串的结束符不会写入到文件缓冲区。若写入成功返回非负值,否则返回EOF。
函数声明
int fprintf ( FILE * stream, const char * format, ... );
函数将格式化字符串 format写入到stream指向的文件缓冲区,如果format包含格式说明符(以%开头的子序列),则格式化format后面的附加参数,并将其插入结果字符串中,替换其各自的说明符。若写入成功,返回写入的字符数,否则返回负值。
下面的示例程序展示了如何将文本数据写入文件。
#include <stdio.h>
int main() {
// 定义文件名
const char *filename = "example.txt";
// 文本数据
const char *text = "Hello, World! This is an example text.";
// 打开文件,准备写入。如果文件不存在,则创建它。
// "w" 模式表示写入模式,会覆盖文件中的任何现有内容。
FILE *file = fopen(filename, "w");
if (file == NULL) {
// 如果文件无法打开或创建,打印错误消息并退出程序
perror("Error opening file");
return 1;
}
fputs(text, file);
// 关闭文件
fclose(file);
// 打印成功消息
printf("Text written to file successfully.\n");
return 0;
}
上述程序使用fputs()函数将指针变量text指向的文本数据写入example.txt文件,程序会在当前目录下创建一个名为 example.txt 的文件(如果该文件已经存在,其内容将被覆盖)。
注意:程序在写入文件时,需要有写入文件的权限,并且文件路径是有效的。如果文件打开或写入失败,fopen 函数将返回 NULL,程序将打印一个错误消息并退出。
读取文本文件内容到缓冲区
C语言提供了多个C函数用于读取文本文件,其中最常用的是fscanf、fgets和fread。
fscanf函数
fscanf函数用于从文件中读取格式化输入。函数原型:
int fscanf(FILE *stream, const char *format, ...);
stream 通过fopen函数返回的文件指针。
format 控制字符串,指定了读取数据的格式。
... 表示格式字符串中指定的额外参数。
例如,如果有一个包含整数的文本文件,可以使用fscanf来读取这些整数:
int number;
FILE *file = fopen("numbers.txt", "r");
if (file != NULL) {
while (fscanf(file,"%d",&number) != EOF) {
printf("%d\n", number);
}
fclose(file);
}
上述例子打开一个名为 "example.txt" 的文本文件,并使用 fscanf 从文件中读取一个十进制整数,将其存储到 number 变量中。如果读取成功,打印出读取到的整数;如果读取失败,打印一个错误消息。
fgets函数
fgets函数用于从文件中读取一行文本。函数原型:
char *fgets(char *str, int n, FILE *stream);
str 是一个字符数组,用于存储读取的文本行。
n 是要读取的最大字符数(包括空字符)。
stream 通过fopen函数返回的文件指针。
例如,可以使用fgets来逐行读取文本文件的内容:
char line[100];
FILE *file = fopen("example.txt", "r");
if (file != NULL) {
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line);
}
fclose(file);
}
上述例子打开一个名为 "example.txt" 的文本文件,并使用 fgets函数 从文件中逐行读取文本内容,将其存储到字符数组line内。如果读取成功,打印出读取到的内容。
文件随机访问
在C语言中,fseek和ftell函数用于文件随机访问,它们可以操作文件指针,实现对文件任意位置的读写操作。rewind函数将文件内部的位置指针重新指向开始。
fseek函数
fseek是C语言中的一个标准库函数,用于移动文件的位置指针到指定的位置,函数定义在 <stdio.h> 头文件中。fseek 允许程序在读取或写入文件时,不按照顺序从头到尾或从尾到头进行,而是可以直接跳转到文件的任意位置进行操作。
函数原型:
int fseek(FILE *stream, long int offset, int whence);
参数:
stream:指向 FILE结构体的指针标识一个打开的文件流。
offset:表示偏移量的长整数。它表示从 whence 指定的位置开始移动的字节数。
whence:决定了 offset 的起始位置。它有三个可能的值:
SEEK_SET:从文件开始位置计算偏移量。
SEEK_CUR:从当前文件位置计算偏移量。
SEEK_END:从文件末尾计算偏移量。
返回值:
如果函数执行成功,fseek 返回零。如果发生错误,它会返回非零值。
ftell函数
C语言中的ftell函数用于确定文件流中的当前读写位置,函数定义在<stdio.h>头文件中,主要用于处理文件操作。
函数原型:
long ftell(FILE *stream);
ftell函数接受一个指向FILE结构体的指针,函数返回一个long类型的值,表示从文件开头到当前读写指针位置的字节偏移量。如果发生错误,函数将返回-1。
ftell函数配合fseek函数可以获取文件的大小;在读取或写入大文件时,可以通过ftell函数来监控文件的读写进度;fseek函数用于设置文件流中的读写位置,ftell可以用于验证fseek是否设置成功。
rewind函数
C语言中的rewind函数将文件流中的位置指针指向流的开始,函数定义在<stdio.h>头文件中。
函数原型:
void rewind(FILE *stream);
rewind函数作用等同于fseek(stream, 0L, SEEK_SET)。
读写二进制文件
C语言提供了fread函数从文件读取二进制数据,fwrite函数将二进制数据写入到文件。
fread函数
该函数从stream输入流读取count个元素,每个元素的大小为size个字节,并存储到ptr指向的内存中。若读取成功,返回读取的元素总个数,若返回小于count的数值,则表示在读取时发生读取错误或到达文件末尾,在这种情况下,可以使用可以分别用ferror和feof进行检查。
函数原型
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
size_t是标准C库中使用typedef关键字定义的基础数据类型的别名,在64位系统中为long long unsigned int,非64位系统中为long unsigned int。
函数原型
int ferror ( FILE * stream );
函数检查是否发生了与stream关联的错误,如果有关联错误则返回不同于零的错误值,否则返回零值。
函数原型
int feof ( FILE * stream );
函数检查是否发生了与stream关联的文件结束指示符,如果设置了,则返回一个不同于零的值,否则返回零值。
下面的示例程序演示了如何使用fread函数读取一个图片文件(例如,一个PNG文件),并将其内容存储到一个缓冲区中。
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file;
char *filename = "example.png"; // 图片文件名
long filesize; // 文件大小
void *buffer; // 用于存储文件内容的缓冲区
// 打开文件
file = fopen(filename, "rb");
if (file == NULL) {
perror("Error opening file");
return EXIT_FAILURE;
}
// 移动到文件末尾以获取文件大小
fseek(file, 0, SEEK_END);
filesize = ftell(file);
// 重置文件指针到文件开头
rewind(file);
// 分配足够的内存来存储文件内容
buffer = malloc(filesize);
if (buffer == NULL) {
perror("Memory allocation failed");
fclose(file);
return EXIT_FAILURE;
}
// 使用fread读取文件内容到缓冲区
if (fread(buffer, 1, filesize, file) != filesize) {
perror("Error reading file");
free(buffer);
fclose(file);
return EXIT_FAILURE;
}
// 读取完成,关闭文件
fclose(file);
// 释放buffer占用的内存
free(buffer);
return EXIT_SUCCESS;
}
在上述示例中使用fopen函数以二进制模式("rb")打开图片文件,然后使用fseek和ftell函数来确定文件的大小,接着分配一个足够大的缓冲区来存储整个文件的内容,并使用fread函数将其读取到缓冲区中,最后关闭文件并释放缓冲区的内存。
fwrite函数
fwrite函数用于向stream指向的文件流,写入size*count字节数据,参数ptr指向待写入的二进制数据。
函数原型
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
参数:
ptr:指向某内存空间的指针,该内存空间中储存有待写入文件的数据块;参数ptr类型为void*型,说明ptr可以指向任何数据类型;
size:指定了每个待写入文件的数据项的字节大小,类型为size_t(unsigned int);
count:指定了待写入文件的数据项的个数,类型为size_t(unsigned int)型;
stream:指向FILE类型结构的指针。
返回值:
fwrite函数返回一个size_t类型的值,表示实际写入的数据元素数量。如果返回值小于count,则表示发生了错误。
下面的示例程序将一个整数数组写入到一个二进制文件中。
#include <stdio.h>
int main() {
FILE *fp;
int data[] = {1, 2, 3, 4, 5};
int count = sizeof(data) / sizeof(data[0]);
fp = fopen("data.bin", "wb"); // 以二进制写模式打开文件
if (fp == NULL) {
perror("Error opening file");
return 1;
}
size_t elements_written = fwrite(data, sizeof(int), count, fp);
if (elements_written < count) {
perror("Error writing to file");
fclose(fp);
return 1;
}
fclose(fp);
return 0;
}
fwrite函数写入的是二进制数据,不是文本数据。因此,在读取这些数据时,应该使用fread函数,并且注意数据的类型和对齐方式。
如果写入的数据类型与读取时的数据类型不匹配,或者数据的对齐方式不正确,可能会导致读取的数据与原始数据不一致。
fwrite函数不会检查文件流是否已经打开或是否已经到达文件末尾,所以在调用fwrite之前,最好先检查文件流是否有效。