C++中的 const 關鍵字
轉載自知乎:https://zhuanlan.zhihu.com/p/37514756
1. 基本描述
定義變數時的限定符,表示變數值不能改變。
const int bufSize = 512;
bufSize = 512; // 錯誤:試圖向const物件寫值
由於const一旦建立就不可更改,所以const物件必須初始化(否則定義一個預設值且不可修改的變數沒有任何意義)。
const int i = get_size(); // 正確
const int j = 42; // 正確
const int k; // 錯誤:未初始化
使用值傳遞初始化時,被初始化的物件是否為const與初始化物件是否為const無關。也即,const物件與非const物件可以互為初始化。
2. const初始化引用時的例外
C++規定引用型別必須與被引用物件一致:
int i = 2;
double &j = i; // 錯誤:引用型別與物件型別不一致
C++還規定引用必須繫結到左值:
注:左值和右值的辨別方法是,能取地址的是左值。
int &i = 2; // 錯誤:不允許用右值初始化
int &j = a * 2 // 錯誤:不允許用表示式初始化,實際上表示式(a*2)是右值
但是用const初始化引用時會有例外:
const引用型別與物件型別不一致(但可以轉化):
int i = 2;
const double &j = i; // 正確:j是常量引用
const引用繫結到一個非左值上(型別一致或可以轉化):
const int &i = 2; // 正確:i是常量引用
const int &j = a * 2 // 正確:j是常量引用
原因在於,const引用將會額外建立一個臨時變數,並繫結上去。
C++支援這種做法的目的在於,既然不能通過const引用修改物件值,那麼額外建立一個常量和直接繫結物件並沒有什麼區別,所以乾脆讓const引用支援這種非常規做法。
3. 頂層const和底層const
通常在指標/引用與const符同時使用時會用到這個概念。修飾指標本身的const稱為頂層const,修飾指標所指向物件的const稱為底層const。底層const與頂層const是兩個互相獨立的修飾符,互不影響。
1. const與指標
指標本身是一個獨立的物件,它又可以指向另一個物件。所以指標和const同時使用時,有兩種情況:
int i = 0;
int *const j = &i; // 指標j指向i,const修飾指標j本身,所以j的地址值不允許修改,但可以通過j修改i的值
const int *k = &i; // 指標k指向i,const修飾k指向的i,所以k的地址值可以修改,但不可以通過k修改i的值
第一行j不可改,i可改
第二行k可改,i不可改
2. const與引用
引用一旦初始化,就不能再修改(繫結),所以引用本身就具有"const"的性質。
與指標相比,引用相當於內建了頂層const(也就是後置的const)。
所以使用引用時,就只需考慮是否為底層const:
int i = 0;
const int &j = i; // j為繫結到i的const引用,不允許使用j來修改i
j預設不能改,此時i也不能改
3. 其他
(1). 可以將底層const的指標(或引用)指向(或繫結)到非const物件,但不允許非底層const的指標(或引用)指向(或繫結)到const物件。 (即:const物件不允許通過任何方式(指標/引用)被修改。)
//非底層const的指標和引用可以改變指向物件的值,所以不能繫結到const物件上
(2). 修飾值本身的const均為頂層const:
const int i = 0; // 頂層const;
4. const與函式
1. 值傳遞的const形參
void fcn(const int i) { /* ... */ }
這個函式中,變數i為值傳遞形參,根據值傳遞的初始化規則**,形參i是否為const與傳入的實參是否為const是完全無關的**。這裡的const僅表示i在函式體中不允許修改。
如下的呼叫均為合法呼叫:
int x = 0;
fcn(x);
const int y = 0;
fcn(y);
因為值傳遞的const形參在呼叫上與非const形參沒有區別(大概是指,無論形參是否為const,實參都不會被修改。因為是值傳遞本身就不能修改實參的值),所以僅僅使用const無法區分引數類別,所以無法實現函式過載,如下的過載是錯誤的:
void fcn1(const int i) { /* ... */ }
void fcn1(int i) { /* ... */ } // 錯誤:重複定義函式,不能實現過載
2. const指標/引用的形參
對於頂層const的指標,與上一小節一樣,其const性質與實參無關,頂層const僅表示指標/引用本身在函式體中不允許修改。
所以我們只需要討論底層const的指標/引用。
void fcn2(const int &x) { /* ... */ } // 接受const或非const的int引用,但是不允許通過x修改傳入的物件
void fcn2(const int *y) { /* ... */ } // 接受const或非const的int指標,但是不允許通過y修改傳入的物件
如上兩個函式都定義了底層const的形式引數,它們可以接受const或非const物件,但是不能在函式體內修改這些物件。所以如下的呼叫都是合法的:
int i = 0;
fcn2(i); // 正確:呼叫第一個函式
fcn2(&i); // 正確:呼叫第二個函式
const int j = 0;
fcn2(j); // 正確:呼叫第一個函式
fcn2(&j); // 正確:呼叫第二個函式
由於底層const描述實參性質(不允許在呼叫函式內部被修改),可以在呼叫時區分const,所以使用底層const的指標/引用可以實現函式過載:
void fcn3(int &x) { /* ... */ }
void fcn3(const int &x) { /* ... */ } // 新函式,作用於const的引用
所以可以分別呼叫兩個函式:
int i = 0;
fcn3(i); // 正確:呼叫第一個函式
const int j = 0;
fcn3(j); // 正確:呼叫第二個函式
注意,當傳遞非常量物件時,編譯器會優先呼叫非常量版本的函式。
總結
- 頂層const的形式引數不能實現函式過載,但底層const形參可以
- 當函式不修改引數值時,儘可能將形式引數定義為(底層)const引數。一方面,(底層)const引數可以保護引數物件;另一方面,因為(底層)const引數可以接受常量與非常量物件,但非(底層)const引數只能接受非常量物件。
5. const與類
1. const與類的成員變數
一個類通常包含成員函式和成員變數。
- 類的物件的const修飾表示該物件的成員變數不允許被修改。
- 無論類的成員變數本身是否為const,只要物件宣告為const,成員變數就不允許被修改。
class Number
{
public:
int number = 0;
};
int main()
{
const Number n;
n.number = 1; // 錯誤,n為const物件,不允許被修改
return 0;
}
2. const與類的成員函式
當物件被宣告為const時,該物件不能呼叫非const函式,因為非const函式可能修改成員變數。
class Number
{
public:
void set(int num) { number = num; }
int get() { return number; }
int number = 0;
};
int main()
{
const Number n;
n.set(1); // 錯誤,n為const物件,不能呼叫非const函式
cout << n.get() << endl; // 錯誤,原因同上
return 0;
}
- 將成員函式宣告為const函式,則可以被const物件呼叫,宣告const函式的方法為在其引數列表後新增const關鍵字。
- const成員函式中不允許修改成員變數。也即,並非所有成員函式都可以被宣告為const函式,C++會在編譯時檢查被宣告為const的函式是否修改了成員變數,若是,則報錯,編譯不通過。
class Number
{
public:
void set(int num) const { number = num; } // 錯誤:const函式不允許修改成員變數
int get() const { return number; } // 正確:沒有修改成員變數,可被宣告為const函式
int number = 0;
};
int main()
{
const Number n;
n.set(1); // 錯誤,const函式不允許修改成員變數
cout << n.get() << endl; // 正確,const物件可以呼叫const函式
return 0;
}
與底層const形參一樣,const成員函式也可以實現過載。同樣,當非常量物件呼叫函式時,編譯器會優先呼叫非常量版本的函式。
class T
{
public:
int fcn() { return 1; }
int fcn() const { return 2; } // 正確:定義了可以過載的新函式
};
int main()
{
T t1;
cout << t1.fcn() << endl; // 呼叫第一個函式,輸出"1"
const T t2;
cout << t2.fcn() << endl; // 呼叫第二個函式,輸出"2"
return 0;
}
3. 總結
- 當函式不修改成員變數時,儘可能將函式宣告為const函式,因為const函式可以被非const物件和const物件呼叫,而非const函式只能被非const物件呼叫。
- const函式並不意味著資料安全,雖然不能通過const函式修改成員變數,但是這樣的const僅為頂層const(即成員變數本身不能被修改),若成員變數包含非底層const的指標/引用,雖然成員變數本身不能被修改,但依然可以通過這些指標/引用修改其指向/繫結的物件。
4. const成員函式實現機制
一個類包含成員變數和成員函式,更簡單一點,一個類包含資料和程式碼。物件是類的例項,一個類可以構造許多物件,物件們的資料(成員變數)各自獨立,而程式碼(成員函式)共用一份。
Number n;
n.number; // 呼叫成員變數
n.set(2); // 呼叫成員函式
實際上,由於成員函式共享,所以呼叫成員函式的機制與呼叫成員變數的機制略有區別,簡而言之,編譯器先找到類,然後呼叫類的函式,再隱式地在引數列表中傳入一個物件指標(this指標),表示需要操作該物件。所以,成員函式set()的宣告和定義可以理解為:
void Number::set(Number *const this, int num) { number = num; }
// 僅作為參考,實際上,C++規定顯式定義this指標為非法操作
即,任何一個成員函式都隱式地接受了一個指向物件的this指標。
而在成員函式中對成員變數的預設呼叫實際上都是使用this指標的隱式呼叫,比如 number = num 等價於 this->number = num。
那麼,C++編譯器檢查const函式是否修改了成員變數的機制就很好理解了。
只需要將this指標定義為底層const,以表示不能通過該指標修改成員變數:
void Number::set(const Number *const this, int num) { number = num; }
// 僅作為參考,實際上,C++規定顯式定義this指標為非法操作
第一個const宣告瞭this指標為底層const,而函式中的 number = num 實際為 this->number = num,由於this為底層const,所不能通過this修改number,該操作非法,所以該函式不能宣告為const。
本質上,const函式還是通過傳統的const機制逐條語句檢查來實現的。
相關文章
- c/c++ const關鍵字C++
- const關鍵字
- let關鍵字和const關鍵字
- const關鍵字在C與C++中修飾變數的區別C++變數
- C語言 關鍵字const的作用 const int* 和int *const 的區別C語言
- 關鍵字const是什麼含意?
- C語言中容易混淆的const關鍵字C語言
- iOS常用關鍵字static、const、extern、defineiOS
- 深入聊一下const關鍵字
- explicit關鍵字【C++】C++
- C++關鍵字decltypeC++
- 物聯網學習教程—const關鍵字
- 詳解C++的模板中typename關鍵字的用法C++
- 【轉】C++ static關鍵字C++
- C++ 62個關鍵字的作用C++
- C++11 關鍵字 const 到底怎麼用?C++
- C++中const的用法C++
- C++中const的妙用C++
- C/C++中的constC++
- 【C++】requires關鍵字簡介C++UI
- C++ typeid關鍵字詳解C++
- C++ explicit&noexcept關鍵字C++
- C++ 中的 const 物件與 const 成員函式C++物件函式
- C++ 的關鍵字(保留字)完整介紹C++
- C++關鍵字(持續更新ing)C++
- mysql 中的explain關鍵字MySqlAI
- java中的instanceof關鍵字Java
- java中的static關鍵字Java
- java中this關鍵字Java
- C++中const的簡單用法C++
- C++中const小結C++
- 完全理解JavaScript中的this關鍵字JavaScript
- Java中transient關鍵字的作用Java
- java中static關鍵字的作用Java
- 在Java中this關鍵字的使用Java
- Java中的各種關鍵字Java
- 說說iOS中的常用的關鍵字static ,class(僅限Swift關鍵字)iOSSwift
- scala中yield關鍵字