關於const的使用方法【轉載】

來源:互聯網
上載者:User

 1、什麼是const?

常類型是指使用類型修飾符const說明的類型,常類型的變數或對象的值是不能被更新

的。(當然,我們可以偷梁換柱進行更新:)

2、為什麼引入const?

const 推出的初始目的,正是為了取代先行編譯指令,消除它的缺點,同時繼承它的優點

3、cons有什麼主要的作用?

(1)可以定義const常量,具有不可變性。

例如:

const int Max=100;

int Array[Max];

(2)便於進行類型檢查,使編譯器對處理內容有更多瞭解,消除了一些隱患。

例如:

void f(const int i) { .........}

編譯器就會知道i是一個常量,不允許修改;

(3)可以避免意義模糊的數字出現,同樣可以很方便地進行參數的調整和修改。

同宏定義一樣,可以做到不變則已,一變都變!如(1)中,如果想修改Max的內

容,只需要:const int Max=you want;即可!

(4)可以保護被修飾的東西,防止意外的修改,增強程式的健壯性。

還是上面的例子,如果在函數體內修改了i,編譯器就會報錯;

例如:

void f(const int i) { i=10;//error! }

(5) 為函數重載提供了一個參考。

class A

{

......

void f(int i)       {......} //一個函數

void f(int i) const {......} //上一個函數的重載

......

};

(6) 可以節省空間的,避免不必要的記憶體配置。

例如:

#define PI 3.14159         //常量宏

const doulbe  Pi=3.14159;  //此時並未將Pi放入ROM中

......

double i=Pi;               //此時為Pi分配記憶體,以後不再分配!

double I=PI;               //編譯期間進行宏替換,分配記憶體

double j=Pi;               //沒有記憶體配置

double J=PI;               //再進行宏替換,又一次分配記憶體!

const定義常量從彙編的角度來看,只是給出了對應的記憶體位址,而不是象#def

ine一樣給出的是立即數,所以,const定義的常量在程式運行過程中只有一份拷貝,而#d

efine定義的常量在記憶體中有若干個拷貝。

(7) 提高了效率。

編譯器通常不為普通const常量分配儲存空間,而是將它們儲存在符號表中,

這使得它成為一個編譯期間的常量,沒有了儲存與讀記憶體的操作,使得它的效率也很高。

 

3、如何使用const?

(1)修飾一般常量

一般常量是指簡單類型的常量。這種常量在定義時,修飾符const可以用在類型說

明符前,也可以用在類型說明符後。

例如:

int const x=2;  或  const int x=2;

(2)修飾常數組

定義或說明一個常數組可採用如下格式:

int const a[5]={1, 2, 3, 4, 5}; 

const int a[5]={1, 2, 3, 4, 5};

(3)修飾常對象

常對象是指對象常量,定義格式如下:

class A;

const A a;

A const a;

定義常對象時,同樣要進行初始化,並且該對象不能再被更新,修飾符const可以

放在類名後面,也可以放在類名前面。 

(4)修飾常指標

const int *A;        //const修飾指向的對象,A可變,A指向的對象不可變

int const *A;       //const修飾指向的對象,A可變,A指向的對象不可變

int *const A;       //const修飾指標A,     A不可變,A指向的對象可變

const int *const A;  //指標A和A指向的對象都不可變

(5)修飾常引用

使用const修飾符也可以說明引用,被說明的引用為常引用,該引用所引用的對

象不能被更新。其定義格式如下:

const double & v;

(6)修飾函數的常參數

const修飾符也可以修飾函數的傳遞參數,格式如下:

void Fun(const int Var);

告訴編譯器Var在函數體中的無法改變,從而防止了使用者的一些無意的或錯誤的

修改。

(7)修飾函數的傳回值:

const修飾符也可以修飾函數的傳回值,是傳回值不可被改變,格式如下:

const int Fun1();

const MyClass Fun2();

(8)修飾類的成員函數:

const修飾符也可以修飾類的成員函數,格式如下:

class ClassName

{

public:

int Fun() const;

.....

};

這樣,在調用函數Fun時就不能修改類裡面的資料

(9)在另一串連檔案中引用const常量

extern const int i;     //正確的引用

extern const int j=10;  //錯誤!常量不可以被再次賦值

另外,還要注意,常量必須初始化!

例如:

const int i=5;

4、幾點值得討論的地方:

(1)const究竟意味著什嗎?

說了這麼多,你認為const意味著什嗎?一種修飾符?介面抽象?一種新類型?

也許都是,在Stroustup最初引入這個關鍵字時,只是為對象放入ROM做出了一種

可能,對於const對象,C++既允許對其進行靜態初始化,也允許對他進行動態初始化。理

想的const對象應該在其建構函式完成之前都是可寫的,在析夠函數執行開始後也都是可寫

的,換句話說,const對象具有從建構函式完成到析夠函數執行之前的不變性,如果違反了

這條規則,結果都是未定義的!雖然我們把const放入ROM中,但這並不能夠保證const的任

何形式的墮落,我們後面會給出具體的辦法。無論const對象被放入ROM中,還是通過儲存

保護機制加以保護,都只能保證,對於使用者而言這個對象沒有改變。換句話說,廢料收集

器(我們以後會詳細討論,這就一筆帶過)或資料庫系統對一個const的修改怎沒有任何問

題。

(2)位元const V.S. 抽象const?

對於關鍵字const的解釋有好幾種方式,最常見的就是位元const 和 抽象const。

下面我們看一個例子:

class A

{

public:

......

A f(const A& a);

......

};

如果採用抽象const進行解釋,那就是f函數不會去改變所引用對象的抽象值,如

果採用位元const進行解釋,那就成了f函數不會去改變所引用對象的任何位元。

我們可以看到位元解釋正是c++對const問題的定義,const成員函數不被允許修

改它所在對象的任何一個資料成員。

為什麼這樣呢?因為使用位元const有2個好處:

最大的好處是可以很容易地檢測到違反位元const規定的事件:編譯器只用去尋

找有沒有對資料成員的賦值就可以了。另外,如果我們採用了位元const,那麼,對於一些

比較簡單的const對象,我們就可以把它安全的放入ROM中,對於一些程式而言,這無疑是

一個很重要的最佳化方式。(關於最佳化處理,我們到時候專門進行討論)

當然,位元const也有缺點,要不然,抽象const也就沒有產生的必要了。

首先,位元const的抽象性比抽象const的層級更低!實際上,大家都知道,一個

庫介面的抽象性層級越低,使用這個庫就越困難。

其次,使用位元const的庫介面會暴露庫的一些實現細節,而這往往會帶來一些

負面效應。所以,在庫介面和程式實現細節上,我們都應該採用抽象const。

有時,我們可能希望對const做出一些其它的解釋,那麼,就要注意了,目前,

大多數對const的解釋都是類型不安全的,這裡我們就不舉例子了,你可以自己考慮一下,

總之,我們盡量避免對const的重新解釋。

(3)放在類內部的常量有什麼限制?

看看下面這個例子:

class A

{

private:

const int c3 = 7;           // ???

static int c4 = 7;          // ???

static const float c5 = 7;  // ???

......

};

你認為上面的3句對嗎?呵呵,都不對!使用這種類內部的初始化文法的時候,

常量必須是被一個常量運算式初始化的整型或枚舉類型,而且必須是static和const形式。

這顯然是一個很嚴重的限制!

那麼,我們的標準委員會為什麼做這樣的規定呢?一般來說,類在一個標頭檔中

被聲明,而標頭檔被包含到許多互相調用的單元去。但是,為了避免複雜的編譯器規則,

C++要求每一個對象只有一個單獨的定義。如果C++允許在類內部定義一個和對象一樣佔據

記憶體的實體的話,這種規則就被破壞了。

(4)如何初始化類內部的常量?

一種方法就是static 和 const 並用,在內部初始化,如上面的例子;

另一個很常見的方法就是初始化列表:

class A

{

public:

A(int i=0):test(i) {}

private:

const int i;

};

還有一種方式就是在外部初始化,例如:

class A

{

public:

A() {}

private:

static const int i;  //注意必須是靜態!

};

const int A::i=3;

(5)常量與數組的組合有什麼特殊嗎?

我們給出下面的代碼:

const int size[3]={10,20,50};

int array[size[2]];

有什麼問題嗎?對了,編譯通不過!為什麼呢?

const可以用於集合,但編譯器不能把一個集合存放在它的符號表裡,所以必

須分配記憶體。在這種情況下,const意味著“不能改變的一Block Storage”。然而,其值在編譯時間

不能被使用,因為編譯器在編譯時間不需要知道儲存的內容。自然,作為數組的大小就不行

了:)

