1. 程式人生 > 其它 >C++: 21---引用和指標

C++: 21---引用和指標

技術標籤:指標c++java記憶體管理記憶體洩漏

一般說到誰和誰怎麼樣,要麼說兩者的相似點,要麼兩者的區別,這裡我們也要說二者的區別和聯絡,同時,也不僅僅是區別和聯絡這麼簡單,因為你可能會發現在變數賦值,函式傳參這兩點還是有很多值得品一品的。

最直觀的賦值方面的區別

首先我們先說二者的區別和聯絡。

(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來修飾引數。