c++筆記_引用

學C++的小萌新發表於2020-11-29


前言

複合型別(compound type)是指基於其他型別定義的型別。


一、引用是什麼?

引用(reference)為物件起了另外一個名字,引用型別引用(refers to)另外一種型別。通過將宣告符寫成&d的形式來定義引用型別,其中d是宣告的變數名:

程式碼如下(示例):

int ival = 1024;
int &refVal = ival;			//refVal指向ival(refVal是ival的另一個名字)
int &refVal2;				//錯誤:引用必須被初始化

在初始化變數中時,初始值會被拷貝到新建的物件中。然而定義時,程式把引用和它的初始值繫結在一起,而不是將初始值拷貝給引用。所以一旦初始化完成,引用將和它的初始值物件一直繫結在一起。因為無法令引用重新繫結到另外一個物件,因此引用必須初始化。

ps:C++11中新增了一種引用:所謂的“右值引用”,這種引用主要用於內建類。嚴格來說當我們使用術語“引用”時,指的其實是“左值引用”。

1.引用即別名

定義了一個引用之後,對其進行的所有操作都是在與之繫結的物件上進行的:

#include<iostream>
using namespace std;
int main(void)
{
	int ival = 1024;
	int &refVal = ival;	
	refVal = 2;				//把2賦給refVal指向的物件,此處是給了ival
	int li = refVal;		//與li = ival執行結果一樣(li = 2)
	
	cout<<"ival="<<ival<<endl;
    cout<<"refVal="<<refVal<<endl;
    cout<<"li="<<li<<endl;
    
    cout<<"&ival="<<&ival<<endl;		//列印變數ival的記憶體地址
	cout<<"&refVal="<<&refVal<<endl;	//列印引用refVal的記憶體地址
	
	return 0;
}

程式碼輸出:

ival=2
refVal=2
li=2
&ival=0x7ffeefbff3f4		//ival和refVal是同一塊記憶體地址,所以可以理解ival完全等價與refVal
&refVal=0x7ffeefbff3f4

所以可以理解成引用就是為一個已經存在的物件起的另外一個名字。

二、引用的定義

允許在一條語句中定義多個引用,其中每個引用識別符號都必須以符號&開頭:

int i = 1024, i2 = 2048;		//i和i2都是int
int &r = i, r2 = i2;			//r是一個引用,與i繫結在一起,r2是int
int i3 = 1024, &ri = i3;		//i3是int,ri是一個引用,與i3繫結在一起
int &r3 = i3, &r4 = i2;			//r3和r4都是引用

大部分情況下引用都型別都要和與之繫結的物件嚴格匹配。而且,引用只能繫結在物件上,而不能與字面值或某個表示式的計算結果繫結在一起。

int &refVal4 = 10;			//錯誤:引用型別的初始值必須是一個物件
double dval = 3.14;			
int &refVal5 = dval;		//錯誤:引用的型別的初始值必須是int物件

三、用引用給陣列取個名字

當然也可以用引用給陣列取個別名,具體方法如下:

#include<iostream>
using namespace std;
int main(void)
{
	int arr[5] = {1,2,3,4,5};
	//目的:給陣列arr取個別名
	int (&my_arr)[5] = arr;		//my_arr就是陣列arr的別名
	
	//列印陣列arr元素
	for(int i=0;i<5;i++)
		cout<<arr[i]<<endl;
	
	cout<<"-------"<<endl;
	
	//列印陣列my_arr元素
	for(int b=0;b<5;b++)
		cout<<my_arr[b]<<endl;
	
	cout<<"陣列arr的記憶體地址:"<<&arr<<endl;
	cout<<"陣列my_arr的記憶體地址:"<<&my_arr<<endl;	

	return 0;
}

程式碼輸出結果:

1
2
3
4
5
-------
1
2
3
4
5
陣列arr的記憶體地址:0x7ffeefbff480
陣列my_arr的記憶體地址:0x7ffeefbff480

四、配合typedef使用引用

首先簡單介紹下typedef是什麼?就是為型別建立個新的名字。

某些場景下,我們也可以配合typedef來使用引用,具體方法如下:

#include<iostream>
using namespace std;
int main(void)
{
	int arr[5] = {1,2,3,4,5};
	
	typedef int TYPE_ARR[5];	//TYPE_ARR就是一個陣列型別(有5個int型別元素)
	
	//用typedef給陣列型別取個別名
	TYPE_ARR &myarr = arr;		//myarr就是陣列arr的別名
	
	for(int i=0;i<5;++i)
		cout<<myarr[i]<<endl;
		
	return 0;
}

四、引用作為函式引數

引用也可以用作函式引數。假設,我們需要交換a和b兩個數的位置,在不用引用的情況下會輸出什麼呢?

