C 關於指標,引用,函式和const的關係

2021-10-01 17:56:16 字數 4346 閱讀 5479

首先引用是對指標進行了簡單的封裝,它的底層依然是通過指標實現的。

有的說法說是引用自身不佔記憶體,和其被引用物件佔同一記憶體,這是比較淺顯的。引用占用的記憶體和指標占用的記憶體長度一樣,在 32 位環境下是 4 個位元組,在 64 位環境下是 8 個位元組,之所以不能獲取引用的位址,是因為編譯器進行了內部轉換。

int a = 1; int &r = a; r = 2; cout<<&r<其具體區別是:

引用必須在定義時初始化,並且以後也要從一而終,不能再指向其他資料;而指標沒有這個限制,指標在定義時不必賦值,以後也能指向任意資料。

可以有 const 指標,但是沒有 const 引用。(因為引用本來就不能改變指向,加上 const 是多此一舉。)

指標可以有多級,但是引用只能有一級,例如,int **p是合法的,而int &&r是不合法的。如果希望定義乙個引用變數來指代另外乙個引用變數,那麼也只需要乙個&即可。

指標和引用的自增(++)自減(–)運算意義不一樣。對指標使用 ++ 表示指向下乙份資料,對引用使用 ++ 表示它所指代的資料本身加 1;自減(–)也是類似的道理。

5)指標自身被分配有記憶體位址,對指標本身取位址會取得該指標在記憶體中的位址;引用也有,但是對其取位址時被編譯器做了轉換,只能取得被引用物件的位址。

其實 c++ **中的大部分內容都是放在記憶體中的,例如定義的變數、建立的物件、字串常量、函式形參、函式體本身、new或malloc()分配的記憶體等,這些內容都可以用&來獲取位址,進而用指標指向它們。除此之外,還有一些我們平時不太留意的臨時資料,例如表示式的結果、函式的返回值等,它們可能會放在記憶體中,也可能會放在暫存器中。一旦它們被放到了暫存器中,就沒法用&獲取它們的位址了,也就沒法用指標指向它們了。

int n =

100, m =

200;

int*p1 =

&(m + n)

;//m + n 的結果為 300

int*p2 =

&(n +

100)

;//n + 100 的結果為 200

bool

*p4 =

&(m < n)

;//m < n 的結果為 false

以上表示式均會報錯。

由於暫存器離 cpu 近,並且速度比記憶體快,將臨時資料放到暫存器是為了加快程式執行。但是暫存器的數量是非常有限的,容納不下較大的資料,所以只能將較小的臨時資料放在暫存器中。int、double、bool、char 等基本型別的資料往往不超過 8 個位元組,用一兩個暫存器就能儲存,所以這些型別的臨時資料通常會放到暫存器中;而物件、結構體變數是自定義型別的資料,大小不可**,所以這些型別的臨時資料通常會放到記憶體中。

同時,常量表示式的值雖然在記憶體中,但是沒有辦法定址,所以也不能使用&來獲取它的位址,更不能用指標指向它。如以下**段也是錯誤的。

int

*p1 =&(

100)

;int

*p2 =&(

23+45*

2);

引用不能繫結到臨時資料,這在大多數情況下是正確的,但是當使用 const 關鍵字對引用加以限定後,引用就可以繫結到臨時資料了。如以下**在 gcc 和 visual c++ 下都能夠編譯通過。

//環境**:

typedef

struct

s;//此段**驗證當返回值是結構體型別時,返回值是在記憶體區域而不是暫存器的

s func_s()

//此段**用運算子過載來返回乙個結構體變數,來驗證當返回值是結構體型別時,返回值是在記憶體區域而不是暫存器的

s operator+(

const s &a,

const s &b)

int m =

100, n =36;

const

int&r1 = m + n;

const

int&r2 = m +28;

const

int&r3 =12*

3;const

int&r4 =50;

const

int&r5 =

func_int()

;//下面的**在gcc下是錯誤的,在visual c++下是正確的 但是若是在s前加上const那麼都能通過編譯

s s1 =

; s s2 =

; s &r6 =

func_s()

; s &r7 = s1 + s2;

這是因為將常引用繫結到臨時資料時,編譯器採取了一種妥協機制:編譯器會為臨時資料建立乙個新的、無名的臨時變數,並將臨時資料放入該臨時變數中,然後再將引用繫結到該臨時變數。注意,臨時變數也是變數,所有的變數都會被分配記憶體。

