智能程序设计(C语言)


选择结构 (下)

计算机学院    杨已彪

yangyibiao@nju.edu.cn

提纲

  • 级联式 if 语句

  • 案例: 经纪人佣金

  • 悬空 else 与条件表达式

  • switch 语句与 break

  • switch 实战: 成绩等级与月份天数

  • 闰年化简与布尔值


级联式if语句

级联 if 语句适合逐级判定: 一旦某个条件为真就停止, 后面的不再判断

具体例子 (判断 n 与 0 的关系):

if (n < 0) {
  printf("n is less than 0\n");
}
else if (n == 0) {
  printf("n is equal to 0\n");
} else {
  printf("n is greater than 0\n"); 
}

通用形式:

if (表达式)
  语句
else if (表达式)
  语句
…
else if (表达式)
  语句
else {
  语句
}
  • 其实 else if 是嵌套: 第二个 if 嵌在第一个的 else 里

  • 惯例: 不缩进, 每个 else 与第一个 if 对齐, 写成 else if 链

  • 好处: 判定很多时避免层层缩进; 一旦某个条件为真, 后面的分支不再判断


程序: 计算经纪人佣金

股票交易的佣金取决于交易金额, 某经纪人按下表收费:

交易规模佣金率
低于 $2,500$30 + 1.7%
$2,500–$6,250$56 + 0.66%
$6,250–$20,000$76 + 0.34%
$20,000–$50,000$100 + 0.22%
$50,000–$500,000$155 + 0.11%
超过 $500,000$255 + 0.09%

另有最低收费: 佣金不足 \(39 按 \)39 收

broker.c 输入交易金额, 输出佣金:

Enter value of trade: 30000
Commission: $166.00
  • 核心是一个级联 if, 判定交易落在哪个区间
  • 区间按大小从低到高排, 只需 < 不用 &&
  • 最后再用 if 兜住最低收费
级联 if: 从低到高, 每档只判上限 value < 2500 是 $30 + 1.7% value < 6250 是 $56 + 0.66% value < 20000 是 $76 + 0.34% value < 50000 是 $100 + 0.22% value < 500000 是 $155 + 0.11% 否则 (≥ 500000) $255 + 0.09% 区间从小到大排 · 每个条件只写上限 不必用 && 连接相邻区间 最后: 佣金 < \(39 时按 \)39 收

程序: 计算经纪人佣金

代码
/* Calculates a broker's commission */
#include <stdio.h>
int main(void) {
  float commission, value;
  printf("Enter value of trade: ");
  scanf("%f", &value);
  if (value < 2500.00f) {
    commission = 30.00f + .017f * value;
  } else if (value < 6250.00f) {
    commission = 56.00f + .0066f * value;
  } else if (value < 20000.00f) {
    commission = 76.00f + .0034f * value;
  } else if (value < 50000.00f) {
    commission = 100.00f + .0022f * value;
  } else if (value < 500000.00f) {
    commission = 155.00f + .0011f * value;
  } else {
    commission = 255.00f + .0009f * value;
  }
  if (commission < 39.00f) {
    commission = 39.00f;
  }
  printf("Commission: $%.2f\n", commission);
  return 0;
}
要点
  • 六个区间 → 六个分支: 从低到高, 每个条件只写上限 (value < 2500.00f …)

  • 佣金不足 $39 时, 最后一个 if 把佣金兜底到 $39

  • 对照左栏: 每个 else if 对应表里一行


"悬空else"的问题

缩进暗示的版本 (程序员想配外层):

if (y != 0)
  if (x != 0)
    result = x / y;
  else
    printf("Error: y is equal to 0\n");

C 的实际配对 (else 属于最近的 if):

if (y != 0) {
  if (x != 0) {
    result = x / y;
  } else {
    printf("Error: y is equal to 0\n");
  }
}
  • C 的规则: else 属于尚未配对的最近一个 if——缩进骗不了编译器

  • 后果: y 为 0 时反而静默跳过 (不报错); x 为 0 时却误报 "y is equal to 0"

  • 下一页看大括号修复版


"悬空else"的问题

为了使else子句成为外部if语句的一部分, 可以将内部if语句用大括号括起来:

if (y != 0) {
  if (x != 0) {
    result = x / y;
  }
} else
  printf("Error: y is equal to 0\n");

if语句中使用大括号可以避免这一问题


条件表达式

代码

条件表达式 根据条件的值产生两个值之一

形式: 表达式1 ? 表达式2 : 表达式3

