C08-files

本文最后更新于:1 小时前

C文件读写

打开文件

使用 <stdio.h> 头文件中的 fopen() 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

FILE *fopen(char *filename, char *mode);

filename为文件名(包括文件路径),mode为访问模式,它们都是字符串。

文件也有不同的类型,按照数据的存储方式可以分为二进制文件和文本文件,它们的操作细节是不同的。最基本的文件打开方式有以下几种:

|控制读写权限的字符串(必须指明)|

模式 说明
“r” 以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。
“w” 以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
“a” 以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
“r+” 以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。
“w+” 以“写入/更新”方式打开文件,相当于w和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
“a+” 以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
控制读写方式的字符串(可以不写)
打开方式 说明
“t” 文本文件。如果不写,默认为”t”。
“b” 二进制文件。

读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(换句话说,不能将读写方式放在读写权限的开头)。例如:

将读写方式放在读写权限的末尾:”rb”、”wt”、”ab”、”r+b”、”w+t”、”a+t”

将读写方式放在读写权限的中间:”rb+”、”wt+”、”ab+”

其中r+和w+看似一样,都是读写,其实还是有几点区别的:

1.模式r+找不到文件不会自动新建,而w+会;

2.模式r+打开文件后,不会清除文件原数据,若直接开始写入,只会从起始位置开始进行覆盖,而w+会直接清零后,再开始读写;

模式的合法性说明:不能用大写,只能是小写,且rb+和r+b都是合法的,但br+和+rb等都是非法的,w和a也是一样的处理;

模式w的自动新建文件是有条件的,只有对应的路径存在(即文件所在的文件夹存在),文件不存在才会新建,否则是不会新建的,返回NULL

整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:
r(read):读;w(write):写;a(append):追加;t(text):文本文件;b(binary):二进制文件;+:读和写

fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。

FILE 是 <stdio.h> 头文件中的一个结构体,它专门用来保存文件信息。

如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如:
FILE *fp = fopen("demo.txt", "r");
表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针。

判断文件是否打开成功

打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,请看下面的代码:

1
2
3
4
5
FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb")) == NULL ){
printf("Fail to open file!\n");
exit(0); //退出程序(结束程序)
}

我们通过判断 fopen() 的返回值是否和 NULL 相等来判断是否打开失败:如果 fopen() 的返回值为 NULL,那么 fp 的值也为 NULL,此时 if 的判断条件成立,表示文件打开失败。

关闭文件

文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。这个函数实际上会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。

fclose() 的用法为:
int fclose(FILE *fp);fp 为文件指针。例如:fclose(fp);

文件正常关闭时,fclose() 的返回值为0,如果关闭文件时发生错误,函数返回 EOF。
EOF 是 end of file 的缩写,表示文件末尾,是一个定义在头文件 stdio.h 中的宏,它的值是一个负数,往往是 -1。EOF 不绝对是 -1,也可以是其他负数,这要看编译器的实现。

读取&写入文件

fgetc和fputc函数用法详解(以字符形式读写文件)

以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数,分别是 fgetc() 和 fputc()。

fgetc() 的用法为:

int fgetc (FILE *fp);
fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。

举例:

1
2
3
char ch;
FILE *fp = fopen("D:\\demo.txt", "r+");
ch = fgetc(fp);

表示从D:\demo.txt文件中读取一个字符,并保存到变量 ch 中。

在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。

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. 每写入一个字符,文件内部位置指针向后移动一个字节。

判断文件读取完毕还是读取出错

feof() 函数用来判断文件内部指针是否指向了文件末尾,它的原型是:

int feof ( FILE * fp );
当指向文件末尾时返回非零值,否则返回零值。

ferror() 函数用来判断文件操作是否出错,它的原型是:

int ferror ( FILE *fp );
出错时返回非零值,否则返回零值。

fgets和fputs函数的用法详解(以字符串的形式读写文件)

fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,它的用法为:

char *fgets ( char *str, int n, FILE *fp );
str 为字符数组,n 为要读取的字符数目,fp 为文件指针。

返回值:读取成功时返回字符数组首地址,也即 str;读取失败时返回 NULL;如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。例如:

1
2
3
4
5
#define N 101
char str[N];
FILE *fp = fopen("D:\\demo.txt", "r");
fgets(str, N, fp);
表示从 D:\\demo.txt 中读取 100 个字符,并保存到字符数组 str 中。

注意,读取到的字符串会在末尾自动添加 ‘\0’,n 个字符也包括 ‘\0’。也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。

需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。这就意味着,不管 n 的值多大,fgets() 最多只能读取一行数据,不能跨行。在C语言中,没有按行读取文件的函数,我们可以借助 fgets(),将 n 的值设置地足够大,每次就可以读取到一行数据。

fputs() 函数用来向指定的文件写入一个字符串,它的用法为:

int fputs(const char *str, FILE *fp );
str 为要写入的字符串,fp 为文件指针。写入成功返回非负数,失败返回 EOF。例如:

1
2
3
char *str = "hello world";
FILE *fp = fopen("D:\\demo.txt", "at+");
fputs(str, fp);

表示把字符串 str 写入到 D:\demo.txt 文件中。

fread和fwrite的用法详解(以数据块的形式读写文件,二进制 I/O 函数)

对于 Windows 系统,使用 fread() 和 fwrite() 时应该以二进制的形式打开文件,下面两个函数用于二进制输入和输出:

fread() 函数用来从指定文件中读取块数据。所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制。fread() 的原型为:

size_t fread ( void *ptr, size_t size, size_t nmemb, FILE *fp );

fwrite() 函数用来向文件中写入块数据,它的原型为:

