07-文件和错误处理
1. 读写文件
1.1 访问文件
文件可以在 C 语言程序中被打开、读取和写入。对于这些操作,C 语言包括 FILE 文件类型,用于定义一个文件流。文件流记录了最后一次读和写的位置。
stdio.h 库包括文件处理函数。 FILE 类型定义了一个文件指针。
fopen(filename, mode) 返回一个指向文件 filename
的 FILE
指针,该文件使用 mode
模式打开。如果一个文件不能被打开,则返回 NULL。
模式 mode
有以下几项:
- -r: 为读取而打开(文件必须存在)。
- -w: 为写而打开(文件不必存在)。
- a: 打开用于追加(文件不需要存在)。
- r+: 为读和写从头开始打开
- w+: 为读写打开,覆盖文件
- a+: 为读写打开,追加到文件上
fclose(fp) 关闭用打开的文件,如果关闭成功则返回 0。如果关闭时有错误,则返回 EOF(文件结束)。
下面的程序打开一个文件进行写入,然后关闭它:
#include <stdio.h>
int main() {
FILE *fptr;
fptr = fopen("myfile.txt", "w");
if (fptr == NULL) {
printf("Error opening file.");
return -1;
}
fclose(fptr);
return 0;
}
#include <stdio.h>
int main() {
FILE *fptr;
fptr = fopen("myfile.txt", "w");
if (fptr == NULL) {
printf("Error opening file.");
return -1;
}
fclose(fptr);
return 0;
}
当一个字符串用来指定一个文件名时,转义字符 \\
表示一个反斜杠。在这个程序中,如果在打开文件时出现错误,会向系统返回错误码-1。错误处理将在以后的课程中解释。
填空,用于打开文件的函数名:
___
fopen
1.2 读取文件
stdio.h 库还包括用于从打开的文件读取的函数。
一个文件可以一次读取一个字符,也可以将整个字符串读入字符缓冲区,该缓冲区通常是用于临时存储的 char
数组。
getc(fp)
返回 fp 指向的文件中的下一个字符。如果已到达文件末尾,则返回 EOF。fgets(buff, n, fp)
fgets 函数功能为从指定的流中读取数据,每次读取一行。其原型为:char *fgets(char *str, int n, FILE *stream);
从指定的流stream
读取一行,并把它存储在str
所指向的字符串内。当读取到第(n-1)
个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。fscanf(fp, conversion_specifiers, vars)
从 fp 指向的文件中读取字符,并使用conversion_specifiers
将输入分配给变量指针vars
列表。与scanf
一样,遇到空格或换行符时,fscanf
会停止读取字符串。
下面的程序演示了从一个文件中读取数据:
#include <stdio.h>
int main() {
FILE *fptr;
int c, stock;
char buffer[200], item[10];
float price;
/* myfile.txt: Inventory\n100 Widget 0.29\nEnd of List */
fptr = fopen("myfile.txt", "r");
fgets(buffer, 20, fptr); /* read a line */
printf("%s\n", buffer);
fscanf(fptr, "%d%s%f", &stock, item, &price); /* read data */
printf("%d %s %4.2f\n", stock, item, price);
while ((c = getc(fptr)) != EOF) /* read the rest of the file */
printf("%c", c);
fclose(fptr);
return 0;
}
#include <stdio.h>
int main() {
FILE *fptr;
int c, stock;
char buffer[200], item[10];
float price;
/* myfile.txt: Inventory\n100 Widget 0.29\nEnd of List */
fptr = fopen("myfile.txt", "r");
fgets(buffer, 20, fptr); /* read a line */
printf("%s\n", buffer);
fscanf(fptr, "%d%s%f", &stock, item, &price); /* read data */
printf("%d %s %4.2f\n", stock, item, price);
while ((c = getc(fptr)) != EOF) /* read the rest of the file */
printf("%c", c);
fclose(fptr);
return 0;
}
fgets()
函数读取直到换行符。 fscanf()
根据转换格式符读取数据。
然后 while
循环一次读取一个字符,直到文件结束。
填空,打开文件并从中读取。
char buffer[200];
FILE* fptr = ____("myfile.txt", "r");
fgets(buffer, 20, ____);
fopen
fptr
1.3 写入文件
stdio.h 库还包括用于写入文件的函数。写入文件时,必须显式添加换行符 \n
。
fputc(char, fp)
将字符char写入fp指向的文件。fputs(str, fp)
将字符串str写入fp指向的文件。fprintf(fp, str, vars)
将字符串 str 打印到 fp 指向的文件。 str 可以选择包括格式转换符和变量 vars 列表。
下面的程序演示了向一个文件的写入。
FILE *fptr;
char filename[50];
printf("Enter the filename of the file to create: ");
gets(filename);
fptr = fopen(filename, "w");
/* write to file */
fprintf(fptr, "Inventory\n");
fprintf(fptr, "%d %s %f\n", 100, "Widget", 0.29);
fputs("End of List", fptr);
#include <stdio.h>
int main() {
FILE *fptr;
char filename[50];
char c;
printf("Enter the filename of the file to create: ");
gets(filename);
fptr = fopen(filename, "w");
/* write to file */
fprintf(fptr, "Inventory\n");
fprintf(fptr, "%d %s %f\n", 100, "Widget", 0.29);
fputs("End of List", fptr);
fclose(fptr);
/* read the file contents */
fptr = fopen(filename, "r");
while ((c = getc(fptr)) != EOF)
printf("%c", c);
fclose(fptr);
return 0;
}
“w” 参数为 fopen 函数定义“写入模式”。
填空,打开“sample.txt”文件进行写入:
FILE* ptr = ___("sample.txt", "___");
fopen
w
2. 二进制文件 I/O
2.1 二进制文件 I/O
当你有一个数组或结构体时,只将字符和字符串写到文件中会变得很乏味。
要将整个内存块写入文件,则需要以二进制模式打开文件,fopen()
函数模式选项如下:
- rb: 为读取而打开(文件必须存在)
- wb: 为写而打开(文件不需要存在)
- ab: 为附加而打开(文件不需要存在)
- rb+: 为读和写从头打开
- wb+: 为读和写打开,覆盖文件
- ab+: 为读和写打开,附加到文件上
其中:
fwrite(ptr, item_size, num_items, fp)
将num_items
个item_size
大小的项目从指针 ptr 写入文件指针 fp 所指向的文件。fread(ptr, item_size, num_items, fp)
从文件指针 fp 所指向的文件中读取item_size
大小的num_items
项到 ptr 所指向的内存中。fclose(fp)
关闭以文件 fp 打开的文件,如果关闭成功,则返回 0。如果关闭错误,则返回 EOF。
feof(fp)
当达到文件流的终点时,返回 0。
填空,打开二进制文件进行只读的模式是:
___
rb
下面的程序演示了对二进制文件的读取和写入:
FILE *fptr;
int arr[10];
int x[10];
int k;
/* generate array of numbers */
for (k = 0; k < 10; k++)
arr[k] = k;
/* write array to file */
fptr = fopen("datafile.bin", "wb");
fwrite(arr, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
fclose(fptr);
/* read array from file */
fptr = fopen("datafile.bin", "rb");
fread(x, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
fclose(fptr);
/* print array */
for (k = 0; k < 10; k++)
printf("%d", x[k]);
#include <stdio.h>
int main() {
FILE *fptr;
int arr[10];
int x[10];
int k;
/* generate array of numbers */
for (k = 0; k < 10; k++)
arr[k] = k;
/* write array to file */
fptr = fopen("datafile.bin", "wb");
fwrite(arr, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
fclose(fptr);
/* read array from file */
fptr = fopen("datafile.bin", "rb");
fread(x, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
fclose(fptr);
/* print array */
for (k = 0; k < 10; k++)
printf("%d", x[k]);
return 0;
}
该程序将整数数组写入文件,将结构体数组写入文件同样容易。
注意,对象大小和数量是通过使用元素的大小和整个变量的大小来确定的。
单独的文件扩展名并不能确定文件中数据的格式,但是它们对于指示期望的数据类型很有用。
例如,.txt
扩展名表示文本文件,.bin
表示二进制数据,.csv
表示逗号分隔的值,.dat
表示数据文件。
填空,将数组以二进制模式写入文件并关闭文件:
FILE* fptr;
int arr[5] = {1, 2, 3, 4, 5};
___ = fopen("datafile.bin", "___");
fwrite(arr, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
___(fptr);
fptr
wb
fclose
2.2 控制文件读写位置
在 stdio.h
中有一些函数用于控制二进制文件中文件指针的位置。
ftell(fp)
返回一个 long int 值,该值对应于 fp 文件指针位置(从文件开头开始的字节数)。
fseek(fp, num_bytes, from_pos)
将 fp 文件指针的位置相对于 from_pos 位置移动 num_bytes 个字节,
该位置可以是以下常量之一:
SEEK_SET
: 文件的开始SEEK_CUR
: 当前位置SEEK_END
: 文件结尾
下面的程序从一个保持结构体的二进制文件中读取一条记录:
typedef struct {
int id;
char name[20];
} item;
int main() {
FILE *fptr;
item first, second, secondf;
/* create records */
first.id = 10276;
strcpy(first.name, "Widget");
second.id = 11786;
strcpy(second.name, "Gadget");
/* write records to a file */
fptr = fopen("info.dat", "wb");
fwrite(&first, 1, sizeof(first), fptr);
fwrite(&second, 1, sizeof(second), fptr);
fclose(fptr);
/* file contains 2 records of type item */
fptr = fopen("info.dat", "rb");
/* seek second record */
fseek(fptr, 1*sizeof(item), SEEK_SET);
fread(&secondf, 1, sizeof(item), fptr);
printf("%d %s\n", secondf.id, secondf.name);
fclose(fptr);
return 0;
}
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[20];
} item;
int main() {
FILE *fptr;
item first, second, secondf;
/* create records */
first.id = 10276;
strcpy(first.name, "Widget");
second.id = 11786;
strcpy(second.name, "Gadget");
/* write records to a file */
fptr = fopen("info.dat", "wb");
fwrite(&first, 1, sizeof(first), fptr);
fwrite(&second, 1, sizeof(second), fptr);
fclose(fptr);
/* file contains 2 records of type item */
fptr = fopen("info.dat", "rb");
/* seek second record */
fseek(fptr, 1*sizeof(item), SEEK_SET);
fread(&secondf, 1, sizeof(item), fptr);
printf("%d %s\n", secondf.id, secondf.name);
fclose(fptr);
return 0;
}
该程序将两个结构体记录写入文件。为了只读取第二条记录,fseek()
将文件指针从文件开头移到 1 * sizeof(item)
个字节。
例如,如果你想将指针移动到第四条记录,则从文件开头(SEEK_SET
)移动 3 * sizeof(item)
。
填空,将指针 fptr
指向结构体文件中的第二条记录:
typedef struct {
int id;
char name[20];
} item;
FILE* fptr = ___("info.dat", "rb");
___(fptr, 1*sizeof(item), SEEK_SET);
fopen
fseek
3. 错误异常处理
3.1 错误异常处理
良好的编程实践的核心是使用错误处理技术。如果你忘记了异常处理,即使是最扎实的编码技巧也不能保证程序不崩溃。
异常是任何导致程序停止正常执行的情况。 异常处理 ,也叫 错误处理,是一种处理运行时错误的方法。
C语言没有明确地支持异常处理,但有一些方法可以管理错误。
- 编写代码以首先防止错误的发生。你不能控制用户的输入,但可以检查以确定用户输入的是有效的输入。当执行除法时,要采取额外的措施来确保不会发生 除以0。
- 使用 exit 语句来优雅地结束程序执行。你可能无法控制一个文件是否可供读取,但不需要让这个问题让程序崩溃。
使用 errno、perror() 和 strerror() 通过错误代码识别错误。
什么是异常?
A. 异常是带有联合的特殊类型的结构。
B. 异常是指导致程序停止正常执行的任何情况。✅
C. 异常是一种避免 main() 函数声明的方法
3.2 exit 退出命令
exit 命令立即停止程序的执行,并将退出代码返回给调用方。例如,如果一个程序被另一个程序调用,那么调用程序可能需要知道退出状态。
使用 exit 避免程序崩溃是一个好习惯,因为它会关闭所有打开的文件连接和进程。
你可以通过 exit 语句返回任何值,常用 0 代表成功,-1 代表失败。预定义的 stdlib.h
宏 EXIT_SUCCESS 和 EXIT_FAILURE 也经常被使用。
例如:
int x = 10;
int y = 0;
if (y != 0)
printf("x / y = %d", x/y);
else {
printf("Divisor is 0. Program exiting.");
exit(EXIT_FAILURE);
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = 10;
int y = 0;
if (y != 0)
printf("x / y = %d", x/y);
else {
printf("Divisor is 0. Program exiting.");
exit(EXIT_FAILURE);
}
return 0;
}
填空,如果 num
的值为0,则立即停止执行并返回错误代码 4:
int num = 0;
___ (num == 0) {
___(4);
}
if
exit
4. 使用错误代码
4.1 errno 使用
一些库函数,如 fopen()
,当它们没有按预期执行时,会设置一个错误代码。错误代码设置在名为 errno 的全局变量中,它被定义在 errno.h 头文件中。当使用 errno 时,你应该在调用库函数之前将其设置为0。
为了输出存储在 errno 中的错误代码,你可以使用 fprintf 打印到 stderr 文件流,即标准错误输出到屏幕上。使用 stderr 是一个约定俗成的习惯,也是一个良好的编程实践。
也可以通过其他方式输出errno,但如果你只使用stderr来处理错误信息,会更容易跟踪你的异常处理。
要使用 errno,你需要在程序的顶部用 extern int errno; 语句声明它(或者也可以包括 errno.h 头文件)。
例如:
#include <stdio.h>
#include <stdlib.h>
// #include <errno.h>
extern int errno;
int main() {
FILE *fptr;
errno = 0;
fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
fprintf(stderr, "Error opening file. Error code: %d\n", errno);
exit(EXIT_FAILURE);
}
fclose(fptr);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
extern int errno;
int main() {
FILE *fptr;
errno = 0;
fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
fprintf(stderr, "Error opening file. Error code: %d\n", errno);
exit(EXIT_FAILURE);
}
fclose(fptr);
return 0;
}
填空,引入有 errno 变量的头文件。
#include <___.h>
errno
4.2 perror 和 strerror 函数
当库函数设置 errno 时,将分配一个隐式错误号。
有关该错误的更多描述性消息,可以使用 perror()
。
你还可以在 string.h
头文件中使用 strerror()
获得消息,该文件返回指向消息文本的指针。
perror()
必须在实际错误消息之前包含一个字符串。
通常,对于相同的错误,不需要同时使用 perror()
和 strerror()
,但是在下面的程序中将二者都用于演示目的:
FILE *fptr;
errno = 0;
fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
perror("Error");
fprintf(stderr, "%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fptr;
errno = 0;
fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
perror("Error");
fprintf(stderr, "%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
fclose(fptr);
return 0;
}
有超过 100 个错误代码。可以使用以下语句列出:
for (int x = 0; x < 135; x++)
fprintf(stderr, "%d: %s\n", x, strerror(x));
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
extern int errno;
int main() {
for (int x = 0; x < 135; x++)
fprintf(stderr, "%d: %s\n", x, strerror(x));
}
填空,打开文件并使用 perror()
消息打印错误消息。
FILE* fptr = ___("test.txt", "r");
if (fptr == NULL) {
___("Error");
exit(EXIT_FAILURE);
}
fopen
perror
4.3 EDOM 和 ERANGE
指定域超出范围的错误代码是什么?
A. EDOM✅
B. EOPEN
C. ERANGE
4.4 feof 和 ferror 函数
除了检查NULL文件指针和使用 errno 外,feof() 和 ferror() 函数可用于确定文件 I/O 错误。
feof(fp)
如果已经到达流的末端,返回一个非零值,否则为 0。 feof 也可以设置 EOF。ferror(fp)
如果有错误,返回一个非零值,没有错误则返回0。
下面的程序包含了几种异常处理技术:
FILE *fptr;
int c;
errno = 0;
fptr = fopen("myfile.txt", "r");
if (fptr == NULL) {
fprintf(stderr, "Error opening file. %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
while ((c = getc(fptr)) != EOF) /* read the rest of the file */
printf("%c", c);
if (ferror(fptr)) {
printf("I/O error reading file.");
exit(EXIT_FAILURE);
}
else if (feof(fptr)) {
printf("End of file reached.");
}
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int main() {
FILE *fptr;
int c;
errno = 0;
fptr = fopen("c:\\myfile.txt", "r");
if (fptr == NULL) {
fprintf(stderr, "Error opening file. %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
while ((c = getc(fptr)) != EOF) /* read the rest of the file */
printf("%c", c);
if (ferror(fptr)) {
printf("I/O error reading file.");
exit(EXIT_FAILURE);
}
else if (feof(fptr))
printf("End of file reached.");
fclose(fptr);
return 0;
}
程序输出会有所不同。但如果文件正常打开,程序完成了对整个文件的读取,则会显示:“End of file reached.”。
填空,打开文件进行读取,并检查是否到达文件末尾。
FILE* fptr = ___("test", "r");
if (___(fptr)) {
printf("End of file reached").
}
fopen
feof
5. 小测验
5.1 练习-1
填空,打开 myfile.txt 文件进行写入,如果发生错误,则输出消息。
FILE *fptr;
fptr = ___("myfile.txt", "w");
if (___ == NULL) {
printf("Error opening file.");
return -1;
}
fopen
fptr
5.2 练习-2
填空,打开并从文件“ myfile.txt”中读取:
___ buffer[200];
FILE* fptr = ___("myfile.txt", "r");
fgets(buffer, 20, ___);
printf("%s", buffer);
char
fopen
fptr
5.3 练习-3
填空,用 fwrite()
函数将 arr
数组写进文件,然后关闭它。
FILE* fptr;
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
fptr = fopen("datafile.bin", "wb");
___(arr, sizeof(int), 10, fptr);
___(fptr);
fwrite
fclose
5.4 练习-4
填空,如果 num1 的值小于 num2 的值,则退出程序。
int num1 = 42;
int num2 = 55;
___ (num1 < num2) {
___(0);
}
if
exit
5.5 练习-5
填空,如果达到文件的末尾,用代码 42 退出程序。
FILE* fptr = fopen("test", "r");
if (___(fptr)) {
printf("End of file reached").
___(42);
}
feof
exit
公众号:AI悦创【二维码】

AI悦创·编程一对一
AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web、Sql」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh
C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh
方法一:QQ
方法二:微信:Jiabcdefh
