指針與引用是 C++ 中最基礎也最容易混淆的兩個概念。雖然語法上僅差一個 */-> 與 .,但二者在內存模型、生命週期綁定、靈活性等本質層面有著根本區別。本文將從「必須綁定對象」、「可重新綁定」、「操作符實現需求」三大核心維度,配合代碼實戰深度剖析差異,並給出工程級選型原則。
// 引用:聲明即綁定,不可為 null
int a = 10;
int& ref = a; // ✅ 合法
// int& ref2; // ❌ 編譯錯誤:引用必須初始化
// int& ref3 = nullptr; // ❌ 編譯錯誤:無空引用
// 指針:可為 null,使用前必須檢查
int* ptr = &a; // ✅ 合法
int* ptr2 = nullptr; // ✅ 合法
if (ptr2) { /* 安全使用 */ }
工程啟示:引用天然消除了空指針解引用風險,適用於「必須存在」的參數傳遞;指針適用於「可選」或「延遲初始化」場景。
string s1("Nancy");
string s2("Clancy");
string& rs = s1; // rs 綁定 s1
string* ps = &s1; // ps 指向 s1
rs = s2; // ⚠️ 並非重新綁定!實際執行 s1.operator=(s2)
// 結果:s1 變為 "Clancy",rs 仍綁定 s1
ps = &s2; // ✅ 真正重新指向 s2
// 結果:ps 指向 s2,s1 值不變
本質解析:rs = s2 調用的是 string::operator=,修改的是 被綁定對象的值,而非引用本身。引用在編譯階段即完成別名綁定,運行期不可改變指向。
典型案例:operator[] 需支持「左值賦值」語義:
class Array {
int data[10];
public:
// 必須返回引用,才能支持 arr[0] = 5;
int& operator[](size_t i) { return data[i]; }
// 若返回 int(值),則 arr[0] = 5 編譯報錯:左值必須為可修改對象
};
同理:operator=、operator++(前置)、流操作符 operator<</operator>> 等均需返回引用以支持鏈式調用與語義正確性。
何時選引用?
- 函數參數/返回值:「必須存在、不為空、不需重新綁定」
- 操作符重載:語法要求返回左值(如
[]、=、++)- 範圍 for 循環:
for (auto& x : vec)避免拷貝- Lambda 捕獲引用:
[&]延長外部變量生命週期何時選指針?
- 可選參數:
void foo(Widget* w = nullptr)- 動態內存管理:
new/delete、智能指針持有- 需重新指向:鏈表節點
next、觀察者模式訂閱者列表- C 風格 API 交互:
void c_api(int* out)- 大對象傳遞且不想拷貝,但可能為空:
void process(BigObj* obj)
std::unique_ptr/shared_ptr。const T& 綁定臨時對象延長生命週期,T& 不可綁定右值。指針與引用的本質差異在於:引用是對象的別名(編譯期綁定、不可空、不可改綁),指針是存儲地址的變量(運行期可空、可改綁)。現代 C++ 開發中,「能用引用不用指針,需動態/可選/擁有語義才用智能指針」是核心準則。理解這一點,能從根源上消除 80% 的內存安全隱患。