學習目標
讀完本章後,你應該能夠:
- 了解 C++ 語言的歷史、設計哲學與特色
- 清楚說明程式的編譯流程(前置處理 → 編譯 → 組譯 → 連結)
- 安裝並設定 C++ 開發環境(g++ / clang++ / IDE)
- 熟悉常用的編譯器旗標(
-std、-Wall、-Wextra、-g、-O2) - 理解 Debug 與 Release 兩種建置模式的差異
- 撰寫、編譯並執行你的第一個 C++ 程式
- 看得懂編譯器錯誤訊息並加以修正
- 認識 C++ 程式的基本結構、註解與命名空間
- 建立一套適合自己的 C++ 學習方法
1. 什麼是 C++?
C++ 是一門通用型程式語言,由 Bjarne Stroustrup 於 1979 年在貝爾實驗室開始開發,最初稱為「C with Classes」。C++ 在 C 語言的基礎上加入了物件導向、泛型程式設計、例外處理等特性,同時保留了 C 語言高效能、低階操作的能力。
C++ 的核心設計哲學有幾個著名的原則:
- 零成本抽象(Zero-overhead abstraction):你不會為了用不到的功能付出效能代價;而你用到的抽象,手寫也不會更快。
- 信任程式設計師:語言給你完整的控制權(包含直接操作記憶體),但相對地也要你自己負責。
- 與 C 高度相容:方便既有的 C 程式碼與生態系沿用。
- 多範式(multi-paradigm):同一個語言可以寫出不同風格的程式。
「C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do, it blows your whole leg off.」這句經常被引用的玩笑話,說明了 C++ 強大但也需要謹慎使用。本課程的目標,就是讓你學會「安全地」使用它的強大。
C++ 的主要特色
| 特色 | 說明 |
|---|---|
| 高效能 | 直接編譯成機器碼,執行速度極快 |
| 多範式 | 支援程序式、物件導向、泛型、函數式程式設計 |
| 跨平台 | 可在 Windows、macOS、Linux 等平台編譯執行 |
| 豐富的標準函式庫 | STL 提供容器、演算法、迭代器等工具 |
| 向下相容 C | 大部分 C 程式碼可直接在 C++ 中使用 |
| 手動記憶體控制 | 可精確掌握資源生命週期(搭配 RAII 更安全) |
C++ 的應用領域
- 作業系統(Windows、Linux 核心模組)
- 遊戲引擎(Unreal Engine、Unity 底層)
- 嵌入式系統與物聯網(IoT)
- 高頻交易系統(對延遲極度敏感的場景)
- 瀏覽器引擎(Chrome V8、Firefox)
- 資料庫(MySQL、MongoDB)
- 機器學習框架底層(TensorFlow、PyTorch 的 C++ 核心)
2. C++ 標準演進
C++ 持續演進,每隔幾年就會由 ISO 委員會發布新標準:
| 標準 | 年份 | 重要新增特性 |
|---|---|---|
| C++98 | 1998 | 第一個 ISO 標準,STL、模板、例外處理 |
| C++03 | 2003 | 小幅修正 C++98 的問題 |
| C++11 | 2011 | auto、nullptr、lambda、智慧指標、範圍 for |
| C++14 | 2014 | 泛型 lambda、decltype(auto)、二進位字面值 |
| C++17 | 2017 | 結構化綁定、std::optional、if constexpr、std::filesystem |
| C++20 | 2020 | Concepts、Ranges、Coroutines、Modules |
| C++23 | 2023 | std::expected、std::print、更多 Ranges |
本課程以 C++17 為基準,這是目前業界最廣泛支援、工具鏈最成熟的現代標準。學會 C++17 後,再學習 C++20/23 的新特性會非常自然。
「現代 C++(Modern C++)」泛指 C++11 之後的寫法風格:多用 auto、智慧指標、範圍 for、{} 初始化,少用裸指標與手動 new/delete。本課程會一路引導你養成現代寫法的習慣。
3. 編譯流程
C++ 是編譯式語言,原始碼必須先轉成機器碼才能執行。這跟 Python、JavaScript 這類「直譯式 / 即時編譯」語言不同——C++ 在程式執行前就已經把所有東西翻譯成 CPU 看得懂的指令,這也是它執行速度快的原因之一。
完整流程其實有四個階段:
原始碼 (.cpp)
│
▼
┌─────────────────┐
│ ① 前置處理器 │ Preprocessing
│ (Preprocessor) │ 展開 #include、#define、條件編譯
└─────────────────┘
│ 產出:純文字(翻譯單元 translation unit)
▼
┌─────────────────┐
│ ② 編譯器 │ Compilation
│ (Compiler) │ 語法/型別檢查 → 組合語言 (.s)
└─────────────────┘
│ 產出:組合語言 (.s)
▼
┌─────────────────┐
│ ③ 組譯器 │ Assembly
│ (Assembler) │ 組合語言 → 機器碼目的檔 (.o)
└─────────────────┘
│ 產出:目的檔 (.o / .obj)
▼
┌─────────────────┐
│ ④ 連結器 │ Linking
│ (Linker) │ 合併多個 .o + 函式庫 → 可執行檔
└─────────────────┘
│
▼
可執行檔 (a.out / program.exe)
各階段詳細說明
-
前置處理(Preprocessing) - 處理所有以
#開頭的指令。 -#include <iostream>會被「整份貼上」標頭檔的內容。 -#define MAX 100會把後面出現的MAX全部替換成100。 - 條件編譯(#ifdef、#ifndef)也在此處理。 - 想親眼看看前置處理的結果,可以執行g++ -E hello_world.cpp(會印出展開後的純文字,通常非常長)。 -
編譯(Compilation) - 檢查語法是否正確、型別是否相容。 - 大部分的錯誤訊息(syntax error、type error)都來自這個階段。 - 將每個翻譯單元轉成組合語言。用
g++ -S hello_world.cpp可以產生.s檔看組合語言。 -
組譯(Assembly) - 把組合語言轉成 CPU 真正執行的機器碼,存成目的檔(object file)。 - 用
g++ -c hello_world.cpp可以只編譯到.o而不連結。 -
連結(Linking) - 把多個
.o檔以及用到的函式庫(例如標準函式庫)合併成一個可執行檔。 - 「找不到符號」這類錯誤(undefined reference to ...)就是連結階段的問題,例如你呼叫了一個只宣告卻沒有定義的函式。
一句指令做完全部
平常我們直接用一行指令讓 g++ 把四個階段一次跑完:
g++ -std=c++17 -Wall -o hello_world hello_world.cpp
但理解中間有四個階段非常重要——當你看到錯誤訊息時,能立刻判斷它是編譯期錯誤(語法/型別)還是連結期錯誤(找不到定義),就能更快定位問題。
4. 安裝開發環境
macOS
# 安裝 Xcode Command Line Tools(包含 clang++)
xcode-select --install
# 驗證安裝
clang++ --version
# 或安裝 g++(透過 Homebrew)
brew install gcc
g++ --version
macOS 上的
g++預設其實是 clang 的別名。若要用「真正的 GCC」,Homebrew 安裝後通常是g++-14之類的名稱。對初學者而言,clang++ 與 g++ 行為幾乎一致,任選其一即可。
Linux(Ubuntu / Debian)
sudo apt update
sudo apt install g++ build-essential
g++ --version
Windows
- 安裝 MSYS2 並透過
pacman -S mingw-w64-ucrt-x86_64-gcc安裝 g++ - 或安裝 Visual Studio 社群版(含 MSVC 編譯器)
- 也可使用 WSL(Windows Subsystem for Linux),在 Windows 中執行完整的 Linux 環境
IDE / 編輯器設定
你不一定需要笨重的 IDE,下面由輕到重列出常見選擇:
| 工具 | 特性 | 適合對象 |
|---|---|---|
| VS Code | 輕量、跨平台、擴充豐富(C/C++ extension) | 大多數初學者的首選 |
| CLion | JetBrains 出品,功能完整(需付費 / 學生免費) | 想要強大重構與除錯 |
| Visual Studio | Windows 上功能最完整 | Windows 開發者 |
| Vim / Neovim | 終端機內,搭配 LSP | 喜歡鍵盤操作的人 |
| 純文字 + 終端機 | 最能理解「編譯到底做了什麼」 | 學習階段強烈建議先試試 |
建議:剛學 C++ 時,先用「純文字編輯器 + 終端機手動
g++編譯」幾天,徹底理解編譯流程後,再轉用 IDE 享受自動化的便利。這樣你才不會在 IDE 出問題時手足無措。
VS Code 的最小設定:安裝官方「C/C++」擴充套件後,按 F5 即可編譯與除錯。背後其實還是呼叫 g++/clang++,只是 IDE 幫你產生了指令。
5. 編譯器旗標(Compiler Flags)
旗標(flag)是給編譯器的選項,控制它的行為。以下是初學階段最該認識的幾個:
| 旗標 | 用途 | 說明 |
|---|---|---|
-std=c++17 |
指定語言標準 | 確保使用 C++17 的語法與函式庫 |
-Wall |
開啟常見警告 | 「all」其實只是「大部分常見的」警告 |
-Wextra |
開啟額外警告 | 比 -Wall 更嚴格,建議搭配使用 |
-Werror |
把警告當成錯誤 | 強迫你處理每一個警告(培養好習慣) |
-g |
加入除錯資訊 | 讓 gdb/lldb 能對應到原始碼行號 |
-O0 |
不最佳化(預設) | 編譯快、好除錯 |
-O2 |
較強最佳化 | Release 常用,執行更快 |
-o 檔名 |
指定輸出檔名 | 不指定時預設為 a.out |
學習階段強烈建議養成這個習慣:
g++ -std=c++17 -Wall -Wextra -g -o program program.cpp
為什麼要開警告? 警告(warning)代表「程式可以編譯,但編譯器覺得這裡很可疑」。很多難以察覺的 bug(例如忘記回傳值、比較 signed/unsigned、變數未使用)都會以警告形式提前被抓出來。把警告當成免費的程式碼審查!
Debug 建置 vs Release 建置
實務上,同一份程式碼通常有兩種建置模式:
| 模式 | 典型旗標 | 特性 | 用途 |
|---|---|---|---|
| Debug | -O0 -g |
不最佳化、含除錯資訊、編譯快 | 開發、除錯階段 |
| Release | -O2(或 -O3)-DNDEBUG |
最佳化、執行快、移除 assert | 正式發佈給使用者 |
# Debug:方便用除錯器逐行追蹤
g++ -std=c++17 -Wall -Wextra -g -O0 -o app_debug main.cpp
# Release:給最終使用者,速度最快
g++ -std=c++17 -O2 -DNDEBUG -o app_release main.cpp
-DNDEBUG 會定義巨集 NDEBUG,使標準函式庫的 assert() 失效。Release 版執行更快,但因為做了最佳化,逐行除錯時行號可能會「跳來跳去」,所以除錯請用 Debug 版。
6. 第一個程式:Hello World
// hello_world.cpp
#include <iostream> // 引入輸入輸出函式庫
int main() { // 程式進入點
std::cout << "Hello, World!" << std::endl;
return 0; // 回傳 0 表示程式正常結束
}
編譯與執行
# 編譯
g++ -std=c++17 -Wall -o hello_world hello_world.cpp
# 執行(macOS / Linux)
./hello_world
# 執行(Windows)
hello_world.exe
預期輸出:
Hello, World!
7. 程式基本結構
每個 C++ 程式都包含以下基本元素:
#include <iostream> // (1) 前置處理指令:引入標頭檔
int main() { // (2) main 函式:程式進入點
// (3) 程式敘述
std::cout << "Hello!" << std::endl;
return 0; // (4) 回傳值:0 表示成功
}
逐行解析
| 元素 | 說明 |
|---|---|
#include <iostream> |
引入標準輸入輸出函式庫,提供 std::cout、std::cin 等 |
int main() |
程式的進入點,作業系統從這裡開始執行 |
{} |
大括號定義程式碼區塊(scope) |
std::cout |
標準輸出物件,std 是命名空間,:: 是作用域運算子 |
<< |
輸出運算子,將資料送到輸出串流 |
std::endl |
換行並清空輸出緩衝區 |
return 0; |
回傳整數 0 給作業系統,表示程式正常結束 |
; |
每個敘述結尾必須加分號 |
關於 main 的回傳值
main() 的回傳值會交給作業系統,慣例上:
return 0;→ 程式成功(也可用return EXIT_SUCCESS;)- 非零值 → 發生錯誤(例如
return 1;或return EXIT_FAILURE;)
在終端機可以用以下方式查看上一個程式的回傳值:
./hello_world
echo $? # macOS / Linux,印出剛剛程式的回傳值
小知識:
main()是唯一可以「不寫return也合法」的函式。若沒寫,編譯器會自動補上return 0;。不過明確寫出來是好習慣。
8. 註解(Comments)
註解是給「人」看的,編譯器會完全忽略。寫好註解能讓程式更易維護。
// 這是單行註解,從 // 到行尾都會被忽略
/*
這是多行註解
可以跨越很多行
常用於檔案開頭說明
*/
int x = 5; // 也可以放在程式碼後面
/* 也可以 */ int y = 10; /* 夾在中間(不建議) */
注意:多行註解 /* */ 不能巢狀:
/* 外層 /* 內層 */ 這裡會出錯 */
好的註解 vs 壞的註解
// ❌ 壞:只是把程式碼翻譯一遍,沒有提供新資訊
i = i + 1; // 把 i 加 1
// ✅ 好:解釋「為什麼」,而非「做什麼」
i = i + 1; // 跳過開頭的標頭位元組
C++ Core Guidelines 建議(NL.1、NL.2):註解應該說明意圖(intent)與程式碼無法表達的事情,而不是重複程式碼本身。最好的「註解」其實是清楚的命名與結構。
9. 命名空間(Namespaces)入門
命名空間用來避免名稱衝突。想像兩個函式庫都有一個叫 print 的函式,命名空間就像「姓氏」,讓編譯器分得清誰是誰。
標準函式庫的所有東西都放在 std 命名空間裡,所以我們要寫 std::cout、std::string。其中 :: 稱為作用域解析運算子(scope resolution operator)。
#include <iostream>
namespace game {
int score = 0;
void reset() { score = 0; }
}
int main() {
game::score = 100; // 用 命名空間::名稱 存取
std::cout << game::score; // 100
return 0;
}
using 的取捨
你可能在很多教學看到 using namespace std;,它讓你可以省略 std:::
#include <iostream>
using namespace std; // 之後可直接寫 cout、endl
int main() {
cout << "Hello" << endl;
return 0;
}
| 寫法 | 優點 | 缺點 |
|---|---|---|
std::cout(加前綴) |
清楚、不會衝突 | 字較多 |
using namespace std; |
簡短 | 可能造成命名衝突,不建議用在標頭檔或大型專案 |
using std::cout;(只引入特定名稱) |
折衷,較安全 | 需逐一列出 |
C++ Core Guidelines(SF.6、SF.7):不要在標頭檔的全域範圍寫
using namespace。本課程一律使用std::前綴,讓你從一開始就養成清楚的好習慣。
10. 輸入與輸出(iostream 深入)
<iostream> 提供幾個重要的全域串流物件:
| 物件 | 用途 |
|---|---|
std::cout |
標準輸出(console output) |
std::cin |
標準輸入(console input) |
std::cerr |
標準錯誤輸出(不緩衝,立即顯示) |
std::clog |
標準錯誤輸出(有緩衝,適合記錄日誌) |
輸出:std::cout 與 <<
std::cout << "整數:" << 42 << ",浮點數:" << 3.14 << '\n';
<< 可以串接,由左到右依序把資料送進串流。它能自動辨識型別(整數、浮點數、字串、字元都可以直接輸出)。
換行:std::endl vs '\n'
std::cout << "A" << std::endl; // 換行 + 清空緩衝區(flush)
std::cout << "B" << '\n'; // 只換行,不清空(效能較好)
大量輸出時,頻繁使用
std::endl會因為一直清空緩衝區而變慢。一般情況用'\n'即可,只有在需要「立刻看到輸出」時(例如除錯)才用std::endl。
輸入:std::cin 與 >>
#include <iostream>
int main() {
int age;
std::cout << "請輸入你的年齡:";
std::cin >> age; // 從鍵盤讀取一個整數存入 age
std::cout << "明年你 " << age + 1 << " 歲\n";
return 0;
}
>> 稱為擷取運算子(extraction operator),會跳過開頭的空白,讀取一個值。可以連續讀多個:
int a, b;
std::cin >> a >> b; // 輸入「3 5」會讓 a=3, b=5
進階提醒:
std::cin >> x在輸入格式錯誤時(例如要 int 卻輸入字母)會進入錯誤狀態。讀取一整行文字則建議用std::getline(std::cin, str)。這些細節會在後面章節深入。
11. 如何看懂編譯器錯誤訊息
初學者最大的挫折來源,就是看不懂編譯器「噴」出來的一大串英文。其實有訣竅。
以這段有錯的程式為例:
#include <iostream>
int main() {
std::cout << "Hello" // 忘記分號
return 0;
}
g++ 可能輸出:
main.cpp:5:5: error: expected ';' before 'return'
5 | return 0;
| ^~~~~~
拆解這個訊息:
main.cpp:5:5→ 檔名 : 行號 : 欄號,問題在第 5 行第 5 欄。error:→ 這是錯誤(會中止編譯);若是warning:則只是提醒。expected ';' before 'return'→ 在return之前少了分號。- 下面的
^~~~~~用箭頭指出可疑的位置。
看錯誤訊息的三個原則
- 永遠從「第一個」錯誤開始修。一個小錯誤常會引發後面一連串連鎖錯誤,修好第一個,後面可能就消失了。
- 看清楚行號,但真正的錯誤有時在「前一行」(例如漏掉分號,編譯器是到下一行才發現不對勁)。
- 複製錯誤訊息去搜尋。
undefined reference、expected ';'、was not declared in this scope都是常見且好查的訊息。
常見錯誤訊息對照表
| 錯誤訊息片段 | 通常代表 |
|---|---|
expected ';' before ... |
漏了分號 |
'cout' was not declared in this scope |
忘記 #include <iostream> 或漏了 std:: |
undefined reference to 'foo' |
函式有宣告但沒有定義(連結階段) |
expected '}' at end of input |
大括號沒有成對 |
invalid conversion from ... |
型別不相容 |
重點整理
- C++ 是一門高效能、多範式的編譯式語言,設計哲學強調「零成本抽象」。
- 程式碼經過前置處理 → 編譯 → 組譯 → 連結四個階段產生可執行檔。
- 學會分辨編譯期錯誤(語法/型別)與連結期錯誤(找不到定義)。
- 養成使用
-std=c++17 -Wall -Wextra的習慣,把警告當作免費的程式碼審查。 - Debug 建置(
-g -O0)用於開發,Release 建置(-O2 -DNDEBUG)用於發佈。 - 每個程式都需要
main()函式作為進入點,回傳 0 表示成功。 - 註解說明「為什麼」,而非重複「做什麼」。
- 命名空間避免名稱衝突;本課程一律使用
std::前綴。 std::cout/<<輸出、std::cin/>>輸入;'\n'通常優於std::endl。- 看編譯錯誤要從第一個開始修,注意檔名、行號與箭頭。
常見錯誤與陷阱
| 錯誤 | 說明 |
|---|---|
忘記加 ; |
每個敘述結尾都要加分號,否則編譯錯誤 |
main 拼錯 |
函式名稱必須是 main,大小寫敏感 |
忘記 #include |
使用 cout 前必須引入 <iostream> |
混淆 << 和 >> |
<< 用於輸出,>> 用於輸入 |
在標頭檔用 using namespace std; |
大型專案中可能造成命名衝突,建議使用 std:: 前綴 |
| 大括號不成對 | { 與 } 必須一一對應,建議寫完 { 立刻補上 } |
| 編譯沒開警告 | 不加 -Wall 會錯過很多潛在 bug |
| 把 Release 版拿來除錯 | 最佳化後行號會錯亂,除錯請用 -g -O0 |
| 中文全形符號 | 程式碼中誤打全形分號 ; 或括號會編譯錯誤 |
練習題
練習 1(基礎):客製化 Hello World
修改 hello_world.cpp,讓它輸出你的名字、今天的日期,以及一句你最喜歡的話(每項各一行)。
- 提示:用多個 std::cout,或用一個 std::cout 串接多個 << 與 '\n'。
練習 2(基礎):多行格式化輸出
參考 basic_output.cpp,寫一個程式輸出以下內容(使用跳脫字元 \n、\t、\"):
C++ 程式設計
================
作者:(你的名字)
日期:2026/06/15
"學習 C++ 是值得的!"
- 提示:
\"可在字串中輸出雙引號;================直接打即可。
練習 3(基礎):編譯錯誤排除
以下程式碼有數個錯誤,請找出並修正,使其能用 g++ -std=c++17 -Wall 通過編譯:
#include <iostream>
int Main() {
std:cout << "Hello" << endl
return 0;
}
- 提示:檢查函式名稱大小寫、
std::的冒號數量、endl前綴、行尾分號。
練習 4(中級):互動式問候
寫一個程式,先用 std::cin 讀取使用者的名字(字串)與年齡(整數),再輸出:
你好,<名字>!明年你就 <年齡+1> 歲了。
- 提示:宣告
std::string name;與int age;(需#include <string>),用std::cin >> name >> age;讀取。
練習 5(中級):觀察編譯的四個階段
針對你的 hello_world.cpp,依序執行下列指令並觀察各自的產物,寫下你的觀察:
1. g++ -E hello_world.cpp -o hello.i(前置處理後的純文字)
2. g++ -S hello_world.cpp(產生組合語言 hello_world.s)
3. g++ -c hello_world.cpp(產生目的檔 hello_world.o)
4. g++ hello_world.o -o hello_world(連結成可執行檔)
- 提示:用編輯器打開 hello.i 與 hello_world.s 看看內容差異有多大。
練習 6(挑戰):比較 Debug 與 Release
用兩種模式各編譯一次同一支程式,並比較產生的可執行檔大小:
g++ -std=c++17 -g -O0 -o app_debug hello_world.cpp
g++ -std=c++17 -O2 -DNDEBUG -o app_release hello_world.cpp
ls -l app_debug app_release
- 思考題:為什麼兩個檔案大小不同?哪一個適合拿去用除錯器逐行追蹤?
- 提示:Debug 版包含
-g的除錯資訊,通常較大。
對應程式碼檔案
- hello_world.cpp — 經典 Hello World 程式
- basic_output.cpp —
std::cout各種輸出示範