你再看看下面的例子:

class A

{

public:

A(int i=0):test[2]({1,2}) {} //你認為行嗎?

private:

const int test[2];

};

vc6下編譯通不過,為什麼呢?

關於這個問題,前兩天,njboy問我是怎麼回事?我反問他:“你認為呢?”他

想了想,給出了一下解釋,大家可以看看:我們知道編譯器堆初始化列表的操作是在構造

函數之內,顯式調用可用代碼之前,初始化的次序依據資料聲明的次序。初始化時機應該

沒有什麼問題,那麼就只有是編譯器對數組做了什麼手腳!其實做什麼手腳,我也不知道

,我只好對他進行猜測:編譯器搜尋到test發現是一個非靜態數組,於是,為他分配內

存空間,這裡需要注意了,它應該是一下分配完,並非先分配test[0],然後利用初始化列

表初始化,再分配test[1],這就導致數組的初始化實際上是賦值!然而,常量不允許賦值

,所以無法通過。

呵呵,看了這一段冠冕堂皇的話,真讓我笑死了!njboy別怪我揭你短呀:)我對

此的解釋是這樣的:C++標準有一個規定,不允許無序對象在類內部初始化,數組顯然是一

個無序的,所以這樣的初始化是錯誤的!對於他,只能在類的外部進行初始化,如果想讓

