引用的本質分析

  • 2019 年 10 月 3 日
  • 筆記

1. 引用的定義

C++新增加了引用的概念:

  • 引用可以看作一個已定義變數的別名
  • 引用的語法Type &name = var;
int a = 4;  int &b = a;  //b為a的別名  b = 5;       //操作b就是操作a

2. 引用的本質

  • 引用在C++中的內部實現是一個常量指針Type &name <==> Type *const name
  • C++編譯器在編譯過程中使用常量指針作為引用的內部實現,因此引用所佔用的記憶體大小和指針相同
  • 從使用的角度,引用只是一個別名,C++為了實用性而隱藏了引用的存儲空間這一細節
#include <cstdio>    struct TRef  {      char &r;  };    int main(int argc, char *argv[])  {      char c = 'c';      char &rc = c;      TRef ref = { c };        printf("sizeof(rc) = %dn", sizeof(rc));      printf("sizeof(TRef) = %dn", sizeof(TRef));      printf("sizeof(ref) = %dn", sizeof(ref));      printf("sizeof(ref.r) = %dn", sizeof(ref.r));        /*sizeof(type &)的大小,就是type類型的大小*/      printf("sizeof(char &) = %dn", sizeof(char &));      printf("sizeof(int &) = %dn", sizeof(int &));      printf("sizeof(double &) = %dn", sizeof(double &));        return 0;  }

#include <stdio.h>    struct TRef  {      char *before;      char &ref;      char *after;  };    int main(int argc, char *argv[])  {      char a = 'a';      char &b = a;      char c = 'c';        TRef r = {&a, b, &c};        printf("sizeof(r) = %dn", sizeof(r));      printf("sizeof(r.before) = %dn", sizeof(r.before));      printf("sizeof(r.after) = %dn", sizeof(r.after));      printf("&r.before = %pn", &r.before);      printf("&r.after = %pn", &r.after);        return 0;  }

3. 引用的意義

  • C++中的引用作為變數別名而存在,旨在大多數的情況下代替指針
  • 引用可以滿足絕大多數需要使用指針的場合
  • 引用可以避開由於指針操作不當而帶來的記憶體錯誤
  • 引用相對於指針來說具有更好的可讀性和實用性

注意:由於引用的內部實現為指針,因此函數不能返回非靜態局部變數的引用

#include <stdio.h>    int &demo()  {      int d = 0;        printf("demo: d = %dn", d);        return d;  }    int &func()  {      static int s = 0;        printf("func: s = %dn", s);        return s;  }    int main(int argc, char *argv[])  {      int &rd = demo();      int &rs = func();        printf("n");      printf("main: rd = %dn", rd);      printf("main: rs = %dn", rs);      printf("n");        rd = 10;      rs = 11;        demo();      func();        printf("n");      printf("main: rd = %dn", rd);      printf("main: rs = %dn", rs);      printf("n");        return 0;  }

4. 特殊的引用—const引用

  • 在C++中可以聲明const引用const Type &name = var
  • 可以使用const常量、變數、字面值常量對const引用初始化
  • 不管使用何種方式初始化,const引用都將產生一個只讀變數
  • 當使用字面值常量對const引用初始化時,C++編譯器會為常量值分配記憶體空間,並將引用作為這段記憶體空間的別名

const引用類型 VS 初始化變數類型

  • 類型相同,const引用的就是初始化變數
  • 類型不同,const引用的不是初始化變數,而是初始化變數的臨時對象

注意:const只是修飾符,不代表類型,也就是說,const int和int是相同類型。

#include <stdio.h>    int main()  {      const int a = 3;      int b = 4;      char c = 'c';        const int &ra = a;      const int &rb = b;      const int &rc = c;      const int &rd = 1;        int *p1 = (int *)&ra;      int *p2 = (int *)&rb;      int *p3 = (int *)&rc;      int *p4 = (int *)&rd;        *p1 = 5;      *p2 = 6;      *p3 = 7;      *p4 = 8;        printf("ra = %dn", ra);      printf("rb = %dn", rb);      printf("rc = %dn", rc);      printf("rd = %dn", rd);        printf("n");        printf("b = %dn", b);  //b的類型和rb相同,rb引用的就是b,所以改變rb的值,b也跟著一起改變      printf("c = %cn", c);  //c的類型和rc不同,rb引用的是c的臨時對象,所以改變rc的值,c不受影響        return 0;  }

5. 引用和指針的關係

指針 引用
指針是一個變數,其值為一個記憶體地址 引用是一個變數的新名字
指針可以不初始化,而是在使用時賦值 引用必須在定義時初始化
通過指針可以訪問對應記憶體地址中的值 對引用的操作(賦值、取地址等)會傳遞到代表的變數上
指針可以保存不同的地址 引用在初始化之後無法代表其他變數
指針可以被const修飾,成為常量只讀變數 const引用使其代表的變數具有隻讀屬性

在工程項目開發中

  • 當進行C++編程時,直接站在使用的角度,引用和指針沒有任何關係
  • 當對C++程式碼進行調試分析時,一些特殊情況,可以考慮站在C++編譯器的角度,引用在內部實現為指針常量

我們給出一個站在C++編譯器的角度看待引用的示例,下面這段程式碼有問題嗎?

int a = 1;  int b = 2;  int *pc = new int(3);  int &array[] = {a, b, *pc};
  • 數組是一片連續的記憶體空間
  • 引用數組會破壞該特性,各元素代表的變數可能存儲在不同的位置
  • 因此,C++不支援引用數組!!!!!!
#include <stdio.h>    int a = 1;    struct SV  {      int &x;      int &y;      int &z;  };    int main()  {      int b = 2;      int *c = new int(3);      SV sv = {a, b, *c};      int &array[] = {a, b, *c}; //&array[1] - &array[0] != 4,編譯報錯        printf("&sv.x = %pn", &sv.x);      printf("&sv.y = %pn", &sv.y);      printf("&sv.z = %pn", &sv.z);        delete c;        return 0;  }

首先,注釋掉程式碼第17行,編譯運行結果如下,可以看出列印出的記憶體地址是各不相同的。

然後,去除程式碼第17行注釋,結果編譯報錯,原因就是數組的三個元素地址不連續,而是各不相同。