el 1012 程式設計

89
EL 1012 程程程程 Instructor Po-Yu Kuo 程程程程程 Program Design Ch. 12 程程程 (pointer)

Upload: alagan

Post on 11-Jan-2016

50 views

Category:

Documents


0 download

DESCRIPTION

EL 1012 程式設計. Program Design. Instructor : Po-Yu Kuo 教師 : 郭柏佑. Ch. 12 指位器 (pointer). 本章簡介. 指位器 (pointer) 也是一種變數 , 但是此種變數儲存的並非一般數值 , 而是記憶體位址。 當指位器變數所存的值是位址時 , 我們稱此指位器指向該位址所表示的記憶體空間。 指位器可以用來表示複雜的資料的結構 , 或者有效率的存取陣列的資料。. 記憶體位址. 記憶體位址. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: EL 1012 程式設計

EL 1012程式設計

Instructor : Po-Yu Kuo

教師:郭柏佑

Program Design

Ch. 12 指位器 (pointer)

Page 2: EL 1012 程式設計

2

本章簡介 指位器 (pointer) 也是一種變數 , 但是此種變

數儲存的並非一般數值 , 而是記憶體位址。

當指位器變數所存的值是位址時 , 我們稱此指位器指向該位址所表示的記憶體空間。

指位器可以用來表示複雜的資料的結構 , 或者有效率的存取陣列的資料。

Page 3: EL 1012 程式設計

3

記憶體位址

Page 4: EL 1012 程式設計

4

記憶體位址 但是 , 因為使用指位器的過程中 , 會涉及許多

記憶體位址與空間的概念 , 造成不了解此種概念的讀者學習上的困難。

指位器是專門用來存放記憶體位址的變數。

我們可以把記憶體的內部 , 想像成一個一個排列整齊可用來裝填資料的小格子 , 每個小格子的大小都相等 (1 byte) 。

Page 5: EL 1012 程式設計

5

本章簡介 我們可以利用 & 算符 , 找出變數在記憶體中

被配置的位址 , 語法如下:

在任何變數名稱前加上 & 算符後 , 就表示該變數在記憶體中的位址 , 而不是變數值。

利用 printf ( ) 配合輸出格式 %p, 就可以輸出以 16 進位數值表示的記憶體位址 , 如下:

Page 6: EL 1012 程式設計

6

記憶體位址

Page 7: EL 1012 程式設計

7

記憶體位址

因為 4 個變數都是整數型別 , 所以在記憶體中各佔 4 bytes 。

在記憶體中 , 每個 byte 都有獨立的位址 , 以代表該空間在記憶體中的位置。

Page 8: EL 1012 程式設計

8

記憶體位址

4 bytes

Page 9: EL 1012 程式設計

9

指位器的基本用法

Page 10: EL 1012 程式設計

10

指位器的基本用法 指位器的運用 , 主要是在一種稱為『指向』的

觀念。

當指位器中儲存著一個變數的位址時 , 我們稱此指位器指向該變數。

宣告的語法如下:

Page 11: EL 1012 程式設計

11

指位器的基本用法 資料型別:指位器的型別 , 必須與該指位器所指

向的變數型別相同。

* :稱為『間接』算符 (indirection), 意指指示器存放的是儲存變數的位址 , 不能直接取得變數的值 , 必須在指位器上使用間接算符才能取得變數值。

宣告指位器變數時 , 需在變數名稱前加上此算符。

Page 12: EL 1012 程式設計

12

指位器的基本用法 指位器:也就是指位器變數的名稱 , 命名的原則

和一般變數相同。

例如:

Page 13: EL 1012 程式設計

13

指位器的基本用法 如此一來 , ptr 便是指向 number 的指位器了。

Page 14: EL 1012 程式設計

14

指位器的空間大小 由於指位器記錄的是記憶體位址 , 所以不管指