它通過,只需要聲明為靜態,然後初始化。

這裡我們看到,常量與數組的組合沒有什麼特殊!一切都是數組惹的禍!

(6)this指標是不是const類型的?

this指標是一個很重要的概念,那該如何理解她呢?也許這個話題太大了,那我

們縮小一些:this指標是個什麼類型的?這要看具體情況:如果在非const成員函數中,t

his指標只是一個類類型的;如果在const成員函數中,this指標是一個const類類型的;如

果在volatile成員函數中,this指標就是一個volatile類類型的。

(7)const到底是不是一個重載的參考對象?

先看一下下面的例子:

class A

{

......

void f(int i)       {......} //一個函數

void f(int i) const {......} //上一個函數的重載

......

};

上面是重載是沒有問題的了,那麼下面的呢?

class A

{

......

void f(int i)       {......} //一個函數

void f(const int i) {......} //?????

......

};

這個是錯誤的,編譯通不過。那麼是不是說明內部參數的const不予重載呢?再

看下面的例子:

class A

{

......

void f(int& )       {......} //一個函數

void f(const int& ) {......} //?????

......

};

這個程式是正確的,看來上面的結論是錯誤的。為什麼會這樣呢?這要涉及到接

口的透明度問題。按值傳遞時,對使用者而言,這是透明的,使用者不知道函數對形參做了什

麼手腳,在這種情況下進行重載是沒有意義的,所以規定不能重載!當指標或引用被引入

時,使用者就會對函數的操作有了一定的瞭解,不再是透明的了,這時重載是有意義的,所

以規定可以重載。

(8)什麼情況下為const分配記憶體?

以下是我想到的可能情況,當然,有的編譯器進行了最佳化,可能不分配記憶體。

A、作為非靜態類成員時;

B、用於集合時;

C、被取地址時;

D、在main函數體內部通過函數來獲得值時;

E、const的 class或struct有使用者定義的建構函式、解構函式或基類時;。

F、當const的長度比電腦字長還長時;

G、參數中的const;

H、使用了extern時。

不知道還有沒有其他情況,歡迎高手指點:)

(9)臨時變數到底是不是常量?

很多情況下,編譯器必須建立臨時對象。像其他任何對象一樣,它們需要儲存空

間而且必須被構造和刪除。區別是我們從來看不到編譯器負責決定它們的去留以及它們存

在的細節。對於C++標準草案而言:臨時對象自動地成為常量。因為我們通常接觸不到臨時

對象,不能使用與之相關的資訊,所以告訴臨時對象做一些改變有可能會出錯。當然,這

與編譯器有關,例如:vc6、vc7都對此作了擴充,所以,用臨時對象做左值,編譯器並沒

有報錯。當他們遇到流庫時,也會引發一系列的問題,限於篇幅所限,我們在討論臨時對

象時詳細討論。

(10)與static搭配會不會有問題?

假設有一個類:

class A

{

public:

......

static void f() const { ......}

......

};

我們發現編譯器會報錯,因為在這種情況下static不能夠與const共存!

