天天看點

原來引用還有這麼多不為人知的秘密呢?

原來引用還有這麼多不為人知的秘密呢?
你好,我是安然無虞。

文章目錄

  • ​​學習網站​​
  • ​​寫在前面​​
  • ​​引用概念​​
  • ​​引用特性​​
  • ​​常引用​​
  • ​​引用使用場景​​
  • ​​· 做參數​​
  • ​​· 做傳回值​​
  • ​​傳值、傳引用效率比較​​
  • ​​引用和指針的差別​​
  • ​​大廠面試真題​​

學習網站

推薦給老鐵們兩款學習網站:

面試利器&算法學習:​牛客網​ 風趣幽默的學人工智能:人工智能學習 首個付費專欄:《C++入門核心技術》

寫在前面

前面我們有講解函數重載相關的知識,因為比較重要,是以講的比較詳細,下面還有一個很重要的知識點——引用,雖然很重要但是不難哦,不信你看看就知道了。

引用概念

引用不是新定義一個變量,而是給已存在變量取一個别名,編譯器不會為引用變量開辟記憶體空間,它和它引用的變量共用同一塊記憶體空間。

比如:我們知道,在水浒傳中有一個好漢名叫李逵,他在家呢叫“鐵牛”,江湖人稱“黑旋風”。

有點小可愛哦。

原來引用還有這麼多不為人知的秘密呢?

基本結構:

類型& 引用變量名 = 引用實體;

注意哦,引用類型必須是和引用實體是同種類型的。

看下面一段代碼:

#include<iostream>
using std::cout;
using std::endl;

int main()
{
  int a = 10;
  int& b = a;//b是a的引用(别名)
  int& c = a;//c是a的引用(别名)
  int& d = b;//d是b的引用(别名)

  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
  cout << "d = " << d << endl;
  return 0;
}      

前面引用的概念已經說了,引用不是新定義一個變量,而是給已存在變量取了一個别名,編譯器不會為引用變量開辟記憶體空間,它和它引用的變量共用同一塊空間。

這樣的話也就是說,變量a、b、c、d共用同一塊空間。

原來引用還有這麼多不為人知的秘密呢?

調試代碼驗證一下上面的說法,看四個變量的位址:

原來引用還有這麼多不為人知的秘密呢?

很明顯位址一樣,是以說,引用不是新定義一個變量,而是給已存在的變量取一個别名,編譯器不會為引用變量開辟記憶體空間,它和它引用的變量共用同一塊記憶體空間。

引用特性

  • 引用在定義時必須初始化;
  • 一個變量可以有多個引用;
  • 引用一旦引用一個實體,不能再引用其他實體。

對上述三個特性一一說明:

1.引用在定義時必須初始化

void Test1()
{
  int a = 10;
  int& ra; //這條語句編譯時會出錯
  int& rb = a; //引用在定義時必須初始化
}      

2.一個變量可以有多個引用

void Test2()
{
  int a = 10;
  int& ra = a; 
  int& rb = a; 
}      

3.引用一旦引用一個實體,再不能引用其他實體(注意哦,這點跟指針不同)

void Test3()
{
  int a = 10;
  int b = 20;
  int& ra = a;
  int& ra = b;//編譯錯誤
}      

常引用

開始之前呢,先說一下取别名的原則:

對原引用變量,讀寫權限要麼不變,要麼縮小,但是不能放大。

原來引用還有這麼多不為人知的秘密呢?
void TestConstRef()
{
  const int a = 10;//常變量,隻讀不可寫
  //int& ra = a;編譯報錯,a->ra 權限放大(a隻讀,ra可讀可寫)
  const int& ra = a;//a->ra 權限不變

  //int& b = 10;編譯報錯,常量10->a 權限放大
  const int& b = 10;//10->b 權限不變

  double d = 3.14;
  //int& rd = d;編譯出錯,double->int 類型不同
  const int& rd = d;//編譯通過,想想為什麼?
}      

下面這段代碼:

double d = 3.14;
//int& rd = d;編譯出錯,double->int 類型不同
const int& rd = d;//編譯通過,想想為什麼?      

為什麼加上const就可以了呢?

不着急,先說說之前學習過的知識。我們在學習C語言的時候知道不同類型的值互相指派時會發生隐式類型的轉換,比如:

double d = 3.14;
int f = d;      
原來引用還有這麼多不為人知的秘密呢?

很明顯,發生隐式類型轉換時中間會産生一個臨時變量,而臨時變量具有常性。

知道這一點上之後,就好解釋這段代碼了:

double d = 3.14;
//int& rd = d;編譯出錯,double->int 類型不同
const int& rd = d;//編譯通過,想想為什麼?      
原來引用還有這麼多不為人知的秘密呢?

上面有說到rd不是d的别名,而是臨時變量的别名,下面我們調試驗證:

原來引用還有這麼多不為人知的秘密呢?

引用使用場景

· 做參數

之前我們寫交換函數是這樣寫的:

void Swap(int* left, int* right)
{
  int temp = *left;
  *left = *right;
  *right = temp;
}      

現在我們學習引用後,大可不必像之前那樣:

void swap(int& left, int& right)
{
  int temp = left;
  left = right;
  right = temp;
}      

使用引用傳參的好處:

  1. 無需解引用,簡單多了;
  2. 使用引用傳參更直覺,更好了解。

