<style> .reveal .slides { text-align: left; font-size: 30px; } </style> # 2025 系學會程式設計補強 WEEK 2 . 2025 / 09 / 22 ---- ## 你會學到什麼? - Introduction to C Language - Variables - Basic Input / Output - Operators - Selection Structures - 一些其他的雜七雜八 - ~~我們課可能會上不完~~ --- # Introduction to C Language 這個章節我沒有要幹古。 ---- <div style="text-align:center;" > ## 一個 C 語言程式碼的結構會長怎樣? ![image](https://hackmd.io/_uploads/SkkTB4hsxe.png =40%x) </div> ---- ```c= [|1-2|4-9|11,18|12-13|15|17] #include <......> #define OOO XXX int global_N = 0; double global_X = 0.0; int foo(......){ ...... } int main(){ int local_N = 0; double local_X = 0.0; ...... return 0; } ``` - 最開始的地方會將該程式所需標頭檔給引入,或是使用 **巨集(define)** 定義一些常數,善用巨集功能能增加程式的可讀性。 - 在 `main` 以外的地方,可以宣告全域變數或其他的函式(未來會教)。一個 C 程式中只能有一個 `main` 函式。 - 在 `main` 或是其他函式中所宣告的變數,稱為區域變數,其他函式不可以用。 - 剩下的部分就是輪到你秀的時候了。 ---- ## 所以,我需要把上面那坨東西背起來嗎? - 很多初學者都覺得寫程式就是要背很多東西。確實不能說你錯。 - 但程式語言是 **人類** 開發的,與其他自然科學不同,很多的邏輯與命名其實是直覺的。 - 你 **不應該** 是像背英文單字一樣把範例程式一字不漏背起來。 - 平時養成寫題目的習慣,久而久之你自然能把常用標頭檔背起來、理解程式的基礎架構。 - 你真的應該背的東西: - C 的 Library 提供許多函式(功能),例如 `abs()`(開絕對值)、`qsort()`(內建的快速排序) 在 `stdlib.h`,`sqrt()`(開根號)在 `math.h`。你可能需要特別記一些好用但不常用到的函式在哪個標頭檔,以備不時之需。 - 期中期末考日期。 - ~~教授的名字。~~ ---- <div style="text-align:center;" > ## C 與 C++ 又差在哪裡? ![image](https://hackmd.io/_uploads/rkZcN43ixl.png =30%x) </div> ---- ## 我們先來看看兩段簡單的程式碼 <div style="display: flex;"> <div style="flex: 0 0 50%"> ```c= // "Hello World" & IO in C #include <stdio.h> int main(){ int n = 0; scanf("%i", &n); printf("Hello World!, %i\n",n); return 0; } ``` </div> <div style="flex: 0 0 60%;"> ```cpp= // "Hello World" & IO in C++ #include <iostream> using namespace std; int main(){ int n = 0; cin >> n; cout << "Hello, World!, " << n << endl; return 0; } ``` </div> </div> ---- <div style="text-align:center"> ## 有發現甚麼不同嗎? ![image](https://hackmd.io/_uploads/BkJl_1ssxg.png =30%x) </div> ---- ## 回到剛剛的程式,發現...... <div style="display: flex;"> <div style="flex: 0 0 50%"> ```c= // "Hello World" & IO in C #include <stdio.h> int main(){ int n = 0; scanf("%i", &n); printf("Hello World!, %i\n",n); return 0; } ``` </div> <div style="flex: 0 0 60%;"> ```cpp= // "Hello World" & IO in C++ #include <iostream> using namespace std; int main(){ int n = 0; cin >> n; cout << "Hello, World!, " << n << endl; return 0; } ``` </div> </div> ---- ## 引入的標頭檔不同 <div style="display: flex;"> <div style="flex: 0 0 50%"> ```c= [2] // "Hello World" & IO in C #include <stdio.h> int main(){ int n = 0; scanf("%i", &n); printf("Hello World!, %i\n",n); return 0; } ``` </div> <div style="flex: 0 0 60%;"> ```cpp= [2] // "Hello World" & IO in C++ #include <iostream> using namespace std; int main(){ int n = 0; cin >> n; cout << "Hello, World!, " << n << endl; return 0; } ``` </div> </div> ---- ## C 語言尚未有"命名空間"的概念 <div style="display: flex;"> <div style="flex: 0 0 50%"> ```c= [3] // "Hello World" & IO in C #include <stdio.h> // ??? int main(){ int n = 0; scanf("%i", &n); printf("Hello World!, %i\n",n); return 0; } ``` </div> <div style="flex: 0 0 60%;"> ```cpp= [3] // "Hello World" & IO in C++ #include <iostream> using namespace std; int main(){ int n = 0; cin >> n; cout << "Hello, World!, " << n << endl; return 0; } ``` </div> </div> ---- ## 輸入 / 輸入方式有顯著不同 <div style="display: flex;"> <div style="flex: 0 0 50%"> ```c= [7,8] // "Hello World" & IO in C #include <stdio.h> int main(){ int n = 0; scanf("%i", &n); printf("Hello World!, %i\n",n); return 0; } ``` </div> <div style="flex: 0 0 60%;"> ```cpp= [7,8] // "Hello World" & IO in C++ #include <iostream> using namespace std; int main(){ int n = 0; cin >> n; cout << "Hello, World!, " << n << endl; return 0; } ``` </div> </div> <div style="font-size: 25px;"> - 在 C 中,`scanf` 與 `printf` 是由標頭檔 `stdio.h` 提供的一種輸出入 **函式**,一個是透過標準輸入讀取格式化資料,另一個則是將資料格式化之後輸出到標準輸出。 - 在 C++ 中,`cin` 與 `cout` 是由標頭檔 `iostream` 提供、在 Standard Library 下的 **串流物件**,透過重載 `>>` 與 `<<` 運算子來代表輸入串流 (istream) 與輸出串流 (ostream) 來現更為方便的格式化輸出輸入控制。 - ~~endl 是啥有點複雜,你們未來會學到,反正他跟換行的效果類似,但實際做了很多事。~~ </div> ---- <div style="text-align:center;"> ## 會不會太複雜? ![image](https://hackmd.io/_uploads/HyTEJboslx.png =60%x) ~~沒關係,我們今天也不會教這麼難~~ </div> ---- ## 說了這麼多,我想告訴你什麼? - C++ 雖然本身經過無數次改進與迭代,但其在基本語法(迴圈、變數的使用與賦值等等)基本上還是與 C 語言高度的相同。 - 即便到了現今,許多語言的語法依然與 C / C++ 有著極高的相似性。 - 如果你能夠在大一上就學好程式設計一,這會對你在資工系未來四年有著莫大的幫助。 - さあ~始めましょう! --- # Variables 在這個小節中你能學會 C 語言的變數使用與宣告。 ---- ## 在 C 語言中,變數大體來說可以分為: - 整數(***int***eger) - 浮點數(***float***ing-point) - 字元(***char***) ---- ## 整數 Integer - 老師上課時可能會介紹很多整數的類型,但其實很多都是另外一種型態的等價形式而已。 - 今天上課只會說明你最常見到的類型,通常你也只需要知道這些。 - 請注意,某些型態可能會因編譯器的不同而有不同的位元數,進而導致其值域的有所增減。 - 簡報中所有依據都是 GNU GCC 為標準,若你的編譯器不是採用這個標準,你需要自己去看說明文件確定值域範圍。 ---- ### 有號整數(有正負之分) | 型態 | 值域 | | ----------- | -------------------------------------------------------- | | `short` | $-2^{15}$ ~ $2^{15}-1$ ($-32768$ ~ $32767$) | | `int` | $-2^{31}$ ~ $2^{31}-1$ (約等於 $-10^9$ ~ $10^9$) | | `long long` | $-2^{63}$ ~ $2^{63}-1$ (約等於 $-10^{18}$ ~ $10^{18}$) | - 請注意, `int` 在某些編譯器下可能只會提供 $16$ 個位元,這時候你可能需要使用 `long` 或是 `long int` 來確保你的值域不受影響。 ---- ### 無號整數(僅有正數) | 型態 | 值域 | | ----------- | -------------------------------------------------------- | | `unsigned short` | $0$ ~ $2^{16}-1$ ($0$ ~ $65535$) | | `unsigned int` | $0$ ~ $2^{32}-1$ (約等於 $0$ ~ $10^9$) | | `unsigned long long` | $0$ ~ $2^{64}-1$ (約等於 $0$ ~ $10^{19}$) | ---- ## 浮點數 | 型態 | 有效位數 | 值域 | | -------- | ------------------- | ------------------------------------------------- | | `float` | 小數點後 6 ~ 7 位 | 約 $1.1 \times 10^{-38}$ ~ $3.4 \times 10^{38}$ | | `double` | 小數點後 15 ~ 16 位 | 約 $2.2 \times 10^{-308}$ ~ $1.7 \times 10^{308}$ | - 電腦儲存浮點數是以類似「科學記號」的方式存於記憶體之中。 - 這導致了電腦在計算浮點數時會產生誤差。 - 具體怎麼存的、以及為什麼會有誤差你們計概會學到。 - 如何避免誤差? - 只要需要用到浮點數,請一律用 `double` 而不是 `float`。 - 任何的條件式都 **不能用** 浮點數做直接判斷(比如說,拿兩數平方後的結果去比較,而不是拿兩數開完根號後之值去比較) ---- ## 字元 `char`: char 的背後其實也是整數(8 位元)在處理,只是之後要輸出時會通過一個名為 [ASCII Code](https://www.geeksforgeeks.org/dsa/ascii-table/) 的表格以查表的形式將數字轉換為特定字符輸出。 - 請注意,C 語言是沒有 `string` (字串)這個東西的(C++才有)。C 語言在處理字串時使用的其實是**字元陣列**進行處理。至於字串怎麼使用、怎麼儲存,之後陣列的章節才會提及,今天只會教各位怎麼輸出字串。 ---- ## 布林(bool) - 一個你需要知道,很重要,但 C 語言其實沒有的東西(C++ 才有)。 - 簡單的來說就是 $1$ 與 $0$(`true` 與 `false`)。 - C 語言其實沒有實作真正意義上的 bool 型態。在 C / C++ 中,你可以拿任何一個整數型態當作是 bool 型態,其中 $0$ 代表 `false`,非 $0$ 代表 `true`。 - C 語言有提供一個名為 `stdbool.h` 讓你能夠使用 `bool` 或 `_Bool` 型態創建變數,但背後依然是使用整數在處理。(C23 起無須額外引入此標頭) ---- ## 使用與創建變數 - 變數的創建:`[變數型態] [變數名稱];` ```cpp int homo; long long year, n; double pi; char alphabet; ``` - 如果你今天要創建兩個(或以上)相同型態的變數時,你可以有兩種做法 ```cpp // 方法 1:分開寫。 int n; int k; int m; // 方法 2:寫在同一行,每個變數以半形逗號(,)分隔。 int n, k, m; ``` ---- ## 使用與創建變數 - 如何命名變數? - C 語言有一種東西叫[保留字](https://www.ibm.com/docs/zh-tw/debug-for-zos/16.0.x?topic=programs-c-reserved-keywords),你沒辦法將這些關鍵字設為變數名稱或是函式名稱。 - 變數名稱開頭不能是數字或底線(_) - 只能以大小寫英文字母、阿拉伯數字與底線(_)作為變數名稱。 - 嚴格區分大小寫(比如 `int Owo` 與 `int owo` 就是兩個不同的變數) ---- ## 使用與創建變數 - 變數在命名時盡量使用有意義的名稱、或是題目給定的名稱來命名,這樣能夠大大增加程式碼的可讀性與可維護性,例如: - 題目:第一個輸入為整數 $N$,表測資的數量... $\rightarrow$ `int N;` - 要將兩數加總存入一個新變數時 $\rightarrow$ `int sum;` - 你不應該: - 隨便亂取變數名稱,例如將變數取作 `int owo;`、`char uwu;` 等。 - 一個變數名稱超過 50 個字符。 ---- ## 使用與創建變數 - 養成好習慣:創建即賦值 - 在 C / C++ 中,你創建完變數後系統只會將記憶體中的一個區塊讓給你,但不會清理。 - 意思是說,若你創建完沒有立即賦值,那變數的值會由記憶體空間中尚未清除的值做決定,而這個值是多少沒有人知道 ─ 此即垃圾值(Garbage Value) - 這會有什麼問題? - 如果你今天有一個變數 n 預計要給使用者輸入,並作為某個 for 迴圈的終止值。 - 但你創建完變數後忘記 `scanf` 了 - 導致的結果:你的程式每次啟動時迴圈的執行次數都不一樣。 - 如果今天程式較小可能還好,但若是動輒 100 行起跳的進階演算法題目,或是大型專案,你可能就很難找到問題。 ---- ## 使用與創建變數 - 回到剛剛的片段程式碼,你可以在創建時就為每個變數賦予一個指定的值 ```cpp int homo = 114514; long long year = 2025, n = 0; double pi = 3.14; char alphabet = 'A'; ``` 如果你不知道一個變數的初值該設為什麼,設為 $0$ 就對了。 - 你也可以先創建變數,並在事後賦予其值 ```cpp int n; // 先創建一個型態為 int 的變數 n n = 1; // 將變數 n 的值設為 1 ``` - 更好的做法 ```cpp int n = 0, k = 0; // 創建兩個變數 n k,並將初值設為 0 // 讓使用者輸入 n ... k = 1; // 將 k 的值設為 1 ``` --- # Basic Input / Output 在這個小節中你能學會使用 `printf` 與 `scanf`。 ---- ## 前情提要 - 這個章節就只會講 `scanf` 與 `printf` 這兩種 C 語言的格式化輸入輸出。 - `puts()` 、 `fputs()` 、 `getchar()` 之類更底層的輸入輸出函式在本章節不會提,有興趣請自行至 cppreference 了解。 - C++ 的 `cin` 、 `cout` 本章節也不會提,未來大一下會學到;更進階的輸入輸出方法(如字串串流)之類的這裡也不會提。 - 如果對其他輸入輸出方法有興趣請自行搜尋,也可以找我討論。 ---- ## 我們先來看一段簡單的片段程式碼 ```c= int n = 0; scanf("%i", &n); printf("The Number is : %i\n", n); ``` - 這段程式碼的功能是要求使用者輸入一個整數 $n$,並輸出 `The Number is : n`(n 為使用者輸入之整數) ---- ## C 語言如何實現格式化輸入輸出? - 在 C 語言中,即使我們在宣告變數時就已經一併指定了其型態,我們還是要在輸入輸出時重新告訴 `printf` 與 `scanf` 這個變數是什麼型態。 - 如果我們想要輸出某一個變數時,我們通常會使用類似 `%d` 之類的語法,並在 `printf`後面放上該變數。 - 這就是 C 語言實現格式化輸入輸出的原理 ─ 格式控制符(format control string) - 通過設定 `printf`、`scanf`內的字串與格式控制符,我們便可以輕易的將欲輸出的字串與變數進行格式化後輸出;或甚至可以指定使用者的輸入格式。 ---- ## 常見的格式控制符 <div style="font-size: 25px;"> | 格式控制符 | 適用的型態 | | ------------------ | ----------------------------------- | | `%d` 或是 `%i` | `int` | | `%lli` 或是 `%lld` | `long long` | | `%f` | `float` | | `%lf` | `double` | | `%lu` | `unsigned int` 或是 `unsigned long` | | `%llu` | `unsigned long long` | | `%o` | 將整數型態轉換為八進制輸出 | | `%x` 或是 `%X` | 將整數型態轉換為十六進制輸出 | | `%c` | `char` | | `%s` | `char[]`(字串 / 字元陣列) | | `%%` | 輸出 '%' 這個字符 | | `%p` | 輸出指標變數(記憶體位置) | </div> ---- ## 回到剛剛的程式碼片段 ```c= [3] int n = 0; scanf("%i", &n); printf("The Number is : %i\n", n); ``` - 現在,我們可以知道 `printf` 中的 `%i` 其實就是所謂的格式控制符,其告訴 printf 函式那個位置應該會有一個 `int` 型態的變數。 - 而我們在 `printf` 字串結束後的位置放上了一個 `int` 型態的變數 `n`,告訴 `printf` 那個格式控制符應該要輸出 `n` 的值。 ---- ## `printf` 使用範例 ```c= long long n = 0; int k = 1, m = 2; double i = 3.0; char j = 'A'; printf("The long long integer is %lli ", n); printf("and the two int integer is %i and %i.\n", k, m); printf("Finally, the double variables is %lf\n", i); printf("%c", j); ``` ```text The long long integer is 0 and the two int integer is 1 and 2. Finally, the double variables is 3.0 A ``` ---- ## 進階格式化輸出 我們先看一個程式碼片段 ```c= int n = 114514; printf("%i", n); // -> 輸出 "114514" ``` 如果你想要靠右對齊,你可以在 `%` 符號的後方加上一個數字: ```c= printf("%8i", n); // -> 輸出 " 114514" ``` 這會保證輸出的結果至少佔 $8$ 個字元,不足的會在數字前方補空白鍵。 反之,你也可以: ```c= printf("%-8i", n); // -> 輸出 "114514 " ``` 改將空白鍵往數字的後方補。 或者改將不足的部分補上前導 $0$ ```c= printf("%08i", n); // -> 輸出 "00114514" ``` ---- ## 進階格式化輸出 我們再來看一個程式碼片段 ```c= double pi = 3.1415926535; printf("%lf", pi); // -> 輸出 "3.141592" ``` 小數的輸出預設會輸出到小數點後六位。 你也可以在 `%`後方加上 `.[數字]` 來指定要輸出幾位小數(四捨五入)。 ```c= printf("%.2lf", pi); // -> 輸出 "3.14" ``` 也可以做到跟剛剛整數一樣的事情,在前方或後方補上空白,或者在前方補上前導 $0$。 ```c= printf("%6.2lf", pi); // -> 輸出 " 3.14" printf("%-6.2lf", pi); // -> 輸出 "3.14 " printf("%06.2lf", pi); // -> 輸出 "003.14" ``` ---- ## 我們再回到剛剛的範例 ```c= [2] int n = 0; scanf("%i", &n); printf("The Number is : %i\n", n); ``` - `scanf` 的情況又有那麼億點點複雜 ![image alt](https://i.meee.com.tw/3c0RBMC.png =40%x) ---- <div style="text-align:center;"> ## 有發現哪裡不一樣嗎? ![image alt](https://i.meee.com.tw/X1z3ZTP.png =50%x) </div> ---- ## 最明顯的不同 — 參數形式 ```c= scanf("%i", &n); // -> &n !!!!!! printf("The Number is : %i\n", n); // -> n ??? ``` - 發現了嗎? 在 `scanf` 後方的變數 `n`的前面加了一個 `&` 符號。 - 這其實會牽扯到一點指標的概念。簡單的來說,`scanf`為了讓將使用者的輸入放入你所指定的變數中,你需要告訴 `scanf` 該變數的記憶體位置是多少,這時候我們就需要再變數的前面放上 `&` 符號。 - 而在 `printf` 中,其實你只需要知道該變數的 **值** 就好,,而不用真正到變數所在的記憶體位置去爆破他,所以我們並不用加上 `&` 的符號。 ---- ## 最明顯的不同 — 參數形式 ```c= scanf("%i", &n); // -> &n !!!!!! printf("The Number is : %i\n", n); // -> n ??? ``` - 當然,凡事都有例外。 - 在未來你學到指標的時候,如果某一個變數中存的資料已經是記憶體位置了(參考),那你在呼叫 `scanf` 時,你自然不需要額外加上 `&` 符號。 - 反之也亦同,如果你真的真的需要將變數所在的記憶體位置打印出來時,那你在呼叫 `printf` 時就需要將變數加上 `&` 符號。 - 但在學到指標前其實你不用關注這麼多,現在你只需要記得 `scanf` 後的變數一定有 `&` 的符號就好。 ---- ## `scanf` 使用範例 ```c= long long n; int k, m; double i; char j; scanf("%lli", &n); scanf("%i, %i", &k, &m); // ?? scanf("%lf", &i); scanf("%c", &j); ``` ---- ## `scanf` 的格式化字串行為 注意到剛剛程式碼的第五行 ```c= scanf("%i, %i", &k, &m); // ?? ``` - 這樣寫,就代表我們要求使用者必須以 `[整數], [整數]` 的形式來輸入資料,比如說 `10, 20`。 - `scanf` 會自動忽略任意數量的空白字元(空白鍵、\t、\n),所以無論你輸入的是 `10, 20` 還是 `10,   20`,都是合法的輸入。 - 其餘的非空白字元,會被當作是「固定字元」,需要原封不動地出現在使用者的輸入字串中 ---- ## `scanf` 的格式化字串行為 注意到剛剛程式碼的第五行 ```c= scanf("%i, %i", &k, &m); // ?? ``` - 如果使用者沒有按照格式輸入呢? - 當 `scanf` 遇到不符合格式的地方時,就會停下,將之後的輸入暫時留在一個緩衝的空間中。 - 已經讀入數值的變數會保留,剩餘的變數不會被修改 - 剩下留在緩衝空間中的資料會被下一個 `scanf` 讀到。 - 如果你不希望下一個`scanf`受到緩衝空間內的值所影響,你需要手動將緩衝空間清除。 - `scanf`、`printf` 也是有回傳值的,你可以透過他們的回傳值來判斷是否成功輸入 / 輸出。 ---- ## 進階格式化輸入 如果今天你想跳過某個輸入值,你可以在 `%` 的後方加上 `*`: ``` int a = 0, c = 0; scanf("%i %*i %i", &a, &c); // 第二輸入的數字會被 scanf 拋棄。 ``` 限定可輸入字元的集合(類似正規表示式): ``` char alphabet; scanf("%[a-z], &alpabet") // 限定只有小寫字母 ``` 這種方法還有很多其他種限定方法,但要拿字串舉例才會比較好理解。 礙於今天沒有教字串,所以這裡就多提,未來上字串會在額外說明。 --- # Operators 在這個小節中你能學會基本的 C 語言運算子。 ---- ## 什麼是運算子? - 程式語言與計算機最初開發的目的就是用於數學計算 - 所以除了輸入輸出與變數之外,我們當然還需要設計一些數學符號來讓電腦進行各種運算,這就是所謂的運算子 - 在這個章節中,你會學到以下幾個運算子 - 算術運算子 (Arithmetic Operators) - 比較運算子(Relational Operators) - 邏輯運算子(Logical Operators) - 位元運算子(Bitwise Operators) - 賦值運算子(Assignment Operators) - 條件運算子(三元運算子)(Conditional Operator) ---- ## 算術運算子 - 顧名思義,讓你進行各種數學計算的。 - 除熟悉的基本加減乘除以外, C 語言還提供了許多好用的運算子。 | 符號 | 說明 | 使用範例 | 備註 | | ---- | -------- | ---------------- | --------------------------------- | | `+` | 算術加法 | `a+b` | 亦可以當成正號使用,如 `a = +a` | | `-` | 算術減法 | `a-b` | 亦可以當成負號使用,如 `a = -a` | | `*` | 算術乘法 | `a*b` | - | | `/` | 算術除法 | `a/b` | 若除數為 $0$ 會發生錯誤使程式中斷 | | `%` | 模運算 | `a%b` | 若模數為 $0$ 會發生錯誤使程式中斷 | | `++` | 自身+1 | `a++` 或者 `++a` | - | | `--` | 自身-1 | `a--` 或者 `--a` | - | ---- ## 算術運算子之你一定會踩的雷 - 算術運算子的優先級與數學相同,先乘、除、模後加減,亦可以用括號 `()` 來優先計算。 - 模運算 `%` 只能用在整數,**浮點數不能用**! - 若整數跟浮點數一起計算時,最後的運算結果會是浮點數。 - 若「整數 / 整數」或 「整數 % 整數」,當其中一個整數是負數時,從 C99 起有明確定義: - 涉及負數的除法,採用 **截斷到 0**(捨去分數部分),而不會向上或向下取整,例如:`-9/7`結果為 `-1`、`7/-3` 結果為 `-2` - 涉及負數的模運算,比如 `a % b`,其結果的正負由 `a` 決定。例如`-9%7`為`-2`、`9%-7`為`2`。 - 對於遵守更早期標準的編譯器,這是一個未定義行為,會發生 [Undefined Behavior](https://en.cppreference.com/w/cpp/language/ub.html)。 ---- ## 算術運算子之你一定會踩的雷 - `1/2` 結果不是 `0.5`...? - 當進行「整數 / 整數」時,C 語言的除法會無條件捨去小數部分。因為 1 與 2 都是整數,所以運算的結果也必然是整數,小數部分就會被無條件捨去掉了。 - 若結果想要得到 `0.5` ,你需要將除數或者被除數擇一換成浮點數。 ---- ## 算術運算子之你一定會踩的雷 - 加加、減減與其先後順序 - 若你想對一個變數 `a`進行運算或是丟給某一個函式,`a++` 代表先將 `a` 的原始值回傳後再對其加值;`++a`則代表先將 `a` 加值後再回傳其值。 - 大家可以試著執行看看這個簡單的範例程式,驗證看看結果。 ```c= #include <stdio.h> int main() { int i = 0; printf("%d\n", i); // Output: 0 printf("%d\n", i++); // Output: 0 printf("%d\n", i); // Output: 1 printf("%d\n", ++i); // Output: 2 printf("%d\n", i); // Output: 2 } ``` ---- ## 算術運算子之你一定會踩的雷 - 加加、減減與其先後順序 - 既然前一頁已經介紹過了先`++/--`與後`++/--`的差別 - 試著用五秒鐘思考一下這個問題,若 `a` 的初值為 `0`,那`a++ + ++a` 的結果會是多少? <!-- .element: class="fragment" data-fragment-index="0" --> - 答案是 $2$ ?是 $3$? <!-- .element: class="fragment" data-fragment-index="1" --> - 都不是,正確答案是 **沒有答案**。 <!-- .element: class="fragment" data-fragment-index="2" --> - 這其實是一個典型的未定義行為([Undefined Behavior](https://en.cppreference.com/w/cpp/language/ub.html)) - 在 C 語言中,並沒有規範 ++ 與 -- 實際上應該在哪個步驟去被執行。 - 這會導致不同編譯器可能對這種操作產生不同的解讀,造成不同的結果。 - 我們在撰寫程式時,應該極力避免這種表達式被寫出來。 <!-- .element: class="fragment" data-fragment-index="3" --> ---- ## 比較運算子 - 比較運算子能夠判斷兩個變數之間的關係,並回傳 `true` 或是 `false` | 符號 | 說明 | 使用範例 | | ---- | -------- | -------- | | `==` | 等於 | `a == b` | | `!=` | 不等於 | `a != b` | | `>` | 大於 | `a > b` | | `>=` | 大於等於 | `a >= b` | | `<` | 小於 | `a < b` | | `<=` | 小於等於 | `a <= b` | ---- ## 邏輯運算子 - 邏輯運算子能夠判斷兩個條件之間的關係。 | 符號 | 說明 | | --------------- | ---- | | `&&` 或 `and` | 且運算(AND),兩個條件都要同時成立才為 `true` | | `\|\|` 或 `or` | 或運算(OR),兩個條件只要其一成立就為 `true` | | `!` | 反運算(NOT),`true` 變 `false`、`false` 變 `true` | - 大家可以執行看看下面這個範例程式看看結果 ```c= #include <stdio.h> int main() { printf("%d\n", 1 + 1 == 2 && 3 > 0); // Output: 1 printf("%d\n", 9 < 0 || 10 > 9); // Output: 1 printf("%d\n", !1); // Output: 0 printf("%d\n", !0); // Output: 1 } ``` ---- ## 位元運算子 - 位元運算會將兩個變數轉換成二進位後在進行運算。 <div style="font-size:21px;"> | 符號 | 名稱 | 說明 | 使用範例 | | ---- | ----------- | ----------------------------------------------- | -------- | | `&` | AND(與) | 逐位進行 AND 運算(注意不要跟邏輯運算的且搞混) | `a&b` | | `\|` | OR(或) | 逐位進行 AND 運算(注意不要跟邏輯運算的或搞混) | `a\|b` | | `^` | XOR(異或) | 逐位進行 XOR 運算(不是開次方!) | `a^b` | | `~` | NOT(非) | 逐位進行 NOT 運算,相當於對二進位取一補數。 | `~a` | | `<<` | 左移 | 將所有位元向左移 $n$ 位,相當於乘以 $2^n$ | `a<<1`(等價`a*2`) | | `>>` | 右移 | 將所有位元向右移 $n$ 位,相當於除以 $2^n$ | `a<<3`(等價`a/8`) | </div> ---- ## 賦值運算子 - C 語言提供了賦值運算子,允許你將某些操作簡化成二至三個運算子。 - 基礎型態 | 符號 | 說明 | 使用範例 | | ---- | ---------------------- | ---------------------- | | `=` | 將某一個變數賦予特定值 | `x = 666` | - 注意**不是邏輯上的等於**,邏輯上的等於是兩個等於`==`,不要誤用。 - ==(這個是表情符號uwu) ---- ## 賦值運算子 - 衍生型態 | 符號 | 等價形式 | 使用範例 | | ----- | ------------ | -------- | | `+=` | `x = x + y` | `x += y` | | `-=` | `x = x - y` | `x -= y` | | `*=` | `x = x * y` | `x *= y` | | `/=` | `x = x / y` | `x /= y` | | `%=` | `x = x % y` | `x %= y` | | `&=` | `x = x & y` | `x &= y` | | `\|=` | `x = x \| y` | `x \|= y` | | `^=` | `x = x ^ y` | `x ^= y` | | `<<=` | `x = x << y` | `x <<= y` | | `>>=` | `x = x >> y` | `x >>= y` | ---- ## 條件運算子 - 條件運算子跟我們等等要講的選擇結構很相似。 - 相較於 `if-else` 結構其實不是這麼容易閱讀,因此大部分情況不常用。 - 與`if-else` 結構幾乎沒有速度上的差異。 - 條件運算子的語法如下: ```c= [某條件] ? [如果條件成立,做 A] : [反之,做 B] ``` - 其等價於 ```c= if (某條件){ 做A; }else{ 做B; } ``` - 一個簡單的範例 ```c= int n = 1; int x = n > 9 ? 1 : 0; printf("%i", x) // Output: 0 ``` ---- ## 運算子們的優先順序 <div style="font-size:20px"> - 請注意,本表只列出目前有教到的運算子。 - 運算子的結合性:當多個相同優先級的運算子同時出現時,要從左邊算還是從右邊算。 | 優先級(越小越高) | 運算子 | 結合性 | | ------------------ | ------------------------------------------------------------------ | ----------- | | 1 | `()`、函式呼叫、`++`(後置)、`--` (後置) | 左 → 右 | | 2 | `++`(前置)、`--` (前置) 、`+`、`-`、`!`、`~`、強制轉型 | **右 → 左** | | 3 | `*`、`/`、`%` | 左 → 右 | | 4 | `+`、`-` | 左 → 右 | | 5 | `<<`、>>` | 左 → 右 | | 6 | `<`、`<=`、`>`、`>=` | 左 → 右 | | 7 | `==`、`!=` | 左 → 右 | | 8 | `&` | 左 → 右 | | 9 | `^` | 左 → 右 | | 10 | `\|` | 左 → 右 | | 11 | `&&` | 左 → 右 | | 12 | `\|\|` | 左 → 右 | | 13 | `? :`(條件運算子) | **右 → 左** | | 14 | `=`、`+=`、`-=`、`*=`、`/=`、`%=`、`&=`、`\|=`、`^=`、`<<=`、`>>=` | **右 → 左** | </div> --- # Selection Structures 在這個小節中你能學會選擇與巢狀選擇結構。 ---- ## C 語言中,提供了三種實現條件選擇語法: - 條件運算子(三元運算子) $\rightarrow$ 已經教過了ㄌ - `if-else` 結構 - `switch` 結構(不是任天堂那個 = =) ---- ## `if-else` 結構 - 我們先來看一段簡單的中文: **如果今天下雨,我就翹課;否則,我就去上課。** <!-- .element: class="fragment" data-fragment-index="0" --> - 好了,你已經會 `if-else` 結構了(?)。 <!-- .element: class="fragment" data-fragment-index="1" --> ---- ## `if-else` 結構 - 沒錯,`if-else` 結構就是這麼簡單。 - 如果我們將剛才的句子轉成程式碼,大概會長這樣: ```c= if ( today.weather() == "rainy" ){ self.sleep(); }else{ self.goToSchool(); } ``` - `if-else` 的語法: ```c= if (條件) { 如果條件成立,做 A。 }else{ 如果條件不成立,做 B。 } ``` ---- ## `if-else` 結構 - 我們來看一個簡單的範例:判斷數字的奇偶性。 ```c= #include <stdio.h> int main(){ int n = 0; scanf("%i", &n); if ( n%2 == 0 ){ printf("%i is even.\n", n); }else{ printf("%i is odd.\n", n); } return 0; } ``` ---- ## `if-else` 結構 - 如果你還有更多條件想判斷怎麼辦? - 你可以使用 `else if` 來判斷更多條件。 - 一個 `if-else` 結構可以有不只一個的 `else if`,但一定只能有一個 `if` 與一個 `else`。 ```c= if (條件A) { 如果條件A成立,做事情 i。 }else if(條件 B){ 如果條件B成立,做事情 j。 }else{ 如果都不成立,做事情 k。 } ``` ---- ## `if-else` 結構 <div style="font-size:25px;"> - 我們來看一個簡單的範例:依照分數將劃分等第。 ```c= [8-12] #include <stdio.h> int main() { int score; printf("Your Score: "); scanf("%i", &score); if (score >= 90) printf("A\n"); else if (score >= 80) printf("B\n"); else if (score >= 70) printf("C\n"); else if (score >= 60) printf("D\n"); else printf("E"); // score less then 60 return 0; } ``` - 發現了嗎?我在這裡似乎沒有使用大括號 `{}` 將每個選擇結構包起來。 - 這其實是 C / C++ 提供給程式設計師們的一個偷懶小撇步,當你的 `if-else`區塊內若只有一行程式碼,那麼這時在使用大括號將就會顯得程式相對冗長。 - 因此,若你的選擇結構你只有一行時,C / C++ 是允許你可以不使用括號的。 </div> ---- ## `if-else` 結構 - 如果你想在一個條件內在判斷另一個條件時該怎麼辦呢? - 這時,你的直覺想法應該會是在某個條件中在寫一個 `if-else`。 - 沒錯,你是對的,恭喜你學會了巢狀選擇結構! ```c= if (條件A) { //如果條件A成立 if (條件B){ 如果條件A成立且條件B成立,做事情 i。 }else{ 如果條件A成立但條件B不成立,做事情 j。 } }else{ 如果條件A不成立,做事情 k。 } ``` ---- ## `if-else` 結構 - 我們現在為前面的範例加上一點小小的變化: ```c= #include <stdio.h> int main(){ int n = 0; scanf("%i", &n); if ( n > 0 ){ if ( n%2 == 0 ){ printf("%i is postive, and also even.\n", n); }else{ printf("%i is postive, but odd.\n", n); } }else{ printf("%i is NEGATIVE!!!\n", n); } return 0; } ``` - 現在,你應該已經差不多熟悉 `if-else` 結構了, ---- ## `switch` 結構 - switch 是一個類似`if-else`的結構。但不同的是,他從頭到尾只會判斷同一個對象,且只要滿足某個條件,就會一直做下去。 - 在某些情況下,使用 `switch` 結構會比使用 `if-else` 結構來的簡潔且易讀。 - switch 語法大概長這樣: ```c= switch(條件){ case 判斷條件1: ...... case 判斷條件2: ...... case 判斷條件3: ...... case 判斷條件4: ...... default: ...... } ``` - 可能還是有點抽象跟難懂,我們直接看範例 ---- ## `switch` 結構 <div style="font-size:25px"> - 這是一個根據分數分配禮物的片段程式。 - 如果你的分數低於 60 分,你將會被請出教室。 - 如果你的分數介於 60 ~ 69分,你可以獲得禮物A。 - 如果你的分數介於 70 ~ 79分,你可以獲得禮物A跟禮物B。 - 以此類推。這個程式將輸出你能夠獲得甚麼禮物 </div> ```c= int score; scanf("%d", &score); printf("Your gift:\n"); switch (score / 10) { case 10: // 100 分 printf("- Gift E\n"); case 9: // 90~99 分 printf("- Gift D\n"); case 8: // 80~89 分 printf("- Gift C\n"); case 7: // 70~79 分 printf("- Gift B\n"); case 6: // 60~69 分 printf("- Gift A\n"); break; // 讓有禮物的人不要輸出 default 訊息 default: // 0~59 分 printf("GET OUT!\n"); } ``` ---- ## `switch` 結構 <div style="font-size:25px"> - 如果我想要讓 `switch` 跟 `if-else` 一樣不會繼續往下執行該怎麼辦? - 這時候,你只需要為每個 case 底下都加上 `break` 就可以了,如同剛剛的 L16。 - 還是剛剛的程式碼範例,不過為每個 case 都加上了 `break` , 讓每個人最多只能拿一個禮物。 </div> ```c= int score; scanf("%d", &score); printf("Your gift:\n"); switch (score / 10) { case 10: // 100 分 printf("- Gift E\n"); break; case 9: // 90~99 分 printf("- Gift D\n"); break; case 8: // 80~89 分 printf("- Gift C\n"); break; case 7: // 70~79 分 printf("- Gift B\n"); break; case 6: // 60~69 分 printf("- Gift A\n"); break; // 讓有禮物的人不要輸出 default 訊息 default: // 0~59 分 printf("GET OUT!\n"); } ``` ---- ## `switch` 結構 - 在某些時候,我們可能想要判斷某一個數字的值是不是存在於某個區間中。 - 如果按照標準的寫法,我們的程式可能會看起來很冗長。 - 比如說,如果我們想要判斷一個變數 $n$ 是不是存在於 $10 \le n \le 20$ 這個區間中,我們應該會寫成 `n >= 10 && n <= 20`。 - 其實,我們有一種能夠讓程式碼看起來更優雅的寫法: [range case syntax](https://www.geeksforgeeks.org/cpp/using-range-switch-case-cc/) - 我們可以在 `switch` 結構中使用 `a ... b` 這樣的語法,來達到一樣的效果(請注意其中 `b`之值必大於`a`)。 - 這樣的寫法雖然不在 C 語言官方的標準內,但受主流編譯器 GNU GCC 與 Clang 的支援。 ---- ## `switch` 結構 回到我們最一開始以分數劃分等第的範例,其實完全可以用 `switch` 結構和 range case syntax 達成更簡潔且可讀性更高的寫法。 ```c= [8-23] #include <stdio.h> int main() { int score; printf("Your Score: "); scanf("%i", &score); switch (score){ case 90 ... 100: printf("A\n"); break; case 80 ... 89: printf("B\n"); break; case 70 ... 79: printf("C\n"); break; case 60 ... 69: printf("D\n"); break; default: printf("E"); } return 0; } ``` ---- <div style="text-align:center; font-size:80px;"> **沒了,就醬** ![image](https://shoplineimg.com/62cb90c69730d2004d2343f2/66d3e615ff614c001c6c20ee/750x.jpeg =60%x) </div> --- <div style="text-align:center;"> ## 今天的課程就到這裡 :D 因為你家講師連講義都差點編不完,根本沒時間找題目。 ![image](https://hackmd.io/_uploads/SyRjk0pill.png) 所以今天沒有題目,但還是請大家回去多多練習老師上課出的題目。 ![image](https://i.ytimg.com/vi/nwS6nqc20UM/maxresdefault.jpg =60%x) </div> ---- ## 一些題目資源 除了上課的題目與我們放在 vjudge 上的題目之外,你也可以到這裡找題目來寫 - [ZeroJudge](https://zerojudge.tw/) - [Uva Online Judge](https://onlinejudge.org/) - [TIOJ](https://tioj.ck.tp.edu.tw/) - [CSES](https://cses.fi/) - [CodeForces](https://codeforces.com/) - [AtCoder](https://atcoder.jp/) ---- # QA 時間 問都問,都給問(限程式題目)。
{"title":"2025 系學會程式設計補強 week2","description":"qwertyuiop","slideOptions":"{\"transition\":\"slide\"}","contributors":"[{\"id\":\"3d52dec2-fae6-4cd0-b9d8-07145517e0ba\",\"add\":36099,\"del\":9568,\"latestUpdatedAt\":1758507099788}]"}
    440 views