c++中const的完全解析
http://blog.csdn.net/jsjwql/archive/2007/09/10/1779516.aspx
1.?? const
類型定義:指明變量或對象的值是不能被更新
,
引入目的是為了取代預編譯指令
2.??
可以保護被修飾的東西,防止意外的修改,增強程序的健壯性。
3.??
編譯器通常不為普通
const
常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率也很高。
4.???
可以節(jié)省空間,避免不必要的內存分配。
?
例如:
???? #define PI 3.14159???????? file://
常量宏
???? const doulbe? Pi=3.14159;? file://
此時并未將
Pi
放入
ROM
中
??????? ......
???? double i=Pi;???????????? file://
此時為
Pi
分配內存,以后不再分配!
???? double I=PI;?????????????? file://
編譯期間進行宏替換,分配內存
???? double j=Pi;?????????????? file://
沒有內存分配
???? double J=PI;?????????????? file://
再進行宏替換,又一次分配內存!
?const
定義常量從匯編的角度來看,只是給出了對應的內存地址,而不是象
#define
一樣給出的是立即數(shù),所以,
const
定義的常量在程序運行過程中只有一份拷貝,而
#define
定義的常量在內存中有若干個拷貝。
對于基本聲明
1.? const int r=100; //
標準
const
變量聲明加初始化,因為默認內部連接所以必須被初始化,其作用域為此文件,編譯器經過類型檢查后直接用
100
在編譯時替換
2.? extend const int r=100; //
將
const
改為外部連接,作用于擴大至全局,編譯時會分配內存,并且可以不進行初始化,僅僅作為聲明,編譯器認為在程序其他地方進行了定義
但是如果外部想鏈接
r
,不能這樣用
extern const int r=10;? //
錯誤!常量不可以被再次賦值
3. const int r[ ]={1,2,3,4};
struct S {int a,b;};
const S s[ ]={(1,2),(3.4)}; //
以上兩種都是常量集合,編譯器會為其分配內存,所以不能在編譯期間使用其中的值,例如:
int temp[r[2]];
這樣的編譯器會報告不能找到常量表達式
?
但是
?const int Max=100;
?int Array[Max];?
正確。
?還有
?
?
定義數(shù)組必須用常量,可以用
const
或者
#define
定義。
Static
雖然是編譯時確定,也不能用來聲明數(shù)組。
對于指針和引用
1.?? const int *r=&x; //
聲明
r
為一個指向常量的
x
的指針,
r
指向的對象不能被修改,但他可以指向任何地址的常量
pointer const
可以指定普通變量
,
用改指針不能修改它指向的對象,并不表示指向的對象是
const
不能被改變,例如:
int i = 10;
const int * p =? &i;
*p = 11; //wrong
?i = 11 ; //correct
自己的一個經驗:一個具體的概念可以用范型的概念來賦值,但是一個范型的概念不能用具體的概念來賦值。
我們可以把
const
指針當成普通指針的父類,因為普通指針改寫了
const
屬性,而具有比
const
指針更多的功能。
這樣的話只有父類指針可以指向子類,而子類指針不能指向父類。
2.?? int const *r=&x; //
與用法
1
完全等價,沒有任何區(qū)別
3.?? int * const r=&x; //
聲明
r
為一個常量指針,他指向
x
,
r
這個指針的指向不能被修改,但他指向的地址的內容可以修改
4.? const int * const r=&x; //
綜合
1
、
3
用法,
r
是一個指向常量的常量型指針
5.?? const double & v;?????
該引用所引用的對象不能被更新
?
引用必須定義是初始話,而且初始化后這個引用不能指向其他的對象。但是這里加的
const
聲明不是這個意思,它是指不能改變
v
引用對象本身,也就是只能調用該對象里面的
const
成員函數(shù)。
對于類型檢查
可以把一個非
const
對象賦給一個指向
const
的指針,因為有時候我們不想從這個指針來修改其對象的值;但是不可以把一個
const
對象賦值給一個非
const
指針,因為這樣可能會通過這個指針改變指向對象的值,但也存在使這種操作通過的合法化寫法,使用類型強制轉換可以通過指針改變
const
對象:
const int r=100;
int * ptr = const_cast<int*>(&r);? //C++
標準,
C
語言使用:
int * ptr =(int*)&r;
對于字符數(shù)組
如
char * name = “china”;
這樣的語句,在編譯時是能夠通過的,但是
”china”
是常量字符數(shù)組,任何想修改他的操作也能通過編譯但會引起運行時錯誤,如果我們想修改字符數(shù)組的話就要使用
char name[ ] = “china”;
這種形式。
對于函數(shù)
1. void Fuction1 ( const int r ); //
此處為參數(shù)傳遞
const
值,意義是變量初值不能被函數(shù)改變
2. const int Fuction1 (int); //
此處返回
const
值,意思指返回的原函數(shù)里的變量的初值不能被修改,但是函數(shù)按值返回的這個變量被制成副本,能不能被修改就沒有了意義,它可以被賦給任何的
const
或非
const
類型變量,完全不需要加上這個
const
關鍵字。但這只對于內部類型而言(因為內部類型返回的肯定是一個值,而不會返回一個變量,不會作為左值使用),對于用戶自定義類型,返回值是常量是非常重要的,見下面條款
3
。
3.? Class CX; //
內部有構造函數(shù),聲明如
CX(int r =0)
CX? Fuction1 () { return CX(); }
const CX Fuction2 () { return CX(); }
如有上面的自定義類
CX
,和函數(shù)
Fuction1()
和
Fuction2(),
我們進行如下操作時:
Fuction1() = CX(1); //
沒有問題,可以作為左值調用
Fuction2() = CX(1); //
編譯錯誤,
const
返回值禁止作為左值調用。因為左值把返回值作為變量會修改其返回值,
const
聲明禁止這種修改。
4. ?
函數(shù)中指針的
const
傳遞和返回:
int F1 (const char * pstr); //
作為傳遞的時候使用
const
修飾可以保證不會通過這個指針來修改傳遞參數(shù)的初值,這里在函數(shù)內部任何修改
*pstr
的企圖都會引起編譯錯誤。
const char * F2 (); //
意義是函數(shù)返回的指針指向的對象是一個
const
對象,它必須賦給一個同樣是指向
const
對象的指針。
const char * const F3(); //
比上面多了一個
const
,這個
const
的意義只是在他被用作左值時有效,它表明了這個指針除了指向
const
對象外,它本身也不能被修改,所以就不能當作左值來處理。
5.??
函數(shù)中引用的
const
傳遞:
void F1 ( const X& px); //
這樣的一個
const
引用傳遞和最普通的函數(shù)按值傳遞的效果是一模一樣的,他禁止對引用的對象的一切修改,唯一不同的是按值傳遞會先建立一個類對象的副本,然后傳遞過去,而它直接傳遞地址,所以這種傳遞比按值傳遞更有效。
**
另外只有引用的
const
傳遞可以傳遞一個臨時對象,因為臨時對象都是
const
屬性,且是不可見的,他短時間存在一個局部域中,所以不能使用指針,只有引用的
const
傳遞能夠捕捉到這個家伙。
6.?
有一點可以注意一下
??? const
為函數(shù)重載提供了一個參考。
???????? class A
???????? {
?????????? ......
?????????? void f(int i)?????? {......} file://
一個函數(shù)
?????????? void f(int i) const {......} file://
上一個函數(shù)的重載
??????????? ......
????????? };
???????
關于函數(shù)
overloading
,
不能根據(jù)返回值類型來確定
??
?????double max( int a, int b);
??????? int??????? max( int a, int b);
???????
也不能根據(jù)參數(shù)的默認值來判斷
??????? int max( int a, int b);
??????? int max( int a, int b, int c=12);
???????
一句話不能讓編譯器有多個選擇就
ok
了
對于類
1. ?
首先,對于
const
的成員變量,只能在構造函數(shù)里使用初始化成員列表來初始化,試圖在構造函數(shù)體內進行初始化
const
成員變量會引起編譯錯誤。初始化成員列表形如:
X:: X ( int ir ): r(ir) {} //
假設
r
是類
X
的
const
成員變量
2.? const
成員函數(shù)。提到這個概念首先要談到
const
對象,正象內置類型能夠定義
const
對象一樣(
const int r=10;
),用戶自定義類型也可以定義
const
對象
(const X px(10);)
,編譯器要保證這個對象在其生命周期內不能夠被改變。如果你定義了這樣的一個
const
對象,那么對于這個對象的一切非
const
成員函數(shù)的調用,編譯器為了保證對象的
const
特性,都會禁止并在編譯期間報錯。所以如果你想讓你的成員函數(shù)能夠在
const
對象上進行操作的話,就要把這個函數(shù)聲明為
const
成員函數(shù)。假如
f( )
是類中的成員函數(shù)的話,它的聲明形如:
int f( ) const; //const
放在函數(shù)的最后,編譯器會對這個函數(shù)進行檢查,在這個函數(shù)中的任何試圖改變成員變量和調用非
const
成員函數(shù)的操作都被視為非法
**
類的構造和析構函數(shù)都不能是
const
函數(shù)。
3.?
建立了一個
const
成員函數(shù),但仍然想用這個函數(shù)改變對象內部的數(shù)據(jù)。這樣的一個要求也會經常遇到,尤其是在一個苛刻的面試考官那里。首先我們要弄清楚考官的要求,因為有兩種方法可以實現(xiàn),如果這位考官要求不改變原來類的任何東西,只讓你從當前這個
const
成員函數(shù)入手,那么你只有使用前面提到的類型強制轉換方法。實例如下:
//
假如有一個叫做
X
的類,它有一個
int
成員變量
r
,我們需要通過一個
const
成員函數(shù)
f( )
來對這個
r
進行
++r
操作,代碼如下
void X::f( ) const
{? (const_cast<X*>(this)) -> ++r;? } //
通過
this
指針進行類型強制轉換實現(xiàn)
另外一種方法就是使用關鍵字:
mutable
。如果你的成員變量在定義時是這個樣子的:
mutable int r ;
那么它就告訴編譯器這個成員變量可以通過
const
成員函數(shù)改變。編譯器就不會再理會對他的檢查了。
關于
const
一些問題
?[
思考
1]
:
以下的這種賦值方法正確嗎?
?const A_class* c=new A_class();
?A_class* e = c;
這種方法不正確,因為聲明指針的目的是為了對其指向的內容進行改變,而聲明的指針
e
指向的是一個常量,所以不正確;
[
思考
2]
:
以下的這種賦值方法正確嗎?
?A_class* const c = new A_class();
?A_class* b = c;
這種方法正確,因為聲明指針所指向的內容可變;
[
思考
3]
:
這樣定義賦值操作符重載函數(shù)可以嗎?
const A_class& operator=(const A_class& a);
不正確;在
const A_class::operator=(const A_class& a)
中,參數(shù)列表中的
const
的用法正確,而當這樣連續(xù)賦值的時侯,問題就出現(xiàn)了:
A_class a,b,c:(a=b)=c;
因為
a.operator=(b)
的返回值是對
a
的
const
引用,不能再將
c
賦值給
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 的修改怎沒有任何問題。
??????? class A
??????? {
???????? public:
?????????????? ......
?????????????? A f(const A& a);
?????????????? ......
???????? };
? 最大的好處是可以很容易地檢測到違反位元 const 規(guī)定的事件:編譯器只用去尋找有沒有對數(shù)據(jù)成員的賦值就可以了。另外,如果我們采用了位元 const ,那么,對于一些比較簡單的 const 對象,我們就可以把它安全的放入 ROM 中,對于一些程序而言,這無疑是一個很重要的優(yōu)化方式。(關于優(yōu)化處理,我們到時候專門進行討論)
? 看看下面這個例子:
????class A
????{
??????private:
?????????? const int c3 = 7;?????????? // ???
?? ??? ???static int c4 = 7;????????? // ???
?? ??? ?????static const float c5 = 7;? // ???
????????? ......
? ???};
? 那么,我們的標準委員會為什么做這樣的規(guī)定呢?一般來說,類在一個頭文件中被聲明,而頭文件被包含到許多互相調用的單元去。但是,為了避免復雜的編譯器規(guī)則, C++ 要求每一個對象只有一個單獨的定義。如果 C++ 允許在類內部定義一個和對象一樣占據(jù)內存的實體的話,這種規(guī)則就被破壞了。
? 一種方法就是 static 和 const 并用,在內部初始化,如上面的例子;
??????class A
????? {
????????? public:
??????????????? A(int i=0):test(i) {}
????????? private:
??????????????? const int i;
???????} ;
?????? 還有一種方式就是在外部初始化,例如:
??????class A
??????{
????????? public:
??????????????? A() {}
????????? private:
??????????????? static const int i;? file://注 意必須是靜態(tài)的!
???????} ;
????????? const int A::i=3;
?? 我們給出下面的代碼:
?????????? const int size[3]={10,20,50};
?????????? int array[size[2]];
?const 可以用于集合,但編譯器不能把一個集合存放在它的符號表里,所以必須分配內存。在這種情況下, const 意味著 “ 不能改變的一塊存儲 ” 。然而,其值在編譯時不能被使用,因為編譯器在編譯時不需要知道存儲的內容。自然,作為數(shù)組的大小就不行了:)
? 你再看看下面的例子:
???????class A
???????{
????????? public:
??????????????? A(int i=0):test[2]({1,2}) {} file://你 認為行嗎?
????????? private:
??????????????? const int test[2];
????????} ;
vc6 下編譯通不過,為什么呢?
? 這里我們看到,常量與數(shù)組的組合沒有什么特殊!一切都是數(shù)組惹的禍!
( 6 ) this 指針是不是 const 類型的?
?this 指針是一個很重要的概念,那該如何理解她呢?也許這個話題太大了,那我們縮小一些: this 指針是個什么類型的?這要看具體情況:如果在非 const 成員函數(shù)中, this 指針只是一個類類型的;如果在 const 成員函數(shù)中, this 指針是一個 const 類類型的;如果在 volatile 成員函數(shù)中 ,this 指針就是一個 volatile 類類型的。
??????? 先看一下下面的例子:
??????? class A
???????? {
??????????????? ? ......
?????????? ???? void f(int i)?????? {......} file://一 個函數(shù)
?????????? ???? void f(int i) const {......} file://上 一個函數(shù)的重載
??????????? ??? ......
????????? };
??????? 上面是重載是沒有問題的了,那么下面的呢?
???????? class A
???????? {
??????????????? ......
?????????? ???? void f(int i)?????? {......} file://一 個函數(shù)
??????????????? void f(const int i) {......} file://?????
??????????????? ? ......
???????? }; 這個是錯誤的,編譯通不過。那么是不是說明內部參數(shù)的 const 不予重載呢?再看下面的例子:
??????? class A
???????? {
??????????????? ......
?????????? ???? void f(int& )?????? {......} file://一 個函數(shù)
??????????????? void f(const int& ) {......} file://?????
??????????????? ......
???????? };
? 這個程序是正確的,看來上面的結論是錯誤的。為什么會這樣呢?這要涉及到接口的透明度問題。按值傳遞時,對用戶而言,這是透明的,用戶不知道函數(shù)對形參做了什么手腳,在這種情況下進行重載是沒有意義的,所以規(guī)定不能重載!當指針或引用被引入時,用戶就會對函數(shù)的操作有了一定的了解,不再是透明的了,這時重載是有意義的,所以規(guī)定可以重載。
??????? 以下是我想到的可能情況,當然,有的編譯器進行了優(yōu)化,可能不分配內存。
??????? A 、作為非靜態(tài)的類成員時;
??????? B 、用于集合時;
??????? C 、被取地址時;
??????? D 、在 main 函數(shù)體內部通過函數(shù)來獲得值時;
??????? E 、 const 的 class 或 struct 有用戶定義的構造函數(shù)、析構函數(shù)或基類時;。
??????? F 、當 const 的長度比計算機字長還長時;
??????? G 、參數(shù)中的 const ;
??????? H 、使用了 extern 時。
??????? 不知道還有沒有其他情況,歡迎高手指點:) ???????
( 9 )臨時變量到底是不是常量?
??????? 假設有一個類:
??????? class A
??????? {
???????? public:
??????????????? ......
???????????? ?? static void f() const { ......}
??????????????? ......
???????? };
?? 我們發(fā)現(xiàn)編譯器會報錯,因為在這種情況下 static 不能夠與 const 共存!
?? 為什么呢?因為 static 沒有 this 指針,但是 const 修飾 this 指針,所以 ...
? ( 11 )如何修改常量?
?? 有時候我們卻不得不對類內的數(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;?? file://這 里處理!
?????????????? } ;
?????????????? class A
?????????????? {
?????????????? public:
????????????????????? A(int i=0):test(i)??????? { }
????????????????????? void SetValue(int i)const
????????????????????? { const_cast <int>(test)=i; }// 這里處理!
?????????????? private:
????????????????????? int test;??
?????????????? }
?????????????? class A
????????????? {
?????????????? public:
????????????????????? A(int i=0):test(i)??????? { }
????????????????????? void SetValue(int i)const
????????????????????? { *test=i; }
?????????????? private:
????????????????????? int* test;?? file://這 里處理!
?????????????? } ;
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;???????????????? file://此 處改變了 test 的值!
??????????????????? return 0;
???????????????? }
?? 既然編譯器可以動態(tài)初始化常量,就自然可以動態(tài)創(chuàng)建,例如:
??const int* pi=new const int(10);
?1 ) const 對象必須被初始化!所以 (10) 是不能夠少的。
?2 ) new 返回的指針必須是 const 類型的。
? 那么我們可不可以動態(tài)創(chuàng)建一個數(shù)組呢?答案是否定的,因為 new 內置類型的數(shù)組,不能被初始化。
? 這里我們忽視了數(shù)組是類類型的,同樣對于類內部數(shù)組初始化我們也做出了這樣的忽視,因為這涉及到數(shù)組的問題,我們以后再討論。
?
??????????
Reference:
http://blog.csdn.net/boox/archive/2005/05/30/384509.aspx
http://www.bloghome.cn/posts/61287.html
http://blog.csdn.net/hwalk/archive/2006/05/20/746471.aspx
?
posted on 2007-10-06 01:43 旅途 閱讀(159) 評論(0) 編輯 收藏 引用 所屬分類: C/C++