跳转至

导学案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;
}

输出:

Hello World
17

%d 叫占位符(也叫格式说明符)。它的作用很直白:先在字符串里占个坑,printf 会把逗号后面给的值填进这个坑里。

  • d = decimal,十进制整数;
  • 字符串里有几个 %,逗号后面就要给几个值,顺序一一对应;
  • 坑里可以放表达式,printf 会先算完再填。
printf("%d + %d = %d\n", 12, 5, 12 + 5);

输出: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%

两个高频坑:

  1. %f 默认打印 6 位小数,所以 3.14 会变成 3.140000。想控制位数就用 %.2f(两位)、%.1f(一位)、%.0f(只留整数位)。
  2. 想在屏幕上打出一个 % 字符,必须写两个 %%,因为单个 % 会被当成占位符的开头。

还有 %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;
}

输出:

姓名  年龄
张三  18
他说:"你好"
路径:D:\code\hello.c

\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;
}

输出:

12 + 5   = 17
12 - 5   = 7
12 * 5   = 60
12 / 5   = 2
12 % 5  = 2
12 + 5 * 6   = 42
(12 + 5) * 6 = 102

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 优先级:只记三条

  1. * / % 高于 + -;
  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;
}

输出:

age = 18
age = 19
next = 20

注意 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 → 再放回去),在数学里则很荒谬。

不初始化的变量是危险的:

int x;               /* 只声明,没放东西 */
printf("%d\n", x);   /* 可能是 0,也可能是 4194432 这种鬼数字 */

盒子里不是"空的",而是残留着上一次别人用过的垃圾值,而且不会报错。请养成铁一样的习惯:声明的时候就赋值,哪怕先赋个 0。

int a = 0;
int b = 0;
double price = 0.0;
char grade = ' ';

2.3.2 命名规则

  1. 只能用字母、数字、下划线,且不能以数字开头:score1 可以,my_score 可以,1score 不行,my-score 也不行(减号是运算符)。
  2. 不能用 C 的关键字:int、return、if、for、while 都不能当名字。
  3. 区分大小写:age、Age、AGE 是三个不同的变量。
  4. 名字要有意义: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;
}

输出:

char   占 1 字节
int    占 4 字节
double 占 8 字节

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 再回车:

请输入两个整数,用空格隔开:12 5
a = 12, b = 5
a + b = 17

2.4.1 & 是什么,为什么 scanf 要写、printf 不用写

&a 读作"a 的地址",就是 2.3 里说的那个门牌号。

  • printf 只要盒子里的值,所以写 a;
  • scanf 要把值放进盒子,必须知道盒子在哪,所以写 &a。

scanf 里填入的每个变量前面都要有 &。

2.4.2 scanf 使用要点

  1. 占位符必须和类型配对:整数 %d,小数 %lf,字符 %c,字符串 %s。注意 scanf 里 double 用 %lf,而 printf 里用 %f,这个不一致是 C 语言的历史包袱,只能记住。
  2. 一次可以读多个:scanf("%d %d", &a, &b);,输入时用空格分隔或分两次敲回车都可以。
  3. 引号里不要写提示文字:scanf("请输入%d", &a); 是常见的错误写法。提示语请用 printf 单独打印。
  4. %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):

请输入电池电压:11.8
请输入模式(a/b):b
电压 = 11.80 V,模式 = 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;
}

输出:

big     = 2147483647
big + 1 = -2147483648
x * x   = 1410065408

装不下了,就像汽车里程表跑到 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.30000000000000004441
0

原因是:计算机用二进制存小数,而 0.1 在二进制下是无限循环小数,只能近似存下来,误差累积后就露馅了(就像十进制写不出精确的 1/3)。

结论:不要用 == 直接比较两个小数,可以想想怎么做浮点数的等值比较。


2.6 中文乱码:一句话原因与三种应对

你在代码里写:

printf("你好,实验室\n");

运行后屏幕上可能出现 浣犲ソ锛屽疄楠屽,或者一堆 烫烫烫、?????。

一句话原因:你的源文件按一种编码(比如 UTF-8)保存成一串字节,而 Windows 控制台默认按另一种编码(GBK)去解读这些字节,两边对不上号就成了乱码。 换句话说:字没坏,是"翻译方式"错了(导学案0第 4 节讲过这件事)。

三种应对,按推荐顺序:

  1. Dev-C++:文件 → 另存为 → 下方"编码"选 ANSI → 保存 → 重新编译。
  2. Visual Studio:文件 → 高级保存选项 → 编码选 "Unicode (UTF-8 带签名)"。VS 默认存的是 UTF-8 无签名,这恰恰是乱码最常见的原因。
  3. 让控制台切换编码:在 cmd 里执行 chcp 65001(切到 UTF-8)或 chcp 936(切到 GBK),让窗口和你的文件对上。

遇到乱码,第一反应不是"我程序写错了",而是"编码没对上"。 编码是个大坑,但展开讲需要一整套背景知识,超出本轮范围。这里只给"现象 + 三种应对",够你自救即可。


2.7 综合练习

本节练习用于自练与自查,不强制完成、不需要提交;需要提交的是实验室统一布置的作业(作业一 / 作业二)。 参考代码统一放在本节末尾,做完之前不要翻。

以下内容比较简单,自行选做练习,不做提交要求

练习 1|个人信息卡片

输出下面这张卡片,年龄和身高用变量存:

==================
姓名:张三
年龄:18
身高:1.75 m
==================

提示:等号那一行可以用一个 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

更新说明:无