编译并运行
gcc -std=c11 -Wall -Wextra lesson03.c -o lesson03.exe
./lesson03.exeC foundations / Lesson 03
采样值怎样求平均?为什么 25 除以 4 可能得到 6?本课从可运行的程序开始,逐步认识算术表达式、计算顺序、类型转换和边界。
一个处理采样总和的 lesson03.c;一张记录商、余数、浮点平均值和括号变化结果的小表。
第 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。
int,C 会怎样计算?建立 lesson03.c,先预测三行输出,再编译。这里的 25 是四次采样值的总和,示例只演示数值运算。
#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;
}gcc -std=c11 -Wall -Wextra lesson03.c -o lesson03.exe
./lesson03.exegcc -std=c11 -Wall -Wextra lesson03.c -o lesson03
./lesson03预期输出:
whole=6 remainder=1
average=6.25
normal=33 grouped=108修改并保存源码后要重新编译,再运行新生成的程序。运行旧的可执行文件不会自动反映源码变化。
total = 25 与 count = 4int 类型;值分别表示总和和样本数。whole = total / countremainder = total % count6 × 4 + 1。average = (double)total / countnormal 与 groupedprintf%d 对应 int;%.2f 对应 double,显示两位小数。格式必须与传入的实际类型匹配。| 符号 | 作用 | 例子 | 结果 |
|---|---|---|---|
+ | 加 | 7 + 3 | 10 |
- | 减;放在前面可表示取负 | 7 - 3 | 4 |
* | 乘 | 7 * 3 | 21 |
/ | 除,结果受操作数类型影响 | 7 / 3 | 2(整数除法) |
% | 整数余数 | 7 % 3 | 1 |
% 的两个操作数必须是整数类型。要获得带小数的结果,使用浮点操作数,例如 7.0 / 3;实际浮点值一般是近似值。
C11 中,整数相除得到整数,商的小数部分向零截去:7 / 3 为 2,-7 / 3 为 -2。对于除数不为零且商可表示的情况,(a / b) * b + a % b == a 成立,所以 -7 % 3 为 -1。余数并不总是非负。
| 表达式 | 结果 | 理由 |
|---|---|---|
25 / 4 | 6 | 两个操作数为整数,向零截断 |
25 % 4 | 1 | 25 = 6 × 4 + 1 |
25.0 / 4 | 6.25 | 浮点运算 |
-7 / 3 与 -7 % 3 | -2 与 -1 | 商向零截断,余数满足上述关系 |
表达式中混用整数与浮点数时,会按 C 的类型转换规则确定计算类型。这里 (double)total 先得到一个 double 值,除法因此按浮点类型进行。比较这两行:
double a = (double)total / count; /* 6.25 */
double b = (double)(total / count); /* 6.00 */第二行先算括号内的整数除法,得到 6;随后转成 6.0,已经截掉的小数不会回来。转换表达式不会改变 total 本身的类型和值。
反方向把浮点值转换成整数时,小数部分会向零截去;目标整数类型还必须能够表示转换后的值。整型宽度因实现而异,需要看目标平台资料,不要假定所有计算都不会超出范围。
乘法、除法和余数运算的优先级高于加减;同一层级的这些运算符按左结合方式组织。例如 12 / 3 * 2 按 (12 / 3) * 2 解释,得到 8。需要改变分组时明确加括号。
优先级和结合性决定表达式怎样组合,并不普遍保证子表达式按从左到右的顺序执行。避免在同一表达式中对同一个变量做多个相互依赖的修改;把复杂计算拆成几条清晰的语句更容易检查。
10 - 2 * 3 与 (10 - 2) * 3 分别是多少?分别是 4 和 24。第一式先计算乘法;第二式先处理括号。
有符号整数计算超出其类型可表示的范围会产生未定义行为,不能依赖它“回绕”。<limits.h> 中的 INT_MIN 和 INT_MAX 可用于查看当前实现的 int 范围。无符号整数运算则遵循该类型规定的模运算,但这仍可能导致不符合需求的结果。
检查分母、范围和中间结果。例如求平均值前先确认样本数不为零;大量数据求和时,不只检查最终结果,也要考虑加法过程是否会超出类型范围。程序有警告时先理解并修复;没有警告也不证明所有输入都安全。
ADC 采样数据做单位换算、均值或时间计算时,同样可能遇到整数截断和中间结果溢出。先写出单位和取值范围,再选择合适的运算类型;不要以本机结果代替目标平台验证。
把 total 改成 29,保持 count 为 4。写出 whole、remainder、average 后再运行。
分别为 7、1 和 7.25;29 = 7 × 4 + 1。
在 total 为 25、count 为 4 时,比较 (double)total / count 和 (double)(total / count)。
前者是 6.25,后者是 6.0;后者在转换前已经执行整数除法。
计算 18 / 3 + 2 * 4;如果除数来自变量,运算前要先确认什么?
结果为 14。要确认除数不为零,且有符号除法结果可表示;其他运算的中间结果也要处于类型范围内。
25 / 4、25 % 4 和浮点平均值的区别。