C语言 文件输入输出

本文将介绍在 C 语言中如何创建、打开、关闭文本文件或二进制文件。

一、文件的基本知识

1.什么是文件

(1) 程序文件和数据文件

文件有不同的类型,在程序设计中,主要用到两种文件:

  • 程序文件:存储程序代码
  • 数据文件:文件的内容是供程序运行时读写的数据。

本文主要讨论的是数据文件。

(2) 数据的文件存储

文件是程序设计中的一个重要概念,指存储在外部介质上数据的集合。数据以文件的形式存放在外部介质中,操作系统以文件为单位对数据进行管理。

(3) C 中的文件

C 语言把文件看作一个字符(或字节)的序列,即由一个个字符(或字节)的数据顺序组成。一个输入输出流就是一个字符流或字节流(内容为二进制数据)。 C 的数据文件由一连串的字符(或字节)组成,不考虑行的界限,增加了处理的灵活性。这种文件被称为流式文件。

2.文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。文件标识包括 3 部分:

  • 文件路径:在哪里找
  • 文件名:叫什么名字
  • 文件后缀:怎么处理

3.文件的编码形式

根据文件存储的编码形式,数据文件可分为文本文件和二进制文件。

文本文件是以字符 ASCII 码值进行存储和编码的文件,其文件内容就是字符, 需要在存储前进行转换,又称为文本文件;

二进制文件直接将内存中的数据不加转换地输出到外存,可以将外存中的数据看作内存的映像,又称为映像文件。

如何选择?

  • 文本的优势在于方便人类读写,并且跨平台
  • 文本的缺点是程序输入输出时需要进行转换
  • 二进制的缺点是人类读写困难,且不支持跨平台
  • 二进制的优点是程序输入输出时速度更快

4.文件缓冲区

ANSI C 标准采用缓冲文件系统处理数据文件。系统会自动地为每一个文件分配一个文件缓冲区。写入数据时,先将数据存到缓冲区,再由操作系统把缓冲区数据真正存入磁盘。读入数据时,先将数据写入缓冲区,再由缓冲区读入到内存。这样做节省了存取时间,提高了效率。

每个文件在内存中只有一个缓冲区,它在输入时作为输入缓冲区,输出时作为输出缓冲区。

5.文件类型指针

在 C 语言中,每一个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息。这些信息存在 FILE 这个结构体变量中,它由系统声明,并且不同 C 编译系统中的 FILE 类型都有所不同。

通常,不定义 FILE 类型的变量,而是定义一个 FILE 类型的指针,指向内存中文件信息区的开头,通过它来引用变量。

1
FILE *fp;

二、打开和关闭文件

1.打开和关闭

在 C 语言中的打开文件,是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来缓冲输入输出的数据)。在打开文件的同时,一般都指定一个指针变量指向该文件,建立起指针变量和文件之间的联系,方便之后的读写操作。

在 C 语言中的关闭文件,是指撤销文件信息区和文件缓冲区。

2.fopen

(1) 调用形式

1
fopen("文件名.扩展名", "使用文件方式");

此处加双引号是在它是字符串常量的情况下,

如果由字符串变量存储文件名和打开方式,则无需加引号。

(2) 标准代码

打开文件,用指针接受地址信息,并判断打开是否成功。

1
2
3
4
5
FILE *fp;
if (fp = fopen("文件名.扩展名", "使用文件方式") == NULL){
printf("打开失败!\n");
exit(0);
}

1
2
3
4
5
6
FILE *fp = fopen("文件名.扩展名", "使用文件方式");
if (fp){

}else{

}

(3) 文件名

  • 文件名应该包含文件路径、文件名、文件后缀三部分

  • 文件路径:若数据文件在程序文件目录下,则可以不用指定文件路径,否则需要指定。

    例如:

    D:/Documents/test.txt

    D:\\Documents\\test.txt

(4) 使用文件方式

在第一个字母后面加 b ,便可以使用二进制方式使用文件。而实际上文本方式和二进制方式的区别仅在于对”换行”的处理。C语言中,”换行”为 ‘\n’ ,而 Windows 中, “换行”为 ‘\r’和’\n’ ,因此需要进行转换。

(5) 标准流文件

程序中可以使用 3 个标准的流文件:标准输入流(stdin)、标准输出流(stdout)、标准出错输出流(stderr)。

3.fclose

(1) 调用形式

1
fclose(文件指针);

(2) 必须关闭

  • 在使用完文件后应该关闭它,防止误用
  • 如果不关闭文件就结束程序运行将会丢失数据

三、顺序读写数据文件

1.读写字符

(1) 调用形式

1
2
fgetc(文件指针);
fputc(字符, 文件指针);

在 C 语言中, fgetc 等价于 getc , fputc 等价于 putc

(2) 例 1

从键盘输入一些字符,并逐个将他们写入到文件,知道用户输入一个 ‘#’ 为止。

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 <stdlib.h>

int main(){
FILE *fp;
char ch;
char filename[10];
printf("请输入文件名:");
scanf("%s", filename);
getchar(); //吸收换行符
if ((fp = fopen(filename,"w")) == NULL){
printf("错误");
exit(0);
}
printf("请输入字符串,以#结束:");
ch = getchar();
while (ch != '#'){
fputc(ch, fp);
putchar(ch);
ch = getchar();
}
fclose(fp);
putchar('\n');
return 0;
}

(3) 例 2

将一个磁盘文件中的信息复制到另一个文件中。

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
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>

