C foundations / Lesson 03

让变量参与计算,
并知道结果从何而来。

采样值怎样求平均?为什么 25 除以 4 可能得到 6?本课从可运行的程序开始,逐步认识算术表达式、计算顺序、类型转换和边界。

03 / C 语言基础先完成变量与类型预测 → 编译 → 对照
01 / 目标

本课完成后,你能独立做到什么?

  • 区分变量、运算符、表达式和赋值语句。
  • 预测整数除法、余数、浮点除法和括号影响下的结果。
  • 解释为什么强制转换应发生在除法之前,并避免除以零和有符号溢出。
  • 运行示例、修改一处再编译,并解释结果。
本课产出

一个处理采样总和的 lesson03.c;一张记录商、余数、浮点平均值和括号变化结果的小表。

02 / 概念

从变量到表达式

第 02 课用 int sample_count = 3; 保存一个整数。sample_count + 1 把变量、常量和 + 运算符组合在一起,是一个表达式;它求值得到一个结果。把该结果放进变量时,才需要赋值:

int sample_count = 3;
int next_count = sample_count + 1;

第一行创建并初始化变量;第二行先求右侧表达式的值,再用结果初始化 next_count。单独计算 sample_count + 1 不会自动改变 sample_count。

先预测:如果总和是 25、次数是 4,平均值在数学上是多少?如果两个数都声明为 int,C 会怎样计算?
03 / 实验

写出一个采样计算程序

建立 lesson03.c,先预测三行输出,再编译。这里的 25 是四次采样值的总和,示例只演示数值运算。

lesson03.c完整示例 · C11
#include <stdio.h>

int main(void)
{
    int total = 25;
    int count = 4;
    int whole = total / count;
    int remainder = total % count;
    double average = (double)total / count;
    int normal = total + 2 * count;
    int grouped = (total + 2) * count;

    printf("whole=%d remainder=%d\n", whole, remainder);
    printf("average=%.2f\n", average);
    printf("normal=%d grouped=%d\n", normal, grouped);
    return 0;
}
Windows · MSYS2 UCRT64

编译并运行

gcc -std=c11 -Wall -Wextra lesson03.c -o lesson03.exe
./lesson03.exe
Ubuntu

编译并运行

gcc -std=c11 -Wall -Wextra lesson03.c -o lesson03
./lesson03

预期输出:

whole=6 remainder=1
average=6.25
normal=33 grouped=108

修改并保存源码后要重新编译,再运行新生成的程序。运行旧的可执行文件不会自动反映源码变化。

04 / 拆解

逐行追踪数值

total = 25 与 count = 4
两个变量都具有 int 类型;值分别表示总和和样本数。
whole = total / count
两个操作数都是整数,所以先进行整数除法;25 除以 4 的商向零截断得到 6。
remainder = total % count
整数余数为 1;25 可以写成 6 × 4 + 1。
average = (double)total / count
先将 total 转为浮点类型,另一个操作数参与转换,随后做浮点除法,得到 6.25。
normal 与 grouped
前者先做乘法:25 + (2 × 4) = 33;后者括号先组合加法:(25 + 2) × 4 = 108。
三条 printf
%d 对应 int;%.2f 对应 double,显示两位小数。格式必须与传入的实际类型匹配。
05 / 运算符

五个最常用的算术运算

符号作用例子结果
+加7 + 310
-减;放在前面可表示取负7 - 34
*乘7 * 321
/除,结果受操作数类型影响7 / 32(整数除法)
%整数余数7 % 31

% 的两个操作数必须是整数类型。要获得带小数的结果,使用浮点操作数,例如 7.0 / 3;实际浮点值一般是近似值。

06 / 除法

整数除法与余数要一起理解

C11 中,整数相除得到整数,商的小数部分向零截去:7 / 3 为 2,-7 / 3 为 -2。对于除数不为零且商可表示的情况,(a / b) * b + a % b == a 成立,所以 -7 % 3 为 -1。余数并不总是非负。

表达式结果理由
25 / 46两个操作数为整数,向零截断
25 % 4125 = 6 × 4 + 1
25.0 / 46.25浮点运算
-7 / 3 与 -7 % 3-2 与 -1商向零截断,余数满足上述关系
关键条件:整数除数为零会产生未定义行为;有符号整数的最小值除以 -1 若商不能表示,除法和对应的余数运算也不能安全执行。不要运行这些错误示例。
07 / 类型

转换发生在计算前,还是计算后?

表达式中混用整数与浮点数时,会按 C 的类型转换规则确定计算类型。这里 (double)total 先得到一个 double 值,除法因此按浮点类型进行。比较这两行:

double a = (double)total / count;  /* 6.25 */
double b = (double)(total / count); /* 6.00 */

第二行先算括号内的整数除法,得到 6;随后转成 6.0,已经截掉的小数不会回来。转换表达式不会改变 total 本身的类型和值。

反方向把浮点值转换成整数时,小数部分会向零截去;目标整数类型还必须能够表示转换后的值。整型宽度因实现而异,需要看目标平台资料,不要假定所有计算都不会超出范围。

08 / 组合

括号让运算关系一眼可见

乘法、除法和余数运算的优先级高于加减;同一层级的这些运算符按左结合方式组织。例如 12 / 3 * 2 按 (12 / 3) * 2 解释,得到 8。需要改变分组时明确加括号。

优先级和结合性决定表达式怎样组合,并不普遍保证子表达式按从左到右的顺序执行。避免在同一表达式中对同一个变量做多个相互依赖的修改;把复杂计算拆成几条清晰的语句更容易检查。

自检:10 - 2 * 3 与 (10 - 2) * 3 分别是多少?

分别是 4 和 24。第一式先计算乘法;第二式先处理括号。

09 / 边界

计算前先确认能否表示

有符号整数计算超出其类型可表示的范围会产生未定义行为,不能依赖它“回绕”。<limits.h> 中的 INT_MIN 和 INT_MAX 可用于查看当前实现的 int 范围。无符号整数运算则遵循该类型规定的模运算,但这仍可能导致不符合需求的结果。

检查分母、范围和中间结果。例如求平均值前先确认样本数不为零;大量数据求和时,不只检查最终结果,也要考虑加法过程是否会超出类型范围。程序有警告时先理解并修复;没有警告也不证明所有输入都安全。

与 STM32 的联系

ADC 采样数据做单位换算、均值或时间计算时,同样可能遇到整数截断和中间结果溢出。先写出单位和取值范围,再选择合适的运算类型;不要以本机结果代替目标平台验证。

10 / 练习

先预测,再只改一处

01 / 整数除法

修改总和

把 total 改成 29,保持 count 为 4。写出 whole、remainder、average 后再运行。

查看参考答案

分别为 7、1 和 7.25;29 = 7 × 4 + 1。

02 / 转换

比较转换时机

在 total 为 25、count 为 4 时,比较 (double)total / count 和 (double)(total / count)。

查看参考答案

前者是 6.25,后者是 6.0;后者在转换前已经执行整数除法。

03 / 括号与边界

判断表达式

计算 18 / 3 + 2 * 4;如果除数来自变量,运算前要先确认什么?

查看参考答案

结果为 14。要确认除数不为零,且有符号除法结果可表示;其他运算的中间结果也要处于类型范围内。

11 / 自检

准备进入下一课

  1. 不运行也能解释 25 / 4、25 % 4 和浮点平均值的区别。
  2. 能说明括号改变分组,优先级并不等同于普遍的求值顺序。
  3. 能指出零除数和有符号溢出的风险,并完成三道练习。
资料

对照官方资料