C++中傳值、傳地址和傳引用究竟有哪些區別
傳引用定義
傳值與傳地址,相信大傢都瞭如指掌瞭,在這裡先介紹一下什麼是引用?
引用不是新定義一個變量,而是給已存在變量取瞭一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。
說白瞭,引用就是給變量起外號,比如一個人可以有乳名,有學名,有筆名,其實就都是一個人而已。
例:林沖,江湖上人稱“豹子頭”
類型& 引用變量名(對象名) = 引用實體;
void TestRef() { int a = 10; int& ra = a; //<====定義引用類型 printf("%p\n", &a); printf("%p\n", &ra); }
可以看出a 和ra地址是一樣的,足以證明,引用就是變量本身。
註意:引用類型必須和引用實體是同種類型的
意思是:對象用 int 定義的,那麼引用必須是 int&
引用特性
1. 引用在定義時必須初始化
#include<iostream> using namespace std; void TestRef() { int a = 10; int& ra; // 該條語句編譯時會出錯 int& ra = a; int& rra = a; printf("%p %p %p\n", &a, &ra, &rra); } int main() { TestRef(); return 0; }
int ra&; // 不賦初值,會報錯
2、一個變量可以有多個引用,一個人可以有多個外號
#include<iostream> using namespace std; void TestRef() { int a = 10; int& ra = a; int& rra = a; printf("%p\n%p\n%p\n", &a, &ra, &rra); } int main() { TestRef(); return 0; }
3、引用一旦引用一個實體,再不能引用其他實體,意思是,ra是 a 的引用後,就不能再引用別的對象
傳引用與傳值的區別
1、 傳值、傳引用返回的比較
傳值返回:
#include<iostream> using namespace std; int Add(int a, int b) { int c = a + b; return c; } int main() { int ret=Add(1,2); cout << "ret:" << ret << endl; return 0; }
註意: 返回時,c會將自己的值,復制給一個臨時變量,ret接收的其實是c的拷貝,c在 Add 函數調用結束後,隨著棧幀的銷毀,而銷毀。
c的拷貝變量一般開在,調用c所在函數的函數中,此例就是在main函數中開辟,當返回變量較小時,業可能在寄存器中開辟空間存放返回變量的拷貝
傳引用返回:
#include<iostream> using namespace std; int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret=Add(1,2); cout << "ret:" << ret << endl; return 0; }
大傢猜猜結果是什麼呢?
是 3 嗎?
結果是隨機值,這是為什麼呢?
因為返回的是 c 的引用,也就是 c本身,而 c 變量是存儲在棧幀中,隨著函數的結束,棧幀銷毀,c也隨著銷毀,空間釋放,這時就造成非法引用,值為隨機值。
那怎麼辦呢?
不將c放到棧幀中就可以瞭,將c放到 靜態區
#include<iostream> using namespace std; int& Add(int a, int b) { static int c = a + b; return c; } int main() { int& ret=Add(1,2); cout << "ret:" << ret << endl; return 0; }
再來一個有趣的題,下面代碼的結果是什麼呢?
#include<iostream> using namespace std; int& Add(int a,int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(5, 7); cout << ret << endl; return 0; }
很多人會以為是 3 吧
結果是 12 ,可是並沒有輸出 Add(5,7) 。為什麼會是12呢
調用Add(1,2)後,將結果返回ret,ret此時是3,棧幀銷毀,釋放空間,後又調用Add(5,7),重新開辟棧幀,此時開辟的棧幀和上次銷毀的是一個地方。ret還指向上一個c的位置,此時c=5+7;
#include<iostream> using namespace std; int& Add(int a,int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(5, 7); printf("你是真狗\n"); cout << ret << endl; return 0; }
此時輸出是隨機值,是因為,又調用瞭printf函數,占用瞭釋放的空間,ret雖然還指向原來c所在的空間,但是,值已經是隨機值瞭。
2、傳值、傳引用效率比較
以值作為參數或者返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數或者返回值類型,效率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低
#include<iostream> #include<time.h> using namespace std; struct A{ int a[10000]; }; void TestFunc1(A a){} void TestFunc2(A& a){} void TestRefAndValue() { A aa; // 以值作為函數參數 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(aa); size_t end1 = clock(); // 以引用作為函數參數 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(aa); size_t end2 = clock(); // 分別計算兩個函數運行結束後的時間 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; } int main() { TestRefAndValue(); return 0; }
可以看出傳引用的效率,遠勝於傳值
下面傳值返回與傳引用返回比較
#include<iostream> #include<time.h> using namespace std; struct A { int a[10000]; }; A a; // 值返回 A TestFunc1() { return a; } // 引用返回 A& TestFunc2() { return a; } void TestReturnByRefOrValue() { // 以值作為函數的返回值類型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作為函數的返回值類型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 計算兩個函數運算完成之後的時間 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { TestReturnByRefOrValue(); return 0; }
可以看出傳引用返回的效率,遠勝於傳值
所以,可以 傳引用的時候要傳引用,效率更高,但要註意,局部變量不可以傳引用,出瞭函數,棧幀銷毀,就會越界訪問。
傳指針(地址)與傳引用的區別
在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間。
在底層實現上,引用和地址是一樣的,在底層實現上實際是有空間的,因為引用是按照指針方式來實現的。
#include<iostream> #include<time.h> using namespace std; int main() { int a = 10; // 在語法上,這裡給a這塊空間取瞭一個別名,沒有新開空間 int& ra = a; ra = 20; // 在語法上,這裡定義個pa指針變量,開瞭4個字節,存儲a的地址 int* pa = &a; *pa = 20; int b = 10; int*& rpa = pa; rpa = &b; return 0; }
可以看出,引用和指針在匯編實現上是一樣的。那麼他們的效率也是一樣的。
指針和引用的區別:
- 引用在定義時必須初始化,指針沒有要求
- 引用在初始化時引用一個實體後,就不能再引用其他實體而指針可以在任何時候指向任何,一個同類型實體
- 沒有NULL引用,但有NULL指針
- 在sizeof中含義不同:引用結果為引用類型的大小,但指始終是地址空間所占字節個數(32位平臺下占4個字節)
- 引用自加即引用的實體增加1,指針自加即指針向後偏移一個類型的大小
- 有多級指針,但是沒有多級引用
- 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來相對更安全
總結
到此這篇關於C++中傳值、傳地址和傳引用區別的文章就介紹到這瞭,更多相關C++傳值、傳地址和傳引用區別內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!