程序设计基础 · 第6次课 · 第2课时
从慢病随访均值计算看除法机制与运算符优先级,理解类型的自动提升与求值时序
385 / 3 算出来的血压均值为什么变成了 128.00 而不是 128.33?
在社区慢病随访数据处理中,患者 3 次收缩压总和为 385 mmHg。直接使用整数除法时,小数部分会被舍弃,使得均值计算不够准确。
理解算术运算符的类型推导机制,掌握显式强制转换与隐式自动提升的规则与次序,防范赋值截断。
操作数类型判定、算术类型自动提升阶梯、(double) 括号结合性优先级,以及前置/后置自增自减的时序细节。
++i / i++)的时序区别(double)(a / b) 这样的无效转换scanf / printf 占位符与变量类型不匹配的问题int totalSys = 385; /* 3 次随访收缩压总和 (mmHg) */
int count = 3; /* 随访测量次数 */
double avgSys = totalSys / count; /* 期望得到 128.33 */
printf("平均收缩压: %.2f mmHg\n", avgSys);
平均收缩压: 128.00 mmHg
计算结果显示为 128.00 mmHg。为什么将结果赋给 double 类型的 avgSys,依然没有保留小数?
totalSys / count 是两个整型变量相除。C 语言在执行除法运算时,会先进行整数除法,向零截断得到整数 128,然后再赋值给 avgSys,因此最终为 128.0。
sum / count(未转换)与 (double)sum / count(显式转换)的实时计算对比。特别注意:当 count = 0 时需提前判断,避免除以 0。
| 表达式 | 操作数类型 | 运算行为 | 最终计算结果 | 原理说明 |
|---|---|---|---|---|
5 / 2 |
int ÷ int |
整数除法 | 2 |
向零截断:小数部分直接被舍弃 |
-5 / 2 |
int ÷ int |
整数除法 | -2 |
向零靠拢(非向下取整,不是 -3) |
5.0 / 2 |
double ÷ int |
浮点除法 | 2.5 |
2 自动提升为 2.0,执行浮点除法 |
(double)5 / 2 |
double ÷ int |
浮点除法 | 2.5 |
强制类型转换运算符 (double) 优先于除法 |
/ 的两个操作数中,只要有至少一个是浮点型,整场运算即作为浮点除法进行。
(double)(totalSys / count)
totalSys / count128,原有小数已不复存在128.00(double)totalSys / count
(double) 优先级高于双目除法 /totalSys 的值提升为浮点数 385.0385.0 / 3 触发浮点除法,得到 128.333...double calcAverageSys(int totalSys, int count)
{
/* 检查除数有效性,避免除以 0 */
if (count <= 0) {
return -1.0;
}
return (double)totalSys / count; /* 正确触发浮点除法 */
}
int main(void)
{
double avg = calcAverageSys(385, 3);
printf("慢病居民平均收缩压: %.2f mmHg\n", avg); /* 输出 128.33 */
return 0;
}
char / shortintunsigned intlong longfloatdouble小于 int 范围的整型(如 char, short)参与算术运算时,通常先自动提升为 int 参与计算。
当两个不同类型的操作数参与双目运算时,低等级类型自动转换为高等级类型,然后执行同精度计算,结果类型为高等级类型。
double bmi = 24.89;
int intBMI = bmi; /* intBMI 变为 24 */
注意:向零截断并非四舍五入。即使是 24.99 赋给整型变量,小数部分也会被直接截去,变为 24。
int x = 257; /* 0x00000101 */
char c = x; /* 只保留低8位 0x01 */
c 的最终数值为 1。超过目标类型可表示范围时,高位字节会被忽略。
如 100 → 100.0,因有效数字位数充裕,可以精确表达。
int big = 16777217; /* 2^24 + 1 */
float f = big;
printf("%d\n", (int)f); /* 打印 16777216 */
float 有效数字约为 7 位(尾数 24 位),数值过大时超出有效位数,低位数字会被舍入。
double。
double result = 10 / 4 + 3.5f * 2 + 'A';💡 课堂互动:建议先手动推演每一步计算类型,再与 AI 拆解结果对照,加深对两阶段模型的理解。
int sbp = 120;
scanf("%d", sbp); /* 错误写法:传入了数值 120 */
这里传入的是变量当前的值 120,scanf 会将 120 视作目标内存地址尝试写入,通常会引发段错误(Segmentation Fault)导致程序异常终止。
int sbp;
scanf("%d", &sbp); /* 正确传递地址 */
&sbp 表示变量在内存中的地址,scanf 通过该地址将键盘读入的数据写入到 sbp 对应的存储单元中。
| 格式控制符 | 匹配数据类型 | 进位制与显示特征 | printf 示例(设 n = 95) | scanf 输入与说明 |
|---|---|---|---|---|
%d / %i |
int |
有符号十进制整数 | printf("%d", n); → 95 |
标准十进制整型输入:scanf("%d", &n); |
%u |
unsigned int |
无符号十进制整数 | printf("%u", n); → 95 |
用于非负整数(如计数、大小、下标) |
%o / %#o |
int / unsigned |
八进制(# 自动输出前缀 0) |
printf("%#o", n); → 0137 |
按八进制读入,输入 0137 实际解析为 95 |
%x / %#x |
int / unsigned |
十六进制小写(# 加前缀 0x) |
printf("%#x", n); → 0x5f |
常用在位运算、颜色编码与内存数据查看 |
%X / %#X |
int / unsigned |
十六进制大写(# 加前缀 0X) |
printf("%#X", n); → 0X5F |
以大写字母(A~F)输出十六进制 |
%hd / %hu |
short / unsigned short |
短整型(2 字节) | printf("%hd", s); |
必须加 h:scanf("%hd", &s); |
%ld / %lu |
long / unsigned long |
长整型(4 或 8 字节) | printf("%ld", l); |
必须加 l:scanf("%ld", &l); |
%lld / %llu |
long long / unsigned ... |
超长整型(标准 8 字节,64 位) | printf("%lld", ll); |
必须加 ll:scanf("%lld", &ll); |
%#o 和 %#x 可以让编译器自动输出 0 和 0x 前缀,与 C 语言整数字面常量的写法完全一致,便于查验和对比。
| 格式控制符 | 匹配数据类型 | 含义与输出特征 | printf 行为与示例 | scanf 规则与要点 |
|---|---|---|---|---|
%f |
float (单精度, 4B) |
单精度浮点数 | 默认输出 6 位小数:3.140000 |
float 输入必须严格用 %f |
%lf |
double (双精度, 8B) |
双精度浮点数 | printf 中 %f 与 %lf 通用 |
double 输入必须严格用 %lf |
%e / %E |
float / double |
科学计数法指数形式 | printf("%e", 123.45); → 1.234500e+02 |
输入可识别 1.23e2 等科学计数法格式 |
%g / %G |
float / double |
自适应紧凑输出 | 自动选 %f 或 %e 简短者,去掉多余尾随0 |
常用于美化输出(如输出 3.5 而非 3.500000) |
%c |
char (1 字节) |
单个字符 | 输出对应字符:printf("%c", 'A'); |
不跳过空白符,读取前建议加空格:" %c" |
%s |
char[] / 字符串 |
以 '\0' 结尾的字符串 |
连续输出字符直到遇到 '\0' |
遇空格或换行终止,数组名即地址,不加 & |
%p |
指针 / 地址值 | 变量在内存中的十六进制地址 | printf("%p", (void*)&n); |
用于观察变量或指针在内存中的物理位置 |
%% |
无操作数 | 输出一个字符 % |
printf("完成率: 98%%\n"); |
因 % 为引导符,连续两个 %% 转义为一个 % |
double 类型,printf 输出时 %f 和 %lf 均可(C99起统一);但在 scanf 输入时,必须严格区分:float 必须对应 %f,double 必须对应 %lf!
| 函数与场景 | float (单精度 4 字节) |
double (双精度 8 字节) |
底层原理与混淆后果 |
|---|---|---|---|
printf 输出 |
%f |
%f 或 %lf(均可) |
在 C 语言可变参数中,float 参数会自动提升为 double 传递,因此 %f 完全通用 |
scanf 输入 |
必须用 %f |
必须用 %lf |
类型必须严格匹配:scanf 需根据说明符向指针写入 4 字节还是 8 字节,混用会导致数据损坏或越界 |
double bmi;
scanf("%f", &bmi); /* 错误写法 */
%f 会指示 scanf 仅写入 4 字节内存,而 bmi 是 8 字节变量,导致高位未写入或数值解析完全错误。
double bmi;
scanf("%lf", &bmi); /* 正确写法 */
使用 %lf 保证完整写入 8 字节 IEEE-754 双精度浮点格式,数值准确无误。
int age;
char gender;
scanf("%d", &age);
scanf("%c", &gender); /* 读入了前序回车换行符 '\n' */
输入年龄后敲击回车产生的 '\n' 残留在输入缓冲区中,紧随其后的 %c 不会等待输入,而是直接将其读入。
int age;
char gender;
scanf("%d", &age);
scanf(" %c", &gender); /* 空格跳过空白符 */
格式串中的空格会让 scanf 自动跳过缓冲区中残留的所有连续空白符(包括换行符、空格与制表符)。
| 修饰符语法 | 功能描述 | 代码示例 | 实际屏幕输出效果 |
|---|---|---|---|
%md(如 %5d) |
指定最小输出宽度为 m 列(默认右对齐,不足补空格) | printf("[%5d]", 42); |
[ 42](前补 3 个空格) |
%-md(如 %-5d) |
指定最小宽度并强制左对齐(不足右侧补空格) | printf("[%-5d]", 42); |
[42 ](后补 3 个空格) |
%0md(如 %04d) |
指定最小宽度,不足时在前面补数字 0 | printf("%04d-%02d", 2026, 9); |
2026-09(常用于日期、时钟、编号) |
%.nf(如 %.2f) |
指定浮点数保留的小数位数(四舍五入) | printf("%.2f", 3.14159); |
3.14 |
%m.nf(如 %8.2f) |
同时指定总宽度 m 与小数位数 n | printf("[%8.2f]", 3.14); |
[ 3.14](总宽 8 位,含小数点和小数) |
printf("%.2f", bmi); 仅改变屏幕显示形式,变量 bmi 在内存中的二进制高精度数据并未改变,参与后续运算仍保持原有精度。
()、数组成员!, ++, --, (type)* / % 高于 加减 + -< <= > >= 高于 == !=&& 高于 ||? := += -= *=,(最低)() 明确结合关系。
++a(先加后用)变量 a 先自增 1,然后以新值作为表达式的返回值参与后续运算或赋值。
a++(先用后加)以 a 的原有值副本作为表达式返回值,随后变量 a 在内存中自增 1。
a += b; 等价于 a = (目标类型)(a + b);
包含 +=, -=, *=, /=, %=。书写简洁,表示对变量的原位更新。
int count = 10;
count *= 1.5; /* 相当于 count = (int)(10 * 1.5) = 15 */
复合赋值会将右侧运算结果自动转换为左侧变量的类型,若目标类型精度较低可能会丢失小数部分。
#include <stdio.h>
double calculateAverage(int sum, int count) {
if (count <= 0) {
return -1.0; /* 检查除数是否有效 */
}
return (double)sum / count; /* 确保执行浮点除法 */
}
int main(void) {
int sbpSum = 0, count = 0, sbp;
while (scanf("%d", &sbp) == 1 && sbp >= 0) {
sbpSum += sbp;
count++;
}
printf("有效随访数: %d | 平均收缩压: %.2f mmHg\n", count, calculateAverage(sbpSum, count));
return 0;
}
| 测试输入 | sbpSum |
count |
期望平均值 | 检验目的 |
|---|---|---|---|---|
120 130 135 -1 |
385 |
3 |
128.33 mmHg |
验证浮点除法保留小数位数 |
-1(直接结束) |
0 |
0 |
-1.00 (无数据) |
验证除数为 0 时的条件分支,避免程序异常终止 |
120 -1(仅测1次) |
120 |
1 |
120.00 mmHg |
验证单次测量的计算结果 |
double x = 5 / 2; → x 的值是?属于哪种转换?double y = (double)5 / 2; → y 的值是?int n = 3.85; → n 的值是?发生了什么?char ch = 'A'; int val = ch + 2; → val 的值是?
① 结果:2.0。 5/2 先执行整数除法得到 2,再通过赋值隐式转换为 2.0。
② 结果:2.5。 (double) 优先将 5 强制转换为 5.0,触发浮点除法得到 2.5。
③ 结果:3。 double 赋给 int 发生赋值截断,向零截断舍弃小数部分。
④ 结果:67。 'A' (ASCII 65) 参与算术运算自动提升为 int,加 2 得到 67。
(double)a / b,避免外层括号scanf 读取需传入变量地址 &,读取 double 需用 %lf
本课我们讨论了算术与赋值表达式。
在条件分支与循环控制中,还需要使用逻辑表达式:
为什么写 if (60 <= sbp <= 140) 会产生逻辑错误?
什么是“短路求值”?如何使用位运算管理多个状态标记?
下节内容:第3课时 表达式的综合应用