const修飾符總結
阿新 • • 發佈:2019-02-14
1、什麼是const?
2、為什麼引入const?
const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優點。3、cons有什麼主要的作用?
(1)可以定義const常量,具有不可變性。 例如: const int Max=100; int Array[Max];class A { ...... void f(int i) {......} //一個函式 void f(int i) const {......} //上一個函式的過載 ...... };
#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定義常量從彙編的角度來看,只是給出了對應的記憶體地址,而不是象#define一樣給出的是立即數,所以,const定義的常量在程式執行過程中只有一份拷貝,而#define定義的常量在記憶體中有若干個拷貝。 4、如何使用const?
class ClassName {
public:
int Fun() const; .....
};
5、幾點值得討論的地方
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、如何初始化類內部的常量?
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、常量與陣列的組合有什麼特殊嗎?
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型別的?
7、const到底是不是一個過載的參考物件?
class A {
......
void f(int i) {......}//一個函式
void f(int i) const {......}//上一個函式的過載
......
};
class A {
......
void f(int i) {......}//一個函式
void f(const int i) {......}//?????
......
};
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 { ......}
......
};
11、如何修改常量?
有時候我們卻不得不對類內的資料進行修改,但是我們的介面卻被聲明瞭const,那該怎麼處理呢?我對這個問題的看法如下: 1)標準用法:mutable class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const { test=i; }
private:
mutable int test;//這裡處理!
};
2)強制轉換: 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;
};
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);