size_t fwrite ( void * ptr, size_t size, size_t nmemb, FILE *fp );

对参数的说明:

ptr 为要写入的数据的头指针,它可以是数组、变量、结构体等。fread() 中的 ptr 用来存放读取到的数据,fwrite() 中的 ptr 用来存放要写入的数据。

size:表示要被写入的每个元素的大小,以字节为单位。

nmemb:表示要读写的数据元素的个数。

fp:表示文件指针,表示往哪里写。

理论上,每次读写 size*nmemb 个字节的数据。

size_t 是在 stdio.h 和 stdlib.h 头文件中使用 typedef 定义的数据类型,表示无符号整数,也即非负数,常用来表示数量。

返回值:返回成功读写的块数,也即 nmemb。如果返回值小于 nmemb:

对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。

对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。

数据写入完毕后,位置指针在文件的末尾,要想读取数据,必须将文件指针移动到文件开头,这要使用rewind(fp);函数。

实例

下面的实例演示了 fwrite() 函数的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>

int main ()
{
FILE *fp;
char str[] = "This is runoob.com";

fp = fopen( "file.txt" , "w" );
fwrite(str, sizeof(str) , 1, fp );

fclose(fp);

return(0);
}

编译并运行上面的程序,这将创建一个文件 file.txt,它的内容如下:

This is runoob.com

下面的实例演示了 fread() 函数的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <string.h>

int main()
{
FILE *fp;
char c[] = "This is runoob";
char buffer[20];

/* 打开文件用于读写 */
fp = fopen("file.txt", "w+");

/* 写入数据到文件 */
fwrite(c, strlen(c) + 1, 1, fp);

/* 查找文件的开头 */
fseek(fp, 0, SEEK_SET);

/* 读取并显示数据 */
fread(buffer, strlen(c)+1, 1, fp);
printf("%s\n", buffer);
fclose(fp);

return(0);
}

编译并运行上面的程序,这将创建一个文件 file.txt,然后写入内容 This is runoob。接下来我们使用 fseek() 函数来重置写指针到文件的开头,文件内容如下所示:

This is runoob

fprintf和fscanf的用法详解(格式化读写文件)

fprintf函数它的原型是:

int fprintf (FILE* stream, const char*format, [argument])

该函数是一个格式化写入文件的库函数,可以看到,除了长得和printf函数很像以外,参数也非常像,多了一个第一个参数,文件指针,即第一步打开文件时得到的文件指针,后面的参数和printf一样,按照指定的格式将数据写入文件。

返回值

如果成功,则返回写入的字符总数,否则返回一个负数。

实例

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE * fp;
fp = fopen ("file.txt", "w+");
fprintf(fp, "%s %s %d", "hello", "world", 111);
fclose(fp);
return(0);
}

编译并运行上面的程序,这将创建文件 file.txt,它的内容如下:

hello world 111

fscanf函数可以从文件里读数据,它的函数原型如下:

int fscanf(FILE *stream, char *format[,argument...]);

作为格式化写数据函数,它的参数同样比scanf也多一个参数,即第一个参数文件指针,表示读取的文件目标,其余参数和scanf一样,按照相应的格式进行读取,返回值表示读取数据的字节数。

返回值

如果成功,该函数返回成功匹配和赋值的个数。但是在遇到第一个空格和换行符时,它会停止读取。如果到达文件末尾或发生读错误,则返回 EOF。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>


int main()
{
char str1[10], str2[10];
int num;
FILE * fp;

fp = fopen ("file.txt", "w+");
fputs("hello world 111", fp);

rewind(fp);
fscanf(fp, "%s %s %d", str1, str2, &num);

printf("Read String1 |%s|\n", str1 );
printf("Read String2 |%s|\n", str2 );
printf("Read Integer |%d|\n", num );

fclose(fp);

return(0);
}

编译并运行上面的程序,这将产生以下结果:

1
2
3
Read String1 |hello|
Read String2 |world|
Read Integer |111|

rewind和fseek函数的用法详解(随机读写文件)

移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek()。

rewind() ,用于将文件指针重新指向文件的开头,同时清除和文件流相关的错误和eof标记,相当于调用fseek(stream, 0, SEEK_SET),它的原型为:

void rewind ( FILE *stream );

参数

stream 为指向 FILE 对象(已打开文件)的指针,该 FILE 对象标识流。

例如,把一个文件的内容显示在屏幕上,并同时复制到另一个文件。

1
2
3
4
5
6
7
8
9
10
11
12
#include "stdio.h"
void main()
{
FILE *fp1, *fp2;
fp1 = fopen("file1.c", "r"); // 源文件
fp2 = fopen("file2.c", "w"); // 复制到file2.c
while(!feof(fp1)) putchar(fgetc(fp1)); // 显示到屏幕上
rewind(fp1); // fp回到开始位置
while(!feof(fp1)) fputc(fgetc(fp1), fp2);
fclose(fp1);
fclose(fp2);
}

fseek() 用来将位置指针移动到任意位置,它的原型为:

int fseek ( FILE *steam, long offset, int whence );

参数说明:

  1. steam 为文件指针,也就是被移动的文件。

  2. offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。offset 为正时,向后移动;offset 为负时,向前移动。

  3. whence 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示:

起始点 常量名 常量值
文件开头 SEEK_SET 0
当前位置 SEEK_CUR 1
文件末尾 SEEK_END 2

例如,把位置指针移动到离文件开头100个字节处:

fseek(fp, 100, 0);

返回值:如果成功,则该函数返回零,否则返回非零值。

值得说明的是,fseek() 一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!