跳至主要內容

07-文件和错误处理

AI悦创原创C教程C教程大约 14 分钟...约 4319 字

1. 读写文件

1.1 访问文件

文件可以在 C 语言程序中被打开、读取和写入。对于这些操作,C 语言包括 FILE 文件类型,用于定义一个文件流。文件流记录了最后一次读和写的位置。

stdio.h 库包括文件处理函数。 FILE 类型定义了一个文件指针。

fopen(filename, mode) 返回一个指向文件 filenameFILE 指针,该文件使用 mode 模式打开。如果一个文件不能被打开,则返回 NULL。

模式 mode 有以下几项:

  • -r: 为读取而打开(文件必须存在)。
  • -w: 为写而打开(文件不必存在)。
  • a: 打开用于追加(文件不需要存在)。
  • r+: 为读和写从头开始打开
  • w+: 为读写打开,覆盖文件
  • a+: 为读写打开,追加到文件上

fclose(fp) 关闭用打开的文件,如果关闭成功则返回 0。如果关闭时有错误,则返回 EOF(文件结束)。

下面的程序打开一个文件进行写入,然后关闭它:

Code1
#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 会停止读取字符串。

下面的程序演示了从一个文件中读取数据:

Code1
#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 列表。

下面的程序演示了向一个文件的写入。

Code1
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); 

“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_itemsitem_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

下面的程序演示了对二进制文件的读取和写入:

Code1
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]); 

该程序将整数数组写入文件,将结构体数组写入文件同样容易。

注意,对象大小和数量是通过使用元素的大小和整个变量的大小来确定的。

单独的文件扩展名并不能确定文件中数据的格式,但是它们对于指示期望的数据类型很有用。

例如,.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: 文件结尾

下面的程序从一个保持结构体的二进制文件中读取一条记录:

Code1
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 语句来优雅地结束程序执行。你可能无法控制一个文件是否可供读取,但不需要让这个问题让程序崩溃。

使用 errnoperror() 和 strerror() 通过错误代码识别错误。

什么是异常?

A. 异常是带有联合的特殊类型的结构。

B. 异常是指导致程序停止正常执行的任何情况。✅

C. 异常是一种避免 main() 函数声明的方法

3.2 exit 退出命令

exit 命令立即停止程序的执行,并将退出代码返回给调用方。例如,如果一个程序被另一个程序调用,那么调用程序可能需要知道退出状态。

使用 exit 避免程序崩溃是一个好习惯,因为它会关闭所有打开的文件连接和进程。

你可以通过 exit 语句返回任何值,常用 0 代表成功,-1 代表失败。预定义的 stdlib.hEXIT_SUCCESSEXIT_FAILURE 也经常被使用。

例如:

s Code1
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);
} 

填空,如果 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 头文件)。

例如:

Code1
#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;
} 

填空,引入有 errno 变量的头文件。

#include <___.h>

errno

4.2 perror 和 strerror 函数

当库函数设置 errno 时,将分配一个隐式错误号。

有关该错误的更多描述性消息,可以使用 perror()

你还可以在 string.h 头文件中使用 strerror() 获得消息,该文件返回指向消息文本的指针。

perror() 必须在实际错误消息之前包含一个字符串。

通常,对于相同的错误,不需要同时使用 perror()strerror() ,但是在下面的程序中将二者都用于演示目的:

Code1
FILE *fptr;
errno = 0;

fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
  perror("Error");
  fprintf(stderr, "%s\n", strerror(errno));
  exit(EXIT_FAILURE);
} 

有超过 100 个错误代码。可以使用以下语句列出:

Code1
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。

下面的程序包含了几种异常处理技术:

Code1
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.");
} 

程序输出会有所不同。但如果文件正常打开,程序完成了对整个文件的读取,则会显示:“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

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度