喵喵喵?喵嘞個咪?---- C++左值引用
編譯環境:VS 2013
一、C 語言傳參
1. 傳值
void swap(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
這個函式很簡單,就是一個交換兩數的函式,我們編譯執行: 咦?我明明通過函式交換了啊,怎麼值還是沒變呢? 原來是因為,僅僅在這個函式內部完成了兩數的交換,呼叫者本身並沒有發生交換,我們可以呼叫記憶體檢視 通過監視和記憶體,我們發現,left 和 right 的值確實交換了 swap 函式執行完畢,我們再來看看 main 函式的 a,b 我們看到了,main 函式內的 a,b 的值並未交換,細心的我們發現,left 的地址和 a 的地址不同,right 的地址和 b 的地址不同,為什麼呢? 因為:
- C 語言,在函式呼叫過程中,會生成一份臨時變數,最終把實參的值傳給臨時變數,即形參是實參的一份臨時拷貝,修改形參的值並不會改變實參的值。 這樣做,避免了函式呼叫的副作用,但也使得無法在函式內部改變實參的值。
- 如果想通過形參改變實參的值,只能通過指標傳遞
2. 傳址
void swap(int *left, int *right)
{
int temp = *left;
*left = *right;
*right = temp;
}
我們編譯執行之: 果然,兩數交換了! 我們再除錯,呼叫監視檢視一下: 我們看到,left 接收的是 a 的地址,解引用就是變數 a,同樣 right 也一樣,這樣就達到了交換兩數的目的。 指標確實可以解決問題
二、引用
1. 引用的概念
引用(reference) 是為物件起了另外一個名字,不是新定義一個變數,而是給已存在變數取了一個別名。 怎樣證明我們沒有定義一個新變數呢? 我們知道,定義變數就要開闢記憶體,如果沒有開闢記憶體,那就沒有定義新變數,我們除錯檢視一下就知道了:
#include <iostream> #include <stdlib.h> int main(void) { int a = 10; int &refa = a; std::cout << a << std::endl; std::cout << refa << std::endl; system("pause"); return 0; }
我們看到,變數 a 和 refa 的地址相同,確實沒有開闢新記憶體!!!
編譯器不會為引用變數開闢記憶體空間,它和它引用的變數共用同一塊記憶體空間
型別& 引用變數名(物件名) = 引用實體;
這裡的型別必須和引用實體是同種型別的。
2. 引用的特性
- 引用在定義時,必須初始化
- 引用一旦引用一個實體(物件),就不能再引用其他實體了 引用相當於繫結,和初始值繫結在一起
- 一個變數可以有多個引用
- 不能定義引用的引用 因為引用本身不是一個物件
3. const 的引用
在說 const 的引用之前,我們先來區別一下 C 語言和 C++ 的 const 限定符的不同。 我們說如果不希望某個變數的值被修改,可以加 const 限定符限定。 在 C 語言中:
- const 修飾的常變數不可以定義陣列 C 中常變數和變數的唯一區別是常變數不能做左值 左值:既可以出現在賦值符(等號)左邊作為可以被修改的變數(或表示式),也可以出現在等號右邊的變數(或表示式) 右值:只能出現在等號右邊的變數(或表示式)
- 在 C 中可以通過指標修改 const 常變數:
int b = 20;
const int a = 10;
int *p = &a;
*p = 20;
- const 修飾指標
const int * p; // p 所指向的變數不能被修改,p可以修改
int const * p; // p 所指向的變數不能被修改,p可以修改
int * const p; // p 所指向的變數可以被修改,p 不能被修改指向
const int * const p; // p 自身和所指向的變數都不能被修改
const 修飾的是離自己最近的變數
在 C++ 中
- 常變數可以定義陣列,可以作為陣列下標。
- const 修飾一個變數,這個變數就是一個常量,不能被修改,指標也不行
const int * p; // p 所指向的變數不能被修改,p可以修改
int const * p; // p 所指向的變數不能被修改,p可以修改
int * const p; // p 只能指向一個可以被修改變數,但 p 不能修改指向
const int * const p; // p 自身和所指向的變數都不能被修改
這一點和 C 語言貌似一樣
const 的引用
- 可以把引用繫結到 const 物件上,常量的引用
const int a = 1024;
const int &refa = a;
這裡的 refa 是 a 的一個引用,同樣 refa 不能被修改- 不能讓一個非常量引用指向一個常量物件 這也再次說明引用的型別必須與其所引用物件的型別一致 但有兩個例外:
- 在初始化常量引用時允許用任意表達式作為初始值,只要該表示式的結構能轉換成引用的型別即可。
int i = 10;
const int &r1 = i; // 允許將 const int& 繫結到普通 int 物件上
const int &r2 = 10; // r2 是一個常量引用
const int &r3 = r1 * 2; // r3 是一個常量引用
我們再做個試驗:double dval = 3.14;
const int ri = dval;
std::cout << ri << std::endl;
這段程式碼的執行結果是 3 ,資料丟失了
- 允許一個常量引用繫結非常量物件、字面值,甚至一般表示式
4. 陣列引用
int a[5] = { 0, 1, 2, 3, 4 };
int(&b)[5] = a;
陣列是可以引用的
但是
int &b[5] = a;
這種寫法是不對的!!!!!!
編譯器報錯是:
error C2440: “初始化”: 無法從“int [5]”轉換為“int *[5]”
5. 引用的使用場景
- 作為函式形參
#include <iostream>
#include <stdlib.h>
void swap(int &left, int &right)
{
int temp = left;
left = right;
right = temp;
}
int main(void)
{
int a = 10;
int b = 20;
std::cout << "a = " << a << " " << "b = " << b << std::endl;
swap(a, b);
std::cout << "a = " << a << " " << "b = " << b << std::endl;
system("pause");
return 0;
}
編譯執行: 果然達到了交換的目的
- 作為函式返回值
#include <iostream>
#include <stdlib.h>
int& TestRefReturn(int &a)
{
a += 10;
return a;
}
int main(void)
{
int a = 10;
std::cout << "a = " << a << std::endl;
TestRefReturn(a);
std::cout << "a = " << a << std::endl;
system("pause");
return 0;
}
編譯執行: 我們再來看看下面這段程式碼輸出什麼:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main(void)
{
int &ret = Add(10, 20);
Add(100, 200);
std::cout << "ret = " << ret << std::endl;
system("pause");
return 0;
}
30?我們編譯執行: 300?出乎意料!!! 為什麼呢? 因為我們返回了棧空間上的引用!!!
6. 引用和指標的區別
其實我們發現,這個引用和指標貌似很像,接下來,我們來看看他們有哪些相同點,又有那些不同點:
-
相同點 底層實現方式相同,都是按照指標的方式實現的
-
不同點
引用 | 指標 |
---|---|
在定義時必須初始化 | 沒有要求 |
初始化之後,不能再指向其他物件 | 可以在任何時候指向任何一個同類型物件 |
沒有 NULL 引用 | 有 NULL 指標 |
sizeof 求得的大小為引用型別的大小 | 32位系統 4 個位元組 |
引用自加改變變數內容 | 指標自加改變了指標的指向 |
沒有引用的引用 | 但有指標的指標 |
編譯器定址 | 手動定址 |
引用比指標相對安全