C/C++引用和指標的聯絡和區別

巴斯比男孩發表於2020-12-10

原文連結

為什麼C/C++語言使用指標?

1. 一方面,每一種程式語言都使用指標。不止C/C++使用指標。
每一種程式語言都使用指標。C++將指標暴露給了使用者(程式設計師),而Java和C#等語言則將指標隱藏起來了。

1. 另一方面使用指標的優點和必要性:

  • 指標能夠有效的表示資料結構
  • 能動態分配記憶體,實現記憶體的自由管理
  • 能較方便的使用字串
  • 便捷高效地使用陣列
  • 指標直接與資料的儲存地址有關,比如:值傳遞不如地址傳遞高效,因為值傳遞先從實參的地址中取出值,再賦值給形參代入函式計算;而指標則把形參的地址直接指向實參地址,使用時直接取出資料,效率提高,特別在頻繁賦值等情況下(注意:形參的改變會影響實參的值!)

引用和指標的區別

c++ primer plus中給引用的定義是:引用是已定義的變數的別名(另一個名稱)
本質:引用是別名,指標是地址,具體的:

  • 從現象上看,指標在執行時可以改變其所指向的值,而引用一旦和某個物件繫結後就不再改變。這句話可以理解為:指標可以被重新賦值以指向另一個不同的物件。但是引用則總是指向在初始化時被指定的物件,以後不能改變,但是指定的物件其內容可以改變。
  • 從記憶體分配上看,程式為指標變數分配記憶體區域,而不為引用分配記憶體區域,因為引用宣告時必須初始化,從而指向一個已經存在的物件。引用不能指向空值。
  • 從編譯上看,程式在編譯時分別將指標和引用新增到符號表上,符號表上記錄的是變數名及變數所對應地址。指標變數在符號表上對應的地址值為指標變數的地址值,而引用在符號表上對應的地址值為引用物件的地址值。符號表生成後就不會再改,因此指標可以改變指向的物件(指標變數中的值可以改),而引用物件不能改。這是使用指標不安全而使用引用安全的主要原因。從某種意義上來說引用可以被認為是不能改變的指標。
  • 不存在指向空值的引用這個事實,意味著使用引用的程式碼效率比使用指標的要高。因為在使用引用之前不需要測試它的合法性。相反,指標則應該總是被測試,防止其為空。
  • 理論上,對於指標的級數沒有限制,但是引用只能是一級。如下:
  int** p1;         // 合法。指向指標的指標
  int*& p2;         // 合法。指向指標的引用
  int&* p3;         // 非法。指向引用的指標是非法的
  int&& p4;         // 非法。指向引用的引用是非法的
 

注意上述讀法是從左到右。

下面用通俗易懂的話來概述一下:

  • 指標-對於一個型別T,T就是指向T的指標型別,也即一個T型別的變數能夠儲存一個T物件的地址,而型別T是可以加一些限定詞的,如const、volatile等等。見下圖,所示指標的含義:
  • 在這裡插入圖片描述
  • 引用-引用是一個物件的別名,主要用於函式引數和返回值型別,符號X&表示X型別的引用。見下圖,所示引用的含義:
    在這裡插入圖片描述
    首先,引用不可以為空,但指標可以為空。前面也說過了引用是物件的別名,引用為空——物件都不存在,怎麼可能有別名!故定義一個引用的時候,必須初始化,不初始化的話連編譯都通不過(編譯時錯誤)。因此如果你有一個變數是用於指向另一個物件,但是它可能為空,這時你應該使用指標;如果變數總是指向一個物件.,你的設計不允許變數為空,這時你應該使用引用。

注意:正因為指標可以不指向任何物件,使用指標之前必須做判空操作,而引用就不必。

  • 其次,引用不可以改變指向,對一個物件"至死不渝";但是指標可以改變指向,而指向其它物件。說明:雖然引用不可以改變指向,但是可以改變初始化物件的內容。

例如就++操作而言,對引用的操作直接反應到所指向的物件,而不是改變指向;而對指標的操作,會使指標指向下一個物件,而不是改變所指物件的內容。

  • 引用的大小是所指向的變數的大小,因為引用只是一個別名而已;指標是指標(地址)本身的大小,32位系統下,一般為4個位元組。
  • 最後,引用比指標更安全。由於不存在空引用,並且引用一旦被初始化為指向一個物件,它就不能被改變為另一個物件的引用,因此引用很安全。對於指標來說,它可以隨時指向別的物件,並且可以不被初始化,或為NULL,所以不安全。const 指標雖然不能改變指向,但仍然存在空指標,並且有可能產生野指標(即多個指標指向一塊記憶體,free掉一個指標之後,別的指標就成了野指標)。

總之,可以歸結為"指標指向一塊記憶體,它的內容是所指記憶體的地址;而引用則是某塊記憶體的別名,引用不改變指向。"

特別之處const

為什麼要提到const關鍵字呢?因為const對指標和引用的限定是有差別的:

1. 常量指標VS常量引用

常量指標:指向常量的指標,在指標定義語句的型別前加const,表示指向的物件是常量。
定義指向常量的指標只限制指標的間接訪問操作,而不能規定指標指向的值本身的操作規定性。

gec@ubuntu:~/demo$ cat main.c
#include <stdio.h>


int main(int argc,char **argv)
{
    int i=1;
    
    const int *pointer=&i;

    *pointer=20; 
    return 0;

}

錯誤:

gec@ubuntu:~/demo$ gcc main.c -o main
main.c: In function ‘main’:
main.c:10:13: error: assignment of read-only location ‘*pointer’
     *pointer=20;