· 做傳回值

我們知道,傳值傳回時會有一個拷貝,而傳引用傳回就沒有這個拷貝,傳回的直接就是變量的别名,這樣可以減少拷貝,提高效率。

思考下面一段代碼:

int& Count()
{
  static int n = 0;//注意靜态變量的特點:隻會初始化一次
  n++;
  return n;
}

int main()
{
  int& ret = Count();
  return 0;
}      
原來引用還有這麼多不為人知的秘密呢?

傳引用傳回的是傳回值的别名,傳值傳回的是那個臨時變量。

是以這裡傳回的是n的别名,為了證明這個說法,隻需要驗證ret 和 n的位址一樣,意味着ret 就是 n 的别名。

好,下面進行調試驗證:

原來引用還有這麼多不為人知的秘密呢?

故而證明上述結論:

傳引用傳回的是傳回值的别名,傳值傳回的是那個臨時變量。

好的,現在将代碼改動一點:

int Count()
{
  static int n = 0;
  n++;
  return n;
}

int main()
{
  int ret = Count();
  return 0;
}      
原來引用還有這麼多不為人知的秘密呢?

我們知道,傳值傳回的是那個臨時變量,那麼如何證明産生臨時變量了呢?很簡單,不信你看:

int& ret = Count();//報錯      

其實不難,不過需要你細品。

再看下面一段代碼,問輸出結果是什麼?為什麼?

int& Add(int a, int b)
{
  int c = a + b;
  return c;
}

int main()
{
  int& ret = Add(1, 2);
  Add(3, 4);
  cout << "Add(1, 2) is :" << ret << endl;
  return 0;
}      

其實這裡的 Add 函數是錯誤的,因為 c 是局部變量,出了作用域就被銷毀了,是以不能使用引用傳參,會導緻野指針。

原來引用還有這麼多不為人知的秘密呢?

注意:函數傳回時,出了函數作用域,如果傳回對象還未還給系統,則可以使用引用傳回,如果已經還給系統了,則必須使用傳值傳回。

傳值、傳引用效率比較

以值作為參數或者傳回值類型,在傳參和傳回期間,函數不會直接傳遞實參或者将變量本身直接傳回,而是傳遞實參或者傳回變量的一份臨時拷貝,是以用值作為參數或者傳回值類型,效率是非常低下的,尤其是當參數或者傳回值類型非常大時,效率就更低。

引用和指針的差別

在文法概念上引用就是一個别名,沒有獨立的空間,和其引用的實體共用同一塊空間。

int a = 10;
//文法角度而言,ra是a的别名,沒有額外開空間
int& ra = a;
//文法角度而言,pa存儲a的位址,pa開了4/8個位元組的空間
int* pa = &a;      

不過在底層實作上其實是有空間的,因為引用是按照指針方式來實作的。看彙編代碼即可證明:

int a = 10;

int& ra = a;
ra = 20;

int* pa = &a;
*pa = 20;      

我們來看一下引用和指針的彙編代碼對比:

原來引用還有這麼多不為人知的秘密呢?

看到了嗎,一模模一樣!

總結引用和指針的不同點:

  1. 引用在定義時必須初始化,指針沒有要求;
  2. 引用在初始化時引用一個實體後,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型的實體;
  3. 沒有NULL引用,但有NULL指針;
  4. 在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是位址空間所占位元組個數(32位平台占4個位元組,64位平台占8個位元組);
  5. 引用自加即引用的實體增加1,指針自加即指針向後偏移一個類型的大小;
  6. 有多級指針,但是沒有多級引用;
  7. 通路實體方式不同,指針需要顯示解引用,引用是編譯器自己處理;
  8. 引用比指針使用起來相對更加安全。

上面這8點,不要死記硬背,要了解性記憶哦!

大廠面試真題

1、關于引用以下說法錯誤的是( )。(阿裡)

A.引用必須初始化,指針不必

B.引用初始化以後不能被改變,指針可以改變所指的對象

C.不存在指向空值的引用,但是存在指向空值的指針

D.一個引用可以看作是某個變量的一個“别名”

E.引用傳值,指針傳位址

F.函數參數可以聲明為引用或指針類型

解析:引用表面好像是傳值,其本質也是傳位址,隻是這個工作有編譯器來做,是以E錯。

2、引用”與指針的差別是什麼( )

A.指針通過某個指針變量指向一個對象後,對它所指向的變量間接操作。程式中使用指針,程式的可讀性差;而引用本身就是目标變量的别名,對引用的操作就是對目标變量的操作

B.引用通過某個引用變量指向一個對象後,對它所指向的變量間接操作。程式中使用引用,程式的可讀性差;而指針本身就是目标變量的别名,對指針的操作就是對目标變量的操作

C.指針比引用更節省存儲空間

D.以上都不正确

解析:指針是間接操作對象,引用時對象的别名,對别名的操作就是對真實對象的直接操作;指針需要開辟空間,引用不需要開辟空間。

3、關于引用與指針的差別,下面叙述錯誤的是( )

A.引用必須被初始化,指針不必

B.指針初始化以後不能被改變,引用可以改變所指的對象

C.删除空指針是無害的,不能删除引用

D.不存在指向空值的引用,但是存在指向空值的指針

解析:空指針沒有任何指向,删除無害,引用是别名,删除引用就删除真實對象。

繼續閱讀