原文鏈接:http://blog.csdn.net/u011421608/article/details/30750355
一、介紹引用
首先說引用是什麼,大家可以記住,引用就是一個別名,比如小王有個外號叫小狗,他的媽媽喊小狗回家吃飯,那就是在喊小王回家吃飯。
接下來我們用兩行代碼來聲明一個引用(就拿小王和小狗來說吧):
int xiaoW;
int &xiaoG=xiaoW;
上面就是一個引用,說明幾點要注意的地方:
1.&不是取地址符,而是引用運算符;
2.xiaoG是xiaoW的別名,所以這兩個變量的值和地址都是一樣的;
3.引用只能初始化,而不能先聲明再賦值,因為引用就相當於一個常量;
4.在聲明一個引用時必須同時對其進行初始化。
引用是非常忠心的,如果你定義了一個變量的別名,那麼這個別名永遠屬於這個變量,它的地址始終和變量的地址相同,當改變別名的值也會改變這個變量的值。
同樣,對對象的引用也與對變量的引用相同,但需要注意的是不能對類引用,因為類是一種類型,沒有內存地址。
關於引用的介紹就到這裡,下面我們來學習一下引用的使用。
二、三種傳參方式的比較
函數的傳參方式有三種:按值傳遞、按地址傳遞及按別名傳遞(按別名傳遞也是按地址傳遞的一種,這裡我為了方便講解把它單列出來)。接下來我們用三個程序來對三種方式作下比較,程序的功能是交換兩個變量的值。
1.按值傳遞。
#include
using namespace std;
void swap(int i,int j)
{
cout<<"交換前的i:"<
int temp;
temp=i;
i=j;
j=temp;
cout<<"交換後的i:"<
}
int main()
{
int a=1,b=2;
cout<<"交換前的a:"<
swap(a,b);
cout<<"交換後的a:"<
return 0;
}
程序的運行截圖如下:
從結果我們可以看出,變量a和b的值分別傳給i和j,函數內的i和j的值是交換了的,而a和b的值並未交換,這是怎麼回事呢。
這是因為,當把a和b的值傳遞到函數中時,這種傳遞方式就是按值傳遞,編譯器會自動在棧中創建a和b的副本,然後再將a和b的副本傳遞給函數,這樣在函數中交換的是a和b的副本,而不是a和b本身。解決這樣的問題就要引入指針了。
2.按地址傳遞。
#include
using namespace std;
void swap(int *i,int *j)
{
cout<<"交換前的i:"<<*i<<" "<<"j:"<<*j<
int temp;
temp=*i;
*i=*j;
*j=temp;
cout<<"交換後的i:"<<*i<<" "<<"j:"<<*j<
}
int main()
{
int a=1,b=2;
cout<<"交換前的a:"<
swap(&a,&b);
cout<<"交換後的a:"<
return 0;
}
程序的運行截圖如下:
可以看到,這樣就可以交換a和b的值了,因為程序傳遞的是a和b的地址,所以在函數中可以通過地址直接訪問a和b,交換它們的值。
指針雖然可以實現交換的功能,但是使用起來比較麻煩,又不易閱讀,使用引用看起來就會直觀得多。
3.按別名傳遞
#include
using namespace std;
void swap(int &i,int &j)
{
cout<<"交換前的i:"<
int temp;
temp=i;
i=j;
j=temp;
cout<<"交換後的i:"<
}
int main()
{
int a=1,b=2;
cout<<"交換前的a:"<
swap(a,b);
cout<<"交換後的a:"<
return 0;
}
程序的運行截圖如下:
結果也是成功交換,程序看起來易懂,而且操作也方便不少。
三、傳遞對象
對象和變量一樣可以作為參數傳遞,而不一樣的是,對象可能包含有大量的數據,那麼用上述的三種方式分別傳遞對象是什麼樣子呢。
1.按值傳遞對象
當按值傳遞的參數是一個對象時,編譯器同樣會建立一個對象的副本,函數中返回對象時,同樣會建立對象的副本,當該對象的數據很多時,那對內存的消耗就非常大了,我們先來看按值傳遞的程序。
#include
using namespace std;
class A
{
public:
A(){cout<<"構造函數被調用\n"<
A(A&a){cout<<"拷貝構造函數被調用\n"<
~A(){cout<<"析構函數被調用\n"<
private:
int x;
};
A func(A a)
{
return a;
}
int main()
{
A a;
func(a);
return 0;
}
程序運行結果截圖如下:
由程序運行結果可以看出,執行了一次構造函數,兩次復制構造函數,三次析構函數,接下來我們對照程序結果和代碼分析一下(此處知識對新手十分重要,大家務必牢記!),main函數中創建了一個對象a,所以調用了構造函數,再將a傳遞到func函數中,這時會自動調用復制構造函數來創建對象a的副本,也就是說傳進func函數的是a的副本而不是a本身,func函數的返回值是對象,返回的方式也是按值返回,此時又自動調用復制構造函數創建返回值的副本,所以我們看到調用一次構造函數和兩次復制構造函數,相應也會調用三次析構函數釋放內存。
2.按地址傳遞對象
從按值傳遞對象的程序結果可以看出,這種方式的系統開銷是非常大的,而按地址傳遞則解決了這一問題。
#include
using namespace std;
class A
{
public:
A(){cout<<"構造函數被調用\n"<
A(A&a){cout<<"拷貝構造函數被調用\n"<
~A(){cout<<"析構函數被調用\n"<
private:
int x;
};
A func(A *a)
{
return *a;
}
int main()
{
A a;
func(&a);
return 0;
}
程序運行結果截圖如下:
可以看出,上面的程序避免了兩次復制構造函數的調用,因為在將對象a傳遞給func函數時傳進的是地址,func函數返回的同樣也是個地址。
3.按別名傳遞對象
#include
using namespace std;
class A
{
public:
A(){cout<<"構造函數被調用\n"<
A(A&a){cout<<"拷貝構造函數被調用\n"<
~A(){cout<<"析構函數被調用\n"<
private:
int x;
};
A &func(A &a)
{
return a;
}
int main()
{
A a;
func(a);
return 0;
}
程序運行結果截圖如下:
可以看出,按別名傳遞對象與按指針傳遞有同樣的效果,而實現起來又簡單的多,這裡給新手朋友們做出個小提示,就是func函數返回的類型是別名,如果想在main函數中接收這個別名,同樣也要用引用類型的數據接收,比如A &aa=func(a),而不能用一個A類型的對象接收,這點大家要注意。
四、到底該用指針還是引用
既然引用可以實現指針的功能,又比指針使用方便,那是不是只用引用就可以了呢,答案是否定的,因為指針的某些功能引用是無法實現的,先說一下引用和指針的區別。
1.指針可以為空,而引用不能為空;
2.指針可以被賦值,而引用不可改變;
3.在堆中創建一塊內存區域時,必須使用指針來指向這塊區域,否則是無法訪問的,而不能用引用來指向
五、使用引用需謹慎
引用是不能為空的,否則是得不到正確的結果,我們通過下面的程序來感受一下。
#include
using namespace std;
class A
{
public:
A(int i){x=i;}
int get(){return j;}
private:
int j;
};
A &func()
{
A a(100);
return a;
}
int main()
{
A&aa=func();
cout<
return 0;
}
程序運行結果截圖如下:
從運行結果可以看出,我們並沒有得到正確的j值(100),這是為什麼呢,原因是,func函數中的a是一個局部對象,在func函數運行後,a就沒有了,所以func函數返回的是一個並不存在的對象的別名,因此無法正確得到j的值,解決這個問題很容易,只要將func函數的引用類型返回值改為A類型,就可以的到正確的結果,因為這樣會創建一個對象a的副本。
六、寄語
我剛開始學習C++時對引用就特別頭疼,但只要清晰的了解了它的工作方式,學習起來還是比較簡單的,所以我以我的親身體驗寫出這篇博文,以供初學者朋友參考。當然,引用的知識比較混亂而且極容易與指針混淆,需要在應用的過程中才能逐漸掌握,所以建議朋友們還是去多讀多寫代碼。祝朋友們都能學到足夠的知識,在實踐中進步,成為合格的互聯網人才。
由於我也是剛入門不久,所以纰漏之處在所難免,請各位多包涵,如果對本文有任何意見或建議,歡迎與我qq交流,謝謝!
yuan