1. const常量,如const int max = 100;
優(yōu)點:const常量有數(shù)據(jù)類型,而宏常量沒有數(shù)據(jù)類型。編譯器可以對前者進行類型安全檢查,而對后者只進行字符替換,沒有類型安全檢查,并且在字符替換時可能會產(chǎn)生意料不到的錯誤。
2. const 修飾類的數(shù)據(jù)成員。 const數(shù)據(jù)成員只在某個對象生存期內(nèi)是常量,而對于整個類而言卻是可變的。因為類可以創(chuàng)建多個對象,不同的對象其const數(shù)據(jù)成員的值可以不同。所以不能在類聲明中初始化const數(shù)據(jù)成員,因為類的對象未被創(chuàng)建時,編譯器不知道const 數(shù)據(jù)成員的值是什么。 const數(shù)據(jù)成員的初始化只能在類的構造函數(shù)的初始化表中進行。
3. const修飾指針的情況,見下式:
const int* a = & [1]
int const *a = & [2]
int* const a = & [3]
const int* const a = & [4]
可以參考《Effective c++》Item21上的做法,如果const位于星號的左側,則const就是用來修飾指針所指向的變量,即指針指向為常量;如果const位于星號的右側,const就是修飾指針本身,即指針本身是常量。因此,[1]和[2]的情況相同,都是指針所指向的內(nèi)容為常量,這種情況下不允許對內(nèi)容進行更改操作,如不能*a = 3 ;[3]為指針本身是常量,而指針所指向的內(nèi)容不是常量,這種情況下不能對指針本身進行更改操作,如a++是錯誤的;[4]為指針本身和指向的內(nèi)容均為常量。
4. const的初始化
先看一下const變量初始化的情況
1) 非指針const常量初始化的情況:A b;
const A a = b;
2) 指針const常量初始化的情況:
A* d = new A();
const A* c = d;
或者:const A* c = new A();
3)引用const常量初始化的情況:
A f;
const A& e = f; // 這樣作e只能訪問聲明為const的函數(shù),而不能訪問一般的成員函數(shù);
5. const在函數(shù)聲明中的應用 在函數(shù)聲明中,const 可以修飾函數(shù)的返回值,或某個參數(shù);對于成員函數(shù),還可以修飾是整個函數(shù)。1) 修飾參數(shù)的const,如 void fun0(const A* a ); void fun1(const A& a);
調(diào)用函數(shù)的時候,用相應的變量初始化const常量,則在函數(shù)體中,按照const所修飾的部分進行常量化,如形參為const A* a,則不能對傳遞進來的指針的內(nèi)容進行改變,保護了原指針所指向的內(nèi)容;如形參為const A& a,則不能對傳遞進來的引用對象進行改變,保護了原對象的屬性。
[注意]:參數(shù)const通常用于參數(shù)為指針或引用的情況,且只能修飾輸入?yún)?shù);若輸入?yún)?shù)采用“值傳遞”方式,由于函數(shù)將自動產(chǎn)生臨時變量用于復制該參數(shù),該參數(shù)本就不需要保護,所以不用const修飾。
[總結]對于非內(nèi)部數(shù)據(jù)類型的輸入?yún)?shù),應該將“值傳遞”的方式改為“const引用傳遞”,目的是為了提高效率。例如,將void Func(A a)改為void Func(const A &a)
對于內(nèi)部數(shù)據(jù)類型的輸入?yún)?shù),不要將“值傳遞”的方式改為“const引用傳遞”。否則既達不到提高效率的目的,又降低了函數(shù)的可理解性。例如void Func(int x)不應該改為void Func(const int &x)
2) 修飾返回值的const,如const A fun2( ); const A* fun3( );
這樣聲明了返回值后,const按照"修飾原則"進行修飾,起到相應的保護作用。 [總結] 1) 一般情況下,函數(shù)的返回值為某個對象時,如果將其聲明為const時,多用于操作符的重載。通常,不建議用const修飾函數(shù)的返回值類型為某個對象或?qū)δ硞€對象引用的情況。2)如果給采用“指針傳遞”方式的函數(shù)返回值加const修飾,那么函數(shù)返回值(即指針)的內(nèi)容不能被修改,該返回值只能被賦給加 const 修飾的同類型指針。
6. 類成員函數(shù)中const的使用
一般放在函數(shù)體后,形如:void fun() const;
任何不會修改數(shù)據(jù)成員的函數(shù)都因該聲明為const類型。如果在編寫const成員函數(shù)時,不慎修改了數(shù)據(jù)成員,或者調(diào)用了其他非const成員函數(shù),編譯器將報錯,這大大提高了程序的健壯性。
7. 使用const的一些建議
1 要大膽的使用const,這將給你帶來無盡的益處,但前提是你必須搞清楚原委;
2 要避免最一般的賦值操作錯誤,如將const變量賦值,具體可見思考題;
3 在參數(shù)中使用const應該使用引用或指針,而不是一般的對象實例,原因同上;
4 const在成員函數(shù)中的三種用法(參數(shù)、返回值、函數(shù))要很好的使用;
5 不要輕易的將函數(shù)的返回值類型定為const;
6除了重載操作符外一般不要將返回值類型定為對某個對象的const引用;
轉(zhuǎn)一篇有關const的文章
1、什么是const?
常類型是指使用類型修飾符const說明的類型,常類型的變量或?qū)ο蟮闹凳遣荒鼙桓碌?。(當然,我們可以偷梁換柱進行更新:)
2、為什么引入const?
const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優(yōu)點。
3、cons有什么主要的作用?
(1)可以定義const常量,具有不可變性。 例如:
const int Max=100; int Array[Max];
(2)便于進行類型檢查,使編譯器對處理內(nèi)容有更多了解,消除了一些隱患。例如: void f(const int i) { .........} 編譯器就會知道i是一個常量,不允許修改; (3)可以避免意義模糊的數(shù)字出現(xiàn),同樣可以很方便地進行參數(shù)的調(diào)整和修改。 同宏定義一樣,可以做到不變則已,一變都變!如(1)中,如果想修改Max的內(nèi)容,只需要:const int Max=you want;即可!
(4)可以保護被修飾的東西,防止意外的修改,增強程序的健壯性。 還是上面的例子,如果在函數(shù)體內(nèi)修改了i,編譯器就會報錯; 例如:
void f(const int i) { i=10;//error! }
(5) 為函數(shù)重載提供了一個參考。
class A { ......
void f(int i) {......} //一個函數(shù)
void f(int i) const {......} //上一個函數(shù)的重載 ......
};
(6) 可以節(jié)省空間,避免不必要的內(nèi)存分配。 例如:
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此時并未將Pi放入ROM中 ......
double i=Pi; //此時為Pi分配內(nèi)存,以后不再分配!
double I=PI; //編譯期間進行宏替換,分配內(nèi)存
double j=Pi; //沒有內(nèi)存分配
double J=PI; //再進行宏替換,又一次分配內(nèi)存!
const定義常量從匯編的角度來看,只是給出了對應的內(nèi)存地址,而不是象#define一樣給出的是立即數(shù),所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內(nèi)存中有若干個拷貝。
(7) 提高了效率。 編譯器通常不為普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內(nèi)存的操作,使得它的效率也很高。
4、如何使用const?
(1)修飾一般常量 一般常量是指簡單類型的常量。這種常量在定義時,修飾符const可以用在類型說明符前,也可以用在類型說明符后。 例如:
int const x=2; 或 const int x=2;
(2)修飾常數(shù)組 定義或說明一個常數(shù)組可采用如下格式:
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)修飾函數(shù)的常參數(shù) const修飾符也可以修飾函數(shù)的傳遞參數(shù),格式如下:
void Fun(const int Var); 告訴編譯器Var在函數(shù)體中的無法改變,從而防止了使用者的一些無意的或錯誤的修改。
(7)修飾函數(shù)的返回值: const修飾符也可以修飾函數(shù)的返回值,是返回值不可被改變,格式如下:
const int Fun1(); const MyClass Fun2();
(8)修飾類的成員函數(shù): const修飾符也可以修飾類的成員函數(shù),格式如下:
class ClassName {
public:
int Fun() const; .....
}; 這樣,在調(diào)用函數(shù)Fun時就不能修改類里面的數(shù)據(jù)
(9)在另一連接文件中引用const常量
extern const int i;//正確的引用
extern const int j=10;//錯誤!常量不可以被再次賦值 另外,還要注意,常量必須初始化! 例如: const int i=5;
5、幾點值得討論的地方:
(1)const究竟意味著什么?
說了這么多,你認為const意味著什么?一種修飾符?接口抽象?一種新類型? 也許都是,在Stroustup最初引入這個關鍵字時,只是為對象放入ROM做出了一種可能,對于const對象,C++既允許對其進行靜態(tài)初始化,也允許對他進行動態(tài)初始化。理想的const對象應該在其構造函數(shù)完成之前都是可寫的,在析夠函數(shù)執(zhí)行開始后也都是可寫的,換句話說,const對象具有從構造函數(shù)完成到析夠函數(shù)執(zhí)行之前的不變性,如果違反了這條規(guī)則,結果都是未定義的!雖然我們把const放入ROM中,但這并不能夠保證const的任何形式的墮落,我們后面會給出具體的辦法。無論const對象被放入ROM中,還是通過存儲保護機制加以保護,都只能保證,對于用戶而言這個對象沒有改變。換句話說,廢料收集器(我們以后會詳細討論,這就一筆帶過)或數(shù)據(jù)庫系統(tǒng)對一個const的修改怎沒有任何問題。
(2)位元const V.S. 抽象const?
對于關鍵字const的解釋有好幾種方式,最常見的就是位元const 和 抽象const。下面我們看一個例子: class A { public: ...... A f(const A& a); ...... }; 如果采用抽象const進行解釋,那就是f函數(shù)不會去改變所引用對象的抽象值,如果采用位元const進行解釋,那就成了f函數(shù)不會去改變所引用對象的任何位元。 我們可以看到位元解釋正是c++對const問題的定義,const成員函數(shù)不被允許修改它所在對象的任何一個數(shù)據(jù)成員。 為什么這樣呢?因為使用位元const有2個好處: 最大的好處是可以很容易地檢測到違反位元const規(guī)定的事件:編譯器只用去尋找有沒有對數(shù)據(jù)成員的賦值就可以了。另外,如果我們采用了位元const,那么,對于一些比較簡單的const對象,我們就可以把它安全的放入ROM中,對于一些程序而言,這無疑是一個很重要的優(yōu)化方式。(關于優(yōu)化處理,我們到時候?qū)iT進行討論) 當然,位元const也有缺點,要不然,抽象const也就沒有產(chǎn)生的必要了。 首先,位元const的抽象性比抽象const的級別更低!實際上,大家都知道,一個庫接口的抽象性級別越低,使用這個庫就越困難。 其次,使用位元const的庫接口會暴露庫的一些實現(xiàn)細節(jié),而這往往會帶來一些負面效應。所以,在庫接口和程序?qū)崿F(xiàn)細節(jié)上,我們都應該采用抽象const。 有時,我們可能希望對const做出一些其它的解釋,那么,就要注意了,目前,大多數(shù)對const的解釋都是類型不安全的,這里我們就不舉例子了,你可以自己考慮一下,總之,我們盡量避免對const的重新解釋。
(3)放在類內(nèi)部的常量有什么限制?
看看下面這個例子:
class A {
private:
const int c3 = 7; // ???
static int c4 = 7; // ???
static const float c5 = 7; // ??? ......
};
你認為上面的3句對嗎?呵呵,都不對!使用這種類內(nèi)部的初始化語法的時候,常量必須是被一個常量表達式初始化的整型或枚舉類型,而且必須是static和const形式。這顯然是一個很嚴重的限制! 那么,我們的標準委員會為什么做這樣的規(guī)定呢?一般來說,類在一個頭文件中被聲明,而頭文件被包含到許多互相調(diào)用的單元去。但是,為了避免復雜的編譯器規(guī)則,C++要求每一個對象只有一個單獨的定義。如果C++允許在類內(nèi)部定義一個和對象一樣占據(jù)內(nèi)存的實體的話,這種規(guī)則就被破壞了。
(4)如何初始化類內(nèi)部的常量?
一種方法就是static 和 const 并用,在內(nèi)部初始化,如上面的例子; 另一個很常見的方法就是初始化列表:
class A {
public:
A(int i=0):test(i) {}
private:
const int i;
}; 還有一種方式就是在外部初始化,例如:
class A {
public:
A() {}
private:
static const int i;//注意必須是靜態(tài)的!
};
const int A::i=3;
(5)常量與數(shù)組的組合有什么特殊嗎? 我們給出下面的代碼:
const int size[3]={10,20,50};
int array[size[2]];
有什么問題嗎?對了,編譯通不過!為什么呢?
Const可以用于集合,但編譯器不能把一個集合存放在它的符號表里,所以必須分配內(nèi)存。在這種情況下,const意味著“不能改變的一塊存儲”。然而,其值在編譯時不能被使用,因為編譯器在編譯時不需要知道存儲的內(nèi)容。自然,作為數(shù)組的大小就不行了:) 你再看看下面的例子:
class A {
public:
A(int i=0):test[2]({1,2}) {}//你認為行嗎?
private:
const int test[2];
};
vc6下編譯通不過,為什么呢? 關于這個問題,前些時間,njboy問我是怎么回事?我反問他:“你認為呢?”他想了想,給出了一下解釋,大家可以看看:我們知道編譯器堆初始化列表的操作是在構造函數(shù)之內(nèi),顯式調(diào)用可用代碼之前,初始化的次序依據(jù)數(shù)據(jù)聲明的次序。初始化時機應該沒有什么問題,那么就只有是編譯器對數(shù)組做了什么手腳!其實做什么手腳,我也不知道,我只好對他進行猜測:編譯器搜索到test發(fā)現(xiàn)是一個非靜態(tài)的數(shù)組,于是,為他分配內(nèi)存空間,這里需要注意了,它應該是一下分配完,并非先分配test[0],然后利用初始化列表初始化,再分配test[1],這就導致數(shù)組的初始化實際上是賦值!然而,常量不允許賦值,所以無法通過。 呵呵,看了這一段冠冕堂皇的話,真讓我笑死了!njboy別怪我揭你短呀:)我對此的解釋是這樣的:C++標準有一個規(guī)定,不允許無序?qū)ο笤陬悆?nèi)部初始化,數(shù)組顯然是一個無序的,所以這樣的初始化是錯誤的!對于他,只能在類的外部進行初始化,如果想讓它通過,只需要聲明為靜態(tài)的,然后初始化。 這里我們看到,常量與數(shù)組的組合沒有什么特殊!一切都是數(shù)組惹的禍!
(6)this指針是不是const類型的?
this指針是一個很重要的概念,那該如何理解她呢?也許這個話題太大了,那我們縮小一些:this指針是個什么類型的?這要看具體情況:如果在非const成員函數(shù)中,this指針只是一個類類型的;如果在const成員函數(shù)中,this指針是一個const類類型的;如果在volatile成員函數(shù)中,this指針就是一個volatile類類型的。
(7)const到底是不是一個重載的參考對象?
先看一下下面的例子:
class A {
......
void f(int i) {......}//一個函數(shù)
void f(int i) const {......}//上一個函數(shù)的重載
......
}; 上面是重載是沒有問題的了,那么下面的呢?
class A {
......
void f(int i) {......}//一個函數(shù)
void f(const int i) {......}//?????
......
}; 這個是錯誤的,編譯通不過。那么是不是說明內(nèi)部參數(shù)的const不予重載呢?再看下面的例子:
class A {
......
void f(int& ) {......}//一個函數(shù)
void f(const int& ) {......}//?????
......
}; 這個程序是正確的,看來上面的結論是錯誤的。為什么會這樣呢?這要涉及到接口的透明度問題。按值傳遞時,對用戶而言,這是透明的,用戶不知道函數(shù)對形參做了什么手腳,在這種情況下進行重載是沒有意義的,所以規(guī)定不能重載!當指針或引用被引入時,用戶就會對函數(shù)的操作有了一定的了解,不再是透明的了,這時重載是有意義的,所以規(guī)定可以重載。
(8)什么情況下為const分配內(nèi)存?
以下是我想到的可能情況,當然,有的編譯器進行了優(yōu)化,可能不分配內(nèi)存。
A、作為非靜態(tài)的類成員時;
B、用于集合時;
C、被取地址時;
D、在main函數(shù)體內(nèi)部通過函數(shù)來獲得值時;
E、const的 class或struct有用戶定義的構造函數(shù)、析構函數(shù)或基類時;。
F、當const的長度比計算機字長還長時;
G、參數(shù)中的const;
H、使用了extern時。 不知道還有沒有其他情況,歡迎高手指點:)
(9)臨時變量到底是不是常量?
很多情況下,編譯器必須建立臨時對象。像其他任何對象一樣,它們需要存儲空間而且必須被構造和刪除。區(qū)別是我們從來看不到編譯器負責決定它們的去留以及它們存在的細節(jié)。對于C++標準草案而言:臨時對象自動地成為常量。因為我們通常接觸不到臨時對象,不能使用與之相關的信息,所以告訴臨時對象做一些改變有可能會出錯。當然,這與編譯器有關,例如:vc6、vc7都對此作了擴展,所以,用臨時對象做左值,編譯器并沒有報錯。
(10)與static搭配會不會有問題? 假設有一個類:
class A {
public:
......
static void f() const { ......}
......
}; 我們發(fā)現(xiàn)編譯器會報錯,因為在這種情況下static不能夠與const共存! 為什么呢?因為static沒有this指針,但是const修飾this指針,所以...
(11)如何修改常量?
有時候我們卻不得不對類內(nèi)的數(shù)據(jù)進行修改,但是我們的接口卻被聲明了const,那該怎么處理呢?我對這個問題的看法如下:
1)標準用法:
mutable class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const { test=i; }
private: mutable int test;//這里處理!
};
2)強制轉(zhuǎn)換:
const_cast class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
const_cast (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)內(nèi)部處理: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);//利用邊緣調(diào)整
*pi=5;//此處改變了test的值!
return 0;
}
雖然我給出了6中方法,但是我只是想說明如何更改,但出了第一種用法之外,另外5種用法,我們并不提倡,不要因為我這么寫了,你就這么用,否則,我真是要誤人子弟了:)
(12)最后我們來討論一下常量對象的動態(tài)創(chuàng)建。 既然編譯器可以動態(tài)初始化常量,就自然可以動態(tài)創(chuàng)建,例如:
const int* pi=new const int(10); 這里要注意2點:
1)const對象必須被初始化!所以(10)是不能夠少的。
2)new返回的指針必須是const類型的。 那么我們可不可以動態(tài)創(chuàng)建一個數(shù)組呢? 答案是否定的,因為new內(nèi)置類型的數(shù)組,不能被初始化。 這里我們忽視了數(shù)組是類類型的,同樣對于類內(nèi)部數(shù)組初始化我們也做出了這樣的忽視,因為這涉及到數(shù)組的問題,我們以后再討論。