iT邦幫忙

5

c/c++語言: 超好懂的指標(pointer)與參考(reference),歡迎初學者

在你開始讀這篇文章之前,
小馬推薦你可以先閱讀C語言: 超好懂的指標,初學者請進這篇文章,
這篇是目前為止小馬搜索網路c語言指標的文章中,
我覺得講述最清楚的一篇

指標是c語言中一個重要的觀念,
但對初學者來說不易理解,常常指來指去最後都很混亂,
尤其再學深入一點,
還會學到一個跟指標很相似的概念-參考
最後就成為惡夢了

底下小馬會嘗試寫個自己的版本,
今天小馬的挑戰便是從頭講起,
挑戰一次讓指標參考這兩個概念變得非常簡單

總結一句話:「指標是地址,參考是別稱」,
本文將細講兩者的觀念。

觀念一、什麼是指標?

其實指標很簡單,你可以把它想成是地址就行了,
就像你想要拜訪一位朋友你會透過地址拜訪它一樣,
譬如說你的朋友叫做「王大明」好了,
他住在「台北市中正路100號」(此地址隨意亂寫的)
https://ithelp.ithome.com.tw/upload/images/20200209/20117114LDBl6e2zLE.png

那這跟指標有什麼關係呢?
其實變數也有類似的三大元素: 指標(地址)、內容、與名稱,
譬如底下簡單的程式碼:

void main(){
  int a = 2;
}

int a = 2;就是宣告了一個整數型態的變數,
名稱叫做a,內容為2
宣告了這個變數的時候,
程式便會去記憶體要一塊空間來儲存這個變數,
存放這個變數的起始位置(用一個十六進位的數字表示)就稱為該變數的地址
我們將變數的三大元素與朋友家的地址比較就一目瞭然:

https://ithelp.ithome.com.tw/upload/images/20200209/20117114Z9ljeIwv6E.png

是不是非常簡單呢?

取址運算& 與 取值運算*

在c語言中,可以用「&」運算查看一個變數的地址
例如:

#include <iostream>
using namespace std;

int main()
{
    int a = 50;
    cout << "變數a的地址為: "<< &a << endl;
    return 0;
}

cout << "變數a的地址為: "<< &a << endl;這一行即是將a這個變數的地址印出來(以十六進位表示),
範例結果: 變數a的地址為: 0015F7C4 (每次重新編譯程式時,程式取用的地址可能會不太一樣)

「*」運算則是可以查看一個地址的內容,
譬如以下的程式:

#include <iostream>
using namespace std;

int main()
{
    int a = 50;
    cout << "變數a的地址為: "<< &a << endl;
    cout << "變數a的值為: " <<  a << endl;
    cout << "變數a的值為: " << *&a << endl;
    return 0;
}

範例結果:
變數a的地址為: 0015F7C4
變數a的值為: 50
變數a的值為: 50

我們解釋一下*&a的意思,
*&a相當於*(&a)
&a是取得變數a的地址,
「*」是取值運算,*(&a)可以取得&a這個地址的內容,
所以*&a其實就跟直接寫a是一樣的。

觀念二、什麼是參考?

講完了指標,那什麼是參考呢?
參考更容易理解了,直接想成是別名或是外號即可,
譬如說你朋友「王大明」的外號可能叫「小明」一樣。
你叫「小明」跟叫「王大明」是在叫同一個人

那麼在c++語言中要如何幫一個變數取「別名」呢?
答案還是用「&」這個符號。

宣告一個參考變數

例如以下程式碼:

int main()
{
    int a = 50;
    int &r = a;
    return 0;
}

int &r = a;這行的意思是宣告一個參考變數r(前面的int指的是r的型別),
r這個外號綁定給a

我們再把它跟朋友家的地址做個比較就一目瞭然:

https://ithelp.ithome.com.tw/upload/images/20200209/20117114JgFGN02INA.png

咦?等等?剛剛不是才教說「&」是「取址運算」嗎?
因為同一個符號可能有多種意思,
這也可能是很多人學到後來會覺得非常混亂的原因,
也是需要下苦功的地方,
你可以看足夠多的例子直到涵義對你來說相當清楚。

原則上「&」前面必須跟著一個型別(如int &r)才是宣告參考變數的意思,
如果直接寫&a才是取得變數a的地址。

參考變數的用法

既然參考指的是一個別名
表示對參考做任何事,
就相當於對原來的變數做任何事一樣
(就好像你對「小明」做任何事相當於對「王大明」做任何事)

看底下的例子:

#include <iostream>
using namespace std;

int main()
{
    int a = 50;
    int& r = a;
    r = 20;
    cout << "變數a的值為: "<< a << endl;
    cout << "變數r的值為: " << r << endl;
    cout << "變數a的地址為: " << &a << endl;
    cout << "變數r的地址為: " << &r << endl;
    return 0;
}