常量指標定義"const int* pointer=&a"告訴編譯器,pointer是常量,不能將pointer作為左值進行操作。

常量引用:指向常量的引用,在引用定義語句的型別前加const,表示指向的物件是常量。也跟指標一樣不能對引用指向的變數進行重新賦值操作。

gec@ubuntu:~/demo$ cat main.c
#include <stdio.h>


int main(int argc,char **argv)
{
    int i=1;
    
    const int &pointer=i;

    pointer=20; 
    return 0;

}

錯誤:

gec@ubuntu:~/demo$ g++ main.c -o main
main.c: In function ‘int main(int, char**):
main.c:10:13: error: assignment of read-only reference ‘pointer’
     pointer=20;
             ^~

2.指標常量VS引用常量
在指標定義語句的指標名前加const,表示指標本身是常量。在定義指標常量時必須初始化!而這是引用與生俱來的屬性,無需使用const。

指標常量定義"int* const pointer=&b"告訴編譯器,pointer(地址)是常量,不能作為左值進行操作,但是允許修改間接訪問值,即*pointer(地址所指向記憶體的值)可以修改。

gec@ubuntu:~/demo$ cat main.c
#include <stdio.h>


int main(int argc,char **argv)
{
   
    int  *const  pointer;

    int* const  &ref=pointer; 
    return 0;

}

這是錯誤的:

gec@ubuntu:~/demo$ g++ main.c -o main
main.c: In function ‘int main(int, char**):
main.c:7:18: error: uninitialized const ‘pointer’ [-fpermissive]
     int  *const  pointer;
                  ^~~~~~~

3.常量指標常量VS常量引用常量
常量指標常量:指向常量的指標常量,可以定義一個指向常量的指標常量,它必須在定義時初始化。
定義"const int* const pointer=&c"
告訴編譯器,pointer和*pointer都是常量,他們都不能作為左值進行操作。

而不存在所謂的"常量引用常量",因為引用變數就是引用常量。C++不區分變數的const引用和const變數的引用。程式決不能給引用本身重新賦值,使他指向另一個變數,因此引用總是const的修飾為const就一定不能作為左值。如果對引用應用關鍵字const,起作用就是使其目標稱為const變數。即

沒有:const double const& a=1;
只有const double& a=1;
double b=1;
constdouble& a=b;
b=2;//正確
a=3;//出錯error: assignment of read-only reference `a'

總結:有一個規則可以很好的區分const是修飾指標,還是修飾指標指向的資料——畫一條垂直穿過指標宣告的星號(*),如果const出現線上的左邊,指標指向的資料為常量;如果const出現在右邊,指標本身為常量。而引用本身就是常量,即不可以改變指向。

指標傳遞和引用傳遞

為了更好的理解指標和引用,下面介紹一下指標傳遞和引用傳遞。當指標和引用作為函式的引數是如何傳值的呢?

  • 指標傳遞引數本質上是值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,被調函式的形式引數作為被調函式的區域性變數處理,即在棧中開闢了記憶體空間以存放由主調函式放進來的實參的值,從而成為了實參的一個副本。值傳遞的特點是被調函式對形式引數的任何操作都是作為區域性變數進行,不會影響主調函式的實參變數的值。
  • 引用傳遞過程中,被調函式的形式引數也作為區域性變數在棧中開闢了記憶體空間,但是這時存放的是由主調函式放進來的實參變數的地址。被調函式對形參的任何操作都被處理成間接定址,即通過棧中存放的地址訪問主調函式中的實參變數。正因為如此,被調函式對形參做的任何操作都影響了主調函式中的實參變數。

引用傳遞和指標傳遞是不同的,雖然它們都是在被調函式棧空間上的一個區域性變數,但是任何對於引用引數的處理都會通過一個間接定址的方式操作到主調函式中的相關變數。而對於指標傳遞的引數,如果改變被調函式中的指標地址,它將影響不到主調函式的相關變數。如果想通過指標引數傳遞來改變主調函式中的相關變數, 那就得使用指向指標的指標,或者指標引用。

從概念上講。指標從本質上講就是存放變數地址的一個變數,在邏輯上是獨立的,它可以被改變,包括其所指向的地址的改變和其指向的地址中所存放的資料的改變。

而引用是一個別名,它在邏輯上不是獨立的,它的存在具有依附性,所以引用必須在一開始就被初始化,而且其引用的物件在其整個生命週期中是不能被改變的(自始至終只能依附於同一個變數)。

最後,總結一下指標和引用的相同點和不同點:

. 相同點

  • 都是地址的概念;

指標指向一塊記憶體,它的內容是所指記憶體的地址;而引用則是某塊記憶體的別名。

不同點

  • 指標是一個實體,而引用僅是個別名;
  • `引用只能在定義時被初始化一次,之後不可變;指標可變;引用“從一而終”,指標可以“見異思遷”;
  • 引用沒有const,指標有const,const的指標不可變;
具體指沒有int& const a這種形式,而const int& a是有的,前者指引用本身即別名不可以改變,這是當然的,所以不需要這種形式,後者指引用所指的值不可以改變)
  • 引用不能為空,指標可以為空;
  • sizeof 引用”得到的是所指向的變數(物件)的大小,而“sizeof 指標”得到的是指標本身的大小;
  • 指標和引用的自增(++)運算意義不一樣;
  • 引用是型別安全的,而指標不是 (引用比指標多了型別檢查)
  • 不可對常量取引用,例如 int &r = 100; 是錯誤的,因為常量是匿名的。
  • 引用必須在定義時同時賦值,不可單獨定義引用,例如 int &r; 是錯誤的。

`

相關文章