选择结构

对应教学日历:讲次 5-7(3月26日-4月2日)


一、条件的表示

关系表达式

用关系运算符连接:> < >= <= == !=

结果:真 → 1,假 → 0(C 语言没有布尔类型,用 int 表示真假)

逻辑表达式

用逻辑运算符连接:&&(与)、||(或)、!(非)

if (a > 0 && a < 100)     // 0 < a < 100 是错误的写法!
if (ch >= 'A' && ch <= 'Z')  // 判断是否大写字母
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)  // 闰年判断

易错0 < a < 100 在C语言中不等于数学中的 “a 在 0 到 100 之间”。 实际运算:先算 0 < a 得 0 或 1,再与 100 比较,结果总是真。 正确写法a > 0 && a < 100

C语言中的”真假”概念

  • 0'\0'NULL
  • 一切非 0 值(包括负数)
if (3)     // 永远为真
if (-1)    // 永远为真
if (0)     // 永远为假
if ('A')   // 永远为真('A' 的 ASCII 码 65 ≠ 0)

浮点数的等于判断

// 错误:if (f == 0.0)  浮点数不能用 == 精确比较
// 正确:if (fabs(f) < 1E-6)

二、if 语句

单分支

if (表达式)
    语句;     // 表达式为真时执行

双分支

if (表达式)
    语句1;   // 表达式为真时执行
else
    语句2;   // 表达式为假时执行

多分支

if (表达式1)
    语句1;
else if (表达式2)
    语句2;
else if (表达式3)
    语句3;
else
    语句n;   // 以上条件都不满足时执行

if 语句的嵌套

if (表达式1)
    if (表达式2)     // 内嵌 if
        语句1;
    else
        语句2;       // else 与最近的未配对的 if 匹配
else
    语句3;

悬空 else 问题else 总是与它前面最近的、未配对的 if 匹配。不确定时用 {} 明确作用域。

复合语句(语句块)

if (a > b)
{
    t = a;       // 多条语句必须用 {} 括起来
    a = b;
    b = t;
}

三、switch 语句

switch (表达式)       // 表达式必须是整数或字符型
{
    case 常量1: 语句1; break;
    case 常量2: 语句2; break;
    ...
    default: 语句n;    // 可选的默认分支
}

执行机制

  1. 计算 switch 后表达式的值
  2. 与各 case 后的常量比较
  3. 匹配成功后从该 case 开始执行,直到遇到 breakswitch 结束
  4. 如果所有 case 都不匹配,执行 default(如果有)

重点(贯穿现象):如果 case 后没有 break,会贯穿到下一个 case 继续执行!这是考试常考点。

switch(score)
{
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5: grade='E'; break;   // 0-5 都对应 E
    case 6: grade='D'; break;
    case 7: grade='C'; break;
    case 8: grade='B'; break;
    case 9: grade='A'; break;
}

四、指针变量初步(讲次 5 引入)

在讲次5引入选择结构的同时,课程也引入了指针的基本概念,为后续章节铺垫。

指针定义与使用

int *p;       // 定义一个指向整型的指针变量 p
int x = 100;
p = &x;       // p 指向 x(p 存储 x 的地址)
printf("%d", *p);  // *p 表示取 p 所指向变量的值(即 x 的值 100)
符号含义示例
&取地址运算符p = &x;
*(定义时)声明指针类型int *p;
*(使用时)指针解引用(取值)*p = 10;

核心理解p 存地址,*p 访问该地址的数据。


作业示例

ex6-1:if-else 猜数

#include<stdio.h>
#include<time.h>
#include <stdlib.h>
int main()
{
    int a,b;
    srand((unsigned)time(NULL));
    a=rand()%101;            // 随机生成 0-100 的数
    printf("输入猜测整数:");
    scanf("%d",&b);
    if(a==b)
        printf("恭喜你猜对了!");
    else if(a>b)
        printf("小了");
    else
        printf("大了");
    return 0;
}

要点

  • srand(time(NULL)) + rand()%101 生成 0-100 随机数
  • if-else if-else 三路分支结构

ex6-2:switch 实现成绩评级