位器宣告成何種型別 , 在記憶體中都會配置相同的空間 (4 bytes), 我們可以利用 sizeof ( ) 來驗證 , 如下:

Page 15: EL 1012 程式設計

15

指位器的空間大小

Page 16: EL 1012 程式設計

16

指位器的空間大小

Page 17: EL 1012 程式設計

17

設定指位器的值 指位器也是一個變數 , 所以和變數一樣可以設

定其值。

如果我們將指位器採用與變數相同的設定方式 , 如下:

Page 18: EL 1012 程式設計

18

設定指位器的值 由於 ptr 本身未被設定初始值 , 所以其值有可

能是任何數值 , 換言之 , ptr 可能指向任何的記憶體空間。

例如其它程式、甚至作業系統正在使用的記憶體空間。此時若將數值 35 指定到此不知位於何處的記憶體空間 , 有可能導致程式執行發生錯誤。

所以 , 指位器不能使用上述的方法來設定其值。

Page 19: EL 1012 程式設計

19

設定指位器的值 當我們要將數值指定給指位器時 , 我們需要以

下面的步驟來完成:

1. 事先宣告一個同型別的變數 , 再讓指位器指向此變數位址:

2. 完成上述指定動作之後 , 就可以利用 * 算符 , 從指位器中取出數值。也就是說 *ptr 的值會等於 10 。

Page 20: EL 1012 程式設計

20

設定指位器的值 上面步驟完成後 , 指位器與變數間的關係如下

圖:

我們先宣告 3 種不同型別的指位器 , 讓其分別指向 3 個變數 , 然後輸出其值。如下:

Page 21: EL 1012 程式設計

21

設定指位器的值

將指位器指向變數

Page 22: EL 1012 程式設計

22

設定指位器的值

Page 23: EL 1012 程式設計

23

指位器的轉型 指位器只能指向同型別的變數位址 , 絕對不能有

以下的情形:

若想將指位器指向不特定型別的變數 , 可使用 void 這個特殊的型別來宣告指位器變數。

void 型別的指位器可指向任何型別。

Page 24: EL 1012 程式設計

24

指位器的轉型 但要取用指位器所指的變數值時 , 必須做強制型

別轉換才能取出正確的值:

Page 25: EL 1012 程式設計

25

指位器的運算 因為指位器內儲存的是位址 , 所以對指位器做加

減 , 就等於是將所存的位址做加減 , 所得到的將會是鄰近的位址。

Page 26: EL 1012 程式設計

26

指位器的運算

Page 27: EL 1012 程式設計

27

指位器的運算

Page 28: EL 1012 程式設計

28

指位器的運算 對 ptri 而言 , 加減 1 時 , 位址值是加減 4

(bytes); 但對 ptrc 加減 1 時 , 位址值則只加減 1 (bytes) 。

這是因為對指位器做加減時 , 是以指位器型別所佔的 byte 數為單位進行加減 , 所以 int 型別一次就是加減 4 、 char 型別一次是加減 1 。

同理 float 、 double 型別的指位器加減時 , 位址值分別是加減 4 、 8 (bytes) 。

Page 29: EL 1012 程式設計

29

指位器的運算

要取得指位器鄰近位址內的值 , 可直接在前面加上 * 算符 , 如下:

Page 30: EL 1012 程式設計

30

指位器的運算 由於 * 算符的優先權高於算術算符 , 所以我們

要在 ptri+1 外面加上括號 , 否則會變成 *ptri 的值再加上 1 。

注意 , 由於指位器的值是變數的位址 , 所以任意兩個指位器相加減 , 等於是兩個記憶體位址相加減 , 其結果是沒有意義的。

Page 31: EL 1012 程式設計

31

使用指位器的好處 變數可以直接儲存數值 , 所以需要數值時可以直

接讀取變數值。

而指位器儲存的卻是存放變數值的記憶體位址 , 所以如果要讀取數值時 , 必須透過指位器內儲存的位址去尋找記憶體中位址的空間 , 才能讀到數值。