/* 求绝对值 */
x = (n >= 0) ? n : -n;

读作: "如果表达式1成立, 那么表达式2, 否则表达式3"

要点
  • 两个符号 (? 和 :) 必须一起使用; 需要三个操作数, 所以也叫三元运算符

  • 操作数可以是任何类型

  • 分阶段计算: 先算表达式1; 非零 → 算表达式2, 其值即整个表达式的值; 为零 → 算表达式3

  • 短路: 只会计算其中一个分支——未被选中的表达式根本不算, 副作用也不会发生 (和 &&/|| 一样): flag ? a++ : b++ 只有一个自增生效


条件表达式

int i, j, k;

i = 1;
j = 2;
k = i > j ? i : j;          /* k is now 2 */
k = (i >= 0 ? i : 0) + j;   /* k is now 3 */

printf("%d\n", i > j ? i : j);   /* prints 2 */

括号是必需的, 因为条件运算符的优先级低于到目前为止讨论的其他运算符的优先级, 赋值运算符除外

条件表达式往往会使程序更短但更难理解, 因此最好谨慎使用它们

条件表达式常用于返回语句: return i > j ? i : j;

printf 的实参也可以直接写条件表达式 (如左最后一行)


switch语句

级联 if 版:

if (grade == 4) {
  printf("Excellent");
} else if (grade == 3) {
  printf("Good");
} else if (grade == 2) {
  printf("Average");
} else if (grade == 1) {
  printf("Poor");
} else if (grade == 0) {
  printf("Failing");
} else {
  printf("Illegal grade");
}

switch 版:

switch (grade) {
  case 4:  printf("Excellent");
           break;
  case 3:  printf("Good");
           break;
  case 2:  printf("Average");
           break;
  case 1:  printf("Poor");
           break;
  case 0:  printf("Failing");
           break;
  default: printf("Illegal grade");
           break;
}
  • 级联 if 适合区间判定; 与一系列离散值比较时, switch 更清晰

  • 左边的 if 版 grade == x 写了六遍; switch 版只写一次

  • 每个分支末尾的 break——下一页就讲它的作用


break语句的作用

没有 break 的版本:

switch (grade) {
  case 4:  printf("Excellent");
  case 3:  printf("Good");
  case 2:  printf("Average");
  case 1:  printf("Poor");
  case 0:  printf("Failing");
  default: printf("Illegal grade");
}

故意穿透 (4 个等级一起计入 num_passing):

switch (grade) {
  case 4: case 3: case 2: case 1:
    num_passing++;
    /* FALL THROUGH */
  case 0: total_grades++;
    break;
}
  • break:跳出 switch,接着执行 switch 后面的语句

  • switch 怎么工作:先算表达式,跳到匹配的 case——注意是"跳"不是"挨个查",跳进去之后一直往下执行,直到 switch 结束或遇到 break

  • 所以没有 break 就会一路"穿透":grade = 3 时输出 GoodAveragePoorFailingIllegal grade

  • 穿透有时是故意的(如右),但更多时候是忘了写 break;故意穿透要加注释 /* FALL THROUGH */,让别人知道你是有意的;最后一个 case 不需要 break,但建议照写——以后加新 case 时不会忘


switch语句

最常见的形式:

switch (表达式) {
  case 常量表达式: 语句
  …
  case 常量表达式: 语句
  default: 语句
}
  • switch 比级联 if 更易读、通常更快

  • 括号里的控制表达式必须是整数: 字符C 里 算整数, 可以判定; 浮点数和字符串不行

  • 每个 case 以下面的标签形式开头: case 常量表达式:

  • 常量表达式不能含变量或函数调用: 5、5 + 10 可以, n + 10 不行 (除非 n 是表示常量的宏)


switch语句

代码
switch (grade) {
  case 4:
  case 3:
  case 2:
  case 1:  printf("Passing");
           break;
  case 0:  printf("Failing");
           break;
  default: printf("Illegal grade");
           break;
}
要点
  • 每个 case 标签之后是任意数量的语句, 不需要大括号; 每组最后一条语句通常是 break

  • 一组语句之前可以有多个 case 标签; 为节省空间, 可将多个 case 写在同一行 (case 4: case 3: ...)

  • 不允许有重复的 case 标签

  • case 标签的顺序无关紧要——它们只是入口标记; default 也不必放在最后 (但惯例放最后更易读)

  • 若缺少 default, 且控制表达式的值与任何 case 都不匹配, 控制传递到 switch 之后的下一条语句


