• 沒有找到結果。

指標

N/A
N/A
Protected

Academic year: 2022

Share "指標"

Copied!
24
0
0

加載中.... (立即查看全文)

全文

(1)

講師: 李根逸 (Ken-Yi Lee), E-mail: feis.tw@gmail.com

指標

【第七講】

!225

(2)

課程⼤大綱

沒有指標的世界

[P.227]

指標使⽤用的情境 [P.228]

指標的基礎

指標變數宣告 (type *) [P.232]

取址運算⼦子 (&) [P.233]

間接運算⼦子 (*) [P.234]

指標與函式

函式傳值呼叫 [P.235]

函式傳址呼叫 [P.236]

傳值還是傳址 ? [P.238]

指標與陣列

[P.240]

!226

(3)

沒有『指標』的世界

『指標』是⼀一種資料型態,⽤用來儲存『記憶體位址』

⼀一般情況下,我們是不需要『指標』這種東⻄西的。

但是 C 語⾔言中為了解決某些問題或提昇程式效率,

在某些情況下需要使⽤用『指標』。

先來看看有什麼問題是以前不能處理的:

在被呼叫的函式中修改⾮非該函式內的變數內容 直接複製陣列

例如呼叫函式時要將陣列作為函式引數複製到函式內

直接複製字串

例如呼叫函式時要將字串作為函式引數複製到函式內

動態配置記憶體

例如要動態改變陣列的⼤大⼩小

!227

(4)

指標使⽤用的情境 [1]

在被呼叫的函式中修改⾮非該函式內的變數內容

當我們將變數送⼊入函式執⾏行時,是使⽤用『傳值呼叫』的

⽅方式。意味著變數的值會被當做引數值⽽而複製⼀一份進函 式內部做為參數。在函式內部對參數做任何的變動不會 改變到原本的引數值:

void addone(int n) {
 n = n+1;


}


int main() {
 int a = 3;


addone(a); /* 複製 a 的值給 addone */


printf(“%d”, a);


return 0;


}

addone 來說他只是得到⼀一個整數的複製 品,無法知道整數原本存放的地⽅方或來源

!228

(5)

指標使⽤用的情境 [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;


}

(6)

指標使⽤用的情境 [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 ⾵風格字串是利⽤用陣列的⽅方式儲存

(7)

指標使⽤用的情境 [4]

動態配置記憶體

動態改變陣列⼤大⼩小

!

!

!

我們在儲存資料前需要先配置記憶體,如果在⼀一開始 未知資料的總數時,我們要怎麼配置?

例如要寫⼀一個程式,讓使⽤用者輸⼊入任意筆資料後儲存起 來要怎麼辦?

!231

int v[5];


int n[10];


v = n; // 語法錯誤

(8)

指標變數宣告 (type *)

指標 (Pointer) 是 C/C++ 語⾔言的⼀一⼤大特⾊色,是⼀一 種儲存記憶體位址的資料型態

指標變數宣告語法 :

資料型態 *變數名稱

;

表⽰示變數名稱内存放的是⼀一存放該資料型態值的記憶體位址

宣告指標變數與其他變數的差別 :

int count;

!

!

int *countAddr ;

(int)


??

count

(int *)


??

countAddr

!232

(存 int 值)

(存存 int 值的位址)

2293620

2293624

(9)

取址運算⼦子 (&)

變數宣告後依照資料型態會佔據⼀一定的記憶體空間,

並具有⼀一個在記憶體的位址。我們可以利⽤用取址運算

⼦子 (&) 去取得變數的記憶體位址 :

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

(10)

相對地,我們可以利⽤用間接運算⼦子 (*) 從位址取得 位於該位址的變數

(別跟宣告指標⽤用的*搞混)

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

(11)

函式傳值呼叫

當我們將變數送⼊入函式執⾏行時,是使⽤用『傳值呼叫』

的⽅方式。意味著變數的值會被當做引數值⽽而複製⼀一份 進函式內部做為參數。在函式內部對參數做任何的變 動不會改變到原本的引數值:

void addone(int n) {
 n = n+1;


return;


}


int main() {
 int a = 3;


addone(a); /* 複製 a 的值給 addone */


printf(“%d”, a);


return 0;


}

addone 來說他只是得到⼀一個整數,無法 知道整數原本存放的地⽅方或來源

!235

(12)

函式傳址呼叫

我們可以將變數的『記憶體位址』作為引數值複製進

⼊入函式執⾏行。此時在函式內部對參數⽤用『間接運算⼦子』

指定新的值時就會改變原本的變數值。

其實這也是函式傳值呼叫的⼀一種,只是該值是個位址

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

(13)

【範例】交換與排序

試寫⼀一函式 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

(14)

傳值還是傳址?

簡⾔言之,在 C 語⾔言裡如果你想要讓其他函式可以幫 你修改變數的值時就需要傳該變數的位址

傳值的設計讓不同函式之間的⾮非全域變數是完全沒有關 係的、是不會互相影響的,可以避免污染與干涉!

要藉由『呼叫函式』來修改⺫⽬目前所在函式的變數值有兩 個⽅方式:

接收回傳值:

!

傳送變數的位址

!

能傳值就傳值,可以避免函式之間的間接汙染!

變數位址就好像變數真實的名字⼀一樣,得到的函式可以 為所欲為!

var = func();

func(&var); 傳陣列只能傳址

!238

(15)

【範例】讀出與印出

試寫兩函式,其中 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

接收回傳值 傳送變數位址

(16)

指標與陣列 [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

(17)

指標與陣列 [2]

指標與陣列的關係:

陣列型態可以轉型為指向該陣列第⼀一個元素的指標

指標可以像陣列⼀一樣使⽤用 [] 運算⼦子來存取記憶體,此 時會以該指標指向的變數做為陣列的第⼀一個元素,以該 指標指向的變數資料型態做為陣列元素型態

指向陣列元素的指標可以對整數做 + 或 - 運算:

加 N:指標會指向往後⾛走 N 個元素 減 N:指標會指向往前⾛走 N 個元素

對兩個指向同陣列元素的指標做 - 的算術運算:

得到兩個指標分別指向的元素差了幾號

!241

(18)

【範例】⽐比⼤大⼩小

寫⼀一個可以讓使⽤用者輸⼊入五個整數,回傳最⼤大值的函 式 max1v:

int

max1v( int * );

!

!

寫⼀一個可以讓使⽤用者輸⼊入任意個整數,回傳最⼤大值的 函式 max2v:

int

max2v( int *, int );

可以⽤用指標變數來接收陣列的位址值

!242

【範例】max.cpp 除了要傳⼊入起始位址,還要傳⼊入元素個數

(19)

【範例】⽤用指標對陣列存取 [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

(20)

【範例】⽤用指標對陣列存取 [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

有⽐比較好嗎?

(21)

!245

(22)

!246

(23)

!247

(24)

!248

參考文獻

相關文件

主要指標 單位 參考期.

主要指標 單位 參考期.

主要指標 單位 參考期.

主要指標 單位 參考期.

主要指標 單位 參考期.

主要指標 單位 參考期.

主要指標 單位 參考期.

主要指標 單位 參考期.