但是 , 指位器的用途並非只是單純的存取數值 , 指位器的主要用途如下:

Page 32: EL 1012 程式設計

32

使用指位器的好處 指位器在某些方面可以代替陣列的使用。

陣列是在記憶體中一段被編譯器配置的連續空間 , 也就是說這些空間的位址是連續的。

如果採用指位器儲存位址的觀念來看 , 我們把指位器作加減的運算 , 也是可以讀取到儲存在連續記憶體空間內的資料。

Page 33: EL 1012 程式設計

33

使用指位器的好處 指位器適用函式間的傳址運算。

當函式間需要傳遞數值時 , 如果使用指位器 , 傳遞到函式中的引數不再只是將數值複製到函式中做處理 , 而是將數值儲存的位址傳到函式 , 由函式直接存取。

此時 , 就算函式無傳回值 , 引數的變數值也會經過函式的運算而有所改變 , 這種做法可以改善函式只能有一個傳回值的缺點。

Page 34: EL 1012 程式設計

34

使用指位器的好處 有些特殊的資料結構 (Data Structure) 及相關演

算法 (Algorithm), 都需利用指位器才能順利實作。

Page 35: EL 1012 程式設計

35

動態記憶體配置 (Dynamic Memory Allocation)

Page 36: EL 1012 程式設計

36

動態記憶體配置 甚麼是動態記憶體配置 ?

當我們在程式開始宣告一個變數時 , 編譯器就會所需的記憶體空間來存放這個變數的值。

只要在程式執行期間 , 該變數會佔用一塊固定大小的記憶體空間 , 就算程式想臨時擴大或縮小佔用的的空間也不行 , 所以稱之為靜態記憶體配置。

Page 37: EL 1012 程式設計

37

動態記憶體配置 動態記憶體配置則是:當程式執行到一半 , 發現

它需要一塊記憶體空間來存放資料 , 才向系統索取一塊沒有被其他程式使用的記憶體空間。

當此記憶體空間用不到時 , 也可隨時將之釋放供其它程式使用 , 如此可大幅的提高記憶體的使用效率。

Page 38: EL 1012 程式設計

38

動態記憶體配置 的語法 向系統索取記憶體區塊主要是透過 malloc ( ) 函

式來做 , 此函式的原型宣告放在 stdlib.h, 使用時要將此含括檔含括進來。呼叫的語法如下:

資料型別:新配置空間的型別。

個數:新配置多少同型別的變數空間。

Page 39: EL 1012 程式設計

39

動態記憶體配置 的語法 如果記憶體空間不夠分配時 , 此函式會傳回

NULL 。

記憶體配置成功後 , 則會傳回所配置記憶體空間的起始位址 , 所以我們要先宣告一個指位器來接受傳回的位址。如下:

Page 40: EL 1012 程式設計

40

動態記憶體配置 的語法 上面的範例 , 是初始化動態記憶體配置的寫法 ,

也就是在宣告指位器的同時 , 便配置記憶體空間。

配置成功後 , 使用 num 和 code 時 , 就和使用以 int num [10]; 與 char code [5]; 取得的陣列記憶體空間一樣 , 可任意儲存資料於其中。

空間使用完畢 , 可用 free ( ) 函式將配置的記憶體釋放。

Page 41: EL 1012 程式設計

41

動態記憶體配置的特色:有需要 , 才配置 動態記憶體配置的主要特色 , 是我們不必在程式開始就先配置記憶體空間。

而是可在程式流程執行到一半 , 發現有需要時 , 才配置所需的空間。例如:

Page 42: EL 1012 程式設計

42

動態記憶體配置的特色:有需要 , 才配置 不管是哪種配置方式 , 記憶體空間使用完畢後 ,

一定要以 free ( ) 的語法將配置的記憶體空間釋放 , 這是一個很重要的習慣 , 可以讓記憶體的使用更具效率。

