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