c++筆記_const限定符

學C++的小萌新發表於2020-12-01


前言

有時候我們希望定義這樣一種型別,它的值不能被改變。為了滿足這一要求,可以用關鍵字const對變數的型別加以限定。


一、const

使用const語法格式:

const int bufSize = 512;		//在資料型別前加上關鍵字const

這樣bufSize就定義成了一個常量。任何試圖為bufSize賦值的行為都將引發錯誤:

const int bufSize = 512;		//定義常量bufSize
bufSize = 512;					//錯誤:試圖向const物件寫值

因為const物件一旦建立後其值就不能在改變,所以const物件必須初始化。const物件初始值可以是任意複雜的表示式:

const int i = get_size();		//正確:執行時初始化
const int j = 42;				//正確:編譯時初始化
const int k;					//錯誤:k是一個未經初始化的常量

二、初始化和const

在不改變const物件的操作中還有一種初始化,如果利用一個物件去初始化另外一個物件,則它們是不是const都無關緊要:

int i = 42;
const int ci = i;				//正確:i的值被拷貝給了ci
int j = ci;						//正確:ci的值被拷貝給了j

儘管ci是整數常量,但無論如何ci中的值還是一個整型數。ci的常量特徵僅僅在執行改變ci的操作時才會發揮作用。當用ci去初始化j時,根本無須在意ci是不是一個常量。拷貝一個物件的值並不會改變它,一旦拷貝完成,新的物件就和原來的物件沒有什麼關係了。

三、const作用域

預設狀態下,const物件僅在檔案內有效。當以編譯時初始化的方式定義一個const物件時,就如對bufSize的定義一樣:

const int bufSize = 512;		

編譯器將在編譯過程中把用到對應變數(bufSize)的地方都替換成對應的值。因此為了執行替換,編譯器必須知道變數的初始值。如果程式包含多個標頭檔案,則每個用了const物件的檔案都必須得到訪問它的初始值才行。要做到這一點,就必須在每一個用到變數的檔案中都有對它的定義。所以預設情況下,const物件被設定為僅在檔案內有效。當多個檔案中出現中了同名的const變數時,其實等同於在不同檔案中分別定義了獨立的變數。

1.如何使const變數在檔案間共享?

有時候我們不希望編譯器為每個檔案分別生成獨立的變數。相反,我們想讓const物件像其他(非常量)物件一樣工作,也就是說,只在一個資料夾中定義const,而在其他多個檔案中宣告並使用它。

解決的辦法是,對const變數不管是宣告還是定義都新增extern關鍵字,這樣只需定義一次就可以了:

//file_1.cpp定義並初始化了一個常量,該常量能被其他檔案訪問
extern const int bufSize = fun();
//file_1.h標頭檔案
extern const int bufSize;		//與file_1.cpp中定義的bufSize是同一個

如上述程式所示,file_1.cpp定義並初始化了bufSize。因為這條語句包含了初始值,所以它是一次定義。然而,因為bufSize是一個常量,必須用extern加以限定使其被其他檔案使用。

file_1.h標頭檔案中的宣告也有extern做了限定,其作用是指明bufSize並非該檔案獨有,它的定義將別處出現。

總結得出:如果想在多個檔案之間共享const物件,必須在變數的定義之前新增extern關鍵字。

四、const的引用

可以把引用繫結到const物件上,就像繫結到其他物件上一樣,我們稱之為對常量的引用。與普通引用不同的是,對常量的引用不能被用作修改它所繫結的物件:

const int ci = 1024;
const int &r1 = ci;		//正確:引用及其對應的物件都是常量
r1 = 42;				//錯誤:r1是對常量的引用
int &r2 = ci;			//錯誤:試圖讓一個非常量引用指向一個常量物件(修改為:const int &r2 = ci;就正確)

因為不允許直接為ci賦值,當然也不能通過引用去改變ci。所以,對r2的初始化是錯誤的。

五、初始化和對const的引用

關於引用之前說到,引用的型別必須與其所有引用的物件的型別一致,但是有兩個例外。第一種外情況就是初始化常量引用時允許用任意表示式作為初始值,只要該表示式的結果能轉換成引用的型別即可。尤其,允許為一個常量引用繫結非常量的物件、字面值,甚至是個一般表示式:

int i = 42;
const int &r1 = i;			//允許將const int&繫結到一個普通的int物件上
const int &r2 = 42;			//正確:r1是一個常量引用
const int &r3 = r1 * 2;		//正確:r3是一個常量引用
int &r4 = r1 * 2; 			//錯誤:r4是一個普通的非常量引用

想要理解這種例外情況的原因,最簡單的辦法是弄清楚當一個常量引用被繫結到另外一種型別上時到底發生了什麼:

double dval = 3.14const int &ri = dval;

此處ri引用了一個int型的數。對ri的操作應該是整數運算,但dval卻是一個雙精度浮點數而非整數。因此為了確保讓ri繫結一個整數,編譯器把上述程式碼變成了如下形式:

