C++: 21---引用和指標
一般說到誰和誰怎麼樣,要麼說兩者的相似點,要麼兩者的區別,這裡我們也要說二者的區別和聯絡,同時,也不僅僅是區別和聯絡這麼簡單,因為你可能會發現在變數賦值,函式傳參這兩點還是有很多值得品一品的。
最直觀的賦值方面的區別
首先我們先說二者的區別和聯絡。
(1)指標:指標是一個變數,只不過這個變數儲存的是一個地址,指向記憶體的一個儲存單元;而引用跟原來的變數實質上是同一個東西,只不過是原變數的一個別名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定義了一個整形變數和一個指標變數p,該指標變數指向a的儲存單元,即p的值是a儲存單元的地址。
而下面2句定義了一個整形變數a和這個整形a的引用b,事實上a和b是同一個東西,在記憶體佔有同一個儲存單
元。
(2)引用不可以為空,當被建立的時候,必須初始化,而指標可以是空值,可以在任何時候被初始化。
(3)可以有const指標,但是沒有const引用;
(4)指標可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(5)指標的值可以為空,但是引用的值不能為NULL,並且引用在定義的時候必須初始化;
(6)指標的值在初始化後可以改變,即指向其它的儲存單元,而引用在進行初始化後就不會再改變了。
(7)”sizeof引用”得到的是所指向的變數(物件)的大小,而”sizeof指標”得到的是指標本身的大小;
(8)指標和引用的自增(++)運算意義不一樣;
(9)如果返回動態記憶體分配的物件或者記憶體,必須使用指標,引用可能引起記憶體洩漏;
指標和引用作為函式引數進行傳遞時的區別
(1)指標作為引數進行傳遞:
#include<iostream> #include<stdlib.h> usingnamespacestd; void swap_int(int *a,int *b) { int *temp=a; a=b; b=temp; } void test(int *p) { int a=1; p=&a; cout<<p<<" "<<*p<<endl<<endl;; } int main(void) { int a=1,b=2; int *p=NULL; swap_int(&a,&b); cout<<a<<" "<<b<<endl<<endl; test(p); if(p==NULL) cout<<"指標p為NULL"<<endl<<endl; }
執行後的結果如下:
swap_int函式使用指標傳遞引數,可以實現對實參進行改變的目的,是因為傳遞過來的是實參的地址,因此
使用*a實際上是取儲存實參的記憶體單元裡的資料,即是對實參進行改變,因此可以達到目的。呼叫test函式
執行結果為:
0x6afecc 1
指標p為NULL
在main函式中聲明瞭一個指標p,並賦值為NULL,當呼叫test函式時,事實上傳遞的也是地址,只不過傳遞
的是指地址。也就是說將指標作為引數進行傳遞時,事實上也是值傳遞,只不過傳遞的是地址。當把指標作
為引數進行傳遞時,也是將實參的一個拷貝傳遞給形參,即上面程式main函式中的p何test函式中使用的p不
是同一個變數,儲存2個變數p的單元也不相同(只是2個p指向同一個儲存單元),那麼在test函式中對p進
行修改,並不會影響到main函式中的p的值。如果要想達到也同時修改的目的的話,就得使用引用了。
(2)將引用作為函式的引數進行傳遞。
在講引用作為函式引數進行傳遞時,實質上傳遞的是實參本身,即傳遞進來的不是實參的一個拷貝,因此對形參的修改其實是對實參的修改,所以在用引用進行引數傳遞時,不僅節約時間,而且可以節約空間。
#include<iostream>
#include<stdlib.h>
usingnamespacestd;
void test(int &a)
{
cout<<&a<<" "<<a<<endl<<endl;
}
int main(void)
{
int a=1;
cout<<&a<<" "<<a<<endl<<endl;
test(a);
}
執行後的結果如下:
所以在引用進行引數傳遞時,事實上傳遞的是實參本身而不是拷貝副本。所以在上述要達到同時修改指標的
目的的話,就得使用引用了。
#include<iostream>
#include<stdlib.h>
using namespace std;
void test(int *&p)
{
int a=1;
p=&a;
cout<<p<<" "<<*p<<endl<<endl;
}
int main(void)
{
int *p=NULL;
test(p);
if(p!=NULL)
cout<<"指標p不為NULL"<<endl<<endl;
cout<<p<<" "<<*p<<endl<<endl;
}
執行後的結果如下:
為了檢查你是否掌握引用和指標,到這裡那我要提問幾個問題:
1.拷貝建構函式的引數為什麼必須是類物件的常引用Object(const Object& O1) ?
原因很簡單不能將一個常物件賦給一個非常物件。
假如非引用傳參,那麼O1是不是要呼叫它的拷貝建構函式,傳參後因為非引用傳參,又要呼叫拷貝建構函式,如此遞迴,將陷入死迴圈。假如是引用傳參,則不會呼叫自己的拷貝建構函式。
2.如果作為函式引數,你不希望函式內修改它,那麼你選擇指標還是引用?
如果是我,我更喜歡選擇引用,因為免去了指標判空(我比較懶),最主要是還是省空間,因為如果引數比較多,傳指標,相當於要給當前的函式入口地址分配棧空間的時候,你的指標引數要分配8位元組空間,這樣重複呼叫此函式可能會產生大量記憶體碎片(實際上記憶體碎片沒有那麼可怕,對於頻繁申請和釋放記憶體的操作我們就必須要重視記憶體碎片,解決辦法就是我們可以使用記憶體池來來分配物件,記憶體池我將會在C++進階的另外一個專題裡說),而引用不需要額外分配空間,它只是相當於一個別名而已,不過由於不希望函式內修改此引數,所以我會通過const來修飾引數。