總網頁瀏覽量

2019年1月11日 星期五

【C語言觀念複習筆記】指標陣列(Array of pointer) 相關觀念之範例解析說明


此篇文章將記錄一下關於C語言中的指標陣列的相關簡單觀念

因為是針對初學者或已將相關觀念淡忘的人作為複習參考, 可能有些地方的用詞不會太過於精確,  因此盡量以觀念上的理解為主要目標

範例程式如下:

#include "stdio.h"

int main()
{
 /*--- part 1 ---*/
 char *name[]={"jim", "harry", "ken"};


 /*--- part 2 ---*/
 printf("%s \n", *(name)); // result: jim


 /*--- part 3 ---*/
 printf("%c \n", **(name)); // result: j


 /*--- part 4 ---*/
 printf("%s \n", *(name)+1); // result: im


 /*--- part 5 ---*/
 printf("%s \n", *(name+1)); // result: harry


 /*--- part 6 ---*/
 printf("%s \n", *(name+1)+2); // result: rry
 printf("%c \n", *(*(name+1)+2)); // result: r

  return 0;

}


執行結果如下圖:




以下將按照code裡面各個部分進行講解(若有筆誤請告知)

---

part 1:
char *name[]={"jim", "harry", "ken"};
上面這段C code的char *name[]={"jim", "harry", "ken"}

即宣告了三個位於連續位址的字串指標陣列元素(name[0], name[1], name[2]),

並分別令其指向三筆字串資料:"jim", "harry", "ken" (也就是將這些字串資料的address放到陣列元素內)


---

part 2:
printf("%s \n", *(name)); // result: jim
在C語言中, 陣列名稱代表該陣列的第一個陣列元素的address (也就是該串陣列的起始位址)

所以name就是name[0]的address

所以*(name)就是name[0]元素的內容資料, 這個內容資料就是"jim"字串的第一個字元資料的address

而由於printf function裡面的%s吃的是字串資料的address, 並且會印出包含該address開始直到遇到字串結束字元(\0)為止的所有字元

所以printf("%s \n", *(name))會直接印出jim

---

part 3:
printf("%c \n", **(name)); // result: j
承接part 2舉一反三可得知

**(name)就是"jim"字串資料中起始的字元資料('j')的address上面的資料(好像在繞口令...其實就是'j'啦)
而由於printf function裡面的%c是直接吃該address上面那個byte的資料(而非像%s那樣是吃字串資料的address並印出結束字元前的所有字元)

所以printf("%c \n", **(name)) 會印出j

---

part 4:
printf("%s \n", *(name)+1); // result: im
已經由part 2知道*(name)就是"jim"字串資料的起始address (也就是j這個字元資料的address)

而因為name[0]裡面的這個內容是char資料型態的address, 所以當對這address做加法時, 該address的值會以字元資料長度(byte)為單位增加

(稍微題外話, 粗略來說, 電腦系統是一個byte對應一個address(此方式稱為byte addressing), 所以在對指向字元資料型態的位址值+1時, 實際位址值也是增加1。如果是32位元的整數型態資料位址, 其位址值加1時, 就會增加4, 因為32除以8等於4)

所以當我們把*(name)+1,就等同指向了j之後的下一個字元, 也就是i  (註:運算子優先權順序是先*(name)然後才是+1)

printf function裡面的%s如同前面的解釋

所以printf("%s \n", *(name)+1); 會印出im

---

part 5:
printf("%s \n", *(name+1)); // result: harry
name是name[0]的address

name+1相當於name[1]的address (對字串陣列元素address加1, 就變成下一個字串陣列元素的address)

所以*(name+1)即是name[1]的內容資料,也就是harry字串資料的起始字元資料(h)的address

故printf("%s \n", *(name+1)) 會印出harry

---

part 6:
printf("%s \n", *(name+1)+2); // result: rry
printf("%c \n", *(*(name+1)+2)); // result: r
由part 5可知*(name+1)就是第二陣列元素(name[1])中的字串資料harry的起始字元資料(h)的address (有點繞口令...)

而對這個address進行加法,就會讓這個address變成是後面的字元資料的address

所以*(name+1)+2就代表著harry字串資料的第三個字元資料(r)的address,也就是h之後的第2個byte資料的address

所以printf("%s \n", *(name+1)+2)會印出rry


再舉一反三思考後可得知  *(*(name+1)+2)就是harry字串資料的第三個字元資料(r)的address的資料內容, 也就是r啦

所以printf("%c \n", *(*(name+1)+2))會印出r (如前面解釋過的, %c是直接吃資料本身, 而不是資料的address)

---

再來探討用指標陣列來儲存字串的一個好處

由範例程式可看出

我們是用 char *name[] = {"jim", "harry", "ken"}; 的方式來配置儲存這些字串的空間

這種作法,compiler會針對我們宣告的字串的字元數,自動配置出足夠容納這些字元的記憶體空間


但如果是純粹使用陣列來放置這些資料

必須用最大的字串數作為統一標準來配置出給每個字串的空間,但並非每個字串都像最大字串有這樣多的資料 (除非現在的compiler是否已聰明到可以自己優化這部分...)

char name[3][6] = {"jim", "harry", "ken"};

其中[6]是因為harry加上結數字元\0共須要有6個byte,但是jim和ken則只須要4個byte就可完整儲存,但是固定宣告了[6]給這兩個字串資料,有可能總共就多浪費了4個byte(各浪費2個)
除非現在的compiler已聰明到可以自己優化這部分

終於打完這篇啦!

【若需要基礎C語言 與 嵌入式系統技術輔導課程 可來信洽談合作方式: iws6645@gmail.com,亦可先點擊參考這篇介紹文章

2 則留言:

  1. 回覆
    1. 非常感謝您的鼓勵
      我才疏學淺,還有很多地方要學習,若有寫得不夠好的地方請大家見諒

      刪除