文本流的本质就是二进制数据流,其表现的形式,就是一维的字符数组。
文本流工作的流程如下:
- 设备将二进制数据送到缓冲区
- 文本流将二进制数据进行转化,主要为:
按照 ‘\n’ 符号,将二进制流解析为一行行文本 - 最后输入\输出函数按照一行行文本进行读写
/*
stream_first.c
Use the stream to solve the problem
BeginnerC
*/
#include <stdio.h>
int main()
{
char c = 'c';
char read_char = 'n';
ungetc(read_char, stdin);
c = getchar();
putchar(c);
return 0;
}
在这个案例中,我们展现了文本流模型的冰山一角,如您所见,getchar 函数直接返回了 ‘n’,而在这之前,我们使用了一个新函数 ungetc 将 ‘n’ 写入 stdin。
下面,我们叙述这一切的工作原理。
在 C语言 中,所有标准库函数的输入输出,都扎根于文本流模型,而 C语言 在运行的时候,默认也提供三个文本流供我们读写数据。
他们分别的 stdin, stdout, stderr,其中 前缀 std 代表 “标准",而in, out, err 代表输入、输出与错误。
他们的抽象就是字符数组,而在刚刚的工作流程之中:
- 一开始,标准输入输出流没有任何数据
- 使用 ungetc 以后,标准输入流里面拥有了字符 ‘n’
- 使用 getchar 函数以后,我们从标准输入流中读入了字符 ‘n’
值得一提的是,ungetc 的原理是对输入流缓冲区进行操作,而如果我们对输出流进行操作,其结果是未定义的。
/*
stream_second.c
Use the stream to solve the problem
BeginnerC
*/
#include <stdio.h>
int main()
{
char c = '\0';
while (EOF != (c = getchar()))
putchar(c);
return 0;
}
在这个程序中,我们将输入流复制到输出流之中,我们可以发现:尽管 getchar 只读取一个字符,但是它真正工作起来是在我们换行之后。
这就体现出文本流模型按行读取的特点。
事实上,这个程序等价于
/*
stream_third.c
Use the stream to solve the problem
BeginnerC
*/
#include <stdio.h>
int main()
{
char c = '\0';
while (EOF != (c = getc(stdin)))
putc(c, stdout);
return 0;
}
事实上,很多 C语言标准库函数 都是对输入输出流做了一层封装。
包括 printf, scanf 这些我们打过许多次交道的程序。
现在,我们发现程序中多出了一个叫做 EOF 的常量,它的含义就是“流结束"。,在 Fedora Linux 下面,CTRL+D可以让输入流结束。
而现在,我们希望以另一种角度看待文本流,那就是普通文件的角度。
Hello World
Here is BeginnerC
如你所见,我们准备一段文本,并将其写入一个文本文件,并进行如图所示的操作。
在这个案例之中,我们首次展现了文件与文本流的关系,这个 < 指令,被称为重定向,意义在于将输入的源头,从 shell 转移到 文件之中。
对于 C语言 而言,这仅仅意味着读写设备的转变,而不是输入输出流的改变,因此,一切照旧。
事实上,除了运用外部的手段以外,C语言自身也提供了许多与文件有关的函数,用于帮助我们将文本流与文件挂钩。
/*
freopen.c
Use the freopen function
BeginnerC
*/
#include <stdio.h>
int main()
{
char c = '\0';
freopen("output.txt", "w", stdout);
while (EOF != (c = getchar()))
putchar(c);
return 0;
}
在这个程序中,我们使用 freopen 函数对输入输出流进行重定向,将输出流 stdout 重定向到文本文件 output.txt 中。
值的注意的是,这里我们首次使用了 C语言 中的文件读写字符串,下面让我们来正式罗列他们。
| 标志 | 含义 |
|---|---|
| w | 写(如果原文件存在内容,则清空文件) |
| r | 读文件(如果文件不存在,失败) |
| a | 追加写模式(不存在则创建,存在则在文件末尾追加) |
| w+ | 读写一体(如果原文件存在内容,则清空文件) |
| r+ | 读写文件一体(如果文件不存在,失败) |
| a+ | 追加读写模式(不存在则创建,存在则在文件末尾追加) 文本流模型与文件操作 |
| b | 采用二进制流模型而不是文本流(在 UNIX 下,二进制流与文本流是一体两面) |
而在这个基础上,我们也引入除“重定向"以外的另一种使用文本流的方式。
/*
fopen.c
Use the fopen to open the file
BeginnerC
*/
#include <stdio.h>
int main()
{
FILE *fp = NULL;
int number_1, number_2, number_result;
fp = fopen("new.txt", "w+");
if (NULL == fp)
{
return -1;
}
fprintf(fp, "%d + %d = %d\n", 1, 2, 1 + 2);
fflush(fp);
fseek(fp, SEEK_SET, 0);
fscanf(fp, "%d + %d = %d", &number_1, &number_2, &number_result);
printf("%d + %d = %d\n", number_1, number_2, number_result);
fclose(fp);
return 0;
}
在这个案例中,我们使用 fprintf 与 fscanf 等函数,对文本流进行读写,fopen 则让文本流与特定的文件挂钩。
可以注意到,实际上:
printf(argument_list)
scanf(argument_list)
// Euqal to
fprintf(stdout, arguemnt_list)
fscanf(stdin, argument_list)
而 fflush 函数,则是一个类似于“加速器"的函数,它会让输出流缓冲区的内容立刻写入对应的文件。
值得注意的是 fseek 函数,它的职责是定位,负责读写位置在文本流中的定位。
其中分为
| 标志 | 含义 |
|---|---|
| SEEK_SET | 文本流缓冲区起始 |
| SEEK_CUR | 文本流缓冲区当下位置 |
| SEEK_END | 文本流缓冲区末尾 |