const int temp = dval;		//由雙精度浮點數生成一個臨時的整型常量
const int &ri = temp;		//讓ri繫結這個臨時變數

這種情況下,ri繫結了一個臨時量物件。所謂臨時量物件就是當編譯器需要一個空間來暫存表示式的求值結果時臨時建立的一個未命名的物件。

1.對const的引用可能引用一個並非const的物件

常量引用僅對引用可參與的操作做出限定,對於引用的物件本身是不是一個常量未作限定。因為物件也可能是個非常量,所以允許通過其他途徑改變它的值:

int i = 42;
int &r1 = i;			//引用ri繫結物件i
const int &r2 = i;		//r2也繫結物件i,但是不允許通過r2修改i的值
r1 = 0;					//r1並非常量,i的值修改為0
r2 = 0;					//錯誤:r2是一個常量引用

六、指標和const

與引用一樣,也可以令指標指向常量或非常量。類似於常量引用,指向常量的指標不能用於變其所指物件的值。要想存放常量物件的地址,只能使用指向常量的指標:

const double pi = 3.14;			//pi是個常量,它的值不能改變
double *ptr = π				//錯誤:ptr是一個普通指標
const double *cptr = π		//正確:cptr可以指向一個雙精度的常量
*cptr = 42;						//錯誤:不能給*cptr賦值

有一種情況下是允許令一個指向常量的指標指向一個非常量的物件:

double dval = 3.14;			//dval是一個雙精度浮點數,它的值可以改變
cptr = &dval;				//正確:但是不能通過cptr改變dval的值

從上述程式碼可以看出,和常量引用一樣,指向常量的指標也沒有規定其所指的物件必須是一個常量。所謂指向常量的指標僅僅要求不能通過該指標改變物件的值,而沒有規定那個物件的值不能通過其他途徑改變。

七、const指標

指標是物件而引用不是,因此就像其他物件型別一樣,允許把指標本身定位常量。常量指標必須初始化,而且一旦初始化完成,則它的值(也就是存放指標中的那個地址)就不能在改變了。

把*放在關鍵字const之前用以說明指標是一個常量,這樣的書寫形式隱含著一層意味,即不變的是指標本身的值而非指向的那個值:

int errNumb = 0;
int *const curErr = &errNumb;			//curErr將一直指向errNumb
const double pi = 3.14159;
const double *const pip =π			//pip是一個指向常量物件的常量指標

解釋一下上述程式碼第四行的含義:語句從右往左閱讀,離pip最近的符號是const,意味著pip是一個常量物件,然後const緊接著是符號*,意思pip是一個常量指標,在接著閱讀資料型別是一個double,最左是個const,所以pip指向的物件是一個雙精度浮點型常量。

1.如何修改指向常量的常量指標

指標本身是一個常量並不意味著不能通過指標修改其所指物件的值,能否這樣做完全依賴於所指物件的型別。例如,上述程式碼pip是一個指向常量的指標,則不論pip所指的物件值還是pip自己儲存的那個地址都不能改變。相反,curErr指向的一個一般的非常量整數,那麼完成可以用curErr去修改errNumb的值:

*pip = 2.72;				//錯誤:pip是一個指向常量的指標
							//如果curErr所指的物件(也就是errNumb)的值不為0
if (*curErr)				
	*curErr = 0;			//正確:把curErr所指的物件的值重置

八、const和#define區別

const和#define區別:
1:const有型別,可進行編譯器型別安全檢查。#define無型別,不可進行型別檢查。
2:const有作用域,而#define不重視作用域,預設定義到檔案結尾,如果定義在指定作用域下有效的常量,那麼#define就不能用。

1.巨集沒有型別const有

#define MAX 1024
const int short my_max = 1024;
void func(short i)
{
	cout<<"shor函式"<<endl;
}
void func(int i)
{
	cout<<"int函式"<<endl;
}
int main(void)
{
	func(my_show);			
	func(MAX);					
	return 0;
}

2.巨集的作用域是整個檔案,const的作用域以定義情況決定

void my_fucn(void)
{
	//作用範圍是當前複合語句
	const int my_num = 10;
	#define MY_NUM 10
}
int main(void)
{
	cout<<"MY_NUM"<<my_num<<endl;		//錯誤:無法識別常量my_num
	cout<<"MY_NUM"<<MY_NUM <<endl;		//正確:可以正確識別常量MY_NUM 
	return 0;
}

3.巨集不能作為名稱空間的成員,const可以

namespace A{
	//const可以作為名稱空間的成員
	const int my_a = 100;
	
	//MY_A屬於檔案不屬於A
	#define MY_A 200
}
int main(void)
{
	cout<<"my_a="<<A::my_a<<endl;		//正確,可以正常輸出常量my_a的值
	cout<<"MY_A="<<A::MY_A<<endl;		//錯誤,無法找到名稱空間A中MY_A的值
	cout<<"MY_A="<<MY_A<<endl;			//正確,不使用域運算子可以正常識別到MY_A的值
	return 0;
}

相關文章