#include<stdio.h>
#include <stdlib.h>
int main()
{
    int score,temp;
    char grade;
    printf("enter score:");
    scanf("%d",&score);
    if((score>100)||(score<0))
    {
        printf("\n error,try again!\n");
        exit(0);
    }
    if(score==100) temp=9;
    else temp=(score%10);         // 只取个位数判断
    switch(temp)
    {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:grade='E';break;
        case 6:grade='D';break;
        case 7:grade='C';break;
        case 8:grade='B';break;
        case 9:grade='A';
    }
    printf("score=%d,grade=%c\n",score,grade);
    return 0;
}

要点

  • 先排除非法输入(score>100 || score<0
  • 通过 temp=score%10 取个位数简化 switch 分支
  • 利用贯穿现象:case 0-5 共享一个执行体
  • switch 的表达式必须是整型(intchar),不能是 float
  • 每个分支末尾的 break 不能忘

ex6-3:个税计算(多分支 else-if)

#include<stdio.h>
int main()
{
    float yingnashui,nashui;
    printf("请输入你的月应纳税所得额:");
    scanf("%f",&yingnashui);
    if((yingnashui-3000)<1E-6)
        nashui=yingnashui*0.03;
    else if((yingnashui-12000)<1E-6&&(yingnashui-3000)>1E-6)
        nashui=yingnashui*0.1-210;
    else if((yingnashui-25000)<1E-6&&(yingnashui-12000)>1E-6)
        nashui=yingnashui*0.2-1410;
    printf("您应交纳个人所得税为:%8.2f\n",nashui);
    printf("--纳税光荣--!\n");
    return 0;
}

要点

  • if-else if-else if 多分支结构
  • 使用 1E-6 替代 0 做浮点比较((yingnashui-3000)<1E-6yingnashui <= 3000
  • 注意各分支的边界划分,不要遗漏或重叠

ex6-4:运算符推断

#include <stdio.h>
int main()
{
    int a, b, c;
    char op;
    scanf("%d,%d,%d", &a, &b, &c);
    if (a + b == c) {
        op = '+'; printf("%c\n", op);
    }
    else if (a - b == c) {
        op = '-'; printf("%c\n", op);
    }
    else if (a * b == c) {
        op = '*'; printf("%c\n", op);
    }
    else if (b != 0 && a / b == c) {
        op = '/'; printf("%c\n", op);
    }
    else if (b != 0 && a % b == c) {
        op = '%'; printf("%c\n", op);
    }
    else {
        printf("error\n");
    }
    return 0;
}

要点

  • 除法和取余必须先判断除数不为 0:b != 0 && a / b == c
  • 注意检查顺序:先判断 b != 0 再计算 a / b(短路求值)

ex6-5:条件判断 + 简单算法

#include <stdio.h>
int main() {
    int n, x, y;
    scanf("%d %d %d", &n, &x, &y);
    int eaten = (y + x - 1) / x;    // 向上取整
    int remaining = n - eaten;
    if (remaining < 0) {
        remaining = 0;
    }
    printf("%d\n", remaining);
    return 0;
}

要点

  • (y + x - 1) / x 是整数向上取整技巧
  • if (remaining < 0) remaining = 0 防止负值

ex4-4:指针基础——取地址与解引用

#include<stdio.h>
int main()
{
    int x=100,y=200;
    int *p,*q,sum;
    p=&x;                    // p 指向 x
    q=&y;                    // q 指向 y
    sum=*p+*q;               // 取值并相加(*p 即 x 的值)
    printf("x的地址=%d\n",p);   // 输出地址(应使用 %p)
    printf("y的地址=%d\n",q);
    printf("*p=%d\n",*p);       // 输出 100
    printf("*q=%d\n",*q);       // 输出 200
    printf("*p+*q=%d\n",sum);   // 输出 300
    return 0;
}

要点

  • & 取变量的地址:p = &x(p 存 x 的地址)
  • * 解引用:*p 取 p 所指变量的值
  • int *p* 表示 p 是指针类型(定义时),*p 表示取值(使用时)
  • 地址格式符:%p(不应使用 %d,程序此处不规范)

作业重点

  • if 条件中 ==(等于)和 =(赋值)绝对不可混淆
  • else 总是与最近的未配对 if 匹配(悬空 else)
  • 多语句时用 {} 括成复合语句
  • switchcase 后必须是整型常量,不能是变量或浮点数
  • switch 没有 break贯穿到下一个 case
  • 浮点数不能直接用 == 比较,应判断差的绝对值是否小于一个极小值
  • &&|| 的短路求值规则
  • 指针基础:& 取地址,* 解引用