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 節

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。