C语言用户层的输出流

1. 使用 C 标准库的 printf(stdout)

1
2
3
4
5
6
#include <stdio.h>

int main() {
printf("Hello from C stdout (printf)!\n");
return 0;
}

✅ 2.示例代码:C 标准输入输出流使用

1. 标准 C 库标准输出流函数(跨平台通用)

这些函数是 C 标准库的一部分,在 Linux 和 Windows 上均可使用:

  • printf / fprintf

    1
    2
    3
    printf("Hello, World!\n");                   // 输出到 stdout
    fprintf(stdout, "Hello, stdout!\n"); // 等价于 printf
    fprintf(stderr, "Error: something wrong\n"); // 输出到 stderr
  • puts / fputs

    1
    2
    puts("Auto-adds newline");      // 输出到 stdout 并自动追加换行符
    fputs("No newline", stdout); // 无自动换行
  • putchar / fputc

    1
    2
    putchar('A');                   // 输出单个字符到 stdout
    fputc('B', stderr); // 输出到指定流

2.下面是一个完整的 C 语言示例程序,展示如何使用标准 I/O 流接口(stdinstdoutstderr)以及常见函数:

  • fgetsfscanfgetchar(从标准输入读取)
  • printffprintfputsputchar(输出到标准输出或标准错误)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <string.h>

int main() {
char name[100];
int age;

// 1. 使用 printf + fgets 从标准输入读取字符串
printf("Enter your name: ");
fgets(name, sizeof(name), stdin);

// 去除末尾换行符
name[strcspn(name, "\n")] = '\0';

// 2. 使用 fprintf 输出到标准输出
fprintf(stdout, "Hello, %s!\n", name);

// 3. 使用 fscanf 从标准输入读取整数
printf("Enter your age: ");
fscanf(stdin, "%d", &age);

// 4. 使用 puts 输出纯文本行
puts("You entered:");

// 5. 使用 putchar 输出字符
printf("Name: ");
for (size_t i = 0; i < strlen(name); i++) {
putchar(name[i]);
}
putchar('\n');

// 6. 使用 fprintf 输出到标准错误流
if (age < 0 || age > 150) {
fprintf(stderr, "Invalid age entered!\n");
} else {
printf("Age: %d\n", age);
}

// 7. 使用 getchar 暂停程序(等待回车)
printf("Press Enter to exit...");
getchar(); // 可能吃到上次 scanf 的换行,可以用两次 getchar() 来解决
getchar();

return 0;
}

📌 使用说明

函数 功能说明
fgets() stdin 读取一行文本(含空格)
fscanf() stdin 按格式读取数据
getchar() 读取一个字符
printf() stdout 输出格式化内容
fprintf() 向指定流输出格式化内容,如 stderr
puts() stdout 输出一行文本(自动换行)
putchar() 输出一个字符

✅3.使用文件指针输出到文件或者控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main() {
FILE *fp = fopen("log.txt", "w");

if (fp) {
fprintf(fp, "Logging some info...\n");
fflush(fp); // 立即写入磁盘
fclose(fp);
}

printf("Please enter a character: ");
int c = fgetc(stdin); // 与 getchar() 等效
fputc(c, stdout); // 输出字符

return 0;
}

✅4.使用文件指针指向标准输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>

int main() {

FILE* p = stdout;
const char* str = "ddd\n";
fputs(str, p);

int num = 11;
wchar_t string[100];
swprintf(string, 2, L"%d\n", num);

fprintf(stdout, "hello \n");
fprintf(stderr, "World!\n");

return 0;

}

windows程序用户层的输出流

1. 使用 CreateFile("CONOUT$") 直接打开控制台输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <windows.h>
#include <stdio.h>

int main() {
// 打开控制台输出设备(相当于 stdout)
HANDLE hConsoleOut = CreateFileA(
"CONOUT$",
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL
);

if (hConsoleOut == INVALID_HANDLE_VALUE) {
printf("无法打开 CONOUT$\n");
return 1;
}

// 写入控制台
const char* msg = "Hello from CreateFile to console output!\n";
DWORD written;
WriteFile(hConsoleOut, msg, (DWORD)strlen(msg), &written, NULL);

CloseHandle(hConsoleOut);
return 0;
}

