导学案2 · 输出、计算与输入¶
前置要求:已经搭起开发环境即可 参考课程:浙江大学 翁恺《C 语言程序设计》第 1~2 章(printf / 变量 / scanf / 数据类型) 预计用时:2 小时(含动手敲代码,按需自取) 本节目标:能输出/输入(printf/scanf),并搞懂基本运算
2.0 序¶
导学案1里的程序只会自己喊一句 Hello World,喊完就结束,你完全插不上嘴。从这一节开始,你要能让它算东西给你看,也能接收你敲进去的数字。这一对能力叫输入输出(I/O),几乎所有程序都是这三步:
当然,以后在单片机上,"输入"会变成传感器读数,"输出"会变成电机转速和串口打印等。
本节路线:printf 输出 → 运算符与计算 → 变量 → scanf 输入 → 类型与精度 → 中文乱码 → 练习
2.1 输出:printf¶
2.1.1 两种用法¶
#include <stdio.h>
int main(void)
{
printf("Hello World\n"); /* 用法一:原样打印一串文字 */
printf("%d\n", 12 + 5); /* 用法二:先算,再把结果填进去 */
return 0;
}
输出:
%d 叫占位符(也叫格式说明符)。它的作用很直白:先在字符串里占个坑,printf 会把逗号后面给的值填进这个坑里。
d= decimal,十进制整数;- 字符串里有几个
%,逗号后面就要给几个值,顺序一一对应; - 坑里可以放表达式,printf 会先算完再填。
输出:12 + 5 = 17
2.1.2 常用占位符¶
| 占位符 | 打印什么 | 例子 | 输出 |
|---|---|---|---|
%d |
整数 int |
printf("%d", 42); |
42 |
%f |
小数 double |
printf("%f", 3.14); |
3.140000 |
%c |
单个字符 char |
printf("%c", 'A'); |
A |
%.2f |
小数,保留 2 位 | printf("%.2f", 3.14159); |
3.14 |
%s |
字符串 | printf("%s", "Hi"); |
Hi |
%% |
打印一个百分号本身 | printf("100%%"); |
100% |
两个高频坑:
%f默认打印 6 位小数,所以3.14会变成3.140000。想控制位数就用%.2f(两位)、%.1f(一位)、%.0f(只留整数位)。- 想在屏幕上打出一个
%字符,必须写两个%%,因为单个%会被当成占位符的开头。
还有
%ld、%lld、%x(十六进制)这些,以后遇到再说,现在不用背。十六进制在单片机里到处都是,导学案0提过,往后你会经常见。
2.1.3 转义字符¶
引号里有些东西没法直接写(比如换行、比如双引号本身),要用 \ 开头表示:
| 写法 | 意思 |
|---|---|
\n |
换行 |
\t |
制表符(对齐用,相当于按一下 Tab) |
\" |
一个双引号 |
\\ |
一个反斜杠 |
#include <stdio.h>
int main(void)
{
printf("姓名\t年龄\n");
printf("张三\t18\n");
printf("他说:\"你好\"\n");
printf("路径:D:\\code\\hello.c\n");
return 0;
}
输出:
\t对齐的效果取决于内容长度,中文环境下经常对不齐。想让表格整齐,用%4d(占 4 位、右对齐)、%-8s(占 8 位、左对齐)更可控。现在知道有这回事就行,导学案4打印乘法表时会用上。
2.2 计算:运算符¶
2.2.1 体验过之前的输出,相信以下内容你都能融汇贯通¶
输入:
#include <stdio.h>
int main(void)
{
printf("12 + 5 = %d\n", 12 + 5);
printf("12 - 5 = %d\n", 12 - 5);
printf("12 * 5 = %d\n", 12 * 5);
printf("12 / 5 = %d\n", 12 / 5);
printf("12 %% 5 = %d\n", 12 % 5);
printf("12 + 5 * 6 = %d\n", 12 + 5 * 6);
printf("(12 + 5) * 6 = %d\n", (12 + 5) * 6);
return 0;
}
输出:
2.2.2 整数除法¶
12 / 5 得到 2,不是 2.4。规则只有一句:
两个整数相除,结果还是整数,向下取整。
想得到 2.4,就让参与运算的数里至少有一个是小数:
| 写法 | 结果 | 说明 |
|---|---|---|
12 / 5 |
2 |
整数除整数,砍掉小数 |
12 / 5.0 |
2.4 |
有小数参与,整体按小数算 |
12.0 / 5 |
2.4 |
同上 |
(double)12 / 5 |
2.4 |
(double) 是强制类型转换,2.5 节讲 |
不报错、不警告,程序照跑。
2.2.3 优先级:只记三条¶
*/%高于+-;- 同级从左往右算;
- 拿不准就加括号。括号不要钱,
(12 + 5) * 6谁都看得懂。
2.2.4 自增自减与复合赋值¶
| 写法 | 等价于 | 什么时候用 |
|---|---|---|
i++; |
i = i + 1; |
循环里走一步,导学案4天天用 |
i--; |
i = i - 1; |
倒着数 |
sum += n; |
sum = sum + n; |
累加求和 |
pwm *= 2; |
pwm = pwm * 2; |
按比例缩放 |
i++和++i单独成一行时效果完全一样,只在赋值时有区别,前者为先加再用,后者为先用再加
2.3 变量:给数据起个名字¶
上面我们每次都把数字写死:12、5。但真实程序里的数字是会变的——用户输入的、传感器读到的、电池电压、电机转速……这时候就需要变量。
变量就是一个有名字的盒子。 你可以往里放东西,可以换掉里面的东西,也可以随时取出来看。
#include <stdio.h>
int main(void)
{
int age = 18; /* 声明一个盒子:型号 int,名字 age,先放个 18 进去 */
printf("age = %d\n", age);
age = 19; /* 换掉里面的东西(注意:这里没有 int 了) */
printf("age = %d\n", age);
int next = age + 1; /* 取出 age 的值算一下,结果放进新盒子 next */
printf("next = %d\n", next);
return 0;
}
输出:
注意
printf("age = %d\n", age);里的age没有引号。如果逗号后面写成"age"(带引号),就是把一段文字当整数打印,结果会是乱的。
2.3.1 三个关键动作¶
| 术语 | 代码 | 说明 |
|---|---|---|
| 声明 declaration | int age; |
告诉编译器:我要一个 int 型号的盒子,名字叫 age。同一个名字在同一范围内只能声明一次 |
| 初始化 initialization | int age = 18; |
声明的同时放个初始值进去。强烈建议永远这样做 |
| 赋值 assignment | age = 19; |
把盒子里的东西换成 19 |
= 不是数学里的"等于",它的意思是"把右边的值,放进左边的盒子"。所以 age = age + 1; 在 C 里完全合法(取出 age → 加 1 → 再放回去),在数学里则很荒谬。
不初始化的变量是危险的:
盒子里不是"空的",而是残留着上一次别人用过的垃圾值,而且不会报错。请养成铁一样的习惯:声明的时候就赋值,哪怕先赋个 0。
2.3.2 命名规则¶
- 只能用字母、数字、下划线,且不能以数字开头:
score1可以,my_score可以,1score不行,my-score也不行(减号是运算符)。 - 不能用 C 的关键字:
int、return、if、for、while都不能当名字。 - 区分大小写:
age、Age、AGE是三个不同的变量。 - 名字要有意义:
totalScore远好于a。循环里临时用i、j是行业习惯,可以接受。
2.3.3 常用数据类型(先混个脸熟)¶
| 类型 | 中文 | 大概能装什么 | 打印用 | 字面量写法 |
|---|---|---|---|---|
int |
整型 | 约 −21 亿 ~ +21 亿 的整数(与操作系统有关) | %d |
18 |
double |
双精度小数 | 约 15 位有效数字 | %f |
3.14 |
char |
字符 | 一个字符 | %c |
'A'(单引号) |
- 字符用单引号
'A',字符串用双引号"A",两者在 C 里完全不是一回事。 - 写
3.14时,它默认是double。本课程小数一律用double,能避免很多问题。 - 还有
float、short、long、long long、unsigned,规则都一样,遇到再查,现在不用背。 - 不希望某个值被改掉,就在类型前加
const:const double PI = 3.14159265;。之后若再写PI = 3.14;,编译器会直接报错——这是好事,编译器是你的第一道防线,不是敌人。
2.3.4 盒子到底在哪:内存¶
内存可以想成一排编了号的格子,每个格子的编号叫地址(address)。当你写下 int age = 18;,编译器就在内存里划出一小片空间(int 通常占 4 个格子),贴上标签 age,把 18 放进去。
变量 = 内存里一小片有名字的空间;值住在里面,地址是它的门牌号。
2.3.5 sizeof:量一量类型占几个字节¶
#include <stdio.h>
int main(void)
{
printf("char 占 %d 字节\n", (int)sizeof(char));
printf("int 占 %d 字节\n", (int)sizeof(int));
printf("double 占 %d 字节\n", (int)sizeof(double));
return 0;
}
输出:
sizeof 是个运算符,问的是"这个东西占几个字节",答案在编译时就定好了。为什么现在就讲?因为导学案5算数组长度时要用到它。这里的 (int) 是强制类型转换。
2.4 输入:scanf¶
程序能输出还不够,得能接收你敲进去的数据。这就要用 scanf。
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
printf("请输入两个整数,用空格隔开:");
scanf("%d %d", &a, &b);
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", a + b);
return 0;
}
运行时你会先看到提示语,然后光标停在那里等你敲字——程序停住是正常的,它在等你。输入 12 5 再回车:
2.4.1 & 是什么,为什么 scanf 要写、printf 不用写¶
&a 读作"a 的地址",就是 2.3 里说的那个门牌号。
printf只要盒子里的值,所以写a;scanf要把值放进盒子,必须知道盒子在哪,所以写&a。
scanf 里填入的每个变量前面都要有 &。
2.4.2 scanf 使用要点¶
- 占位符必须和类型配对:整数
%d,小数%lf,字符%c,字符串%s。注意 scanf 里 double 用%lf,而 printf 里用%f,这个不一致是 C 语言的历史包袱,只能记住。 - 一次可以读多个:
scanf("%d %d", &a, &b);,输入时用空格分隔或分两次敲回车都可以。 - 引号里不要写提示文字:
scanf("请输入%d", &a);是常见的错误写法。提示语请用printf单独打印。 %s遇到空格就停:想读入Hello World,只会拿到Hello。
#include <stdio.h>
int main(void)
{
double voltage = 0.0;
char mode = ' ';
printf("请输入电池电压:");
scanf("%lf", &voltage);
printf("请输入模式(a/b):");
scanf(" %c", &mode); /* 注意 %c 前面的空格 */
printf("电压 = %.2f V,模式 = %c\n", voltage, mode);
return 0;
}
运行(依次输入 11.8 和 b):
2.5 类型与精度¶
2.5.1 整数溢出¶
int 的范围大约是 −21 亿 ~ +21 亿,超了就"绕回去":
#include <stdio.h>
int main(void)
{
int big = 2147483647; /* int 能装的最大值 */
int x = 100000;
printf("big = %d\n", big);
printf("big + 1 = %d\n", big + 1);
printf("x * x = %d\n", x * x);
return 0;
}
输出:
装不下了,就像汽车里程表跑到 999999 之后回到 000000。程序不会报错,只会给你一个错得离谱的数。 需要更大范围就用 long long(8 字节,约 ±922 亿亿),打印用 %lld。
溢出在单片机上同样致命:一个计时器变量悄悄溢出,你的"飞行时间"就会突然变成负数。养成习惯:写下一个数之前,先想想它最大能到多少。
2.5.2 一个有趣的现象(自行选做)¶
有兴趣的同学可以尝试一下如下代码的结果是什么,把127改成128呢?问题在哪里,思考为什么?
#include<stdio.h>
int main() {
char ch = 127;
printf("ch = %d\n", ch);
printf("ch + 1 = %d\n", ch + 1);
return 0;
}
2.5.3 小数是不精确的¶
#include <stdio.h>
int main(void)
{
printf("%.20f\n", 0.1 + 0.2);
printf("%d\n", 0.1 + 0.2 == 0.3);
return 0;
}
输出:
原因是:计算机用二进制存小数,而 0.1 在二进制下是无限循环小数,只能近似存下来,误差累积后就露馅了(就像十进制写不出精确的 1/3)。
结论:不要用 == 直接比较两个小数,可以想想怎么做浮点数的等值比较。
2.6 中文乱码:一句话原因与三种应对¶
你在代码里写:
运行后屏幕上可能出现 浣犲ソ锛屽疄楠屽,或者一堆 烫烫烫、?????。
一句话原因:你的源文件按一种编码(比如 UTF-8)保存成一串字节,而 Windows 控制台默认按另一种编码(GBK)去解读这些字节,两边对不上号就成了乱码。 换句话说:字没坏,是"翻译方式"错了(导学案0第 4 节讲过这件事)。
三种应对,按推荐顺序:
- Dev-C++:文件 → 另存为 → 下方"编码"选
ANSI→ 保存 → 重新编译。 - Visual Studio:文件 → 高级保存选项 → 编码选 "Unicode (UTF-8 带签名)"。VS 默认存的是 UTF-8 无签名,这恰恰是乱码最常见的原因。
- 让控制台切换编码:在 cmd 里执行
chcp 65001(切到 UTF-8)或chcp 936(切到 GBK),让窗口和你的文件对上。
遇到乱码,第一反应不是"我程序写错了",而是"编码没对上"。 编码是个大坑,但展开讲需要一整套背景知识,超出本轮范围。这里只给"现象 + 三种应对",够你自救即可。
2.7 综合练习¶
本节练习用于自练与自查,不强制完成、不需要提交;需要提交的是实验室统一布置的作业(作业一 / 作业二)。 参考代码统一放在本节末尾,做完之前不要翻。
以下内容比较简单,自行选做练习,不做提交要求
练习 1|个人信息卡片¶
输出下面这张卡片,年龄和身高用变量存:
提示:等号那一行可以用一个
printf打完;身高用%.2f。
练习 2|四则运算器¶
读入两个整数 a 和 b,输出它们的和、差、积、商、余数,每行一个。
提示:
printf里想打出%要写成%%。 额外任务(重要):请故意输入b = 0,看看程序会发生什么。除以零在 C 语言里是未定义行为,程序可能崩溃、可能输出一个荒唐的数、也可能什么都不发生。
练习 3|摄氏转华氏¶
读入一个摄氏温度(可以是小数),输出华氏温度,保留一位小数。公式:F = C × 9 / 5 + 32
提示:输入
25,正确输出是77.0。
练习 4|秒数拆分¶
读入一个总秒数(例如 3725),输出 1 小时 2 分 5 秒。
练习 5|交换两个变量¶
读入 a 和 b,交换它们的值后输出。
提示:一杯可乐和一杯果汁要互换,你需要第三个空杯子。 直接写
a = b; b = a;是不行的——想清楚为什么。
练习 6|圆的周长与面积¶
读入半径 r(double),输出周长和面积,都保留两位小数。π 用 const 存起来。
提示:
r * r是对的,r ^ 2是错的(见 2.2 坑 3)。
2.8 报错自查表¶
| 现象 | 最可能的原因 |
|---|---|
expected ';' before ... |
上一行漏了分号。报错行号常常指向下一行,往上找 |
unknown type name 'Int' |
类型名拼错或大小写错,是 int,不是 Int |
'printf' was not declared |
忘了 #include <stdio.h>(scanf 也在里面) |
| scanf 输入没反应 / 程序直接崩溃 | 忘了写 &,本节第一大坑 |
输出 0.000000 或一堆乱数字 |
占位符与类型不匹配,%f 配了 int,或 %d 配了 double |
scanf 读 double 结果不对 |
scanf 里 double 要用 %lf,不是 %f |
%c 读到了一个看不见的东西 |
忘了在 %c 前加空格,缓冲区里残留着上次的换行 |
12 / 5 得到 2 |
整数除法,见 2.2 |
| 结果比预期小很多,甚至变成负数 | 整数溢出,见 2.5 |
0.1 + 0.2 不等于 0.3 |
浮点精度,见 2.5,这是正常的 |
中文变成 浣犲ソ / 烫烫烫 |
编码不一致,见 2.6 |
VS 报 C4996 说 scanf 不安全 |
项目属性里加 _CRT_SECURE_NO_WARNINGS,见导学案1 1.2 |
r ^ 2 结果完全不对 |
^ 不是乘方,写成 r * r |
| 编译通过了,但运行结果没变 | 忘了重新编译,或者你改的文件不是正在编译的那个文件 |
2.9 本节小结与自查¶
学完本节,你应该能做到:
- 用
printf输出文字、整数、小数,并控制小数位数 - 说清
%d、%f、%.2f、%c、%s、%lf分别配什么类型、用在哪个函数里 - 声明、初始化、修改一个变量,并解释
=为什么不是数学里的"等于" - 用
scanf读入整数、小数、字符,知道&绝对不能少 - 解释
12 / 5 == 2的原因,并写出三种得到2.4的方法 - 知道 int 会溢出、小数有误差,而且两者都不报错
- 用
sizeof说出char/int/double各占几个字节 - 遇到中文乱码知道往哪个方向查
下一节:导学案3 · 判断。程序终于能"看情况办事"了,顺便回来收拾练习 2 留下的那个除以零烂摊子。
2.10 练习参考代码(做完再看)¶
练习 2 参考代码¶
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
printf("请输入两个整数,用空格隔开:");
scanf("%d %d", &a, &b);
printf("%d + %d = %d\n", a, b, a + b);
printf("%d - %d = %d\n", a, b, a - b);
printf("%d * %d = %d\n", a, b, a * b);
printf("%d / %d = %d\n", a, b, a / b);
printf("%d %% %d = %d\n", a, b, a % b);
return 0;
}
练习 3 参考代码¶
#include <stdio.h>
int main(void)
{
double c = 0.0;
double f = 0.0;
printf("请输入摄氏温度:");
scanf("%lf", &c);
f = c * 9.0 / 5.0 + 32.0;
printf("%.1f 摄氏度 = %.1f 华氏度\n", c, f);
return 0;
}
练习 4 参考代码¶
#include <stdio.h>
int main(void)
{
int total = 0;
int h = 0;
int m = 0;
int s = 0;
printf("请输入总秒数:");
scanf("%d", &total);
h = total / 3600;
m = total % 3600 / 60;
s = total % 60;
printf("%d 小时 %d 分 %d 秒\n", h, m, s);
return 0;
}
练习 5 参考代码¶
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int t = 0; /* 第三个空杯子 */
printf("请输入两个整数:");
scanf("%d %d", &a, &b);
t = a;
a = b;
b = t;
printf("交换后:a = %d, b = %d\n", a, b);
return 0;
}
练习 6 参考代码¶
#include <stdio.h>
int main(void)
{
const double PI = 3.14159265;
double r = 0.0;
printf("请输入半径:");
scanf("%lf", &r);
printf("周长 = %.2f\n", 2 * PI * r);
printf("面积 = %.2f\n", PI * r * r);
return 0;
}
节末终了。这一节的东西看起来琐碎,但它是你以后每一行代码的地基:变量、类型、输入输出。
作者:Mike-lin300
最后更新:2026-09-23
更新说明:无