重溫C++——變數和基本型別
文章目錄
基本內建型別
型別 | 含義 | 最小尺寸 |
---|---|---|
bool | 布林 | 未定義 |
char | 字元 | 8位 |
wchar_t | 寬字元 | 16位 |
char16_t | Unicode字元 | 16位 |
char32_t | Unicode字元 | 16位 |
short | 短整型 | 16位 |
int | 整型 | 16位 |
long | 長整型 | 32位 |
long long | 長整型 | 64位 |
float | 單精度浮點數 | 6位有效數字 |
double | 雙精度浮點數 | 10位有效數字 |
long double | 擴充套件精度浮點數 | 10位有效數字 |
複合型別
引用
引用(reference)為物件起了另外一個名字,引用型別引用(refers to)另外一種型別,通過將宣告符寫成&d的形式來定義引用型別,其中d是宣告的變數:
int ival = 1024;
int& refVal = ival; //refVal是ival的另一個名字
int& refVal2; //錯誤,引用型別必須初始化
一般在初始化變數時,初始值會被拷貝到新建的物件中。然而定義引用時,程式把引用和它的初始值繫結(bind)在一起,而不是將初始值拷貝給引用。一旦初始化完成,引用將和它的初始值物件一直繫結在一起。因為無法令引用重新繫結到另外一個物件,因為引用必須初始化。
引用只是為一個已經存在的物件另外取了一個名字。
指標
指標(pointer)是指向(point to)另外一種型別的複合型別。與引用類似,指標也實現了對其他物件的間接訪問。然而指標與引用相比有有很多不同點。其一,指標本身就是一個物件,允許對指標賦值和拷貝,而且在指標的生命週期內它可以先後指向幾個不同的物件。其二,指標無須在定義時賦初值。和其它內建型別一樣,在塊作用域內定義的指標如果沒有被初始化,也將擁有一個不確定的值。
定義指標型別的方法將宣告符寫成*d的形式,其中d是變數名:
int* p = NULL; //也可以使用0
複合型別的宣告
變數定義包括一個基本的資料型別(base type)和一組宣告符。在同一行的定義域居中,雖然基本資料型別只有一個,但是宣告符的形式卻可以不同。也就是說,一條定義語句可能定義出不同型別的變數:
//i是一個int型別,p是一個int指標,r是一個int型的引用
int i = 1024, *p = &i, &r = i;
經常有一種觀點誤以為,型別修飾符(*和&)作用於本行定義的全部變數。造成這種錯誤看法的原因有很多,其中之一是我們可以把空格寫在型別修飾符和變數名中間:
int* p = NULL;
感覺int*
可以修飾這條語句中的所有變數,其實恰恰相反,基本資料型別是int
,而不是int*
。*僅僅修飾了p而已,對該宣告語句中的其它變數,它並不產生任何作用:
int* p1, p2; //p1是int型別的指標,p2是int
涉及指標或者引用的宣告,一般那有兩種寫法。第一種把修飾符和變數識別符號寫在一起,這種形式強調變數具有複合型別:
int *p1, *p2; //p1和p2都是指向int的指標
另一種形式把修飾符和型別名寫在一起,並且每條語句只定義一個變數,這種形式著重強調本次宣告定義了一種複合型別:
int* p1;
int* p2;
我喜歡第二種寫法!!!
指向指標的指標
一般來說,宣告符中修飾符的個數並沒有限制。當有多個修飾符連寫在一起時,按照邏輯關係解釋即可。以指標為例,指標是記憶體中的物件,像其它物件一樣有自己的地址,因此允許把指標的地址放到一個一個指標中,通過*的個數可以區分指標的級別:
int ival = 1024;
int* pi = &ival; //pi指向int
int** ppi = π //ppi指向int型別的指標
引用指標的引用
引用本身不是一個物件,因此不能定義指向引用的指標。但指標是物件,所以可以有指標的引用:
int i = 42;
int* p = NULL; //p是指標
int*& r = p; //r是指標p的引用
r = &i; //r即p, 相當於p = &i
const修飾符
const和引用
可以把引用繫結到const物件上,就像繫結到其它物件一樣,稱為對常量的引用(reference to const)。與普通引用不同的是,對常量的引用不能用來修改它所繫結的物件:
const int ci = 1024;
const int& r1 = ci;
r1 = 42; //錯誤,r1所引用的物件是常量
int& r2 = ci; //錯誤,非常量引用不能繫結到常量物件上
經常把“對const的引用”簡稱為“常量引用”,這不是說引用是常量(因為不能更改引用所繫結的物件,在這個層面上所有的引用都是常量),而是說不能通過引用去改變所繫結的物件。為了對ci
進行引用,r1
必須有const
修飾符,否則就像和r2
一樣了。const type&
這種形式主要使用在函式傳參中。
下面的程式碼也是合法的:
int i = 42;
int& r1 = i;
const int& r2 = i;
r1 = 0;
r2 = 0; //錯誤
這裡r2
同樣有const
修飾符,所以不能通過r2
改變i
。
const和指標
指向常量的指標
和引用一樣,也可以讓指標指向常量或非常量。類似於常量引用,不能通過指向常量的指標(pointer to const)來改變指標所指向的物件。要想存放常量物件的地址,只能使用指向常量的指標:
const double pi = 3.14;
double* ptr = π //錯誤,pi是個常量,普通(非常量)指標不能指向常量
const double* cptr = π
*cpter = 42;
double dval = 3.14;
*cptr = &dval;
類似引用,因為pi
用const
修飾,所以cptr
必須有const
修飾,否則就和ptr
一樣,可以通過指標來修改pi
的值了。指向常量的指標也可以指向非常量:
double dval = 3.14;
const double* cptr = &dval;
常量指標
因為指標是物件,所以可以把指標定義為const
的,即常量指標(const pointer),表示指標本身是常量。這種型別的指標必須初始化,而且一旦初始化完成,它的值(即存放在指標中的地址)就不能再改變了。把*
放在const
之前說明指標是一個常量,這樣的寫法表示指標本身的值不變而不是指標所指向的值不變:
int errNumb = 0;
int* const curErr = &errNumb; //寫成int*的形式更容易理解,表示curErr是指標型別的常量
const double pi = 3.14;
const double* const pip = π
int*
的形式表示curErr
是指標型別的,const
表示它是常量,既然是某種型別的常量,那就不能修改,所以不能修改curErr
的值,它將一直指向errNumb
。
同樣的,pip
是const double*
型別的,第二個const
表示pip
是常量,所以pip
是指向雙精度浮點型別常量的常量指標。第二個const
表示pip
不能被修改,第一個const
表示pip
指向的值不能被修改。
頂層const
頂層const(top-level const)表示物件本身是常量,底層const(low-level const)表示所指向和引用的物件是常量,指標型別既可以是頂層const,也可以是底層const:
int i = 0;
int* const p1 = &i; //p1是頂層const,不能改變p1的值
const int ci = 42; //ci是頂層const,不能改變ci的值
const int* p2 = &ci; //p2是底層const,可以改變p2的值,但是不能通過p2修改所指向的物件
const int* const p3 = p2; //靠右的是頂層const,靠左的是底層const
const int& r = ci; //引用的const都是底層const
i = ci; //頂層const不影響拷貝
p2 = p3; //同上
int* p = p3; //錯誤,p3具有底層const,而p沒有
p2 = &i; //int*可以轉換為const int*
int& r = ci; //錯誤,int&不能引用const int
const int& r2 = i; //const int&可以繫結到const 上
例項分析
以const int* const& r
為例,r
為對指向常量的指標的常量引用。
這種複雜的複合型別一般採用從右往左的方式進行解讀:
&r
表明r
是一個引用;
const& r
表明不能通過r
修改所引用的物件;
* const& r
表明r
是所引用的物件是一個指標;
int* const& r
表明指標指向int
型別;
const int* const& r
表明r
所引用的物件指向的是常量整型;
第二個const
表示r
是對常量的引用,即不能通過r
改變所引用的物件,第一個const
表示指標所指向的物件是常量,即不能通過r
來改變所指向的物件。簡單來說r
是一個指標,第二個const
是頂層的,表示不能通過r
來改變指標的值,即r++
是錯誤的;第一個const
是底層的,表示不能通過r
來改變指標所指向的值,*r = 0
是錯誤的。