對(duì)于cv
(const
與volatile
)類型限定符和關(guān)鍵字mutable
在《cppreference》中的定義為:
cv
可出現(xiàn)于任何類型說(shuō)明符中,以指定被聲明對(duì)象或被命名類型的常量性(constness)或易變性(volatility)。
- const ----------定義類型為常量類型。
- volatile --------定義類型為易變類型。
mutable
用于指定不影響類的外部可觀察狀態(tài)的成員(通常用于互斥體、記憶緩存、惰性求值和訪問(wèn)指令等)。
- mutable ------容許常量類類型對(duì)象修改相應(yīng)類成員。
const
const
實(shí)際上是一個(gè)類型說(shuō)明,告訴編譯器const
修飾的目標(biāo)是不變的,允許編譯器對(duì)其進(jìn)行額外的優(yōu)化,如果后面代碼不小心修改它了,就編譯失敗,告訴用戶該目標(biāo)被意外修改了,提高程序的安全性和可控性。
const修飾普通變量
被const
修飾過(guò)的變量,編譯器往往將其作為一個(gè)常量進(jìn)行處理,同時(shí),const
修飾的變量在編譯階段會(huì)被編譯器替換為相應(yīng)的值,來(lái)提高程序的執(zhí)行效率。
#include < iostream >
using namespace std;
int main() {
const int i = 50; // 普通常量
const static int si = 50; // 靜態(tài)常量
int* p_int = (int*)&i; // 強(qiáng)制類型轉(zhuǎn)換為int*
int* p_sint = (int*)&si;
*p_int = 100; // 通過(guò)非常常量指針修改常量i的值,該行為是C++為未定義行為
//*p_sint = 100;//編譯不會(huì)報(bào)錯(cuò),程序運(yùn)行時(shí)崩潰,且該行為也是C++為未定義行為
cout < < "i:" < < i < < ", i的地址: " < < &i < < endl;//編譯器階段會(huì)將常量i替換為50
cout < < "*p_int:" < < *p_int < < ", *p_int的地址: " < < p_int < < endl;
return 0;
}
【 注 :類型是 const
修飾的對(duì)象,或常量對(duì)象的非可變子對(duì)象。這種對(duì)象不能被修改: 直接嘗試這么做是編譯時(shí)錯(cuò)誤,而間接嘗試這么做(例如通過(guò)到非常量類型的引用或指針修改常量對(duì)象)的行為未定義。 】
輸出結(jié)果:
i:50, i的地址: 0x7fffffffd9d4
*p_int:100, *p_int的地址: 0x7fffffffd9d4
從i
和*p_int
打印出的地址都是0x7fffffffd9d4
可以看出,我們偷偷修改i
的值成功了(但該行為是C++未定義的行為),但是為何i
和*p_int
的結(jié)果卻是不同的,這就從側(cè)面證實(shí)了const
常量具有宏替換的特性,即程序在編譯階段就會(huì)其進(jìn)行部分的替換,例如上述例子中對(duì)語(yǔ)句
cout < < "i:" < < i < < ", i的地址: " < < &i < < endl;
在編譯階段替換為
cout < < "i:" < < 50 < < ", i的地址: " < < &i < < endl;
因此導(dǎo)致我們輸出的i
的值為50
。
同時(shí),當(dāng)我們想要通過(guò)使用非常量指針修改靜態(tài)常量si
時(shí)候,編譯通過(guò),但是在運(yùn)行過(guò)程中導(dǎo)致程序崩潰(這就是不按規(guī)矩辦事的后果,使用了C++未定義的行為,編譯器也幫不了我們,最終導(dǎo)致程序掛掉)。
const的內(nèi)部鏈接性
通常情況下,在未聲明為 extern
的變量聲明上使用 const
限定符,會(huì)給予該變量?jī)?nèi)部連接(即名稱在用于聲明變量的文件的外部是不可見(jiàn)的)的特性。即與static
類似,但是與其不同的是其可以通過(guò)extern
來(lái)改變其內(nèi)部連接性。
const修飾指針和引用
/********指針********/
//指向const對(duì)象的指針
const int* p1; //const修飾的是int,表示p1指向的內(nèi)容不能被修改
int const* p2; //const修飾的是int,表示p2指向的內(nèi)容不能被修改
//指向?qū)ο蟮腸onst指針
int* const p3; //const修飾的是*,表示p3的指向不能被修改
//指向const對(duì)象的const指針
const int* const p4; //第一個(gè)修飾的是int,第二個(gè)修飾的是*,表示p4指向的內(nèi)容和p4的指向都不能被修改
const int const* p5; //同上,表示p5指向的內(nèi)容和p5的指向都不能被修改
/*注:從上面我們可以總結(jié)出來(lái)的一個(gè)規(guī)律:
const優(yōu)先修飾其左邊最近的類型,
如果左邊沒(méi)有,就修飾右邊離他最近的那個(gè)*/
/********引用********/
const int a = 0;
//由于a1引用a之后,不能引用其他實(shí)體,所以對(duì)于const int&可以看作是const int* const
const int& a1 = a;
int b = 0;
const int& b1 = b;//C++允許無(wú)限定類型的引用/指針能被轉(zhuǎn)換成到 const 的引用/指針
const在類中的應(yīng)用
非靜態(tài)數(shù)據(jù)成員可以被cv
限定符修飾,這些限定符寫在函數(shù)聲明中的形參列表之后。其中,帶有不同cv
限定符(或者沒(méi)有)的函數(shù)具有不同的類型,它們可以相互重載。在具有cv
限定符的成員函數(shù)內(nèi),*this
擁有同向的cv
限定。例子如下:
#include < iostream >
using namespace std;
class A {
public:
A(int a) : a(a){};
void show() { cout < < "void show(): " < < a < < endl; }
void show() const { cout < < "void show() const: " < < a < < endl; } // 重載
/*這里
void show() 等價(jià)于 void show(A* this)
void show() const 等價(jià)于 void show(const A* this)
因此該成員函數(shù)show()可以被重載
*/
void set_a(int n) { a = n; }
/*
void set_a(int n) const
{
a = n;//此時(shí)*this擁有const限制,導(dǎo)致a不能夠被修改
}//程序報(bào)錯(cuò)
*/
// void print_a() const 等價(jià)于 void print_a(const A* this)
void print_a() const { cout < < "print_a() const: " < < a < < endl; }
private:
int a;
};
int main() {
A a1(1);
const A a2(2);
a1.show();
a2.show();
a1.print_a(); // 非const對(duì)象可以調(diào)用const成員函數(shù)
// 根本原因是A* this可以隱式轉(zhuǎn)換const A* this
// 最終調(diào)用void print_a(const A* this)
// 即void print_a() const
a2.print_a();
a1.set_a(2);
// a2.set_a(1); // 編譯報(bào)錯(cuò),const對(duì)象不能調(diào)用非const成員函數(shù),根本原因是根本原因是const A* this可以隱式轉(zhuǎn)換A* this
return 0;
}
輸出結(jié)果:
void show(): 1
void show() const: 2
print_a() const: 1
print_a() const: 2
對(duì)于上述例子我們可以得出:
const
對(duì)象不能調(diào)用非const
成員函數(shù)。 =>const
成員函數(shù)內(nèi)部不能調(diào)用其他非cosnt
成員函數(shù)。- 非
const
對(duì)象可以調(diào)用const
成員函數(shù)。=> 非cosnt
成員函數(shù)可以調(diào)用其他cosnt
成員函數(shù)。
volatile
volatile
主要作用是告訴編譯器其修飾的目標(biāo)是易變的,在編譯的時(shí)候不要去優(yōu)化它(例如讀取的時(shí)候,都從目標(biāo)內(nèi)存地址讀取),防止編譯器誤認(rèn)為某段代碼中目標(biāo)是不會(huì)被改變,而造成過(guò)度優(yōu)化。
注 :編譯器大部分情況是從內(nèi)存讀取變量的值的,但有時(shí)候編譯器認(rèn)為在某段代碼中,某個(gè)變量的值是沒(méi)有變化的,所以認(rèn)為寄存器里的值跟內(nèi)存里是一樣的,為了提高讀取速度,編譯器可能會(huì)從寄存器中讀取變量,但是在某些情況下變量的值被其他元素(如另外一個(gè)線程或者中斷服務(wù))修改,這樣導(dǎo)致程序讀取變量的值不是最新的,產(chǎn)生異常。
因此,volatile
關(guān)鍵字對(duì)于聲明共享內(nèi)存中可由多個(gè)進(jìn)程訪問(wèn)的對(duì)象或用于與中斷服務(wù)例程通信的全局?jǐn)?shù)據(jù)區(qū)域很有用。如果某個(gè)目標(biāo)被聲明為 volatile
,則每當(dāng)程序訪問(wèn)它的時(shí)候,編譯器都會(huì)重新加載內(nèi)存中的值。 這雖然降低了目標(biāo)的讀取速度,但是保證了目標(biāo)讀取的正確性,這也是保證我們程序可預(yù)見(jiàn)性的唯一方法。
下面我們通過(guò)一個(gè)讀取系統(tǒng)時(shí)間的例子來(lái)看一下volatile
在實(shí)際開(kāi)發(fā)過(guò)程中的應(yīng)用:
#include < iostream >
#include < ctime >
#include < unistd.h >
// #include < windows.h > //win下為該頭文件
using namespace std;
int main()
{
const time_t time_val = 0;
time_t *p_time_t = const_cast< time_t * >(&time_val);
time(p_time_t);
cout < < time_val < < endl;
// 休眠1s
sleep(1); // linux下sleep函數(shù),單位為秒
// Sleep(1000); // win下的sleep函數(shù),單位為毫秒
time(p_time_t);
cout < < time_val < < endl;
return 0;
}
注 :
time
函數(shù)在ctime
頭文件中定義,其主要作用是獲取系統(tǒng)當(dāng)前時(shí)間,其原型如下:
std::time_t time( std::time_t* arg );
返回編碼為
std::time_t
對(duì)象的當(dāng)前日歷時(shí)間,并將它存儲(chǔ)于arg
所指向的對(duì)象,除非arg
是空指針。
輸出結(jié)果:
0
0
很明顯結(jié)果不符合我們的預(yù)期,具體原因就是我們上面介紹的 const常量具有宏替換的特性 ,編譯器認(rèn)為這段可以更好的優(yōu)化,在編譯階段就對(duì)其進(jìn)行了替換。那我們?nèi)绾涡薷牟拍苓_(dá)到我們的實(shí)現(xiàn)呢?對(duì),就是添加volatile
關(guān)鍵字修飾,具體實(shí)現(xiàn)如下:
#include < iostream >
#include < ctime >
#include < unistd.h >
// #include < windows.h > //win下為該頭文件
using namespace std;
int main()
{
volatile const time_t time_val = 0;
time_t *p_time_t = const_cast< time_t * >(&time_val);
time(p_time_t);
cout < < time_val < < endl;
// 休眠1s
sleep(1); // linux下sleep函數(shù),單位為秒
// Sleep(1000); // win下的sleep函數(shù),單位為毫秒
time(p_time_t);
cout < < time_val < < endl;
return 0;
}
輸出結(jié)果:
1680339937
1680339938
從輸出結(jié)果看出,結(jié)果符合我們的預(yù)期。
這時(shí)候你可能會(huì)有疑問(wèn):volatile const
是什么鬼?const
表示time_val
是常量,volatile
表示time_val
是可變的,難道是易變的常量?這不是矛盾嗎?
這里我們確實(shí)可以將time_val
成為易變的常量,只不過(guò)常量(不可修改)意味著time_val
在其作用域中(這里指的是main
函數(shù)中)是不可以被改變的,但是在其作用域外面(這里指的是time()
函數(shù)內(nèi))是可以被改變的。volatile const
其實(shí)是在告訴編譯器,在main()
函數(shù)內(nèi),time_val
是const
的,你幫我看點(diǎn),我不能隨意的修改,但這個(gè)值在作用域外可能會(huì)被其他東西修改,這玩意也是volatile
的,你編譯的時(shí)候也別優(yōu)化它了,在每次讀取的時(shí)候,也老老實(shí)實(shí)從它的存儲(chǔ)位置重新讀取吧。
注:
volatile const
和const volatile
是一樣的,都代表易變的常量。
volatile修飾常量、指針和引用
volatile
修飾常量指針和引用的使用方法域const
類似,這里不做過(guò)多的解釋,但需要注意的是volatile
沒(méi)有像const
的內(nèi)部鏈接屬性。
volatile修飾函數(shù)的參數(shù)
int sequare(volatile int* ptr)
{
return *ptr * *ptr;
}
上述例子是為了計(jì)算一個(gè)易變類型的int
的平方,但是函數(shù)內(nèi)部實(shí)現(xiàn)存在問(wèn)題,因?yàn)?/p>
return *ptr * *ptr;
其處理邏輯類似下面的情況:
int a = *ptr;
int b = *ptr;
return a * b;
由于*ptr
是易變的,因此a
、b
獲取的值可能是不一樣的,因此最好采用如下的方式:
int sequare(volatile int* ptr)
{
int a = *ptr;
return a * a;
}
mutable
mutable
主要是為了突破const
的某些限制而設(shè)定的,即允許常量類型對(duì)象相應(yīng)的類成員可以被修改,其常在非引用非常量類型的非靜態(tài)數(shù)據(jù)成員中出現(xiàn)。
在上面的介紹中,我們知道在在獲取類某些狀態(tài)的成員函數(shù)中,如果不涉及狀態(tài)的變更,我們一般會(huì)將成員函數(shù)聲明成const
,這將意味著在該函數(shù)中,所有的成員函數(shù)都不可以被修改,但有些時(shí)候我們需要在該const
函數(shù)中修改一些跟類狀態(tài)無(wú)關(guān)的數(shù)據(jù)乘員,那么這時(shí)候就需要mutable
發(fā)揮作用了,即將該需要修改的成員使用mutable
修飾。
#include < iostream >
using namespace std;
class A
{
public:
A(int data = 0) : int_data(data), times(0) {}
void show() const
{
times++; //因?yàn)閠imes被mutable修飾,突破了const的限制
cout < < "data : " < < int_data < < endl;
}
int getNumOfCalls() { return times; }
private:
int int_data;
mutable int times;
};
int main()
{
A a(1);
cout < < "a的show()被調(diào)用了:" < < a.getNumOfCalls() < < "次。" < < endl;
a.show();
cout < < "a的show()被調(diào)用了:" < < a.getNumOfCalls() < < "次。" < < endl;
return 0;
}
輸出結(jié)果:
a的show()被調(diào)用了:0次。
data : 1
a的show()被調(diào)用了:1次。
上例void show()
被const
修飾后,導(dǎo)致在該函數(shù)內(nèi)類成員不能被修改,但由于times
被mutable
修飾后,突破了const
的限制,使得times
在該函數(shù)內(nèi)部可以被修改。
mutable
的另一個(gè)應(yīng)用場(chǎng)景就是用來(lái)移除lambda
函數(shù)中按復(fù)制捕獲的形參的const
限制。通常情況下(不提供說(shuō)明符),復(fù)制捕獲的對(duì)象在lambda
體內(nèi)是 const
的,并且在其內(nèi)部無(wú)法修改被捕獲的對(duì)象,具體的例子如下:
#include < iostream >
using namespace std;
int main() {
int a = 0;
const int b = 0;
auto f1 = [=]() {
/*
a++; // 錯(cuò)誤,不提供說(shuō)明符時(shí)復(fù)制捕獲的對(duì)象在 lambda 體內(nèi)是 const 的。
b++; // 錯(cuò)誤,同上,且按值傳遞const也會(huì)傳遞進(jìn)來(lái)
*/
return a;
};
auto f2 = [=]() mutable { // 提供mutable說(shuō)明符
a++; // 正確,mutable解除const限制。
/*
b++; // 錯(cuò)誤,mutable無(wú)法突破b本身的const限制
*/
return a;
};
cout < < a < < ", " < < b < < endl; // 輸出0, 0
cout < < f1() < < ", " < < f2() < < endl; // 輸出0, 1
return 0;
}
總 結(jié)
const
主要用來(lái)告訴編譯器,被修飾的變量是不變類型的,在有些情況下可以對(duì)其進(jìn)行優(yōu)化,同時(shí)如果后面代碼不小心修改了,編譯器在編譯階段報(bào)錯(cuò)。在類的應(yīng)用中,const
對(duì)象不能調(diào)用非const
成員函數(shù),非const
對(duì)象可以調(diào)用const
成員函數(shù)。
volatile
主要用來(lái)告訴編譯器,被修飾變量是易變,在編譯的時(shí)候不要對(duì)其進(jìn)行優(yōu)化,讀取它的時(shí)候直接從其內(nèi)存地址讀取。
同時(shí),const
和volatile
限定的引用和指針支持下列的隱式轉(zhuǎn)換:
- 無(wú)限定類型的引用/指針能被轉(zhuǎn)換成
const
的引用/指針 - 無(wú)限定類型的引用/指針能被轉(zhuǎn)換成
volatile
的引用/指針 - 無(wú)限定類型的引用/指針能被轉(zhuǎn)換成
const volatile
的引用/指針 const
類型的引用/指針能被轉(zhuǎn)換成const volatile
的引用/指針volatile
類型的引用/指針能被轉(zhuǎn)換成const volatile
的引用/指針
對(duì)于const
修飾的成員函數(shù)內(nèi)類成員const
的屬性,可以通過(guò)使用對(duì)mutable
來(lái)解除const
限制。同樣的,mutable
也可以用來(lái)移除lambda
函數(shù)中按復(fù)制捕獲的形參的const
限制。
-
寄存器
+關(guān)注
關(guān)注
31文章
5357瀏覽量
120592 -
狀態(tài)機(jī)
+關(guān)注
關(guān)注
2文章
492瀏覽量
27561 -
C++語(yǔ)言
+關(guān)注
關(guān)注
0文章
147瀏覽量
7007 -
gcc編譯器
+關(guān)注
關(guān)注
0文章
78瀏覽量
3396
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論