由於寫程式時無法預知使用者想計算多少個數字。

因此可設計成在使用者輸入數字個數後 , 再動態配置所需的記憶體空間來存放數值。程式如下:

Page 43: EL 1012 程式設計

43

動態記憶體配置的特色:有需要 , 才配置

使用者輸入數字個數 n

動態配置 n 個記憶體空間

Page 44: EL 1012 程式設計

44

動態記憶體配置的特色:有需要 , 才配置

Page 45: EL 1012 程式設計

45

動態記憶體配置的特色:有需要 , 才配置

Page 46: EL 1012 程式設計

46

指位器與陣列

Page 47: EL 1012 程式設計

47

指位器與陣列 指位器儲存的是記憶體的位址 , 而陣列是記憶體中一段位址連續的記憶體空間。

用指位器代替陣列 使用動態配置的空間來儲存資料 使用指位器來儲存字串

指位器陣列 自行調整字串長度 利用指位器陣列作字串排序

Page 48: EL 1012 程式設計

48

用指位器代替陣列 雖然指位器是用來儲存位址的變數 , 但我們可以採用動態記憶體配置的方法 , 配置一塊記憶體空間給指位器。

因為我們可以設定配置多少空間 , 就如同宣告陣列時 , 設定陣列容量一樣。

使用動態記憶體配置配置一塊空間 , 並讓指位器 ptr 指向此空間時。

Page 49: EL 1012 程式設計

49

用指位器代替陣列 若把這個空間看成是個陣列 , 則 ptr+i 就相當於

陣列中 , 第 i 個陣列元素的位址。

並且可以利用 *(ptr+i) 的方式 , 取得取得第 i 個陣列元素的數值。

如以下範例中 , 我們將利用指位器代替陣列 , 儲存 5 個由鍵盤輸入的數值 , 並找出最大值:

Page 50: EL 1012 程式設計

50

使用動態配置的空間來儲存資料

動配置 5 個記憶體空間的位址

利用 *(ptr+i) 的方式 , 取得第 i 個陣列元素的數值

Page 51: EL 1012 程式設計

51

使用動態配置的空間來儲存資料

Page 52: EL 1012 程式設計

52

使用指位器來儲存字串 第 11 章有提過以指位器設定字串的方法。在

此我們將繼續做詳細的說明 , 語法如下:

宣告完畢後 , 會自動的配置相當於字串內容加上結束字元的記憶體空間 , 來儲存字串。

Page 53: EL 1012 程式設計

53

指位器陣列 陣列元素也可以是指位器 , 這種陣列叫做 " 指

位器陣列 " 。指位器陣列宣告語法如下:

例如:char *p[m];

這行敘述的意思是說 p 是一個陣列 , 其元素是字元的指位器:

Page 54: EL 1012 程式設計

54

指位器陣列

字元型別的指位器指向字串時 , 所儲存的是字串第一個字的位址 , 所以字串長度對指位器並無影響。

而當我們利用指位器陣列儲存多個字串時 , 由於記憶體空間是連續排列 , 所以各字串會緊接存放。

Page 55: EL 1012 程式設計

55

指位器陣列 也就是下一字串的起始字元會接在上一字串結束字元之後 , 我們可觀察以下範例的執行結果:

Page 56: EL 1012 程式設計

56

自行調整字串長度

Page 57: EL 1012 程式設計

57

自行調整字串長度

字串 " 智 " 一個中文字為 2 個字元 , 再加上結束字元 , 共佔了 3 個字元 , 所以記憶體位址由 004020B0~004040B3 共 3 個字元。

我們可以看出所有記憶體空間都是連續的。

Page 58: EL 1012 程式設計

58

指位器與函式

Page 59: EL 1012 程式設計

59

指位器與函式 變數可以在函式中作數值的傳遞 , 指位器當然

也可以被傳遞。

