C++ const 引用 指針
先簡單回憶一下常量的性質:
int main()
{
const int buffSize = 512;
buffsize = 512; //× buffSize是常量
}
初始化時:
const int i = get_val(); //√ 運行時初始化
const int j = 42; //√ 編譯時初始化
const int k; //× k未經初始化
當用一個對象去初始化另外一個對象,他們是不是const就無關緊要
int i = 42;
const int ci = i;
int j = ci;
ci是整形常量,但ci的常量特征僅僅在執行 改變ci 的操作時才會發揮作用
const和引用
對常量的引用
把引用綁定到const對象上,稱之為對常量的引用
對常量的引用不能用作修改它所綁定的對象,也就是說:引用 及其 引用的對象 都是常量
const int ci = 1024;
const int &r1 = ci;
需要注意的是,非常量引用不能引用常量對象:
const int ci = 1024;
const int &r1 = ci;
r1 = 42; //× r1是對常量的引用
int &r2 = ci; //× r2是一個非常量引用,ci是一個常量對象
因為不允許把ci
用作修改它所綁定的對象,所以也不能通過引用去改變ci(假設第四句合法,那我們就可以通過r2去改變ci了,顯然是不對的)
以下兩句同理
int &r3 = r1; //×
const int &r4 = r1; //√
我們口頭所說的常量引用其實是對const的引用,嚴格來說是不存在常量引用的,因為和指針不一樣,引用不是對象,我們沒有辦法讓引用本身很定不變
(P.S:由於C++不允許隨意改變引用所綁定的對象,所以也可以理解為,所有的引用都是常量,當然了,引用的對象是否是常量,會決定其所能參與的操作,但無論如何也不會影響到引用和對象的綁定關系)
初始化對常量的引用
我們知道引用的類型必須要和所引用的對象類型一致,但涉及初始化常量的引用會出現第二種例外(第一種:初始化常量引用是允許用任意表達式作為初始值,只要該表達式能轉換成引用的類型)
int i = 42;
const int &r1 = i; //√ 允許const int綁定到一個普通int對象上
const int &r2 = 42; //√ r2是一個常量引用
const int &r3 = r1 * 2; //√ r3是一個常量引用
int &r4 = r1 * 2; //× r4是一個普通的非常量引用,非常量引用初始值必須為左值
int &r5 = 2; //× 非常量引用初始值必須為左值
我們知道,非常量引用的初始值必須為左值,常量引用的初始值可以為左值、右值,再看看下面的情況
int i =2;
double &r =i; //編譯報錯
const double &r =i; //編譯通過
//難道這個i不是左值?
double i =2;
double &r =i; //編譯通過
//難道這里的i又是左值了?
為什么會出現這種情況?先來看一個簡單的例子
double dval = 0.114514;
const int &ri = dval;
cout << "ri = " << ri <<endl;
運行輸出
ri=0
在這個過程中,由於類型不匹配,編譯器把代碼改成了:
double dval = 0.114514;
const int temp = dval;
const int &ri = temp;
cout << "ri = " << ri <<endl;
這種情況下,ri綁定了一個臨時量對象,臨時變量都是const,所以沒有const的引用會失敗
這下你可看懂上面的代碼發生了什么了吧
你可以想象以下,如果ri不是常量時,執行了上述初始化過程會帶來怎樣的后果:如果ri不是常量。就允許對ri賦值,這樣就會改變ri所引用對象的值(此時綁定的是臨時量而非dval),所以C++也把以下這種行為歸為非法
double dval = 0.114514;
int &ri = dval;//編譯報錯
cout << "ri = " << ri <<endl;
同時注意,對const的引用可能引用一個並非const的對象
對const的引用僅對引用可參與的操作做出了限定,對於引用對象本身是否是一個常量沒有做出限定,因此對象也可能是個非常量,允許通過其他途徑改變它的值
int i = 42;
int &r1 = i;
const int &r2 = i;
//r2 = 0; //× r2是一個常量引用
cout << "r2 = " << r2 <<endl;
r2 = 0; //不可通過編譯
i = 0; //可通過編譯
cout << "r2 = " << r2 <<endl;
該程序輸出如下:
r2 = 42
r2 = 0
const和指針
指向常量的指針
類似於對常量的引用,指向常量的指針不能用於改變其所指對象的值
同時,想要存放常量對象的地址,只能使用指向常量的指針:
const double homo = 1.14;
double *ptr = &homo; //× ptr是一個普通指針
const double *cptr = &homo; //√
cptr = 5.14; //× 不能給*cptr賦值
//不允許 指向常量的指針 用於改變其所指對象的值
不同於引用,我們能改變指向常量的指針所指向的對象
const double homo = 1.14;
const double *cptr = &homo;
cout << "cptr = " << *cptr <<endl;
cout << "*cptr = " << cptr <<endl;
const double homo2 = 5.14;
cptr = &homo2;
cout << "cptr = " << *cptr <<endl;
cout << "*cptr = " << cptr <<endl;
cptr++; //合法
//允許一個 指向常量的指針 指向 一個非常量對象
注意,與引用類似,雖然我們說指針的類型必須與所指對象一致,但是這里有第一種例外:允許一個指向常量的指針指向一個非常量對象,但是不允許通過 指向常量的指針 修改非常量對象的值
const double homo = 1.14;
const double *cptr = &homo;
double dval = 3.14;
cptr = &dval; //允許一個 指向常量的指針 指向 一個非常量對象
*cptr = 0.0 //但是不允許通過 指向常量的指針 修改非常量對象的值
所以,指向常量的指針也沒有規定其所指的對象必須是一個常量,所謂指向常量僅僅要求不能通過該指針修改所指向對象的值,而沒有規定所指對象的值不能通過其他途徑改變
const double homo = 1.14;
const double *cptr = &homo;
cout << "cptr = " << *cptr <<endl
double dval = 5.14;
cptr = &dval; //允許一個 指向常量的指針 指向 一個非常量對象
//*cptr = 0.0 //但是不允許通過 指向常量的指針 修改非常量對象的值
cout << "cptr = " << *cptr <<endl;
dval = 0.0 //所指對象的值可以通過其他途徑改變
cout << "cptr = " << *cptr <<endl;
現在我們輸出就變成了:
cptr = 1.14
cptr = 5.14
cptr = 0
const指針
和引用不同,指針本身是對象,所以允許把指針本身定為常量,也就是常量指針,常量指針必須被初始化,並且初始化完成后值(存放在指針對象里的地址)不能改變
把*
放const關鍵字之后,用以說明指針是指向常量的指針(不能通過該指針修改所指向的對象)
把*
放const關鍵字之前,用以說明指針是一個常量(即指針本身的值——存儲的地址不變)
int errorNumb = 0;
int* const curErr = &errorNumb; //curErr是一個常量指針,一直指向errNumb
const double pi = 3.1415;
const double* const pip = π //pip是一個 指向常量對象 的 常量指針
以下兩種寫法區別很大:
int* const curErr = &errorNumb; //curErr一直指向errNumb
*curErr = 1; //可以修改所指變量的值
const int* curErr = &errorNumb; //curErr是一個 指向常量的指針
*curErr = 1; //× 不能通過curErr修改所指對象的值
頂層const和底層const
由於指針本身是一個對象,它又可以指向另外一個對象,因此指針本身是不是常量和指針所指的對象是不是常量就是兩個互相獨立的問題,頂層const表示指針本身是個常量,底層const表示指針所指的對象是個常量
頂層const其實可以表示任意的對象(自身)是常量,指針式比較特殊的,因為它既可以是頂層也可以是底層const
int i = 0;
int* const p1 = &i; //p1本身是常量,頂層const
const int ci = 42; //ci本身是常量,頂層const
const int* p2 = &ci; //*在const之后,p2是指向常量的指針,底層const
const int* const p3 = p2; //先看左邊是頂層,再看右邊是底層,p3是指向常量的常量指針
const int& r = ci; //聲明引用的const都是底層const,r是一個對常量的引用
拷貝操作不會影響被拷貝對象的值,頂層const不受影響
i = ci;
p2 = p3;
但是底層const就會產生限制:
拷貝操作時,拷入和拷出的對象必須有相同的底層const資格,活着兩個對象數據類型能轉換(非常量能轉成常量,反之不行)
int* p = p3; //× p3包含底層const,p沒有
const int* p = p3; //√ p和p3都是底層const
p2 = p3; //√ p2和p3都是底層const
p2 = &i; //√ int能轉換成const int*
int& r = ci; //× 普通int&不能綁到const int&上
const int& r2 = i; //√ const int&可以綁到一個普通int上
常量對象成員
class Corrdinate //坐標
{
public:
Corrdinate(int x,int y);
private:
const int m_iX;
const int m_iY;
};
可以看到兩個整形成員變量都是常量,我們也類似指針和數組,稱之為常量成員
由於是常量,初始化肯定就會受到限制:
//m_iXh,m_iY是常量成員,以下寫法是錯誤的
Corrdinate::Corrdinate(int x, int y) {
m_iX = x;
m_iY = y;
}
正確的方法應該是使用初始化列表
Corrdinate::Corrdinate(int x, int y):m_iX(x), m_iY(y) {
}
那如果類的成員也是對象呢?
我們再看一個線段類
class Line
{
public:
Line(int x1, int y1, int x2, int y2);
private:
const Corrdinate m_corrA;
const Corrdinate m_corrB;
};
我們需要讓一個線段定義后不能修改,於是我們把線段的兩個端點定義為const,類似地稱之為常量對象
定義完后,想通過構造函數初始化,也是使用初始化列表
Line::Line(int x1, int y1, int x2, int y2) :m_corrA(x1, y1), m_corrB(x2, y2) {
}
常量成員函數
我們還可以用cosnt修飾成員函數,這樣的成員函數叫常量成員函數
class Corrdinate //坐標
{
public:
Corrdinate(int x,int y);
void changeX() const; //常量成員函數
void changeX();
private:
int m_iX;
int m_iY;
};
然后你就會發現
void Corrdinate::changeX() const { //×
m_iX = 10;
}
void Corrdinate::changeX() { //√
m_iX = 20;
}
常量成員函數中不能改變數據成員的值,為什么呢?
這個函數只是看似沒有參數
void Corrdinate::changeX() {
m_iX = 20;
}
實際在編譯時會變成
void changeX(Corrdinate *this) {
this->m_iX = 20;
}
當我們定義了常量成員函數時:
void Corrdinate::changeX() const {
m_iX = 20;
}
實際在編譯時會變成
void changeX(const Corrdinate *this) {
this->m_iX = 20;
}
此時的this指針是個常量指針,不能用於改變其所指對象的值
還要注意的是,由於我們定義了兩個版本的changeX
函數,你還要弄明白什么時候會調用哪個changeX
int main()
{
Corrdinate cor1(3, 5);
cor1.changeX(); //此時調用的是void changeX()
const Corrdinate cor2(3, 5); //常量對象
cor1.changeX(); //此時調用的是void changeX() const
}
當成員函數的 const 和 non-const 版本同時存在時:
const object 只能調用 const 版本,non-const object 只能調用 non-const 版本
const object(data members不得變動) | non-const objectdata members可變動) | |
---|---|---|
const member function(保證不更改data members) | 可 | 可 |
non-const member function(不保證) | 不可 | 可 |