指標與參照

2023-05-27 06:00:24

指標與參照

本文講解程式語言中指標(Pointer)與參照(Reference)的概念, 並且對比了常見語言中這兩個概念的區別.

C++

指標和參照在C++中是非常重要的概念, 初學者很容易在指標和參照的概念上混淆(因為這兩者太像了).

如何理解兩者的本質:

  • 指標的本質: 儲存地址的變數(實體)
  • 參照的本質: 變數的別名

但在組合層面指標和參照是一樣的,只是編譯器對兩者做了區分,參照可以看作是更加安全的指標,進行了許多限制,大部分情況應該優先使用參照

下面列個表格,對比這兩者的區別

  • &的二義性: 既是參照的定義, 也是取地址符
指標 ( *a=&v;) 參照 ( &a=v;)
可以多級 (type **a) ×
可以不用初始化 ×
可以指向 NULL ×
可以改變 ×
有自己獨立的地址 ×
sizeof(a) len( *) len()
自增運算 地址++ 變數值++
成員存取 -> .
const(右) * const (指標不可變) & const (未定義)
const(左) const * (指向物件不可變) const & (參照物件不可變)
  • 指標的參照 ( *&r = p;)
  • 參照的指標 ( *p = &r;)

使用場景

優先使用參照,必要時用指標

  • 指標的操作許可權更多, 容易帶來危險操作

指標的使用場景

  • 實現連結串列或者樹之類的資料結構
  • 傳遞函數引數 (避免copy引起的開銷)

參照的使用場景

  • 傳遞函數引數 (避免copy引起的開銷, 且不需要檢查參照是否為NULL)

此外還有函數指標,這裡不談了 (C++的內容實在是繁雜)

Go

Golang中也有指標型別, 但沒有參照.

而且Go的指標也與C++中的指標有很多不同之處:

  • 指標值不支援數學運算 (避免指標對記憶體的任意操作)
  • 不同型別的指標無法直接賦值
  • 區域性變數的指標可以被"安全"的返回

這裡需要介紹一些關於Go中的函數引數傳遞的知識.

  • Go中只有值傳遞 (指標型別一樣)

  • 如果需要在函數中修改引數值, 可以使用指標

  • 參照型別變數儲存的是地址, 因此可以在引數中修改原值

    Go的變數包括值型別和參照型別, 參照型別只有slice, channel 和 map 三種, 參照型別的特點是變數儲存的是地址, 因此傳參照型別到函數中修改會影響原值.

Python

Python沒有參照, 也沒有顯式的指標, 只有物件參照, 這和其記憶體管理方式密切相關.

Python是通過"參照計數"自動管理記憶體的, 程式中的每個物件的參照個數都會被記錄, 當參照個數為0時會被垃圾回收機制回收, 既不需要分配, 也不用擔心回收.

Python中一切皆物件, 物件是一個三元組 (Id, Type, Value), 變數和物件之間的關係為參照, 變數更像是指標.

Python的物件型別分為可變(mutable)和不可變(immutable). 可變型別包括 list, dict, set, bytearray, user-defined classes, 這些型別在函數內部修改會影響原值.

這裡又會引出淺拷貝和深拷貝的區別. 淺拷貝只複製了物件的參照, 本質共用同一個物件; 深拷貝則是建立了物件的副本, 修改不會影響原值.

Java

Java的資料型別分為基本型別(byte, short, int, long, double, float, char, boolean)和參照型別.

Java中沒有指標, 但參照本質上就是"指標", Java參照儲存實際就是物件的地址, 參照儲存在棧區, 而物件儲存在堆區. 區別在於Java參照對指標進行了封裝, 使其無法直接操作記憶體.

Java參照和C++參照存在區別, Java參照有自己的記憶體 (和C++指標比較像), 儲存地址, 而C++參照只是別名.

參考

C++中指標與參照的區別 - 知乎 (zhihu.com)

C++中,參照和指標的區別是什麼? - 知乎 (zhihu.com)

C++中「參照」的底層實現 - hoodlum1980 - 部落格園 (cnblogs.com)

Go 語言沒有參照型別,指標也與眾不同 - 知乎 (zhihu.com)

go 值型別與參照型別_歐陽惜竹的部落格-CSDN部落格

Python學習系列之值型別與參照型別_answer3lin的部落格-CSDN部落格

Java真的沒有「指標」嗎? - 知乎 (zhihu.com)