← 12/24|期末上機考(範圍 Ch 1–12、Ch 14) | 回總覽
這一頁是查的,不是讀的:忘了編譯指令或 Makefile 怎麼寫 → 〈常用 g++ 編譯選項〉、〈整學期通用的 Makefile〉;編譯或執行冒出看不懂的訊息 → 〈常見錯誤訊息對照表〉;忘記某個語法長怎樣 → 〈語法速查〉;名詞對不上 → 最後的〈名詞速查表〉。
常用 g++ 編譯選項
1 | g++ -std=c++11 -o app main.cpp # 學校投影片的標準寫法:指定 C++11、-o 指定執行檔名 |
除錯就用最後這組 -fsanitize=address -g:陣列越界、重複 delete 會在出事當下停下來並指出第幾行;記憶體洩漏則是等程式跑完才補印一段 LeakSanitizer: detected memory leaks,指到當初 new 的那一行。交出去的 Makefile 不要留這個選項。
整學期通用的 Makefile
放在每週的作業資料夾根目錄,make 一鍵編譯(先 .o 再連結,就是助教範本的「模組化」形式)、make clean 一鍵清乾淨。每一行的意思見〈環境設置〉,這裡只放可以直接貼的成品:
1 | # 指定編譯器 |
當週有幾題就寫幾組(all: 後面和 clean: 那行記得一起補);執行檔名稱另有規定時,改目標名稱和 -o 後面的名字就好。
某一題要拆成多個檔案時(例如 .h / .cpp),照 11/26 的寫法每個 .cpp 一條 .o 規則、相依清單列進 .h,最後一起連結;把 Q6 加進 all: 與 clean::
1 | Q6: main.o Student.o |
語法速查(建議印出來)
上機考在實驗課的虛擬機上考。能不能上網查語法、能不能帶紙本,開學第一堂一定要先跟老師或助教確認;若允許帶紙本,這一節可以印出來帶去。
讀法:底下的 型別、成員、回傳型別、名稱、Name 這類中文代稱與 ... 都是佔位符,要換成自己的內容,照打一定編譯失敗;其餘英文關鍵字與符號(class、const、:、;、&)要一字不差照打。
標頭檔:什麼時候要 include 什麼
| 標頭檔 | 提供什麼 |
|---|---|
<iostream> |
cin、cout、cerr、endl |
<iomanip> |
setw、setprecision、setfill(fixed、defaultfloat、left、right 其實 <iostream> 就有,一起 include 也不會錯) |
<string> |
string 類別、getline、stoi、to_string |
<vector> |
vector |
<algorithm> |
sort、max、min、swap |
<cmath> |
sqrt、pow、fabs(浮點絕對值)、ceil、floor、round |
<cstdlib> |
rand、srand、abs(整數絕對值)、exit(n)(在任何函式裡都能立刻結束整個程式,離開碼是 n;但它不會跑區域物件的解構子,所以在 main 裡還是用 return n;) |
<ctime> |
time(配合 srand) |
<cctype> |
isalpha、isdigit、toupper、tolower |
<climits> |
INT_MAX、INT_MIN |
<limits> |
numeric_limits(配合 cin.ignore) |
<cstring> |
strlen、strcpy、strcat、strcmp(C 風格字串) |
<fstream> |
ifstream、ofstream |
<sstream> |
istringstream、ostringstream |
<cassert> |
assert |
輸入輸出
1 | cin >> a >> b; // 依序讀兩個值,空白與換行都會自動跳過 |
流程控制
1 | if (cond) { } else if (cond) { } else { } |
函式
1 | 回傳型別 名稱(參數列); // 宣告(prototype),結尾有分號 |
陣列 / vector / string
1 | int a[5] = {}; // 全 0 |
類別骨架(含分離編譯)
1 | // ── Name.h:只放宣告,不寫 using namespace std ── |
運算子重載
1 | // ── 寫在 class 裡 ── |
指標與動態記憶體
1 | int a = 5; |
檔案 I/O
1 | ifstream fin("input.txt"); |
繼承
1 | class Base { |
常見錯誤訊息對照表
編譯錯誤(compile error)
| 訊息 | 怎麼修 |
|---|---|
expected ';' after class definition |
class / struct 的右大括號後面漏了分號,補成 }; |
expected ';' before 'X' 或 expected ',' or ';' before 'X' |
上一行敘述結尾漏了分號,X 就是下一行的第一個字 |
expected initializer before '...' |
語法在更前面就斷了:往上一行找漏掉的分號或沒配對的括號 |
'xxx' was not declared in this scope |
檢查:變數沒宣告、拼錯字、忘了 #include、超出作用域 |
no matching function for call to ... |
參數型別或個數跟宣告對不上;建立物件時出現多半是少了預設建構子 |
call of overloaded ... is ambiguous |
兩個重載版本一樣符合,把參數型別改明確(或加 cast) |
passing 'const X' as 'this' argument discards qualifiers |
對 const 物件呼叫了非 const 函式:那個唯讀成員函式後面補 const |
invalid conversion from 'int' to 'int*' |
型別不合,多半是漏了 & 或多寫了 * |
redefinition of 'class X' |
header 忘了 include guard,補 #ifndef / #define / #endif |
fatal error: xxx.h: No such file or directory |
檔名拼錯,或自己寫的 header 用了 <>(應該用 "") |
'X::a' is private within this context |
從類別外面碰到 private 成員:漏了 public:,或應該改走 getter/setter |
no match for 'operator<<' (operand types are 'std::ostream' and 'X') |
自訂型別還沒重載 <<,或重載了卻忘了在 class 裡宣告成 friend |
警告(warning)——一個扣 2 分,不能放著不管
| 訊息 | 怎麼修 |
|---|---|
unused variable 'x' / unused parameter 'x' |
宣告了卻沒用到:刪掉它,或確認是不是漏寫了那段程式 |
'x' is used uninitialized |
變數宣告完沒給初值就拿來用,補上初值 |
comparison of integer expressions of different signedness |
int i 拿去跟 v.size() 比:迴圈變數改成 size_t i |
連結錯誤(link error)
| 訊息 | 怎麼修 |
|---|---|
undefined reference to 'foo()' |
檢查三件事:(1) 那個 .cpp 有沒有一起編 (2) 定義時漏寫 類別名:: (3) 宣告與定義的簽名不一致 |
undefined reference to 'Widget::count' |
static 成員忘了在類別外定義,補上 int Widget::count = 0; |
undefined reference to 'main' |
拼成 Main,或整個專案根本沒有 main |
multiple definition of 'x' |
變數或函式的定義被寫進 header 了:只留宣告,定義搬到 .cpp |
執行時期錯誤(runtime error)
| 現象 | 怎麼修 |
|---|---|
Segmentation fault (core dumped) |
陣列越界、對 nullptr 解參考、函式一直呼叫自己停不下來;用 -fsanitize=address -g 重編會直接指出第幾行 |
free(): double free detected / double free or corruption |
同一塊記憶體被釋放兩次:淺拷貝沒補三法則,或 delete 寫了兩次 |
std::bad_array_new_length |
完整訊息是 terminate called after throwing an instance of 'std::bad_array_new_length'。new T[n] 的 n 是負數或算到溢位——最常見就是 int n; 忘了 cin >> n 就 new int[n] |
std::bad_alloc |
要不到那麼多記憶體:最常見是 new 後面的數量算錯(乘法算爆、負數轉成超大的無號數);虛擬機的記憶體本來就少,不必到 GB 等級就可能失敗 |
| 程式卡住不動 | 兩種可能:程式正在等你輸入——cin >> 沒東西可讀就停在那裡,看起來跟當掉一樣,先打個值按 Enter 或改用 ./Q1 < in.txt 餵檔案;不然就是無窮迴圈:迴圈變數沒更新,或條件用 != 剛好跳過 |
| 輸出多一筆或少一筆 | 讀檔用了 while (!fin.eof()),改成 while (fin >> x) 或 while (getline(fin, line)) |
terminate called after throwing an instance of 'std::out_of_range' |
v.at(i) / s.at(i) 越界;下一行的 what(): 會直接告訴你索引是多少、長度是多少 |
Assertion 'cond' failed. |
assert(cond) 沒通過;前面的 檔名:行號: 就是那個 assert 的位置 |
Makefile 錯誤
| 訊息 | 怎麼修 |
|---|---|
missing separator |
recipe 開頭必須是 Tab 不能是空格;用 cat -A Makefile 確認行首是 ^I |
No rule to make target 'Q3.cpp' |
檔名打錯,或檔案不在這個目錄 |
make: 'Q1' is up to date. / make: Nothing to be done for 'all'. |
沒改過檔案時這兩句都正常(直接打 make 會看到後者,make Q1 會看到前者);剛改過檔案卻還看到它,才是相依關係漏寫(最常見:改了 .h,但規則沒把 .h 列為相依) |
名詞速查表
| 名詞 | 白話解釋 |
|---|---|
| 連結(link) | 把多個 .o 與函式庫接成一個執行檔 |
| 參數 / 引數 | 參數是函式定義裡的變數名;引數是呼叫時實際傳進去的值 |
| 傳值 / 傳參考 | 傳複製品(改不到外面)/傳本人的別名(改得到外面) |
| 重載(overload) | 同名函式、不同參數列表並存 |
| 越界(out of range) | 存取了陣列合法範圍以外的格子,C++ 不會幫你擋 |
| 未定義行為(UB) | 標準沒規定該產生什麼結果的寫法(越界、delete 兩次、用未初始化的變數…):可能看起來正常、可能當掉、換台電腦又是另一個答案 |
| 結構(struct) | 把幾個相關欄位綁成一包的自訂型別 |
| 類別(class) | 資料 + 操作資料的函式綁在一起;預設成員是 private |
| 封裝(encapsulation) | 資料設成 private,只開放少數 public 函式操作 |
| 建構子(constructor) | 與類別同名、沒有回傳型別,物件誕生時自動執行 |
| 解構子(destructor) | ~類別名,物件消失時自動執行,用來還資源 |
| 初始化列表 | 建構子參數列後面用 : 成員(值) 直接初始化成員 |
static 成員 |
屬於整個類別、所有物件共用的一份;宣告寫在類別裡,定義要寫在類別外 |
| 運算子重載 | 定義 +、<< 等符號對自訂型別的意義 |
friend |
破例允許某個外部函式存取 private 成員 |
| 指標(pointer) | 存放「記憶體位址」的變數 |
| 解參考(dereference) | 用 *p 取出指標所指的內容 |
new / delete |
執行時向系統要記憶體 / 把記憶體還回去 |
| 記憶體洩漏 | new 了卻沒 delete,記憶體一直被佔著 |
| 懸空指標 | 指向已經被釋放的記憶體的指標 |
| 淺拷貝 / 深拷貝 | 只複製位址(兩者共用同一塊)/另外配一塊並複製內容 |
| 三法則(Rule of Three) | 有 new 的類別要自己寫解構子、拷貝建構子、指派運算子 |
| 標頭檔(header) | .h 檔,放宣告,給別的檔案 #include |
| include guard | #ifndef/#define/#endif,防止同一個 header 被重複引入 |
| 命名空間(namespace) | 幫名字加上「姓氏」,避免不同函式庫的名稱相撞 |
| 串流(stream) | 資料流動的管道,cin/cout/ifstream/ofstream 都是 |
| 繼承(inheritance) | 新類別 = 舊類別 + 額外的東西 |
protected |
對外面關閉、對子類別開放的存取層級 |
| 覆寫(redefine) | 子類別定義同名同簽名的函式,蓋掉父類別的版本 |
| is-a / has-a | 「是一種」用繼承;「有一個」用組合(當成員變數) |
補充:遞迴(不在課程與考試範圍)
課本 Ch13 的遞迴(recursion)不在這門課的進度與考試範圍裡(期末範圍是 Ch 1–12、Ch 14),這一節只是給好奇的人看,跳過完全沒關係。
遞迴就是函式呼叫自己。它必須有兩個部分:
- 終止條件(base case):不再呼叫自己的情況。
- 遞迴步驟:呼叫自己,但問題規模要變小。
1 | int factorial(int n) { |
追蹤 factorial(4):
1 | factorial(4) = 4 * factorial(3) |
每一層 factorial 都有自己的一份 n(09/24 的區域變數規則),裡層把 n 算成什麼都不會影響外層——這是遞迴能成立的關鍵。09/24 那個迴圈版的 gcd 也可以寫成遞迴:
1 | int gcd(int a, int b) { |
忘記 base case 或問題沒變小,函式會一直呼叫自己停不下來;每呼叫一次都要多佔一點記憶體,額度用完程式就當掉——這叫堆疊溢位(stack overflow),畫面上看到的是 Segmentation fault。
經典例子是河內塔:三根柱子 A、B、C,A 上有 n 個盤子(1 最小、n 最大,小的在上),每次只能移動一根柱子最上面的盤子,且大盤子不能壓在小盤子上。要把 n 個盤子從 A 搬到 C,就「先把上面 n-1 個搬到 B,把最大的那個搬到 C,再把那 n-1 個從 B 搬到 C」——搬 n-1 個的方法跟搬 n 個一模一樣,只是柱子的角色換了,所以函式呼叫自己、只把三根柱子的順序調換;結束條件是 n == 0(沒盤子可搬)。總步數是 $2^n - 1$。
1 |
|
1 | 輸入: 3 |
說些什麼吧!