也可以类似地打开 CONIN$,然后用 ReadFile 从控制台读取输入。

一些常见的特殊设备名

设备名 说明
CON 控制台(等价于 CONIN$ + CONOUT$,但行为不一致)
CONIN$ 控制台输入(键盘)
CONOUT$ 控制台输出(屏幕)
NUL 空设备,相当于 /dev/null
PRN 打印机
COM1COM9 串口设备
LPT1LPT9 并口设备

如果重定向了 stdout/stderr,或者是 GUI 应用没有默认控制台窗口时,用 CreateFile("CONOUT$", ...) 也可以获取控制台输出句柄进行直接写入,非常常用。


2. 使用 GetStdHandle(STD_OUTPUT_HANDLE)

1
2
3
4
5
6
7
8
9
10
#include <windows.h>
#include <stdio.h>

int main() {
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
const char* msg = "Hello from GetStdHandle!\n";
DWORD written;
WriteFile(hStdOut, msg, (DWORD)strlen(msg), &written, NULL);
return 0;
}

4. 使用 freopen 重定向 stdout 到控制台

这个适用于GUI 应用或重定向后恢复标准输出。

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <windows.h>

int main() {
AllocConsole(); // 为 GUI 应用创建控制台(控制台程序可以省略)

freopen("CONOUT$", "w", stdout);
printf("Hello from freopen to CONOUT$!\n");

return 0;
}

5. 使用 WriteConsole

1
2
3
4
5
6
7
8
9
10
11
#include <windows.h>
#include <stdio.h>

int main() {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
const wchar_t* msg = L"Hello from WriteConsole!\n";
DWORD written;

WriteConsoleW(hConsole, msg, wcslen(msg), &written, NULL);
return 0;
}

6.示例:Windows 控制台 I/O 专用函数演示

以下是使用 Windows 控制台 API 的一组完整示例,展示如何使用:

  • ReadConsole:从控制台读取输入
  • WriteConsole:写入控制台文本
  • WriteConsoleOutput:向控制台缓冲区写入字符信息(低级操作)
  • SetConsoleCursorPosition:设置光标位置
  • SetConsoleTextAttribute:设置输出文字颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <windows.h>
#include <stdio.h>

int main() {
// 获取标准输入和输出句柄
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

// 1. 设置控制台颜色属性(绿色文本)
SetConsoleTextAttribute(hOutput, FOREGROUND_GREEN | FOREGROUND_INTENSITY);

// 2. 写入一行文本到控制台
const wchar_t* msg = L"Hello from WriteConsole (Green Text)\n";
DWORD written;
WriteConsoleW(hOutput, msg, wcslen(msg), &written, NULL);

// 3. 设置光标位置(第 10 行,第 20 列)
COORD coord = { 20, 10 };
SetConsoleCursorPosition(hOutput, coord);

// 4. 写入第二行文本到指定位置
const wchar_t* msg2 = L"Cursor Moved Here!";
WriteConsoleW(hOutput, msg2, wcslen(msg2), &written, NULL);

// 5. 从用户读取一行输入(宽字符)
wchar_t buffer[128];
DWORD read;
WriteConsoleW(hOutput, L"\nEnter some text: ", 19, &written, NULL);
ReadConsoleW(hInput, buffer, 128, &read, NULL);
buffer[read - 2] = L'\0'; // 去掉回车换行

// 6. 显示读取的内容
WriteConsoleW(hOutput, L"You typed: ", 11, &written, NULL);
WriteConsoleW(hOutput, buffer, wcslen(buffer), &written, NULL);
WriteConsoleW(hOutput, L"\n", 1, &written, NULL);

// 7. 使用 WriteConsoleOutput 写入一个字符块
CHAR_INFO ci[4];
for (int i = 0; i < 4; i++) {
ci[i].Char.AsciiChar = 'A' + i;
ci[i].Attributes = FOREGROUND_RED | FOREGROUND_INTENSITY;
}

COORD bufferSize = { 2, 2 }; // 2x2 字符块
COORD bufferCoord = { 0, 0 }; // 从缓冲区左上角写入
SMALL_RECT writeRegion = { 30, 12, 31, 13 }; // 屏幕上的位置区域

WriteConsoleOutputA(hOutput, ci, bufferSize, bufferCoord, &writeRegion);

return 0;
}