任务 3: 成绩等级

任务: 输入百分制成绩 (0–100 的整数), 输出等级 A/B/C/D/F

样例:

请输入成绩: 87
等级: B

等级规则: 90–100 A, 80–89 B, 70–79 C, 60–69 D, 0–59 F

提示:

  • 用 switch (score / 10) 把分数段变成 10、9、8……

  • 多个 case 可以共用一组语句; 每个分支别忘 break

⚡ 在 VS Code 中打开 task-grade.c (骨架)

任务 3: 参考代码与要点

#include <stdio.h>
int main(void) {
  int score;
  printf("请输入成绩: "); scanf("%d", &score);
  if (score < 0 || score > 100) {
    printf("成绩非法\n");
  } else {
    switch (score / 10) {
      case 10: case 9: printf("等级: A\n"); break;
      case 8: printf("等级: B\n"); break;
      case 7: printf("等级: C\n"); break;
      case 6: printf("等级: D\n"); break;
      default: printf("等级: F\n"); break;
    }
  }
  return 0;
}
  • score / 10 是整数除法: 87 → 8, 100 → 10

  • case 10: 与 case 9: 共用一个分支——多标签写法

  • break 跳出 switch; 漏写会穿透到下一个 case

  • 先判 0–100, 非法成绩不进入 switch

  • switch 的控制表达式必须是整数, 不能用浮点


程序: 某年某月有多少天

#include <stdio.h>
int main(void)
{
  int year, month;
  printf("请输入年份和月份: ");
  scanf("%d%d", &year, &month);

  switch (month) {
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
      printf("31 天\n"); break;
    case 4: case 6: case 9: case 11:
      printf("30 天\n"); break;
    case 2:
      printf("%d 天\n",
             (year % 4 == 0 && year % 100 != 0)
               || year % 400 == 0 ? 29 : 28);
      break;
    default:
      printf("非法月份\n");
  }
  return 0;
}

monthdays.c: 输入年份和月份, 输出该月天数

  • 大月 (31 天) 七个 case 共用一个语句——多 case 标签的典型用法

  • 2 月用条件表达式 + 闰年判断 (刚讲过的 leap 条件直接复用)

  • 测试: 2024 2 → 29; 1900 2 → 28; 2000 2 → 29; 2026 9 → 30; 13 → 非法月份


演示: 闰年判断的化简

现场演示: 把上一课的闰年嵌套版化简成一个布尔表达式

⚡ 打开 skeleton.c

要求: 输出与嵌套版完全一致 (is a leap year / is a common year)

  • 用 && 和 || 把三条规则合并: 能被 4 整除且不能被 100 整除, 或能被 400 整除

  • 测试: 2024 → leap; 1900 → common; 2000 → leap


演示: 闰年判断的化简 (参考代码)

代码
#include <stdio.h>

int main(void) {
    int year = 0;
    scanf("%d", &year);

    int leap = 0; // boolean; indicator; flag
    // &&: and (&); ||: or (|); !: not
    leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);

    if (leap) {
      printf("%d is a leap year\n", year);
    } else {
      printf("%d is a common year\n", year);
    }
    return 0;
}
要点
  • leap 直接用布尔表达式赋值——结果就是 0/1, 不需要 if/else

  • 级联 if → 条件合并 → 布尔表达式直接赋值, 三种写法功能相同

  • 测试 2024/1900/2000, 输出与嵌套版一致


布尔值 (C99)

C89 没有布尔类型, 我们一直用 int 变量当标志 (比如上一页的 leap)

C99 提供了真正的布尔类型: _Bool + 头文件 <stdbool.h> 的 bool / true / false

#include <stdbool.h>

bool leap = false;
…
leap = true;
if (leap) …      /* 直接测试 */
if (!leap) …     /* 测试是否为假 */ {
  • bool 变量只能存 0 或 1: flag = 5; 会变成 1

  • 建议: 新代码用 <stdbool.h> 的 bool——但不强制, int 标志照旧能用 (本课作业两种都行)


课堂小测

  1. 级联式 if 语句适合什么场景?它和嵌套 if 的书写区别是什么?
  2. 条件表达式 i > j ? i : j 的值是什么?可以嵌套使用吗?
  3. switch 语句中如果漏写 break 会发生什么?多个 case 共用一段代码怎么写?
  4. 悬空 else 问题中,else 实际和哪个 if 配对?如何避免歧义?



未完待续