程序设计基础 · 第5次课 · 第1课时
从社区慢病管理(体质指数 BMI 计算)到经典数值与判断函数,理解“接口设计、值传递与边界测试”
同样的体质指数(BMI)计算规则,要在程序里复制粘贴多少遍?
同一公式在多处重复书写,一旦规则调整,极易漏改或前后不一致。
把相对独立的计算或判断封装为自定义函数,实现“一次定义,多次调用”。
函数声明、函数定义、函数调用,以及实参到形参的单向值传递。
return 的作用calcBMI 函数。居民体检采集:体重 70.0 kg,身高 1.75 m。
背景:体质指数(BMI)是社区高血压、糖尿病等慢病随访建档的核心指标。
统一按标准公式计算:
70.0 ÷ (1.75 × 1.75) ≈ 22.86
double bmi1 = 70.0 / (1.75 * 1.75);
double bmi2 = 82.0 / (1.80 * 1.80);
double bmi3 = 55.0 / (1.60 * 1.60);
weightheightdouble| 接口设计问题 | 在本例中的约定 |
|---|---|
| 1. 函数叫什么名字? | calcBMI(名称清晰表达单一职责) |
| 2. 需要调用者提供什么输入(形参)? | double weight(体重/kg)、double height(身高/m) |
| 3. 向调用者交还什么结果(返回类型)? | double(计算出的体质指数 BMI) |
| 4. 输入不合法时如何响应? | 若 weight <= 0 或 height <= 0,统一返回错误标识 -1.0 |
double calcBMI(double weight, double height)
{
if (weight <= 0 || height <= 0)
return -1.0;
return weight / (height * height);
}
double(与返回值类型一致)calcBMI(double weight, double height),每个参数都必须单独写明类型return 把结果交还调用者main 怎样使用一个自定义函数?#include <stdio.h>
double calcBMI(double weight, double height); /* 1. Function prototype */
int main(void)
{
double bmi = calcBMI(70.0, 1.75); /* 2. Function call */
printf("BMI = %.2f\n", bmi);
return 0;
}
double calcBMI(double weight, double height) /* 3. Function definition */
{
if (weight <= 0 || height <= 0)
return -1.0;
return weight / (height * height);
}
告诉编译器函数的返回类型、名称和参数类型,末尾有分号。
在 main 中给出实参 (70.0, 1.75),启动函数并接收返回值。
真正实现计算逻辑的代码块,首部末尾无分号。
| 测试类型 | weight |
height |
期望返回 | 设计目的 |
|---|---|---|---|---|
| 正常数据 | 70.0 |
1.75 |
22.86 |
验证常规计算公式正确性 |
| 零值边界 | 0.0 |
1.75 |
-1.0 |
验证非正体重被合法拦截 |
| 除零边界 | 70.0 |
0.0 |
-1.0 |
拦截除以 0 的严重运行错误 |
| 非法负值 | -5.0 / 70.0 |
1.75 / -1.75 |
-1.0 |
暴露“只判断 height == 0”的漏洞 |
💡 课堂互动:学生先写出自己的测试表,再提问 AI 并逐条人工核验 AI 建议的测试点是否真正对应代码分支。
main 暂停calcBMI(70.0, 1.75)70.0 → weight1.75 → height22.86return 返回bmimain,而是精确回到刚才调用它的位置继续往下走。void tryChange(double x)
{
x = 100.0;
}
int main(void)
{
double weight = 70.0;
tryChange(weight);
printf("%.1f\n", weight);
return 0;
}
先预测再解释:屏幕最终打印 70.0 还是 100.0?
main 的变量区weight 始终保存在 main 自己的内存单元中,值为 70.0。
tryChange 的形参区形参 x 只是复制了一份 70.0 的副本;x = 100.0 只改了副本,函数结束即销毁。
calcBMI(w + 2.5, 1.75)int、double、char)作为参数,数据单向从实参传给形参;后续学习指针与数组时,再讨论如何让函数修改调用者的数据。
💡 课堂互动:同桌对照 AI 生成的四步内存表,解释为什么“改了形参 a、b 却没改实参 x、y”。
cylinder(r, h) 到五边形分割求面积 area(x, y, z)。
输入圆柱体的半径 \(r\) 和高 \(h\),计算并输出圆柱体的体积 \(V = \pi r^2 h\)。
要求定义和调用函数 cylinder(r, h) 计算圆柱体的体积。
\[V = \pi r^2 h\]
#include <stdio.h>
double cylinder(double r, double h);
int main(void)
{
double height, radius, volume;
scanf("%lf%lf", &radius, &height);
volume = cylinder(radius, height);
printf("Volume = %.3f\n", volume);
return 0;
}
double cylinder(double r, double h)
{
double result;
result = 3.1415926 * r * r * h;
return result;
}
calcBMI:同样是“两个数值参数 → 公式计算 → 返回 double”。radius 传给形参 r,height 传给 h,按位置顺序一一对应。2.0 3.0,输出 Volume = 37.699。将一个五边形分割成三个三角形(如右图),输入各边长 \(a_1 \sim a_7\),定义并调用计算三角形面积的函数 area(x, y, z),求五边形面积。
已知三角形三边 \(x, y, z\),半周长 \(p = \frac{x+y+z}{2}\):
\[\text{area} = \sqrt{p(p-x)(p-y)(p-z)}\]
#include <stdio.h>
#include <math.h>
double area(double x, double y, double z);
int main(void)
{
double a1, a2, a3, a4, a5, a6, a7, s;
scanf("%lf%lf%lf%lf%lf%lf%lf", &a1, &a2, &a3, &a4, &a5, &a6, &a7);
s = area(a1, a5, a6) + area(a4, a6, a7) + area(a2, a3, a7);
printf("The area of the Pentagon is %.2f\n", s);
return 0;
}
double area(double x, double y, double z)
{
double p = (x + y + z) / 2;
return sqrt(p * (p - x) * (p - y) * (p - z));
}
(a1,a5,a6)、(a4,a6,a7)、(a2,a3,a7) 连续调用三次!如果输入的边长是 1, 2, 10,半周长 p = 6.5,则 p - z = -3.5,导致 sqrt 对负数开方,产生 NaN(非数值)!
三边均为正数,且满足任意两边之和大于第三边:
x > 0 && y > 0 && z > 0 && x + y > z && x + z > y && y + z > x
💡 课堂互动:对比“完全信任输入”与“在函数入口处校验参数”的差异,理解函数契约。
isSquare(n):用返回值 1 和 0 表达“是”与“否”。
定义一个判断完全平方数的函数 isSquare(n),当 \(n\) 为完全平方数时返回 1,否则返回 0。
(注:不允许调用数学库函数 sqrt)
sqrt,怎样判断完全平方数?对于非负整数 \(n\),让 \(i\) 从 \(0, 1, 2, \dots\) 递增,测试 \(i \times i\) 是否等于 \(n\)(当 \(i \times i > n\) 时停止)。
利用数学性质:前 \(k\) 个连续正奇数之和等于 \(k^2\):
\(1 + 3 + 5 + \dots + (2k-1) = k^2\)
0,说明它是完全平方数!int isSquare(int n)
{
int i;
for (i = 1; n > 0; i = i + 2) {
n = n - i;
}
if (n == 0)
return 1;
else
return 0;
}
n,会影响 main 里的原变量吗?(不会,因为是值传递!)初始 n |
依次减去的奇数 i |
循环结束时 n |
返回值 |
|---|---|---|---|
9 |
9-1=8 → 8-3=5 → 5-5=0 |
0 |
1(是) |
10 |
10-1=9 → 9-3=6 → 6-5=1 → 1-7=-6 |
-6 |
0(否) |
0 |
n > 0 不成立,循环 0 次 |
0 |
1(\(0=0^2\)) |
-4 |
n > 0 不成立,循环 0 次 |
-4 |
0(否) |
main 中调用?int isSquare(int n);
if (isSquare(n)) {
printf("%d is a square number.\n", n);
} else {
printf("%d is not a square number.\n", n);
}
sqrt 算法的对比与边界核验💡 课堂互动:用 -1, 0, 1, 15, 16 五个测试值核验函数,并观察 AI 指出的 i * i 潜在溢出风险。
A. double calcBMI(double weight, height)
B. double calcBMI(double weight, double height)
C. calcBMI(double weight, double height)
D. double calcBMI(double weight, double height);
结论:选 B
A 漏写第二个形参类型;C 漏写返回类型;D 末尾多了分号(变成了声明而非定义首部)。
double calcBMI(double weight, double height)
{
if (height == 0) return -1.0;
return weight / (height * height);
}
暴露问题的测试输入:(70.0, -1.75) 或 (-5.0, 1.75)
修复建议:改为 if (weight <= 0 || height <= 0) return -1.0;
| 函数类型 | 典型代表 | 返回类型与含义 | 调用方式 |
|---|---|---|---|
| 数值计算型 | calcBMI(w, h)、cylinder(r, h)、area(x, y, z) |
double / int:计算出的具体数值(或错误码) |
参与赋值、算术运算或直接作为 printf 参数 |
| 条件判断型 | isSquare(n) |
int:返回 1 表示真(满足),0 表示假(不满足) |
常直接放进 if (isSquare(n)) 条件表达式中 |
-1, 0, 1, 15, 16 五组数据测试例5-3 isSquare(n)isSquare(n),编写判断整数是否为偶数的函数 even(n)area(x, y, z) 补全三角形三边合法性检查,记录一条 AI 测试建议与自己的验证结果写函数前,先定好“输入什么、交还什么、边界怎么处理”。