大廠一面:我說 Const 一般放在類型左側(cè),他意味深長的'哦'了一聲...面試官到底在暗示什么?
在 C++ 的編程實踐中,const 關(guān)鍵字扮演著至關(guān)重要的角色。它不僅是實現(xiàn)常量語義、保護數(shù)據(jù)不被意外修改的基石,更是接口設(shè)計、編譯器優(yōu)化和代碼意圖表達的關(guān)鍵工具。
然而,這個看似簡單的關(guān)鍵字,其在代碼中的書寫位置卻引發(fā)了一場曠日持久的風格之爭——即所謂的“東 const 派”(East Const / Right Const)與“西 const 派”(West Const / Left Const)。
一、西 Const 派 (West Const / Left Const):約定俗成的起點
"西 const"風格是許多 C++ 開發(fā)者學習 C++ 時首先接觸到的形式。它的核心規(guī)則是:const 關(guān)鍵字通常放置在它所修飾的類型名稱的左側(cè)(西邊)。
1. 基本示例:
#include <string>
#include <vector>
#include <cassert>
// 1. 基本類型的常量
const int MAX_USERS = 1000; // 一個整型常量
const double PI = 3.14159; // 一個雙精度浮點常量
const bool IS_ENABLED = true; // 一個布爾常量
// 2. 常量引用 (Reference to Const)
const std::string GREETING = "Hello, World!";
const std::string& ref_greeting = GREETING; // 引用一個常量字符串,不能通過 ref_greeting 修改 GREETING
void printMessage(const std::string& msg){ // 函數(shù)參數(shù)為常量引用,保證函數(shù)內(nèi)部不修改傳入的字符串
// msg = "Cannot change"; // 編譯錯誤:不能修改 const 引用指向的內(nèi)容
std::cout << msg << std::endl;
}
// 3. 指向常量的指針 (Pointer to Const)
const int* ptr_to_const_int; // ptr_to_const_int 是一個指針,它指向一個常量 int
int value = 10;
ptr_to_const_int = &value; // 指針可以指向一個非 const 變量...
// *ptr_to_const_int = 20; // 編譯錯誤:不能通過 ptr_to_const_int 修改所指向的值
const int const_value = 20;
ptr_to_const_int = &const_value; // ...也可以指向一個 const 變量
// 指針本身是可變的
int another_value = 30;
ptr_to_const_int = &another_value; // 合法:讓指針指向另一個 int (即使是 const int*)
2. 解讀西 Const:
這種風格的直觀性在于,const 緊跟在類型之前,讀起來像是"一個常量整數(shù)"、"一個常量字符串的引用"或"一個指向常量整數(shù)的指針"。這在簡單情況下非常自然。
3. 西 Const 的復(fù)雜性:常量指針
當 const 不僅修飾指向的內(nèi)容,還要修飾指針本身時,西 Const 的寫法需要特別注意 const 的位置:
int score = 90;
int highScore = 100;
// 4. 常量指針 (Const Pointer)
int* const const_ptr = &score; // const_ptr 是一個常量指針,它*總是*指向 score
// const 修飾的是 ptr 本身,不是 *ptr
*const_ptr = 95; // 合法:可以通過常量指針修改其指向的 *非 const* 內(nèi)容
// const_ptr = &highScore; // 編譯錯誤:不能改變常量指針的指向
// 5. 指向常量的常量指針 (Const Pointer to Const)
const int MAX_SCORE = 100;
const int* const const_ptr_to_const = &MAX_SCORE; // 指針本身和它指向的內(nèi)容都是常量
// *const_ptr_to_const = 99; // 編譯錯誤:不能修改指向的 const 內(nèi)容
// const_ptr_to_const = &value; // 編譯錯誤:不能改變常量指針的指向
在西 Const 風格下,const int* ptr 和 int* const ptr 的區(qū)別對于開發(fā)者來說可能是一個常見的混淆點。const 的位置決定了它是限制了"指針指向的內(nèi)容"還是"指針本身"。
二、 東 Const 派 (East Const / Right Const):追求一致性的規(guī)則
"東 const"風格的支持者認為,將 const 放在類型的右側(cè)(東邊)能夠提供一種更統(tǒng)一、更少歧義的解讀方式。其核心規(guī)則是:const 關(guān)鍵字修飾其緊鄰左側(cè)的內(nèi)容。
1. 基本示例 (與西 Const 等價):
#include <string>
#include <vector>
#include <type_traits> // 用于 static_assert 證明等價性
// 1. 基本類型的常量
int const MAX_USERS_EAST = 1000;
static_assert(std::is_same_v<constint, intconst>, "const int == int const"); // 證明類型等價
// 2. 常量引用 (Reference to Const)
std::string const GREETING_EAST = "Hello, East!";
std::string const& ref_greeting_east = GREETING_EAST;
static_assert(std::is_same_v<const std::string&, std::string const&>, "const T& == T const&");
void printMessageEast(std::string const& msg){
// msg = "Cannot change"; // 編譯錯誤
std::cout << msg << std::endl;
}
// 3. 指向常量的指針 (Pointer to Const)
int const* ptr_to_const_int_east;
static_assert(std::is_same_v<constint*, intconst*>, "const int* == int const*");
int value_east = 10;
ptr_to_const_int_east = &value_east;
// *ptr_to_const_int_east = 20; // 編譯錯誤
2. 關(guān)鍵等價性: 對于基本類型、常量引用、指向常量的指針,const T 和 T const 是完全等價的,編譯器將它們視為同一種類型。const T* 和 T const* 也是完全等價的。
3. 東 Const 的解讀優(yōu)勢
讓我們用"const 修飾左側(cè)"的規(guī)則來重新審視指針的情況:
(1) 指向常量的指針 (Pointer to Const):
int const* ptr;
解讀:const 修飾其左邊的 int。所以 ptr 是一個指針 (*),指向一個 int,這個 int 是 const 的。
(2) 常量指針 (Const Pointer):
int* const ptr = &score;
解讀:const 修飾其左邊的 *(指針)。所以 ptr 是一個 const 的指針 (*),它指向一個 int。指針本身是常量,不能改變指向。
(3) 指向常量的常量指針 (Const Pointer to Const):
int const* const ptr = &MAX_SCORE;
解讀: 第一個 const (從右往左看) 修飾其左邊的 * (指針)。表示 ptr 是一個常量指針。 第二個 const (繼續(xù)往左) 修飾其左邊的 int。表示這個指針指向一個 const 的 int。
這種從右向左或遵循“const 修飾左側(cè)”的閱讀方式,在東 Const 風格下顯得尤為一致和清晰。
對比表格:
含義 | 西 Const | 東 Const | "修飾左側(cè)"規(guī)則解讀 (東 Const) |
指向常量的指針 | const T* ptr | T const* ptr | ptr is a * to (T which is const) |
常量指針 | T* const ptr | T* const ptr | ptr is a (* which is const) to T |
指向常量的常量指針 | const T* const ptr | T const* const ptr | ptr is a (* which is const) to (T which is const) |
東 Const 的支持者(包括一些 C++ 標準委員會成員和資深開發(fā)者,如 Bjarne Stroustrup 在某些場合也推薦)認為,這種一致性在處理更復(fù)雜的類型聲明(如函數(shù)指針、成員函數(shù)指針)時尤其有價值,減少了特殊規(guī)則記憶的負擔。
三、核心概念:頂層 Const (Top-Level) 與 底層 Const (Low-Level)
其實不管我們習慣東 Const 還是西 Const,要真正掌握 const 的行為,理解 頂層 const 和 底層 const 的區(qū)別至關(guān)重要。這個概念與東西風格無關(guān),而是關(guān)乎 const 修飾的是對象本身還是其所指向或引用的內(nèi)容。
- 頂層 const (Top-Level Const): 表示對象本身是常量。這適用于任何類型的對象。
const int x = 10; // x 本身是常量,具有頂層 const。
int* const ptr = &some_int; // ptr 本身是常量(不能改變指向),具有頂層 const。指針指向的 int 不是常量。
const std::vector<int> vec = {1, 2, 3}; // vec 本身是常量,具有頂層 const。
- 底層 const (Low-Level Const): 表示指針或引用所指向(或引用)的對象是常量。這只與復(fù)合類型(如指針、引用)相關(guān)。
const int* ptr; // ptr 指向的 int 是常量,具有底層 const。ptr 本身不是常量(可以改變指向)。int const* ptr; // 同上,底層 const。
const int& ref = x; // ref 引用的 int 是常量,具有底層 const。引用本身總是“常量”(不能重新綁定),但我們通常不稱引用本身有頂層 const。
int* const* ptr_ptr; // ptr_ptr 指向一個 int* const(常量指針)。這里的 const 是底層 const (相對于 ptr_ptr 而言,它指向的對象類型中帶有 const)。
const int* const ptr; // ptr 既有頂層 const (指針本身不能改),也有底層 const (指向的 int 不能改)。
為什么區(qū)分頂層與底層 Const 很重要?
(1) 拷貝與賦值操作: 當進行對象拷貝時(包括函數(shù)傳值參數(shù)),頂層 const 通常會被忽略。
const int a = 10;
int b = a; // 合法:a 的頂層 const 被忽略,拷貝的是值 10
int i = 0;
int* const p1 = &i; // p1 有頂層 const
int* p2 = p1; // 合法:拷貝指針地址時,p1 的頂層 const 被忽略
底層 const 則必須被保留或加強,不能丟失。
const int c = 20;
const int* p_const = &c; // 底層 const
// int* p_non_const = p_const; // 編譯錯誤:不能將 const int* 賦給 int* (試圖丟棄底層 const)
int d = 30;
int* p_d = &d;
const int* p_const_d = p_d; // 合法:可以從 int* 轉(zhuǎn)換為 const int* (增加底層 const)
const int& r_const = c; // 底層 const
// int& r_non_const = r_const; // 編譯錯誤:不能將 const int& 綁定到 int&
(2) 函數(shù)重載: 函數(shù)可以根據(jù)參數(shù)的底層 const 進行重載。
void process(int* ptr); // 處理非 const int 的指針
void process(const int* ptr); // 重載版本:處理 const int 的指針
int num = 1;
const int cnum = 2;
process(&num); // 調(diào)用 process(int*)
process(&cnum); // 調(diào)用 process(const int*)
函數(shù)不能僅根據(jù)參數(shù)的頂層 const 進行重載(對于按值傳遞或指針/引用本身的頂層 const)。
// void setup(int x);
// void setup(const int x); // 編譯錯誤:重定義。參數(shù) x 的頂層 const 在函數(shù)簽名中被忽略
// void configure(int* p);
// void configure(int* const p); // 編譯錯誤:重定義。參數(shù) p 的頂層 const 被忽略
(3) 特殊: 成員函數(shù)的 const 限定符(void MyClass::func() const;)實際上是改變了隱式 this 指針的類型,為其添加了底層 const (從 MyClass* 變?yōu)?nbsp;const MyClass* 或 MyClass const*),因此可以基于此進行重載。
class Widget {
public:
void update(){ /* ... */ } // 可以修改對象狀態(tài)
void display()const{ /* ... */ } // 保證不修改對象狀態(tài) (this 是 const Widget*)
};
Widget w;
const Widget cw;
w.update(); // OK
w.display(); // OK, 調(diào)用 display() const
// cw.update(); // 編譯錯誤:不能在 const 對象上調(diào)用非 const 成員函數(shù)
cw.display(); // OK, 調(diào)用 display() const
(4) 模板類型推導(dǎo):
模板參數(shù)推導(dǎo)時,頂層 const 通常會被忽略,而底層 const 會被保留。
template<typename T>
void foo(T param);
const int ci = 0;
foo(ci); // T 推導(dǎo)為 int (頂層 const 被忽略)
const int* pci = &ci;
foo(pci); // T 推導(dǎo)為 const int* (底層 const 被保留)
int* const cpi = &some_int;
foo(cpi); // T 推導(dǎo)為 int* (頂層 const 被忽略)
(5) 將頂層/底層 Const 與 East/West 聯(lián)系起來:
無論使用哪種風格,const 的位置都明確地指示了它是頂層還是底層:
T const* ptr / const T* ptr: const 應(yīng)用于 T,是底層 const。
T* const ptr: const 應(yīng)用于 * (指針本身),是頂層 const。
T const* const ptr / const T* const ptr: 第一個 const (靠近 T) 是底層 const,第二個 const (靠近 ptr) 是頂層 const。
const T x / T const x: const 應(yīng)用于對象 x 本身,是頂層 const。
const T& ref / T const& ref: const 應(yīng)用于 T (被引用的類型),是底層 const。
理解了頂層與底層 const 的概念,就能準確把握 const 在各種聲明中的含義,而東 const 或西 const 只是表達這些含義的兩種語法風格。
四、 風格選擇與一致性的重要性
既然東 Const 和西 Const 在功能上完全等價,并且都能表達頂層與底層 const 的語義,那么該如何選擇?
(1) 西 Const (Left Const):
- 優(yōu)點: 更為常見,尤其是在老代碼庫和許多入門教程中;對于簡單類型 (const int) 的閱讀可能更符合自然語言習慣。
- 缺點: 在復(fù)雜指針聲明中,const 的位置規(guī)則不如東 Const 統(tǒng)一,可能需要更多思考來區(qū)分常量指針和指向常量的指針。
(2) 東 Const (Right Const):
- 優(yōu)點: 遵循單一、一致的"const 修飾左側(cè)"規(guī)則,有助于系統(tǒng)性地解讀復(fù)雜聲明;被一些專家和風格指南推薦,認為更具邏輯性。
- 缺點: 不如西 Const 普及,初學者可能不太習慣;對于最簡單的 const int,寫法 int const 可能感覺略微冗余。
(3) 最終的建議:一致性壓倒一切!
在技術(shù)層面上,沒有哪種風格是絕對"正確"或"錯誤"的。最重要的原則是在一個項目、一個團隊,甚至個人的代碼庫中保持一致性 。
- 團隊項目: 遵循團隊既定的編碼規(guī)范。如果規(guī)范未明確,應(yīng)進行討論并達成一致?;旌鲜褂脙煞N風格會嚴重降低代碼的可讀性和可維護性。
- 個人項目: 選擇認為更清晰、更能幫助準確理解代碼的風格,并堅持使用。甚至完全可以普通類型的變量用西 const,涉及到指針類型那么就用東 const
- 閱讀代碼: 無論偏好哪種風格,都需要能夠熟練閱讀和理解這兩種風格的代碼,因為不可避免地會遇到使用不同風格的代碼庫。
五、 結(jié)論
- 東 Const 與 西 Const 是兩種表達 const 限定符位置的風格,功能等價,各有優(yōu)劣。西 Const 更常見,東 Const 規(guī)則更統(tǒng)一。
- 理解 頂層 Const(修飾對象本身)與 底層 Const(修飾指針/引用指向的內(nèi)容)是掌握 const 用法的關(guān)鍵,它決定了 const 在拷貝、賦值、函數(shù)重載和模板推導(dǎo)中的行為。
- 無論選擇哪種風格,保持一致性是提高代碼質(zhì)量的黃金法則。