講師: 李根逸 (Ken-Yi Lee), E-mail: feis.tw@gmail.com
指標
【第七講】
!225
課程⼤大綱
沒有指標的世界
[P.227]指標使⽤用的情境 [P.228]
指標的基礎
指標變數宣告 (type *) [P.232]
取址運算⼦子 (&) [P.233]
間接運算⼦子 (*) [P.234]
指標與函式
函式傳值呼叫 [P.235]
函式傳址呼叫 [P.236]
傳值還是傳址 ? [P.238]
指標與陣列
[P.240]!226
沒有『指標』的世界
『指標』是⼀一種資料型態,⽤用來儲存『記憶體位址』
⼀一般情況下,我們是不需要『指標』這種東⻄西的。
但是 C 語⾔言中為了解決某些問題或提昇程式效率,
在某些情況下需要使⽤用『指標』。
先來看看有什麼問題是以前不能處理的:
在被呼叫的函式中修改⾮非該函式內的變數內容 直接複製陣列
例如呼叫函式時要將陣列作為函式引數複製到函式內
直接複製字串
例如呼叫函式時要將字串作為函式引數複製到函式內
動態配置記憶體
例如要動態改變陣列的⼤大⼩小
!227
指標使⽤用的情境 [1]
在被呼叫的函式中修改⾮非該函式內的變數內容
當我們將變數送⼊入函式執⾏行時,是使⽤用『傳值呼叫』的
⽅方式。意味著變數的值會被當做引數值⽽而複製⼀一份進函 式內部做為參數。在函式內部對參數做任何的變動不會 改變到原本的引數值:
void addone(int n) { n = n+1;
}
int main() { int a = 3;
addone(a); /* 複製 a 的值給 addone */
printf(“%d”, a);
return 0;
}
對 addone 來說他只是得到⼀一個整數的複製 品,無法知道整數原本存放的地⽅方或來源
!228
指標使⽤用的情境 [2]
直接複製陣列:
!
!
例如在呼叫函式時要將陣列作為函式引數複製到函式內
:
!229
int v[5] = {1, 2, 3, 4, 5};
int n[5];
n = v; // 語法錯誤 void print(int n[3]) {
for (int i = 0; i < 3; ++i) { printf(“%d ”, n[i]);
} }
int main() {
int v[3] = {1, 2, 3};
print(v);
printf(“\n”);
return 0;
}
指標使⽤用的情境 [3]
直接複製字串:
!
!
例如在呼叫函式時要將字串作為函式引數複製到函式內
:
!230
char v[6] = “Hello”;
char n[6];
n = v; // 語法錯誤 void print(char n[6]) {
for (int i = 0; i < 5; ++i) { printf(“%c”, n[i]);
} }
int main() {
char v[6] = “Hello”;
print(v);
printf(“\n”);
return 0;
}
C ⾵風格字串是利⽤用陣列的⽅方式儲存
指標使⽤用的情境 [4]
動態配置記憶體
動態改變陣列⼤大⼩小
!
!
!
我們在儲存資料前需要先配置記憶體,如果在⼀一開始 未知資料的總數時,我們要怎麼配置?
例如要寫⼀一個程式,讓使⽤用者輸⼊入任意筆資料後儲存起 來要怎麼辦?
!231
int v[5];
int n[10];
v = n; // 語法錯誤
指標變數宣告 (type *)
指標 (Pointer) 是 C/C++ 語⾔言的⼀一⼤大特⾊色,是⼀一 種儲存記憶體位址的資料型態
指標變數宣告語法 :
資料型態 *變數名稱
;
表⽰示變數名稱内存放的是⼀一存放該資料型態值的記憶體位址
宣告指標變數與其他變數的差別 :
int count;
!
!
int *countAddr ;
(int)
??
count
(int *)
??
countAddr
!232
(存 int 值)
(存存 int 值的位址)
2293620
2293624
取址運算⼦子 (&)
變數宣告後依照資料型態會佔據⼀一定的記憶體空間,
並具有⼀一個在記憶體的位址。我們可以利⽤用取址運算
⼦子 (&) 去取得變數的記憶體位址 :
int
count = 9;int
*countAddr = &count;!233
表⽰示式 資料型態 值
count int 9
&count int * 2293620 countAddr int * 2293620 (int)
9
count
(int *)
2293620
countAddr
2293620
2293624
(int) 9
count
countAddr
2293620
2293624
相對地,我們可以利⽤用間接運算⼦子 (*) 從位址取得 位於該位址的變數
(別跟宣告指標⽤用的*搞混)int
count = 9;int
*countAddr = &count;int
result = *countAddr;間接運算⼦子 (*)
!234
表⽰示式 資料型態 值
count int 9
&count int * 2293620 countAddr int * 2293620
*countAdd
r int 9
result int 9
(int) 9
count
(int *)
2293620
countAddr
2293620
2293624
(int) 9
count
countAddr
2293620
2293624
注意這裡出現的兩個星號 (*) 不同!
【範例】pointer.cpp
函式傳值呼叫
當我們將變數送⼊入函式執⾏行時,是使⽤用『傳值呼叫』
的⽅方式。意味著變數的值會被當做引數值⽽而複製⼀一份 進函式內部做為參數。在函式內部對參數做任何的變 動不會改變到原本的引數值:
void addone(int n) { n = n+1;
return;
}
int main() { int a = 3;
addone(a); /* 複製 a 的值給 addone */
printf(“%d”, a);
return 0;
}
對 addone 來說他只是得到⼀一個整數,無法 知道整數原本存放的地⽅方或來源
!235
函式傳址呼叫
我們可以將變數的『記憶體位址』作為引數值複製進
⼊入函式執⾏行。此時在函式內部對參數⽤用『間接運算⼦子』
指定新的值時就會改變原本的變數值。
其實這也是函式傳值呼叫的⼀一種,只是該值是個位址
void addone(int *n) { *n = *n+1;
return;
}
int main() { int a = 3;
addone(&a); /* 複製 a 所在位址給 addone */
printf(“%d”, a);
return 0;
}
對 addone 來說他得到了⼀一個位址,經由間 接運算⼦子 (*) 可以取得並指定該位址上的值
可以看成 C 語⾔言只能⽤用複製傳值的
⽅方式呼叫函式,⽽而位址也是⼀一種值
!236
【範例】交換與排序
試寫⼀一函式 void swap(int *, int *),將輸⼊入的兩 個整數參數的值交換
例如:
a = 1,b = 2,執⾏行完 swap 後,a = 2, b = 1
!
試寫⼀一函式 void sort(int *, int *),將輸⼊入的兩 個整數參數的值由⼩小到⼤大排
例如:
a = 1,b = 2,執⾏行完 sort 後,a = 1, b = 2 a = 2,b = 1,執⾏行完 sort 後,a = 1, b = 2
!237
【範例】sort.cpp
【範例】swap.cpp
傳值還是傳址?
簡⾔言之,在 C 語⾔言裡如果你想要讓其他函式可以幫 你修改變數的值時就需要傳該變數的位址
傳值的設計讓不同函式之間的⾮非全域變數是完全沒有關 係的、是不會互相影響的,可以避免污染與干涉!
要藉由『呼叫函式』來修改⺫⽬目前所在函式的變數值有兩 個⽅方式:
接收回傳值:
!
傳送變數的位址 :
!
能傳值就傳值,可以避免函式之間的間接汙染!
變數位址就好像變數真實的名字⼀一樣,得到的函式可以 為所欲為!
var = func();
func(&var); 傳陣列只能傳址
!238
【範例】讀出與印出
試寫兩函式,其中 read 函式可讀⼊入⼀一成績,並⽤用 show 函式將成績印出
#include <stdio.h>
#include <stdlib.h>
void read(int *);
void show(int);
int main() { int grade;
read(&grade);
show(grade);
system(“pause”);
return 0;
}
!239
#include <stdio.h>
#include <stdlib.h>
int read();
void show(int);
int main() {
int grade = read();
show(grade);
system(“pause”);
return 0;
}
【範例】read_show.cpp
接收回傳值 傳送變數位址
指標與陣列 [1]
指標與陣列關係相當密切,似乎是⼀一體的兩⾯面,但是
⼜又有著蠻多的不同:
int
v[5]; /* 會配置五個(int)的記憶體空間 */int
*vptr = v; /* 會配置⼀一個(int *)的記憶體空間 */(int)
?? (int)
?? (int)
?? (int)
?? (int)
??
2293624 2293628 2293632 2293636
v[0] v[1] v[2] v[3] v[4]
v
(int [5]) 2293620
vptr
(int *) 2293620
v[0] 等於 *vptr
v[1] 等於 *(vptr+1) v[2] 等於 *(vptr+2) v[3] 等於 *(vptr+3)
等於 vptr[0] 等於 *v
等於 vptr[1] 等於 *(v+1) 等於 vptr[2] 等於 *(v+2) 等於 vptr[3] 等於 *(v+3)
2293620
2293616 2293620
(int *) (int *) (int *) (int *) (int *) (int (*)[5])
(int **)
!240
指標與陣列 [2]
指標與陣列的關係:
陣列型態可以轉型為指向該陣列第⼀一個元素的指標
指標可以像陣列⼀一樣使⽤用 [] 運算⼦子來存取記憶體,此 時會以該指標指向的變數做為陣列的第⼀一個元素,以該 指標指向的變數資料型態做為陣列元素型態
指向陣列元素的指標可以對整數做 + 或 - 運算:
加 N:指標會指向往後⾛走 N 個元素 減 N:指標會指向往前⾛走 N 個元素
對兩個指向同陣列元素的指標做 - 的算術運算:
得到兩個指標分別指向的元素差了幾號
!241
【範例】⽐比⼤大⼩小
寫⼀一個可以讓使⽤用者輸⼊入五個整數,回傳最⼤大值的函 式 max1v:
int
max1v( int * );!
!
寫⼀一個可以讓使⽤用者輸⼊入任意個整數,回傳最⼤大值的 函式 max2v:
int
max2v( int *, int );可以⽤用指標變數來接收陣列的位址值
!242
【範例】max.cpp 除了要傳⼊入起始位址,還要傳⼊入元素個數
【範例】⽤用指標對陣列存取 [1]
將下述程式迴圈的部份⽤用指標改寫:
int main() { int v[10];
for (int i = 0; i < 10; i++) { v[i] = 0;
} return 0;
}
int main() { int v[10];
int *p = v;
for (int i = 0; i < 10; i++) { *p = 0;
p++;
} return 0;
}
!243
【範例】⽤用指標對陣列存取 [2]
將下述程式迴圈的部份⽤用指標改寫:
int main() { int v[10];
for (int *p = v; p != &v[10]; p++) { *p = 0;
} return 0;
}
int main() { int v[10];
for (int i = 0; i < 10; i++) { v[i] = 0;
} return 0;
}
!244
有⽐比較好嗎?