聽說指標是很多人在學 C 或 C++ 的痛,但我覺得好像跟初次教你這個概念的老師有關。我非常受不了有些老師在教某個大魔王單元之前會先恐嚇學生說:這超難喔不認真會死掉。我相信很多人數學(或是任一科目)都毀在這種老師手裡,希望各位老師明白教學方式和吸收成果因人而異,打這種預防針只會有反效果。在此寫下我理解的指標概念,希望可以教教未來金魚腦的我XDD
為什麼要用指標
傳遞陣列或字串更有效率、較複雜的資料結構需用指標 linked list
指標的概念
指標可以看成是一種特殊的變數
指標(pointer)宣告方式為:
資料型別 *變數名稱
ex: int *ptr
,意思是宣告指向整數的指標變數。
或是
資料型別* 變數名稱
ex: int* ptr
,意思是宣告型態為「指向整數之指標」的變數 ptr
。
指標簡單來說可以想像成很多東西,例如以隨便的地址為例子好了。
住址:建國路 1 號
屋子裡面的人:Alex
如果我宣告了一個指標變數 *p
,那大概會是這樣:p
= 住址:建國路 1 號*p
= 屋子裡面的人:Alex
以比較專業一點的講法,如果我寫了 int *p
,表示我宣告了一個指標變數:p
代表一個記憶體的位址*p
代表這個記憶體位址的資料內容,並且這個資料內容的型態是 int
。
位址列印示範
&
是位址符號,以上述例子來說,&a
表示取得該變數的位址,也就是「建國路 1 號」。記憶體位置和指標運作息息相關,我們可以藉由下方程式碼實現列印實際存放位置,並觀察變化:
#include <stdio.h> #include <stdlib.h> int main(void){ int a; int b=5; double c=2.78; printf("a=%4d, 佔據 %d 個位元組, 位址為%p \n", a, sizeof(a), &a); printf("b=%4d, 佔據 %d 個位元組, 位址為%p \n", b, sizeof(b), &b); printf("c=%4.2f, 佔據 %d 個位元組, 位址為%p \n", c, sizeof(c), &c); }
outputa= 0, 佔據 4 個位元組, 位址為0x7ffe92729cec
b= 5, 佔據 4 個位元組, 位址為0x7ffe92729ce8
c=2.78, 佔據 8 個位元組, 位址為0x7ffe92729ce0
再度說明符號
&
:位址運算子,用來取得變數的位址。*
:依位址取值運算子,依照位址取得指向的變數內容。
例如我現在宣告 int a = 123
,在電腦內表示如下,&a
就是取出 a
的位址 0x7ffe
:
例外我們也能把這個位址儲存在一個變數(指標變數)內:
int a = 123
int *ptr = &a
上述意思一樣是宣告一個型態為 int
的 a
變數,並且把 a
的位址存進去 ptr
內。之後使用 *ptr
可以取出 a
的內容,也就是 123
。
當然,指標變數也會有自己的位址:
#include <stdio.h> #include <stdlib.h> int main(void){ int *ptr, n = 20; ptr = &n; printf("n=%d, &n=%p \n", n, &n); printf("*ptr=%d, ptr=%p, &ptr", *ptr, ptr, &ptr); return 0; }
outputn=20, &n=0x7fffa9ce246c
*ptr=20, ptr=0x7fffa9ce246c, &ptr=0x7fffa9ce2470
也可以在過程中更改指標變數指向的對象,例如下方程式碼原先將 ptr
指向 a
,後來改成指向 b
,可以觀察一下兩者變化:
#include <stdio.h> #include <stdlib.h> int main(void){ int a=11, b = 999999, *ptr; ptr = &a; printf("&a=%p, &ptr=%p, ptr=%p, *ptr=%d \n", &a, &ptr, ptr, *ptr); ptr = &b; printf("&b=%p, &ptr=%p, ptr=%p, *ptr=%d \n", &b, &ptr, ptr, *ptr); return 0; }
output&a=0x7ffedb27e45c, &ptr=0x7ffedb27e450, ptr=0x7ffedb27e45c, *ptr=11
&b=0x7ffedb27e458, &ptr=0x7ffedb27e450, ptr=0x7ffedb27e458, *ptr=999999
指標、函數與一些操作
上述都算是簡單宣告的例子,而指標也很常用在函數當中。下方是寫一個列印出位址和內容的函數:
#include <stdio.h> #include <stdlib.h> void show(int *); int main(void){ int a = 5; int *ptr = &a; show(&a); show(ptr); return 0; } void show(int *p){ printf("在位址%p內,內容為%d \n", p, *p); }
這裡要注意的是,函數中的變數是用位址。
output在位址0x7ffc4a740854內,內容為5
在位址0x7ffc4a740854內,內容為5
傳遞指標
這邊就可以很明顯看到直接以位址取得變數,並直接改變該變數的樣子:
#include <stdio.h> #include <stdlib.h> void add1(int *); int main(void){ int a = 99; printf("呼叫之前 a=%d \n", a); add1(&a); printf("呼叫之後 a=%d \n", a); return 0; } void add10(int *p){ *p = *p + 10; }
output
呼叫之前 a=99
呼叫之後 a=109
交換
#include <stdio.h> #include <stdlib.h> void swap(int *, int *); int main(void){ int a = 99, b = 1; printf("swap之前 a=%d, b=%d \n", a, b); swap(&a, &b); printf("swap之後 a=%d, b=%d \n", a, b); return 0; } void swap(int *p1, int *p2){ int tmp = *p1; *p1 = *p2; *p2 = tmp; }
outputswap之前 a=99, b=1
swap之後 a=1, b=99
下方程式也是差不多的例子,利用
#include <stdio.h> #include <stdlib.h> void rec(int, int, int *, int *); int main(void){ int a = 2, b = 7; int area, peri; rec(a, b, &area, &peri); printf("area=%d, length=%d", area, peri); return 0; } void rec(int x, int y, int *area, int *peri){ *area = x*y; *peri = 2*(x + y); }
outputarea=14, length=18
Call by value & Call by reference
也不怕大家笑,我面試的時候真的被這題經典題問倒XD
Call by value
直接拿程式碼當作例子。我們寫了一個函式 add
,希望能夠把 a+b
的結果存進去 a
:
#include <stdio.h> #include <stdlib.h> void add(int a, int b) { a = a + b; } int main() { int a = 99; int b = 1; add(a, b); printf("a = %d", a); return 0; }
原本我們預期會列印出 100
,但是因為是傳值進去,因此只有改變當時的數值,並不會改變變數 a
本身。
這算是一開始學程式時,最常運用到的方式。但若在大型程式中,使用結束後沒有刪除未使用的記憶體空間,很可能有記憶體不足的狀況,嚴重一些會導致程式崩潰。
另外如果使用超大陣列,例如宣告了 int array [9999999]
,會花時間在複製資料,效能會比較低落。
Call by reference
有了上述錯誤的教訓,我們試圖將程式碼改成正確的:
#include <stdio.h> #include <stdlib.h> void add(int *a, int *b) { *a = *a + *b; } int main() { int a = 99; int b = 1; add(&a, &b); printf("a = %d", a); return 0; }
因為引用了變數本身(而不是數值),所以結果成功地輸出 100
了。
另外嚴格上來說 C 語言沒有 call by reference,它在運作上算是把位址當作 value 使用進行 call by value。而為了理解方便,我們會將它叫做 call by address 或是 call by pointer,嚴格來說這不是一個正統的說法,但為了容易理解,所以有這個名稱。
那麼實際上的 call by reference 要怎麼操作呢?一樣以上述例子,改用 C++ 來寫:
#include <stdio.h> #include <stdlib.h> void add(int &a, int &b) { a = a + b; } int main() { int a = 99; int b = 1; add(a, b); printf("a = %d", a); return 0; }
結論
其實指標的概念真的不難,但非常建議要實際操作看看。