Bo2SS

Bo2SS

3 程序流程控制方法

课程内容#

if 语句、switch 语句#

  • (C 语言关系运算符)
    • 0 → false;1 → true
    • false 👉 0、NULL(null)、'\0'
    • <= → =<
    • ! 取非 (关系运算符)
      • 要注意区分:位取反运算符~(位运算符)
      • ⭐!! 非非操作:逻辑上的归一化
        • 将所有真值统一到 1,假值还是 0
  • 顺序结构
  • 分支结构

IF 语句#

  • 图片
  • (表达式)

    • 任何表达式都有逻辑返回值
    • 对于赋值表达式 a = 123;
      • 返回值为 a 变量的值 123
  • {代码段;}

    • 了解什么是一条语句?
      • 单语句:“;” 结尾的语句
        • int a = 1, b = 234; // 是一条语句,“,” 前后分为为一个表达式
      • 空语句:只有一个 “;”,没有作用
      • 复合语句
        • 用花括号
        • 即代码段👆
  • if else 语句

    • 可以用问号表达式(三目运算符)来代替
      • ...? ...: ...;

SWITCH 语句#

  • 图片
  • 变量 a 要有一个明确的唯一的整型值作为映射!

    • 字母也有这样的映射:ASCII 码
  • 进入 case 后,依次执行后续代码

    • 直到遇到break、continue 或结构末尾~
    • 而不需要再判断其后的 case 是否满足
  • default 分支

    • 当 case都不满足时,会进入,相当于 if else 语句的 else

CPU 的分支预测#

从力扣题 -回文整数引出

  • 图片

船长超高时间效率代码如下:

  • 图片
  • 红框处实现的就是 x<0

    • 用来筛选小于 0 的整数,负数肯定不是回文整数
  • 这段语句在操作系统内核里出现:

  • 图片
  • !!(x) 逻辑归一化,结果只会是 1 或 0

    • 经常成立与否的情况会告诉正在处理程序的 CPU

⭐CPU 执行程序过程解析

  • C 语言程序执行过程:编译后生成可执行文件→加载到内存里→CPU 执行
  • cash 缓存
    • 一级缓存:速度最快,而容量最小
  • CPU 指令执行方式对比
    • 早期串行(取址、指令预解析、写数据、执行、写回内存)
    • 当今并行
    • 示意图如下左右:
      • 图片
      • 对于 5 条指令

        • 串行方式需要 5 * 5 = 25 个时钟周期
        • 并行方式只需要 5 + 4 = 9 个时钟周期
      • 当指令非常多时,提高效率将近 5 倍

  • 所以!CPU喜欢顺序结构
    • 讨厌分支结构(要少用 if else 语句)
    • 分支预测问题
    • 对于并行的方式,CPU 不知道分支结构下一步是谁,该提前加载哪一步?
      • CPU 实际是随机加载下一步
        • 所以,如果预测错了,后面就白做了,此时又要返回重新处理
  • 而__builtin_expect () 做的就是告诉CPU 哪种分支情况更易发生!!!

循环结构#

  • WHILE 语句
    • while () {代码块;}
      • 先判断,再执行
    • do {代码块;} while (表达式); ←分号要注意!
      • 至少执行一次
  • FOR 语句
    • for (初始化;循环条件;执行后操作) {代码块;}
    • 让循环变得很秀,由三部分组成
    • 这三部分可以都不写
      • 例如:for (; ;);,那就相当于 while (1);
      • 上面是一个死循环,啥都没有

随堂练习#

  • 图片
  • 记得条件判断冗余检查

  • 输入非法时(如 "q")陷入死循环问题

    • 解决方法及思考过程详见下文:思考点 1
  • 代码

  • 图片
  • 图片

  • 图片

    • 熟悉 switch 语句结束的特性
    • 代码
  • 图片
  • 图片
  • 代码

  • 图片

​ 对于 while 方式,如果首次判断成立,则与 do...while 方式没有区别

  • 图片
  • 代码

  • 图片

亮点笔记#

  • ⭐对条件判断语句进行冗余检查(剪裁),通常可以有更精简的代码
    • 所有判断表达式是否为 0 的情况,可以直接使用 if (! 表达式)
      • 对于 if (表达式) 单语句,可以利用逻辑与
        • (表达式) && 单语句
        • 比如 i && printf (" "); ,用来在除第一次循环中输出空格
        • 单语句不能是 {代码块;}
    • if...else 用三目运算符代替
  • 一般将循环变量 i 定义在初始化部分:int i = 0
    • 不需要提前放在前面
      • 更规范、简明:用变量之前再定义,不要离的太远
    • 作用域问题
      • 只在循环里用到该变量
  • ++i 比 i++ 快?
    • 能用 ++i 就用
      • 从函数栈的角度:👇
      • ++i 直接入栈 i+1 的值
      • 而 i++ 的话,先入栈 i 再入栈 i+1

代码演示#

