C++編程思想學習筆記---第八章 常量

第八章

常量概念(由關鍵字const表示)是爲了使程序員能夠在變和不變之間畫一條界限。這在C++程序設計項目中提供了安全性和可控性


1、值代替

C語言用#define  BUFSIZE 100的宏定義方式來做值替換。好處是100的意義清楚,並且修改方便。有經驗的程序員會把100加上括號,以防止某些因爲邏輯不嚴謹造成的錯誤。但是因爲預處理器只做些文本替代,它既沒有類型檢查概念,也沒有該功能,所以這樣的做法經常會產生一些問題。C++中用const值避免這些問題。具體做法是:

const int bufsize = 100;

這樣就可以在編譯時編譯器需要知道這個值得任何地方使用bufsize。同時編譯器還可以執行常量摺疊(constant folding),也就是說,編譯器在編譯時可以通過必要的計算把一個複雜的常量表達式通過縮減簡單化。這一點在數組的定義裏顯得尤爲重要:

char buf[bufsize];
可以爲所有的內建類型以及由他們所定義的變量使用限定符const。

1.1 頭文件裏的const

要使用const而非#define,同樣必須把const定義放進頭文件。C++的const默認爲內部連接(internal linkage),即,const僅在const被定義過的文件裏纔是可見的,而連接時不能被其他編譯單元看到。當定義一個const時必須給它賦值。除非用extern作出了清楚的說明:

extern const int bufsize;
關於const變量的內存空間問題:通常C++編譯器並不爲const創建存儲空間,它把這個定義保存到它的符號表裏。要進行存儲空間分配的情況:1)如上使用了extern 2)取一個const的地址。因爲extern意味着有幾個不同的編譯單元能夠引用它,所以必須有存儲空間。

1.2 const 的安全性
如果用運行期間的產生的值初始化一個變量而且知道在變量生命期內是不變的,則用const限定改變量是程序設計中的一個很好的做法。

關於聚合,如果編譯一下代碼

//C08: Constag.cpp
const int i[] = { 1, 2, 3, 4 };
float f[i[3]];
int main()
{

}
會發現第二行,float f[i[3]]報錯。error C2057: 應輸入常量表達式。當定義整型數組i的時候,實際分配了一塊不能改變的存儲空間。然後不能在編譯期間使用它的值,因爲編譯器在編譯期間不需要知道存儲的內容。這樣在定義float型數組f的時候,i[3]就不是一個常量或常量表達式,因爲不能作爲數組的下標。

1.3 const在C語言和C++中的不同

1)C默認const外部連接,C++默認是內部連接。什麼意思呢?如果你寫以下語句

const int bufsize;
int array[bufsize];
在C語言中,這是合理的。編譯器會認爲bufsize是一個外部變量(默認外部連接),這裏只是一個聲明而已。

如果在C++中這麼寫,則會報錯,因爲編譯器找不到bufsize的定義,它不會去文件外部尋找bufsize的定義。如果想完成同樣的事情,應該顯式地表達出來,如下

extern const int bufsize;
int array[bufsize];

當然此處的bufsize仍然是一個聲明,而不是定義。


2)在C++中const不必創建內存空間,而在C中,一個const總是需要創建一塊內存空間。

