1. 字符与字符串输入输出核心理论
1.1 getchar与putchar:单字符IO的基础
在C语言中,getchar()和putchar()是最基础的字符输入输出函数。它们的工作方式看似简单,但理解其底层机制对构建稳健的IO系统至关重要。
getchar()的函数原型为:
c复制int getchar(void);
这个函数每次调用时,会从标准输入(stdin)读取一个字符。关键点在于:
- 返回值是int而非char,这是为了能容纳EOF(通常定义为-1)这个特殊值
- 它会读取所有字符,包括空格、制表符和换行符,不会自动跳过任何空白字符
- 实际实现中,它通常使用缓冲IO,意味着输入会先存储在缓冲区,直到遇到换行或缓冲区满
一个典型的读取循环示例:
c复制int c;
while ((c = getchar()) != EOF) {
putchar(c);
}
这个简单的代码实际上实现了一个字符级别的复制功能。注意我们把getchar()的返回值赋给int而非char,这是为了避免某些平台上char默认无符号导致无法正确检测EOF。
putchar()的对应实现:
c复制int putchar(int c);
它接受一个int参数(实际使用时可以传入char),但内部会转换为unsigned char输出。返回值是输出的字符,出错时返回EOF。
重要提示:在Windows系统中,控制台输入EOF需要按Ctrl+Z;在Unix/Linux系统中则是Ctrl+D。这个差异经常让初学者困惑。
1.2 gets与puts:字符串IO的历史与隐患
字符串级别的IO函数中,gets()和puts()是最早引入的一批函数。puts()相对安全,但gets()已经被现代C标准(C11)正式移除,原因就是其致命的安全缺陷。
puts()的函数原型:
c复制int puts(const char *str);
它接受一个以null结尾的字符串,将其输出到stdout并自动追加换行符。相比printf("%s\n", str),puts()效率更高,因为它不需要解析格式字符串。
gets()的函数原型曾经是:
c复制char *gets(char *str);
这个函数的危险在于:
- 它无法知道目标缓冲区的长度
- 会一直读取直到遇到换行符或EOF
- 如果输入超过缓冲区大小,必然导致缓冲区溢出
一个典型的缓冲区溢出示例:
c复制char buffer[10];
gets(buffer); // 如果输入超过9个字符就会溢出
这种溢出可能被利用来执行任意代码,是许多安全漏洞的根源。
1.3 安全的字符串输入方法
现代C编程中,我们必须使用安全的替代方案来处理字符串输入:
1.3.1 fgets函数
fgets()是最推荐的替代方案:
c复制char *fgets(char *str, int n, FILE *stream);
关键特性:
- 第二个参数n指定了缓冲区大小
- 最多读取n-1个字符,保证最后一个字符是'\0'
- 保留换行符(与gets不同)
- 可以指定输入流(stdin或文件)
使用示例:
c复制char buffer[10];
fgets(buffer, sizeof(buffer), stdin);
1.3.2 scanf的限制长度用法
scanf()也可以相对安全地读取字符串:
c复制char buffer[10];
scanf("%9s", buffer); // 限制最大长度为9
但需要注意:
- 遇到空白符会停止读取
- 不会读取换行符
- 格式字符串中的长度限制必须比缓冲区实际大小少1
1.4 基础文本处理的核心思路
文本处理的核心模式可以归纳为:
- 逐字符或逐行读取输入
- 对每个字符/行进行处理(统计、转换、分析等)
- 输出处理结果
这个过程中,ASCII码的判断是关键。例如:
- 判断字母:
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - 判断数字:
c >= '0' && c <= '9' - 判断空白:
c == ' ' || c == '\t' || c == '\n'
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 字符与字符串IO实战训练
2.1 getchar和putchar基础应用
让我们实现一个简单的字符过滤
