C 语言本身没有像 Java 或 C# 那样名为 OutputStream 的类或接口。

C 语言采用的是一种更接近底层、基于文件指针的 I/O 模型,我们可以将 C 语言中用于写入数据的一系列函数和机制,理解为“输出流”的等价概念,这些函数共同构成了 C 语言的输出功能。
下面我将从几个层面来为你解析 C 语言的“OutputStream”:
核心概念:文件指针
在 C 语言中,所有 I/O 操作都通过一个指向 FILE 结构体的指针来完成,这个指针通常被称为文件指针。FILE 结构体在 <stdio.h> 中定义,它包含了控制文件 I/O 所需的所有信息,比如文件的缓冲区、当前读写位置、文件描述符等。
你可以把 FILE* 想象成一个“流”的句柄或控制器,当你打开一个文件(或标准输入/输出设备)时,你会得到一个 FILE*,后续所有的写入操作都通过这个指针来“流入”目标。

标准输出流
最基本、最常用的“输出流”就是标准输出流,它在程序启动时自动打开,对应的文件指针是 stdout。
- 声明:
extern FILE *stdout;(在<stdio.h>中) - 默认目标: 你的终端/控制台。
- 使用: 你不需要手动打开它,直接使用
printf,puts,putchar等函数时,数据默认就会写入stdout。
#include <stdio.h>
int main() {
// printf 的输出默认流向 stdout,即你的控制台
printf("Hello, World!\n");
// puts 也是如此
puts("This is going to stdout as well.");
return 0;
}
通用输出流函数
除了 printf 这种格式化输出函数,C 语言还提供了一系列通用的字节写入函数,它们是构建更复杂输出流的基础。
fwrite 函数 - 二进制写入
fwrite 是最核心的“输出流”函数之一,它将一个内存块中的数据原样(二进制格式)写入到输出流中。
#include <stdio.h>
int main() {
// 打开一个文件用于写入(如果不存在则创建,如果存在则清空)
FILE *fp = fopen("data.bin", "wb"); // "wb" 表示二进制写入
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
int numbers[] = {10, 20, 30, 40, 50};
size_t num_elements = sizeof(numbers) / sizeof(numbers[0]);
// 将 numbers 数组的内容写入文件流 fp
size_t written = fwrite(numbers, sizeof(int), num_elements, fp);
if (written != num_elements) {
perror("Failed to write data");
} else {
printf("Successfully wrote %zu integers to data.bin\n", written);
}
// 关闭文件流,释放资源
fclose(fp);
return 0;
}
fwrite 的原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

ptr: 指向要写入数据的内存地址。size: 每个数据项的大小(字节)。nmemb: 要写入的数据项的数量。stream: 目标文件指针(即我们的“输出流”)。
fputc 和 fputs 函数 - 字符和字符串写入
fputc: 向输出流写入单个字符。FILE *fp = stdout; fputc('A', fp); // 在控制台输出字符 'A'fputs: 向输出流写入一个字符串(不自动添加换行符)。FILE *fp = stdout; fputs("This is a string.\n", fp); // 在控制台输出字符串
高级特性:缓冲与刷新
C 语言的 I/O 流是缓冲的,这意味着数据并不是立即写入到磁盘或终端,而是先存放在一个内存缓冲区中,当缓冲区满了或者被手动刷新时,才会真正发生 I/O 操作。
- 为什么需要缓冲? 减少 I/O 操作次数,大大提高程序性能,因为直接向磁盘或终端写一个字节非常慢,而向内存写则快得多。
- 刷新缓冲区:
fflush(stream): 强制将指定流缓冲区中的数据写入其关联的文件或设备。fclose(stream): 在关闭文件时,会自动刷新缓冲区。- 缓冲区满时: 会自动刷新。
- 程序正常结束时: 所有打开的流都会被自动刷新和关闭。
#include <stdio.h>
#include <time.h>
int main() {
printf("This message is in the buffer.\n");
// 下面的代码会让程序暂停5秒,但你不会在控制台看到任何输出
// 因为 printf 的输出还在缓冲区里
sleep(5);
// 当程序结束时,缓冲区才会被刷新,所以你会看到消息一闪而过
return 0;
}
如果你想在 sleep 之前就看到输出,就需要手动刷新:
#include <stdio.h>
#include <time.h>
int main() {
printf("This message is in the buffer.\n");
fflush(stdout); // 手动刷新标准输出流
sleep(5); // 现在你会立即看到消息,然后程序暂停5秒
return 0;
}
“流”的重定向
C 语言的“流”概念非常灵活,你可以轻松地将同一个输出函数(如 printf)的目标从 stdout(控制台)重定向到文件、管道或其他任何地方。
#include <stdio.h>
int main() {
// 默认情况下,printf 输出到控制台
printf("This goes to the console.\n");
// 打开一个文件
FILE *file_output = fopen("output.txt", "w");
if (file_output == NULL) {
return 1;
}
// 将 stdout 重定向到我们的文件
// 这之后,所有对 stdout 的写入操作都会写入到 output.txt 文件中
stdout = file_output;
printf("This line goes to output.txt!\n");
printf("This one too.\n");
// 恢复 stdout 到原始的控制台
// 这是一个好习惯,尤其是在更复杂的程序中
fflush(stdout); // 先刷新缓冲区
stdout = freopen(NULL, "w", stdout); // 重定向回原始标准输出
printf("And this is back on the console.\n");
// 关闭文件
fclose(file_output);
return 0;
}
注意: 直接对 stdout 进行赋值 (stdout = file_output;) 是一种简单但不够稳健的方法,更推荐使用 freopen 函数。
C 语言的“OutputStream”等价物
| Java/C# 概念 | C 语言等价物 | 描述 |
|---|---|---|
OutputStream |
FILE* 指针 |
代表一个输出流的句柄,通过 fopen() 获取。 |
write(int b) |
fputc(int c, FILE *stream) |
写入单个字节/字符。 |
write(byte[] b) |
fwrite(const void *ptr, ...) |
写入一块二进制数据。 |
flush() |
fflush(FILE *stream) |
强制将缓冲区内容写入底层设备。 |
System.out |
stdout |
预定义的标准输出流文件指针。 |
构造函数/new |
fopen("filename", "mode") |
打开一个文件,返回一个 FILE* 流。 |
close() |
fclose(FILE *stream) |
关闭流,释放资源,并自动刷新。 |
核心要点:
- 没有类:C 的 I/O 是基于过程的,而不是面向对象的。
- *核心是 `FILEFILE*` 指针就是“流”本身。
fwrite是基石:它是处理二进制数据写入最通用的函数。- 缓冲是关键:理解缓冲和刷新对于控制 I/O 行为和性能至关重要。
- 灵活性高:通过
freopen等函数可以轻松地重定向流的输入/输出目标。
