0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C++ const、volatile和mutable關(guān)鍵字詳解

冬至子 ? 來源:iDoitnow ? 作者:艱默 ? 2023-07-18 16:49 ? 次閱讀

對于cvconstvolatile)類型限定符和關(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_valconst的,你幫我看點(diǎn),我不能隨意的修改,但這個值在作用域外可能會被其他東西修改,這玩意也是volatile的,你編譯的時候也別優(yōu)化它了,在每次讀取的時候,也老老實實從它的存儲位置重新讀取吧。

注:volatile constconst 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é)果:

ashow()被調(diào)用了:0次。
data : 1
ashow()被調(diào)用了:1次。

上例void show()const修飾后,導(dǎo)致在該函數(shù)內(nèi)類成員不能被修改,但由于timesmutable修飾后,突破了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)存地址讀取。

同時,constvolatile限定的引用和指針支持下列的隱式轉(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限制。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 寄存器
    +關(guān)注

    關(guān)注

    31

    文章

    5274

    瀏覽量

    119668
  • 狀態(tài)機(jī)
    +關(guān)注

    關(guān)注

    2

    文章

    490

    瀏覽量

    27438
  • C++語言
    +關(guān)注

    關(guān)注

    0

    文章

    147

    瀏覽量

    6945
  • gcc編譯器
    +關(guān)注

    關(guān)注

    0

    文章

    78

    瀏覽量

    3334
收藏 人收藏

    評論

    相關(guān)推薦

    C語言關(guān)鍵字volatile的用法

    許多程序員都無法正確理解C語言關(guān)鍵字volatile,這并不奇怪。因為大多數(shù)C語言書籍通常都是一兩句一帶而過,本文將告訴你如何正確使用它。
    發(fā)表于 04-11 09:35 ?2511次閱讀

    C語言關(guān)鍵字const的幾種用法

    本期來講解一個C語言的關(guān)鍵字——const。
    發(fā)表于 06-21 11:05 ?1493次閱讀
    <b class='flag-5'>C</b>語言<b class='flag-5'>關(guān)鍵字</b><b class='flag-5'>const</b>的幾種用法

    C++中常用關(guān)鍵字詳解(2)

    C++中,volatile是一個關(guān)鍵字,用于修飾變量,告訴編譯器該變量的值可能在程序流程之外被意外修改,因此編譯器不應(yīng)該對該變量進(jìn)行優(yōu)化(如緩存變量值或重排指令順序)。
    發(fā)表于 08-08 16:15 ?275次閱讀

    【原創(chuàng)分享】單片機(jī)編程關(guān)鍵字volatile

    、volatile其實和const一樣是一種類型修飾符,用它修飾的變量表示可以被某些編譯器未知的因素而改變,比如操作系統(tǒng)、硬件或者其他線程等等。遇到這個關(guān)鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進(jìn)行優(yōu)化
    發(fā)表于 06-29 11:17

    C語言volatile關(guān)鍵字詳解 精選資料分享

    1.volatile和什么有關(guān)百度翻譯是這樣子翻譯volatile的:圖1-1 百度翻譯volatile截圖volatile屬于C語言的
    發(fā)表于 07-22 07:20

    嵌入式程序員常見的const、static、volatile關(guān)鍵字

    嵌入式程序員const、static、volatile三個關(guān)鍵字的樸素認(rèn)識摘要:在C語言程序編寫中,const、static
    發(fā)表于 12-21 06:08

    C語言中的volatile關(guān)鍵字

    volatile關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。
    發(fā)表于 05-27 09:32 ?3258次閱讀

    C++Cconst關(guān)鍵字有何差別?

    C++C中的const關(guān)鍵字有何差別?
    的頭像 發(fā)表于 02-03 14:51 ?1763次閱讀

    關(guān)于volatile關(guān)鍵字對編譯器優(yōu)化的影響

    volatile關(guān)鍵字對編譯器優(yōu)化的影響
    的頭像 發(fā)表于 02-28 17:15 ?2873次閱讀

    C++中的const和引用的討論

    今天給大家分享一下這段時間學(xué)習(xí)c++的總結(jié)學(xué)習(xí):c++里面的const關(guān)鍵字和引用。
    的頭像 發(fā)表于 12-24 15:35 ?789次閱讀

    C++mutable關(guān)鍵字詳解與實戰(zhàn)

    mutable關(guān)鍵字詳解與實戰(zhàn) 在C++mutable關(guān)鍵字是為了突破
    的頭像 發(fā)表于 09-10 09:23 ?5491次閱讀

    一文詳解volatile關(guān)鍵字

    volatile 是易變的、不穩(wěn)定的意思。和const一樣是一種類型修飾符,volatile關(guān)鍵字修飾的變量,編譯器對訪問該變量的代碼不再進(jìn)行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問。
    的頭像 發(fā)表于 02-15 11:54 ?1008次閱讀
    一文<b class='flag-5'>詳解</b><b class='flag-5'>volatile</b><b class='flag-5'>關(guān)鍵字</b>

    C++中的const關(guān)鍵字介紹

    前一篇文章我們主要介紹了C++中的復(fù)合類型引用和指針,這篇文章我們將會主要介紹C++const關(guān)鍵字。有時候我們想定義一個值不能被改變的變量,例如我們想使用一個變量存儲buffer的
    的頭像 發(fā)表于 03-17 14:01 ?595次閱讀

    淺談C++mutable關(guān)鍵字

    C++11中推出了一種特殊的關(guān)鍵字mutable用于修飾類變量。它的作用是標(biāo)注該變量一定會被修改,因此也就不是const類型。目的是為了使這些成員變量在被
    的頭像 發(fā)表于 04-15 11:13 ?3699次閱讀

    const關(guān)鍵字應(yīng)用總結(jié)

    C++中的const關(guān)鍵字的用法非常靈活,而使用const將大大改善程序的健壯性
    的頭像 發(fā)表于 05-26 09:06 ?522次閱讀