一開始我們宣告了一個變數a,初始值設為50
我們將r這個別名給了a,並將r的值改為20

結果為:
變數a的值為: 20
變數r的值為: 20
變數a的地址為: 0040FAC4
變數r的地址為: 0040FAC4

可以看到變數a和r的值和地址是完全一樣的。

--------------我是分隔線--------------------
小馬知道本文的篇幅還蠻大的,
畢竟在短篇幅中把指標和參考講清楚並不是易事,
如果讀累了,你可以先起身喝杯茶,
或先將上述內容消化過再繼續往下看,加油!
------------------------------------------

宣告一個指標變數

了解指標就是變數的地址後,
在c++語言,我們可以宣告一個指標變數來存放這個地址,
語法仍然是使用「*」
例如:

int a = 50;
int *p= &a; //變數p存有a的地址

int *宣告一個指標,型態是int
作用就是專門用來存放地址。
注意上述程式碼不可以寫int *p= a
因為int *是專門用來儲存地址的。

與上面談「&」符號的狀況相似,
「*」前面必須跟著一個型別(如int *p)才是宣告指標變數的意思,
如果直接寫*p是「取值運算」,取得地址p裡面的值。

指標的指標

指標變數也是一個物件,它也存放在記憶體中的某個位置
指標變數也會有自己的地址。

這是跟現實中比較難連結的地方了,
如果說「台北市中正路100號」是「王大明」的地址,
怎麼會有「台北市中正路100號」的地址這種東西呢?

這是因為電腦會把資料存在記憶體中,
要用一個變數儲存地址,勢必也會佔用記憶體空間

程式碼int a = 50; int *p= &a;可以這樣理解:

https://ithelp.ithome.com.tw/upload/images/20200209/201171149u3QTsBIiW.png

指標變數的用法

還記得「*」就是取值運算嗎?
例如下面的程式碼:

int main()
{
    int a = 50;
    int *p = &a;
    cout << *p << endl;
    return 0;
}

我們嘗試將地址p裡面的值印出來,
結果當然就是變數a所存放的值- 50

我們可以直接將*p當做變數a來使用,
例如:

int main()
{
    int a = 50;
    int *p = &a;
    *p = 20;
    cout << *p << endl;
    cout << a << endl;
    return 0;
}

結果為:
20
20

可以看到透過地址p去修改a的值,
a真的發生改變了

--------------我是分隔線--------------------
目前又有蠻多觀念要消化了,
休息一下,做個自我檢測看看自己觀念是否清楚了
------------------------------------------

自我檢測: 你能說出下面每行程式碼中的&與*所代表的涵義嗎?(歡迎於留言區中討論)

(提示: 取址運算、取值運算、宣告指標變數、宣告參考變數)

int main()
{
    int a = 5;
    int &r = a;
    int *p;
    p = &a;
    *p = 10;
    int &r2 = *p;
    r2 = 20;
    cout << a << endl;
    return 0;
}

第二問: 上述程式碼cout << a << endl;會印出什麼?

重點觀念: 指標與參考的差異

這是新手很容易搞迷糊的地方,
感覺上用指標與用參考都能夠存取/修改一個變數,
那麼指標參考到底差在哪裡呢?
這就可以用生活化的例子來想了,
我們可以說「王大明」和「小明」是同一個人,
但「王大明」和「台北市中正路100號」顯然是不同的東西。

做個表格來比較兩者差異:

https://ithelp.ithome.com.tw/upload/images/20200209/20117114cFOMh4p4dX.png

以下針對幾點較容易困惑的點闡述,

  • 是否需要初始化: 參考本身不是物件,宣告一個參考一定要綁定一個物件; 但指標可以先宣告,需要的時候才去指向變數,例如(假設已宣告int i=1;):

<正確寫法一>

int *p; //正確,指標變數不一定要初始化

<正確寫法二>

int *p = &i; //正確,p儲存地址i

<正確寫法三>

int &r = i; //正確,r是變數i的別名

<錯誤寫法>

int &r; //程式會報錯,參考必須初始化
  • 是否占額外的記憶體空間: 參考不是物件,只是一個別名,故不用額外的記憶體空間儲存資訊; 指標變數是物件,必須用記憶體儲存。
  • 可否改變所指/參考的對象: 參考變數一經初始化,即不可以改變參考的對象; 但指標變數可以改變所指的對象。

篇幅蠻大的,今天先教到這邊吧,
歡迎大家於留言區給建議或發問哦。
指標和參考是c++中重要的觀念,預計之後會再寫一些相關的主題來幫助大家融會貫通


1 則留言

1
Robin
iT邦新手 5 級 ‧ 2020-02-10 12:13:54

內容非常清晰 平易近人
感謝大大分享

心原一馬 iT邦研究生 5 級 ‧ 2020-02-10 12:30:04 檢舉

感謝留言,很高興能幫到您

我要留言

立即登入留言