但是由於兩者所儲存資料的意義完全不同 , 所以傳遞給函式的效用也不相同 , 接下來我們將討論兩者的不同。 傳值呼叫 (Call by Value) 傳址呼叫 (Call by Address)

Page 60: EL 1012 程式設計

60

傳值呼叫 (Call by Value) 我們在函式一章討論過函式與 main ( ) 之間的傳引數與傳回值的方法。

變數在函式之間的傳遞方式 , 是將呼叫者 ( 例如 main ( )) 中的變數值 , 複製到被呼叫函式中的變數。

所以就算 main ( ) 與被呼叫函式使用相同的變數名稱 , 事實上兩個變數值卻是儲存在不同的記憶體空間 , 我們可由以下的程式來證明:

Page 61: EL 1012 程式設計

61

傳值呼叫 (Call by Value)

Page 62: EL 1012 程式設計

62

傳值呼叫 (Call by Value)

印出交換後 a, b 的值

Page 63: EL 1012 程式設計

63

傳值呼叫 (Call by Value)

Page 64: EL 1012 程式設計

64

傳值呼叫 (Call by Value) 如執行結果所示 , 在 main ( ) 中的 a 、 b 變

數與在函式中的 a 、 b 變數儲存位址完全不同。

所以在 swap ( ) 函式中做交換 , 只對函式中的 a 、 b 變數值有效 , 對 main ( ) 函式中的 a 、 b 完全沒有影響。

Main ( ) 的 a 、 b 變數值完全不受 swap ( )函式的影響 , 因為實際傳到函式中的只是 a 、 b 的值而已。

Page 65: EL 1012 程式設計

65

傳值呼叫 (Call by Value) 我們稱此種引數傳遞方式為傳值呼叫 (Call by

Value) 。

Page 66: EL 1012 程式設計

66

傳址呼叫 (Call by Address) 變數儲存的資料是數值 , 而指位器儲存的資料

是位址。

如果我們呼叫函式時 , 將欲被當成引數的變數以位址的型態傳遞 , 也就是在變數前加上求址算符 &, 然後在定義函式的部分以指位器型態宣告引數 , 這樣就能將變數的位址傳到函式。

Page 67: EL 1012 程式設計

67

傳址呼叫 (Call by Address)

Page 68: EL 1012 程式設計

68

傳址呼叫 (Call by Address)

將變數 a, b 的位址傳入函式 swap( )

Page 69: EL 1012 程式設計

69

傳址呼叫 (Call by Address)

Page 70: EL 1012 程式設計

70

傳址呼叫 (Call by Address) 如執行結果所示 , main ( ) 用 & 算符取得變數

a 、 b 的值傳遞到 swap ( ) 函式中 , 而 swap ( ) 函式中則用指位器變數 a 、 b 來接受傳遞進來的位址值。

所以在 swap ( ) 函式的指位器 a 、 b 就是指向 main ( ) 中的變數 a 、 b, 因此在 swap ( ) 函式中用指位器來交換數值後 , main ( ) 中的 a, b 變數值也跟著被交換了。

Page 71: EL 1012 程式設計

71

傳址呼叫 (Call by Address)

Page 72: EL 1012 程式設計

72

傳址呼叫 (Call by Address) 這種將位址當成引數來呼叫函式的方式 , 我們

稱之為傳址呼叫 (Call by Address) 。

指位器不只能當引數 , 也可以當傳回值。

return 只能傳回單一變數 , 當函式想傳回一串字串或陣列時 , 我們就可以傳回這個字串或陣列的起始位址 , 然後在主程式中以一個字元指位器來接收字串的起始位址。

Page 73: EL 1012 程式設計

73

傳址呼叫 (Call by Address) 要傳回一個位址 , 我們要在宣告函式時 , 就將

其傳回值宣告為指位器型別 , 宣告方式如下:

如此一來 , 函式傳回值就會是記憶體位址值。

Page 74: EL 1012 程式設計

