原文: File Handling in C — How to Open, Close, and Write to Files

如果你以前写过 C 语言的 helloworld 程序,你已经知道 C 语言里的基本文件 I/O:

/* C 语言里的简单的 hello world */
#include <stdlib.h>

// 导入 IO 函数
#include <stdio.h>

int main() {
    // This printf is where all the file IO magic happens!
    // How exciting!
    printf("Hello, world!\n");
    return EXIT_SUCCESS;
}

文件处理是编程中最重要的部分之一。在 C 语言中,我们使用一个文件类型的结构指针来声明一个文件:

FILE *fp;

C 语言提供了一些内建函数来执行基本的文件操作:

  • fopen() - 创建一个新文件或打开一个现有文件
  • fclose() - 关闭一个文件
  • getc() - 从文件中读取一个字符
  • putc() - 将一个字符写到一个文件中
  • fscanf() - 从一个文件中读取一组数据
  • fprintf() - 将一组数据写到一个文件中
  • getw() - 从文件中读取一个整数
  • putw() - 将一个整数写到一个文件中
  • fseek() - 将位置设置为期望的点
  • ftell() - 给出文件中的当前位置
  • rewind() - 将位置设置为起始点

打开一个文件

fopen() 函数用于创建一个文件或打开一个现有文件:

fp = fopen(const char filename,const char mode);

有许多模式可以打开一个文件:

  • r - 以读模式打开文件
  • w - 以写模式打开或创建一个文本文件
  • a - 以附加模式打开一个文件
  • r+ - 以读写模式打开一个文件
  • a+ - 以读写模式打开一个文件
  • w+ - 以读写模式打开一个文件

下面是一个从文件中读取数据和向文件中写入数据的例子:

#include<stdio.h>
#include<conio.h>
main()
{
FILE *fp;
char ch;
fp = fopen("hello.txt", "w");
printf("Enter data");
while( (ch = getchar()) != EOF) {
  putc(ch,fp);
}
fclose(fp);
fp = fopen("hello.txt", "r");

while( (ch = getc(fp)! = EOF)
  printf("%c",ch);
  
fclose(fp);
}

现在你可能在想,“这只是把文本打印到屏幕上。这个文件怎么会是 IO?”

答案起初并不明显,需要对 UNIX 系统有一些了解。在 UNIX 系统中,一切都被视为文件,这意味着你可以从文件中读取和写入文件。

这意味着你的打印机可以被抽象为一个文件,因为你对打印机所做的就是对它进行写入。把这些文件看作是流也是很有用的,因为你将在后面看到,你可以用 shell 重定向它们。

那么,这与 helloworld 和文件 IO 有什么关系?

当你调用 printf 时,你实际上只是在向一个叫作 stdout 的特殊文件写东西,这是 standard output(标准输出)的简称。stdout 代表由你的 shell 决定的标准输出,通常是终端。这就解释了为什么它会打印到你的屏幕上。

有两个流(即文件)可供你使用,即 stdinstderrstdin 代表 standard output(标准输出),你的 shell 通常将其连接到键盘上。stderr 代表 standard error output(标准错误输出),你的 shell 通常将其连接到终端。

基本文件 IO,或我如何学习创建管道

理论够多了,让我们写点代码吧!写入文件的最简单方法是使用输出重定向工具 > 来重定向输出流。

如果你想追加,你可以使用 >>

# This will output to the screen...
./helloworld
# ...but this will write to a file!
./helloworld > hello.txt

毫不奇怪,hello.txt 的内容将是:

Hello, world!

假设我们有另一个叫作 greet 的程序,类似于 helloworld,它有一个给定的 name

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 初始化一个数组,存有 name
    char name[20];
    // 读取一个字符串,并把它存到 name
    scanf("%s", name);
    // 打印 greeting
    printf("Hello, %s!", name);
    return EXIT_SUCCESS;
}

我们可以使用 < 工具将 stdin 重定向为从文件中读取,而不是从键盘上读取:

# 写一个包含 name 的文件
echo Kamala > name.txt
# 这将从文件读取 name,并把 greeting 打印到屏幕上
./greet < name.txt
# ==> Hello, Kamala!
# 如果你想也把 greeting 写入一个文件,你可以使用 “>”

注意:这些重定向操作符是在 bash 和类似的 shell 中。

重要内容

上面的方法只适用于最基本的情况。如果你想做更大、更好的事情,你可能想从 C 语言中而不是通过 shell 来处理文件。

为了达到这个目的,你将使用一个叫作 fopen 的函数。这个函数需要两个字符串参数,第一个是文件名,第二个是模式。

模式基本上是权限,所以 r 代表读,w 代表写,a 代表追加。你也可以把它们结合起来,所以 rw 意味着你可以读和写该文件。还有更多的模式,但这些是最常用的。

在你有了 FILE 指针之后,你可以使用基本上与你原来使用的相同的 IO 命令,只是你必须在它们前面加上 f,并且第一个参数将是文件指针。例如,printf 的文件版本是 fprintf

下面是一个名为 greetings 的程序,它从一个包含名字列表的文件中读取问候语,并将问候语写到另一个文件中:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 创建文件指针
    FILE *names = fopen("names.txt", "r");
    FILE *greet = fopen("greet.txt", "w");

    // 检查是否一切没问题
    if (!names || !greet) {
        fprintf(stderr, "File opening failed!\n");
        return EXIT_FAILURE;
    }

    // Greetings time!
    char name[20];
    // 持续读取所有内容
    while (fscanf(names, "%s\n", name) > 0) {
        fprintf(greet, "Hello, %s!\n", name);
    }

    // 到达末尾时,打印一条信息到终端以通知用户
    if (feof(names)) {
        printf("Greetings are done!\n");
    }

    return EXIT_SUCCESS;
}

names.txt 包含:

Kamala
Logan
Carol

然后,在运行 greetings 之后,文件 greet.txt 将包含:

Hello, Kamala!
Hello, Logan!
Hello, Carol!