1. 引言
在 c 语言中,字符是文本处理的最小单位。无论是读取用户敲下的一个字母、处理文件中的一个字节,还是向屏幕输出一行提示,都离不开字符输入/输出。掌握字符 i/o 是理解标准输入输出流、缓冲区和更复杂文本处理的基础。
本文将围绕 c 标准库中最常用的两个字符函数展开:
getchar():从标准输入读取一个字符。putchar():向标准输出写入一个字符。
通过原理讲解、代码示例和常见陷阱分析,帮助你彻底掌握 c 语言中的字符输入/输出。
2. 字符 i/o 的基本概念
2.1 标准输入与标准输出
c 语言通过三类标准流与外部环境交互:
| 流名称 | 文件指针 | 说明 |
|---|---|---|
| 标准输入 stdin | stdin | 通常关联键盘 |
| 标准输出 stdout | stdout | 通常关联屏幕 |
| 标准错误 stderr | stderr | 通常关联屏幕,用于输出错误信息 |
getchar() 和 putchar() 本质上分别是 fgetc(stdin) 与 fputc(c, stdout) 的简化形式,定义于 <stdio.h> 头文件中。
2.2 字符与整型
一个容易忽略的细节:getchar() 的返回值类型是 int,而不是 char。
原因在于,除了 0~255 的合法字符值外,到达文件末尾或发生错误时 getchar() 需要返回特殊值 eof。eof 通常被定义为 -1。如果把返回值直接赋给 char 类型变量,-1 可能无法与合法字符区分,导致程序无法正确判断输入结束。
因此推荐用法是:
int c;
while ((c = getchar()) != eof) {
// 处理字符 c
}
3. 常用输入输出函数详解
3.1 getchar:从标准输入读取一个字符
函数原型:
#include <stdio.h> int getchar(void);
getchar() 每次调用会从标准输入流中读取并返回一个字符。如果输入流中没有数据,则程序会阻塞等待,直到用户输入内容并按下回车键。
返回值:
- 成功:读到的字符(以
unsigned char转换为int返回)。 - 失败或到达文件末尾:返回
eof。
3.2 putchar:向标准输出写入一个字符
函数原型:
#include <stdio.h> int putchar(int c);
putchar() 把参数 c 对应的字符写入标准输出流。注意虽然参数类型是 int,但内部只使用其低 8 位作为字符输出。
返回值:
- 成功:返回写入的字符。
- 失败:返回
eof。
标准输出 stdout 同样受缓冲机制影响。putchar() 调用成功后,字符首先被写入 stdout 的缓冲区,并不保证立刻显示到屏幕上:
- 当
stdout连接到终端时,通常采用行缓冲,只有写入换行符\n或缓冲区满时,内容才会刷新并显示。 - 当
stdout被重定向到文件时,通常变为全缓冲,可能直到缓冲区满或程序正常结束才真正写入文件。
因此,下面的代码在部分环境中可能看不到提示语:
#include <stdio.h>
int main(void) {
printf("请输入字符:");
putchar('>'); // 没有换行,可能暂时看不到
fflush(stdout); // 立即刷新输出缓冲区
return 0;
}常见的刷新时机包括:写入换行符、缓冲区写满、调用 fflush(stdout)、程序正常结束,以及部分流在读取输入前自动刷新。理解这一点可以避免「提示语没显示」或「输出顺序看起来不对」的问题。
3.3 一个最简单的示例
以下程序从键盘读取字符,并在屏幕上原样输出,直到遇到文件结束符:
#include <stdio.h>
int main(void) {
int c;
printf("请输入字符(按 ctrl+d 或 ctrl+z 结束):\n");
while ((c = getchar()) != eof) {
putchar(c);
}
return 0;
}在 linux/macos 终端中,通常按 ctrl+d 表示输入结束;在 windows 命令行中,通常按 ctrl+z 后回车。
3.4 printf:格式化输出
printf() 是 c 中最常用的格式化输出函数,定义在 <stdio.h> 中。它根据格式串把数据格式化后写入标准输出 stdout。
函数原型:
#include <stdio.h> int printf(const char *format, ...);
format:格式控制串,普通字符原样输出,%开头的转换说明用于格式化后续参数。- 返回值:成功时返回输出的字符数,失败时返回负值。
常用转换说明:
| 说明 | 含义 | 示例 |
|---|---|---|
%d / %i | 有符号十进制整数 | printf("%d", 42) 输出 42 |
%u | 无符号十进制整数 | printf("%u", 42u) 输出 42 |
%f | 浮点数 | printf("%f", 3.14) 输出 3.140000 |
%c | 单个字符 | printf("%c", 'a') 输出 a |
%s | 字符串 | printf("%s", "hello") 输出 hello |
%x / %x | 十六进制整数 | printf("%x", 255) 输出 ff |
%% | 输出百分号本身 | printf("%%") 输出 % |
示例:
#include <stdio.h>
int main(void) {
int age = 20;
double score = 92.5;
printf("年龄:%d\n", age);
printf("成绩:%.1f\n", score); // 保留 1 位小数
printf("百分号:%%\n");
return 0;
}示例中 %.1f 表示浮点数保留 1 位小数,是 printf() 格式控制能力的典型体现。
3.5 scanf:格式化输入
scanf() 用于从标准输入 stdin 按指定格式读取数据,并把结果写入对应变量的地址中。
函数原型:
#include <stdio.h> int scanf(const char *format, ...);
- 形参必须传入变量的地址,例如
scanf("%d", &age),不能只写变量名age。 - 返回值:成功读取并赋值的项数;若在读取前遇到输入结束,则返回
eof。
示例:
#include <stdio.h>
int main(void) {
int age;
double score;
printf("请输入年龄和成绩:");
scanf("%d %lf", &age, &score);
printf("年龄:%d,成绩:%.1f\n", age, score);
return 0;
}注意事项:
%d读取int,%lf读取double,%c读取单个字符,%s读取一个以空白分隔的字符串(不包含空格)。scanf()同样受缓冲区影响:它会等待用户按下回车后才开始解析输入。%s不会检查缓冲区大小,容易越界;读取含空格的一整行时,通常使用fgets()更安全。
3.6 四种函数对比与使用场景
| 函数 | 功能 | 操作对象 | 是否格式化 | 典型场景 |
|---|---|---|---|---|
getchar() | 读取一个字符 | stdin | 否 | 逐字符处理、字符统计、过滤空白 |
putchar() | 输出一个字符 | stdout | 否 | 逐字符输出、大小写转换 |
scanf() | 按格式读取数据 | stdin | 是 | 读取整数、小数、单词等结构化输入 |
printf() | 按格式输出数据 | stdout | 是 | 输出变量、调试信息、格式化文本 |
选择建议:
- 只需逐字符读取或处理时,优先用
getchar()/putchar(),代码简单、行为可控。 - 需要读取整数、浮点数或单词等结构化数据时,用
scanf()更直接。 - 需要定制输出格式(小数位、宽度、十六进制等)时,选择
printf()。 - 交互式输入中混用
scanf()与getchar()时,要特别注意缓冲区中残留的换行符。
4. 缓冲区机制:为什么输入总是不“即时”
很多初学者运行下面的程序时会感到疑惑:
#include <stdio.h>
int main(void) {
int c;
printf("输入一个字符:");
c = getchar();
printf("你输入了:%c\n", c);
return 0;
}运行后程序并不会在用户按下第一个键时立刻响应,而是要等到用户按下回车后才输出结果。原因是标准输入默认采用**行缓冲(line buffering)**策略。
4.1 三种缓冲模式
| 模式 | 说明 | 典型场景 |
|---|---|---|
| 全缓冲 | 缓冲区满或显式刷新时才 i/o | 文件读写 |
| 行缓冲 | 遇到换行符或缓冲区满时刷新 | 交互式终端 |
| 无缓冲 | 每次读写立即执行 | 标准错误流 stderr |
标准输入 stdin 在连接到终端时通常为行缓冲,因此用户的输入会被暂存在缓冲区中,直到按下回车,数据才真正交付给程序。
4.2 回显机制
在终端中,用户键入的字符会立即显示在屏幕上,这是终端自身的回显行为,并不代表程序已经读取了这些字符。等到按下回车后,getchar() 才逐个读取缓冲区内的字符。
4.3 用 while 循环消费完剩余输入
下面的程序提示用户输入一个字符,但如果用户输入了多个字符,多余的字符仍会残留,进而影响后续读取:
#include <stdio.h>
int main(void) {
int c;
printf("请输入一个字符:");
c = getchar();
// 消费掉本行剩余的所有字符,直到换行符
while (getchar() != '\n')
;
printf("你输入的字符是:%c\n", c);
return 0;
}上面的技巧常用于“只读取第一个字符,丢弃本行剩余内容”。
5. 字符计数、行计数与单词计数
字符 i/o 是很多文本统计程序的基础。下面实现一个统计输入中字符数、行数和单词数的程序:
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
int main(void) {
int c;
long chars = 0;
long lines = 0;
long words = 0;
bool in_word = false;
while ((c = getchar()) != eof) {
chars++;
if (c == '\n') {
lines++;
}
if (isspace(c)) {
in_word = false;
} else if (!in_word) {
in_word = true;
words++;
}
}
printf("字符数:%ld\n", chars);
printf("行数:%ld\n", lines);
printf("单词数:%ld\n", words);
return 0;
}程序使用 <ctype.h> 中的 isspace() 判断空白字符,使用布尔变量 in_word 追踪当前是否处于单词内部,从而在单词起始处正确计数。
6. 大小写转换实战
字符 i/o 常用于逐字符变换。下面程序读取输入,将小写字母转换为大写字母,其他字符原样输出:
#include <stdio.h>
#include <ctype.h>
int main(void) {
int c;
while ((c = getchar()) != eof) {
putchar(toupper(c));
}
return 0;
}toupper() 同样来自 <ctype.h>,它会无条件返回大写形式,对非小写字母则原样返回。
7. 常见陷阱与错误
7.1 用 char 接收 getchar 返回值
错误示范:
char c;
while ((c = getchar()) != eof) {
// ...
}
当 getchar() 返回 eof(即 -1)时,赋给 char 后可能变成 255 或溢出,导致循环条件永远无法与 eof 比较成功,可能陷入死循环。应使用 int 类型接收。
7.2 忘记处理换行符
下面这段代码看似能依次读取两个字符:
char c1 = getchar(); char c2 = getchar();
如果用户输入 a 后回车,第一个 getchar() 读到 'a',第二个 getchar() 立即读到了换行符 '\n',而不是用户期望的下一个字符。处理交互式输入时要格外注意缓冲区中残留的换行符。
7.3 混淆 eof 与实际按键
在终端中,eof 并不是键盘上某一个普通按键,而是由终端通过特殊按键组合(如 ctrl+d 或 ctrl+z)发送的信号。ctrl+z 在 windows 下往往需要按回车后才生效,ctrl+d 在 linux/macos 下通常需要在一行开头按下才能触发 eof。
8. 文件字符 i/o 的扩展
getchar() 与 putchar() 只能作用于标准输入输出。若要对文件进行逐字符读写,可以使用功能更通用的 fgetc() 与 fputc():
#include <stdio.h> int fgetc(file *stream); int fputc(int c, file *stream);
例如,将一个文本文件的内容复制到另一个文件:
#include <stdio.h>
int main(void) {
file *in = fopen("input.txt", "r");
file *out = fopen("output.txt", "w");
if (in == null || out == null) {
perror("打开文件失败");
return 1;
}
int c;
while ((c = fgetc(in)) != eof) {
fputc(c, out);
}
fclose(in);
fclose(out);
return 0;
}通过将操作对象从标准流扩展为任意文件指针,就可以用同样的思路处理文件内容。
9. 总结
字符输入/输出是 c 语言文本编程的基石:
getchar()和putchar()分别负责从标准输入读取和向标准输出写入一个字符。scanf()与printf()负责结构化数据的格式化输入与输出。scanf()需要传入变量地址;getchar()返回int类型,以便与eof正确比较。- 标准输入的行缓冲机制决定了程序通常要等用户按下回车后才开始处理输入。
- 使用
while循环配合eof可以逐字符处理任意长度的输入。 - 注意换行符残留、
char溢出以及scanf()地址参数等常见陷阱。理解了字符 i/o 和缓冲区机制后,再学习字符串处理、格式化输入输出以及文件操作就会轻松许多。建议读者动手运行文中的示例,并尝试修改程序,观察不同输入下的输出行为,从而真正掌握这些基础而重要的概念。
到此这篇关于c 语言字符输入/输出:getchar、putchar 与缓冲区机制详解(实例代码)的文章就介绍到这了,更多相关c 语言getchar、putchar 与缓冲区机制内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论