74

雙重指位器

Page 75: EL 1012 程式設計

75

雙重指位器 我們已經知道 , 指位器儲存的是變數的位址 ,

而指位器也有自己的位址。

所以我們可以宣告一個指位器指向另一個指位器 , 我們稱之為雙重指位器 , 第一重指位器是儲存了變數的位址 , 而第二重指位器則是儲存了變數位址的位址。 雙重指位器的宣告與初始值設定 雙重指位器的動態記憶體配置

Page 76: EL 1012 程式設計

76

雙重指位器的宣告與初始值設定 雙重指位器的宣告語法如下:

資料型別:指位器內所儲存資料的型別。

** :雙重取址算符 , 當指位器加上此算符時 , 所代表就是雙重指位器儲存位址內的數值。

指位器:代表儲存變數位址的位址。

Page 77: EL 1012 程式設計

77

雙重指位器的宣告與初始值設定 例如要宣告一個指向字元型別指位器的雙重指

位器:

我們將一個變數的位址指定給指位器 , 然後再將此指位器變數的位址指定給另一個指位器 , 就可以變成雙重指位器的型態。

我們同樣可用 printf ( ) 函式配合 %p 格式 , 從螢幕輸出雙重指位器內所儲存的位址 , 如下:

Page 78: EL 1012 程式設計

78

雙重指位器的宣告與初始值設定

Page 79: EL 1012 程式設計

79

雙重指位器的宣告與初始值設定

雙重指位器的宣告語法也可寫成:

Page 80: EL 1012 程式設計

80

雙重指位器的宣告與初始值設定

雙重指位器的宣告語法也可寫成: ptr 是一個指位器 , 它指向一個字元指位器。

Page 81: EL 1012 程式設計

81

雙重指位器的動態記憶體配置 假設我們想要動態配置一個 int array [10] [20]

陣列空間 , 可以用如下的方式來配置:

第 1 行中 , 所配置的是 “列 (Row)" 的空間。

第 2 、 3 行的迴圈 , 則是依列配置“行(Column)” 的空間。請記得在空間使用完畢後 , 同樣必需用 free (array); 來釋放空間。

Page 82: EL 1012 程式設計

82

雙重指位器的動態記憶體配置 下面這個範例程式 , 就是用動態配置的方式取

得一塊記憶體空間來儲存由鍵盤輸入的字串 , 然後分別替每個字串加上行號後輸出。

輸入字串時 , 只要輸入 “ qq” 就表示輸入完畢。程式內容如下:

Page 83: EL 1012 程式設計

83

雙重指位器的動態記憶體配置

動態配置一個 陣列array [100] [10]

Page 84: EL 1012 程式設計

84

雙重指位器的動態記憶體配置每一列 (Row) 有 10 行的長度

指位器指向輸入的字串

Page 85: EL 1012 程式設計

85

雙重指位器的動態記憶體配置

Page 86: EL 1012 程式設計

86

雙重指位器的動態記憶體配置

Page 87: EL 1012 程式設計

87

上機練習

Page 88: EL 1012 程式設計

88

將字串內的空格移除 設計一程式可讓使用者輸入一個字串 , 之後利

用指位器的運作方式將字串內的空格移除後印出來 .

Page 89: EL 1012 程式設計

89

Homework#41. 由鍵盤輸入一任意字串 , 然後輸出英文大小寫對調的結果字串。 ( 注意 : 使用者可以輸入空格或其他符號 ) Hints: 1. 利用 gets( ) 讀入字串 .

2. ASCII碼中對應英文的位置 .

2. 利用雙重指位器 , 從螢幕輸出以下陣列的內容 . array[3][5]={{2,3,4,5,6},{7,8,9,10,11},{12,13,14,15}}

3. 設計一程式可讓使用者輸入一個字串 , 之後利用指位器的運作方式將字串內的空格移除後印出來 .

繳交期限 : 2014/5/14