蝦皮C++一面面經(jīng):const與constexpr的深度剖析
最近去蝦皮參加了 C++ 的一面,本以為自己準備得還算充分,結(jié)果卻被一道看似基礎(chǔ)的題目給難住了。面試官微笑著問我:“能說一下 const 和 constexpr 的區(qū)別嗎?” 當時我心里就 “咯噔” 一下,這兩個關(guān)鍵字平時都在用,可真要系統(tǒng)地說出它們的區(qū)別,還真有點犯難。磕磕絆絆地回答了一些,明顯能感覺到面試官不太滿意。面試結(jié)束后,我就下定決心,一定要把這兩個關(guān)鍵字研究透徹。
Part1.const:“只讀” 屬性的堅守者
在深入了解 const 和 constexpr 之前,我們先來回顧一下 const 的基本用法。const 是 C++ 中一個非常重要的關(guān)鍵字,它的主要作用是修飾變量、指針、函數(shù)參數(shù)和成員函數(shù),以表明這些被修飾的對象具有 “只讀” 屬性,即一旦初始化后,其值就不能被隨意修改 。
1.1修飾變量:讓值不可更改
當 const 修飾一個普通變量時,這個變量就成為了常量,其值在初始化后不能被修改。例如:
const int num = 10;
num = 20; // 編譯錯誤,不能修改常量的值
在這個例子中,num被定義為常量,初始化值為 10。如果后續(xù)嘗試修改num的值,編譯器會報錯,從而保證了num的值的不可變性。這種用法在定義一些固定不變的值時非常有用,比如數(shù)學常量、配置參數(shù)等。比如在一個計算圓面積的程序中,我們可以定義圓周率PI為常量:
const double PI = 3.1415926;
double radius = 5.0;
double area = PI * radius * radius;
這樣,PI的值在程序運行過程中不會被意外修改,保證了計算的準確性。
1.2修飾指針:多維度限制
指針和 const 的組合使用稍微復雜一些,但也非常強大。根據(jù) const 的位置不同,指針可以分為以下三種情況:
⑴指向常量的指針:const int* ptr,這種指針指向的對象是常量,不能通過指針來修改所指向的值,但指針本身可以指向其他地址。例如:
int value = 10;
const int* ptr = &value;
// *ptr = 20; // 編譯錯誤,不能通過指針修改指向的值
ptr = &value; // 合法,指針可以指向其他地址
⑵常量指針:int* const ptr,這種指針本身是常量,一旦初始化后,指針的值(即所指向的地址)不能再改變,但可以通過指針修改所指向的值。例如:
int value1 = 10;
int value2 = 20;
int* const ptr = &value1;
*ptr = 30; // 合法,可以修改指針指向的值
// ptr = &value2; // 編譯錯誤,不能修改指針的值
⑶指向常量的常量指針:const int* const ptr,這種指針既不能修改所指向的值,也不能改變指針本身的值。例如:
int value = 10;
const int* const ptr = &value;
// *ptr = 20; // 編譯錯誤,不能修改指針指向的值
// ptr = &value; // 編譯錯誤,不能修改指針的值
1.3修飾函數(shù)參數(shù):保障參數(shù)安全
當 const 修飾函數(shù)的參數(shù)時,可以確保在函數(shù)內(nèi)部不能修改傳入的參數(shù)值,這在很多情況下可以提高代碼的安全性和可讀性。例如:
void printValue(const int value) {
// value = 20; // 編譯錯誤,不能修改const參數(shù)
std::cout << value << std::endl;
}
在這個printValue函數(shù)中,參數(shù)value被 const 修飾,所以在函數(shù)內(nèi)部不能對value進行修改。如果嘗試修改,編譯器會報錯。這樣,當我們調(diào)用這個函數(shù)時,就不用擔心傳入的參數(shù)會被函數(shù)內(nèi)部意外修改。比如:
int num = 10;
printValue(num);
num的值在調(diào)用printValue函數(shù)前后不會發(fā)生改變。
1.4修飾成員函數(shù):維護類成員穩(wěn)定
在類中,const 還可以修飾成員函數(shù)。在成員函數(shù)后面加上 const 關(guān)鍵字,表示該成員函數(shù)不會修改類的非靜態(tài)成員變量,也被稱為 “常量成員函數(shù)”。例如:
class MyClass {
private:
int value;
public:
MyClass(int v) : value(v) {}
int getValue() const {
// value = 20; // 編譯錯誤,不能修改成員變量
return value;
}
};
在MyClass類中,getValue函數(shù)被聲明為常量成員函數(shù),這意味著在getValue函數(shù)內(nèi)部不能修改value成員變量。如果一個對象被聲明為 const 對象,那么它只能調(diào)用類中的常量成員函數(shù),以確保對象的狀態(tài)不會被意外修改。例如:
const MyClass obj(10);
int val = obj.getValue();
這里,obj是一個 const 對象,它只能調(diào)用getValue這個常量成員函數(shù)來獲取value的值,而不能調(diào)用其他可能會修改對象狀態(tài)的成員函數(shù)。
Part2.constexpr:編譯期常量的締造者
了解完 const,我們再來看看 constexpr 這個關(guān)鍵字。constexpr 是 C++11 引入的一個重要特性,它的出現(xiàn)進一步強化了 C++ 在編譯期計算的能力。constexpr 的主要作用是用于聲明常量表達式,無論是變量還是函數(shù),只要被 constexpr 修飾,就意味著它們的值或結(jié)果可以在編譯期確定 。
2.1修飾變量:編譯期確定值
當 constexpr 修飾變量時,這個變量的值必須在編譯期就能夠確定,并且它本身也是一個常量。例如:
constexpr int num = 10;
constexpr int result = num + 5;
在這個例子中,num和result都是在編譯期就確定了值的常量。num被初始化為 10,result通過num + 5在編譯期計算得到值為 15。這與 const 修飾的變量有所不同,const 修飾的變量雖然也是常量,但其值可以在運行期才確定,只要在初始化后不再改變即可。例如:
int value = 10;
const int num = value; // 合法,const變量的值可以在運行期確定
// constexpr int num1 = value; // 編譯錯誤,constexpr變量的值必須在編譯期確定
constexpr 修飾的變量在一些場景下有著獨特的優(yōu)勢。比如在定義數(shù)組大小時,使用 constexpr 變量可以讓數(shù)組大小在編譯期就確定,提高程序的效率和安全性。例如:
constexpr int size = 10;
int arr[size]; // 合法,數(shù)組大小在編譯期確定
2.2修飾函數(shù):編譯期求值
constexpr 還可以修飾函數(shù),這樣的函數(shù)被稱為常量表達式函數(shù)。常量表達式函數(shù)的特點是能夠在編譯期計算出結(jié)果,前提是傳入的參數(shù)都是編譯期常量。在 C++11 中,常量表達式函數(shù)有一些嚴格的限制,例如函數(shù)體一般只有一條返回語句,且返回的必須是常量表達式 。例如:
constexpr int square(int x) {
return x * x;
}
constexpr int result = square(5); // 在編譯期計算,result的值為25
這里的square函數(shù)被 constexpr 修飾,當傳入編譯期常量 5 時,square(5)的結(jié)果會在編譯期就計算出來并賦值給result。如果傳入的參數(shù)不是編譯期常量,那么該函數(shù)會在運行期執(zhí)行。例如:
int a = 5;
int result1 = square(a); // 在運行期計算,因為a不是編譯期常量
在 C++14 中,對 constexpr 函數(shù)的限制有所放寬,允許函數(shù)體中包含更復雜的邏輯,如局部變量、循環(huán)等,只要這些操作都能在編譯期完成即可。例如:
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
constexpr int res = factorial(5); // 在編譯期計算,res的值為120
這個factorial函數(shù)在 C++14 中是合法的常量表達式函數(shù),它可以在編譯期計算出階乘的結(jié)果。
2.3在類中的應(yīng)用:編譯時對象創(chuàng)建
constexpr 也可以用于類的構(gòu)造函數(shù)和成員函數(shù),使得我們可以在編譯時創(chuàng)建和初始化類的對象。例如:
class Point {
public:
constexpr Point(double xVal, double yVal) : x(xVal), y(yVal) {}
constexpr double getX() const { return x; }
constexpr double getY() const { return y; }
private:
double x, y;
};
constexpr Point origin(0, 0); // 在編譯時創(chuàng)建Point對象
在Point類中,構(gòu)造函數(shù)和getX、getY成員函數(shù)都被聲明為 constexpr。這樣,我們可以使用constexpr關(guān)鍵字創(chuàng)建Point對象origin,并且可以在編譯期調(diào)用getX和getY函數(shù)獲取對象的成員值。如果構(gòu)造函數(shù)或成員函數(shù)不是 constexpr,那么就無法在編譯期創(chuàng)建對象或調(diào)用函數(shù) 。例如:
class AnotherPoint {
public:
AnotherPoint(double xVal, double yVal) : x(xVal), y(yVal) {}
double getX() const { return x; }
double getY() const { return y; }
private:
double x, y;
};
// constexpr AnotherPoint p(0, 0); // 編譯錯誤,因為構(gòu)造函數(shù)不是constexpr
Part3.const 與 constexpr 的全面對比
通過前面的介紹,我們對 const 和 constexpr 各自的用法有了較為深入的了解。接下來,我們將從多個方面對它們進行全面的對比,以便更清晰地認識它們之間的區(qū)別和聯(lián)系 。
3.1常量性質(zhì)對比:運行期與編譯期之異
從常量的性質(zhì)來看,const 和 constexpr 有著本質(zhì)的區(qū)別。const 修飾的常量,其值可以在運行期確定,只要在初始化之后保持不變即可。這使得 const 在很多情況下用于表示那些在程序運行過程中不會被修改,但具體值在編譯時無法確定的量。比如從配置文件中讀取的參數(shù)、根據(jù)用戶輸入確定的值等。例如:
int getConfigValue();
const int configNum = getConfigValue();
這里的configNum是一個 const 常量,它的值是通過調(diào)用getConfigValue函數(shù)在運行期獲取的 。
而 constexpr 修飾的常量,其值必須在編譯期就確定下來,這就要求初始化它的表達式必須是常量表達式。constexpr 的這種特性使得它在一些對編譯期常量有嚴格要求的場景中發(fā)揮著重要作用,比如定義數(shù)組的大小、作為模板參數(shù)等。例如:
constexpr int num = 10;
int arr[num];
這里的 num 是 constexpr 常量,它的值在編譯期就確定為10,因此可以用于定義數(shù)組 ar r的大小。如果將constexpr 換成 const,并且 num 的值是通過運行期函數(shù)獲取的,那么就無法用于定義數(shù)組大小,會導致編譯錯誤 。
3.2修飾對象對比:范圍與功能差別
在修飾對象方面,const 的應(yīng)用范圍非常廣泛。它可以修飾普通變量,使其變?yōu)橹蛔x變量;可以修飾指針,根據(jù) const 的位置不同,實現(xiàn)指向常量的指針、常量指針以及指向常量的常量指針等不同功能;可以修飾函數(shù)參數(shù),防止函數(shù)內(nèi)部修改傳入的參數(shù)值;還可以修飾類的成員函數(shù),表明該成員函數(shù)不會修改類的非靜態(tài)成員變量 。例如:
// 修飾普通變量
const int value = 10;
// 修飾指針
int num = 20;
const int* ptr1 = # // 指向常量的指針
int* const ptr2 = # // 常量指針
const int* const ptr3 = # // 指向常量的常量指針
// 修飾函數(shù)參數(shù)
void func(const int param) {
// param = 30; // 編譯錯誤,不能修改const參數(shù)
}
// 修飾成員函數(shù)
class MyClass {
private:
int data;
public:
MyClass(int d) : data(d) {}
int getData() const {
// data = 40; // 編譯錯誤,不能修改成員變量
return data;
}
};
相比之下,constexpr 主要用于修飾變量和函數(shù)。當修飾變量時,確保變量的值在編譯期確定;當修飾函數(shù)時,使函數(shù)成為常量表達式函數(shù),能夠在編譯期計算出結(jié)果。雖然 constexpr 也可以用于類的構(gòu)造函數(shù),使得可以在編譯時創(chuàng)建和初始化類的對象,但它的應(yīng)用場景相對 const 來說沒有那么多樣化 。例如:
// 修飾變量
constexpr int result = 5 + 3;
// 修飾函數(shù)
constexpr int multiply(int a, int b) {
return a * b;
}
constexpr int product = multiply(4, 6);
// 修飾類的構(gòu)造函數(shù)
class Point {
public:
constexpr Point(double xVal, double yVal) : x(xVal), y(yVal) {}
constexpr double getX() const { return x; }
constexpr double getY() const { return y; }
private:
double x, y;
};
constexpr Point origin(0, 0);
3.3函數(shù)修飾對比:計算時機與函數(shù)要求不同
當 const 和 constexpr 用于修飾函數(shù)時,也存在明顯的差異。const 修飾函數(shù)時,主要是對函數(shù)返回值的一種限定,表示返回的值是不可變的,但函數(shù)的計算過程是在運行時進行的,對函數(shù)本身并沒有特別嚴格的要求,函數(shù)體可以包含各種復雜的邏輯和操作 。例如:
const int calculate() {
int a = 5;
int b = 3;
return a + b;
}
這里的calculate函數(shù)返回一個 const int 類型的值,在運行時計算a + b的結(jié)果并返回,返回值不可被修改。
而 constexpr 修飾函數(shù)時,要求函數(shù)能夠在編譯期計算出結(jié)果,前提是傳入的參數(shù)都是編譯期常量。在 C++11 中,常量表達式函數(shù)的函數(shù)體一般只有一條返回語句,且返回的必須是常量表達式;在 C++14 及以后,雖然允許函數(shù)體中包含更復雜的邏輯,但也要求這些操作都能在編譯期完成 。例如:
// C++11風格的constexpr函數(shù)
constexpr int add(int a, int b) {
return a + b;
}
// C++14風格的constexpr函數(shù)
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
在上面的例子中,add函數(shù)符合 C++11 中 constexpr 函數(shù)的要求,factorial函數(shù)符合 C++14 中 constexpr 函數(shù)的要求。它們都可以在編譯期根據(jù)傳入的編譯期常量參數(shù)計算出結(jié)果。
Part4.面試應(yīng)對技巧總結(jié)
回顧這次蝦皮 C++ 面試中被 const 和 constexpr 問題 “考倒” 的經(jīng)歷,我深刻認識到在面試中清晰準確地回答問題的重要性 。通過這次深入的研究和總結(jié),我為大家整理了一些在面試中應(yīng)對 const 和 constexpr 相關(guān)問題的實用技巧。
在回答問題時,一定要清晰闡述概念。當被問到 const 和 constexpr 時,要像我們前面詳細介紹的那樣,準確地說出它們各自的定義和基本用途。比如,對于 const,要提到它主要用于修飾變量、指針、函數(shù)參數(shù)和成員函數(shù),以保證被修飾對象的 “只讀” 屬性;對于 constexpr,要強調(diào)它用于聲明常量表達式,使得變量或函數(shù)的結(jié)果能在編譯期確定。用簡潔明了的語言表達這些概念,讓面試官能夠快速了解你對它們的理解。
對比差異也是關(guān)鍵的一點。面試官常常會考察你對這兩個關(guān)鍵字區(qū)別的掌握程度,所以要從多個方面進行對比。像我們前面從常量性質(zhì)、修飾對象、函數(shù)修飾等方面進行的對比,要能夠清晰地闡述出來。例如,在常量性質(zhì)上,const 修飾的常量值可以在運行期確定,而 constexpr 修飾的常量值必須在編譯期確定;在修飾對象上,const 應(yīng)用范圍更廣,而 constexpr 主要用于變量和函數(shù)等。通過這樣全面的對比,展示你對這兩個關(guān)鍵字的深入理解。
結(jié)合代碼示例則能讓你的回答更加生動和有說服力。在解釋概念和區(qū)別時,適當插入一些簡單易懂的代碼示例,就像我們在前面介紹中所做的那樣。比如,在說明 const 修飾指針的不同情況時,給出具體的代碼const int* ptr、int* const ptr、const int* const ptr,并解釋它們的含義和區(qū)別;在介紹 constexpr 修飾函數(shù)時,給出constexpr int square(int x) { return x * x; }這樣的示例,說明它如何在編譯期計算結(jié)果。通過實際的代碼,面試官能夠更直觀地看到你對這些關(guān)鍵字的實際運用能力。
面試前的充分準備是必不可少的。對于 C++ 中的重要知識點,如 const 和 constexpr,要提前進行系統(tǒng)的復習和總結(jié)。不僅要掌握它們的基本用法和區(qū)別,還要了解它們在實際項目中的應(yīng)用場景。可以通過閱讀相關(guān)的書籍、博客,做一些練習題來加深理解。同時,回顧自己在平時項目中使用這些關(guān)鍵字的經(jīng)驗,以便在面試中能夠結(jié)合實際案例進行回答。
希望通過我的這次面試經(jīng)歷以及對const和 constexpr 的深入分析,能幫助大家在今后的 C++ 面試中更加從容地應(yīng)對相關(guān)問題,順利拿到心儀的 offer !