C语言 - 文件读写
在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立连接的过程。
打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。
标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的,可直接使用。
1. 打开文件
使用 <stdio.h> 头文件中的 fopen() 函数即可打开文件,它的用法为:
FILE *fopen(char *filename, char *mode);
参数说明
filename:
为文件名(包括文件路径)
mode:
为打开方式,它们都是字符串
fopen() 函数的返回值
fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。
FILE 是 <stdio.h> 头文件中的一个结构体,它专门用来保存文件信息。我们不用关心 FILE 的具体结构,只需要知道它的用法就行。
如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如:
FILE *fp = fopen("demo.txt", "r");
表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针。
再来看一个例子:
FILE *fp = fopen("D:\\demo.txt","rb+");
表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写。
判断文件是否打开成功
打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,请看下面的代码:
FILE *fp;
fp = fopen("d:\\demo.txt", "rt")
if( fp == NULL )
{
printf("Fail to open file!\n");
exit(0); //退出程序(结束程序)
}
我们通过判断 fopen() 的返回值是否和 NULL 相等来判断是否打开失败:如果 fopen() 的返回值为 NULL,那么 fp 的值也为 NULL,此时 if 的判断条件成立,表示文件打开失败。
以上代码是文件操作的规范写法,读者在打开文件时一定要判断文件是否打开成功,因为一旦打开失败,后续操作就都没法进行了,往往以“结束程序”告终。
fopen() 函数的打开方式
不同的操作需要不同的文件权限。例如,只想读取文件中的数据的话,“只读”权限就够了;既想读取又想写入数据的话,“读写”权限就是必须的了。
另外,文件也有不同的类型,按照数据的存储方式可以分为二进制文件和文本文件,它们的操作细节是不同的。
在调用 fopen() 函数时,这些信息都必须提供,称为“文件打开方式”。最基本的文件打开方式有以下几种:
控制读写权限的字符串(必须指明) | |
---|---|
打开方式 | 说明 |
"r" | 以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。 |
"w" | 以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 |
"a" | 以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 |
"r+" | 以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。 |
"w+" | 以“写入/更新”方式打开文件,相当于w 和r+ 叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 |
"a+" | 以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 |
控制读写方式的字符串(可以不写) | |
打开方式 | 说明 |
"t" | 文本文件。如果不写,默认为"t" 。 |
"b" | 二进制文件。 |
调用 fopen() 函数时必须指明读写权限,但是可以不指明读写方式(此时默认为"t"
)。
读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(换句话说,不能将读写方式放在读写权限的开头)。例如:
- 将读写方式放在读写权限的末尾:"rb"、"wt"、"ab"、"r+b"、"w+t"、"a+t"
- 将读写方式放在读写权限的中间:"rb+"、"wt+"、"ab+"
整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:
- r(read):读
- w(write):写
- a(append):追加
- t(text):文本文件
- b(binary):二进制文件
- +:读和写
2. 关闭文件
文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的用法为:
int fclose(FILE *fp);
fp 为文件指针。例如:
fclose(fp);
文件正常关闭时,fclose() 的返回值为0,如果返回非零值则表示有错误发生。
3. 文件读写
3.1 fgetc和fputc
fgetc() 和 fputc()是以字符形式读写文件
在C语言中,读写文件比较灵活,既可以每次读写一个字符,也可以读写一个字符串,甚至是任意字节的数据(数据块)。本节介绍以字符形式读写文件。
以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数,分别是 fgetc() 和 fputc()。
字符读取函数 fgetc
fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符。fgetc() 的用法为:
int fgetc (FILE *fp);
fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF
。
EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1。fgetc() 的返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)。
EOF 不绝对是 -1,也可以是其他负数,这要看编译器的实现。
fgetc() 的用法举例:
char ch;
FILE *fp = fopen("D:\\demo.txt", "r+");
ch = fgetc(fp);
表示从D:\\demo.txt
文件中读取一个字符,并保存到变量 ch 中。
在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。
注意:这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。
【示例】在屏幕上显示 D:\\demo.txt 文件的内容。
#include<stdio.h>
int main()
{
FILE *fp;
char ch;
//如果文件不存在,给出提示并退出
if( (fp=fopen("D:\\demo.txt","rt")) == NULL )
{
puts("Fail to open file!");
exit(0);
}
//每次读取一个字节,直到读取完毕
while( (ch=fgetc(fp)) != EOF )
{
putchar(ch);
}
putchar('\n'); //输出换行符
fclose(fp);
return 0;
}
运行结果:
在D盘下创建 demo.txt 文件,输入任意内容并保存,运行程序,就会看到刚才输入的内容全部都显示在屏幕上。
该程序的功能是从文件中逐个读取字符,在屏幕上显示,直到读取完毕。
程序第 13 行是关键,while 循环的条件为(ch=fgetc(fp)) != EOF
。fget() 每次从位置指针所在的位置读取一个字符,并保存到变量 ch,位置指针向后移动一个字节。当文件指针移动到文件末尾时,fget() 就无法读取字符了,于是返回 EOF,表示文件读取结束了。
对 EOF 的说明
EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回 EOF 时,到底是文件读取完毕了还是读取出错了?我们可以借助 stdio.h 中的两个函数来判断,分别是 feof() 和 ferror()。
feof() 函数用来判断文件内部指针是否指向了文件末尾,它的原型是:
int feof ( FILE * fp );
当指向文件末尾时返回非零值,否则返回零值。
ferror() 函数用来判断文件操作是否出错,它的原型是:
int ferror ( FILE *fp );
出错时返回非零值,否则返回零值。
需要说明的是,文件出错是非常少见的情况,上面的示例基本能够保证将文件内的数据读取完毕。如果追求完美,也可以加上判断并给出提示:
#include<stdio.h>
int main()
{
FILE *fp;
char ch;
//如果文件不存在,给出提示并退出
if( (fp=fopen("D:\\demo.txt","rt")) == NULL )
{
puts("Fail to open file!");
exit(0);
}
//每次读取一个字节,直到读取完毕
while( (ch=fgetc(fp)) != EOF )
{
putchar(ch);
}
putchar('\n'); //输出换行符
if(ferror(fp))
{
puts("读取出错");
}else
{
puts("读取成功");
}
fclose(fp);
return 0;
}
这样,不管是出错还是正常读取,都能够做到心中有数。
字符写入函数 fputc
fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符。fputc() 的用法为:
int fputc ( int ch, FILE *fp );
ch 为要写入的字符,fp 为文件指针。fputc() 写入成功时返回写入的字符,失败时返回 EOF,返回值类型为 int 也是为了容纳这个负数。例如:
fputc('a', fp);
或者:
char ch = 'a';
fputc(ch, fp);
表示把字符 'a' 写入fp所指向的文件中。
两点说明
1) 被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,并将写入的字符放在文件开头。如需保留原有文件内容,并把写入的字符放在文件末尾,就必须以追加方式打开文件。不管以何种方式打开,被写入的文件若不存在时则创建该文件。
2) 每写入一个字符,文件内部位置指针向后移动一个字节。
【示例】从键盘输入一行字符,写入文件。
#include<stdio.h>
int main()
{
FILE *fp;
char ch;
//判断文件是否成功打开
if( (fp=fopen("D:\\demo.txt","wt+")) == NULL )
{
puts("Fail to open file!");
exit(0);
}
printf("Input a string:\n");
//每次从键盘读取一个字符并写入文件
while ( (ch=getchar()) != '\n' )
{
fputc(ch,fp);
}
fclose(fp);
return 0;
}
运行程序,输入一行字符并按回车键结束,打开D盘下的 demo.txt 文件,就可以看到刚才输入的内容。
程序每次从键盘读取一个字符并写入文件,直到按下回车键,while 条件不成立,结束读取。
3.2 fgets和fputs
fgets() 和 fputs()函数是以字符串形式读写文件
fgetc() 和 fputc() 函数每次只能读写一个字符,速度较慢;实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率。
读字符串函数 fgets
fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,它的用法为:
char *fgets ( char *str, int n, FILE *fp );
str 为字符数组,n 为要读取的字符数目,fp 为文件指针。
返回值:读取成功时返回字符数组首地址,也即 str;读取失败时返回 NULL;如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。
注意,读取到的字符串会在末尾自动添加 '\0',n 个字符也包括 '\0'。也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。例如:
#define N 101
char str[N];
FILE *fp = fopen("D:\\demo.txt", "r");
fgets(str, N, fp);
表示从 D:\\demo.txt 中读取 100 个字符,并保存到字符数组 str 中。
需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管 n 的值多大,fgets() 最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助 fgets(),将 n 的值设置地足够大,每次就可以读取到一行数据。
【示例】一行一行地读取文件。
#include <stdio.h>
#include <stdlib.h>
#define N 100
int main()
{
FILE *fp;
char str[N+1];
if( (fp=fopen("d:\\demo.txt","rt")) == NULL )
{
puts("Fail to open file!");
exit(0);
}
while(fgets(str, N, fp) != NULL)
{
printf("%s", str);
}
fclose(fp);
return 0;
}
那么运行结果为:
fgets() 遇到换行时,会将换行符一并读取到当前字符串。该示例的输出结果之所以和 demo.txt 保持一致,该换行的地方换行,就是因为 fgets() 能够读取到换行符。而 gets() 不一样,它会忽略换行符。
写字符串函数 fputs
fputs() 函数用来向指定的文件写入一个字符串,它的用法为:
int fputs( char *str, FILE *fp );
str 为要写入的字符串,fp 为文件指针。写入成功返回非负数,失败返回 EOF。例如:
char *str = "http://c.biancheng.net";
FILE *fp = fopen("D:\\demo.txt", "at+");
fputs(str, fp);
表示把把字符串 str 写入到 D:\\demo.txt 文件中。
【示例】向上例中建立的 d:\\demo.txt 文件中追加一个字符串。
#include<stdio.h>
int main()
{
FILE *fp;
char str[102] = {0}, strTemp[100];
if( (fp=fopen("D:\\demo.txt", "at+")) == NULL )
{
puts("Fail to open file!");
exit(0);
}
printf("Input a string:");
gets(strTemp);
strcat(str, "\n");
strcat(str, strTemp);
fputs(str, fp);
fclose(fp);
return 0;
}
运行程序,输入一些字符串,打开 D:\\demo.txt,文件内容为:
3.3 fread和fwrite
fread() 和 fwrite()函数是以数据块形式读写文件
fread() 和 fwrite() 两个函数用于二进制输入和输出。
fgets() 有局限性,每次最多只能从文件中读取一行内容,因为 fgets() 遇到换行符就结束读取。如果希望读取多行内容,需要使用 fread() 函数;相应地写入函数为 fwrite()。
对于 Windows 系统,使用 fread() 和 fwrite() 时应该以二进制的形式打开文件。
fread() 函数用来从指定文件中读取块数据。所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制。fread() 的原型为:
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
fwrite() 函数用来向文件中写入块数据,它的原型为:
size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );
对参数的说明:
- ptr 为内存区块的指针,它可以是数组、变量、结构体等。fread() 中的 ptr 用来存放读取到的数据,fwrite() 中的 ptr 用来存放要写入的数据。
- size:表示每个数据块的字节数。
- count:表示要读写的数据块的块数。
- fp:表示文件指针。
- 理论上,每次读写 size*count 个字节的数据。
size_t 是在 stdio.h 和 stdlib.h 头文件中使用 typedef 定义的数据类型,表示无符号整数,也即非负数,常用来表示数量。
返回值:返回成功读写的块数,也即 count。如果返回值小于 count:
- 对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
- 对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。
【示例】从键盘输入一个数组,将数组写入文件再读取出来。
#include<stdio.h>
#define N 5
int main()
{
//从键盘输入的数据放入a,从文件读取的数据放入b
int a[N], b[N];
int i, size = sizeof(int);
FILE *fp;
if( (fp=fopen("D:\\demo.txt", "rb+")) == NULL ){ //以二进制方式打开
puts("Fail to open file!");
exit(0);
}
//从键盘输入数据 并保存到数组a
for(i=0; i<N; i++){
scanf("%d", &a[i]);
}
//将数组a的内容写入到文件
fwrite(a, size, N, fp);
//将文件中的位置指针重新定位到文件开头
rewind(fp);
//从文件读取内容并保存到数组b
fread(b, size, N, fp);
//在屏幕上显示数组b的内容
for(i=0; i<N; i++){
printf("%d ", b[i]);
}
printf("\n");
fclose(fp);
return 0;
}
运行结果:
打开 D:\\demo.txt,发现文件内容根本无法阅读。这是因为我们使用"rb+"
方式打开文件,数组会原封不动地以二进制形式写入文件,一般无法阅读。
数据写入完毕后,位置指针在文件的末尾,要想读取数据,必须将文件指针移动到文件开头,这就是rewind(fp);
的作用。
文件的后缀不一定是 .txt,它可以是任意的,你可以自己命名,例如 demo.ddd、demo.doc、demo.diy 等。
【示例】从键盘输入两个学生数据,写入一个文件中,再读出这两个学生的数据显示在屏幕上。
#define _CRT_SECURE_NO_WARNINGS //如果你需要继续使用 strcpy 并且理解其风险,可以在代码的开始部分添加宏定义 #define _CRT_SECURE_NO_WARNINGS 来关闭这个特定警告。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 2
struct stu
{
char name[10]; //姓名
int num; //学号
int age; //年龄
float score; //成绩
}boya[N], boyb[N], * pa, * pb;
int main()
{
FILE* fp;
int i;
pa = boya;
pb = boyb;
if ((fp = fopen("d:\\demo.txt", "wb+")) == NULL)
{
puts("Fail to open file!");
exit(0);
}
//从键盘输入数据
printf("Input data:\n");
for (i = 0; i < N; i++, pa++)
{
scanf("%s %d %d %f", pa->name, &pa->num, &pa->age, &pa->score);
}
//将数组 boya 的数据写入文件
fwrite(boya, sizeof(struct stu), N, fp);
//将文件指针重置到文件开头
rewind(fp);
//从文件读取数据并保存到数据 boyb
fread(boyb, sizeof(struct stu), N, fp);
//输出数组 boyb 中的数据
for (i = 0; i < N; i++, pb++)
{
printf("%s %d %d %f\n", pb->name, pb->num, pb->age, pb->score);
}
fclose(fp);
return 0;
}
运行结果:
3.4 fscanf和fprintf
fscanf:按格式从文件中读取指定内容,与scanf函数类似
函数原型:
int fscanf(
FILE* const _Stream,//文件标识符
char const* const _Format, //指定读取格式,与printf格式相同
... //存放读取内容的缓存区
)
示例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//#include <cstdio>
int main()
{
FILE* file = fopen("test.txt", "r"); //已只读方式打开
if (file == NULL)
{ //打开失败直接返回
printf("文件打开失败!\n");
return -1;
}
int d; //存放数字
char buf[255]; //存放字符串的缓存区
char buff[255];
int a;
int num = fscanf(file, "%d %s %s %d", &d, buf, buff,&a); //按格式读取文件内容
printf("num = %d\n", num);
printf("d = %d , buf = %s, buf = %s ,a = %d", d, buf, buff, a);
}
运行结果:
fprintf:向文件中写入指定格式字符串,与pritnf函数类似
函数原型:
int fprintf(
FILE* _Stream, //文件标识符
char const* const _Format, //写入格式 与printf使用方式相同
...
)
示例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
FILE* file = fopen("test.txt", "w");
if (file == NULL)
{
printf("文件打开失败!\n");
return -1;
}
fprintf(file, "%d %s", 888, "\ntest"); //向fille标识的文件写入数字888和字符串test
}
4. 文件指针偏移
对于文件的读写方式,C 语言不仅支持简单地顺序读写方式,还支持随机读写(即只要求读写文件中某一指定的部分)。对顺序读写方式来说,随机读写方式需要将文件内部的位置指针移动到需要读写的位置再进行读写,这通常也被称为文件的定位。
对于文件的定位,可以通过 rewind、fseek 与 ftell 函数来完成。
fseek()//是一个C标准库函数,用于在文件中定位文件指针的位置。
ftell()//是一个C标准库函数,用于获取文件指针的当前位置(偏移量)。
其中,rewind 函数用于将文件内部的位置指针重新指向一个流(数据流或者文件)的起始位置。这里需要注意的是,这里的“指针”表示的不是文件指针,而是文件内部的位置指针。即随着对文件的读写,文件的位置指针(指向当前读写字节)向后移动。而文件指针指向整个文件,如果不重新赋值,文件指针不会发生改变。
rewind 函数的一般原型如下所示:
void rewind(FILE *fp);
从上面的函数原型可以看出,rewind 并没有返回值,因此也无法做安全性检查。如下面的示例代码所示:
FILE *fp = NULL;
fp = fopen("Test.txt","r");
if(fp == NULL)
{
puts("Fail to open file!");
exit(0);
}
rewind(fp);
在上面的示例代码中,由于 rewind 函数没有返回值,所以我们很难判断“rewind(fp)”是否执行成功。因此,应该尽量使用 fseek 来替换 rewind 函数,从而以验证流已经成功地回绕。如下面的示例代码所示:
if (fseek(fp, 0L, SEEK_SET) != 0)
{
}
相对于 rewind 函数而言,fseek 函数的功能更加强大,它用来设定文件的当前读写位置,从而可以实现以任意顺序访问文件的不同位置,以实现文件的随机访问。其函数的一般原型如下所示:
int fseek(FILE *fp,long offset,int from);
如果该函数执行成功,fp 将指向以 from 为基准,偏移 offset 个字节的位置,函数的返回值为 0;如果该函数执行失败(比如 offset 超过文件自身大小),则不改变 fp 指向的位置,函数的返回值为 -1,并设置 errno 的值,可以用 perror 函数来输出错误信息。
对于 fseek 函数中的参数:
- 第一个参数 fp 为文件指针;
- 第二个参数 offset 为偏移量,它表示要移动的字节数,整数表示正向偏移,负数表示负向偏移;
- 第三个参数 from 表示设定从文件的哪里开始偏移,取值范围如表 1 所示。
起始点 | 表示符号 | 数字表示 |
---|---|---|
文件首 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
由表 1 可知:
- SEEK_SET 表示从文件起始位置增加 offset 个偏移量为新的读写位置;
- SEEK_CUR 表示从目前的读写位置增加 offset 个偏移量为新的读写位置;
- SEEK_END 表示将读写位置指向文件尾后,再增加 offset 个偏移量为新的读写位置。
当 from 值为 SEEK_CUR 或 SEEK_END 时,参数 offset 允许出现负值。如下面的示例代码所示:
/*将读写位置移动到离文件开头100字节处*/
fseek(fp,100L,0);
/*将读写位置移动到离文件当前位置100字节处*/
fseek(fp,100L,1);
/*将读写位置退回到离文件结尾100字节处*/
fseek(fp,-100L,2);
/*将读写位置移动到文件的起始位置*/
fseek(fp,0L,SEEK_SET);
/*将读写位置移动到文件尾*/
fseek(fp,0L,SEEK_END);
不难发现,上面的语句“(void)fseek(fp,0L,SEEK_SET);”的作用实际上等同于 rewind 函数。与此同时,在使用 fseek 函数时,还应该注意如下 3 点。
- 首先,调用 fseek 函数的文件指针 fp 应该指向已经打开的文件,否则将会出现错误。
- 其次,fseek 函数一般用于二进制文件,当然也可以用于文本文件。需要特别注意的是,当 fseek 函数用于文本文件操作时,一定要注意回车换行的情况。因为在一般浏览工具(如 UltraEdit)中,回车换行被视为两个字符 0x0D 和 0x0A,但真实的文件读写和定位却按照一个字符 0x0A 进行处理。因此,在碰到此类问题时,可以考虑将文件整个读入内存,然后在内存中手工插入 0x0D的方法,这样可以达到较好的处理效果。
- 最后,fseek 函数只返回执行的结果是否成功,并不返回文件的读写位置。因此,你可以使用 ftell 函数来取得当前文件的读写位置。
ftell 函数的原型为:
long ftell(FILE *fp);
该函数用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁前后移动,程序不容易确定文件的当前位置。在使用 fseek 函数后,再调用函数 ftell 就能非常容易地确定文件的当前位置。如下面的示例代码所示:
5. 获取文件大小
方式一:使用C标准库函数fseek()和ftell()获取文件大小。
不推荐使用此方式
当文件太大时,移动文件光标指针会非常耗时!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int getFileSize(const std::string& filename)
{
FILE* file = std::fopen(filename.c_str(), "rb");
if (!file)
{
std::cerr << "Failed to open file: " << filename << std::endl;
return -1; // or throw an exception
}
fseek(file, 0, SEEK_END);// 移动到文件末尾
long size = ftell(file);// 获取文件大小
fseek(file, 0, SEEK_SET); // 移动到文件开头
fclose(file);
return static_cast<int>(size);
}
int main()
{
size_t filesize = getFileSize("test.txt");
cout << "文件大小:" << filesize << endl;
return 0;
}
6. 判断文件读取结束
在C语言中,文件读取结束的判断依赖于文件的类型和读取数据的方式。对于文本文件,可以通过返回的读取字符来判断是否到达文件末尾。
6.1 文本文件
判断文本文件读取是否结束
方式一:
使用fgetc() :fgetc函数返回EOF时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
#include <stdio.h>
int main()
{
FILE *file = fopen("example.txt", "r");
if (file == NULL)
{
perror("Error opening file");
return -1;
}
int ch;
while ((ch = fgetc(file)) != EOF)
{
// 处理读取到的字符
printf("%c", ch);
}
fclose(file);
return 0;
}
使用fgets() :fgets函数返回NULL时,可能是读取数据时发生错误,也可能是已经读取到文件末尾了。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE* fp = fopen("test.txt", "rt");//r:以读的方式打开 t:文本文件
if (fp == NULL)
{
puts("Fail to open file!");
exit(0);
}
char sendBuf[1024] = { 0 };
// 读取文件
while (fgets(sendBuf, sizeof(sendBuf), fp) != NULL)// 如果fgets()返回NULL,意味着已到达文件末尾或发生错误,如果不为NULL,表示还没有到达文件末尾。
{
// 处理读取到的字符
printf("%s", sendBuf);
}
//关闭文件
fclose(fp);
}
方法二:使用 feof() 函数判断文件读取结束
feof
函数用于检查文件是否到达末尾。它是一个标准C库函数,在 <stdio.h>
头文件中声明。
函数原型如下:
int feof(FILE *stream);
当文件指针位于文件末尾时,feof
返回非零值(真),否则返回0(假)。
使用 feof
函数时,通常配合 fgetc
或 fscanf
等读取函数使用,因为这些读取函数在到达文件末尾时不会返回错误,而是返回特定的值(如 EOF
)。
下面是一个使用 feof
的示例代码:
#include <stdio.h>
int main()
{
FILE *file = fopen("example.txt", "r");
if (file == NULL)
{
perror("Error opening file");
return 1;
}
int ch;
while (!feof(file))
{
ch = fgetc(file);
if (ferror(file))
{
printf("读取文件时发生错误\n");
break;
}
printf("%c", ch);
}
fclose(file);
return 0;
}
在这个例子中,我们尝试打开并读取名为example.txt
的文件。使用feof(file)
在循环中检查是否到达文件末尾。
如果没到达末尾,就使用fgetc(file)
读取下一个字符。如果发生错误,我们使用ferror(file)
进行检查,并打印错误信息。最后,关闭文件并退出程序。
或者这样写:
区别是下面使用了fgets()读文件,上面使用了fgetc()读文件。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
FILE* file = fopen("test.txt", "r");
if (file == NULL)
{
perror("Error opening file");
return 1;
}
char sendBuf[1024] = { 0 };
while (!feof(file))
{
fgets(sendBuf, sizeof(sendBuf), file);
if (ferror(file))
{
printf("读取文件时发生错误\n");
break;
}
printf("%s", sendBuf);
}
fclose(file);
return 0;
}
6.2 二进制文件
判断二进制文件读取是否结束
方法一:使用fgetc() 和 fputc()函数读二进制文件和写二进制文件。fgetc()函数读文件速度非常慢,因为是按一个字符一个字符读的,所以不推荐使用。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
FILE* file;
int byte;
// 打开二进制文件
file = fopen("蓝光菜单.png", "rb");
if (file == NULL)
{
perror("Error opening file");
return 1;
}
FILE* fp2 = fopen("demo.png", "wb");
//判断文件是否成功打开
if (fp2 == NULL)
{
puts("Fail to open file!");
return 1;
}
// 读取文件直到到达末尾
while (!feof(file))
{
// 读取一个字节
byte = fgetc(file);
// 打印
putchar(byte);
// 写文件
if (fp2 != NULL)
{
fputc(byte, fp2);
}
}
// 检查是否由于已到达文件末尾而退出循环
if (feof(file))
{
printf("已到达文件末尾\n");
}
// 关闭文件
fclose(file);
fclose(fp2);
return 0;
}
方法二:推荐使用 fread() 和 fwrite() 进行二进制的形式读写文件。
fread()
函数用于从文件中读取数据,如果读取的字节数为0,则表示到达文件末尾。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
printf("打开文件失败\n");
return -1;
}
char buffer[1024];
size_t bytes;
while ((bytes = fread(buffer, sizeof(char), 1024, fp)) > 0)
{
// 处理读取的数据
printf("bytes = %d\n", bytes);
printf("buffer : %s", buffer);
}
if (feof(fp))
{
printf("文件已到达末尾\n");
}
fclose(fp);
return 0;
}