如果僅把const用來作值替換,則不需要創建內存空間(如同#define一樣)。

如果對其取了地址,則會創建內存或者定義爲extern也會創建內存。

下面再來區分幾種const的寫法:

extern const int x;    表示x是一個外部變量,這裏實在引用聲明。

extern const int x = 1;     表示x是在當前定義的一個變量,並且聲明爲extern,可以被其他文件引用,並且強制分配內存

他們的區別僅在於是否初始化。


2、指針

2.1指向const的指針

const int* u;

u是一個指針,它指向一個const int。這裏不需要初始化,因爲u可以指向任何標示符,該指針本身並不是一個const,它可以被任意更改,但被指向的地址中的內容是不能更改的。

int const* v;

可能很多人以爲它應該是標示一個指向int的const指針,但實際上它跟const int* u一樣。


2.1.2const指針

int d = 1;

int* const w = &d;

w是一個指針,這個指針指向int的const指針,它現在不能指向其他地方了,所以必須要初始化。然後該地址中存放的內容是可變的。*w = 2;

再來兩個例子,我們看看到底怎麼區分常指針和常變量。

int d = 1;

const int* const x = &d;  x是一個常指針,同時它指向一個常變量

int const* const x2 = &d; x2也是一個常指針,同時指向一個常變量

所以看出來怎麼區分了嗎?如果const挨着指針名稱寫,它就是一個常指針,否則就只是修飾所指向的變量。

注意:可以把一個非const對象的地址賦給一個const指針,因爲有時不想改變某些可以改變的東西;但是不能把一個const對象的地址賦給一個非const指針,因爲這樣做可能改變該const對象的值。


3、類

3.1類裏的const

考慮這樣一種情況,程序員需要像#define一樣定義一個數組大小size,他使用了一個變量const int size; 但是這個數據成員是類裏面的,需要在構造函數裏初始化。而另一個需要它的數組array[size]卻並不一定在size之後初始化(雖然這完全可以由程序員的細心做到),這樣肯定會出錯,因爲此時編譯器並不知道size是什麼。爲了防止這種情況,C++使用了一個叫做“構造函數初始化列表”的東西來優先初始化一些數據,這些賦值操作比任何構造函數體裏面的語句都先執行,而這裏也正是const類型初始化的地方。

class Fred
{
	const int size;
public:
	Fred(int sz);
	void print();
};

Fred::Fred(int sz) : size(sz) {}
void Fred::print() { cout << size << endl; }
在構造函數後面加一個冒號,後面跟着數據成員。不難發現,這跟繼承的語法有點相似。可以把size(sz)看成是基類的構造函數,sz是它的參數。實際上,這叫做內建類型的構造函數


3.2編譯期間類裏的常量

上面才說了,const量必須在構造函數的初始化列表裏優先初始化,這裏又說有一種const量可以而且必須在定義時賦值

讓一個類在編譯期間就有常量,那這個變量必定是屬於整個類的,而不是屬於某個對象的。可以推到出,它必定要用關鍵字static修飾。對之前的代碼稍加修改

class Fred
{
	static const int size = 100;
	int array[size];
public:
	Fred(int sz);
	void print();
};
這樣,在編譯期間,size的值就是100。另外,在老的C++代碼中,static const並不被支持,通常用一種不帶示例的無標記枚舉enum來代替,因爲一個枚舉在編譯期間必須有值,它在類中局部出現,而且它的值對於常量表達式是可以使用的。以下代碼功能相同

class Fred
{
//	static const int size = 100;
	enum { size = 100 };
	int array[size];
public:
	Fred(int sz);
	void print();
};

3.3const對象和成員函數

如何定義一個const成員函數?

必須在聲明和定義中同時加上const修飾符,再次強調,必須同時。

這樣做有什麼用?

一個const成員函數不管以什麼方式改變一個非const數據成員變量(const本身無論在何處都不能更改,討論沒有意義)或調用非const成員函數,編譯器都會報錯。

class Fred
{
	int i;
public:
	Fred(int ii);
	int f() const;
};

Fred::Fred(int ii) : i(ii) {}
int Fred::f() const { return i; }

3.4mutable關鍵字

繼續打臉!上面才說了,const成員函數保證了不能對數據成員進行修改,同時不能調用其他非const成員函數。現在問:如果確實需要在const函數裏改變某個變量改怎麼辦?

答案是:關鍵字mutable

class Fred
{
	mutable int i;
public:
	Fred(int ii);
	int f() const;
};

Fred::Fred(int ii) : i(ii) {}
int Fred::f() const { return ++i; }
現在可以對i爲所欲爲了。類的用戶可從聲明裏看到哪個成員能夠用const成員函數進行修改


3.5volatile關鍵字

與const相反,volatile表示在編譯器的認識範圍以外,這個數據可以被改變

發佈了27 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章