const修飾變量,通常有兩種寫法:
const TYPE value;
TYPE const value;
這兩種寫法在本質上是同樣的。它的含義是:const修飾的類型爲TYPE的變量value是不可變的。對於一個非指針的類型TYPE,不管怎麼寫,都是一個含義,即value值不可變。 例如:
const int nValue; //nValue是const
int const nValue; //nValue是const
可是對於指針類型的TYPE,不一樣的寫法會有不一樣狀況:
l 指針自己是常量不可變
(char*) const pContent;
l 指針所指向的內容是常量不可變
const (char) *pContent;
(char) const *pContent;
l 二者都不可變
const char* const pContent;
識別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成員函數會有修改爲員變量的企圖。
例如:
安全
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 數據成員的值是什麼,例如:
多線程
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寫在成員函數的最後:
優化
class A { … void function()const; //常成員函數, 它不改變對象的成員變量. 也不能調用類中任何非const成員函數。 }
對於const類對象/指針/引用,只能調用類的const成員函數。spa
1、通常狀況下,函數的返回值爲某個對象時,若是將其聲明爲const時,多用於操做符的重載。一般,不建議用const修飾函數的返回值類型爲某個對象或對某個對象引用的狀況。緣由以下:若是返回const對象,或返回const對象的引用,則返回值具備const屬性,返回實例只能訪問類A中的公有(保護)數據成員和const成員函數,而且不容許對其進行賦值操做,這在通常狀況下不多用到。
二、若是給採用「指針傳遞」方式的函數返回值加const修飾,那麼函數返回值(即指針所指的內容)不能被修改,該返回值只能被賦給加const 修飾的同類型指針:
const char * GetString(void);
以下語句將出現編譯錯誤:
char *str=GetString();
正確的用法是:
const char *str=GetString();
三、函數返回值採用「引用傳遞」的場合很少,這種方式通常只出如今類的賻值函數中,目的是爲了實現鏈式表達。如:
操作系統
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就不正確了。
線程
l 編譯器處理方式不一樣
define宏是在預處理階段展開。
const常量是編譯運行階段使用。
l 類型和安全檢查不一樣
define宏沒有類型,不作任何類型檢查,僅僅是展開。
const常量有具體的類型,在編譯階段會執行類型檢查。
l 存儲方式不一樣
define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配內存。
const常量會在內存中分配(能夠是堆中也能夠是棧中)。
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的值可能被意想不到地該變,所以a和b多是不一樣的。結果,這段代碼可能返不是你所指望的平方值!正確的代碼以下:
long square(volatile int *ptr) { int a; a = *ptr; return a * a; }
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也是用來輸出的,裏面調用了對象lx的Output輸出方法,爲了防止在函數中調用成員函數修改任何成員變量,因此參數也被const修飾。
假如如今,咱們要增添一個功能:計算每一個對象的輸出次數。假如用來計數的變量是普通的變量的話,那麼在const成員函數Output裏面是不能修改該變量的值的;而該變量跟對象的狀態無關,因此應該爲了修改該變量而去掉Output的const屬性。這個時候,就該咱們的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_iTimes被mutable修飾,那麼它就能夠突破const的限制,在被const修飾的函數裏面也能被修改。