C++對C語言的擴充套件(1)--引用

空中北斗星發表於2021-01-05

1 引用

1.1 變數名

  1. 變數名實質上是一段連續儲存空間的別名,是一個標號
  2. 通過變數名來申請並命名記憶體空間
  3. 通過變數的名字可以使用記憶體空間

1.2 引用的概念

  1. 變數名,本身是一段記憶體的引用,即別名(alias)。引用可以看作一個已定義變數的別名。

  2. 引用的語法:Type &name = var;
    用法如下:

int a = 10;		// 編譯器分配4個位元組的記憶體空間,a為此記憶體空間的別名,可以通過a來修改記憶體空間的值
int &b = a;		// b為a的引用,即b為a的別名,同樣可以通過b來修改記憶體空間的值

1.3 規則

  • 引用沒有定義,是一種關係型宣告。宣告它和原有某一變數(實體)的關係。故而型別與原型別保持一致,且不分配記憶體。與被引用的變數有相同的地址。
  • 宣告的時候必須初始化,一經宣告,不可變更。
  • 可對引用,再次引用。多次引用的結果,是某一變數具有多個別名。
  • &符號前有資料型別時,是引用。其它皆為取地址。
int a, b;
int &r = a;		// 正確,變數與引⽤用具有相通的地址
int &r = b;		// 錯誤,不可以修改原有的引用關係
float &rr = b;	// 錯誤,引用型別不匹配
int &ra = r;	// 正確,可以對引用再次引用,此時a有兩個別名r和ra

1.4 引用作為函式引數

普通引用在宣告時必須用其它的變數進行初始化,引用作為函式引數宣告時不進行初始化。

void test01(int &i)	// 形參為引用,相當於對傳入形參起別名
{
	r = 100;	// 相當於修改了主函式中a為100
	return;
}
void test02(int j)
{
	j = 1000;
}
int main()
{
	int a = 10;
	cout << a << endl;	// a = 10
	test01(a);	// 當傳a進去時,i為a的別名,i與a為相同的記憶體地址
	cout << a << endl;	// a = 100;
	test02(a);	
	cout << a << endl;	// a = 100;
}

1.5 引用的意義

  • 引用作為其它變數的別名而存在,因此在一些場合可以代替指標
  • 引用相對於指標來說具有更好的可讀性和實用性

1.6 引用的本質

  • 引用在C++中的內部實現是一個常指標

Type& name <===> Type const name*

  • C++編譯器在編譯過程中使用常指標作為引用的內部實現,因此引用所佔用的空間大小與指標相同
  • 從使用的角度,引用會讓人誤會其只是一個別名,沒有自己的儲存空間。這是C++為了實用性而做出的細節隱藏。

間接賦值的3各必要條件

  1. 定義兩個變數 (一個實參一個形參)
  2. 建立關聯 實參取地址傳給形參
  3. *p形參去間接的修改實參的值

引用在實現上,只不過是把:間接賦值成立的三個條件的後兩步和二為一.
當實參傳給形參引用的時候,只不過是c++編譯器幫我們程式設計師手工取了
一個實參地址,傳給了形參引用(常量指標) 。

1.7 引用作為函式的返回值

1.7.1 若返回棧變數的引用

不能成為其它引用的初始值(不能作為左值使用)
儘量不要返回棧變數作引用

#include <iostream>
using namespace std;

int getA1()
{
    int a;
    a = 10; 
    return a;
}

int& getA2()
{
    int a;
    a = 10; 
    return a;
}

int main()
{
    int a1 = 0;
    int a2 = 0;

    // 值拷貝
    a1 = getA1();

    // 將一個引用賦給一個變數,會有拷貝動作
    // 理解:編譯器類似做了如下隱藏操作,a2 = *(getA2());
    a2 = getA2();

    // 將一個引用賦給另一個引用作為初始值,由於是棧記憶體的引用(函式getA2中變數a為棧記憶體,出了作用域就棧記憶體就釋放了),記憶體非法
    //int &a3 = getA2();
      
    cout << "a1 = " << a1 << endl;
    cout << "a2 = " << a2 << endl;
//    cout << "a3 = " << a3 << endl;

	return 0;
}

1.7.2 若返回靜態變數或全域性變數引用

可以成為其他引用的初始值(可作為右值使用,也可作為左值使用)

#include <iostream>
using namespace std;

int getA1()
{
    int a;
    static a = 10; 
    return a;
}

int& getA2()
{
    int a;
    static a = 10; 
    return a;
}

int main()
{
    int a1 = 0;
    int a2 = 0;

    // 值拷貝
    a1 = getA1();

    // 將一個引用賦給一個變數,會有拷貝動作
    // 理解:編譯器類似做了如下隱藏操作,a2 = *(getA2());
    a2 = getA2();

    // 將一個引用賦給另一個引用作為初始值,由於是靜態區域,記憶體合法
    int &a3 = getA2();
 
    cout << "a1 = " << a1 << endl;
    cout << "a2 = " << a2 << endl;
    cout << "a3 = " << a3 << endl;

	return 0;
}
  1. 如果返回值為引用可以當左值。(返回變數本身)(全域性/靜態變數)
  2. 如果返回值為普通變數不可以當左值。(返回變數的值)
#include <iostream>
using namespace std;

// 函式當左值
// 返回變數的值
int func1()
{
    int a;
    static a = 10; 
    return a;
}