為什麼會出現這種問題呢?我們知道static修飾的函數使其被各個對象以及派生

類的對象共用,然而const卻使得所有引用它的對象內部的資料不能被修改,換句話說,c

onst的意義在基類不知道的情況下被擴充了,為了防止這類事情的發生,乾脆就定義stat

ic不能夠與const共存(只是上面的情況下)!

我們再進一步想一下如果不這樣定義會不會出現什麼情況呢?(換句話說,有什

麼情況使得這種擴充產生異常呢?)

由於static函數只能調用靜態對象與函數,並且不能夠與virtual共存(virtua

l為動態,而static為靜態,兩者內部機制衝突),所以不可能由於虛擬機器制產生問題。

下面我們再看一種情況:

class A

{

public:

static void f() { cout<<"A"<<endl; }

static void g() { f(); }

};

class B:public A

{

public:

static void f() { cout<<"B"<<endl; }

};

由於靜態函數獨立於類體外所以B也可以調用g(),但是也由於靜態函數依次初

始化,所以就註定了g()所調用的f()只能為基類A的,永遠都不會是B的!(這裡,有兩個f

()並不會發生衝突,因為編譯器利用了name mangling機制),所以這種情況下也不會發生

意外!

那麼究竟在那種情況下使得static不能夠與const共存呢?或者說根本就沒有

,而是標準委員會為了將來擴充避免發生意外而作的這項規定?希望高手指點一下:)

(11)如何修改常量?

有時候我們卻不得不對類內的資料進行修改,但是我們的介面卻被聲明了cons

t,那該怎麼處理呢?我對這個問題的看法如下:

1)標準用法:mutable

class A

{

public:

A(int i=0):test(i)        { }

void SetValue(int i)const { test=i; }

private:

mutable int test;   //這裡處理!

};

2)強制轉換:static_cast

class A

{

public:

A(int i=0):test(i)        { }

void SetValue(int i)const

{ static_cast <int>(test)=i; }//這裡處理!

private:

int test;

};

3)靈活的指標:int*

class A

{

public:

A(int i=0):test(i)        { }

void SetValue(int i)const

{ *test=i; }

private:

int* test;   //這裡處理!

};

4)未定義的處理

class A

{

public:

A(int i=0):test(i)        { }

void SetValue(int i)const

{ int *p=(int*)&test; *p=i; }//這裡處理!

private:

int test;

};

注意,這裡雖然說可以這樣修改,但結果是未定義的,避免使用!

5)內部處理:this指標

class A

{

public:

A(int i=0):test(i)        { }

void SetValue(int i)const

{ ((A*)this)->test=i; }//這裡處理!

private:

int test;

};

6)最另類的處理:空間布局

class A

{

public:

A(int i=0):test(i),c('a') {  }

private:

char c;

const int test;

};

int main()

{

A a(3);

A* pa=&a;

char* p=(char*)pa;

int*  pi=(int*)(p+4);//利用邊緣調整

*pi=5;                 //此處改變了test的值!

return 0;

}

雖然我給出了6中方法,但是我只是想說明如何更改,但出了第一種用法之外,另

外5種用法,我們並不提倡,不要因為我這麼寫了,你就這麼用,否則,我真是要誤人子弟

了:)

(12)最後我們來討論一下常量對象的動態建立。

既然編譯器可以動態初始化常量,就自然可以動態建立,例如:

const int* pi=new const int(10);

這裡要注意2點:

1)const對象必須被初始化!所以(10)是不能夠少的。

2)new返回的指標必須是const類型的。

那麼我們可不可以動態建立一個數組呢?

答案是否定的,因為new內建類型的數組,不能被初始化。

這裡我們忽視了數組是類類型的,同樣對於類內部數組初始化我們也做出了這

樣的忽視,因為這涉及到數組的問題,我們以後再討論。

好了,就寫到這吧,太累了,休息一下:)

五 使用const 的一些建議
1 要大膽的使用const,這將給你帶來無盡的益處,但前提是你必須搞清楚原委;
2 要避免最一般的賦值操作錯誤,如將const 變數賦值,具體可見思考題;
3 在參數中使用const 應該使用引用或指標,而不是一般的對象執行個體,原因同上;
4 const 在成員函數中的三種用法(參數、傳回值、函數)要很好的使用;
5 不要輕易的將函數的傳回值類型定為const;
6 除了重載操作符外一般不要將傳回值類型定為對某個對象的const 引用;

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.