前言
本文系統整理 C/C++ 核心語言特性:三種參數傳遞方式、三大記憶體分配區、常見記憶體錯誤、malloc vs new、重載/覆蓋/隱藏辨析、extern "C"、預設參數規則、巨集 vs 內聯函數、建構/解構順序、字串類別實現要點。
一、函數參數傳遞三種方式
| 方式 | 語法 | 機制 | 影響外部變數 | 典型用途 |
| 值傳遞 | void f(int x) | 建立副本 | 否 | 小型型別、不需修改原值 |
| 指標傳遞 | void f(int* x) | 傳遞位址 | 是 (透過解參考) | C 風格、可選參數 (nullptr)、陣列 |
| 參考傳遞 | void f(int& x) | 別名綁定 | 是 | C++ 慣用、修改原值、避免拷貝大型物件 |
程式碼驗證
void Func1(int x) { x += 10; } // 值傳遞
void Func2(int* x) { *x += 10; } // 指標傳遞
void Func3(int& x) { x += 10; } // 參考傳遞
int n = 0;
Func1(n); cout << n; // 0
Func2(&n); cout << n; // 10
Func3(n); cout << n; // 20
二、記憶體分配三大區域
| 區域 | 對象 | 特點 |
| 靜態儲存區 | 全域變數、static 變數 | 編譯期分配、程式全生命週期存在 |
| 堆疊 | 函數區域變數、參數 | CPU 指令集支援、極快、容量有限 (~1-8MB) |
| 堆積 | new/malloc 動態分配 | 程式員手動管理、靈活大容量、易碎片化 |
三、常見記憶體錯誤與對策
- 分配失敗卻使用 → 檢查
nullptr/NULL
- 未初始化就引用 → 定義即初始化、
std::vector 等容器
- 越界存取 → 邊界檢查、
at()、範圍 for、std::span (C++20)
- 忘記釋放 → 記憶體洩漏 → RAII、智慧指標 (
unique_ptr/shared_ptr)
- 釋放後繼續使用 (懸空指標) → 釋放後設
nullptr
- 返回區域物件指標/參考 → 返回值、智慧指標、
std::optional
- 物件關係複雜難追蹤 → 重新設計資料結構、所有權模型明確化
- 釋放未置 NULL → 野指標 → 封裝
SafeDelete、智慧指標
四、malloc/free vs new/delete
| 維度 | malloc/free | new/delete |
| 性質 | 標準庫函數 | C++ 運算符 |
| 型別安全 | 無 (返回 void*) | 有 (返回具型別指標) |
| 建構/解構 | 不呼叫 | 自動呼叫 |
| 失敗處理 | 返回 NULL | 拋出 std::bad_alloc |
| 重新分配 | realloc | 無直接對應 (用 vector) |
| 適用對象 | POD 型別、C 介面 | 所有 C++ 類別 (非 POD 必須用 new) |
核心原因:malloc 是函數,編譯器無法注入建構/解構呼叫;new 是運算符,語言層面保證物件語意。
五、重載 vs 覆蓋 vs 隱藏
| 特徵 | 重載 | 覆蓋 | 隱藏 |
| 範圍 | 同一類別 | 基類/衍生類別 | 基類/衍生類別 |
| 函數名 | 相同 | 相同 | 相同 |
| 參數 | 不同 | 相同 | 相同/不同 |
virtual | 可有可無 | 基類必須有 | 基類無 virtual |
| 回傳型別 | 可不同 | 相同 (或共變) | 可不同 |
程式碼示範
class Base {
void f(int); void f(float); // 重載
virtual void g(); void h(float); void k(float);
};
class Derived : public Base {
virtual void g(); // 覆蓋
void h(int); // 隱藏 Base::h(float)
void k(float); // 隱藏 Base::k(float) (非 virtual)
};
六、extern "C" 解決 C/C++ 混合連結
C++ 編譯器對函數名進行 Name Mangling (如 _foo_int_int) 支援重載;C 編譯器不會 (_foo)。
// 告訴 C++ 編譯器用 C 連結規則
extern "C" {
void foo(int x, int y);
#include "c_header.h"
}
七、函數預設參數規則
- 只能出現在宣告,不可在定義體:
void f(int x=0); ✓ / void f(int x=0) {} ✗
- 右邊參數有預設值,左邊必須也有:
void f(int x, int y=0, int z=0); ✓ / void f(int x=0, int y, int z=0); ✗
八、巨集 vs 內聯函數
| 維度 | 巨集 | 內聯函數 |
| 處理階段 | 預處理器 (文字替換) | 編譯器 (語意分析) |
| 型別檢查 | 無 | 完整 |
| 自動轉換 | 無 | 有 |
this 指標 | 無法處理 | 正確傳遞 |
| 側邊效應 | 易發生 (多次求值) | 無 (參數單次求值) |
| 除錯 | 不可見 | 可見 |
| 正確用法 | 常數、簡單運算 | inline 與定義體放在一起 |
九、建構/解構順序鐵律
- 建構:基類 → 成員物件 (宣告順序) → 本類別
- 解構:嚴格相反順序 (本類別 → 成員 → 基類)
- 順序唯一,否則編譯器無法自動管理
十、字串類別完整實現要點
class String {
char* m_data;
public:
String(const char* str = nullptr); // 普通建構
String(const String& other); // 拷貝建構
~String(); // 解構
String& operator=(const String& other); // 指派
};
String::~String() { delete[] m_data; }
String::String(const char* str) {
if (!str) { m_data = new char[1]; *m_data = '\0'; }
else { int len = strlen(str); m_data = new char[len+1]; strcpy(m_data, str); }
}
String::String(const String& other) {
int len = strlen(other.m_data);
m_data = new char[len+1];
strcpy(m_data, other.m_data);
}
String& String::operator=(const String& other) {
if (this == &other) return *this; // 1. 自指派檢查
delete[] m_data; // 2. 釋放舊資源
int len = strlen(other.m_data);
m_data = new char[len+1]; // 3. 分配新資源
strcpy(m_data, other.m_data);
return *this; // 4. 返回 *this
}
關鍵點:自指派檢查、釋放舊記憶體、深度拷貝、返回參考支援鏈式指派。