C++ 基礎速查
C++ 在 C 的基礎上,多了會自己管理記憶體的工具:std::string、std::vector 這類容器,以及物件離開範圍時自動執行的解構子。善用這些工具,大部分情況就不必自己 malloc / free。C 和 C++ 的關係見C 與 C++ 的關係。
#include <iostream>#include <string>#include <vector>
int main() { std::vector<int> v = {3, 1, 2}; std::string name = "Roy"; std::cout << name << " has " << v.size() << " numbers\n"; return 0;}標準函式庫的東西都放在 std 命名空間裡,所以要寫 std::cout。很多範例會在開頭寫 using namespace std; 省略 std::。小程式這樣寫沒問題,但它會把 std 裡上千個名字全部帶進來,容易和自己取的名字衝突(例如 count、max),所以比較大的程式不建議這樣寫。
| 寫法 | 作用 |
|---|---|
std::cin >> x; | 讀一個值,會自動跳過空白和換行 |
while (std::cin >> x) | 讀到輸入結束為止 |
std::getline(std::cin, line); | 讀一整行,包含空白 |
std::cout << x << '\n'; | 輸出 |
std::cout << std::fixed << std::setprecision(2) << d; | 小數點後 2 位(需要 <iomanip>) |
std::setw(2) << std::setfill('0') | 寬度和補字元(setw 只對下一個輸出有效) |
'\n'和std::endl的差別:std::endl除了換行,還會把緩衝區清空(flush),輸出很多行時比較慢。一般用'\n'就好。- 讀很多資料時可以在
main開頭加上std::ios::sync_with_stdio(false); std::cin.tie(nullptr);,在很多平台上能讓cin/cout快不少。加了之後就不能再混用scanf/printf。 cin >> x讀完數字後,換行字元還留在輸入裡。如果接著用getline,會讀到一個空行,要先用std::cin.ignore()把它丟掉。
std::string
Section titled “std::string”// std::string:會自己管理記憶體的字串,不用擔心空間不夠。#include <iostream>#include <string>
int main() { std::string s = "hello"; s += ", world"; // 串接 std::cout << s << " (size " << s.size() << ")\n"; std::cout << "substr(7, 5) = " << s.substr(7, 5) << '\n'; std::cout << "find(\"world\") = " << s.find("world") << '\n'; std::cout << "s == \"hello, world\" ? " << (s == "hello, world" ? "yes" : "no") << '\n'; // 可以直接用 == 比較內容
if (s.find("xyz") == std::string::npos) { // 找不到時回傳 npos std::cout << "\"xyz\" not found\n"; } return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
hello, world (size 12)substr(7, 5) = worldfind("world") = 7s == "hello, world" ? yes"xyz" not found| 操作 | 寫法 |
|---|---|
| 長度、是否為空 | s.size()、s.empty() |
| 串接 | a + b、s += "x"、s.push_back('c') |
| 取字元 | s[i](不檢查範圍)、s.at(i)(超出範圍會丟例外) |
| 子字串 | s.substr(起點, 長度) |
| 尋找 | s.find("abc"),找不到回傳 std::string::npos |
| 比較 | ==、<(字典順序) |
| 數字和字串互轉 | std::to_string(42)、std::stoi("42")、std::stoll(...) |
std::string 會自己管理記憶體,不用擔心空間不夠,也不用自己補 '\0'。
參考 (reference)
Section titled “參考 (reference)”參考是變數的別名。函式用參考接參數,就能直接修改呼叫者的變數:
// 參考 (reference) 就是變數的「別名」:函式用參考接參數,就能修改呼叫者的變數。#include <iostream>#include <string>
void swap_by_value(int a, int b) { int t = a; a = b; b = t; } // 改的是複本,呼叫者看不到void swap_by_ref(int &a, int &b) { int t = a; a = b; b = t; } // 改的是本人void swap_by_ptr(int *a, int *b) { int t = *a; *a = *b; *b = t; } // C 的做法
// const 參考:不複製、也保證不修改,傳大型物件(例如 string)時常用std::size_t length(const std::string &s) { return s.size(); }
int main() { int x = 1, y = 2; swap_by_value(x, y); std::cout << "by value: x = " << x << ", y = " << y << '\n'; swap_by_ref(x, y); std::cout << "by reference: x = " << x << ", y = " << y << '\n'; swap_by_ptr(&x, &y); std::cout << "by pointer: x = " << x << ", y = " << y << '\n'; std::cout << "length = " << length("a very long string") << '\n'; return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
by value: x = 1, y = 2by reference: x = 2, y = 1by pointer: x = 1, y = 2length = 18| 參數寫法 | 會複製嗎 | 能修改呼叫者的變數嗎 | 什麼時候用 |
|---|---|---|---|
int x(傳值) | 會 | 不能 | 小型別(int、double) |
int &x(參考) | 不會 | 能 | 要修改外面的變數 |
const std::string &s(const 參考) | 不會 | 不能 | 傳大型物件,只讀不改 |
int *p(指標) | 只複製位址 | 能(透過 *p) | C 的寫法;可以是 nullptr |
auto x = 3.14; // 讓編譯器推導型態(double)for (int v : numbers) { ... } // 範圍 for:走過容器的每個元素for (const auto &[key, value] : m) {} // 結構化綁定:一次拆開 pair 的兩個值int max(int a, int b); // 函式多載:同名但參數型態不同double max(double a, double b);void greet(std::string name = "you"); // 預設參數// 最常用的三種容器:vector(動態陣列)、map(有排序的對照表)、set(不重複的集合)。#include <iostream>#include <map>#include <set>#include <string>#include <vector>
int main() { std::vector<int> v = {5, 3, 8}; v.push_back(1); std::cout << "vector:"; for (int x : v) std::cout << ' ' << x; std::cout << " (size " << v.size() << ")\n";
std::map<std::string, int> age; age["bob"] = 20; age["amy"] = 19; age["bob"] += 1; std::cout << "map:"; for (const auto &[name, a] : age) std::cout << ' ' << name << '=' << a; // 依 key 排序 std::cout << '\n';
std::set<int> s = {3, 1, 3, 2, 1}; std::cout << "set:"; for (int x : s) std::cout << ' ' << x; // 自動去除重複、排序 std::cout << " (contains 2? " << (s.count(2) ? "yes" : "no") << ")\n"; return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
vector: 5 3 8 1 (size 4)map: amy=19 bob=21set: 1 2 3 (contains 2? yes)| 容器 | 特性 | 常用操作的速度 |
|---|---|---|
std::vector<T> | 動態陣列,記憶體連續 | 尾端加入 O(1)(平均)、用索引存取 O(1) |
std::map<K, V> | key 會自動排序 | 搜尋、插入 O(log n) |
std::unordered_map<K, V> | 不排序,用雜湊表 | 搜尋、插入平均 O(1) |
std::set<T> / std::unordered_set<T> | 不重複的集合 | 同上 |
std::pair<A, B> | 把兩個值綁在一起 | p.first、p.second |
std::stack、std::queue、std::priority_queue | 堆疊、佇列、優先佇列 | 加入、取出 O(1) 或 O(log n) |
不知道選哪個的時候,先用 vector:它最省記憶體,也最快。
演算法 (<algorithm>)
Section titled “演算法 (<algorithm>)”std::sort(v.begin(), v.end()); // 由小到大std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 用 lambda 自訂順序:由大到小std::reverse(v.begin(), v.end());auto it = std::max_element(v.begin(), v.end()); // 最大值的位置,*it 是值v.erase(std::unique(v.begin(), v.end()), v.end()); // 去掉相鄰的重複(先排序)long long sum = std::accumulate(v.begin(), v.end(), 0LL); // 加總(<numeric>)[](int a, int b) { return a > b; } 叫做 lambda:直接寫在需要的地方的小函式。
class / struct
Section titled “class / struct”class 和 struct 幾乎一樣,差別只在成員的預設存取權限:struct 預設 public(外面都能用),class 預設 private(只有自己的成員函式能用)。
最重要的兩個特殊成員函式:
- 建構子:物件誕生時自動執行,負責初始化
- 解構子(名字前面加
~):物件消失時自動執行,負責清理
// 建構子在物件誕生時執行,解構子在物件離開範圍時「自動」執行。// 把「釋放資源」寫在解構子裡,就不會忘記釋放:這個技巧叫 RAII。#include <iostream>#include <string>#include <utility>
struct Tracer { std::string name; explicit Tracer(std::string n) : name(std::move(n)) { std::cout << " construct " << name << '\n'; } ~Tracer() { std::cout << " destruct " << name << '\n'; }};
int main() { std::cout << "enter main\n"; Tracer a("a"); { std::cout << "enter block\n"; Tracer b("b"); Tracer c("c"); std::cout << "leave block\n"; // b、c 在這裡被自動解構(順序和建立時相反) } std::cout << "leave main\n"; // a 在 main 結束時解構 return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
enter main construct aenter block construct b construct cleave block destruct c destruct bleave main destruct a把「釋放資源」寫在解構子裡,就保證不會忘記釋放,這個技巧叫 RAII。std::string、std::vector、智慧指標都是用這個方法自動管理記憶體的,詳見控制記憶體的必備指令。
const、constexpr、nullptr
Section titled “const、constexpr、nullptr”| 寫法 | 意思 |
|---|---|
const int n = 10; | 不能修改的變數 |
constexpr int N = 100; | 編譯時就確定的常數,可以拿來當陣列大小。C++ 用它取代 #define N 100 |
nullptr | 空指標。C++ 用它取代 NULL |
v[i]不檢查範圍:越界和 C 陣列一樣危險。除錯時可以改用v.at(i),越界會丟出例外。push_back之後,舊的指標、參考、迭代器可能失效:vector 容量不夠時會搬到新的記憶體,指向舊位置的指標就懸空了。m[key]找不到時會自動新增一筆:只想查詢的話,用m.count(key)或m.find(key)。int和v.size()比較:size()是無號整數,i < v.size()會出現有號、無號混用的警告。迴圈變數可以用size_t。- 混用
cin和getline:記得先cin.ignore()丟掉留在輸入裡的換行。
用到這個主題的題目
Section titled “用到這個主題的題目”- 11|單字反轉 — 還沒完成
- 12|排序與去重 — 還沒完成
- 13|單字計數 — 還沒完成
- 14|參考與函式 — 還沒完成
- 19|智慧指標與二元搜尋樹 — 還沒完成
(還沒寫)