# Introduction - What is OO - Why learn OO - [Example](https://www.canva.com/design/DAGbDMnYmks/Be1tmFOVpqubOsY71vo4jg/edit?utm_content=DAGbDMnYmks&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton) Note: [clang-uml](https://github.com/bkryza/clang-uml) [plantUML web editor](https://www.plantuml.com/plantuml/umla/SoWkIImgAStDuNBCoKnELT2rKt3AJx9IS2mjoKZDAybCJYp9pCzJ24ejB4qjBk42oYde0jM05MDHLLoGdrUSoeLkM5u-K5sHGY9MGw6ARNHryQb66EwGcfS2T300) [github](https://github.com/JeffBla/art-center-local-sys/tree/main/src/models) [cpp ref](https://cplusplus.com/reference/) --- # 第一堂:函式與遞迴 --- - **學習目標:** - 認識函式基本結構與使用方式 - 理解遞迴原理並學會實作遞迴函式 --- ## 🔖 函式的基本概念 - 函式是一段可重複呼叫的程式碼區塊。 - 好處: - 重用程式碼(Code reuse) - 增進程式可讀性 - 容易維護與除錯 --- ## 📌 函式的基本語法 ```cpp return_type function_name(parameter_list) { // 函式主體 return value; } ``` - return_type:回傳資料型態 - function_name:函式名稱 - parameter_list:參數清單(可有可無) --- ### 🚩 函式範例 ```cpp // 計算兩數之和 int add(int a, int b) { return a + b; } int main() { int result = add(5, 3); // <-- 5, 3 稱為 argument cout << "5 + 3 = " << result; // 輸出 5 + 3 = 8 } ``` --- ### 📗 函式參數傳遞方式 兩種參數傳遞方式: - 傳值(`Call by value`):傳遞複製值給函式 - 傳參考(`Call by reference`):傳遞參考,直接修改原值 Note: - 一般來說,call by ref 的方式較好,尤其對於龐大的資料結構如:long long[100000],避免重複資料複製,造成記憶體不足。 ---- ### 🚩 傳值範例 ```cpp void increment(int x) { x = x + 1; } int main() { int num = 5; increment(num); cout << num; // 輸出仍為 5 } ``` ---- ### 🚩 傳參考範例 ```cpp void increment(int &x) { x = x + 1; } int main() { int num = 5; increment(num); cout << num; // 輸出為 6 } ``` ---- 傳指標 ```cpp void increment(int *x) { *x = *x + 1; // x = &num } int main() { int num = 5; increment(&num); // &num 是一個 int 指標 cout << num; // 輸出為 6 } ``` --- ### 🎲 函式重載(`Overloading`) - 函式重載允許同名稱函式透過不同參數進行區別: ```cpp int square(int x) { return x * x; } double square(double x) { return x * x; } int main() { cout << square(5); // int 版本 cout << square(3.5); // double 版本 } ``` --- ## 🔄 遞迴函式(`Recursion`) - 遞迴函式定義: - 一個函式在函式內部直接或間接呼叫自己。 - 遞迴需具備: - 終止條件(Base Case) - 遞迴步驟(Recursive Step) Note: - 在 linux kernel 上通常不用 recursion,而是用 loop 或是展開,因為 kernel 能用的記憶體空間很小,recursion 容易造成記憶體空間不足,但優點是簡單易讀。 ---- ### 🚩 遞迴範例:階乘函式 階乘定義:$n!=n \cdot (n-1)!$ ```cpp int factorial(int n) { if (n == 0) return 1; // Base case int c = n * factorial(n - 1); // Recursive step return c; } ``` ---- ### 🎯 遞迴執行流程圖示 ```text factorial(3) = 3 × factorial(2) = 3 × (2 × factorial(1)) = 3 × (2 × (1 × factorial(0))) = 3 × (2 × (1 × 1)) = 3 × (2 × 1) = 3 × 2 = 6 ``` --- ### ⚠️ 遞迴使用注意 - 如迴圈一樣,必須設立明確終止條件 - 遞迴可能導致效能問題(Stack overflow) Note: - 在 linux kernel 上通常不用 recursion,而是用 loop 或是展開,因為 kernel 能用的記憶體空間很小,recursion 容易造成記憶體空間不足,但優點是簡單易讀。 --- ### 🚩 經典練習:費氏數列 定義: $F(n)=F(n−1)+F(n−2)$ ```cpp int fib(int n) { if(n <= 1) return n; return fib(n-1) + fib(n-2); } ``` ---- 實驗一下遞迴導致的 stack overflow --- ## 🛠️ 課堂練習 (Lab) - 實作一個函式,計算兩數的最大公因數(`GCD`),並使用遞迴方式完成。 Note: ```cpp int gcd(int a, int b) { if(b == 0) return a; return gcd(b, a % b); } ``` --- ## 💡 作業 - 撰寫一個函式,可判斷輸入的整數是否為質數 `Prime Number`。 - 用遞迴撰寫 `Fibonacci` 函式,並比較遞迴版與迴圈版的效率差異。 Note: 補充:`recursion` 的概念需搭配演算法與資料結構才好融會貫通,可以看一下 binary tree 的 preorder/inorder/postorder traversial --- # 第二堂:陣列與向量 (Array & Vector) --- - **學習目標:** - 熟悉陣列與向量的基本觀念與操作 - 學習如何透過陣列與向量處理資料 --- ## 🔖 陣列 `Array` 基本概念 - 陣列:一系列具有相同資料型態的元素集合。 - 優點: - 存取元素速度快(使用索引直接存取) - 缺點: - 長度固定,無法動態調整 --- ## 📌 陣列宣告與初始化 ```cpp // 宣告並初始化陣列 int arr1[5] = {1, 2, 3, 4, 5}; // 不指定大小,自動推導大小 int arr2[] = {1, 2, 3, 4, 5}; arr2[0] = 10; // 修改第一個元素 cout << arr2[2]; // 輸出第三個元素 (3) ``` ---- ### 利用 `Object-orient program` 技巧 ```cpp int arr3[5]{1, 2, 3, 4, 5}; cout << arr3[2]; ``` --- ### 🚩 陣列與迴圈搭配使用 使用迴圈快速操作陣列: ```cpp int scores[5] = {80, 75, 90, 85, 70}; int sum = 0; for(int i = 0; i < 5; i++) { sum += scores[i]; } cout << "平均成績:" << sum / 5.0; ``` --- ### 🔖 二維陣列(多維陣列) 常用於處理矩陣資料: ```cpp int matrix[2][3] = { {1, 2, 3}, {4, 5, 6} }; cout << matrix[1][2]; // 輸出 6 ``` --- ### 🔖 向量(Vector)基本概念 - 向量 (std::vector) 為動態大小的陣列 - 優點: - 可自動調整大小 - 操作靈活且方便 - 📌 建議:一般情況多使用 vector 提高程式彈性。 --- ## 📌 向量基本操作 ```cpp #include <vector> using namespace std; vector<int> v; // 宣告空向量 v.push_back(10); // 加入元素 10 v.push_back(20); // 加入元素 20 cout << v.size(); // 輸出元素個數 (2) cout << v[0]; // 存取第一個元素 (10) vector<int> vec{1,2,3}; // 宣告存有 1,2,3 的向量 ``` --- ### 🚩 遍歷向量的方法 方法一:使用 `for` 迴圈 ```cpp for (int i = 0; i < v.size(); i++) { cout << v[i] << " "; } ``` 方法二:範圍 `for (Range-based for loop)` ```cpp for(int num : v) { cout << num << " "; } ``` 方法三:利用 `iterator` ```cpp for (auto it = v.begin(); it!=v.end(); it++){ cout << v[i] << " "; } ``` Note: iterator 是 cpp 獨有的物件,利用此物件來優化/統整/自訂迴圈運算。 --- ### 🎯 範例:找出向量中的最大值 ```cpp #include <vector> #include <iostream> using namespace std; int main() { vector<int> nums{2, 9, 5, 7, 4}; // vector<int> nums = {2, 9, 5, 7, 4} 也是等效 *注1 int maxVal = nums[0]; for (int num : nums) { if (num > maxVal) maxVal = num; } cout << "最大值:" << maxVal; } ``` > **\*注1**:由於其根本是指定,因此底層實作是先建立 `vector<int>{2, 9, 5, 7, 4}` 在指定給 `nums`,因此較慢 --- ### 🚩 二維向量(Vector of Vectors) ```cpp vector<vector<int>> matrix = { {1, 2, 3}, {4, 5, 6} }; matrix[1][1] = 10; // 修改元素 for (auto &row : matrix) { for (int val : row) { cout << val << " "; } cout << endl; } ``` --- ### 🛠️ 課堂練習 (Lab) - 練習一: - 將 vector 內的元素由小到大排序 Note: ```cpp #include <vector> #include <iostream> #include <algorithm> using namespace std; void printVector(vector <int> &target){ for(auto v : target){ cout << v << " "; } cout << endl; } int main() { vector<int> nums{2, 9, 5, 7, 4}; for(int i = 0; i < nums.size(); i++){ for(int j = 0; j < nums.size(); j++){ if(nums[i] < nums[j]){ swap(nums[i], nums[j]); } } } printVector(nums); } ``` --- # 第三堂:指標 (Pointer) --- - **學習目標:** - 了解指標與記憶體管理的概念 - 學習指標與陣列的關係與動態記憶體配置 --- ## 🔖 指標的基本概念 - 指標是一種變數,用來存放**記憶體位址**。 - 透過指標可以間接存取其他變數。 - 📌 優點: - 有效處理動態記憶體 - 實現更複雜的資料結構 ---- ![pointer graph](https://www.alphacodingskills.com/cpp/img/cpp-pointer-to-pointer.PNG) --- ## 📌 指標變數宣告 指標的宣告方式: ```cpp int *ptr; // 整數型別的指標 double *dptr; // 浮點數型別的指標 vector<int> *vec; // vector<int> 的指標 -> 物件的指標 ``` --- ### 🚩 取得記憶體位址(& 運算子) ```cpp int num = 100; int *ptr = &num; // 存放num的位址 cout << &num; // 輸出 num 的記憶體位址 cout << ptr; // 同上,ptr 存放 num 位址 ``` --- ### 🚩 存取指標所指內容(* 運算子) ```cpp int num = 100; int *ptr = &num; cout << *ptr; // 透過指標取得 num 的值 (100) *ptr = 200; // 修改 num 的值為 200 cout << num; // 輸出變為 200 ``` --- ### 🎯 指標與陣列的關係 陣列名稱本身即為位址: ```cpp int arr[3] = {10, 20, 30}; int *ptr = arr; // arr即為 &arr[0] cout << ptr[1]; // 20 cout << *(arr+2); // 30 ``` --- ### 📗 指標的算術運算 指標可透過加減運算移動到相鄰元素 ```cpp int arr[3] = {10, 20, 30}; int *ptr = arr; ptr++; // 指向 arr[1] cout << *ptr; // 20 ptr += 1; // 指向 arr[2] cout << *ptr; // 30 ``` --- ### 進階:pointer to pointer ```cpp #include <iostream> using namespace std; int main() { // 建立三筆資料 const char *data1 = "Alice"; const char *data2 = "Bob"; const char *data3 = "Charlie"; // 建立一個陣列來模擬「下一個」的關係(鏈結) const char **links[3]; links[0] = &data1; // 第 1 個指向 data1 links[1] = &data2; // 第 2 個指向 data2 links[2] = &data3; // 第 3 個指向 data3 // 遍歷所有資料 for (int i = 0; i < 3; ++i) { cout << "節點 " << i + 1 << ": " << *(links[i]) << endl; } return 0; } ``` ---- ### 進階: function as a pointer - 在 C++ 中,函式本身也可以被當作一種「可以指向的東西」。這就像是指向變數的指標,不同的是它指向的是一個函式的起始位置,可以用來: 1. 傳遞函式當作參數 2. 動態決定要呼叫哪個函式 3. 實作 callback(回呼函式) ---- ### 🧪 範例:簡單的函式指標 ```cpp #include <iostream> using namespace std; void greet() { cout << "Hello!\n"; } int main() { void (*funcPtr)() = greet; // 宣告一個指向 greet 的函式指標 funcPtr(); // 呼叫函式(就像呼叫 greet 一樣) return 0; } ``` ---- ### 🧠 說明 - void (*funcPtr)() 表示一個「指向回傳 void、無參數」的函式指標。 - 把 greet 指給它,就能用 funcPtr() 呼叫。 - 和普通函式呼叫沒兩樣,但可以讓程式在執行時選擇呼叫哪個函式。 ---- ### 🧪 範例:計算機功能選單 ```cpp #include <iostream> using namespace std; int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int divide(int a, int b) { return b != 0 ? a / b : 0; } int main() { // 定義一個陣列,裡面放四個函式指標 int (*ops[4])(int, int) = {add, sub, mul, divide}; int a = 10, b = 2; int choice; cout << "請選擇操作:0加 1減 2乘 3除: "; cin >> choice; if (choice >= 0 && choice < 4) { cout << "結果為: " << ops[choice](a, b) << endl; } else { cout << "無效選項" << endl; } return 0; } ``` --- ### 🚩 動態記憶體配置(new 與 delete) - 用於物件建立 - 動態配置可於執行期間決定大小: ```cpp int *ptr = new int; // 動態配置整數記憶體 *ptr = 100; cout << *ptr; // 100 delete ptr; // 釋放記憶體 ``` ---- ```cpp #include <iostream> using namespace std; class Student { private: string name; int score; public: Student(string name):name(name){}; void setInfo(string n, int s) { name = n; score = s; } void printInfo() { cout << name << " 成績: " << score << endl; } string getName(){ return name; } }; int main() { Student *me = new Student("Jeff"); cout << me->getName() << endl; } ``` ---- ## 重要 - new 必須要搭配 delete,因為 new 是在記憶體中分配空間,沒有用到時,必須手動釋放空間,以免空間占滿沒有記憶體使用。 --- ### 🚩 動態配置陣列 ```cpp int n; cin >> n; int *arr = new int[n]; // 動態陣列配置 for(int i = 0; i < n; i++) arr[i] = i + 1; delete[] arr; // 釋放陣列記憶體 ``` --- ### 📌 指標與函式參數傳遞 - 透過指標傳遞參數: - 可以把它看成一種 call by ref ```cpp void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; swap(&x, &y); cout << x << ", " << y; // 10, 5 } ``` --- ### ⚠️ 常見指標錯誤 未初始化指標: ```cpp int *p; *p = 10; // 未初始化,危險! ``` 使用已釋放的記憶體: ```cpp int *p = new int; delete p; *p = 5; // 錯誤! ``` ---- ### Pointer 進階技巧 - 利用 pointer 改變取用元素屬性 ```cpp #include <iostream> #include <bitset> using namespace std; int main() { int a = 2147483647; // 2^31-1 char *c_1 = (char *)&a, *c_2 = c_1 + 1, *c_3 = c_1 + 2, *c_4= c_1 + 3; cout << "Binary: " << bitset<32>(a) << endl; // 8 表示用 8 位元顯示 cout << "Pointer4: " << bitset<32>((unsigned char)*c_4) << endl; cout << "Pointer3: " << bitset<32>((unsigned char)*c_3) << endl; cout << "Pointer2: " << bitset<32>((unsigned char)*c_2) << endl; cout << "Pointer1: " << bitset<32>((unsigned char)*c_1) << endl; cout << "Pointer: " << bitset<8>((unsigned char)*c_4) << bitset<8>((unsigned char)*c_3) << bitset<8>((unsigned char)*c_2) << bitset<8>((unsigned char)*c_1) << endl; return 0; } ``` --- ## 🛠️ 課堂練習 (Lab) - 練習一: - 建立一個 linkedlist 並印出儲存元素 Note: ```cpp #include <iostream> using namespace std; // Define a node structure struct Node { int data; Node* next; }; void append(Node*& head, int value) { Node* newNode = new Node{value, nullptr}; if (head == nullptr) { head = newNode; return; } Node* current = head; while (current->next != nullptr) { current = current->next; } current->next = newNode; } void printList(Node* head) { while (head != nullptr) { cout << head->data << " -> "; head = head->next; } cout << "null" << endl; } void freeList(Node* head) { while (head != nullptr) { Node* temp = head; head = head->next; delete temp; } } int main() { Node* head = nullptr; append(head, 10); append(head, 20); append(head, 30); printList(head); // Output: 10 -> 20 -> 30 -> null freeList(head); // Clean up memory return 0; } ```
{"title":"遞迴 vector 與 pointer","description":"C++課程教材|函式定義與遞迴函式","contributors":"[{\"id\":\"01487228-6720-47a9-875f-2f01b5d455ad\",\"add\":14800,\"del\":2740,\"latestUpdatedAt\":1753864677262}]"}
    148 views