分支结构(6.struct_program.cpp)#

  • 图片
  • a - b 可以代替 a == b 判等

  • if else 可以用三目运算符代替

  • 图片
  • 输出 1:false

    • a++ 和 && 的运算优先级?
      • ++ 优先级大于 &&
      • a++ 外面的 () 为什么没有把 ++ 的运算优先级提高到比 && 先加呢?
        • 是优先的
        • 这里输出 false 与此无关~
          • 应该对于 if 判断来说,先判断 a,再去 ++
          • a++ 表达式的值是没有 ++ 的 a
  • 输出 2:a = 1, b = 0

    • ⭐逻辑与 -- 短路规则
      • 只要前面有假值,就不会往后走了【聪明人的做法】
  • 图片
  • (接上 a = 1, b = 0)

  • 输出 1:true

  • 输出 2:a = 2, b = 0

    • ⭐逻辑或 -- 长路规则
      • 只要前面有一个真值,就不用了往后走了
      • 要想执行后面的表达式,必须前面的表达式均为假值
  • 图片
  • 以空格为间隔(末尾无空格)的输出方式参考

    • 方式一:如果不是第一次循环,输出前空格
      • 首次更好得到:i == 0
      • 可优化第 43 行:i && printf(" ");
    • 方式二:如果不是最后一次循环,输出后空格
  • rand()

    • 需引入 <stdlib.h>

    • 输出的是随机的无符号数

      • rand () % 100 可以随机输出 0-99 的数
    • ⭐其实是固定的随机

      • 图片
      • 通过 srand() 设定随机种子

        • srand(time(0))

          • time(0)
            • 获取当前的时间,需引入 <time.h>
            • 精确到 s,1970.1.1 至今的秒数
        • 可以看出值是变化的了,但计算机中没有真正意义上的随机

          • 图片
  • 优化版

    • 上述代码有三处可以简化!两个 if 都可以去掉

    • 图片
    • ⭐~

      1. 位运算与取余的转换:% 2 等价于 & 1
        • % (2 ^ n) 等价于 & (2 ^ n - 1)
      2. +1 与真值 1 的等价
      3. 逻辑与的短路规则

循环结构(7.cpp)#

判断回文整数(十进制)

  • 图片
  • 记得对负数做特判!

  • 如果想判断二进制下的回文整数呢?

    • 将圆圈处都改成 2 即可

      • 计算机底层其实都是以二进制存储数据的
      • 不管什么进制下,都可以判断回文
    • base 进制下回文整数判断

      • 图片

计算整数位数(十进制)

  • while 循环和 do...while 循环的细微区别比较
    • 关键在于第一次循环的条件是否成立~
      • 图片
      • 当输入非 0 数字时,digit 和 digit2 没有区别
    • 当输入数字 0 时,有区别
      • 图片
      • 所以有 0 的情况判断位数
        * 用 do...while
        * 或者特判

附加知识点#

  • switch 语句

    • case 后面不允许声明变量
    • default 的末尾也要加 break 吗
      • 可有可无,加 break 是为了统一风格
    • Python 不支持 switch 语句
  • C 语言中,如果 main 函数的末尾没有 return 语句将会有什么影响?

    • C99 开始,基本没影响,标准要求等价于自动补上return 0;
    • 而 C99 之前,如果控制抵达 main 函数结尾而没有 return,则为行为未定义
  • 浮点数判等

    • 不直接用 ==,会不准确,利用差值是否小于某个极小值来判等
  • __builtin_expect () 还有 6 + 个兄弟

  • 图片
  • 位权决定左移右移的倍数

    • 十进制,左移一位,*10
    • 二进制,左移一位,*2

思考点#

  • 💡(解决)对于 5.if.cpp,即随堂练习 1。输入非法值,如字母‘q’,会陷入无限循环

    • 与‘q’的 ASCII 码有关吗?

      • 无关~
      • 以 % d 形式输出‘q’,看起来是一个地址,单次运行是固定的,应该是初始化 n 时随机分配的一个值
        • 如果输入正常值再输入错误值,此时该值就是之前的 n 值
        • 因为其实没有读入值来改变 n
    • 读不进‘q’值

      • scanf 的返回值为 0
      • 但是也不会移向下一个输入,类似第一讲里的 %[^\n] 遇到 \n 无限循环事件
    • ⭐当然! 同样可以使用 getchar () 吞掉这个非法值

      • 输入 qw 属于非法吗
        • 是的,需要 getchar () 吞两次
    • 改后代码

      • 图片
      • getchar () 放在 if (!ret) 里更好,只有在读入非法字符时才吞字符

        • 上图的代码:getchar会吞空白符和非法字符

        • 下图的代码:只会吞非法字符

          • 图片
          • 为了演示吞掉的字符没有空白符只有非法字符,这里加了 printf 函数
  • 图片
  • 💡同样,对于 5.if.cpp(有循环,需手动停止),用 > output 将标准输出重定向时,会遇到输出不写入重定向的文件的问题

Ctrl+C:Linux 下默认的中断键,当键入此键时,系统会发送一个中断信号给正在运行的程序和 shell。
Ctrl+D:Linux 下标准输入输出的EOF。在使用标准输入输出的设备中,遇到该符号,程序会认为读到了文件的末尾,因此结束输入或输出。

  • 如果有非法输入导致有大量输出时,Ctrl+C 也可以写入
    • 疑问:这是因为缓存不够,提前强制输出到 output 文件了吗?
      • 是这样理解
    • 疑问:那么这部分写入 output 文件的输出仅仅是缓存溢出的输出,还是 Ctrl+C 前的所有输出呢?
      • 溢出的
  • 代码 5.switch.cpp:为啥输入字母之类的会无限循环?
    • 同 5.if.cpp,见思考点 1

Tips#

  • OJ 做题方式
    • 在 vim 里编写代码
    • cat 显示代码复制出来
    • 提交
  • 经典题:已知年月日判断是否合理
    • 有没有必要用那么多的 if else?
    • 可以用空间换时间:把每月的天数用数组存起来
  • 参考工具书第 6 章 6.4 节

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。