C语言输入和输出
本章定位 :程序要能"干活",就必须能接收用户给的数据 ,并把结果展示给用户。输入和输出(简称 I/O,Input/Output)就是程序与外界沟通的"嘴巴和耳朵"。本章学完,你写的程序就不再是"闷头算数",而是能和人对话的程序了。
4.1 初步认识 C 语句
4.1.1 C 语句简介
1. 什么是"语句"
知识点说明 :C 程序的基本组成单位是函数 ,而函数体是由一条条语句(statement)组成的。语句是"让计算机做事的一条命令",每条语句以 分号 ; 结尾。
通俗理解 :如果把一个 C 程序比作一本菜谱,那么函数就是"一道菜的做法",语句就是做法里的每一个步骤------"切菜"、"下锅"、"放盐"。分号就是一个步骤结束的句号。
2. C 语句的 5 大分类(重点,必须记住)
| 类别 | 形式 | 作用 | 例子 |
|---|---|---|---|
| ① 控制语句 | if、for、while、switch、return 等 |
控制程序执行的流程和顺序 | if (a > 0) b = 1; |
| ② 函数调用语句 | 函数名(参数); |
调用一个函数完成某功能 | printf("hello"); |
| ③ 表达式语句 | 表达式; |
把一个表达式变成一条命令 | a = b + 1;、i++; |
| ④ 空语句 | ; |
什么都不做(占位) | while (getchar() != '\n') ; |
| ⑤ 复合语句 | { ... } |
把多条语句打包成"一条" | { a=1; b=2; c=a+b; } |
3. 三条容易踩坑的规则
规则1:表达式 ≠ 语句,加分号才成语句
c
a = b + 3; // 这是语句(有分号)
a = b + 3 // 这是表达式,单独出现会报错
规则2:复合语句的 {} 后面不要加分号
c
{ x = 1; y = 2; } // ✅ 正确
{ x = 1; y = 2; }; // ❌ 多了分号(多数编译器只警告,但属于坏习惯)
规则3:空语句不是"没写完",它是合法的
c
for (i = 0; i < 100000; i++) ; // 循环体故意留空,只做延时/等待
4. 为什么要学这个知识点?有什么用?
- 看懂编译器报错 :80% 的初学报错(
expected ';' before '}')都是分号问题,理解"语句"概念后一眼就能看懂报错。 - 写出自洽的代码 :知道
{}是一条语句,才能理解后面if...else的配对规则(第5章)。 - 读懂别人的代码 :
for(;;);这种"看着像乱码"的写法,本质就是"空语句"。
4.1.2 赋值语句 ★★★(本章第一个重点)
1. 基本格式
c
变量名 = 表达式;
功能 :先计算右边表达式的值,再把这个值存入左边的变量中。
c
int a;
a = 3 + 5; // 先算 3+5=8,再把 8 放进 a
通俗理解 :变量就是一个贴了标签的盒子 ,
=就是"把右边的东西装进左边的盒子里"。所以左边必须是盒子(变量),不能是数字。
2. 最最最重要的区分:= 是赋值,== 才是判断相等
这是 C 语言初学者的第一大坑。
| 符号 | 名称 | 含义 | 例子 |
|---|---|---|---|
= |
赋值运算符 | 把右边的值放进左边变量 | a = 5; |
== |
关系运算符 | 判断两边是否相等,结果是 1 或 0 | a == 5 |
经典 Bug 演示:
c
int a = 10, b = 20;
if (a == b) printf("相等\n");
else printf("不相等\n"); // 输出:不相等 ✅
if (a = b) printf("相等\n"); // ⚠️ 注意这里是 =
上面 if (a = b) 干了什么?
- 把
b(20)赋给a,a变成 20; - 赋值表达式的值是 20,非 0 为"真";
- 所以输出"相等"------逻辑完全错了,而且编译器通常只给一个警告。
通俗理解 :
==是"问一下:这两个一样吗?"(只看不动手);=是"把右边的东西塞给左边"(动手搬运)。用错=就像你想问"你是不是王明?",结果直接把对方改名成王明。
防坑技巧 :写判断时把常量写在左边 ,即使误写 = 也会立刻报错:
c
if (5 == a) // ✅ 习惯写法
if (5 = a) // ❌ 但编译器立刻报错:5 是常量不能被赋值 ------ 帮你抓住 Bug
3. 赋值表达式本身有"值"
a = b = c = 5; 这种多重赋值 之所以能写,就是因为"赋值"这个动作完成之后,还会返回一个结果(就是被赋的那个值)。
c
int a, b, c;
a = b = c = 5; // 从右往左:c=5 → b=5 → a=5
printf("%d %d %d", a, b, c); // 输出:5 5 5
通俗理解 :像"接力传水",一桶水(5)从
c传给b,再传给a,最后三个变量都是 5。
注意顺序:
c
int a = 1, b = 2;
a = b = 10; // b=10, 然后 a=10
4. 复合赋值运算符(简写形式)
| 完整写法 | 简写 | 说明 |
|---|---|---|
a = a + 3 |
a += 3 |
加 |
a = a - 3 |
a -= 3 |
减 |
a = a * 3 |
a *= 3 |
乘 |
a = a / 3 |
a /= 3 |
除 |
a = a % 3 |
a %= 3 |
取余 |
例子:统计购物车总价
c
#include <stdio.h>
int main(void)
{
int total = 0; // 购物车总价
total += 15; // 加一本书 15 元
total += 8; // 加一支笔 8 元
total += 32; // 加一个杯子 32 元
printf("总价 = %d 元\n", total); // 输出:总价 = 55 元
return 0;
}
为什么有用 :累加、累减是编程里最高频的操作(求和、计数、扣钱),+= 写起来短,读起来也清晰。
5. 赋值时的类型转换(自动"改装")
把一个值装进盒子时,如果盒子大小和值不匹配,C 会自动转换,可能丢东西。
c
int a;
a = 3.99; // a = 3 (小数部分被直接砍掉,不是四舍五入!)
c
float f;
f = 10 / 3; // f = 3.000000 !(10/3 是整数除法先算出 3,再转成 3.0)
c
float f;
f = 10.0 / 3; // f = 3.333333 ✅(有一个是小数就做小数除法)
通俗理解 :整数除法就像"分月饼只数整块",10 块月饼 3 人分,每人 3 块,剩下 1 块直接不管了。想要"精确分",就得让数字带小数点(
10.0)。
6. 为什么学赋值语句?
- 它是程序保存数据的唯一手段。不赋值,算出来的结果就没地方放。
=与==混淆是笔试面试高频陷阱题。- 理解"赋值表达式有值",才能看懂
while ((c = getchar()) != EOF)这种高级写法(见 4.2.2)。
4.2 数据的输入和输出
4.2.0 先搞懂一个底层概念:缓冲区 ★
知识点说明:你从键盘敲的东西,并不是"敲一个字符就立刻送到程序手里",而是先进入一块叫**缓冲区(buffer)**的"临时水池",等满足条件了才一次性交给程序。
通俗理解 :就像水龙头和水池 。你敲键盘是往水池里倒水,程序是接水的杯子。水池满了,或者你按了回车("放水阀"),水才会流到杯子里。这个机制叫行缓冲。
这有什么用? 后面 scanf 和 getchar/gets_s 混用时"莫名其妙跳过输入"的问题,根源全在缓冲区残留。理解了它,你就理解了本章 50% 的疑难杂症。
4.2.1 putchar 函数 ------ 输出一个字符
1. 函数原型
c
int putchar(int c); // 头文件:stdio.h
2. 功能与参数
| 项目 | 说明 |
|---|---|
| 功能 | 向屏幕(标准输出)输出一个字符 |
| 参数 | 一个字符或其 ASCII 码值 |
| 返回值 | 输出的字符的 ASCII 码;失败返回 EOF |
| 头文件 | #include <stdio.h> |
3. 三种常见用法
c
putchar('A'); // ① 直接写字符常量,输出 A
putchar(65); // ② 写 ASCII 码值,65 就是 'A',输出 A
putchar('\n'); // ③ 输出转义字符,作用是换行(最常用!)
4. 完整例子:用 putchar 输出 "OK" 并换行
c
#include <stdio.h>
int main(void)
{
putchar('O');
putchar('K');
putchar('\n'); // 换行
return 0;
}
运行结果:
OK
5. 例子:把小写字母转成大写输出
c
#include <stdio.h>
int main(void)
{
char ch = 'a';
putchar(ch - 32); // 'a' 的 ASCII 是 97,减 32 变 65,即 'A'
putchar('\n');
return 0;
}
输出 :A
通俗理解 ASCII :字符在计算机里都对应一个编号,
'a'=97,'A'=65,小写比大写正好大 32。所以"减 32"就是"变大写"。
6. putchar 与 printf 的选择
| 场景 | 用哪个 |
|---|---|
| 只输出一个字符、或拼字符 | putchar(更轻快、更直观) |
| 输出数字、字符串、带格式内容 | printf |
7. 为什么要学 putchar?
- 它是最简单的输出函数,用来入门"输出"概念刚刚好。
- 输出字符图形(星号三角形)、逐字符处理字符串时必须用它。
- 理解了"字符 = 数字",就理解了字符运算的底层逻辑。
4.2.2 getchar 函数 ------ 输入一个字符 ★
1. 函数原型
c
int getchar(void); // 头文件:stdio.h
2. 关键点:为什么返回 int 而不是 char?
| 项目 | 说明 |
|---|---|
| 功能 | 从键盘(标准输入)读入一个字符 |
| 参数 | 无 |
| 返回值 | 读到的字符的 ASCII 码;读到末尾/出错返回 EOF |
| 注意 | 每调用一次,只吃掉一个字符 |
原因(重要) :char 类型的取值范围是 -128~127,而 EOF 一般定义为 -1 。如果返回 char,读到的第 255 号字符可能被误判成 EOF,程序会提前结束。所以用 int 来"装得下 EOF"。
通俗理解 :就像用一个小瓶子装水,还要专门留出一格表示"没水了"。如果瓶子太满(用
char),就分不清"满瓶"和"空瓶"。用大一号的瓶子(int)就没这个问题。
3. 基础用法:读一个字符并显示
c
#include <stdio.h>
int main(void)
{
char ch;
ch = getchar(); // 等你敲一个字符并按回车
putchar(ch);
putchar('\n');
return 0;
}
运行示例:
x↵
x
4. 用 getchar 清空缓冲区(超实用技巧)
c
while (getchar() != '\n') ; // 一直读,直到把回车('\n')吃掉为止
为什么需要它 :scanf("%d", &a) 读完数字后,你敲的回车还留在缓冲区里 。接着用 scanf("%s",...) 或 gets_s 时,它会立刻把那个回车当输入------程序就"跳过输入"了。上面这行代码就是"把水管里的剩水放干净"。
5. 完整案例:统计一行有多少个字符
c
#include <stdio.h>
int main(void)
{
int count = 0, ch;
printf("请输入一行文字:");
while ((ch = getchar()) != '\n') // 不是回车就计数
count++;
printf("这一行共有 %d 个字符\n", count);
return 0;
}
运行示例:
请输入一行文字:hello world↵
这一行共有 11 个字符
逐句解释(这是本章最经典的写法,务必看懂):
(ch = getchar())先把读到的字符赋给ch;- 整个赋值表达式的值就是那个字符;
- 再拿它和
'\n'比较,不相等就继续循环。
通俗理解:像安检传送带------每件行李(字符)先拿出来放手上(赋值),再看是不是终点标志(回车),不是就继续往下传。
6. 为什么要学 getchar?
- 它是控制"一个字符一个字符"处理的最基础手段(数字符、加密解密、判断大小写)。
- 它是解决输入残留问题的万能钥匙(几乎所有混合输入 Bug 都靠它修)。
(ch = getchar()) != EOF是读取"不定长度输入"的标准写法,面试必考。
4.2.3 printf 函数 ------ 格式化输出 ★★★
1. 函数原型
c
int printf(const char *格式控制字符串, 输出表列);
2. 功能
按"格式控制字符串"指定的样式,把后面各项的值输出到屏幕。
一般格式:
c
printf("普通文字%格式字符 普通文字%格式字符...", 值1, 值2, ...);
3. 格式控制字符串的两个成分
| 成分 | 作用 | 例子 |
|---|---|---|
| 普通字符 | 原样照打(含空格、标点、汉字) | printf("结果是:"); |
格式声明 %... |
按格式替换成后面的值 | %d → 整数 |
c
int a = 25;
printf("今年 %d 岁\n", a);
// ↑原样 ↑替换 ↑原样 ↑换行
// 输出:今年 25 岁
4. 常用格式字符表(必须背熟)★
| 格式字符 | 输出类型 | 例子 | 输出结果 |
|---|---|---|---|
%d |
int 十进制整数 | printf("%d", 25) |
25 |
%ld |
long 长整型 | printf("%ld", 123456L) |
123456 |
%c |
char 单个字符 | printf("%c", 'A') |
A |
%s |
字符串 | printf("%s", "hello") |
hello |
%f |
float/double 小数 | printf("%f", 3.14) |
3.140000 |
%e |
指数形式小数 | printf("%e", 1234.5) |
1.234500e+03 |
%u |
无符号整数 | printf("%u", 100) |
100 |
%o |
八进制 | printf("%o", 8) |
10 |
%x |
十六进制 | printf("%x", 255) |
ff |
%% |
输出一个 % |
printf("80%%") |
80% |
%5d |
右对齐,占 5 格 | printf("[%5d]", 25) |
[ 25] |
%-5d |
左对齐,占 5 格 | printf("[%-5d]", 25) |
[25 ] |
%05d |
不足补 0 | printf("%05d", 25) |
00025 |
%.2f |
保留 2 位小数 | printf("%.2f", 3.14159) |
3.14 |
%8.2f |
占 8 格,2 位小数 | printf("[%8.2f]", 3.14159) |
[ 3.14] |
格式总结口诀 :% [对齐-] [宽度] [.小数位数] 类型字符
5. 例1:输出一个学生的成绩单(表格对齐)
c
#include <stdio.h>
int main(void)
{
printf("%-8s%-6s%-8s\n", "姓名", "学号", "成绩");
printf("%-8s%-6d%-8.1f\n", "张三", 1001, 92.55);
printf("%-8s%-6d%-8.1f\n", "李四", 1002, 78.44);
return 0;
}
运行结果(整齐的表格):
姓名 学号 成绩
张三 1001 92.6
李四 1002 78.4
为什么要用 %-8s 而不是直接打空格 :用空格手动对齐,一旦字数不同就全乱;用宽度控制,让编译器自动补空格,永远整齐。
6. 例2:温度换算(展示 %f 的小数位控制)
c
#include <stdio.h>
int main(void)
{
float f = 98.6; // 华氏度
float c = (f - 32) * 5 / 9; // 摄氏度
printf("华氏 %.1f 度 = 摄氏 %.2f 度\n", f, c);
return 0;
}
输出 :华氏 98.6 度 = 摄氏 37.00 度
7. 例3:printf 的返回值(表示"输出了几个字符")
c
#include <stdio.h>
int main(void)
{
int n = printf("hello"); // 输出 5 个字符
printf("\n输出字符个数 = %d\n", n); // 5
return 0;
}
8. 三大易错点
易错1:格式字符与参数类型必须一一对应、个数一致
c
printf("%d %d", 5); // ❌ 少一个参数 → 第二个是垃圾值
printf("%d", 3.5); // ❌ 类型不匹配 → 输出乱码数字
易错2:% 后面漏写字母 / 忘了转义
c
printf("完成 100%"); // ❌ % 后面没有格式字符,结果不可预期
printf("完成 100%%"); // ✅ 用 %% 表示一个 %
易错3:不要在 printf 里同时修改同一个变量(未定义行为)
c
int i = 5;
printf("%d %d\n", i++, i++); // ⚠️ 输出结果不确定!不同编译器不同
通俗理解 :就像让两个助手同时 从同一个盒子里取东西并放东西,谁先谁后没有规定,结果自然不可靠。这种写法考试题里常见,答案是"结果不确定/未定义"。
9. 为什么要学 printf?
- 它是90% 的结果输出手段,不会 printf 就等于程序没长嘴。
- 格式控制(对齐、精度)能做出专业报表,在数据类程序中非常实用。
- "格式字符与参数匹配"是笔试基础题,
printf返回值、求值顺序是面试陷阱题。
4.2.4 scanf 函数 ------ 格式化输入 ★★★
1. 函数原型
c
int scanf(const char *格式控制字符串, 地址表列);
2. 功能
按"格式控制字符串"规定的格式,从键盘读入数据,存入指定变量的内存地址。
3. 两个最重要的规则 ★★★
规则1:变量名前面必须加 &(取地址符)
c
int a;
scanf("%d", &a); // ✅ 正确:把读到的数放进 a 所在的"地址"
scanf("%d", a); // ❌ 错误:没有地址,程序不知道往哪放 → 运行崩溃
通俗理解 :寄快递必须写收件地址 ,光写收件人名字(变量名)快递员找不到地方。
&a就是"a 的收件地址"。例外 :数组名本身代表地址,
scanf("%s", str);不加&(字符串部分会讲)。
规则2:格式串里的普通字符,输入时必须原样敲
c
scanf("%d,%d", &a, &b); // 你必须输入:3,5 ↵
scanf("%d %d", &a, &b); // 输入 3 5 ↵ 或 3↵5↵ 都行(空格=任意空白)
scanf("a=%d,b=%d", &a, &b); // 必须输入:a=3,b=5 ↵
通俗理解 :
%d是"填空处",其他字符是"印好的字"。你填表格时,印好的"姓名:"不能涂改,只能在空格处填内容。
4. 常用格式字符(与 printf 类似)
| 格式字符 | 含义 | 示例输入 |
|---|---|---|
%d |
int 整数 | 25 |
%ld |
long | 123456 |
%f |
float | 3.14 |
%lf |
double ⚠️ | 3.14 |
%c |
单个字符(含空格) | A |
%s |
字符串(到空白为止) | hello |
%x |
十六进制整数 | ff |
⚠️ scanf 里的特殊规定 :%f 对应 float*,%lf 对应 double* 。这和 printf 不同(printf 中 %f 就能输出 double)------这是高频考点。
5. 抑制符 *(只读不存)
c
scanf("%*d %d", &a); // 输入:10 20 ↗ → 10 被丢弃,a = 20
用途:跳过不需要的数据(比如跳过年份只取月份)。
6. 完整案例:输入长宽求矩形面积
c
#include <stdio.h>
int main(void)
{
float length, width, area;
printf("请输入长和宽(用空格隔开):");
scanf("%f %f", &length, &width);
area = length * width;
printf("长=%.2f 宽=%.2f 面积=%.2f\n", length, width, area);
return 0;
}
运行示例:
请输入长和宽(用空格隔开):3.5 2.0↵
长=3.50 宽=2.00 面积=7.00
7. 案例:输入"2025-06-18"这种格式的日期
c
#include <stdio.h>
int main(void)
{
int y, m, d;
printf("请输入日期(格式 2025-06-18):");
scanf("%d-%d-%d", &y, &m, &d); // 普通字符 '-' 必须原样输入
printf("%d年%d月%d日\n", y, m, d);
return 0;
}
运行示例:
请输入日期(格式 2025-06-18):2025-06-18↵
2025年6月18日
8. scanf 的四大坑 ★★
坑1:%c 会"吃掉"前面的空格和回车
c
int a; char ch;
scanf("%d", &a);
scanf("%c", &ch); // ⚠️ ch 里是残留的 '\n',而不是你想要的字符!
修法(三选一):
c
scanf("%d %c", &a, &ch); // 方法1:格式串中加个空格(推荐)
getchar(); // 方法2:手动吃掉回车
scanf("%d", &a);
while (getchar() != '\n') ; // 方法3:清空整行
scanf("%c", &ch);
坑2:%s 遇到空格就停止
c
char name[20];
scanf("%s", name);
// 输入:Li Lei ↵ → name 里只有 "Li"," Lei" 留在缓冲区
→ 这正是下一节 gets_s 要解决的问题。
坑3:输入类型不匹配,程序直接卡死
c
int a;
scanf("%d", &a); // 你输入 abc ↗
// scanf 读不到数字,直接返回 0,a 里是垃圾值,缓冲区还留着 abc
坑4:输入个数不足,程序会一直等
c
scanf("%d %d", &a, &b); // 只输了一个 5 就回车,程序继续等第二个数
9. scanf 的返回值(有用!)
返回成功读入并赋值的数据个数 ,出错或遇到文件末尾返回 EOF。
c
int a, b;
if (scanf("%d %d", &a, &b) == 2)
printf("输入成功:%d + %d = %d\n", a, b, a + b);
else
printf("输入格式错误!\n");
为什么有用 :这是防御性编程的第一步------先检查输入是否合法,再做计算。写正式程序必备。
10. 为什么要学 scanf?
- 它是程序接收用户数据的主要入口,没有输入的程序只能算固定答案。
- 格式串中"普通字符原样输入"这一特性,能一次性解析
2025-06-18、a=1,b=2这类结构化输入,非常强大。 &号、%lf、%c吃回车,是笔试面试的"必考三连"。
4.2.5 puts 函数 ------ 输出一行字符串
1. 函数原型
c
int puts(const char *str); // 头文件:stdio.h
2. 功能与特点
| 特点 | 说明 |
|---|---|
| 输出内容 | 输出一个字符串 |
| 自动换行 | 输出完自动追加一个换行 '\n'(和 printf 不同!) |
遇到 '\0' 停止 |
输出到字符串结束标志为止 |
| 返回值 | 非负值成功,EOF 失败 |
⚠️ 易错理解 :
puts不是把'\0'显示成换行,而是输出完字符串后自动加一个\n。'\0'本身是"字符串的句号",不显示。
3. 基础用法对比
c
#include <stdio.h>
int main(void)
{
puts("Hello, C!"); // 输出并自动换行
printf("Hello, C!"); // 输出但不换行
printf("\n"); // 需手动换行
printf("%s\n", "Hello, C!"); // 等价于 puts("Hello, C!");
return 0;
}
输出:
Hello, C!
Hello, C!
Hello, C!
4. 案例:输出个人名片
c
#include <stdio.h>
int main(void)
{
puts("======== 个人名片 ========");
puts("姓名:张三");
puts("职业:C语言学习者");
puts("==========================");
return 0;
}
为什么要用 puts :每一行都要换行时,puts 比 printf 短,也更清楚地表达"我只是打一行字"的意图。
5. puts 和 printf 怎么选?
| 需求 | 用哪个 |
|---|---|
| 只输出一个完整字符串,且要换行 | puts(简洁) |
| 要拼数字、格式化、不换行 | printf |
| 输出字符串中间带变量 | 只能 printf |
6. 为什么要学 puts?
- 它是最省事的"打印一行文字"方式,写提示信息时特别好用。
- 理解"字符串以
\0结尾"这个核心概念,是后面学习字符串、数组、指针的基石。 - 与
printf、puts、gets_s、scanf("%s")的区别是面试常问对比题。
4.2.6 C11 标准函数 gets_s ------ 安全地读入一行 ★★
1. 背景:为什么会有 gets_s?
旧函数 gets() 的致命缺陷 :它不检查缓冲区大小 ,你输入多少它就往数组里塞多少。输入一长串,就会溢出数组边界 ,覆盖掉别人的内存------这是著名的"缓冲区溢出漏洞",是很多病毒攻击的入口。因此 C11 标准正式删除了 gets()。
通俗理解 :
gets()像一个不看容量就倒水的人 ,你给它一个小杯子,它不管照样倒,水漫出来弄坏旁边的设备。gets_s()则会先问"杯子多大?",倒到快满就停手。
2. 函数原型
c
char *gets_s(char *str, rsize_t n);
// 参数1:存放字符串的数组(缓冲区首地址)
// 参数2:最多读入的字符个数(含结尾 '\0')
// 返回值:成功返回 str;失败或读到 EOF 返回 NULL
头文件 :#include <stdio.h>(部分编译器需 <stdlib.h>,MSVC 支持最好)
3. 功能与行为 ★
| 行为 | 说明 |
|---|---|
| 读取范围 | 读入一整行 ,直到遇到回车 '\n' |
| 能读空格和制表符 | 这是它相对 scanf("%s") 的最大优势 |
| 换行处理 | 丢弃读到的 '\n',替换成 '\0' |
| 越界保护 | 输入超过 n-1 个字符时报错/清空,不会溢出 |
4. 基础案例:读入并显示完整姓名
c
#include <stdio.h>
int main(void)
{
char name[20];
printf("请输入你的姓名:");
gets_s(name, 20); // 最多读 19 个字符 + 1 个 '\0'
printf("你好,%s!\n", name);
return 0;
}
运行示例:
请输入你的姓名:Li Lei↵
你好,Li Lei!
对比 :若换成 scanf("%s", name);,输入 Li Lei 只会得到 Li," Lei" 被丢在缓冲区------这正是 gets_s 的价值。
5. 案例:读入一句话并输出
c
#include <stdio.h>
int main(void)
{
char sentence[100];
printf("请输入一句话:");
gets_s(sentence, 100);
puts("你输入的是:");
puts(sentence);
return 0;
}
运行示例:
请输入一句话:I love C language↵
你输入的是:
I love C language
6. gets_s 与"兄弟函数"对比表 ★★★
| 函数 | 能否读空格 | 遇回车 | 是否安全 | 是否保留 \n |
备注 |
|---|---|---|---|---|---|
gets() |
✅ 能 | 丢弃 | ❌ 极不安全,C11 删除 | 否 | 不要用! |
gets_s(str, n) |
✅ 能 | 丢弃 | ✅ 安全 | 否 | 本章推荐 |
fgets(str, n, stdin) |
✅ 能 | 保留 | ✅ 安全 | 是 | 标准 C,跨平台最稳 |
scanf("%s", str) |
❌ 遇空格断开 | 留在缓冲区 | ⚠️ 有隐患 | --- | 快速读单词 |
额外提醒 :如果编译器不支持 gets_s(如老版 GCC),请用 fgets 代替,用法:
c
fgets(name, 20, stdin); // 会把末尾的 '\n' 也读进来,需手动去掉
7. 为什么要学 gets_s?
- 安全意识:知道"不检查边界的输入函数"是安全隐患,这是程序员的基本素养。
- 能读带空格的整行文本 ,而
scanf("%s")做不到。 gets/gets_s/fgets/scanf("%s")的区别是面试必考对比题。
4.3 技术解惑
4.3.1 scanf 函数和 gets_s 函数的区别 ★★★
这是本章最核心的对比,很多初学者分不清。请从 5 个维度理解:
1. 对比表
| 对比维度 | scanf("%s", str) |
gets_s(str, n) |
|---|---|---|
| 读取范围 | 只读到第一个空白字符为止 | 读一整行(直到回车) |
| 能否读空格 | ❌ 不能,空格是"结束符" | ✅ 能,空格只是普通字符 |
| 能否读制表符/回车 | 都会终止读取 | 制表符能读,回车终止 |
| 结尾处理 | 自动加 '\0' |
丢弃 '\n',自动加 '\0' |
| 长度限制 | 可用 %19s 限制,否则不安全 |
必须给长度参数 n,安全 |
| 格式化能力 | 强(可 %d%s%f 混用) |
弱(只读字符串) |
| 典型用途 | 读单词、读"姓名 成绩"这类结构 | 读整句、地址、自我介绍 |
2. 最直观的实验对比
c
#include <stdio.h>
int main(void)
{
char a[50], b[50];
printf("用 scanf 输入:");
scanf("%s", a); // 输入:Hello C World
while (getchar() != '\n') ; // 清掉缓冲区残留
printf("用 gets_s 输入:");
gets_s(b, 50); // 输入:Hello C World
printf("scanf 结果:[%s]\n", a); // [Hello]
printf("gets_s 结果:[%s]\n", b); // [Hello C World]
return 0;
}
运行示例:
用 scanf 输入:Hello C World↵
用 gets_s 输入:Hello C World↵
scanf 结果:[Hello]
gets_s 结果:[Hello C World]
通俗理解:
scanf("%s")像一个只认"单词"的收银员,看到空格就认为这条结束了,剩下的一半留给下一位顾客。gets_s像一个认真抄写整行便签的人,空格、标点都照抄,直到你按下回车才停笔。
3. 什么时候用哪个?(实用口诀)
| 你想输入什么 | 用什么 |
|---|---|
| 一个单词(姓名、密码、英文名) | scanf("%s", ...) |
| 一整句话、地址、留言 | gets_s(...) |
| 数字和字符串混着输(如"张三 90") | scanf("%s %d", ...) |
| 需要跨平台、编译器不支持 gets_s | fgets(...) |
4. 混合使用的经典 Bug 及解法
Bug 场景 :先 scanf("%d") 读年龄,再 gets_s 读姓名。
c
int age;
char name[20];
scanf("%d", &age); // 你输入:20 ↵
gets_s(name, 20); // ⚠️ 直接跳过!name 里是空串
原因 :scanf("%d") 只吃掉了 20,那个回车 '\n' 还留在缓冲区 ;gets_s 一看立刻有"一行结束了"的信号,于是读到一个空行。
三种修法:
c
// 方法①:加一个空格在格式串中(只对后续 scanf 有效)
scanf("%d ", &age);
// 方法②:吃掉一个字符(简单场景)
scanf("%d", &age);
getchar();
// 方法③:清空整行(最稳妥,强烈推荐)★
scanf("%d", &age);
while (getchar() != '\n') ;
gets_s(name, 20);
5. 为什么这个知识点重要?
- 这是**"输入函数选型"的核心判断依据**,写真实项目时天天用。
- 缓冲区残留是初学 C 语言最让人抓狂的现象,理解原理后一通百通。
- 面试中"scanf 和 gets 的区别"几乎是必考题。
4.3.2 C 语言的输入和输出问题(常见疑难汇总)
疑难1:为什么 C 语言"没有"专门的输入输出语句?
答 :C 语言的核心只提供运算、控制、函数调用 等机制,输入输出全部由标准库函数 (scanf、printf 等)实现。
通俗理解 :C 语言本体只提供"发动机和轮子",收音机、空调(I/O 功能)都是选装配件 (库函数)。所以必须写
#include <stdio.h>来"取配件"。
实际意义:这也让 C 语言的 I/O 可以被替换(如换文件、换网络),非常灵活。
疑难2:为什么必须要写 #include <stdio.h>?
答 :stdio.h 是 standard input output header (标准输入输出头文件),里面是 printf、scanf 等函数的说明书(声明) 。不包含它,编译器不知道 printf 怎么用,会报"未声明的标识符"。
疑难3:输入输出是"函数",效率会不会低?
答 :scanf/printf 确实比纯计算慢(涉及系统调用和格式解析),但对普通程序完全够用。竞赛/高频数据场景 会用更快的 getchar 手写读入、putchar/puts 输出。
效率排序(快 → 慢):
getchar 手写解析 > gets_s / puts > scanf("%s") > printf 格式化
疑难4:为什么输出的浮点数总是"3.140000"?
答 :%f 默认保留 6 位小数 。用 %.2f 控制精度即可。
c
printf("%.2f", 3.14159); // 3.14
疑难5:为什么 printf 的参数求值顺序不确定?
答 :C 标准没有规定 函数实参的求值顺序(可能从左到右,也可能从右到左),由编译器决定。所以 printf("%d %d", i++, i++) 的结果是未定义的,不同编译器结果不同。
通俗理解:标准只是说"这些材料都要下锅",但没规定"先拿哪一样"。所以你不能指望某种特定顺序。
正确做法 :一次只做一件事。
c
printf("%d ", i);
i++;
printf("%d\n", i); // ✅ 清晰、无歧义
疑难6:为什么 scanf 读字符串不要加 &?
答 :数组名本身就已经是"地址",str 等价于 &str[0]。再加 & 就变成了"地址的地址",类型不匹配,会报警告甚至崩溃。
c
char name[20];
scanf("%s", name); // ✅
scanf("%s", &name); // ❌ 类型是 char(*)[20],不匹配
疑难7:输入被"跳过"了怎么办?(万能排查清单)
- 上一步是不是用了
scanf("%d/%f")?→ 用while(getchar()!='\n');清缓冲区 - 是不是用了
scanf("%c")?→ 格式串前加空格" %c" - 是不是格式串里的普通字符没按格式敲?→ 检查
-、,、a= - 是不是输入类型不对(该输数字却输了字母)?→ 用
scanf返回值检查
疑难8:Windows 下的 getchar() 为什么"读不到"东西?
答:有些 IDE(如老版 VS)在程序结束时窗口一闪而过,其实数据读到了,只是你看不到。解决方法:
c
printf("按回车退出...");
getchar(); // 用一次 getchar "卡住"窗口
return 0;
或使用 system("pause");(Windows 专用)。
为什么要看"技术解惑"?
- 这些都是真实学习中一定会遇到、但课本正文很少讲清的问题。
- 每个疑难背后都藏着一个核心机制(缓冲区、类型匹配、标准规定),理解机制比记答案更有用。
- 面试官爱从这些"看起来不起眼"的细节切入,考察你是否真的懂 C。
4.4 常见面试题解析
【面试题1】= 和 == 有什么区别?下面代码输出什么?
c
int a = 1, b = 2;
if (a = b) printf("Yes");
else printf("No");
答:
=是赋值运算符 ,==是关系运算符(判断相等)。a = b把b(2)赋给a,整个赋值表达式的值为 2(真)。- 输出:
Yes,同时a变成 2。
加分点 :提示用 if (2 == a) 这种"常量在左"的写法来避免此类错误。
【面试题2】下面的 printf 输出什么?
c
int i = 3;
printf("%d %d %d\n", i++, i++, i++);
答 :结果是未定义的(未定义行为) ,不同编译器可能输出 5 4 3、3 4 5 或其他值。
原因 :C 标准没有规定函数实参的求值顺序。不要这样写,应该分开写:
c
printf("%d %d %d\n", i, i, i); i += 3;
【面试题3】putchar('A' + 1) 会输出什么?
答 :输出 B。
解释 :字符 'A' 的 ASCII 码是 65,'A' + 1 = 66,66 对应的字符是 'B'。putchar 参数可以是字符常量,也可以是 ASCII 整数值。
【面试题4】getchar() 的返回类型为什么是 int 而不是 char?
答 :因为要区分"读到的字符 "和"出错/文件结束(EOF) "。EOF 通常是 -1,而 char 只有 8 位(-128~127 或 0~255),可能与合法字符值冲突。用 int 能确保 EOF 唯一可辨识。
延伸:因此标准读入写法是
c
int ch;
while ((ch = getchar()) != EOF) { ... }
【面试题5】写出下面语句的输出
c
printf("[%5.2f]\n", 3.14159);
printf("[%-5.2f]\n", 3.14159);
printf("[%05d]\n", 42);
答:
[ 3.14]
[3.14 ]
[00042]
解释 :5.2 表示总宽度 5、保留 2 位小数 ;- 表示左对齐 (不足补空格在右侧);05 表示宽度 5、不足补 0。
【面试题6】scanf、gets、gets_s、fgets、scanf("%s") 的区别?
答题要点(对比维度从 4 个方面展开):
| 维度 | 要点 |
|---|---|
| 读取范围 | scanf("%s") 遇空白停止;其余读整行到回车 |
| 空格处理 | scanf("%s") 不读空格;gets/gets_s/fgets 读空格 |
| 安全性 | gets 无长度检查,C11 已删除 ;gets_s/fgets 有长度限制 |
| 换行符 | gets/gets_s 丢弃 \n;fgets 保留 \n |
标准答案推荐 :用 fgets 或 gets_s 代替 gets;用 scanf("%s") 只在确定输入无空格时使用。
【面试题7】puts 和 printf 的区别?
答:
puts只能输出一个字符串 ,printf可以格式化输出多种类型。puts自动加换行 ,printf需要手动\n。puts遇'\0'停止输出,printf("%s", ...)同理。puts更简洁,printf更灵活。
【面试题8】下面的程序能读入完整姓名吗?为什么?如何修改?
c
char name[20];
scanf("%s", name);
答 :不能 。scanf("%s") 遇空格就停止,输入 Li Lei 只会读入 Li,剩下的 Lei 留在缓冲区,还会污染下一次输入。
修改:
c
gets_s(name, 20); // 方法1
// 或
scanf("%19[^\n]", name); // 方法2:读到回车为止
// 或
fgets(name, 20, stdin); // 方法3(会多读一个 '\n')
【面试题9】编程题:用 getchar 统计输入中的字母、数字、其他字符个数
c
#include <stdio.h>
#include <ctype.h>
int main(void)
{
int ch, letter = 0, digit = 0, other = 0;
printf("请输入一行文字:");
while ((ch = getchar()) != '\n')
{
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
letter++;
else if (ch >= '0' && ch <= '9')
digit++;
else
other++;
}
printf("字母:%d 数字:%d 其他:%d\n", letter, digit, other);
return 0;
}
考察点 :getchar 循环读入、字符的 ASCII 范围判断、复合语句的使用。
【面试题10】编程题:交换两个变量的值(三种方法)
c
// 方法1:中间变量法(最易懂,推荐)
int t = a; a = b; b = t;
// 方法2:加减法(无额外变量,但可能溢出)
a = a + b; b = a - b; a = a - b;
// 方法3:异或法(无额外变量,不会溢出)
a = a ^ b; b = a ^ b; a = a ^ b;
考察点 :赋值语句的执行顺序、表达式求值。面试建议:先说方法1,再展示方法3 更显功底。
4.5 课后练习
一、选择题
1. 以下哪个是正确的赋值语句?
A. a + b = c; B. a = b = 5; C. 5 = a; D. a = ;
2. printf("%5.2f", 3.14159); 的输出是( )
A. 3.14 B. 3.14 C. 3.14159 D. 03.14
3. scanf("%d,%d", &a, &b); 要求用户输入( )
A. 3 5 B. 3,5 C. 3;5 D. 35
4. 下列关于 gets_s 的说法正确的是( )
A. 会把输入的 \n 也保存到数组中
B. 可以读入包含空格的字符串
C. 不需要包含 stdio.h
D. 和 gets() 用法完全一样
5. getchar() 的返回值类型是( )
A. char B. int C. void D. double
参考答案
- B(A 左边不是变量;C 左边是常量;D 右边表达式为空)
- B(总宽 5、2 位小数,右对齐补 1 个空格)
- B (格式串中的
,必须原样输入) - B (gets_s 丢弃
\n,需stdio.h,比gets()多了长度参数) - B (为容纳
EOF,必须是int)
二、判断题(对的打 √,错的打 ×)
putchar()一次可以输出一整个字符串。( )puts("hi")输出完会自动换行。( )scanf("%d", &a);中的&可以省略。( )printf的返回值是输出内容的字符个数。( )gets()是 C11 标准推荐使用的安全函数。( )scanf("%f", &d);可以用来读入double变量d。( )- 赋值语句
a = 5;执行后,表达式a = 5的值是 5。( )
参考答案
- × (
putchar一次只输出一个字符) - √
- × (普通变量必须加
&,数组名除外) - √
- × (
gets因不安全已被 C11 删除) - × (
double必须用%lf) - √
三、填空题
- C 语句分为 5 类:控制语句、、表达式语句、、____。
printf("%c", 66);的输出是 ______。- 要用
scanf读入一个double类型变量x,应写scanf("____", &x);。 - 输入函数
gets_s的两个参数分别是 ______ 和 ______。 - 想清空输入缓冲区,可以使用语句
while (getchar() != '____') ;。
参考答案
- 函数调用语句;空语句;复合语句
B%lf- 字符数组首地址(缓冲区);最多读入的字符数
n \n
四、写出下列程序的运行结果
1.
c
#include <stdio.h>
int main(void)
{
int a = 3, b = 5, t;
t = a; a = b; b = t;
printf("%d %d\n", a, b);
printf("%d\n", a = b = 10);
return 0;
}
2.
c
#include <stdio.h>
int main(void)
{
int i = 3;
printf("%d\n", i = i + 1);
printf("%5d|%05d|%-5d|\n", i, i, i);
printf("%.2f%%\n", 3.14159);
return 0;
}
3. (假设输入为 12 34)
c
#include <stdio.h>
int main(void)
{
int a, b;
scanf("%2d%2d", &a, &b);
printf("%d %d\n", a, b);
return 0;
}
参考答案
1.
5 3
10
解释:交换后 a=5, b=3;a = b = 10 使 b=10, a=10,赋值表达式的值为 10。
2.
4
4|00004|4 |
3.14%
解释:i = i + 1 后 i=4;%5d 右对齐、%05d 补零、%-5d 左对齐;%% 输出百分号。
3.
12 34
解释:%2d 表示最多读 2 位数字 ,所以前两位 12 给 a,接着 34 给 b。
五、程序填空
1. 读入一个字符,若是小写字母则转成大写输出。
c
#include <stdio.h>
int main(void)
{
char ch;
ch = ______; // ① 读入一个字符
if (ch >= 'a' && ch <= 'z')
ch = ______; // ② 转大写(提示:减去一个数字)
______(ch); // ③ 输出
putchar('\n');
return 0;
}
2. 读入整行文字并原样输出。
c
#include <stdio.h>
int main(void)
{
char str[100];
printf("请输入:");
______(str, 100); // ④ 安全读入一整行
puts(______); // ⑤ 输出
return 0;
}
参考答案
① getchar() ② ch - 32 ③ putchar ④ gets_s ⑤ str
六、编程题
1. 编写程序:输入圆的半径 r(小数),计算并输出面积和周长(保留 2 位小数)。
提示:面积
πr²、周长2πr,取π = 3.14159。
2. 编写程序:输入两个整数,输出它们的和、差、积、商(商保留 2 位小数)。
3. 编写程序:用 getchar 逐个读入一行字符,倒序输出。
4. 编写程序:输入"姓名 语文 数学 英语"(如 张三 90 85 92),输出姓名和三科平均分(1 位小数)。
5. 编写程序:输入华氏温度 F,输出摄氏温度 C = 5(F-32)/9(保留 1 位小数)。
6. (提高)输入一串字符,用 putchar 输出由 * 组成的直角三角形(行数由输入决定)。
例如输入
3,输出:
* ** ***
参考答案(编程题)
1.
c
#include <stdio.h>
int main(void)
{
float r, area, perimeter;
printf("请输入半径:");
scanf("%f", &r);
area = 3.14159f * r * r;
perimeter = 2 * 3.14159f * r;
printf("面积=%.2f 周长=%.2f\n", area, perimeter);
return 0;
}
2.
c
#include <stdio.h>
int main(void)
{
int a, b;
printf("请输入两个整数:");
scanf("%d %d", &a, &b);
printf("和=%d 差=%d 积=%d\n", a + b, a - b, a * b);
if (b != 0)
printf("商=%.2f\n", (float)a / b); // 注意:转成 float 才是精确除法
else
printf("除数不能为 0!\n");
return 0;
}
3.
c
#include <stdio.h>
int main(void)
{
char s[100];
int i, len = 0;
printf("请输入一行:");
while (len < 99 && (s[len] = getchar()) != '\n')
len++;
printf("倒序输出:");
for (i = len - 1; i >= 0; i--)
putchar(s[i]);
putchar('\n');
return 0;
}
4.
c
#include <stdio.h>
int main(void)
{
char name[20];
int a, b, c;
printf("请输入姓名 语文 数学 英语:");
scanf("%s %d %d %d", name, &a, &b, &c); // %s 不加 &
printf("%s的平均分是 %.1f\n", name, (a + b + c) / 3.0);
return 0;
}
5.
c
#include <stdio.h>
int main(void)
{
float F, C;
printf("请输入华氏温度:");
scanf("%f", &F);
C = 5.0f * (F - 32) / 9;
printf("摄氏温度 = %.1f\n", C);
return 0;
}
6.
c
#include <stdio.h>
int main(void)
{
int n, i, j;
printf("请输入行数:");
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
for (j = 1; j <= i; j++)
putchar('*');
putchar('\n');
}
return 0;
}
附:本章核心速查卡
| 函数 | 头文件 | 作用 | 关键注意 |
|---|---|---|---|
putchar(c) |
stdio.h |
输出 1 个字符 | 参数可为字符或 ASCII 码 |
getchar() |
stdio.h |
输入 1 个字符 | 返回 int ,用于判断 EOF |
printf(fmt, ...) |
stdio.h |
格式化输出 | %d %c %s %f;% 要写成 %% |
scanf(fmt, ...) |
stdio.h |
格式化输入 | 变量加 & ;double 用 %lf |
puts(str) |
stdio.h |
输出字符串 | 自动换行 |
gets_s(str, n) |
stdio.h |
安全读入一行 | 能读空格;替代不安全的 gets() |
本章三条铁律:
=是赋值,==是比较,别混。scanf读普通变量要加&;格式串里的普通字符要原样输入。- 混合输入时,记得清缓冲区:
while (getchar() != '\n') ;