1. 程式人生 > >喵喵喵?喵嘞個咪?---- C++左值引用

喵喵喵?喵嘞個咪?---- 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 也一樣,這樣就達到了交換兩數的目的。 指標確實可以解決問題

在 C++ 中我們採用另一種方式,也可以解決這個問題,那就是左值引用

二、引用

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 不能被修改
  • 不能讓一個非常量引用指向一個常量物件 這也再次說明引用的型別必須與其所引用物件的型別一致 但有兩個例外
  1. 在初始化常量引用時允許用任意表達式作為初始值,只要該表示式的結構能轉換成引用的型別即可。 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 個位元組
引用自加改變變數內容 指標自加改變了指標的指向
沒有引用的引用 但有指標的指標
編譯器定址 手動定址

引用比指標相對安全