C++中const、volatile、mutable的用法

constvolatilemutable用法

const修飾普通變量和指針

const修飾變量,一般有兩種寫法:

const TYPE value;

TYPE const value;

這兩種寫法在本質上是一樣的。它的含義是:const修飾的類型爲TYPE的變量value是不可變的。對於一個非指針的類型TYPE,無論怎麼寫,都是一個含義,即value值不可變。 例如:

const int nValue    //nValueconst

int const nValue    //nValueconst

但是對於指針類型的TYPE,不同的寫法會有不同情況:

l  指針本身是常量不可變

(char*) const pContent;

l  指針所指向的內容是常量不可變

const (char) *pContent;

(char) const *pContent;

l  兩者都不可變

const char* const pContent;

識別const到底是修飾指針還是指針所指的對象,還有一個較爲簡便的方法,也就是沿着*號劃一條線:

如果const位於*的左側,則const就是用來修飾指針所指向的變量,即指針指向爲常量;

如果const位於*的右側,const就是修飾指針本身,即指針本身是常量。

const修飾函數參數

const修飾函數參數是它最廣泛的一種用途,它表示在函數體中不能修改參數的值(包括參數本身的值或者參數其中包含的值)

void function(const int Var);     //傳遞過來的參數在函數內不可以改變(無意義,該函數以傳值的方式調用)

void function(const char* Var);   //參數指針所指內容爲常量不可變

void function(char* const Var);   //參數指針本身爲常量不可變(也無意義,var本身也是通過傳值的形式賦值的)

void function(const Class& Var); //引用參數在函數內不可以改變

參數const通常用於參數爲指針或引用的情況,若輸入參數採用“值傳遞”方式,由於函數將自動產生臨時變量用於複製該參數,該參數本就不需要保護,所以不用const修飾。

const修飾類對象/對象指針/對象引用

const修飾類對象表示該對象爲常量對象,其中的任何成員都不能被修改。對於對象指針和對象引用也是一樣。

const修飾的對象,該對象的任何非const成員函數都不能被調用,因爲任何非const成員函數會有修改成員變量的企圖。

例如:

class AAA

{
   void func1();

void func2() const;

}

const AAA aObj;

aObj.func1(); 錯誤

aObj.func2(); 正確

 

const AAA* aObj = new AAA();

aObj->func1(); 錯誤

aObj->func2(); 正確

const修飾數據成員

const數據成員只在某個對象生存期內是常量,而對於整個類而言卻是可變的。因爲類可以創建多個對象,不同的對象其const數據成員的值可以不同。所以不能在類聲明中初始化const數據成員,因爲類的對象未被創建時,編譯器不知道const 數據成員的值是什麼,例如:
class A
{
    const int size = 100; //
錯誤
    int array[size];       //
錯誤,未知的size
}
const
數據成員的初始化只能在類的構造函數的初始化列表中進行。要想建立在整個類中都恆定的常量,可以用類中的枚舉常量來實現,例如:

class A
{


enum {size1=100, size2 = 200 };
int array1[size1];
int array2[size2];


}
枚舉常量不會佔用對象的存儲空間,他們在編譯時被全部求值。但是枚舉常量的隱含數據類型是整數,其最大值有限,且不能表示浮點數。

const修飾成員函數

const修飾類的成員函數,用const修飾的成員函數不能改變對象的成員變量。一般把const寫在成員函數的最後:

class A

{

   …

void function()const; //常成員函數它不改變對象的成員變量也不能調用類中任何非const成員函數。

}

對於const類對象/指針/引用,只能調用類的const成員函數。

const修飾成員函數的返回值

1、一般情況下,函數的返回值爲某個對象時,如果將其聲明爲const時,多用於操作符的重載。通常,不建議用const修飾函數的返回值類型爲某個對象或對某個對象引用的情況。原因如下:如果返回const對象,或返回const對象的引用,則返回值具有const屬性,返回實例只能訪問類A中的公有(保護)數據成員和const成員函數,並且不允許對其進行賦值操作,這在一般情況下很少用到。 

2
、如果給採用指針傳遞方式的函數返回值加const修飾,那麼函數返回值(即指針所指的內容)不能被修改,該返回值只能被賦給加const 修飾的同類型指針:

const char * GetString(void);
如下語句將出現編譯錯誤:
char *str=GetString();
正確的用法是:
const char *str=GetString();
3
、函數返回值採用引用傳遞的場合不多,這種方式一般只出現在類的賻值函數中,目的是爲了實現鏈式表達。如:
class A
{


    A &operate= (const A &other); //
賦值函數

}
A a,b,c; //a,b,c
A的對象

a=b=c;   //
正常
(a=B)=c; //
不正常,但是合法
若賦值函數的返回值加const修飾,那麼該返回值的內容不允許修改,上例中a=b=c依然正確。(a=b)=c就不正確了。

const常量與define宏定義的區別

l  編譯器處理方式不同

define宏是在預處理階段展開。

const常量是編譯運行階段使用。

l  類型和安全檢查不同

define宏沒有類型,不做任何類型檢查,僅僅是展開。

const常量有具體的類型,在編譯階段會執行類型檢查。

l  存儲方式不同

define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配內存。

const常量會在內存中分配(可以是堆中也可以是棧中)

volatile關鍵字

volatile的本意是“易變的”,volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。

當要求使用volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被寄存。例如:

volatile int i=10;

int a = i;

。。。//其他代碼,並未明確告訴編譯器,對i進行過操作

int b = i;

volatile 指出 i是隨時可能發生變化的,每次使用它的時候必須從i的地址中讀取,因而編譯器生成的彙編代碼會重新從i的地址讀取數據放在b中。而優化做法是,由於編譯器發現兩次從i讀數據的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的數據放在b中。而不是重新從i裏面讀。這樣以來,如果i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說volatile可以保證對特殊地址的穩定訪問。

 