Linux程序用户层的输出流

1. 低级 I/O 函数(Unix/Linux 系统调用)

直接使用文件描述符(fd),性能更高但无缓冲:

  • write

    1
    2
    3
    #include <unistd.h>
    write(STDOUT_FILENO, "Hello\n", 6); // 标准输出(文件描述符 1)
    write(STDERR_FILENO, "Error\n", 6); // 标准错误(文件描述符 2)

2. 格式化输出扩展

  • dprintf
    类似 fprintf,但直接输出到文件描述符:

    1
    dprintf(STDOUT_FILENO, "Value: %d\n", 42); // 格式化到 fd
  • sprintf / snprintf
    格式化到字符串(可配合 write 输出):

    1
    2
    3
    char buf[100];
    snprintf(buf, sizeof(buf), "Formatted: %s", "text");
    write(STDOUT_FILENO, buf, strlen(buf));

3. 控制台专用操作(Linux 终端控制)

类似 Windows 的控制台 API,Linux 通过终端控制实现高级功能:

  • ioctl
    获取/设置终端属性(如窗口大小、光标位置):

    1
    2
    3
    #include <sys/ioctl.h>
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // 获取终端窗口大小
  • ANSI 转义序列
    通过特殊字符序列控制终端(颜色、光标等):

    1
    2
    printf("\033[31mRed Text\033[0m\n"); // 红色文本
    printf("\033[2J"); // 清屏

4. 示例:Linux 终端控制完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>

int main() {
// 1. 标准 C 库输出
printf("Standard printf\n");
fprintf(stderr, "Error message\n");

// 2. 低级 write 输出
write(STDOUT_FILENO, "Low-level write\n", 16);

// 3. 格式化到文件描述符
dprintf(STDOUT_FILENO, "dprintf: %d\n", 123);

// 4. 终端控制(ANSI 转义序列)
printf("\033[32mGreen Text\033[0m\n"); // 绿色文本
printf("\033[2;5HCursor at (2,5)\n"); // 移动光标

// 5. 获取终端窗口大小
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
printf("Terminal size: %d rows x %d cols\n", w.ws_row, w.ws_col);

return 0;
}

对比 Windows 与 Linux 输出函数

