複合型別有多重,而本篇只講兩種參照
和指針
。
現在對於變數的宣告我們可以理解爲:一條宣告語句有一個基本數據型別和緊隨其後的一個宣告符號列表組成
此處的參照,是指"左值參照
",而C++裡還有一種右值參照
,將會在後面介紹。
而應用的基本概念就是將參照和它的初始值系結
在一起,而不是把初始值拷貝給參照。且參照必須被初始化和無法再系結到另一個物件。
參照並非物件,它只是對一個已存在的物件所起的另外一個名字。
通俗一點,就是:我可以給李小明取個外號叫李狗蛋,當我去找李狗蛋的時候,並不是找的一個新事物,而是李狗蛋。
這個我們可以從變數的地址或彙編程式碼裡看出,但可能有些人還不知道地址的概念,所以就不放出了,可以列印變數及其參照的地址來看看結果。
參照的本身不是一個物件,所以不能定義參照的參照,這就好比,你不能給李狗蛋起外號叫狗蛋兒,因爲李狗蛋只是一個代號,它代替的是李小明,你要新起的外號,是對李小明起的外號,而不是對李狗蛋起的外號。
參照除了後面會講的兩種情況,其他情況都必須遵守:
&
開頭。例如:
int i = 1024; //對
int &r = i, r2 = i; //r是參照, r2是int變數
int &a = 10; //錯誤
int &b = (1+1); //錯誤
char &c = i; //錯誤
指針就是一個"指向"另一種型別的複合型別。指針本身就是一個物件,允許對指針賦值和拷貝,而且能在指針生命週期裡它可以先後 先後指向幾個不同對象;指針無需定義時賦值,但和其他型別一樣,在塊兒定義域內的指針沒有初始化,也將擁有一個不確定的值。
且定義指針型別的方法將宣告符寫成*d的形式,其實d是變數名,每個變數前面都必須有符號*
:
int *ip1, *ip2;
你可以把變數名想成一個門,變數值就是門裏住的人,比如我可以從開小夫家的門找小夫,也可以開胖虎家的門找胖虎。而可以把指針當成一個任意門,我從這個任意門進去,又可以找到小夫,又可以找到胖虎,甚至還可以找到靜香。
指針存放某個物件的地址,需要使用取地址符 &
而型別實際上被用於指定它所指向物件的型別,所以二者必須匹配。
int a = 10;
int *ptr = &a;
int *pptr = ptr;
指針的值(即地址)屬於下列4種狀態之一:
如果指針指向了一個物件,則允許使用解除參照符*
來存取該物件。
int a = 42;
int *b = &a;
std::cout << *b;
這段程式碼就會輸出42
&
和*
在不同的地方有不同的含義:有可能是取地址符,有可能是個解除參照符,也有可能是指針或者參照前面的識別符號,也可能是位運算,需要具體根據程式碼來判斷這符號是啥含義。
空指針
不指向任何物件,生成空指針的三個辦法
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL; //需要include cstdlib
在C++中,我們推薦使用nullptr
來初始化指針而不是C語言裡的NULL
,因爲這種特殊的字面值可以使他變成其他任意型別的指針型別。
NULL
是個預處理變數,它被定義的值就是0
建議:初始化所有指針
記住,賦值永遠改變的是等號左側的物件:
例如
int a = 42;
int b = 36;
int *ptr1 = &a;
int *ptr2 = &b;
*ptr1 = *ptr2; //這裏是把b的值賦值給 ptr1指向的地址的值,也就是說,現在a=36了
ptr1 = ptr2; //這裏是吧ptr1指向的地址改成了ptr2, 也就是說,現在對ptr1賦值,會改變b的值了
後面會詳細介紹更多的用法,現在則可以記住:
==
或!=
進行地址是否相同進行比較void*
是一種特殊的指針型別,可以用於存放任意物件的地址。其能做的事比較有限:
而我們不直接操作void*指針所指的物件,因爲我們不能確定物件的型別
這是一種宣告方法:
int* p; //合法,但是容易殘生誤導↓
int* p1, p2; //合法,但是p1是指向int的指針,p2是int型別的變數。
//使用這種方式,一行最好只定義一個變數
所以,我們推薦修飾符和型別緊挨在一起:
int *p1, *p2;
搞過逆向的可能直接不講就懂,不知道的話,就可以理解爲「通向傳送門的傳送門」。將一個指針的指向另一個指針的地址:
形如:
int a = 233;
int *b = &a;
int **c = &b;
結構就像這樣:
因爲參照不是物件,所以不能定義指向參照的指針。指針是物件,所以可以有指向指針的參照:
int i = 233;
int *p;
int *&r = p;
r = &i;
*r = 0;
這樣,就把i變成了0.
對於閱讀比較複雜的語句的時候,可以從右往左讀,也許會更容易理解。