編碼 · 程式語言
C++ 入門教學
簡答
C++ 是編譯式的靜態型別語言,結合了 C 的速度與底層控制能力,並加入類別、樣板與龐大的標準函式庫。用 g++ -std=c++20 hello.cpp -o hello 編譯 hello.cpp,再執行 ./hello。現代 C++ 程式碼以 std::string、std::vector 和 std::map 取代原始陣列,以智慧指標取代手動 delete,並利用 RAII 讓資源自動釋放。
C++ 是什麼,用來做什麼
C++ 由 Bjarne Stroustrup 於 1979 年在貝爾實驗室開始開發,最初名為「C with Classes」。它由 ISO 標準化;C++11 開啟了「現代 C++」時代,之後有 C++17、C++20 與 C++23。C++ 用在重視效能與控制力的地方:遊戲引擎(Unreal Engine)、網頁瀏覽器(Chrome、Firefox)、資料庫、交易系統、嵌入式裝置、編譯器與機器學習執行環境。它的核心理念是零負擔抽象:你沒用到的功能不會有任何代價。
安裝編譯器並執行 Hello, world
Linux 與 macOS 上使用 GCC(g++)或 Clang(clang++),Windows 上則透過 Visual Studio 使用 Microsoft Visual C++。請明確指定標準版本。
#include <iostream>
int main() {
std::cout << "Hello, world\n";
return 0;
}g++ -std=c++20 -Wall -Wextra hello.cpp -o hello
./hello
# Hello, worldstd::cout 是標準輸出串流,<< 用來寫入它。標準函式庫中的所有內容都位於命名空間 std 之下。
變數、型別與 auto
C++ 擁有與 C 相同的內建型別,另外還有作為關鍵字的 bool、參考(reference),以及 std::string 等函式庫型別。auto 讓編譯器根據初始值推導型別。大括號初始化 {} 不允許窄化轉換。
| 型別 | 範例 | 說明 |
|---|---|---|
int, long long | int n{42}; | 有號整數 |
double | double x{3.14}; | 64 位元浮點數 |
bool | bool ok{true}; | true 或 false |
char | char c{'A'}; | 一個位元組 |
std::string | std::string s{"hi"}; | 可增長的文字,來自 <string> |
auto | auto v = 2.5; | 自動推導,這裡是 double |
T& | int& r = n; | 參考:n 的另一個名稱 |
#include <iostream>
#include <string>
int main() {
int count{7};
auto avg = count / 2.0; // double, 3.5
std::string name{"Ada"};
name += " Lovelace";
const int& ref = count; // read-only alias of count
std::cout << name << " " << name.size() << " " << avg << " " << ref << "\n";
// Ada Lovelace 12 3.5 7
}流程控制與函式
流程控制與 C 相同,另外還有範圍式 for 迴圈。傳遞大型物件時請用 const 參考以避免複製;若函式需要修改它們,則使用非 const 參考。
#include <iostream>
#include <string>
#include <vector>
int square(int x) { return x * x; }
void addSuffix(std::string& s, const std::string& suffix = "!") {
s += suffix;
}
int main() {
int score = 72;
if (score >= 90) std::cout << "A\n";
else if (score >= 70) std::cout << "B\n"; // B
else std::cout << "C\n";
std::vector<int> nums{1, 2, 3};
for (int n : nums) std::cout << square(n) << " "; // 1 4 9
std::cout << "\n";
std::string word = "hi";
addSuffix(word);
std::cout << word << "\n"; // hi!
}類別
類別把資料成員與成員函式組合在一起。建構子負責建立物件的初始狀態;private 成員對外部程式碼是隱藏的。struct 與 class 相同,只是成員預設為 public。
#include <iostream>
#include <string>
#include <utility>
class Account {
public:
explicit Account(std::string owner) : owner_(std::move(owner)) {}
void deposit(double amount) {
if (amount > 0) balance_ += amount;
}
double balance() const { return balance_; }
const std::string& owner() const { return owner_; }
private:
std::string owner_;
double balance_ = 0.0;
};
int main() {
Account acc{"Ann"};
acc.deposit(50);
acc.deposit(-5); // ignored
std::cout << acc.owner() << ": " << acc.balance() << "\n"; // Ann: 50
}繼承(class Savings : public Account)與 virtual 函式提供執行階段的多型。請在覆寫的函式上標示 override,讓編譯器幫你檢查。
標準函式庫容器與演算法
標準函式庫(常稱為 STL)提供容器與演算法,它們透過迭代器彼此搭配運作。
| 容器 | 類似 | 適用情境 |
|---|---|---|
std::vector<T> | 動態陣列 | 預設的序列容器 |
std::array<T, N> | 固定大小陣列 | 大小在編譯期已知 |
std::map<K, V> | 已排序的字典 | 鍵有序,查找為 O(log n) |
std::unordered_map<K, V> | 雜湊表 | 平均查找速度快,為 O(1) |
std::set<T> | 已排序且不重複的集合 | 成員檢查、有序輸出 |
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::vector<int> nums{3, 1, 2, 5};
std::sort(nums.begin(), nums.end());
auto evens = std::count_if(nums.begin(), nums.end(),
[](int n) { return n % 2 == 0; });
std::cout << nums.front() << " " << nums.back() << " " << evens << "\n"; // 1 5 1
std::map<std::string, int> ages{{"bob", 25}, {"ann", 31}};
ages["cy"] = 40;
for (const auto& [name, age] : ages) { // structured bindings
std::cout << name << "=" << age << " "; // ann=31 bob=25 cy=40
}
std::cout << "\n";
}[](int n) { ... } 是 lambda,一種直接寫在原地的匿名函式。樣板(template)讓同一個 std::vector 能用於任何元素型別;你也可以用 template <typename T> 自己撰寫。
招牌特色:RAII 與智慧指標
RAII(Resource Acquisition Is Initialization,資源取得即初始化)表示資源由物件擁有:建構子取得資源,解構子釋放資源。由於物件離開作用域時解構子會自動執行,即使拋出例外也一樣,因此記憶體、檔案與鎖都不會洩漏。智慧指標就是把 RAII 應用在堆積記憶體上。
#include <iostream>
#include <memory>
struct Noisy {
int id;
explicit Noisy(int i) : id(i) { std::cout << "create " << id << "\n"; }
~Noisy() { std::cout << "destroy " << id << "\n"; }
};
int main() {
auto a = std::make_unique<Noisy>(1); // sole owner
{
auto b = std::make_shared<Noisy>(2); // shared, reference-counted
auto c = b; // two owners now
std::cout << "owners: " << b.use_count() << "\n";
} // destroy 2 happens here
std::cout << "end of main\n";
} // destroy 1 happens here輸出:create 1、create 2、owners: 2、destroy 2、end of main、destroy 1。在現代程式碼中,你幾乎不該自己寫 new 和 delete。
以例外處理錯誤
拋出例外物件,並以 const 參考捕捉。標準例外型別位於 <stdexcept>。不會拋出例外的函式可標示為 noexcept。
#include <iostream>
#include <stdexcept>
#include <string>
int parseAge(const std::string& text) {
int age = std::stoi(text); // throws std::invalid_argument on "abc"
if (age < 0) throw std::out_of_range("negative age");
return age;
}
int main() {
try {
std::cout << parseAge("41") << "\n"; // 41
std::cout << parseAge("abc") << "\n";
} catch (const std::invalid_argument&) {
std::cout << "not a number\n"; // not a number
} catch (const std::exception& e) {
std::cout << "error: " << e.what() << "\n";
}
}對於可預期的失敗,許多程式碼庫會回傳 std::optional<T>(C++17)或 std::expected<T, E>(C++23),而不是拋出例外。
完整的小程式:單字頻率統計
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << argv[0] << " FILE\n";
return 1;
}
std::ifstream in(argv[1]);
if (!in) {
std::cerr << "cannot open " << argv[1] << "\n";
return 1;
}
std::unordered_map<std::string, int> counts;
std::string word;
while (in >> word) {
std::string clean;
for (unsigned char ch : word)
if (std::isalpha(ch)) clean += static_cast<char>(std::tolower(ch));
if (!clean.empty()) ++counts[clean];
}
std::vector<std::pair<std::string, int>> top(counts.begin(), counts.end());
std::sort(top.begin(), top.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
for (size_t i = 0; i < top.size() && i < 5; ++i)
std::cout << top[i].second << " " << top[i].first << "\n";
}以 g++ -std=c++20 -O2 words.cpp -o words 編譯,再執行 ./words notes.txt。in 離開作用域時檔案會自動關閉,這又是 RAII 的應用。
初學者常見錯誤
- 在標頭檔中寫
using namespace std;,這會污染所有引入該標頭檔的檔案。 - 明明用
const&就夠了,卻以傳值方式傳遞std::vector這類大型物件。 - 在比較中混用有號與無號數,例如
int i與v.size()。開啟警告就能發現這類問題。 - 在
push_back之後仍保留指向std::vector的迭代器或參考,元素可能已被搬移。 - 使用原始的
new和delete,而不是std::make_unique、std::vector或其他具擁有權的型別。
接下來學什麼
把 cppreference.com 當作語言與函式庫的參考,並參考 C++ Core Guidelines(由 Bjarne Stroustrup 與 Herb Sutter 編輯)學習良好實務。Standard C++ Foundation 的網站 isocpp.org 有入門頁面與推薦書籍。學習用 CMake 建置專案,再進一步學習移動語意、樣板、constexpr,以及使用 std::thread 的並行處理。
常見問題
學 C++ 之前要先學 C 嗎?
不是必要的。現代 C++ 教學從 std::string、std::vector 與類別開始,之後才介紹原始指標與 C 風格陣列。先學 C 確實有助於理解記憶體。
該使用哪個 C++ 標準?
目前的 GCC、Clang 與 MSVC 對 C++17 和 C++20 支援良好,C++23 的支援也在增加。請用 -std=c++20 之類的旗標明確指定。
指標和參考有什麼差別?
參考是別名,建立時就必須綁定,之後不能改綁,也不能是 null。指標儲存位址,可以是 null,也可以改指向其他位置。
C++ 很難學嗎?
這門語言相當龐大,學習曲線會比 Python 或 Go 更長。堅持使用現代功能、標準容器與智慧指標,就能避開大部分經典陷阱。