功能 Windows API Linux 等效方法
基础输出 WriteConsole write / printf
格式化输出 wsprintf printf / dprintf
控制台颜色 SetConsoleTextAttribute ANSI 转义序列(\033[31m
光标位置 SetConsoleCursorPosition ANSI 转义序列(\033[x;yH
低级缓冲区操作 WriteConsoleOutput ioctl + ANSI 控制

Linux 更依赖标准 C 库和 POSIX 系统调用,而 Windows 使用专用的控制台 API(如 WriteConsole)。

linux程序的标准输入流

在 Linux 系统中,标准输入流(stdin) 是程序默认的输入来源(通常是键盘输入或管道/重定向内容)。以下是 Linux 中处理标准输入流的常用函数和方法:

1. 标准 C 库函数(跨平台通用)

适用于大多数场景,具有缓冲机制:

scanf / fscanf

1
2
3
4
#include <stdio.h>
int num;
scanf("%d", &num); // 从 stdin 读取整数
fscanf(stdin, "%d", &num); // 等价于 scanf
  • 缺点:不安全(易缓冲区溢出),推荐使用 fgets + sscanf 组合。

getchar / fgetc

1
2
char c = getchar();              // 从 stdin 读取单个字符
char c2 = fgetc(stdin); // 等价于 getchar

gets (危险!已废弃) / fgets

1
2
char buf[100];
fgets(buf, sizeof(buf), stdin); // 安全读取一行(包括换行符)
  • fgets 是安全的,会限制读取长度,避免溢出。

getline (POSIX 标准)

动态分配内存读取任意长度行:

1
2
3
4
char *line = NULL;
size_t len = 0;
ssize_t read = getline(&line, &len, stdin); // 自动扩容缓冲区
free(line); // 需手动释放内存

2. 低级 I/O 函数(系统调用)

直接使用文件描述符(fd),无缓冲,适合高性能场景:

read

1
2
3
#include <unistd.h>
char buf[100];
ssize_t bytes = read(STDIN_FILENO, buf, sizeof(buf)); // 从 stdin 读取
  • STDIN_FILENO 是标准输入的文件描述符(值为 0)。
  • 返回实际读取的字节数,可能小于请求的字节数(如遇到 EOF)。

② 非阻塞输入(fcntl

1
2
3
4
#include <fcntl.h>
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // 设为非阻塞模式
char buf[100];
ssize_t bytes = read(STDIN_FILENO, buf, sizeof(buf)); // 立即返回,无数据时返回 -1

3. 终端控制(Linux 特有)

① 禁用行缓冲(termios

默认情况下,终端会启用行缓冲(需按回车键才提交输入)。以下代码禁用缓冲:

1
2
3
4
5
6
7
8
9
10
#include <termios.h>
struct termios old, new;
tcgetattr(STDIN_FILENO, &old); // 获取当前设置
new = old;
new.c_lflag &= ~(ICANON | ECHO); // 禁用规范模式(逐字符读取)和回显
tcsetattr(STDIN_FILENO, TCSANOW, &new);

char c;
read(STDIN_FILENO, &c, 1); // 直接读取单个字符(无需回车)
tcsetattr(STDIN_FILENO, TCSANOW, &old); // 恢复原始设置
  • 用途:实现实时按键检测(如游戏控制)。

② 获取终端输入事件

通过 ioctl 监听终端事件(如窗口大小变化):

1
2
3
#include <sys/ioctl.h>
struct winsize w;
ioctl(STDIN_FILENO, TIOCGWINSZ, &w); // 获取终端窗口大小

4. 示例代码

示例 1:安全读取用户输入

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
char buf[100];
printf("Enter a line: ");
fgets(buf, sizeof(buf), stdin); // 安全读取一行
printf("You entered: %s", buf);
return 0;
}

示例 2:非阻塞输入检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>
#include <fcntl.h>
int main() {
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); // 非阻塞模式
char c;
while (1) {
ssize_t bytes = read(STDIN_FILENO, &c, 1);
if (bytes > 0) {
printf("Got: %c\n", c);
if (c == 'q') break;
}
usleep(100000); // 避免 CPU 占用过高
}
return 0;
}

示例 3:逐字符读取(禁用行缓冲)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <termios.h>
#include <unistd.h>
int main() {
struct termios old, new;
tcgetattr(STDIN_FILENO, &old);
new = old;
new.c_lflag &= ~(ICANON | ECHO); // 禁用缓冲和回显
tcsetattr(STDIN_FILENO, TCSANOW, &new);

printf("Press any key (q to quit): ");
char c;
while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
printf("You pressed: %c\n", c);
}

tcsetattr(STDIN_FILENO, TCSANOW, &old); // 恢复设置
return 0;
}

5. 对比 Windows 与 Linux 输入函数

功能 Windows API Linux 等效方法
基础输入 ReadConsole read(STDIN_FILENO, ...)
格式化输入 scanf / fscanf scanf / fgets + sscanf
逐字符读取 _getch termios 禁用缓冲 + read
非阻塞输入 PeekConsoleInput fcntl + O_NONBLOCK

总结

  • 通用场景:优先使用 fgets + sscanfgetline
  • 高性能/底层控制:用 readtermios 调整终端模式。
  • 非阻塞输入:结合 fcntlO_NONBLOCK 标志。
  • 终端控制ioctl 和 ANSI 转义序列可实现复杂交互(如游戏、CLI 工具)。