# CPP Lecture3 ![upload_0673f86c099c4a1a5741d9a110bedecf](https://hackmd.io/_uploads/rJaDXxjo1x.png) - __學習++陣列++/++字串++、++函式與遞迴++、++const 保留字++__ - __`string` 和 `char[]` 怎麼用? 差別在哪?__ - __函式如何宣告、使用? 遞迴又是什麼?__ - __保留字是什麼? `const` 有什麼功用?__ --- #### 📌 陣列(Array) <br/> 1. **陣列** 是一組++相同型別++的++變數集合++。 2. 每個元素在記憶體中是++連續存放++的, 因此可以快速存取特定索引位置的元素。 3. 陣列的++大小必須固定++,不能動態擴展。 4. 陣列中元素++索引從 0 開始++ (index = 0)。 > ➣ **如果使用 `STL` 提供的 `<vector>` 結構 是支援動態分配的** --- ### 📜 陣列圖解 ![Arrays-in-C](https://hackmd.io/_uploads/S1z7Wj-6Jl.png) --- #### 🛈 陣列宣告及初始化 <br/> - 以`整數型別`來宣告/初始化陣列: ```cpp[] int arr1[5]; // 宣告一個大小 5 的整數陣列 元素皆為未初始化的「隨機值」 int arr2[6] = {0, 1, 2, 3, 4, 5}; // 含完整初始化 int arr3[6] = {0, 1, 2, 3}; // 不完整初始化,沒初始化到的元素值? int arr4[] = {10, 20, 30, 40}; // 不指定大小,C++ 自動推斷大小為 4 ``` - 存取(取值)、更動陣列元素: ```cpp[] int arr[3] = {5, 10, 15}; cout << arr[0] << " "; // 陣列 arr 的第0個元素為 5 arr[1] = 1; cout << arr[1] << " "; // 陣列 arr 的第1個元素變為 1 cout << arr[2] << " "; // 陣列 arr 的第2 個元素還是 15 cout << arr[3] << " "; // 輸出 ??? (編譯器不會報錯喔!) cout << endl; ``` --- #### ➰ 迭代陣列中元素 - 範圍內取值 <br/> - ++範圍內++迭代: ```cpp[] int arr[5] = {1, 2, 3, 4, 5}; for (int i = 0; i < 5; i++) cout << arr[i] << " "; cout << endl; // 輸出:1 2 3 4 5 ``` ```cpp[] int arr[5] = {1, 2, 3}; for (int i = 0; i < 5; i++) cout << arr[i] << " "; cout << endl; // 輸出:1 2 3 0 0 ``` ```cpp[] int arr[5]; for (int i = 0; i < 5; i++) cout << arr[i] << " "; cout << endl; // 輸出:? ? ? ? 0 ``` 為何會有這樣的現象呢(有零有亂數)❓ --- #### ➰ 迭代陣列中元素 - 範圍外取值 <br/> - 迭代到++範圍外++: ```cpp[] int arr[5] = {1, 2, 3, 4, 5}; for (int i = 0; i < 8; i++) cout << arr[i] << " "; cout << endl; // 輸出:1 2 3 4 5 0 ? ? ``` ```cpp[] int arr[5] = {1, 2, 3}; for (int i = 0; i < 8; i++) cout << arr[i] << " "; cout << endl; // 輸出:1 2 3 0 0 0 ? ? ``` ```cpp[] int arr[5]; for (int i = 0; i < 8; i++) cout << arr[i] << " "; cout << endl; // 輸出:? ? ? ? 0 0 ? ? ``` <br/> 所以可見陣列是 **連續的記憶體** 但我們++不能確定++範圍外接續的記憶體的內容 --- #### ➰ 迭代陣列中元素 - 更動元素值 <br/> ```cpp[] int array[8]; for (int i = 0; i < 8; ++i) array[i] = i + 1; for (int i = 0; i < 8; ++i) cout << array[i] << " "; cout << endl; // 輸出:1 2 3 4 5 6 7 8 ``` ```cpp[] int array[8]; for (int i = 0; i < 8; i = i + 2) array[i] = i + 1; for (int i = 0; i < 8; ++i) cout << array[i] << " "; cout << endl; // 輸出:1 ? 3 ? 5 ? 7 ? ``` <br/> 可以看到沒有初始化 就算後續有賦值的動作 未定義的元素依然是 ++不可預期 (亂數)++ 並非 0 --- #### ➰ 迭代陣列中元素 - 大小設定謬誤 ⚠ <br/> ```cpp[] int size = 7; int arr[size]; for (int i = 0; i < size; i++) { arr[i] = i * 2; cout << arr[i] << " "; } cout << endl; // 輸出:0 2 4 6 8 10 12 (沒有任何 issue) ``` ```cpp[] int size; // ⚠ int arr[size]; size = 7; for (int i = 0; i < size; i++) { arr[i] = i * 2; cout << arr[i] << " "; } cout << endl; // Error! (這跟上面正常運作的差在哪裡?) ``` --- #### ⓘ 不同型態的陣列(宣告/初始化/大小) ```cpp[] #include <iostream> #include <string> using namespace std; int main() { // 整數陣列 int int_arr[3] = {1, 2, 3}; int int_arr_size = sizeof(int_arr) / sizeof(int_arr[0]); cout << "整數陣列陣列大小:" << int_arr_size << endl; // 3 // 長整數陣列 long long_arr[3] = {111111111, 222222222222, 3333333333}; int long_arr_size = sizeof(long_arr) / sizeof(long_arr[0]); cout << "長整數陣列陣列大小:" << long_arr_size << endl; // 3 // 浮點數陣列 float float_arr[3] = {1.0, 2.0, 3.0}; double double_arr[3] = {1.01, 2.02, 3.03}; int float_arr_size = sizeof(float_arr) / sizeof(float_arr[0]); int double_arr_size = sizeof(double_arr) / sizeof(double_arr[0]); cout << "單精度浮點數陣列大小:" << float_arr_size << endl; // 3 cout << "雙精度浮點數陣列大小:" << double_arr_size << endl; // 3 // 布林陣列 bool bool_arr[3] = {true, false, true}; int bool_arr_size = sizeof(bool_arr) / sizeof(bool_arr[0]); cout << "布林陣列大小:" << bool_arr_size << endl; // 3 // 字元陣列 char chr_arr[3] = {'a', '\n', '\0'}; // '\0' 字元陣列結尾符號 int chr_arr_size = sizeof(chr_arr) / sizeof(chr_arr[0]); cout << "字元陣列大小:" << chr_arr_size << endl; // 3 // 字串陣列(需要 <string> 標頭檔) string str_arr[3] = {"apple", "bubble", "candy"}; int str_arr_size = sizeof(str_arr) / sizeof(str_arr[0]); cout << "字串陣列大小:" << str_arr_size << endl; // 3 return 0; } ``` 理論上每種型態都能以陣列儲存,但要注意: - `char` 陣列記得確保 `'\0'` 結尾,否則可能導致非預期的行為(亂碼)。 - `string` 陣列是 ++`string` 類別的陣列++,每個元素都是一個 ++`string` 物件++,而==非== `char` 陣列。 --- ### ⚔ 字元陣列 v.s. 字串 兩種對於 C++ 來說都是字串: - C-style 字串 (`char[]`) ```cpp[] char str1[] = "Hello"; cout << str1 << "\n"; // Hello cout << sizeof(str1) / sizeof(char); // 長度為 6 ! ``` - `char[]` 結尾會有一個 `null` 結尾字元 `'\0'`,用來++標記字串結束++ - 不支援 `+` 直接++連接字串++(不像 `string`) - C++ `string` 類別(建議使用) ```cpp[] string str2 = "Hello"; cout << str2 << ", " << str2.length() << endl; // Hello 5 ``` - 支援 `.length()` 取得長度,以及許多方法 --- ### 📝 小練習1 - 字元陣列 <br/> 請把以下`字元陣列` 所有元素的 ++ASCII CODE++ 加總: - 模板: ```cpp[] #include <iostream> using namespace std; int main() { char list[] = { 'a', 'A', 'b', 'b', '.', '\'', '\"', '\n', '\0'}; return 0; } ``` :::spoiler ▼ 解答 ```cpp[] int sum = 0; for (const auto& chr : list) sum += static_cast<int>(chr); cout << sum << endl; // 487 ``` ::: --- ### 📝 小練習2 - 字串陣列 <br/> 請把以下`字串陣列` 所有元素中的每個字元的 ++ASCII CODE++ 加總: - 模板: ```cpp[] #include <iostream> #include <string> using namespace std; int main() { string list[] = {"apple", "ball", "candy"}; return 0; } ``` :::spoiler ▼ 解答 ```cpp[] int sum = 0; for (const auto& str : list) for (const auto& chr : str) sum += static_cast<int>(chr); cout << sum << endl; // 1468 ``` ::: --- ### ⛮ 基本字串處理1 - `substr(start, length=預設總長)` 取得子字串 ```cpp[] string s = "HelloWorld"; cout << s.substr(4) << endl; // "oWorld" cout << s.substr(0, 5) << endl; // "Hello" ``` - `find(string)` 查找(子)字串 ```cpp[] string s = "hi, you and you"; cout << s.find("hi") << endl; // 0(回傳 hi" 的起始索引) cout << s.find("you") << endl; // 4(回傳第一個 "you" 的起始索引) cout << s.find("me") << endl; // ?(找不到,亂碼) ``` <br/> - `replace(start, length, string)` 替換子字串 ```cpp[] string s = "Hello World"; s.replace(6, 5, "C++"); // 從索引 6 開始,長度 5,替換成 "C++" cout << s << endl; // "Hello C++" cout << s.length() << endl; // 長度變為 9 ``` --- ### ⛮ 基本字串處理2 - `append(string)` 附加字串 ```cpp[] string s = "Hello"; s.append(" World"); cout << s << endl; // "Hello World" ``` - `+` 加號來串接字串 ```cpp[] string s = "Hello"; s += " World"; cout << s << endl; // "Hello World" ``` <br/> - `.size()` / `.length()` 取得字串大小 ```cpp[] string s = "This is a sentence"; cout << s.size() << endl; // 18 cout << s.length() << endl; // 18 ``` --- ### 📝 小練習3 - 字串處理(`string`) 請用學到範圍內的方法來達成字串反轉: ``` e.g: "hello" -> "olleh" 或是 "a" -> "a" ``` - 模板: ```cpp[] #include <iostream> #include <string> using namespace std; int main() { string input; cin >> input; // input 已確保不會有空格 int len = input.length(); // 你的程式碼... cout << input << endl; return 0; } ``` :::spoiler ▼ 解答 ```cpp[] for (int i = 0; i < len / 2; ++i) { char temp = input[i]; input[i] = input[len - 1 - i]; input[len - 1 - i] = temp; } ``` ::: --- ### 📝 小練習4 - 字串處理(`char[]`) 沒錯就跟上一題一樣 但是這次沒有 `<string>`: ``` e.g: "hello" -> "olleh" 或是 "a" -> "a" ``` - 模板: ```cpp[] #include <iostream> #define MAX_SIZE 101 // input 已確保不會超過長度 100 using namespace std; int main() { char input[MAX_SIZE]; cin >> input; // 你的程式碼... int len = ? cout << input << endl; return 0; } ``` :::spoiler ▼ 解答 ```cpp[] int len = 0; while (input[len++] != '\0'){} len -= 1; for (int i = 0; i < len / 2; ++i) { char temp = input[i]; input[i] = input[len - 1 - i]; input[len - 1 - i] = temp; } ``` ::: --- ### 📝 進階練習題(++Medium++) (**_[LeetCode:Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/)_**) <br/> 給定一個字串,找出++最長++的`回文`子字串 ```latex example 1:"babad" -> "bab" 是其最長的回文子字串,"aba" 也是 example 2:"cbbd" -> "bb" 是其最長的回文子字串 ``` > 如果一個字串正讀和反讀都一樣,那麼它就是`回文字串`。 > 💡提示:用 `substr()` 來提取子字串,然後檢查是否是回文。 <br/> <!-- --- --> #### ⚡$O(n^2)$ 解法:[CPP Solution](https://leetcode.com/submissions/detail/1588965379/) --- ### ⌱ 多維陣列 <br/> - ++多維陣列++(Multidimensional Array) - 一種用來儲存多維資料的`陣列`結構。 - 其中最常見的是++二維陣列++(2D Array)。 - 三維或更高維度的陣列則可視為嵌套的結構。 常見用途包含: - ☑ 矩陣運算(Matrix Operations) - ☑ 表格與數據儲存(Tabular Data Storage) - □ 圖像處理(Image Processing) - □ 地圖與遊戲開發(Maps & Game Dev) --- ### 二維陣列 (2D-Array) > 我們知道++一維陣列++就是一個單純的一個串列 其中每個內容都是單一個元素。 > > 那++二維陣列++就其實也是一個串列 其中每個元素也是一個串列。 - 宣告與初始化: ```cpp[] int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}}; /* 左邊 [2] 代表裡面含2個串列,右邊 [3] 代表他每個串列的長度為3 */ cout << matrix[0][1] << "\n"; // 2 cout << matrix[1][2] << "\n"; // 6 ``` ```cpp[] int 2D_Array[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; cout << "2D_Array[0][1] = " << 2D_Array[0][1] << endl; // 訪問元素:? cout << "2D_Array[1][2] = " << 2D_Array[1][2] << endl; // 訪問元素:? cout << "2D_Array[2][3] = " << 2D_Array[2][3] << endl; // 訪問元素:? ``` --- #### 🚀 遍歷二維陣列 ```cpp[] int arr[4][5] = { {0, 2, 4, 6, 8}, {-2, -8, -14, -20, -26}, {4, 8, 12, 16, 20}, {-8, -16, -24, -32, -40}, }; ``` 要如何遍歷呢❓ <br/> - 那一定是++巢狀迴圈++ (多層迴圈:兩層) - 是要用 `for` loop? - 或是 `while` loop? - 還是 `do-while` loop? :::spoiler ▼ 答案 ==都可以== 但最++簡單++的做法是 `for` 迴圈 ::: --- #### 🚀 遍歷二維陣列 - `for` loop <br/> ```cpp[] int row = 4, col = 5; int arr[row][col] = { {0, 2, 4, 6, 8}, {-2, -8, -14, -20, -26}, {4, 8, 12, 16, 20}, {-8, -16, -24, -32, -40}, }; // index 方法 for (int i = 0; i < row; ++i) { for (int j = 0; j < col; ++j) { arr[i][j] = i * j; // 更新索引位置上的元素值 } } // auto& 方法(& 代表的是以位址來傳遞 而不是做一份陣列的 copy) for (auto& row : arr) { for (auto& number : row) { cout << number << " "; } cout << endl; } ``` --- #### 🛠️ 二維陣列 - 常見錯誤 <br/> - 確保索引++正確範圍++: ```cpp[] int arr[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; arr[2][4] = 13; // 錯誤!陣列索引超出範圍 ``` - 確保++正確維度++: ```cpp[] int arr[2][3]; cout << arr[3][2] << endl; // 超出行數 ``` - 確保++正確初始化++: ```cpp[] int arr[3][3]; // 未初始化,可能含有垃圾值! cout << arr[0][0] << endl; // => int arr[3][3] = {0}; ``` --- ### 📝 小練習5 - 最大陣列 請寫程式來找出下方的二維陣列 總和最大的子陣列 並輸出數值及行數: ```cpp[] int list[][5] = { // 第一個 dimension 可以不寫 會自動判斷 {2, 3, -5 , -10, 100}, {3, 4, 92, -30, -81}, {5, -6, 42, 11, -19}, {-7, 8, 30, 60, 87}, {9, 10, 75, -109, 47}, {11, 12, 21, 39, -99}, }; ``` :::spoiler ▼ 解答 ```cpp[] // #include <climits> // int max = -INT_MIN; int max = -2147483648, max_index = -1, rows = sizeof(list) / sizeof(list[0]); for (int i = 0; i < rows; ++i) { int sum = 0; for (auto num : list[i]) sum += num; if (sum > max) { max = sum; max_index = i; } } cout << max_index << " " << max << endl; ``` ::: --- ### 📌 函式(Function) <br/> 函式是++封裝++一段邏輯,可以==重複使用==的++程式碼塊++。 - 函式的基本語法: ```cpp[] #include <iostream> using namespace std; // 定義函式 void printHello() { // 一個印出 "Hello Word!" 的函式 cout << "Hello Word!" << endl; return; // return 跳脫語句 (如 break、continue),用來結束函式 cout << "this line here will not show" << endl; } int main() { printHello(); // 使用函式:輸出 Hello Word! return 0; // main() 也會有自己的 return } ``` - ``printAdd(int a, int b)`` → 參數 a 和 b 是int - `main()` → 本身也是函式,呼叫了 `printAdd()` --- ### 🚚 函式參數傳遞 函式參數可有可無,但如果需要就需要++給定型態++ 範例: ```cpp[] void printAdd1(int a, int b) { // 要求兩個傳入參數 a, b 皆為整數型態 cout << a + b << endl; } void printAdd2(int a, char b) { // 要求傳入參數 a 為整數, b 為字元 cout << a + b << endl; } void printAdd3(auto a, auto b) { // 要求傳入參數 a, b 型態編譯器判斷 cout << a + b << endl; } // => 試著帶入以下 cases 到以上函數看看 ``` | | Case 1 | Case 2 | Case 3 | |-|-|-|-| | `a` | 65 | 65 | 'A' | | `b` | 66 | 'B' | 'B' | | | | | _發現什麼了嗎?_| --- ### ⛟ 函式參數傳遞方法 | 方式 | 說明 | 範例 | |-|-|-| | 傳遞值 | 建立變數副本,不影響原值 | `void func(int a)` | | 引用傳遞 | 直接操作原始變數,較安全 | `void func(int *b)` | | 傳遞指標 | 修改變數內容,可能產生++空指標++問題 | `void func(int &c)` | ➤ 為何會需要三種傳遞方式呢? 1. ++影響範圍++ 函式內變數更動 是否影響外部 2. ++效率問題++ 製作一份 copy 就是一個開銷 3. ++考量用途++ 陣列的傳遞就會需要指標只向開頭 > `指標`本身存的是指向的變數 ++的地址++ > 但其本身也會有這個++存地址的變數++ 的地址 這的部分可以等學到再細說 --- #### ⦾ 函式傳遞參數/使用 - 範例 ```cpp[] #include <iostream> using namespace std; void modify_with_copy(int x) { x = 6; } void modify_with_reference(int *x) { *x = 66; } // (*)用來取值 void modify_with_pointer(int &x) { x = 666; } int main() { int num1 = 1, num2 = 2, num3 = 3; modify_with_copy(num1); cout << num1 << endl; // 輸出 1 因為函式更動的值是副本的 modify_with_reference(&num2); // 引用所以必須 (&)傳遞地址 cout << num2 << endl; // 輸出 ? modify_with_pointer(num3); cout << num3 << endl; // 輸出 ?? return 0; } ``` 可以看到只有++傳遞值++的方法不會影響傳入的變數值 <br/> 函式還有一個部分還沒有討論到! - 那函式名稱前面的 `void` 又是什麼呢? - `main()` 函式前面的 `int` 又代表什麼意思呢? ➤➤➤ --- #### ☆ 函式回傳(`return`) > 我們知道基本上 C++ 程式 是從上到下 由左到右來讀取/執行 代碼的 > 那使用到 `函式` 就可以將高度重複的代碼重複利用/或是邏輯分離。 假如我們今天想要找一個整數的最大質因數, 可把++計算過程寫在函式++以找多個不同變數質因數, 外部只需要要呼叫函式來獲取答案,可以怎麼做`?` 1. 函式回傳? - 是要函式內部還是外部需要答案? - 是外部,那如何接收到計算出的答案? 2. `return` 語法: - 在定義函式時,函式名稱前加上型態: - `double`、`int`、`int[]`、`char`、`char[]` - 或是 `void`(空值、不回傳的型態) - 語法:`return 值;`(值形態需匹配) - ++沒有寫++ `return` 的函式++預設++會回傳空值 --- ### 函式回傳 - 𝑓(找最大質因數) 我們就來定義一個函式 ++`int getMaxFactor(int x);`++ ```cpp[] #include <iostream> using namespace std; // 函式純粹宣告(編譯器接受不馬上接著寫實踐 道理跟變數宣告一樣) int getMaxPrimeFactor(int x); int getMaxPrimeFactor(int x) { // 可以看到函式的功能實踐可以寫在其他地方 // 找最大質因數的代碼 // return ?; // return 答案給外部 } int main() { int max_prime_factor_of_ten; max_prime_factor_of_ten = getMaxPrimeFactor(10); // 透過接收 return 值 外部就可以自由利用 cout << "Max prime factor of 10 is: " << max_prime_factor_of_ten << endl; cout << "Max prime factor of 100 is: " << getMaxPrimeFactor(100) << endl; // 重複利用! return 0; } // 函式的功能實踐也是可以寫在 main 下方 // int getMaxFactor(int x) { 找最大質因數的代碼 } ``` `main()` 主函式中就利用預先邊寫好的功能 方便獲取一個數的最大質因數 現在輪到你來實踐函式內容了![image](https://hackmd.io/_uploads/S1PCh2L61g.png) --- ### 📝 小練習6 - 最大質因數 與前面範例小小的不同 現在我們的函式++不能有回傳值++(`void function`) 但是外部同樣可以正確的打印出出答案: 請撰寫一個程式把傳入的 int 變數 更新成自己的最大質因數 → ```cpp[] void beMaxPrimeFactor(...) { // 你的代碼 } ``` :::spoiler ▼ 解答 ```cpp[] void beMaxPrimeFactor(int &x) { for (int i = x; i > 0; --i) { int factorCount = 0; for (int j = 2; j < i; ++j) { if (!(i % j)) { factorCount = 1; break; } } if (!factorCount) { x = i; return; } } } ``` ::: --- ### 📝 小練習7 - 矩陣完全平方篩選 請撰寫一個接收一維 int 陣列為參數的函式 `int result[]` 來存只剩下完全平方數的版本 → ```cpp[] #include <iostream> #include <cmath> // sqrt(x) -> x 的平方根 using namespace std; // 返回原本矩陣的完全平方數個數(模板) int getPerfectSquareArray(const int arr[], int n, int result[]) { // 參數: 原陣列、大小、結果陣列 int count = 0; // 你的代碼 return count; } int main() { int array[] = {2, 4, 5, 6, 7, 9}; int size = sizeof(array) / sizeof(int); int res[size]; int num = getPerfectSquareArray(array, size, res); for (int i = 0; i < num; ++i) cout << res[i] << endl; return 0; } ``` :::spoiler ▼ 解答 ```cpp[] int getPerfectSquareArray(const int arr[], int n, int result[]) { int count = 0; for (int i = 0; i < n; ++i) { int square_root = sqrt(arr[i]); if (arr[i] == square_root * square_root) result[count++] = arr[i]; } return count; } ``` ::: --- ### 📝 小練習8 - 旋轉矩陣函式 ![images](https://hackmd.io/_uploads/HkM_g1wa1l.png) 如上圖所示,將方形矩陣++順時針旋轉 90°++, 請撰寫函式來更新輸入的++2D矩陣++為其旋轉矩陣 → - 模板 ```cpp[] #define SIZE 4 void rotateMatrix(int matrix[][SIZE]); ``` :::spoiler ▼ 詳解 ```cpp[] void rotateMatrix(int matrix[][SIZE]) { int temp[SIZE][SIZE]; for (int i = 0; i < SIZE; ++i) { for (int j = 0; j < SIZE; ++j) temp[j][SIZE - 1 - i] = matrix[i][j]; } for (int i = 0; i < SIZE; ++i) { for (int j = 0; j < SIZE; ++j) matrix[i][j] = temp[i][j]; } } ``` ::: --- ### ∞ 遞迴(Recursion) <br/> `遞迴` 是函式呼叫自己的方式,常用於解決: - 數學問題(費氏數列、階乘) - 樹的遍歷 (樹狀結構) - 深度優先搜尋(DFS) - 背包問題 ([Knapsack Problem](https://web.ntnu.edu.tw/~algo/KnapsackProblem.html)) 這些用法都是在解決深度不確定的問題, 那其實遞迴能解決的問題 ++迴圈++也同樣能夠 遞迴的概念 需要培養他的思考邏輯 (畫圖是一個好方法) 使用時 要小心當++函式終止條件++始終不能滿足時 就會造成==無窮遞迴== 沒錯 跟++無窮迴圈++是相似的概念 --- ### ⁍ 遞迴的基本結構 ```cpp[] void recursion1(int n) { cout << n << endl; if (n == 0) return; // 終止條件 也稱為基礎條件(Base Case) recursion1(n - 1); // 遞迴呼叫 -> 遞迴關係(Recursive Case) } ``` ➛ `recursion1` 可以寫成以下 `loop1` ```cpp[] void loop1(int n) { for (int i = n; i >= 0; --i) cout << i << endl; } ``` <br/> ```cpp[] int recursion2(int n) { if (n == 0) return 0; // 終止條件 return n + recursion2(n - 1); // 返回遞迴呼叫結果 } ``` ➛ `recursion2` 可以寫成以下 `loop2` ```cpp[] int loop2(int n) { int accumulate = 0; for (int i = n; i >= 0; --i) accumulate += i; return accumulate; } ``` --- #### 常見的遞迴1 - `費式數列`(Fibonacci Sequence) <br/> 定義一個++數列++的第一項 $a_1$ = 第二項 $a_2$ = 1 ==When $n > 2, a_n = a_n-1 + a_n-2$== 可以來練習遞迴了 → ```cpp[] int fibonacci(int n) { // 參數 n: 第 n 項(已確保 n > 0) if (n <= 2) return 1; // 第一項、第二項 都等於 1 return __(?)__; // 回傳費式數列 第 n 項的值 } ``` ‣ 看一下迴圈怎麼做的: ```cpp[] int fibonacci_loop(int n) { int last1 = 1, last2 = 1, val = 0; if (n <= 2) return 1; for (int i = 1; i <= n; ++i) { if (i <= 2) continue; val = last1 + last2; last1 = last2; last2 = val; } return val; } ``` --- #### 常見的遞迴2 - `階乘!`(Factorial) <br/> 定義 ==$n! = 1 * 2 * ... * (n -1) * n$== 想一下遞迴要怎麼達成 → ```cpp[] int factorial(int n) { // 已確保 n >= 1 if (n == 1) return 1; // 1! = 1 return __(?)__; } ``` > 概念跟`累加`很像,不懂可以畫圖++層層展開++ <br/> ‣ 迴圈作法: ```cpp[] int factorial_loop(int n) { int val = 1; for (int i = n; i >= 1; --i) val *= i; return val; } ``` --- ### 💾 `const` 保留字 <br/> 首先什麼++保留字++(keyword)❔[關鍵字 (C++)](https://learn.microsoft.com/zh-tw/cpp/cpp/keywords-cpp?view=msvc-170) - 有特殊意義/功能的++預先定義++保留識別項。 - ==不能==在程式中將關鍵字當做識別碼使用。 - 像是以下這些都不能拿來命名變數/函數: - `if`、`for`、`goto`、`this`、`class` ... - `int`、`char` 還有 `const` 也都是 - `const` 關鍵字的功用❓ - 來指定變數值為常數,告知編譯器禁止修改。 --- ### `const` 用法 - 語法: ```cpp[] const 變數 = 初始值; ``` - 範例: ```cpp[] const int MAX_SIZE = 100; // 定義一個常數變數 MAX_SIZE = 200; // ❌ 錯誤,const 變數無法更改 ``` - 編譯器++報錯++: ``` Output: test.cpp: In function ‘int main()’: test.cpp:8:10: error: assignment of read-only variable ‘MAX_SIZE’ 8 | MAX_SIZE = 200; | ~~~~~~~~~^~~~~ ``` 我們宣告的 const 變數 被當作是 ++read-only++ 的對象 --- #### ☴ `const` 其他用法 - 函式參數 <br/> 1. `const` 參數: ```cpp[] void printMessage(const string &msg) { cout << msg << endl; } ``` ```cpp[] void printValue(const int &val) { val++; // 編譯器不會允許修改 cout << val << endl; } => error: increment of read-only reference ‘val’ ``` 當函數參數被傳入時設定為 `const` 的引用, 這樣就可以確保函式內部++不會更動到引用的值++ --- #### ☴ `const` 其他用法 - 指標 > **++指標++、++成員函式++ 這些先大致看過就好** <br/> 2. `const` 指標: ```cpp[] int a = 10, b = 20; const int *ptr = &a; // 指標指向的值不能改變 ptr = &b; // ✅ 可以更改指標指向的位置 *ptr = 30; // ❌ 錯誤,不能修改 ptr 指向的內容 int *const ptr2 = &a; // 指標本身不能改變,但內容可變 ptr2 = &b; // ❌ 錯誤 *ptr2 = 40; // ✅ 可修改 ptr2 指向內容 ``` > ++指標++本身是一個指針 指向一個變數的地址 (往後細講) --- #### ☴ `const` 其他用法 - 成員函式 > **++指標++、++成員函式++ 這些先大致看過就好** <br/> 3. `const` 成員函式: ```cpp[] class Example { public: void show() const { // const 確保外部無法重新定義 Example.show() cout << "這是 const 函式,無法改變物件內容" << endl; } }; ``` > 此處 `Example` 是一個 `class`,`show()` 則為 `Example` 中的一個++成員函式++ --- #### 📝 基礎題 - 合法括號([Valid Parentheses](https://leetcode.com/problems/valid-parentheses/)) 有一行 input string 字串,請撰寫函示判斷是否 所有括號皆有封閉(只會有 左括號`(` 和 右括號 `)`) :::spoiler ▼ 詳解 ```cpp[] #include <iostream> #include <cstdlib> #include <string> using namespace std; bool validParentheses(string input) { int index = 0; while (input[index] == ')') { index++; } // 檢查不能以右括號開頭 if (index) return false; int count = 0; for (const auto& c : input) { switch (c) { case '(': ++count; break; case ')': --count; break; } } return count == 0; } int main() { string str; cin >> str; cout << validParentheses(str) << endl; return 0; } ``` ::: <br/> - 進階版本:[LeetCode - Valid Parentheses](https://leetcode.com/problems/valid-parentheses/) - 有多種括號:`'('`, `')'`, `'{'`, `'}'`, `'['`, `']'` - 解答: [switch-case 解法](https://leetcode.com/submissions/detail/1591260870/) --- #### 📝 進階題 - 樓梯問題(Climbing Stairs) 輸入一個 `n`,代表有 `n` 階樓梯,你一次可以爬 ++1 階或 2 階++,請問有幾種爬法? > 題示: 遞迴 `f(n) = f(n-1) + f(n-2)` 來解 - 模板: ```cpp[] int climbingStairs(int n) { // 你的程式碼 return __(?)__; } ``` :::spoiler ▼ 詳解 ```cpp[] int climbingStairs(int n) { if (n == 1) return 1; else if (n == 2) return 2; return climbingStairs(n - 1) + climbingStairs(n - 2); } ``` ::: --- ### 🖊️ 習題練習 (LeetCode) - [Find the Index of the First Occurrence in a String](https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/) > 提示:善用 `substr()` <br/> 解答:[string.substr() 解法](https://leetcode.com/submissions/detail/1591277535/)
{"title":"CPP Lecture3","description":"CPP Lecture3.","image":"https://hackmd.io/_uploads/HJKOkQjnkx.png","contributors":"[{\"id\":\"08ecf684-cada-47c1-ad99-984ab62fb65e\",\"add\":25498,\"del\":4577}]"}
    182 views