為什麼編譯器為常引用建立臨時變數是合理的,而為普通引用建立臨時變數就不合理呢?

我們知道,將引用繫結到乙份資料後,就可以通過引用對這份資料進行操作了,包括讀取和寫入(修改);尤其是寫入操作,會改變資料的值。而臨時資料往往無法定址,是不能寫入的,即使為臨時資料建立了乙個臨時變數,那麼修改的也僅僅是臨時變數裡面的資料,不會影響原來的資料,這樣就使得引用所繫結到的資料和原來的資料不能同步更新,最終產生了兩份不同的資料,失去了引用的意義。

void

swap

(int

&r1,

int&r2)

如上段**,如果編譯器會為 r1、r2 建立臨時變數,那麼函式呼叫swap(10, 20)就是正確的,但是 10 不會變成 20,20 也不會變成 10,所以這種呼叫是毫無意義的。

總起來說,不管是從「引用的語義」這個角度看,還是從「實際應用的效果」這個角度看,為普通引用建立臨時變數都沒有任何意義,所以編譯器不會這麼做。

const 引用和普通引用不一樣,我們只能通過 const 引用讀取資料的值,而不能修改它的值,所以不用考慮同步更新的問題,也不會產生兩份不同的資料。

這樣為 const 引用建立臨時變數的時候,引用的意義沒有絲毫動搖,反而增進了引用的靈活性和通用,相當巧妙。

「型別嚴格一致」是為了防止發生讓人匪夷所思的操作,但是這條規則僅僅適用於普通引用,當對引用新增 const 限定後,情況就又發生了變化,編譯器允許引用繫結到型別不一致的資料。請看下面的**:

int n =

100;

int&r1 = n;

//正確

const

float

&r2 = n;

//正確

char c =

'@';

char

&r3 = c;

//正確

const

int&r4 = c;

//正確

當引用的型別和資料的型別不一致時,如果它們的型別是相近的,並且遵守「資料型別的自動轉換」規則,那麼編譯器就會建立乙個臨時變數,並將資料賦值給這個臨時變數(這時候會發生自動型別轉換),然後再將引用繫結到這個臨時的變數,這與「將 const 引用繫結到臨時資料時」採用的方案是一樣的。

注意,臨時變數的型別和引用的型別是一樣的,在將資料賦值給臨時變數時會發生自動型別轉換。請看下面的**:

float f =

12.45

;const

int&r = f;

printf

("%d"

, r)

;

該**的輸出結果為 12,說明臨時變數和引用的型別都是 int(嚴格來說引用的型別是 int &),並沒有變為 float。

當引用的型別和資料的型別不遵守「資料型別的自動轉換」規則,那麼編譯器將報錯,繫結失敗,例如:

char

*str =

"abcd"

;const

int&r = str;

char *和int兩種型別沒有關係,不能自動轉換,這種引用就是錯誤的。

結合上面講到的知識,總結起來說,給引用新增 const 限定後,不但可以將引用繫結到臨時資料,還可以將引用繫結到型別相近的資料,這使得引用更加靈活和通用,它們背後的機制都是臨時變數。

當引用作為函式引數時,如果在函式體內部不會修改引用所繫結的資料,那麼請盡量為該引用新增 const 限制。

概括起來說,將引用型別的形參新增 const 限制的理由有三個:

使用 const 可以避免無意中修改資料的程式設計錯誤;

使用 const 能讓函式接收 const 和非 const 型別的實參,否則將只能接收非 const 型別的實參;

使用 const 引用能夠讓函式正確生成並使用臨時變數。

指標和引用,const 指標和const 引用

指標和引用是在使用中經常弄混淆的兩個概念。引用 reference 為物件起了另外乙個名字,用符號 表示。name,例如 int i 1024 int ref i 一般在初始化變數時,初始值會被拷貝到新建立的物件中,然而定義引用時,程式把引用和它的初始值繫結 bind 在一起,而不是將初始值拷貝給引...

c 引用 指標 const

1 const定義常量 const int valuename value 2 const與指標 指標常量 int const p 指標的位址不允許修改,值可以修改 常量指標 const int p 值不能修改,位址可以修改 指向常量的指標常量 const int const p 3 const與函...

關於函式返回引用和const物件

class node const node fun const node obj return obj 則可以這樣使用該函式 node obj node res fun obj res的位址與obj的不同,其中任乙個變數值的改變不會影響另乙個變數 或const node res fun obj 必須...