#include<iostream>
using namespace std;
//不使用引用進行交換
void swap(int a,int b)
{
	int tmp;		//定義變數tmp用於存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
int main(void)
{
	int a=1;
	int b=2;
	swap(a,b);
	cout<<"a="<<a<<" "<<"b="<<b<<endl;
		
	return 0;
}

程式碼輸出:

a=1 b=2

從程式碼輸出結果來看,a和b還是原來的引數,並沒有交換到。那麼我們換做引用的方式處理看看結果會發生什麼?

#include<iostream>
using namespace std;
//函式引數不使用引用
void swap(int a,int b)
{
	int tmp;		//定義變數tmp用於存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
//函式引數使用引用
void refSwap(int &a,int &b)
{
	int tmp;		//定義變數tmp用於存放a的值
	tmp = a;
	a = b;
	b=tmp;
}

int main(void)
{
	int a=1;
	int b=2;
	refSwap(a,b);
	cout<<"a="<<a<<" "<<"b="<<b<<endl;
		
	return 0;
}

程式碼輸出:

a=2 b=1

使用引用的方法進行交換,看到程式碼結果a和b已經互換的位置,因此可以總結出,如果需要操作(改變)物件本身,那麼需用引用處理,當然也可以用指標的方式進行處理,但是書寫對比引用會相對麻煩,所以一般都是引用。不信可以對比看看。

#include<iostream>
using namespace std;
//函式引數不使用引用
void swap(int a,int b)
{
	int tmp;		//定義變數tmp用於存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
//函式引數使用引用
void refSwap(int &a,int &b)
{
	int tmp;		//定義變數tmp用於存放a的值
	tmp = a;
	a = b;
	b=tmp;
}
//函式引數使用指標
void ptrSwap(int *a,int *b)
{
	int tmp=*a;
	*a = *b;
	*b=tmp;
}
int main(void)
{
	int a=1;
	int b=2;
	ptrSwap(&a,&b);
	cout<<"a="<<a<<" "<<"b="<<b<<endl;
		
	return 0;
}

程式碼輸出:

a=2 b=1

從上述的程式碼看出,引用和指標傳參的方式都能使a和b資料進行交換,但是從書寫上來看指標會相對複雜且程式碼難理解,因此建議使用引用的方式對物件本身進行處理。


五、引用作為函式返回值

如上所說引用可以用作函式引數,那麼引用也可以作為函式返回值。

不過注意的是引用作為函式返回值,不能返回區域性變數的引用,比如:

#include<iostream>
using namespace std;
//引用作為函式返回值型別
int &data(void)
{
	int num = 100;		//num數區域性變數
	return num;			//函式返回什麼變數,那麼引用的就是該變數的別名
}
int main(void)
{
	int &ret = data();	//ret是函式data()中變數num的別名
	cout<<"ret="<<ret<<endl;
}

程式碼輸出:

ret=32767

從程式碼輸出結果來看返回的是值跟預期結果的值不一致,因此可以知道如果返回的是區域性變數的引用,那麼編譯器會把返回的結果處理成一個隨機值,主要原因是因為區域性變數會在返回的過程中進行銷燬,從而導致區域性變數丟失。

那麼如何解決這個問題?通常我們可以使用靜態變數,在變數前加上關鍵字static。

#include<iostream>
using namespace std;
//引用作為函式返回值型別
int &data(void)
{
	static int num = 100;		//變數num前加上關鍵字statci,使num變成靜態變數
	return num;			
}
int main(void)
{
	int &ret = data();	//ret是函式data()中變數num的別名
	cout<<"ret="<<ret<<endl;
}

程式碼輸出:

ret=100

這時候程式碼輸出結果就跟預期一致啦,當然我們也可以把變數num定義成全域性變數,從而達到正確結果。

ps:補充如果函式返回值作為左值,那麼函式到返回型別必須是引用。

#include<iostream>
using namespace std;
//函式到返回值作為左值
int &data(void)
{
	static int num = 100;	
	return num;			
}
int main(void)
{	
	int num1=data();
	int num2=data()=200;
	cout<<"num1="<<num1<<endl;
	cout<<"num2="<<num2<<endl;
	return 0;
}

程式碼輸出:

num1=100
num2=200

那麼如果函式返回值用作左值,但是函式返回型別不是引用的話,函式返回值將無法用作左值,編譯器會報錯。

六、引用的本質

引用的本質在C++內部實現是一個指標常量,C++編譯器在編譯過程中使用常指標作為引用的內部實現,因此引用所佔的空間大小和指標相同,只是這個過程編譯器內部實現,使用者不可見。

int num = 10;
int &a = num;		//a就是num的別名
//編譯器記憶體轉換:int* const a = &num;

a=100;				//等價與num=100

總結

1:引用是什麼?->引用(reference)為物件起了另外一個名字。
2:引用怎麼定義?->使用操作符&。
3:引用定義的注意事項:引用必須進行初始化;引用一旦初始化就不能修改別名。
4:引用作為函式返回值需要注意什麼?->不能返回區域性變數的引用。
5:使用什麼方法可以解決返回變數的引用?->使用靜態變數在資料型別前加上關鍵字static或定義全域性變數。
6:引用用作函式引數有什麼效果?->操作物件本身而不會產生副本,從而提升程式的時間效率。
7:還有個常引用後面複習到有關類的知識點時在補充(const tt &b)。

相關文章