注意,在vc6中,一般調試模式沒有進行代碼優化,所以這個關鍵字的作用看不出來。下面通過插入彙編代碼,測試有無volatile關鍵字,對程序最終代碼的影響。首先用classwizard建一個win32 console工程,插入一個voltest.cpp文件,輸入下面的代碼:

#include <stdio.h>

void main()

{

int i=10;

int a = i;

 

printf("i= %d/n",a);

//下面彙編語句的作用就是改變內存中i的值,但是又不讓編譯器知道

__asm {

  mov dword ptr [ebp-4], 20h

}

 

int b = i;

printf("i= %d/n",b);

}

然後,在調試版本模式運行程序,輸出結果如下:

i = 10

i = 32

然後,在release版本模式運行程序,輸出結果如下:

i = 10

i = 10

 

輸出的結果明顯表明,release模式下,編譯器對代碼進行了優化,第二次沒有輸出正確的i值。下面,我們把 i的聲明加上volatile關鍵字,看看有什麼變化:

#include <stdio.h>

void main()

{

volatile int i=10;

int a = i;

 

printf("i= %d/n",a);

__asm {

  mov dword ptr [ebp-4], 20h

}

 

int b = i;

printf("i= %d/n",b);

}

分別在調試版本和release版本運行程序,輸出都是:

i = 10

i = 32

這說明這個關鍵字發揮了它的作用!

 

關於volatile的補充信息:

一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子:

    1). 並行設備的硬件寄存器(如:狀態寄存器)

    2). 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)

    3). 多線程應用中被幾個任務共享的變量

    我認爲這是區分C程序員和嵌入式系統程序員的最基本的問題。嵌入式系統程序員經常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內容將會帶來災難。假設被面試者正確地回答了這是問題(嗯,懷疑這否會是這樣),我將稍微深究一下,看一下這傢伙是不是直正懂得volatile的重要性:

    1). 一個參數既可以是const還可以是volatile嗎?解釋爲什麼。

    2). 一個指針可以是volatile 嗎?解釋爲什麼。

    3). 下面的函數有什麼錯誤:

         int square(volatile int *ptr)

         {

              return *ptr * *ptr;

         }

    下面是答案:

    1). 是的。一個例子是隻讀的狀態寄存器。它是volatile因爲它可能被意想不到地改變。它是const因爲程序不應該試圖去修改它。

    2). 是的。儘管這並不很常見。一個例子是當一箇中服務子程序修該一個指向一個buffer的指針時。

    3). 這段代碼的有個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由於*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:

    int square(volatile int *ptr)

    {

         int a,b;

         a = *ptr;

         b = *ptr;

         return a * b;

     }

    由於*ptr的值可能被意想不到地該變,因此ab可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:

     long square(volatile int *ptr)

     {

            int a;

            a = *ptr;

            return a * a;

     }

mutable關鍵字

mutalbe的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。在C++中,mutable也是爲了突破const的限制而設置的。被mutable修飾的變量(mutable只能用於修飾類的非靜態數據成員),將永遠處於可變的狀態,即使在一個const函數中。

我們知道,假如類的成員函數不會改變對象的狀態,那麼這個成員函數一般會聲明爲const。但是,有些時候,我們需要在const的函數裏面修改一些跟類狀態無關的數據成員,那麼這個數據成員就應該被mutalbe來修飾。下面是一個小例子:

class ClxTest

{

public:

void Output() const;

};

 

void ClxTest::Output() const

{

cout << "Output for test!" << endl;

}

 

void OutputTest(const ClxTest& lx)

{

lx.Output();

}

ClxTest的成員函數Output是用來輸出的,不會修改類的狀態,所以被聲明爲const

函數OutputTest也是用來輸出的,裏面調用了對象lxOutput輸出方法,爲了防止在函數中調用成員函數修改任何成員變量,所以參數也被const修飾。

假如現在,我們要增添一個功能:計算每個對象的輸出次數。假如用來計數的變量是普通的變量的話,那麼在const成員函數Output裏面是不能修改該變量的值的;而該變量跟對象的狀態無關,所以應該爲了修改該變量而去掉Outputconst屬性。這個時候,就該我們的mutable出場了,只要用mutalbe來修飾這個變量,所有問題就迎刃而解了。下面是修改過的代碼:

class ClxTest

{

public:

ClxTest();

~ClxTest();

 

void Output() const;

int GetOutputTimes() const;

 

private:

mutable int m_iTimes;

};

 

ClxTest::ClxTest()

{

m_iTimes = 0;

}

 

ClxTest::~ClxTest()

{}

 

void ClxTest::Output() const

{

cout << "Output for test!" << endl;

m_iTimes++;

}

 

int ClxTest::GetOutputTimes() const

{

return m_iTimes;

}

 

void OutputTest(const ClxTest& lx)

{

cout << lx.GetOutputTimes() << endl;

lx.Output();

cout << lx.GetOutputTimes() << endl;

}

計數器m_iTimesmutable修飾,那麼它就可以突破const的限制,在被const修飾的函數裏面也能被修改。

 

 

參考資料:

http://www.cppblog.com/jukevin/archive/2008/12/27/70499.html

http://dev.csdn.net/develop/article/50/50538.shtm

http://www.eb163.com/club/thread-956-1-1.html

http://www.diybl.com/course/3_program/c++/cppjs/200798/70290_4.html

(這篇博文太經典了,必須轉了^-^ 轉自:http://blog.csdn.net/wuliming_sc/article/details/3717017

發佈了29 篇原創文章 · 獲贊 9 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章