--- title: Python C C++ bestmark520 autoHeadingNumber: true --- [TOC] <!-- 按 Ctrl+Shift+P → Markdown: Create Table of Contents--> # Python C C++ ## for 迴圈 (要加冒號) ### for 迴圈 + range ```python for i in range(stop) # 同 range(0, stop, 1) for i in range(start, stop) # 同 range(start, stop, 1) for i in range(start, stop, step) # 完整版 for _ in range(3): # 若迴圈的變數之後不會用到,可以用 `_` for i in range(0, n): # 0到 n-1,數量不會減少,輸出n個數字 for j in error: # 迴圈中把每一個項目都掃一次 `for A in B` # `error` 可以是str, list, tuple, dict, set for (int j : error) #c++ ``` #### C++ 跟 Python 迴圈的整數範圍: C++ 的中間可以自己決定是 `<=` 還是 `<`, Python 的中間隨遞增/遞減固定是 `<` 或 `>`。 ```python for j in range(1, 10): for (int j = 1; j < 10; j = j + 1) #c++ # 滿足 j < 10 的最小整數,即 j = 9 for j in range(1, 10): for (int j = 1; j <= 9; j = j + 1) #c++ # 滿足 j < 10 的最小整數,即 j = 9 for j in range(10, -1, -1): for (int j = 10; j > -1; j = j - 1) #c++ ``` ### 迴圈中的迴圈 ```python for i in range(4): # i 遍歷 0 到 3(half-1) for j in range(2): # j 遍歷 0 到 1(col-1) for k in range(3): # k 遍歷 0、1、2(三個通道:紅、綠、藍) print(f"i={i}, j={j}, k={k}") ``` ![001](https://hackmd.io/_uploads/H18K6Briel.png) ```python for i in range(0, 4, 1): for j in range(3, -1, -1): ``` 如圖,`col` = `0`,`row` = `3`、`2`、`1` → `col` = `1`,`row` = `3`、`2`、`1`,`i` 是外圈,`j` 是內圈: ![002](https://hackmd.io/_uploads/B1aqTrBjel.png =40%x) #### Bubble Sort 寫法一:由 i 配合 j (較直觀) ```python def bubble_sort(nums: list[int]): n = len(nums) print(nums) for i in range(n - 1, 0, -1): for j in range(i): if nums[j] > nums[j + 1]: nums[j], nums[j + 1] = nums[j + 1], nums[j] print(nums) else: print(f'{nums}, nochange') ``` #### Bubble Sort 寫法二:由 j 配合 i(Jack 寫法) ```python def bubble_sort_Jack(nums: list[int]): n = len(nums) print(nums) for i in range(0, n-1, 1): for j in range(0, n-1-i, 1): if nums[j] > nums[j + 1]: nums[j], nums[j + 1] = nums[j + 1], nums[j] print(nums) else: print(f'{nums}, nochange') ``` ### Iteration and Recursive 迴圈與遞迴 iteration迭代: - 迭代就是迴圈 - For迴圈:指定次數 - while迴圈:指定條件 ![046](https://hackmd.io/_uploads/HJoK-XPugg.png =55%x) recursive遞迴: - 遞迴透過函式呼叫自身來解決問題,可以參考merge sort程式的流程圖 - 遞:程式不斷深入地呼叫自身 - 迴:觸發終止條件後,程式從最深層的遞迴函式開始逐層返回 - 遞迴與堆疊的先入後出原則異曲同工 ```python def quick_sort(arr): if len(arr) <= 1: return arr # 遞迴要給最底層回傳的東西 pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quick_sort(left) + middle + quick_sort(right) ``` ```cpp void insertion_sort_recursive(int nums[], int n) { if (n <= 1) return; // 遞迴要給最底層回傳的東西 // 先遞迴排序前面 n-1 個 insertion_sort_recursive(nums, n - 1); int base = nums[n - 1]; int j = n - 2; while (j >= 0 && nums[j] > base) { nums[j + 1] = nums[j]; j--; } nums[j + 1] = base; } ``` ## while 迴圈 (要加冒號) ### while 數值範圍漸漸縮小 ```c // i 跟 j 的範圍逐漸縮小 while (left <right){ // func left++; right--; } ``` ![image](https://hackmd.io/_uploads/rJur84mBWg.png =60%x) ```c // 當list.next不是null就work // 直到list.next是null為止 while (ListArray[found_entry].NextPtr != NULL) ``` ### while 條件達成時執行 ```python while rest >= c: # 達成 rest>=c時,不斷地run #不斷迴圈直到rest < c sum += rest // c rest = rest // c + rest % c print(sum) # 廣達面試題目,if迴圈是有指定次數;while迴圈是指定條件 ``` CPE 11689 ```python t = int(input()) for case in range(1, t + 1): e, f, c = map(int, input().split()) soda = 0 bottles = e + f while bottles >= c: # 如果瓶子數 >=兌換數,就繼續換 soda += bottles // c bottles = bottles // c + bottles % c print(soda) while B: #表示只要B不是0(真數)時,就繼續run ``` ### While Ture:,搭配if break確保不會無限循環 ```python while True: user_input = input("請輸入 'quit' 來退出迴圈: ") if user_input == 'quit': break print("迴圈已退出") ``` CPE 11428,20230919例題2 找x^3 - y^3的xy解 x^3 - y^3 =(x-y)(x^2 +xy+y^2 ) <10^5 →x-y <100 →x < y+100 ```python while True: N = int(input()) if N == 0: break # 使這個while停止 a = 0 for j in range(1, 100): for i in range(j, 100 + j): if i**3 - j**3 == N: print(i, j) a = 1 break # 上面for迴圈停止,不代表這個while停止 if a == 0: print("No solution") break # 如果沒加這個,輸入0 while也不會停止,可以一直輸入值 ``` ### while True + try/except EOFError 20231205 第二題,except EOFError:,放在整個程式最後面 無限輸入迴圈: ```python while True: try: #程式碼 except EOFError: break ``` ### C:do while ```c do { // 程式碼區塊 // 會先執行一次程式區域,在看有沒有符合條件 } while (condition); ``` 群聯 UFS 考古第一題 ```c int i,x,y; i=x=y=0; do{ ++i; if(i%2 != 0) x = x + i++; y = y + i++; } while(i<=7); printf("%d %d\n", x, y); // 1 20 // x = 0 + 1 // y = 0 + 2 + 4 + 6 + 8 ``` ## if(要加冒號),條件判斷 ### if、elif、else 用法 ```python if x > 5: print("x 大於 5") elif x == 5: print("x 等於 5") else: print("x 小於 5") ``` CPE 11942 正確寫法(if + elif + else) CPE 11942 錯誤寫法(if + if + else) 如果是 if + if + else,那第二個if會跟else變成一組 要讓程式1、2、3合在一起,就要if + elif +else C++: ```cpp if (nums[i] == val) // 要有括號 { nums.erase(nums.begin() + i); } ``` ### 條件式 | 類別| 意義| C| C++| Python| Verilog| | - | - | - | - | - | - | | **if 結構** | 條件判斷 | `if (cond) {}` | `if (cond) {}` | `if cond:` | `if (cond) begin end` | | | | | | | | **else** | 否則 | `else {}` | `else {}` | `else:` | `else begin end` | | | | | | | | **AND** | 且 | `&&` | `&&` | `and` | `&&` | | | | | | | |**OR**| 或|`||`| `||` | `or` | `||` | | **NOT** | 非 | `!` | `!` | `not` | `!` | | | | | | | | **等於** | equal | `==` | `==` | `==` | `==` | | | | | | | | **不等於** | not equal | `!=` | `!=` | `!=` | `!=` | | | | | | | | **大於** | greater than | `>` | `>` | `>` | `>` | | | | | | | | **小於** | less than | `<` | `<` | `<` | `<` | | | | | | | | **大於等於** | ≥ | `>=` | `>=` | `>=` | `>=` | | | | | | | | **小於等於** | ≤ | `<=` | `<=` | `<=` | `<=` | | | | | | | | **True 值** | 布林真 | 非 0 | `true` | `True` | `1'b1` | | | | | | | | **False 值** | 布林假 | `0` | `false` | `False` | `1'b0` | | | | | | | | **三元運算** | 條件指派 | `a ? b : c` | `a ? b : c` | `b if a else c` | `a ? b : c` | | | | | | | ```python if a > 0 or b < 5: ``` ```c if (a > 0 || b < 5) ``` #### 條件的等於與不等於 條件的等於 `if t == 1:`;條件的不等於 `if t !=0:` c++數學式 ```c++ if (nums_dict.find(val) != nums_dict.end()) // != .end() 不在.end()表示存在 if val in self.nums_dict: // python if (nums_dict.find(val) == nums_dict.end()) // == .end() 表示不存在 if val not in self.nums_dict: // python ``` ## OOP 物件導向,struct、class ### python class ```python # class定義不放input class mathperson: def __init__(self): self.age = None self.name = None p1 = mathperson() p1.age = 18 p1.name = Jack # class定義放input class mathperson: # class將相同類型的程式放在一起 def __init__(self, name, age): # __init__ 是當class被直接呼叫時,會做的動作 # 如person1 = mathperson("Alice", 30) # self是物件引用初始值 # 當class被呼叫後,self變成person1 self.name = name self.age = age self.count = 0 def n_years_pass(self, n): # 過了n年後,年紀是多少 self.count += 1 self.age += n def add(self, a, b): # 計算新輸入的a, b相加 self.count += 1 return a + b def get_count(self): return self.count person1 = mathperson("Alice", 30) person2 = mathperson ("Bob", 25) ``` ```python from test import mathperson # 引入test.py中的mathperson這個class from test import * # 引入test.py中的所有class from method.test import * # method資料夾內的test.py person1 = mathperson("Alice", 30) # 創建一個新的 Person 物件 #__init__ 使 Person("Alice", 30) 等同 __init__(1, "Alice", 30),但不能真的直接呼叫__init__ print(person1.name) #在創建過程中,__init__ 方法被呼叫,並且設定了 name 和 age 屬性 print(person1.age) answer1 = person1.add(5, 3) # 使用class中的函式語法,直接加 .func print(f"計算次數:, {person1.get_count()} and 兩數字相加:, { {answer1}") # 獲取計算次數 person2 = mathperson ("Bob", 25) # __init__ 再次work print(person2.name) print(person2.age) ``` ### C C++ OOP名詞 | 名稱 | 說明 | | --- | --- | | Encapsulation(封裝)|函式有回傳值叫`Getter`,僅寫入叫`Setter`| | Inheritance(繼承) |子類別使用父類別的函式,如蘋果繼承水果,不會繼承父類別的private、建構子、解構子| | Polymorphism(多型) | 配合virtual,動態繫結 (dynamic binding)。使用父類別指標宣告,那個指標隨時可以改成別的子類別,main裡執行時期才決定要呼叫哪個子版本的 speak() | | Abstraction(抽象) | 定義一個父類別不能被實例化(new),只能被繼承,如人類、形狀、水果這種父類別,通常透過 抽象類別或 純虛擬函式`virtual void draw() = 0;`實現| | Interface | 純虛擬類別,提供統一介面,抽象只要有一個純虛函式即可,Interface一定要全部都是純虛函式 | | Virtual Function(虛擬函式) | 允許子類別覆寫父類別函式,父類別語法,`virtual void draw() = 0;` 支援 動態繫結(dynamic binding),也就是 執行期決定呼叫哪個版本的函式,可以實現多型跟抽象 | | Pure Virtual Function | 強制子類別實作,用於抽象類別 | | Override(複寫)| 子類別重新定義父類別虛擬函式,子類別使用`override`這個語法| | Overloading(多載) | 相同名稱,參數型態或數量不同,呼叫同個函式很多次| | Access Specifiers(private/public/protected) |private:不能被修改且不能被繼承,protected:不能被修改但可以被繼承,class 預設 private,struct 預設 public | | Constructor / Destructor | 建立與銷毀物件的初始化與清理,一個class裡面可以有很多種建構子,C沒有建構子 | | this 指標 | 怕有名稱衝突,指向當前創建的物件的變數 | |Function Hiding(函式遮蔽)|父與子都有同名的函式(沒有 virtual)時,子類別的版本會蓋掉父類別的版本| ### Encapsulation(封裝) 透過 Getter / Setter 隱藏內部資料,函式名稱不一定要取名叫get / set ```cpp class Person { private: int age; public: void setAge(int a) { age = a; } int getAge() { return age; } }; int main() { Person p; p.setAge(25); cout << p.getAge(); } ``` ### Interface、Abstraction、Pure Virtual Function、Virtual Function、Inheritance、Override、Polymorphism - Interface(介面):純虛擬類別,提供統一介面 - Abstraction(抽象):定義介面或純虛擬函式 - Pure Virtual Function(純虛擬函式):強制子類別實作,用於抽象類別 - Virtual Function(虛擬函式):子類別可以修改 - Inheritance(繼承):子類別可使用父類別的方法 - Override(複寫):子類別重新定義父類別虛擬函式 - Polymorphism(多型):配合 `virtual`,執行時期才會決定呼叫哪個子版本的函式 ```cpp class Animal { public: virtual void speak() = 0; // 純虛擬函式 → 抽象 int age = 25; // 有非純虛函式,不是interface }; // Animal a; // 會報錯,只能被繼承 // 是抽象不是interface class Animal2 { // class裡面只有純虛函式,是抽象也是interface public: virtual void speak() = 0; virtual void breathe() = 0; }; class Animal3 { public: virtual void speak() { cout << "Animal sound" << endl; } }; // Animal3 a; // 不會報錯 // 不是抽象 class Dog : public Animal { // Inheritance語法 public: void speak() override { cout << "Woof!" << endl; } // override寫在子類別裡面 }; class Cat : public Animal { public: void speak() override { cout << "Meow!" << endl; } }; int main() { Dog d; // 這不是多型,Dog d已經寫死是dog了 d.speak(); // 編譯前就知道一定是Dog Animal* a = new Dog(); delete a; // 釋放 Dog a = new Cat(); // 指向 Cat a->speak(); // 執行時才知道是Cat // a可以隨時變換 } ``` ### Overloading(多載) 相同名稱但參數型態或數量不同 ```cpp class Math { public: int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } }; int main() { Math m; cout << m.add(1, 2) << endl; cout << m.add(1.5, 2.5) << endl; } ``` #### Access Specifiers(存取權限) 控制可見範圍;`class` 預設 private,`struct` 預設 public ```cpp class Example { private: int secret = 10; public: void show() { cout << secret; } }; int main() { Example e; e.show(); // OK // e.secret; // 錯誤: private } ``` ### Constructor / Destructor(建構子 / 解構子) 建立與銷毀物件時自動執行初始化與清理,一個class裡面可以有很多種建構子 ```cpp class MyVector { public: MyVector() : n(0), m(nullptr) {} private: int n; int* m; }; class MyVector2 { public: int n; int* m; }; int main() { MyVector v; // 有自動初始化 MyVector2 v; // 沒有自動初始化,n跟m會是未知值 } ``` ### this 指標 指向當前物件,用於區分變數或鏈式操作 ```cpp class Counter { private: int value; public: Counter(int v) { this->value = v; } Counter& add(int x) { value += x; return *this; } void show() { cout << value << endl; } }; int main() { Counter c(5); c.add(3).add(2).show(); // 鏈式呼叫 } ``` ### Function Hiding(函式遮蔽) 父與子都有同名的函式(沒有 virtual)時,子類別的版本會蓋掉父類別的版本 ```cpp class Base { public: void show() { cout << "Base" << endl; } }; class Derived : public Base { public: void show() { cout << "Derived" << endl; } }; int main() { Derived d; d.show(); // 呼叫 Derived 版本 } ``` # C C++ ## C C++宣告 | 資料型態 | 中文說明 | 範例值 | 記憶體大小(常見) | | ------------------ | ------------ | ----------------- | ----------- | | **char** | 單一字元 | 'A', 'b', '3' | 1 byte | | **unsigned char** | 不含負數的字元 / 整數 | 0~255 | 1 byte | | **short** | 短整數(含負) | -32768 ~ 32767 | 2 bytes | | **unsigned short** | 無號短整數 | 0 ~ 65535 | 2 bytes | | **int** | **整數** | -21 億 ~ 21 億 | 4 bytes | | **unsigned int** | 無號整數 | 0 ~ 42 億 | 4 bytes | | **long** | 長整數 | 視系統而定 | 4 或 8 bytes | | **long long** | 更大的整數 | -2⁶³ ~ 2⁶³-1 | 8 bytes | | **float** | **小數(單精度)** | 3.14 | 4 bytes | | **double** | 小數(雙精度) | 3.1415926 | 8 bytes | | **long double** | 超高精度小數 | 視編譯器(10~16 bytes) | 10~16 bytes | | **bool**(C++) | 布林 | true/false | 1 byte(實際) | | 型態 | 中文說明 | 範例 | | ---------- | --------- | -------------------- | | **char[]** | 字元陣列 → 字串 | `char s[] = "ABC";` | |**int[]** |array|`int a[3] = {1,2};`| | **char*** | 指向字串的指標 | `char *p = "Hello";` | | **int*** | 指向整數的指標 | `int *p;` | (C 沒有 STL) | 型態 | 說明 | 範例 | | -- | -- | -- | | **vector** | 動態陣列 | `vector<int> v;` | | **string** | 字串類別 | `string name = "Jack";` | | **map<K, V>** | key-value 結構 | `map<string, int> mp;` | | **set** | 自動排序,不重複 | `set<int> s;` | ## C C++差異 | 項目 | C | C++ | | ----- | --------------------------------- | ----------------------------------------------- | | 語言類型 | 結構化程式語言 | 支援物件導向(OOP) + 結構化程式 | | 標準庫 | 標準 C 函式庫 `<stdio.h>` `<stdlib.h>` | 標準 C++ 庫 `<iostream>`, STL `<vector>` `<map>` 等 | | 記憶體管理 | 手動(malloc/free) | 支援 new/delete,還可用智能指標(C++11) | | 函式多載 | 不支援 | 支援函式多載(同名不同參數) | | 運算子多載 | 不支援 | 支援(可以自訂 +, -, << 等) | | 結構 | `struct` 只存資料 | `struct`/`class` 可存資料與函式(方法) | | 引用 | 無 | 有引用 `int &a = b;` | | 名稱空間 | 無 | `namespace`,避免命名衝突 | 輸入輸出 | C | C++ | | -------------------------- | -------------------------------- | | `printf("Hello %d\n", x);` | `cout << "Hello " << x << endl;` | | `scanf("%d", &x);` | `cin >> x;` | 記憶體分配 | C | C++ | | ------------------------------------- | ------------------- | | `int *p = (int*)malloc(sizeof(int));` | `int* p = new int;` | | `free(p);` | `delete p;` | 字串處理 | C | C++ | | ------------------------------------------------- | ------------------------------- | | char 陣列 + 函式:`char str[100]; strcpy(str, "abc");` | `string s = "abc"; s += "def";` | | 不支援直接比較字串 | `==` 可直接比較 `string` | 容器 | C | C++ | | ------- | ------------------------------------ | | 陣列或自訂結構 | STL:`vector`, `list`, `map`, `set` 等 | ```cpp vector<int> v; v.push_back(10); cout << v[0]; ``` 函式差異 * **C**:同名函式只能一個(無多載) * **C++**:支援多載 ```cpp int add(int a, int b) { return a+b; } double add(double a, double b) { return a+b; } // 多載 ``` 結構/類別差異 C: ```c struct Node { int data; struct Node* next; }; ``` C++: ```cpp struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} // 建構子 }; ``` * C++ 可以在 struct/class 裡放 **函式、建構子、解構子** * C 只能存資料欄位 其他常用差異 | 功能 | C | C++ | | ---- | ------------ | ------------------------------------- | | bool | 無(用 int 0/1) | 有 `bool`,true/false | | enum | 基本 enum | enum class(類型安全) | | 預處理器 | `#define` 常用 | 可以用 `const`, `inline`, `constexpr` 替代 | | 指標 | 完全依賴指標 | 可以用引用 & 智能指標,減少手動管理 | **總結** * **C**:簡單、結構化,手動管理記憶體,多用函式、指標 * **C++**:C 的超集,支援 OOP、STL(標準模板函式庫)、引用、函式多載、物件管理,更高階抽象 ## pointer 指標 ### 語法 | 符號 | 內容/房間 | 說明 | | -- | ----- | --------------------- | | x | 5 | x 的value是 5 | | &x | A房間 | &口,口是變數,取其位址,用 A 房間表示 | | *p | 5 |*口,口是地址,讀其地址的變數value| | p | A房間 | p 是x 的位址,A 房間 | | &p | B房間 | p真正的房間 | ```cpp int a = 5; // 此為簡寫 int *p = &a; // 宣告p變成指標變數,並指向a的地址 // 此為展開後 int *p; // 宣告p變成指標變數 p = &a; // 指向 a 的地址 ``` ```cpp int x = 5; int *p = &x; *p = 10; // 指定這個位子的value是10 /* x → 10 *p → 10 p → 還是 A 房間 */ int y = 20; p = &y; // 改變p這個變數指向的地址 /* p → y 的房間 *p → 20 x → 5 不變 */ ``` |![image](https://hackmd.io/_uploads/r15wf8ngbl.png)|![image](https://hackmd.io/_uploads/S1YkQU3xWx.png)| |- | - | - 地址賦值給指標 ```cpp struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} }; Node* top; Node* newNode = new Node(val); newNode->next = top; top = newNode; // 因為top跟newNode都是指標,直接改top指向的位子 ``` ### 變數、stack、heap ```c int a = 10; // 建立在stack上 int *p = &a; // p 指向 stack 上的 a *p = 20; // 修改 a 的值 int* p = (int*)malloc(sizeof(int)); // 建立在heap上 *p = 5; // 同p[0]是5 // 實際上並不知道heap上value是5的位子的identify是什麼 ``` - int*p = malloc 在heap上建立任何東西 其實都看不到heap層的id - 所以用指標會覺得不知道自己在指什麼,但實際上指到了 ### 為什麼要用指標? call by address,func輸入指標(地址)改stack的value - call by value,是讀`5`這個數字 - call by address,是讀`0x100`這個地址 - 想改main上stack的任何變數、指標,都要傳stack上的地址 - 指標 + malloc將記憶體分配到 heap 層:函式可以操作 heap 上的資料,任何函式外也可見 - Python 為何沒有指標?:因為 Python 使用 mutable(可變)型別,例如 `list`、`dict`、`set`,函式可以直接修改物件內容,而不需要指標概念。 ```c void func(int* c) { *c = 10; // 指定這個位子的value是10 } int main() { int a = 5; func(&a); printf("%d", a); // 透過指標間接改值 } ``` |address|identifier|value| |-|-|-| |0x100|a|5| |0x200|c|0x100| |0x300|*c|10| ### 建立空指標 - 只要看到建立指標,就要知道在stack建立了一個變數 ```c int *a; /* stack address | id | value | 0x1000 | a | ??? | */ a = (int*)malloc(sizeof(int)); // 在 heap 配置一塊可存 int 的空間,並把位址存進 a // 同int*a = (int*)malloc(sizeof(int)); /* stack address | id | value | 0x1000 | a | 0x3000 | heap address | id | value | 0x3000 | ???| ??? | */ *a = 5; // 將 5 存進 heap 中 a 指向的位置 /* stack address | id | value | 0x1000 | a | 0x3000 | heap address | id | value | 0x3000 | ???| 5 | */ ``` ### func輸入指標的地址改stack的指標的value - 要改stack上的變數或指標,func都要input其地址 - 看到func有輸入指標 `func(Node *head)` 此時`head`在func內就是地址,指向Node - `func(Node **head)` 此時`*head`在func內就是地址,指向Node* ```c int *b = NULL; func(&b); // 寫法1 void,用func更改指標 b = func2(b); // 寫法2 int*,用func回傳地址賦值給指標 // 想要改 stack 上的指標(b 指向哪) // 一定要傳入指標的位址 → int ** // call by address 也適用於更改指標的value(地址) void func(int **c) { *c = (int*)malloc(sizeof(int)); // 配置 heap,並改變外層指標 b (*c) 指向某個heap地址,該地址放int **c = 10; int *d; // d 是 func stack 內的指標 d = (int*)malloc(sizeof(int)); // heap 配置空間 // c 跟 d 會在 func 結束後消失 // 若不 return 或 free, d 指向的這塊 heap 會 memory leak } int* func2(int* e) { int f = (e == NULL) ? 10 : *e; int *g = malloc(sizeof(int)); *g = f + 10; return g; // 回傳heap建立的地址 } ``` ### python自帶指標功能 ```python # list範例 a = [1, 2, 3] b = a # b 指向同一個 list b[0] = 10 print(a) # [10, 2, 3] # class範例 class Node: def __init__(self, val): self.val = val a = Node(5) b = a # b 指向同一個 Node 物件 b.val = 10 print(a.val) # 10 ``` 對應c++ ```cpp #include <iostream> #include <vector> using namespace std; // 直接學python的寫法沒用 int main() { vector<int> a = {1, 2, 3}; vector<int> b = a; // 拷貝 a 的內容 b[0] = 10; for(int v : a) cout << v << " "; // 1 2 3 return 0; } int main() { vector<int>* a = new vector<int>{1, 2, 3}; vector<int>* b = a; // b 指向同一個 vector (*b)[0] = 10; for(int v : *a) cout << v << " "; // 10 2 3 delete a; return 0; } class Node { public: int val; Node(int v) : val(v) {} }; // 直接學python的寫法沒用 int main() { Node a(5); Node b = a; // 值拷貝 b.val = 10; cout << a.val << endl; // 5 return 0; } int main() { Node* a = new Node(5); Node* b = a; // b 指向同一個 Node b->val = 10; // 等效 (*b).val cout << a->val << endl; // 10 delete a; return 0; } ``` ### 台大上課補充 - `*(&x)`,指標提取`&x`這個地址,所以`*(&x) equal x` - `int* p`跟`int *p`是一樣的 - 宣告用的`int* p`跟取值用的`*p`是完全不一樣的 - 取值的`&x`跟reference的`int &x = a;`是完全不一樣的 - 宣告空的指標要用`int* p = nullptr;` - 掃描array時可以知道a[0]的指標跟當下a[i]的指標,那要如何知道當下的i是多少? `int* a = a[0];` `int* p = a[i]` `p-a equal i` - call by value的時機: - 不想改變main宣告的變數 - 函式只需要讀變數,不需要改它 - call by pointer的時機: - 想改變main宣告的變數但函式沒return - 使用指標的時機: - Dynamic memory allocation - Dynamic arrays - Dynamic data structures - C strings ### `++*p`, `*p++`, `*++p`的不同 - 討論兩件事情: - 賦值 - 事後p的指標指向哪裡 - ++放後面,事後再+,++放前面,賦值前先處理 ```c #include <stdio.h> int main() { int arr1[] = {10, 20, 30}; int arr2[] = {10, 20, 30}; int arr3[] = {10, 20, 30}; int *p1 = arr1; int *p2 = arr2; int *p3 = arr3; // ++*p // ++(*p) int val1 = ++*p1; // arr1[0] 原本 10 → 11 printf("%d %d\n", val1, *p1); // 11 11 ← *p1 和 val1 都是 11 // *p++ int val2 = *p2++; // 先取 arr2[0] = 10,然後 p2 指向 arr2[1] /* 後置遞增縮寫 int val2 = *p2; // 先取值 p2 = p2 + 1; // 再指標遞增 */ printf("%d %d\n", val2, *p2); // 10 20 ← val2 = 10,*p2 = 20 // *++p // *(++p) int val3 = *++p3; // p3 先移到 arr3[1],再取值 arr3[1] = 20 printf("%d %d\n", val3, *p3); // 20 20 ← val3 = 20,*p3 = 20 return 0; } ``` ### function pointer 函式指標 ```c int (*math)(int a, int b); math = add; ``` ```c // (int*)(*f(int,int))(int) int*(*f(int,int))(int){ // 讀取f回傳的函數指標 // 所以這邊要寫的是f的功能 // f回傳的函數input是int,output是int* }; ``` ```c typedef int* (*G_FUNC)(int); G_FUNC f(int a, int b); ``` ```c #include <stdio.h> int* g1(int x) { int x = x + 100; return &x; } int* g2(int x) { int x = x * 10; return &x; } int *(*f(int a, int b))(int) { if (a + b > 10) return g1; // 因為你前面定義了回傳int*,所以會回傳 g1 的函式指標 else return g2; // 回傳 g2 的函式指標 } int main() { int *(*func_ptr)(int); // 第一次呼叫 f,給兩個 int func_ptr = f(7, 5); // 7+5=12 > 10 → 會回傳 g1 int *p = func_ptr(123); // 第二次呼叫 func_ptr ,給一個 int printf("Result = %d\n", *p); // g1(123) = 123 + 100 = 223 return 0; } ``` ### 群聯考古題1 What is the result? ```c void change(int *a, int &b, int c) { c = *a; b = 3; *a = 2; return; } void main() { int a = 1, b = 2, c = 3; change(&a, b, c); printf(“%d %d %d”, a, b, c); return; } ``` ``` 2 3 3 ``` ### 群聯考古題2 What is the result? ```c int a[] = {1,2,3,4,5,6}; int *p = a; *(p++) += 100; // postfix先做等號兩邊運算 再做括號內的運算 所以是p[0]+100 再位址+1到p[1] *(++p) += 100; //prefix先做括號內的運算 再做等號兩邊運算 所以先位址+1到p[2]再取值+100 for(int i = 0; i<6 ; i++) printf("%d ", a[i]); //補充 int a[] = {1,2,3,4,5,6}; int *p2 = a; int *p3 = a; int e = (*p2)++; //e = *p = 1, 再e+1 但沒有e = e+1 所以++沒用 int f = ++*p3; //f = *p2 + 1 = 2 cout << "e =" << e << endl; cout << "f =" << f << endl; ``` ``` a = {101, 2, 103, 4, 5, 6} e = 1 f = 3 ``` ## array and pointer 陣列與指標 ### 陣列a退化成地址 - 非常反直覺,宣告好陣列a後,大部分a單獨出現時會退化成地址`0x1000` - `siezof(a)`, `int *a`, 這種時候陣列a不會退化 ### 1D array main建立好array -> func(int *a) ```c // main int arr[5] = {1, 2, 3, 4, 5}; // 存入stack int *p = arr; // 合法 // 因為陣列退化成指標 a會變成存a的地址 int *p = &arr[0]; // 等價 int x = 10; int *p = x; // 不合法 int *p = &x; // 正確 func(arr); // 傳入 stack 陣列 int *aaa = malloc(sizeof(int) * 5); // 存入heap aaa[0] = 1; func(aaa); free(aaa); // func void func(int *a) { a[0] = 99; // 改變 arr[0] } ``` * main:定義 stack 陣列 `arr[5]`,內容初始化 * func input:`int* a`,退化成 pointer * 操作結果:func 可以改內容,但不能 malloc 新記憶體給 arr * 記憶體位置:全部在 stack ```c int nums[3] = {1, 2, 3}; // 傳陣列,用索引操作 void sort(int arr[], int size) { // 同 int *arr for (int i = 0; i < size; i++) arr[i] += 10; // 編譯器幫你隱式轉成 *(arr + i) } // 傳指標,用指標運算 void sort(int *arr, int size) { for (int i = 0; i < size; i++) *(arr + i) += 10; // 同*arr++ += 10; } // 用指標 + 指標移動 void sort(int *arr, int size) { int *ptr = arr; /* 同 int *ptr; int ptr = &arr[0]; 用 ptr 不會修改原本的 arr */ for (int i = 0; i < size; i++) *ptr++ += 10; // 先解引用,再讓指標移到下一個元素 } ``` 三種寫法功能完全等價,只是表達方式不同 | 寫法 | 內部本質 | 好處 | 何時常用 | | ------------ | --------------- | -------- | ----------------------------- | | `arr[i]` | 隱式 `*(arr + i)` | 易懂、直觀 | 一般陣列操作 | | `*(arr + i)` | 指標運算 | 明確顯示位址運算 | 教學、低階操作 | | `*ptr++` | 指標迭代 | 適合走訪動態結構 | linked list、pointer traversal | ### 指標的指標 int** ```c int* a; // a是指向int(整數)的指標 int** b; // b是指標的指標 pointer to pointer // b是指向int*的指標 // 指向指向整數的指標的指標 // b的value地址 →地址 →整數 ``` |address|identifier|value| |-|-|-| |0x100|c|5| |0x200|a|0x100| |0x300|b|0x200| ```c int* change(int* aaa, int* bbb) { aaa[0] = 10; // 等價於 *(aaa + 0) *bbb = 10; return aaa; } int main() { int aaa[5] = {0}; int bbb = 0; int* res = change(aaa, &bbb); printf("aaa[0] = %d\n", aaa[0]); // 10 printf("bbb = %d\n", bbb); // 10 return 0; } ``` - `int* change(int* aaa, int* bbb)`兩個都是 `int*` - aaa 指向陣列第一個元素 - bbb 指向單一整數 - 在 C 裡函式不能直接回傳陣列,只能回傳指標 - res是指向陣列第一個元素的指標 ### 1D array main建立指標 -> func(int **a) malloc建立array在Heap 並回傳給 caller ```c // main int* p = NULL; // 只是定義指標,尚未分配記憶體 func(&p); // 傳入 p 的地址 // func void func(int **a) { *a = malloc(sizeof(int) * 5); // malloc 給 caller (*a)[0] = 42; } ``` * main:定義 pointer,未指向有效記憶體 * func input:`int** a` → 可以 malloc 並修改 main 的 pointer * 你建立了一個指標,你想在函式內修改這個指標(建立malloc),就必須傳這個指標的地址,所以要傳入指標的指標進func * 函式內才用`malloc`建立`array` * 操作結果:main 的 `p` 會指向 heap 上的新 array * 記憶體位置:array 在 heap,指標本身在 stack ### 2D array main建立好array在stack -> func(int a[][3]) ```c // main int arr[2][3] = {{1,2,3},{4,5,6}}; // 在記憶體上是 1D 連續的,但在型態與語意上仍然是 2D array func(arr); // 傳入 stack 二維陣列 // func void func(int a[][3]) { // func(int (*a)[3]) // 不能int **a,因為記憶體排序其實是1D // 這裡的[3]是在說a每次移動3格 a[0][0] = 99; // 改變 arr[0][0] } ``` * main:定義 stack 二維陣列 `arr[2][3]` * func input:`int a[][3]` 或 `int (*a)[3]` * 操作結果:func 可以改內容,但不能 malloc 新 array 給 arr * 記憶體位置:全部在 stack ### 2D array main建立好array在heap -> func(int **a) ```c // main int **arr = malloc(sizeof(int*) * 2); for(int i = 0; i < 2; i++) arr[i] = malloc(sizeof(int) * 3); // 已分配記憶體 func(arr); // 傳入已 malloc 的 array // func void func(int **a) { a[0][0] = 99; // 可以直接改內容 } ``` * main:malloc 外層指標和每一層子陣列 * func input:`int** a` * 操作結果:func 可以直接改內容 * 記憶體位置:array 在 heap,指標本身在 stack Stack | Address | Identify | Value | | ------- | --------- | ---------------------- | | 0x1000 | arr | 0x5000 (指向 heap 外層陣列) | Heap | Address | Identify | Value | | ------- | --------- | ---------------------- | | 0x5000 | arr[0] | 0x6000 (指向第一列 int[3]) | | 0x5008 | arr[1] | 0x7000 (指向第二列 int[3]) | | 0x6000 | arr[0][0] | ? | | 0x6004 | arr[0][1] | ? | | 0x6008 | arr[0][2] | ? | | 0x7000 | arr[1][0] | ? | | 0x7004 | arr[1][1] | ? | | 0x7008 | arr[1][2] | ? | ### 指標陣列(陣列裡的元素是指標) ```c int* a[3]; // 陣列裡的每個元素都是 int* int** pa = a; // 指向陣列裡的元素 → int** // a 退化成 &a[0] int*** c = &pa; // c 指向 pa a[0] = malloc(sizeof(int) * 5); // 每個元素又指向一個 int 陣列 a[0][0] = 10; // 等價pa[0][0] = 10 c[0][0][0] // 最終等於 a[0][0] → 10 ``` * `int* a[3]` → 陣列裡的每個元素都是 int* * `int** pa` → 指向這個陣列裡的指標元素 * a指標陣列 * pa是pointer to pointer * `pa[i][j] ≡ *(*(pa + i) + j)` * 因為陣列a會退化成0x1000,a[0] → 0x2000(heap 開始地址) * a[0][0] → *(a[0] + 0) → *(0x2000) → 值 = 10 * pa[0] → *(pa + 0) → *(0x1000) * *(0x1000) → a[0] 的值 → 0x2000(heap 地址) Stack | Address | Identify | Value | | ------- | -------- | ------------------ | | 0x1000 | a | 0x2000 (a[0]) | | 0x1008 | | 0x0000 (a[1] 尚未指向) | | 0x1010 | | 0x0000 (a[2] 尚未指向) | | 0x1018 | pa | 0x1000 (指向 a[0]) | Heap | Address | Identify | Value | | ------- | -------- | ----- | | 0x2000 | a[0][0] | 10 | | 0x2004 | a[0][1] | ? | | 0x2008 | a[0][2] | ? | | 0x200C | a[0][3] | ? | | 0x2010 | a[0][4] | ? | * 用法對應 LeetCode 的 2D array: ```c int** result = malloc(sizeof(int*) * capacity); // result[i] 是 int*(每個三元組) result[i] = malloc(sizeof(int) * 3); // malloc 真正的三元組 result[i][0] = nums[i]; ``` ### 2D array main建立指標 -> func(int ***a) malloc建立array在Heap 並回傳給 caller ```c // main int **arr = NULL; func(&arr); // 傳入 arr 的地址 // func void func(int ***a) { *a = malloc(sizeof(int*) * 2); // malloc 外層 for(int i=0;i<2;i++) (*a)[i] = malloc(sizeof(int) * 3); // malloc 每一列 (*a)[0][0] = 42; } ``` * main:只定義指標,未 malloc * func input:`int*** a` → 可以 malloc 外層 pointer 給 caller * 操作結果:main 的 `arr` 會指向 heap 上的 2D array * 記憶體位置:外層和內層都在 heap ### 總整理 ```c int a; // 單一變數 int *p; // 指標 int a[5]; // array int *a[5]; // array of pointer int (*a)[5]; // pointer to array,input進func的形式 ``` | Array 類型 | main 做什麼 | func input | pointer 層數 | 需 malloc 給 caller? | | ------------------- | ----------------------- | ---------- | ---------- | ------------------ | | Stack 1D | arr[5] 初始化 | int* a | 1 | ❌ | | Heap 1D | int* p=NULL | int** a | 2 | ✅ | | Stack 2D | arr[2][3] 初始化 | int a[][3] | 2D pointer | ❌ | | Heap 2D | int** arr = malloc(...) | int** a | 2 | ❌(已 malloc) | | Heap 2D + malloc 回傳 | int** arr=NULL | int*** a | 3 | ✅ | ## string and pointer 字串與指標 ### char *str → func(char *s),指標(pointer to char) ```c // main char *str = "dog"; str[0] = 'D'; // 無法更改(UB) // func void func(char *s); /* Stack str ──► 0x3000 RODATA 0x3000 d o g \0 */ ``` * `"dog"` 在 rodata(唯讀資料區) * `str` 在 stack * 本體是 指標 * 不可改內容(Undefined Behavior) ### char str[] → func(char *s),char array(陣列) ```c // main char str[] = "dog"; str[0] = 'D'; // 合法 func(str); // func void func(char *s) { s[0] = 'D'; } /* Stack str[0] = 'd' str[1] = 'o' str[2] = 'g' str[3] = '\0' */ ``` * 字串內容在 stack * 本體是 陣列 * 傳入 func 時 退化成 char* * 可以改內容 * 只要宣告時有 `[]`,本體一定是 array ### char *str = malloc(...) → func(char *s),指標(指向 heap 上的 char array) ```c // main char *str = malloc(4); strcpy(str, "dog"); /* Stack str ──► 0x5000 Heap 0x5000 d o g \0 */ ``` * `str` 在 stack * 字串內容在 heap * 本體仍然是 指標 * 可以改內容 * 需要 `free(str)` ### static char str[] / global char str[],char array ```c static char str[] = "dog"; // or char str[] = "dog"; // global /* Data segment d o g \0 */ ``` * 字串內容在 data segment * 本體是 陣列 * 不在 stack、不在 heap * 可以改內容 ### char *str[] → func(char **s) 本體是:指標陣列(array of char*) ```c // main char *str[] = {"cat", "dog"}; func(str); // func void func(char **s); /* Stack str[0] ──► 0x3000 str[1] ──► 0x3010 RODATA 0x3000 c a t \0 0x3010 d o g \0 */ ``` * `str` 本體在 stack * 每個元素是 `char*` * 指向的字串在 rodata * 本體是 array * 傳入 func 時退化成 `char** ` * 不可改字串值 ### 總整理 | 宣告 | 本體 | 字串在哪 | | -------------------- | ----- | ------ | | `char *s = "dog"` | 指標 | rodata | | `char s[] = "dog"` | array | stack | | `char *s = malloc()` | 指標 | heap | | `static char s[]` | array | data | | `char *s[] = {...}` | 指標陣列 | rodata | > 宣告看「[] 在哪」,使用看「退化後是什麼」 ## reference 引用 ```cpp int b = 10; int &ref = b; // 此時ref等同b這個變數 // 此寫法不是縮寫 ``` - call by reference 是 call by pointer的捷徑 - call by call by pointer 是 call by reference的原理 - 因為call by pointer比較複雜,所以使用call by referece,以swap舉例 ```cpp // call by reference int main() { int a = 10, b = 20; cout << a << " " << b << n"; cout << &a << n"; swap(a, b); cout << a << " " << b << n"; } void swap(int & x, int &y) { cout << &x << n"; int temp = x; x = y; y = temp; } ``` ## c malloc & c++ new ```cpp // malloc: C 語言函式,不會呼叫建構子 int* arr1 = (int*)malloc(5 * sizeof(int)); free(arr1); // new: C++ 運算子,會呼叫建構子 int* arr2 = new int[5]; delete[] arr2; // 以上宣告的是array [0, 0, 0, 0, 0] ``` - malloc - 給左邊identifier的value一個地址 - 那個地址在Heap上取得一段指定大小的連續記憶體 - malloc 經常用來建立C陣列的array - 用malloc建立陣列的優點是可以在程式跑的過程中決定大小 - 看不懂就列出`|address|indentifier|value|` - Python:list 幾乎就是日常用的主要容器 - C:array 最常用,linked list 偶爾用 - C++:vector 幾乎取代了大部分需要 list 的情況,真正的 linked list (std::list) 很少用 ### leetcode15 result 的用法 ```c int** result = (int**)malloc(sizeof(int*) * 5); // result = [[], [], [], [], [],] result[*returnSize] = (int*)malloc(sizeof(int) * 3); // result = [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3],] // result是5x3矩陣,5個row,3個column result[*returnSize][0] = nums[i]; result[*returnSize][1] = nums[left]; result[*returnSize][2] = nums[right]; ``` ```c int** result = (int**)malloc(sizeof(int*) * 5); // 地址分配在result這個identifier的value ``` Stack(指標本身) | address | identifier | value | | ------- | ---------- | ----- | | 0x080 | result | 0x100 | Heap(5 個 int*) | address | element | value | | ------- | --------- | ----- | | 0x100 | result[0] | ? | | 0x108 | result[1] | ? | | 0x110 | result[2] | ? | | 0x118 | result[3] | ? | | 0x120 | result[4] | ? | ```c result[0] = (int*)malloc(sizeof(int) * 3); // 地址分配在result[0]這個identifier的value result[0][0] = 1; result[0][1] = 2; result[0][2] = 3; ``` | address | element | value | | ------- | --------- | ----- | | 0x080 | result | 0x100 | | 0x100 | result[0] | 0x200 | | 0x108 | result[1] | ? | | 0x110 | result[2] | ? | | 0x118 | result[3] | ? | | 0x120 | result[4] | ? | | 0x200 | result[0][0] | 1 | | 0x204 | result[0][1] | 2 | | 0x208 | result[0][2] | 3 | - 最後因為是 int** threeSum,所以return result的value 0x100 ## memory 記憶體 ### 記憶體區域總覽 ``` +-----------------------+ | Code Segment | <- 程式碼 +-----------------------+ | RO Data / Const | <- const 變數、字串 +-----------------------+ | Data Segment | <- 全域 / static 變數 +-----------------------+ | Heap | <- new / malloc 動態分配,額外增加的空間,只要是指標就能使用 +-----------------------+ | Stack | <- func內變數、參數 +-----------------------+ ``` | 區域 | 存放對象 | 生命週期 | 常用語法 / 函式 | | ----------------------- | ---------------- | ------------------------ | ---------------------------------- | | **Stack(堆疊)** | 函式內的局部變數、參數、回傳地址 | 隨函式呼叫/結束自動分配與釋放 | `int x = 5;`、`char arr[10];` | | **Heap(動態記憶體區)** | 動態分配物件 / 陣列 | 直到 `delete` / `free` 才釋放 | `new` / `delete`、`malloc` / `free` | | **Data Segment (靜態區)** | 全域變數、static 變數 | 程式啟動到結束 | `int g = 10;`、`static int s = 5;` | | **Code Segment (程式碼區)** | 程式本身的機器碼 | 程式啟動到結束 | 只讀,不可改寫程式碼 | | **常數區 (RO Data)** | `const` 變數、字串常數,不可更改 | 程式啟動到結束 | `const int c = 5;`、`"Hello"` | ### Stack 詳細 * 優點:分配快速、自動管理 * 缺點:大小有限,過大可能造成 stack overflow * 用法: ```cpp void func() { int x = 10; // 存在 stack char buf[100]; // 存在 stack } ``` ### Heap 詳細 * 優點:大小彈性,可動態控制生命週期 * 缺點:分配慢,需要手動釋放,否則會記憶體洩漏 * 用法: ```cpp // C++ 語法 int* p = new int(5); // 分配單一 int int* arr = new int[10]; // 分配陣列 delete p; // 釋放單一 int delete[] arr; // 釋放陣列 // C 語法 int* p2 = (int*) malloc(sizeof(int) * 10); free(p2); ``` ### Data Segment / 靜態區 / 全域 static 變數 ```cpp class Example { public: static int staticVar; // 宣告一個「靜態成員變數」 }; Example::staticVar = 300; // 可直接透過類別名稱存取 Example a, b; a.staticVar = 400; // 也可透過物件名,但其實是同一個變數 cout << b.staticVar; // 會輸出 400 ``` ```cpp int g = 100; // 全域變數 -> Data Segment static int s = 50; // static 變數 -> Data Segment void main() { int x = 10; // 存在 stack char buf[100]; // 存在 stack } ``` * 全域變數、static 變數會在程式啟動時分配 * 程式結束自動釋放 ### Code Segment(程式碼區) ```cpp void func() { // 函式指令儲存於 code segment } ``` * 儲存程式機器碼 * 通常不可修改 ### 常數區(只讀資料) ```cpp const int c = 10; // 儲存在常數區 const char* str = "Hello"; // 字串常數 -> 常數區 ``` * 儲存不可變的常數 * 防止程式修改 ### 為什麼一定要用heap ```cpp Stack: enqueue() { Node newNode --> 函式結束(只要return一次)就消失 } Heap: enqueue() { Node* newNode = new Node --> 永遠存在 } ``` ### 我如果都全域宣告,是否就能避免使用指標(heap)? ```c int data = 10; // 全域變數 void func() { data = 20; // 直接改! } // 這裡就不需要用: void func(int *p) { *p = 20; } ``` 在小型程式,很多人會為了簡單直接用全域變數取代指標傳遞 風險: - 無法重複利用函式(失去模組化): 假設 ```c void sort() { // sort global array } // 這只能排序你那個全域陣列,但如果你改寫成: void sort(int *arr, int size) { // sort 任何傳進來的陣列 } ``` - 可讀性與維護性差:如果程式有 10 個函式都在改 `global_data`,誰改過、何時改的,很難追蹤 - 執行緒安全(Thread Safety) 多執行緒同時操作全域變數會發生衝突(race condition),使用區域變數或用指標傳遞資料能避免互相干擾 ## typedef vs. typedef stuck vs. stuck - `typedef`定義宣告名稱 ```c typedef unsigned int u32; u32 a = 100; // 等於 unsigned int a = 100; ``` ```c struct Point { int x; int y; }; struct Point p1; // 必須加 struct typedef struct Point { int x; int y; } Point; Point p2; // 不需要寫 struct ``` | 寫法 | 宣告變數方式 | | -------------------------------------- | ---------------- | | 只用 `struct node` | `struct node a;` | | 加上 `typedef struct node { ... } Node;` | `Node a;` | | 名稱 | 說明 | | -------------------------- | -------------------- | | `struct node` | 原始結構體型別名稱 | | `typedef struct node Node` | 幫這個型別取個簡短名字 `Node` | | `Node` | 就是 `struct node` 的別名 | ## 面試常見問題 ### Swap Function 差別 (C 語言) 為什麼要呼叫指標swap才能work? ```cpp #include <stdio.h> // call by value -> 無法交換 void swap1(int a, int b) { int temp = a; a = b; b = temp; } // call by pointer -> 可以交換 void swap2(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; swap1(x, y); printf("After swap1: x=%d y=%d\n", x, y); // 不會變 swap2(&x, &y); printf("After swap2: x=%d y=%d\n", x, y); // x=10 y=5 } ``` A:參考c記憶體區域,呼叫值僅在swap函數內生效,僅在stack層生效,return後兩個數值又回歸原本的值,要呼叫指標才能更改heap層的數值 ### C: `i++` `++i` 差異,python沒有 ```c // 後置遞增 int i = 0; value = i++; // value = 0, i = 1 int i = 0; value = ++i; // value = 1, i = 1 // 沒賦予變數沒差 i++; ++i; ``` ### 實做 C str所有函式 ```c #include <stdio.h> size_t my_strlen(const char *str) { size_t len = 0; while (str[len] != '\0') { len++; } return len; } char* my_strcpy(char *dest, const char *src) { char *p = dest; while (*src != '\0') { *p++ = *src++; } *p = '\0'; return dest; } int my_strcmp(const char *s1, const char *s2) { while (*s1 && (*s1 == *s2)) { s1++; s2++; } return *(unsigned char*)s1 - *(unsigned char*)s2; } char* my_strcat(char *dest, const char *src) { char *p = dest; while (*p) p++; // 移到 dest 的結尾 '\0' while (*src) *p++ = *src++; *p = '\0'; return dest; } int main() { char str1[50] = "Hello"; char str2[] = ", world!"; char str3[50]; printf("=== Testing my_strlen ===\n"); printf("Length of '%s' = %zu\n\n", str1, my_strlen(str1)); printf("=== Testing my_strcpy ===\n"); my_strcpy(str3, str1); printf("Copied string: '%s'\n\n", str3); printf("=== Testing my_strcmp ===\n"); char a[] = "abc"; char b[] = "abd"; printf("Compare '%s' and '%s' = %d\n", a, b, my_strcmp(a, b)); // -1 printf("Compare '%s' and '%s' = %d\n", a, a, my_strcmp(a, a)); // 0 printf("Compare '%s' and '%s' = %d\n\n", b, a, my_strcmp(b, a)); // 1 printf("=== Testing my_strcat ===\n"); my_strcat(str1, str2); printf("Concatenated string: '%s'\n", str1); return 0; } ``` ### C手刻vector ```c #include <stdio.h> #include <stdlib.h> typedef struct { int *data; int size; int capacity; } Vector; // 初始化 void init(Vector *v, int capacity) { v->data = (int*)malloc(sizeof(int) * capacity); v->size = 0; v->capacity = capacity; } // 釋放記憶體 void free_vector(Vector *v) { free(v->data); v->size = 0; v->capacity = 0; } // push_back void push_back(Vector *v, int val) { if (v->size == v->capacity) { v->capacity *= 2; v->data = (int*)realloc(v->data, sizeof(int) * v->capacity); } v->data[v->size++] = val; } // erase idx void erase(Vector *v, int idx) { if (idx < 0 || idx >= v->size) return; for (int i = idx; i < v->size - 1; i++) v->data[i] = v->data[i+1]; v->size--; } // resize void resize(Vector *v, int new_size) { if (new_size > v->capacity) { v->capacity = new_size; v->data = (int*)realloc(v->data, sizeof(int) * v->capacity); } v->size = new_size; } // 印出 vector void print_vector(Vector *v) { printf("["); for (int i = 0; i < v->size; i++) { printf("%d", v->data[i]); if (i != v->size - 1) printf(", "); } printf("]\n"); } int main() { Vector v; init(&v, 2); push_back(&v, 10); push_back(&v, 20); push_back(&v, 30); print_vector(&v); // [10, 20, 30] erase(&v, 1); print_vector(&v); // [10, 30] resize(&v, 5); print_vector(&v); // [10, 30, 0, 0, 0] free_vector(&v); return 0; } ``` # 韌體0x10 ## 預處理器 (Preprocessor):`#define`聲明 >用預處理指令 `#define` 聲明一個常數,用以表示一年中有多少秒 (忽略閏年問題) ```c #define SECOND_PER_YEAR (365 * 24 * 60 * 60)UL ```` * `#define` 語法基本知識(不能以分號結束、括號的使用) * 命名習慣:大寫 + 底線 * 預處理器會計算常數運算式 * 在 16-bit 系統上可能整數溢位,因此需要加 `L` * 使用 `UL`(unsigned long)避免符號與大小問題 | 型態名稱 | 關鍵字 | 常見大小 (32/64-bit) | printf 格式化符號 | | ------- | -------------- | ---------------- | ------------ | | 字元 | char | 1 | %c | | 無號字元 | unsigned char | 1 | %u | | 短整數 | short | 2 | %d | | 無號短整數 | unsigned short | 2 | %u | | 整數 | int | 4 | %d | | 無號整數 | unsigned int | 4 | %u | | 長整數 | long | 4/8 | %ld | | 無號長整數 | unsigned long | 4/8 | %lu | | 單精度浮點數 | float | 4 | %f | | 倍精度浮點數 | double | 8 | %f | | 長倍精度浮點數 | long double | 12/16 | %Lf | > 整數常數預設為 `int`,浮點數常數預設為 `double`。 > 若要避免溢位或明確指定型態,需加上後綴字: | 後綴 | 意義 | | ----- | --------- | | U/u | unsigned | | L/l | long | | LL/ll | long long | | F/f | float | 範例: ```c unsigned int a = 10U; long b = 10L; float c = 3.14F; long double d = 2.06L; unsigned long e = 25UL; ``` ## 預處理器 (Preprocessor):Macro 巨集 文字取代功能 >寫一個標準 `MIN` 巨集,輸入兩個參數並回傳較小的那個。 ```c #define MIN(X, Y) ((X) >= (Y) ? (X) : (Y)) ``` * `#define` 用於巨集的基礎知識 * 三元運算子 `?:` 的使用 * 必須在巨集參數外加括號以防副作用 副作用範例: ```c least = MIN(*p++, b); // 會展開為: // *p 可能被遞增一次或兩次 least = ((*p++) >= (b) ? (*p++) : (b)); // 解法: int val = *p++; least = MIN(val, b); ``` ## 預處理器 (Preprocessor):`#error`的作用 ```c #error message ``` 在編譯時輸出錯誤訊息並中斷編譯 範例: ```c #include <stdio.h> //#define NUM 3 int main(void) { #ifndef NUM // if not defined #error NOT define NUM #endif printf("NUM = %d", NUM); return 0; } ``` ## 無窮迴圈 (Infinite loops) 嵌入式系統常見的無窮迴圈寫法: ```c while (1) { /* statement */ } for (;;) { /* statement */ } ``` ## 資料宣告 (Data declaration) | 類型說明(中文) | 類型說明(英文) | 宣告語法 | | ----------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------- | | 一個整數 | An integer | `int a;` | | 一個指向整數的指標 | A pointer to an integer | `int *a;` | | 一個指向指標的指標,它指向的指標是指向一個整數 | A pointer to a pointer to an integer | `int **a;` | | 一個有 10 個整數的陣列 | An array of 10 integers | `int a[10];` | | 一個有 10 個指標的陣列,該指標是指向一個整數 | An array of 10 pointers to integers | `int *a[10];` | | 一個指向有 10 個整數陣列的指標 | A pointer to an array of 10 integers | `int (*a)[10];` | | 一個指向函式的指標,該函式有一個整數參數且回傳一個整數 | A pointer to a function that takes an integer as an argument and returns an integer | `int (*a)(int);` | | 一個有 10 個指標的陣列,該指標指向一個函式,該函式有一個整數參數並回傳一個整數 | An array of ten pointers to functions that take an integer argument and return an integer | `int (*a[10])(int);` | ## static `static` 有三種用途: * 函式內的變數:函式結束後仍保留數值 * 模組內的全域變數:僅該檔案可存取 * 函式:僅限該檔案可呼叫 ### `static` vs. `extern` 這是 **變數與函式作用域 (scope)** 的重要概念。 | 關鍵字 | 用途 | 範圍 / 壽命 | 使用場合 | | ---------- | ---- | ---------------------- | ------------- | | **static** | 靜態 | 只在定義的檔案或函式內可見(不能被外部使用) | 限制作用範圍、防止外部干擾 | | **extern** | 外部宣告 | 告訴編譯器這個變數或函式在別的檔案定義 | 多檔案共用全域變數或函式 | ```c // file1.c static int count = 0; // 只在 file1.c 有效 void add() { count++; } // file2.c extern void add(); // OK,宣告函式 // printf("%d", count); // 無法使用 static 變數 ``` ```c // file1.c int value = 10; // 定義 // file2.c extern int value; // 宣告 printf("%d", value); // 可讀取 ``` | 關鍵字 | 儲存位置 | 作用範圍 | 可否被外部使用 | | ------ | ----- | ---- | ------- | | static | 全域變數區 | 檔案內 | ❌ | | extern | 全域變數區 | 跨檔案 | ✅ | ## const | 宣告| 意義 |舉例 | | --- | --- | --- | | `const int a;` | 整數常數 | | `int const a;` | 整數常數 | | `const int *a = &x;` | 指向整數常數的指標 (整數值不可改),綁住`*`a對指標來說是value|```*a = 15; 不能改變 a 所指的內容,a = &y; 可以改變 a 指向哪裡``` | | `int *const a;` =`int *a`+`const` | 常數指標 (地址不可改) | | `const int *const a;` | 皆不可改 | > 小訣竅:從右往左解釋 ## volatile `volatile` 告訴編譯器: >「這個變數可能在程式看不見的地方被改變,不要優化讀寫。」 如果沒有 `volatile`,編譯器可能會把變數讀一次放在暫存器,後續運算都用暫存器的值,可能錯過外部變化。 常見用途: 1. 週邊裝置暫存器 2. ISR 可存取的變數 3. 多執行緒共享變數 進階問題: | 問題 | 答案 | | ---------------------------- | --------------- | | 可同時為 `const` 和 `volatile` 嗎? | 可以。例如唯讀暫存器 | | 指標可為 `volatile` 嗎? | 可以,例如 ISR 修改指標 | | 下列函式有何錯誤? | 語義錯誤,應先存區域變數再運算 | ```c int square(volatile int *ptr) { int temp = *ptr; return temp * temp; } ``` ```c #include <stdio.h> #include <stdbool.h> bool button_pressed = false; volatile bool v_button_pressed = false; // 用 volatile int main() { // 模擬硬體中斷改變按鈕狀態 v_button_pressed = true; // 假設中斷改變這個值 button_pressed = true; // 假設中斷改變這個值 // 沒用 volatile 的情況 while (!button_pressed) { // 編譯器可能認為 button_pressed 永遠是 false,優化成無窮迴圈 // do nothing } // 用 volatile 的情況 while (!v_button_pressed) { // 每次都重新讀取,能正確跳出迴圈 // do nothing } printf("Button pressed!\n"); return 0; } ``` * `button_pressed` 可能被編譯器優化掉,讀一次就不再讀。 * `v_button_pressed` 因為 `volatile`,每次迴圈都會重新讀取記憶體。 ## Interrupt Service Routine ISR ```c __interrupt double compute_area(double radius) { double area = PI * radius * radius; printf("\nArea=%f", area); return area; } ``` 錯誤原因: - ISR 是硬體觸發的函式,像是按鈕按下、計時器溢位等事件 - ISR 不能有參數與回傳值 - 不建議使用浮點運算 - 不建議在 ISR 中使用 `printf`(可重入性問題) 正確 ISR 範例(C 語法簡化) ```c #include <stdio.h> #include <stdint.h> volatile uint16_t timer_flag = 0; void __interrupt timer_ISR(void) { timer_flag = 1; // ISR 簡單操作,設定 flag } int main() { // 模擬主程式 while (1) { if (timer_flag) { printf("Timer triggered!\n"); timer_flag = 0; } } return 0; } ``` * ISR 只做「簡單、快速」的操作,例如設定 flag * 主程式讀 flag 去做複雜運算或輸出 ### 中斷 Interrupt 中斷(Interrupt)= 一個「打斷CPU」的事件 - 鍵盤按下一個鍵 → 產生中斷 → CPU跳去執行「鍵盤處理程式」 - 定時器時間到 → 產生中斷 → CPU執行「定時器中斷處理程式」 8051 的中斷機制讓系統可以即時處理特定事件,而不必「一直輪詢 (polling)」去檢查事件是否發生 | 中斷名稱 | 含義 | 觸發方式 | 向量位址 | | --------- | ---------- | --------------- | ------- | | **IE0** | 外部中斷 0 | 可設成電平觸發或邊緣觸發 | `0003H` | | **TF0** | 定時器 0 溢出中斷 | Timer0 overflow | `000BH` | | **IE1** | 外部中斷 1 | 可設成電平觸發或邊緣觸發 | `0013H` | | **TF1** | 定時器 1 溢出中斷 | Timer1 overflow | `001BH` | | **RI/TI** | 串列通訊中斷 | 接收或傳送完成 | `0023H` | ## 位元操作 (Bit Operation) - bitwise manipulation - 往右邊8bit = 除以2的8次方 - 往左邊8bit = 乘以2的8次方 - 因為位移比乘法快很多,可以切換到bitwise的操作 - 把bit設為0,跟0做AND - 把bit設為1,跟1做OR - 微分做xor,有變化是1、沒變化是0 設定與清除 bit 3 (bit3 = 0000 1000 = 0x08): ```c a = a | 0x08; // 設定a的bit3 = 1 a = a & (~0x08); // 清除a的bit3 = 0 ``` 更好的可移植寫法: ```c #define BIT3 (0x1 << 3) void set_bit3(void) { a |= BIT3; } void clear_bit3(void) { a &= ~BIT3; } ``` > 需用指標轉型成 `int *` 才能存取指定位址 | 運算子 | 名稱 | 說明 | 範例 | | - | - | - | - | | `&` | AND(且)| 兩邊都為 1 才是 1 | `1100 & 1010 = 1000` | | `\|` | OR(或)| 只要有一邊是 1 即為 1| `1100 \| 1010 = 1110` | | `^` | XOR(互斥或) | 不同為 1,相同為 0 | `1100 ^ 1010 = 0110` | | `~` | NOT(反相)| 0→1、1→0 | `~0101 = 1010` | | `<<` | 左移 | 乘以 2ⁿ | `0001 << 2 = 0100` | | `>>` | 右移 | 除以 2ⁿ | `1000 >> 3 = 0001` | ## `unsigned`,求result ```c void foo(void) { unsigned int a = 6; int b = -20; (a + b > 6) ? puts(">6") : puts("<=6"); } ``` 輸出: ``` >6 ``` 原因:`signed` + `unsigned` → 轉為 `unsigned`,導致 `b` 被轉為非常大的正數。 ## `unsigned` 以下程式何者有錯? ```c unsigned int zero = 0; unsigned int compzero = 0xFFFF; ``` 不同平台整數長度不同,在 32-bit 系統上此值為 `0x0000FFFF` 正確寫法: ```c unsigned int compzero = ~0; ``` ## 動態記憶體分配 (Dynamic Memory Allocation) 嵌入式系統中使用動態配置的問題: * Heap 空間有限 * 碎片化 (fragmentation) * 無法保證即時性 * 無法輕易釋放導致記憶體洩漏 ## 存取固定記憶體位址 ```c int main() { int *ptr = (int *)0x67a9; *ptr = 0xaa55; return 0; } ``` ## 遞增運算子 Increment Operator ```c int a = 5, b = 7, c; c = a+++b; // 等價於: c = (a++) + b; ``` 結果: ``` a = 6; b = 7; c = 12; ``` * "maximum munch" 原則:盡可能解析最長合法 token ## typedef ```c #define dPS struct s * typedef struct s * tPS; ``` **建議使用 typedef** **原因:** ```c dPS p1, p2; // 展開為 struct s *p1, p2; (p2 不是指標) tPS p3, p4; // 皆為 struct s * 類型 ``` `typedef` 更安全且語義清楚 ## 8051 ### 8051 是什麼? 8051是 Intel 在1980年推出的一款8位元微控制器(Microcontroller, MCU)。 它是一顆「小型電腦晶片」,內部整合了 CPU、記憶體、I/O 埠、計時器與中斷控制器等模組。 > 8051 = 一顆可以執行程式、控制外部設備的「迷你電腦」 ### 8051 的主要組成架構 | 模組| 功能說明 | | - | - | | CPU(Central Processing Unit) | 控制與運算核心| | ROM(程式記憶體)| 儲存使用者程式(通常 4KB) | | RAM(資料記憶體) | 儲存變數(128 Bytes) | | I/O Ports | 4 組(P0~P3),可連接 LED、按鍵、感測器等 | | Timer/Counter | 2 組計時器(Timer0、Timer1) | | Serial Port | 串列通訊埠,可做 UART 通訊 | | Interrupt System| 支援 5 個中斷源(外部、Timer、Serial) | | Oscillator & Clock | 提供時脈訊號(通常是 12MHz)| ### 8051 與一般電腦的差別 | 項目 | 微控制器(8051) | 一般電腦(PC) | | ---- | --------------------- | ------------------- | | 構成 | 單晶片整合 CPU、記憶體、I/O | CPU、RAM、硬碟分開 | | 目的 | 控制特定裝置(嵌入式系統) | 通用計算 | | 執行環境 | 實時、無作業系統 | 有 OS(Windows/Linux) | | 儲存容量 | 幾 KB ~ 幾十 KB | 幾十 GB 起跳 | | 範例 | 洗衣機控制板、微波爐、電風扇、車用 MCU | 筆電、伺服器 | ### 8051 的記憶體架構 8051 採用「**哈佛架構(Harvard Architecture)**」: 程式記憶體(ROM)與資料記憶體(RAM)是**分開**的。 ``` ┌───────────────┐ │ ROM (程式區) │ ← 儲存程式碼 ├───────────────┤ │ RAM (資料區) │ ← 儲存變數、堆疊 └───────────────┘ ``` ### 常見功能介紹(重點複習) | 功能 | 簡介 | 範例 | | -------------------------- | --------------- | ------------ | | **I/O 控制** | 可控制 LED、按鍵輸入 | `P1 = 0xFF;` | | **Timer/Counter** | 定時或計數外部事件 | 計算時間、週期性中斷 | | **Interrupt(中斷)** | 即時處理事件 | 按鍵觸發 ISR | | **UART 串列通訊** | 與 PC 或其他 MCU 溝通 | 傳送資料 | | **位元操作 (Bit-addressable)** | 直接操作單一位元 | `SETB P1.0` | ### 常見面試 / 口試題目整理 | 類型 | 常見問題 | 你可以怎麼回答 | | --------- | ---------------------- | ------------------------------------------------- | | **基本概念** | 什麼是 8051? | 一種 8-bit 微控制器,內含 CPU、RAM、ROM、I/O、Timer、UART、中斷系統。 | | **架構理解** | 8051 有幾個 I/O port? | 4 個:P0、P1、P2、P3。 | | **記憶體** | RAM 與 ROM 的大小? | 128 Bytes RAM、4KB ROM(標準版本)。 | | **中斷** | 8051 有幾個中斷來源? | 5 個中斷源(IE0、TF0、IE1、TF1、Serial)。 | | **Timer** | Timer0 和 Timer1 有什麼作用? | 可作為計時器或事件計數器。 | | **應用** | 給一個 8051 實際應用例子? | 電子時鐘、LED 閃爍、溫度感測控制器。 | | **組語題** | MOV A, #55H 代表什麼? | 將立即數 55H 傳入累加器 A。 | | **中斷題** | ISR 是什麼? | Interrupt Service Routine,中斷服務程式。 | ### 8051範例程式 ```c #include <reg51.h> // 基礎延遲 void delay() { unsigned int i, j; for(i = 0; i < 1000; i++) for(j = 0; j < 120; j++); } // UART:傳送單一字元 void uart_send(char c) { SBUF = c; while(TI == 0); TI = 0; } // UART:傳送字串 void uart_send_string(char *s) { while(*s) { uart_send(*s); s++; } } // Timer0 中斷:LED 翻轉 void Timer0_ISR(void) interrupt 1 { P1_0 = ~P1_0; } // 外部中斷 INT0:亮 LED void External_INT0(void) interrupt 0 { P1_0 = 0; } // LED 閃爍 void demo_led_blink() { while(1) { P1 = 0x00; delay(); P1 = 0xFF; delay(); } } // 點亮單一 LED(P1.0) void demo_led_single() { P1 = 0xFF; P1_0 = 0; while(1); } // 按鍵控制 LED(P3.2) void demo_button_led() { while(1) { if(P3_2 == 0) P1_0 = 0; else P1_0 = 1; } } // Timer0 週期中斷 void demo_timer0_interrupt() { TMOD = 0x01; TH0 = 0xFC; TL0 = 0x66; ET0 = 1; EA = 1; TR0 = 1; while(1); } // UART 傳送字串 void demo_uart() { SCON = 0x50; TMOD = 0x20; TH1 = 0xFD; TR1 = 1; while(1) { uart_send_string("Hello\n"); delay(); } } // 外部中斷 INT0 void demo_ext_int0() { EA = 1; EX0 = 1; IT0 = 1; while(1); } // 軟體 PWM(簡易) void demo_pwm() { while(1) { P1_0 = 1; _nop_(); _nop_(); P1_0 = 0; _nop_(); _nop_(); } } void main() { // 根據需要啟用其中一個功能: // demo_led_blink(); // demo_led_single(); // demo_button_led(); // demo_timer0_interrupt(); // demo_uart(); // demo_ext_int0(); // demo_pwm(); // 預設跑 LED 閃爍 demo_led_blink(); } ``` # Python ## Python 常用的內建函式語法: ### `print` ```python i, j, k = 3, 4, 5 print("i") #輸出i print(i) #輸出3 # print 一次印多個數值 print(f"i={i}, j={j}, k={k}") #輸出i=3, j=4, k=5 print("i=", i, "j=", j, "k=", k) print("i=" + str(i) + ", j=" + str(j) + ", k=" + str(k)) ``` ### `def` (要加冒號),傳值呼叫 ( )內為函式內的變數,python雖然沒有括號,在函式內空格還是要對整齊,最後return的值,是函式最後的輸出 ```python def main(a, b): #程式碼 return 0 #return為這個函式的輸出 ``` Python定義任何函式都用def,C++定義函式要依據回傳值決定 ![004](https://hackmd.io/_uploads/SkYW0BHsxx.png) leetcode 1.twoSum ```python class Solution(object): def twoSum(self, nums, target): for i in range(len(nums)): for j in range(i+1, len(nums)): if nums[i] + nums[j] == target: return i, j nums = [2, 7, 11, 15] target = 9 print(Solution.twoSum(1, nums, target)) ``` ### `return` vs `print` retrun是func輸出的值,要用print印出來 ### Python 讀取 Module,讀取同個資料夾內檔案名叫 `tool.py` 程式的四種方法 ```python import tool #第一種呼叫module法 print(tool.sum_mul(1, 2, 3, 4)) #要加. import tool as t #第二種呼叫module法,改掉呼叫module的名稱 print(t.sum_mul(1, 2, 3, 4)) #要+. from tool import sum_mul #第三種呼叫module法,指定呼叫某個def print(sum_mul(1, 2, 3, 4)) from tool import * #第四種呼叫module法,全部呼叫,若主程式內有與module相同名稱的程式會有bug print(sum_mul(1, 2, 3, 4)) ``` ### `if __name__ == "__main__":`,防止被引用時誤跑程式 ```python def sum_mul(a, b, c, d): return (a+b+c)*d if __name__ == "__main__": print("直接執行時才會列印,可以防止被引用時執行") if __name__ == "tool": print("被引用時才會列印") # __name__是一個python內部的變數,當程式直接執行時會變__main__,被引入時會變「檔案名稱」 # 使用if __name__ == "__main__":是為了防止被引入時執行內部的程式 ``` #### 範例 ```python def main(): print("hello") if __name__ == "__main__": #防止被引用時直接執行 main() ``` ### `map` 大量宣告 `int` ```python # map(函式, 可迭代物件) 是一種簡潔的「批次運算」方式 map(int, …) map(float, …) num1 = "123" num2 = "456" result = num1 + num2 print(result) # 輸出 "123456",而不是整數相加的結果 num1 = int(num1) # 將字符轉換為整數 num2 = int(num2) # 將字符轉換為整數 result = num1 + num2 print(result) # 輸出整數相加的結果,即 579 ``` #### 用map一次對一個變數要用list外包 ```python num1 = "123" num2 = "456" num1 = list(map(int, [num1])) #因為用list,所以是列表,值存在num1[0] num2 = list(map(int, [num2])) # 將字符轉換為整數並放入列表 result = num1[0] + num2[0] # 從列表中提取整數值並相加 print(result) # 輸出整數相加的結果,即 579 num1, num1 = map(int, [num1, num1]) # 用map的多重賦值特性,達成一次對一個變數不用list result = num1 + num2 print(result) # 輸出整數相加的結果,即 579 ``` ### `stored` ```python sorted(iterable(遞迴物), key=函數) # 有點像.sort print(sorted([5, 3, 9, 1])) # [1, 3, 5, 9] ``` ### `lambda` 匿名函式 - `lambda func`匿名函式:一行內建的小函式,可以在 `sorted()`、`map()`、`filter()` 等地方使用,不用額外`def` - `lambda` 參數1, 參數2, ... : 表達式、運算結果 ![003](https://hackmd.io/_uploads/B14NABHjex.png) ```python add = lambda x, y: x + y print(add(3, 5)) # 8 data = [('apple', 5), ('banana', 2), ('cherry', 8)] sorted_data = sorted(data, key=lambda x: x[1]) # 按數字大小排序 print(sorted_data) nums = [1, 2, 3, 4] squared = list(map(lambda x: x ** 2, nums)) print(squared) # [1, 4, 9, 16] nums = [1, 2, 3, 4, 5, 6] even_nums = list(filter(lambda x: x % 2 == 0, nums)) print(even_nums) # [2, 4, 6] from functools import reduce nums = [1, 2, 3, 4] product = reduce(lambda x, y: x * y, nums) print(product) # 24 (1*2*3*4) data = ['apple', 'banana', 'cherry'] sorted_data = sorted(data, key=lambda x: x[1]) # 用每個字串的 第 2 個字元 來排序 # ['banana', 'cherry', 'apple'] ``` ### 輸入以空格相間的列表 ```python user_input = input("請輸入一些文字: ") #等待用戶輸入一行以空格分隔的數字或單詞 words = user_input.split() #.split是將字串轉成list,"hello world" →["hello", "world"] print(words) for word in words: print(word) # 打印分割後的單詞列表 data = [int(x) if x.isdigit() else x for x in words] print(data) ``` ### 輸入多個資料 `input().split()` + `map(int, …)` + `list` ```python x, y = map(int, input("輸入兩個整數(以空格分隔): ").split()) print(f"{x},{y}") numbers = list(map(int, input("輸入多個整數(以空格分隔): ").split())) #比上面多一個list print(numbers) floats = list(map(float, input("輸入多個浮點數(以空格分隔): ").split())) print(floats) words = input("輸入多個字串(以空格分隔): ").split() print(words) ``` ### enumerate 使迴圈變比較好寫,第一個i表示編號,第二個j表示值 ```python data = ['a', 'b', 'c', 'd', 'e'] for index, obj in enumerate(data): print(f"Index: {index}, Value: {obj}") strs = ["flower","flow","flight"] for i, char in enumerate(strs[0]): # 以第一個flower為基準 for word in strs: # 依序比對所有字元的i=1、i=2、i=3等 # 超出字串範圍,或字串不同,直接結束返回結果 if i >= len(word) or word[i] != char: return ans ``` ### List Comprehension(列表生成式) ```python squares = [x**2 for x in range(10)] ``` >用一行簡潔的語法產生新 list,常取代 for 迴圈。 輸入 (Input): `range(10)` 輸出 (Output): `[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]` 可加條件判斷,例如 `[x for x in range(10) if x % 2 == 0]` 效率高、語法簡潔 * Generator / yield(生成器) ```python def gen(): for i in range(3): yield i for x in gen(): print(x) ``` >建立可逐步產生值的迭代器,節省記憶體 輸入 (Input):** 無 輸出 (Output):** 依序輸出 `0, 1, 2` `yield` 暫停函式狀態,下次繼續執行,有點像建立好一個list 適合大資料流,例如逐行讀檔 ### Decorator(裝飾器) ```python def timer(func): def wrapper(): print("Start") func() print("End") return wrapper @timer def hello(): print("Hello World") hello() ``` >在不修改原函式的情況下,額外包裝功能 可以用在記錄執行時間、登入檢查、快取控制 輸入 (Input): 呼叫 `hello()` 輸出 (Output): ``` Start Hello World End ``` ### Context Manager(上下文管理器) ```python with open('file.txt', 'r') as f: data = f.read() ``` >自動處理資源開啟與釋放(例如檔案)。 輸入 (Input): `'file.txt'` 輸出 (Output): 讀取到的檔案內容字串 不需手動 `close()` 即使出錯也會自動釋放資源 ## 數學有關的語法: ### 整數的大於、等於、小於 - 整數中 `i+1 >9` 同 `i>=9` - 整數中 `10>9` 同 `10-1>=9` ### 除法取整數 `//`,除法取餘數 `%` - 除法取整數 `t = 10//3` - 除法取餘數 `t = 10%3` ### 奇偶判斷,`x % 2 == 0`(例:x 10783) ```python if x % 2 == 1: # 奇數 if x % 2 == 0: # 偶數 ``` ### 找數列中最大值 `max(list)` ```python num = [(1,2.5),(1.5,3.2),(1.3,4.0),(2.2,1.8)] y, z = max(num, key=lambda x:x[0]) print(y,z) a, b = max(num, key=lambda x:x[1]) print(a, b) ``` ### 累加縮寫 `a += b` `a += b` 同 `a = a + b` ### 印小數點(`format`、`round`、`f"{:.2f}"`) 印到小數點第三位`print(f"{age:.3f}%")` ### 輸入變數後指定變數 ```python t = int(input("請輸入一個數字: ")) name = input("請輸入你的名字: ") print("你好," + name) ``` 常用在輸入指定變數後決定迴圈數量 ```python t = int(input()) # int表示把值變成可計算 for case in range(1, t + 1): ``` ### 兩數值交換 ```python c = a # 口 ← A a = b # A ← B b = c # B ← 口 # 簡寫為 a, b = b, a ``` ```python a, b = b, a % b ``` ![005](https://hackmd.io/_uploads/H19GASBieg.png) ### 歐幾里得找最大公因數(`math.gcd`) - 歐幾里德演算法參考https://hackmd.io/@donglinwu/HyOMwBhfY - 每次都會有三個數字,a, b, a%b,選其中最小的兩個繼續做,新a,新b,新a%新b,直到某a%某b = 0 為止 - 如 a=120, b=1512 - round1, a = 120, b = 1512,a%b = 120,此時120與1512的最大公因數跟1512與120的最大公因數相同 - round2, a = 1512,b = 120 ,a%b = 72,此時120與1512的最大公因數跟120與72的最大公因數相同 - round3, a = 120, b = 72, a%b = 48,此時120與1512的最大公因數跟72與48的最大公因數相同 - round4, a = 72, b = 48, a%b = 24,此時120與1512的最大公因數跟48與24的最大公因數相同 - round5, a = 48, b = 24, a%b = 0,此時120與1512的最大公因數同(48 ,24),最大公因數是24 ```python a = 120 b = 1512 def find_gcd(a, b): while b: a, b = b, a % b print(f"a = {a},b = {b}") print(a) return a find_gcd(a, b) ``` ## 匯入外部函式: ### `matplotlib.pyplot` ```python import matplotlib.pyplot as plt plt.plot(x, y) # 繪製折線圖(連續的線條) plt.stem(x, y) # 繪製桿狀圖(垂直線條,底部連接 x 軸,上方有圓點) plt.bar(range(0, 256), y) # 繪製條形圖(每個區間以矩形表示) b.imshow(image, cmap='gray') # 加入圖片image,gray是顯示灰階圖,沒這行不會有圖片 plt.figure(figsize=(width, height)) # 設置 Matplotlib 圖形的尺寸大小 plt.xlim([0, 12]) # 設一個大概的x範圍 plt.ylim([-60, 140]) plt.title(label) # 設置 Matplotlib 圖形的標題 plt.legend(labels) # 添加 Matplotlib 圖例,用於標識不同的數據序列或圖形 plt.xlabel("年資") # 設定x軸名稱 plt.ylabel("月薪(千)") plt.savefig('照片名稱') # 放在show前面就會儲存照片 plt.show() # 顯示 Matplotlib 圖形,將之前設置的圖形顯示出來 plt.figure() # 畫一張大圖放好幾個子圖 plt.suptitle(標題) # 加入第一個子圖 plt.subplot(1, 2, 1) # 這個子圖要放在哪裡,plt.subplot(總共的row, 總共的column, 位子) plt.axis('off') # xy軸不要出現 plt.imshow(A, cmap="gray") # 加入圖片A,gray是顯示灰階圖,沒這行不會有圖片 # 加入第二個子圖 plt.subplot(1, 2, 2) plt.axis('off') plt.imshow(B, cmap="gray") plt.savefig(f'HW4_{filename1} and {filename2}') plt.show() ''' plt.subplot(2, 2, 1) # 左上角的子圖 plt.subplot(2, 2, 2) # 右上角的子圖 plt.subplot(2, 2, 3) # 左下角的子圖 plt.subplot(2, 2, 4) # 右下角的子圖 ''' # 使用 fig.add_subplot() 函數來添加子圖 for i in range(4): fig = plt.figure(figsize=(12, 8)) for j in range(7): output_image = together[i][j] if j == 0: fig.add_subplot(n, m, 2) plt.imshow(output_image, cmap='gray') plt.title("{}\nSNR = {}".format(together_title[i], SNR(img, output_image))) else: fig.add_subplot(n, m, j + 3) plt.imshow(output_image, cmap='gray') plt.title("{} SNR = {:.6f}".format(title[j - 1], SNR(img, output_image))) plt.xticks([]) plt.yticks([]) plt.savefig(os.path.join(output_directory, f'HW8_image_{together_title[i]}.jpg')) plt.show() ``` --- ### `numpy` ```python import numpy as np # 陣列(array): # 矩陣的標準用法 A = np.mat([ [1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3] ]) # 用陣列也可以代表矩陣 # 上面是3x2矩陣,下面是2x3矩陣 aaa1 = np.array([np.array([-0.707, 0.707]), np.array([0.8,0.6]), np.array([ 0.,-1.])]) aaa2 = np.array([[-0.707, 0.8, 0], [0.707, 0.6, -1]]) a_array = np.array() # 函式創建數組,例如 arr = np.array([1, 2, 3, 4, 5]) a_array = np.zeros_like(A) # 函式創建與A同大小,元素全為零的矩陣 a_array = np.zeros() # 函式創建元素全為零的數組,例如 zeros_arr = np.zeros((3, 3)),np.zeros((列, 行)) a_array = np.ones() # 函式創建元素全為一的數組,例如 ones_arr = np.ones((2, 4)) a = np.round(a_array, decimals=6) # 取小數點到第六位,數字會變比較漂亮 # 兩種矩陣乘法 c = a @ b c = np.dot(a, b) A = np.array([[-0.707, 0.8, 0], [0.707, 0.6, -1]] # row在台灣是列,在大陸、日本是行 a_array = np.split(A, A.shape[1], axis=1) # np.split(切割A, 切割多少份, 由哪裡切), # A.shape[1]是行數 = 3,A.shape[0]是列數 = 2,A.shape = (2, 3) # axis = 0、1、2對應x、y、z,在此僅能用axis = 1由行切割 b_array = np.concatenate(a_array, axis=1) # 把分開的矩陣合在一起 max_idx = np.argmax(np.abs(w)) # np.abs(w),找w的最大值,argmax找w的最大值在矩陣中的位子 Anew[:, max_idx] = b_array[max_idx][:, 0] # 參考7.5 #[:, 0]是[列,行],冒號是選擇全部,所以[:, 0]是指定第0行 # 這邊b_array是由concatenate來的,由好幾列(口,口)疊在一起,只能用[max_idx][:, 0]的方式去找, # 其中[max_idx]是取列,測其他數字都沒用 Anew[:, max_idx] = 某數列,某數列加入到Anew的行裡面 a_inv = np.linalg.pinv(a) # 取a的反矩陣 # 數組屬性: array.shape # 獲取數組的形狀,例如 (3, 3) array.dtype # 獲取數組的數據類型,例如 int64 array.T # array的T轉換 # 數組索引和切片: arr[index] # 獲取數組中指定索引位置的元素,例如 arr[0] 獲取第一個元素。 arr[start:end] # 進行切片操作,獲取指定範圍的元素,例如 arr[1:4] 獲取第二個到第四個元素。 # 數學函式: np.sum() # 計算數組中元素的和,例如 total = np.sum(arr)。 np.mean() # 計算數組中元素的平均值,例如 avg = np.mean(arr)。 y = np.exp(x) # 計算e^x次方,例如x = np.arange(-5, 5, 0.1),y = np.exp(x) y = np.log(x) # 計算log(x),x = np.arange(1, 10, 0.1),y = np.log(x) y = np.sin(x) # 計算sin(x),x = np.arange(0, 2 * np.pi, 0.1),y = np.sin(x) np.fft.fft(x) # 計算fourier transform,time = np.array([0, 1, 2, 1, 0]),frequency = np.fft.fft(time) np.fft.ifft(x) # 逆fourier transform,frequency = np.array([0, 1, 2, 1, 0]),time = np.fft.ifft(frequency) # 矩陣運算: np.dot() # 執行矩陣乘法,例如 result = np.dot(matrix1, matrix2) np.transpose() # 獲取矩陣的轉置,例如 transposed_matrix = np.transpose(matrix) ``` --- ### `OpenCV` #### 112-1傅楸善CV HW只有hw3.5.8.有用到plt.savefig ```python import cv2 as cv img = cv.imread('lena.bmp') # 讀取跟.py檔同個資料夾名稱為lena.bmp的照片,並傳送到img這個位子 img = cv.resize(img, (1000, 600)) # 改變相片長與寬,比例可能會跑掉 cv.imwrite(image_eq) # 存照片到跟.py檔同個資料夾 cv.imshow('photo', img) # 輸出照片叫photo,從img內輸出;按enter可以關掉,plt.show不行 cv.imwrite('HW3_part(b)_divided_by_3.jpg', image2_new) cv.waitKey(0) # 鍵盤訊號或等待框內時間後關閉,0表示無限時間,1000表示1秒,plt.show不用刻意+這個 ``` --- ### `OS` ```python import os output_directory = 'results' os.makedirs(directory, exist_ok=True) # 等效以下 if not os.path.exists(output_directory): os.makedirs(output_directory) cv.imwrite(os.path.join(output_directory, 'equalized_image.jpg'), image_eq) # 一定要+ cv.waitKey(0)一張一張存 plt.savefig(os.path.join(output_directory, f'HW8_{together_title[i]}.jpg')) plt.savefig(os.path.join(output_directory, 'HW7_image.jpg')) # 等同以下 cv.imwrite('results/dif_gaussian.jpg', dif_gaussian) # 沒result資料夾,會自動建立,可以一次存很多張照片 plt.savefig('results/HW7_image.jpg') # 如果沒result資料夾,程式會卡住 ``` ## 資料處理相關的外部函式: ### 資料處理的流程 ```python input_file_name = 'P0001' input_dir = f'./data/{input_file_name}' output_dir = './ data_output' output_csv_file = f'./{output_dir}/output.csv' variable1 = 1 variable2 = 2 variable3 = 3 def function1(): def function2(): ``` 以下是我在撰寫 Python 時的習慣與原則: * 盡可能不使用 `def` 函式定義 * 不使用 `def process():` * 不使用 `if __name__ == "__main__"` * 把input/output路徑變數放在程式最上方 * Jupyter Notebook (`.ipynb`) 很方便,但我仍偏好標準的 `.py` ### 讀取資料夾內所有檔案 處理每張圖片 ```python input_folder = f"datasets/test" # 原始圖片路徑 for file_name in photo_files: file_path = os.path.join(input_folder, file_name) """ 檔案處理,如predict、解壓縮等,input是file_path """ ``` 處理所有檔案 ```python directory = "." directory = os.path.abspath(directory) # 確保路徑為絕對路徑 os.makedirs(directory, exist_ok=True) # 確定有此資料夾 for file_name in os.listdir(directory): # 遍歷目錄下所有檔案 file_path = os.path.join(directory, file_name) """ 檔案處理,如predict、解壓縮等,input是file_path """ ``` ### `pandas`(讀取 `csv`) ```python ### 智慧醫療程式設計 HW5-1的輸出圖片 import pandas as pd data = pd.read_csv('./100.csv') #讀跟.py檔案同個資料夾內的excel print(data.columns, data.shape) #data.columns是列的數量,data.shape是行的數量 #確認有讀到資料 import matplotlib.pyplot as plt def ecg_peak(data): plt.figure(figsize=(13, 5)) #設定圖形大小 plt.plot(data) #繪製折線圖 plt.show() #顯示圖形 ecg_peak(data["'MLII'"][:1000]) ecg_peak(data["'MLII'"][11000:12000]) ecg_peak(data["'MLII'"][51000:55000]) ### 讀excel跟網路上的csv import pandas as pd data = pd.read_csv("https://raw.githubusercontent.com/GrandmaCan/ML/main/Resgression/Salary_Data.csv") ### 410Lab讀mimi3的csv df = pd.read_csv("./60_people.csv") row = 1 gender = df.loc[row - 1, 'gender'] # 讀取第0 row的gender這個項目,第一行項目名稱不算row age = df.loc[row - 1, 'age'] death = df.loc[row - 1, 'death'] loadPath = df.loc[row - 1, 'loadPath'] ### 112-2數位訊號處理導論 import pandas as pd data = pd.read_csv('stock_price_globe.csv') data.head() # 選擇最後 5 個日期的資料 last_5_data = data.loc[11390:11395] # loc就是row,把第二列當0,實際範圍[0:14651] # 計算對數收益率 adj_close = last_5_data['Adj Close'] adj_close_shifted = adj_close.shift(1) # 取得上一期的 adj_close # 計算對數收益率 log_returns_Q1 = np.log(adj_close / adj_close_shifted) #Q1 顯示最後 5 個日期的對數收益率 print("\nLog Returns of Last 5 Dates in Taiwan:") print(log_returns_Q1[1:].values) # 不印出索引為 11390 的結果 ``` ### `json` ### `openpyxl` ## 範例程式: ### CPE 程式範本 ```python # 輸入用到的變數 t = int(input()) #跑幾個迴圈 # 輸入一個整數 for case in range(1,t+1): #跑1到t個迴圈 data = list(map(int, input().split())) # 輸入一個字串 # 1 2 3 4 5 # 讀取多個數字並轉換為整數列表 string = input() # 讀取單行字串 x, y = map(int, input().split()) # 輸入兩個整數以空格隔開 # 5 10 a, b, c = map(int, input().split()) # 輸入三個整數以空格隔開 int n; // 讀取輸入的行數 cin >> n; // 對應 t = int(input()) string s; getline(cin, s); // 清除緩衝區的 '\n',避免影響後續輸入 // 對應string = input() # 列表lis # list可以指定位子,如s[1][2] s = [] # 初始化空列表list來存放輸入的每一行 while True: try: s.append(input()) # 逐行加入到s except: break # 字串 number = ' ' number += '1' number += '0' # 數列,1D_array,可以指定位子,如number[2] number = [0]*3 for i in range(len(number)): number[i] = i # 2D 陣列 # 可以指定位子,如arr_2d[1][2],python中list更靈活更常用 arr_2d = np.array([[1, 2, 3], [4, 5, 6]]) arr_2d[0][0] = 10 # 常用的非輸入變數 count = 0 # 要計算某次數,放在迴圈外面 for i in range( ): count += 1 max_cols = max(max_cols, len(s[i])) # 一直更新最大值 # 迴圈範例 for case2 in range(a): # for迴圈 b+=1 while a>b: # while條件內迴圈 c+=1 while True: # while設定條件結束迴圈 a+=1 if 5>=a>0: # if用法 print("so small") elif a==6: print("mid") else: print("big") if a==10: # 條件必須用== print(data[1]) print(f"hello,world{a},I like {b}") break ``` ### `numpy` 實例 ```python ### 程式1 import numpy as np b_list = np.array([np.array([[-0.707],[ 0.707]]), np.array([[0.8],[0.6]]), np.array([[ 0.],[-1.]])]) Anew = np.array([[0., 0., 0.], [0., 0., 0.]]) Anew[:, 1] = b_list[1][:, 0] print(f"{b_list}\n{b_list[0][:, 0]}\n{Anew}") ### 程式2 import numpy as np x = np.array([[1,2,3], [4,5,6]])# array盡量不和matrix混用, #row列操作 a = x[0] #取第一列 [[1 2 3]] row在台灣是列,在大陸日本是行 print(f'x[0] is {a}\n') b = x[1] #取第二列 [[4 5 6]] print(f'x[1] is {b}\n') c = x[1,0:2] # 索引取值,取第二列,1元素 [[4 5]] print(f'x[1,0:2] is {c}\n') #column行操作 d = x[ :,0] #取第一行 print(f'x[ :,0] is\n {d}\n') ''' [[1] [4]] ''' e = x[:,:2] #取前两行 print(f'x[ :,:2] is\n {e}\n') ''' [[1 2] [4 5]] ''' f = x[:,-1]#取最后一行 print(f'x[ :,-1] is\n {f}\n') ''' [[3] [6]] ''' g = x[:,1:3]#取第2-3行 print(f'x[ :,1:3] is\n {g}\n') ''' [[2, 3], [5, 6]] ''' h = x[0:2,:]#取1-2列 print(f'x[0:2,:] is\n {h}\n') ''' [[1, 2, 3], [4, 5, 6]] ''' ``` ## 程式環境: ### IDE、compiler、interpreter #### IDE:集成開發環境(Integrated Development Environment) * 功能強大的記事本,撰寫、排版、除錯與執行程式的完整工具 * 常見 IDE: * PyCharm * VSCode * Visual Studio #### Compiler:編譯器 * 將原始碼編譯成可執行檔(例如 `.exe`) * 適用語言:C、C++、C#、Java(編譯成 JVM bytecode) * 常見 C/C++ 編譯器: * GCC * Clang * 安裝方式:可透過MinGW或CodeBlocks取得 #### Interpreter:解譯器 * 將程式逐行解釋執行 * 適用語言:Python、JavaScript、R 等。 * 常見解譯器: * Python 解譯器 * Node.js(JavaScript 執行環境) #### 系統環境變數 PATH 設定 * 不論是編譯器或解譯器,都必須將其路徑加入系統環境變數的PATH * 目的:讓 IDE 或 Terminal 能在任何資料夾直接找到對應的執行檔(如 `gcc`, `python`)。 #### 常見路徑範例: * C/C++ 編譯器路徑: ```txt C:\mingw64\bin C:\Program Files\CodeBlocks\MinGW\bin ``` * Python 解譯器路徑: ```txt C:\Users\User\AppData\Local\Programs\Python\Python311\ ``` ### Python interpreter 設定 #### pycharm創立新環境 ##### 選項 1:New environment(建立新環境) * 在專案資料夾內會自動創建一個 `venv` 子資料夾。 * 該資料夾即為虛擬環境,專案所需套件將安裝在此,不會影響其他專案。 ##### 選項 2:Previously configured interpreter(使用現有的 Interpreter) * 路徑範例: ``` C:\Users\User\AppData\Local\Programs\Python\Python311 ``` * 好處: * 之後新建的專案可以共用相同的 Interpreter。 * 統一管理套件與環境設定,避免重複安裝。 * 換電腦時,只需將該 Interpreter 資料夾整個複製到新電腦上即可繼續使用。 ![006](https://hackmd.io/_uploads/HJZ8RBSixg.png =80%x) #### 已經建立好project想改環境 * 使用 pip 安裝的套件,會依據所選 Interpreter 對應安裝到指定路徑。 * 專案建立後,也可以進入 `Settings > Project: 專案名稱 > Python Interpreter` 修改 Interpreter 設定。 ![007](https://hackmd.io/_uploads/rJ6ICBSsxx.png) ### C++ vscode、vs2019 設定 #### Visual Studio 2019(VS2019) * VS2019 在建立專案時,會在資料夾中自動建立 `.vs` 資料夾 * `.vs` 裡面包含設定檔(如 `.json`)來儲存 project 設定 * **可以透過 GUI 設定 include path 和 linker 選項**,不需手動編輯 `.json` * 這些設定完成後,系統會自動產生對應的 `.json` 設定檔 * 已於2024/02/29在臺大完成設定測試 #### Visual Studio Code(VSCode)工作區概念 * VSCode使用工作區(Workspace)管理專案設定 * 建立工作區會產生 `.vscode/` 資料夾,內含下列重要檔案: | 檔案名稱 | 說明 | | --------------- | --------------------------- | | `settings.json` | VSCode 的基本環境設定(如字體大小、換行寬度等) | | `launch.json` | 設定除錯器的執行參數與目標檔案 | | `tasks.json` | 設定自動化任務,例如編譯程式、執行指令腳本 | * 這些 `.json` 檔可透過 VSCode 介面設定,也可手動編輯,方便搬移設定。 #### 是否需要使用工作區? * 如果你主要在 Terminal 執行程式,就不太需要設定 `.vscode` 裡的 `.json` * 使用 Terminal 執行程式是最直接、簡單的方式 * 你也可以安裝套件(如Extension Pack、Code Runner)來執行程式: * 但這些套件執行時無法正確處理 `input()` 輸入類型的程式 ![008](https://hackmd.io/_uploads/rJuPABBjxl.png) #### 使用 Terminal 指令執行 C / C++ 程式 > 執行 C 程式 ```bash gcc name.c -o name; ./name.exe ``` > 執行 C++ 程式 ```bash g++ -o test test.cpp; ./test ``` ### Verilog 環境 #### 方法一:使用 Intel Quartus Prime(含波形顯示) 詳細使用步驟與圖示請參考另一份 Word 文件。 #### 方法二:使用 VSCode 搭配 Icarus Verilog + GTKWave * 使用VSCode作為文字編輯介面。 * 下載並安裝 [Icarus Verilog](http://bleyer.org/icarus/)(內含 `iverilog`、`vvp`、`GTKWave`)。 #### 專案檔案結構 將以下兩個檔案放在同一資料夾: * `test.v`:主設計檔 * `test_tb.v`:測試檔(testbench) > test\_tb.v 需額外加入以下程式碼以產生波形檔: ```verilog initial begin $dumpfile("test_tb.vcd"); // 產生 vcd 波形檔 $dumpvars(0, counter_tb); // 設定 testbench 模組名稱 end ``` > 執行步驟(在 Terminal 中操作) ```bash iverilog -o test.bar test.v test_tb.v # 編譯兩個檔案成一個可執行檔 vvp test.bar # 執行模擬,產出 test_tb.vcd gtkwave test_tb.vcd # 使用 GTKWave 開啟波形 ``` # Reference - [Hello Algo](https://www.hello-algo.com/zh-hant/) - [GeeksforGeeks](https://www.geeksforgeeks.org/) - [Leetcode](https://leetcode.com/) - [Collegiate Programming Examination](https://cpe.mcu.edu.tw/) - [HackerRank](https://www.hackerrank.com/) - [韌體0x10](https://hackmd.io/@ChenZE/ByOoO0LEee)