int main(){
FILE *in, *out;
char ch;
char infile[10];
char outfile[10];
printf("请输入读入文件的名字");
scanf("%s", infile);
printf("请输入输出文件的名字");
scanf("%s", outfile);
if ((in = fopen(infile,"r")) == NULL){
printf("错误");
exit(0);
}
if ((out = fopen(outfile,"w")) == NULL){
printf("错误");
exit(0);
}
ch = fgetc(in);
while (!feof(in)){
fputc(ch, out);
putchar(ch);
ch = fgetc(in);
}
putchar('\n');
fclose(in);
fclose(out);
return 0;
}
  • 在文件的所有有效字符后有一个文件尾标志,用标识符 EOF 表示,其值为 -1
  • ch != EOF 等价于 ch != -1 等价于 !feof(in)

2.读写字符串

1
2
fgets(字符串, 字符数组大小, 文件指针);
fputs(字符串, 文件指针);

若字符数组大小为 n ,此时应填入 n ,字符串大小最大为 n - 1

3.格式化方式读写

1
2
fscanf("格式声明", 地址);
fprintf(文件指针, "格式声明", 值);

用 fprintf 和 fscanf 函数对文件读写,

优点:使用方便,容易理解,支持跨平台

缺点:由于输入输出时都需要进行二进制与 ASCII 码之间的转换,需要花费额外的时间。

4.二进制方式读写

在程序中不仅需要一次输入输出一个数据,而且往往需要一次输入输出一组数据, C 语言允许用 fread 从文件中读一个数据块,用 fwrite 向文件写一个数据块。读和写都是以二进制进行,数据在内存和文件之间原封不动地转移。

(1) 调用形式

1
2
fread(地址 , 单个数据项大小, 数据项个数, 文件指针);
fwrite(地址 , 单个数据项大小, 数据项个数, 文件指针);
  • 对于 fread 来说,地址是指用来存放读入数据的储存区的地址;

    对于 fwrite 来说,地址是指要将这个地址开始的存储区中的数据写入文件。

  • 若函数执行成功,则返回数据项的个数

例如:

1
2
int f[10];
fread(f, sizeof(int), 10, fp);

从 fp 所指的文件中读入 10 个 sizeof(int) 字节的数据,存储到数组 f 中。

(2) 例 1

从键盘输入 10 个学生的有关数据,然后把它们转存到文件中。

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
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>

#define SIZE 10
struct Student{
char name[10];
int num;
int age;
char address[15];
}student[SIZE];

void save(){
FILE *fp;
int i;
if ((fp = fopen("stu.dat", "wb")) == NULL){ //用二进制方方式打开
printf("wrong!");
exit(0);
}
for (i = 0; i < SIZE; i++){
if (fwrite(&student[i], sizeof(struct Student), 1, fp) != 1) //若函数执行成功,则返回数据项的个数
printf("wrong!");
}
fclose(fp);
}

int main(){
int i;
printf("请输入学生的数据:\n");
for (i = 0; i < SIZE; i++)
scanf("%s %d %d %s", student[i].name, &student[i].num, &student[i].age, student[i].address);
save();
return 0;
}

(3) 例 2

从文件中读入数据,并将其打印在屏幕上.

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 <stdlib.h>

#define SIZE 10
struct Student{
char name[10];
int num;
int age;
char address[15];
}student[SIZE];

int main(){
int i;
FILE *fp;
if ((fp = fopen("stu.dat", "rb")) == NULL){ //用二进制方式打开
printf("wrong!");
exit(0);
}
for (i = 0; i < SIZE; i++){
fread(&student[i], sizeof(struct Student), 1, fp);
printf("%-7s %4d %4d %4s\n", student[i].name, student[i].num, student[i].age, student[i].address);
}
fclose(fp);
return 0;
}

四、随机读写数据文件

1.文件位置标记

系统为每个文件设置了一个文件读写位置标记,用来指示“接下来要读写的下一个字符的位置”。

2.rewind

(1) 函数作用

rewind 函数的作用是使文件位置标记重新范围文件的开头,于是便可以从头开始处理文件。

(2) 调用形式

1
rewind(文件指针);

(3) 示例

读一个文件两次,第一次将其中数据现实在屏幕上,第二次将它复制到另一文件中。

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(){
FILE *fp1, *fp2;
char ch;
fp1 = fopen("file1.dat", "r");
fp2 = fopen("file2.dat", "w");
ch = getc(fp1); //读入第一个字符
while (!feof(fp1)){
putchar(ch);
ch = getc(fp1); //读入下一个字符
}
putchar('\n');
rewind(fp1);
ch = getc(fp1); //读入第一个字符
while (!feof(fp1)){
fputc(ch,fp2);
ch = getc(fp1); //读入下一个字符
}
fclose(fp1);
fclose(fp2);
return 0;
}

3.fseek

(1) 函数作用

跳转到文件中的指定位置。

(2) 调用形式

1
fseek(文件指针, 位移量, 起始点);

(3) 位移量

位移量是指以起始点为基点,位移的字节数,有正负之分。位移量应是 long 型,需要在数字后加 l 。

(4) 起始点

位移的基点,用数字表示。其中, 0 表示文件开始位置; 1 表示当前位置; 2 表示文件末尾位置。

(5) 示例

1
fseek(fp, 5L, 0);

4.ftell

(1) 函数作用

得到文件中”文件位置标记”的当前位置。如果出错,将会返回 -1L 。

(2) 调用形式

1
2
long num;
num = ftell(fp);

五、文件读写的出错检测

1.ferror

(1) 函数作用

用于检查是否发生错误,返回值为 0 表示未出错,返回值非 0 表示出错。

(2) 调用形式

1
ferror(文件指针);

2.clearerr

(1) 函数作用

将文件出错标志和文件结束标志置为 0 。

(2) 调用形式

1
clearerr(文件指针);

参考