// 返回變數本身
int& func2()
{
    int a;
    static a = 10; 
    return a;
}

int main()
{
    int a1 = 0;
    int a2 = 0;

    // 函式當右值
    a1 = func1();
	cout << "a1 = " << a1 << endl;	// a1 = 10
	
	// 函式返回值是一個引用,並且當右值
    a2 = func2();
	cout << "a2 = " << a2 << endl;	// a2 = 10
	
	// 函式當左值
	//func1 = 100;	// 錯誤
	func2 = 100;	// 函式返回值是⼀一個引⽤用,並且當左值
	
	int a3 = func2();
	cout << "a3 = " << a3 << endl;	// a3 = 100
	
	return 0;
}

1.8 陣列的引用

//1.直接建立引用
int arr[10];
int(&pArr)[10] = arr;
// 使用
pArr[0] = 1;	// 相當於arr[0] = 1;
----------------------------------------------------------------
//2.先定義出陣列型別,再通過型別 定義引用
int arr[10];
typedef int(ARRAY_TYPE)[10];	// 建立出10個int型別的陣列,叫ARRAY_TYPE,即ARRAY_TYPE為10個int型別的陣列
ARRAY_TYPE & pArr2 = arr;
// 使用
pArr2[0] = 1;	// 相當於arr[0] = 1;

1.9 指標的引用

#include <iostream>
using namespace std;

struct Person
{
    string name;
    int age;
};

void getPerson01(Person **p)
{
    (*p)->name = "張三";
    (*p)->age  = 18; 
    return;
}

void getPerson02(Person *&p)
{
    p->name = "李四";
    p->age  = 20; 
    return;
}

int main()
{
    Person *pP = new Person;

	//1	c語⾔言中的⼆二級指標
    getPerson01(&pP);
    cout << "姓名:" << pP->name << " 年齡:" << pP->age << endl;

	//2	c++中的引⽤用 (指標的引⽤用)
	//引⽤用的本質 間接賦值後2個條件 讓c++編譯器幫我們程式設計師做了
    getPerson02(pP);
    cout << "姓名:" << pP->name << " 年齡:" << pP->age << endl;

    delete pP; 
    return 0;
}

利用引用可以簡化指標
可以直接用同級指標的引用,給同級指標分配空間

1.10 const 引用

const 引用有較多使用。它可以防止物件的值被隨意修改。因而具有一些特性。

1.const 物件的引用必須是const的,將普通引用繫結到const物件是不合法的。

const int a =1;	// const變數
int &b = a;		// 普通引用b繫結到const物件不合法
------------------------------------------------------------
int x = 1;
const int &y = a;	// 合法,常引⽤用是限制變數為只讀 不能通過y去修改x了
// y = 2;			// 錯誤
  1. const 引用可使用相關型別的物件(常量,非同型別的變數或表示式)初始化。這個是const 引用與普通引用最大的區別。
const int a =1;	// const變數
const int &b = a;	// 合法
------------------------------------------------------------
double x = 3.14;
const int &y = x;	// 合法  y = 3;

1.11 常量的引用

const int &ref = 10;
// 加了const之後,相當於寫成 int temp = 10;  const int &ref = temp;

//常量引用的使用場景:修飾函式中的形參,防止誤操作
void test(const int &a)
{
	a = 10;	// 錯誤
}

1.11 const 引用的原理

const 引用的目的是,禁止通過修改引用值來改變被引用的物件。
const 引用的初始化特性較為微妙,可通過如下程式碼說明:

double val = 3.14;
const int &ref = val;	// 使用相關型別的物件初始  相當於 int temp = val; const int &ref = temp;
double &ref2 = val;
cout << ref << " " << ref2 << endl;
val = 4.14;
cout << ref << " " << ref2 << endl;

上述輸出結果為3 3.14和3 4.14。因為ref是const的,在初始化的過程中已經給定值,不允許修改.而被引用的物件是val,是非const的,所以val的修改並未影響ref的值,而ref2的值發生了相應的改變。

為什麼非const的引用不能使用相關型別(常量,非同型別的變數或表示式)初始化呢???
實際上,const 引用使用相關型別物件初始化時發生瞭如下過程:

 int temp = val; 
 const int &ref = temp; 

如果 ref 不是const的,那麼改變ref 值,修改的是 temp,而不是 val。期望對ref的賦值會修改val的程式設計師會發現val實際並未修改。
因此不允許使用相關型別初始化非const引用

// 1.⽤用變數 初始化 常引⽤用
int x1 = 30;
const int &y1 = x1;	// 用x1變數初始化  常引用

// 2.用字面量初始 常量引用
const int a = 40;	// c++編譯器把a放在符號表中

// int &m = 41;		// 錯誤
const int &m = 42;	// c++ 會分配記憶體空間
					// 相當於 int temp = 42; const int &m = temp;

1.12 引用的注意事項

  1. 引用必須引一塊合法記憶體空間
  2. 不要返回區域性變數的引用
  3. 當函式返回值是引用時候,那麼函式的呼叫可以作為左值進行運算

結論:

  1. const int & e 相當於 const int * const e
  2. 普通引用 相當於 int *const e
  3. 當使用常量(字面量)對const引用進行初始化時,C++編譯器會為常量值分配空間,並將引用名作為這段空間的別名
  4. 使用字面量對const引用初始化後,將生成一個只讀變數

相關文章