程式設計 博碩文化出版發行
第九章 結構與其它自訂資料型態 課前指引 陣列是一個集合,可以用來記錄一組型態相同的資料,然而考慮一個狀況,例如您要同時記錄多筆資料型態不同的,資料,此時陣列就不適合使用。這時就可以使用C的結構型態(structure),結構可以集合不同的資料型態,並形成一種新的資料型態。
章節大綱 9-1 結構 9-4 其它自訂資料型態 9-2 鏈結串列 9-5 專案程式簡介 9-3 函數與結構 備註:可依進度點選小節
9-1 結構 結構是允許使用者將多筆不同資料型態的資料組成一組具有意義新的資料型態,它是一種衍生資料型態(derived data type),而不是變數,因此結構宣告後,只是告知編譯器產生一種新的資料型態,接著還必須宣告結構變數,才可以使用結構來存取其成員。
9-1 結構 結構變數的宣告 結構的架構必須具有結構名稱與結構項目,而且必須使用關鍵字struct來建立,宣告方式如下所示: { 資料型態 結構成員1; 資料型態 結構成員2; …… }結構變數1; 或 結構型態名稱 結構變數2;
9-1 結構 結構變數的宣告 在結構定義中可以使用基本的變數、陣列、指標,甚至是其它結構成員以形成巢狀結構的宣告等。下面是結構定義的範例: struct person { char name[10]; int age; int salary; }; /*務必加上分號*/
9-1 結構 結構變數的宣告 在在定義了結構之後,就等於定義了一種新的資料型態,並可以依下列的宣告方式,宣告結構變數: 定義完新的結構型態及宣告結構變數後,就可以開始使用所定義的結構成員項目。只要在結構變數後加上成員運算子"."與結構成員名稱,就可以直接存取該筆資料,語法如下: struct student s1, s2; 結構變數.項目成員名稱;
9-1 結構 範例CH09_01.c
9-1 結構 執行結果 程式解說 第6~10行同時宣告結構型態與變數。 第13行輸入長與寬的值。 第15行利用點運算子來輸出結構變數中的各項值。
9-1 結構 範例CH09_02.c
9-1 結構 執行結果 程式解說 第6~11行不定義結構型態,卻宣告結構變數。 第11行設定結構變數成員時,必須以大括號括住。 第13~15行輸出各項結構成員。
9-1 結構 範例CH09_03.c
9-1 結構 執行結果 程式解說 第4~8行宣告結構型態rectangle,其中width成員為指標型態。 第15行設定長度為20。 第16行將rec1.width指向實體位址。 第20行計算並輸出長方形面積。
9-1 結構 結構指標 結構指標所儲存的內容是位址,因此要存取其指定結構變數的成員,必須先指定結構變數的位址給指標,才能間接存取。例如有一結構內容如下: 接著宣告一個結構指標如下: struct animal { float weight; int age; }tiger; struct animal *getData; /*宣告指向animal結構的指標getData*/ getData = &tiger; /*將指標指向結構變數tiger*/
9-1 結構 範例 CH09_04.c
9-1 結構 範例 CH09_04.c
9-1 結構 執行結果 程式解說 第6~10行宣告結構型態book,其中有title字串與整數變數price兩個成員。 第12行宣告兩個book結構變數m1與m2。 第13行宣告結構指標ptr,並在第25行初始化指標,將ptr指向m1結構變數。
9-1 結構 範例 CH09_05.c
9-1 結構 執行結果 程式解說 第4~8行宣告結構型態。 第13行宣告rec1為結構指標。 第22行以(->)運算子存取指標成員及其他資料成員,並計算面積值。
9-1 結構 動態配置結構變數 第一步就是要定義資料結構 product,其中包含幾個欄位:產品名稱 char name[10] 為 10 個字元的字元陣列、單價為int price,是整數型態的變數,以及數量 int amount為整數型態的變數。另外宣告product *list 是一個指向 product 型態的指標。如下所示: list=(product *)malloc(sizeof(product));
9-1 結構 動態配置結構變數 本在結構變數中存取各分量時是以「.」運算子存取。換句話說,若是程式宣告 list 變數為 product list 而非 product *list,則存取其成員變數應為 list.name[10],list.price,以及list.amount。 然而以product *list 宣告並以動態方式配置記憶體時,若要存取各成員變數,則必須改以「->」運算子來存取。亦即list->name[10],list->price,以及list->amount。
9-1 結構 範例CH09_06.c
9-1 結構 範例CH09_06.c
9-1 結構 執行結果 程式解說 第5~9行宣告商品資料 product 為基本結構,包含商品名稱name [10]、單價 price、數量 amount 共三個欄位。 第14行宣告一結構指標。 第15行動態配置此結構變數的記憶體。 第26行輸出各項結構成員的資料值。
9-1 結構 結構陣列 宣告方式如下: 例如以下student型態的結構陣列class1: struct 結構名稱 結構陣列名稱[陣列長度 ]; struct student { char name[20]; int math; int english; }; struct student class1[3]= {{"方立源",88,78},{"陳忠憶",80,97},{"羅國煇",98,70}};
9-1 結構 範例CH09_07.c
9-1 結構 範例CH09_07.c
9-1 結構 執行結果 程式解說 第6~11行中定義student結構,其中包括字串name、整數math與整數english三種資料成員。 第13~14行定義並直接設定3個元素的結構陣列初始值。 第21行計算數學總分及第22行計算英文總分。 第27行計算3個學生的兩科平均成績。
9-1 結構 範例 CH09_08.c
9-1 結構 執行結果 程式解說 第6~10行宣告student 結構,包括name字串與score整數變數。 第11~15行設定5個成員的初始值。 第17~18行列印student結構陣列的成員元素。 第21~22行可以使用指標的觀念來存取student結構成員。
9-1 結構 範例CH09_09.c
9-1 結構 範例CH09_09.c
9-1 結構 執行結果
9-1 結構 程式解說 5~9行定義名為product 的資料結構,包含有 name[10]、price與 amount 三個欄位。 第13行宣告結構指標。 第18行使用指標變數 product *list 產生一個動態一維陣列。 第32行中各位或許注意到存取各陣列元素時用的是「.」運算子而非「->」運算子,這是因為指標變數 list 所配置出來的一維陣列的每一個元素都是 product 型態的資料,故存取每一個成員變數時只要使用 list[i].name[10],list[i].price,以及list[i].amount。
9-1 結構 範例 CH09_10.c
9-1 結構 範例 CH09_10.c
9-1 結構 執行結果 程式解說 第6~10行宣告student 結構,包括name字串與score整數變數。 第11~15行設定5個成員的初始值。 第17~18行列印student結構陣列的成員元素。 第21~22行可以使用指標的觀念來存取student結構成員。
9-1 結構 巢狀結構 struct 結構名稱1 { …… }; struct 結構名稱2 struct 結構名稱1 變數名稱; }
9-1 結構 範例 CH09_11.c
9-1 結構 執行結果 程式解說 第6~11行定義結構size,包含3個成員變數。 第12~16行定義巢狀結構parce,並宣告結構變數large。 第18~21行以小數點「.」存取裡層結構物件,再存取裡層結構物件的成員,一層接著一層。
9-1 結構 認識位元欄位 結構宣告時,是定義一個資料型態,並不像變數的宣告會配置記憶體,當我們宣告結構變數時,才會配置記憶體,如果想要取得結構變數的記憶體位址,可以使用「&」運算子。 但是要特別注意的是,最小可定址的記憶體單位為位元組,而下例中使用位元欄位時,就無法取得它的記憶體位址,因此下段的程式碼就會造成錯誤:
9-1 結構 認識位元欄位 01 struct student 02 { 03 unsigned bit1: 1; 07 } a = {1, 0, 1, 1}; 08 09 int main(void) 10 { 11 unsigned *ptr; 12 ptr = &a.bit1; 13 printf("%u", *ptr); 14 15 return 0; 16 }
9-1 結構 認識位元欄位 宣告位元欄位結構的格式如下: 其中資料型態必須是整數(int)或無號整數(unsigned),不過如同上一段程式碼,大部份會以無號整數(unsigned)作為位元欄位宣告的資料型態。 struct 位元欄位結構名稱 { 資料型態 欄位名稱1:位元長度1; 資料型態 欄位名稱2:位元長度2; 資料型態 欄位名稱3:位元長度3; …………………………………… 資料型態 欄位名稱n:位元長度n; };
9-2 鏈結串列 鏈結串列的成立 鏈結串列是一種相當常用的資料結構,將具有相同結構的多個結構變數串接在一起。 當然,這裡所使用的串接方是就是利用指標。 使用者只要先定義每一筆資料的型態,當需要新增一筆資料時,才動態配置記憶體,並將新配置的資料與原本的資料以指標串接起來即可。
9-2 鏈結串列 鏈結串列的成立 以下我們將來說明如何使用結構來建立一個鏈結串列的範例。 首先請定義一個資料結構 node,用以表示每個節點的資料: struct node { int value; /*表示節點所含資料*/ struct node *next; /*表示指向下一個同型態的指標*/ };
9-2 鏈結串列 鏈結串列程式實作 接下來的目的是要利用C語言來實作一個線性鏈結串列,在這個程式開頭先宣告三個指標 *ptr、*head,以及 *newnode。而且,每一個指標變數都是指向 node 型態的結構。其中*head 變數是指向鏈結串列的開頭點,而 *ptr 則指向鏈結串列的尾端節點。要建立鏈結串列時,則依循以下的步驟:
9-2 鏈結串列 鏈結串列程式實作 Step1. 建立 head 的部位,head 及 ptr 均指在同一個節點上。 Step2. 建立一個新的節點,並且由 newnode 指向該新建節點。而此新建的節點則先指向NULL。 Step3. ptr->next 指向 newnode 所指向的節點。
9-2 鏈結串列 範例CH09_12.c
9-2 鏈結串列 範例CH09_12.c
9-2 鏈結串列 執行結果
9-2 鏈結串列 執行結果 第4~7行自訂一個結構資料型態node。 第12行宣告三個型態為 node * 的指標變數 *head、*ptr,以及 *newnode。其中 *head 變數是指向鏈結串列的起始點;而 *ptr 變數是指向鏈結串列的結束點。 第19行每當要動態配置一個新的節點的時候,就由 *newnode 指標變數進行配置,並記錄新節點的位址資訊,後續得以加入鏈結串列。 第39~40行輸出串列中的每一個節點。
9-2 鏈結串列 範例CH09_13.c
9-2 鏈結串列 範例CH09_13.c
9-2 鏈結串列 範例CH09_13.c
9-2 鏈結串列 執行結果
9-2 鏈結串列 程式解說 第5~12行定義一個資料結構 struct grades_node,用來記錄學生成績,包含字串型態的 name 欄位,用以記錄學生姓名;整數型態的 mid、final、usual 欄位,分別為期中、期末、平時成績,至於浮點數的 avg 欄位是表示平均成績。 第28、46行拷貝字串值。 第38行動態配置新節點。 第50行成績計算公式。
9-3 函數與結構 由於結構是一種使用者自訂資料型態,並不是基本資料型態,因此要在函數中傳遞結構型態,必須在全域範圍內事先作宣告,其它函數才可以使用此結構型態來定義變數。在C中函數中的結構資料傳遞也可以使用這兩種參數傳遞方法。
9-3 函數與結構 結構參數與傳值呼叫 結構傳值呼叫的函數宣告如下: 至於呼叫函數的語法如下: 函數型別 函數名稱(struct 結構名稱 結構變數) { 函數主體; } 函數名稱(結構變數);
9-3 函數與結構 範例CH09_14.c
9-3 函數與結構 範例CH09_14.c
9-3 函數與結構 執行結果 程式解說 第4~10行定義box結構,包含4項結構成員。 第11~15行使用巢狀結構來定義新結構。 第17行傳值呼叫的函數原型宣告。 第26行呼叫heavy函數。 第45行傳回d2結構變數。
9-3 函數與結構 範例CH09_15.c
9-3 函數與結構 範例CH09_15.c
9-3 函數與結構 執行結果 程式解說 第4~9行在全域範圍內作結構型態的宣告。 第10行傳值呼叫的函數原型宣告。 第27行:呼叫函數時,直接將結構變數傳入函數即可。 第32~37行定義calculate函數。 第35行計算訂購金額。
9-3 函數與結構 結構參數與傳址呼叫 傳址呼叫傳入的參數為指向結構資料型態的記憶體位址,以「&」運算子將位址傳給函數。當在函數中更改了傳來的參數值,那麼主程式內結構變數的值也會同步更改。函數原型宣告如下: 函數型態 函數名稱(struct 結構名稱 *結構變數); 或 函數型態 函數名稱(struct 結構名稱 *);
9-3 函數與結構 結構參數與傳址呼叫 結構傳址呼叫的函數宣告如下: 呼叫函數的語法如下: 函數型別 函數名稱(struct 結構名稱 *結構變數) { 函數主體; } 函數名稱(&結構變數);
9-3 函數與結構 範例CH09_16.c
9-3 函數與結構 範例CH09_16.c
9-3 函數與結構 執行結果 程式解說 第4~10行定義box結構,包含4項結構成員。 第11~15行使用巢狀結構來定義新結構。 第18行傳址呼叫的函數原型宣告。 第23行如果if判斷式成立,則呼叫discount()函數。 第37~41定義discount函數內容。
9-3 函數與結構 範例CH09_17.c
9-3 函數與結構 範例CH09_17.c
9-3 函數與結構 範例CH09_17.c
9-3 函數與結構 執行結果
9-3 函數與結構 程式解說 第5~11行定義一種結構型態 record,用以表示一筆商品資料。 第6行*name 就是表示商品名稱的字元指標,用以動態配置記憶體。 第8~10行price、amount,以及 subtotal 分別代表單價、數量,以及分項小計價格。 第13行宣告一個函數原型record * countsubtotal(char *,int,int) 這個函數主要接受產品名稱、單價與數量三個參數。 第24行用 record* 型態的指標變數r向系統索取一塊記憶體位址。 第47~59行這個函數會依據使用者傳入的商品名稱之字串長度動態配置記憶體,並計算分項小計價格,亦即以單價乘以數量即得分項小計價格。 第57行當函數 countsubtotal 將所有資料計算完成後,直接將剛剛索取的記憶體位址,亦即 temp 變數內容回傳給主程式。
9-4 其它自訂資料型態 所謂自訂資料型態,就是將指定資料型態來自訂名稱,接著在程式中,就可以自訂的資料型態來宣告變數,在C中,除了struct可自訂資料型態外,還包含列舉(enum)、聯合(union)與型態定義(typedef)三種方式。
9-4 其它自訂資料型態 型態定義(typedef) 可以用來定義自己喜好的資料型態名稱,它就是將已有的資料型態來以另外一個名稱重新定義,目的也是讓程式可讀性更高。宣告語法如下: 例如: typedef 原資料型態 新定義型態識別字 typedef int integer; integer age=120; type char* string; string s1="生日快樂"
9-4 其它自訂資料型態 範例CH09_18.c
9-4 其它自訂資料型態 執行結果 程式解說 第4行int被定義成 INTEGER型態。 第5行把char*定義成STRING。 第9行宣告amount是 INTEGER型態。 第10行宣告s1是STRING型態。
9-4 其它自訂資料型態 列舉(enum) 列舉型態的定義及宣告方式其實和結構有些類以,列舉型態的宣告是以enum為其關鍵字,在enum後面接續列舉型態名稱,宣告語法如下: enum 列舉型態名稱 { 列舉成員1, 列舉成員2, …… } enum列舉型態名稱 列舉變數1,列舉變數2…; /* 宣告變數 */
9-4 其它自訂資料型態 列舉(enum) 例如以下宣告: enum fruit { apple, banana, watermelon, grape }; /* 定義列舉型態 fruit */ enum fruit fru1,fru2; /* 宣告列舉型態fruit的變數 */
9-4 其它自訂資料型態 範例CH09_19.c
9-4 其它自訂資料型態 執行結果 程式解說 第6行定義列舉型態 fruit,第1個列舉常數apple的預設值為1,第2個列舉常數banana的預設值為2,第3個列舉常數watermelon的預設值為3,第4個列舉常數grape的預設值為4。 第8~9行宣告一字串陣列。 第11~12利用列舉常數輸出字串陣列元素。
9-4 其它自訂資料型態 聯合(union) 聯合型態指令(union) 與結構型態指令(struct),無論是在定義方法或成員存取上都十分相像,但結構型態指令所定義的每個成員擁有各自記憶體空間,不過聯合卻是共用記憶體空間。 聯合型態的宣告方式如下: union 聯合名稱 { 資料型態1 資料成員1; 資料型態2 資料成員2; 資料型態3 資料成員3; …… }聯合變數;
9-4 其它自訂資料型態 聯合(union) 例如以下是聯合型態的宣告: 聯合變數內的各成員是以同一塊記憶體來儲存資料,並以成員中佔用記憶體最大的成員作為聯合變數的空間大小。 union student { char name[10];/* 佔10bytes 空間 */ int score;/* 佔 4bytes 空間 */ };
9-4 其它自訂資料型態 範例CH09_20.c
9-4 其它自訂資料型態 執行結果 程式解說 第5~9行宣告結構型態。 第11~16行宣告聯合型態。 第20行宣告結構變數。 第21行宣告聯合變數。 第22行輸出此結構變數所佔用位元組。 第23行輸出此聯合變數所佔用位元組。
9-5 專案程式簡介 之前曾說明過函數的好處之一就是可以將許多函數分到多個檔案之中,不但可以降低維護成本,也能讓多位程式開發人員分工合作。 如果要將函數放在不同檔案中執行,仍然要注意到函數的宣告、定義,以及呼叫。 一般而言,函數的宣告會放在副檔名為 .h 的檔案中,而函數的定義會放在 .c 的檔案中。 如果有程式需要使用到這個函數,可以引入該函數宣告所在的 .h 檔案中。
9-5 專案程式簡介 專案實作 如果要將一個專案分割給多位程式設計師來開發,只要在副檔名為 .h 的標頭檔中定義好規格,由各位程式設計師編寫不同檔案,即可達成這項需求。未來如果計算含稅價的方式有所更動,僅僅只要修改 counttax.c 檔案即可。 專案中應包含所需要的檔案: main.c:主程式 conttax.c:函式 counttax 的定義 global.h:結構 product 的定義與 counttax 函式的定義
9-5 專案程式簡介 專案實作 然後請在 Dev C++ 中點選【檔案】→【開新檔案】→【專案】,如下圖所示:
9-5 專案程式簡介 專案實作 在「建立新專案」的對話框中選擇 Console Application(純文字模式之專案),專案名稱設定為 tax,並選擇「C專案」。接下來將專案存檔:
9-5 專案程式簡介 專案實作 下一步,我們使用【新增檔案】→【新增檔案】,並儲存為三個檔案:main.c、counttax.c,以及 global.h。如果已經有現成檔案,就用「將檔案加入專案」的選項:
9-5 專案程式簡介 專案實作 以下則是三個程式的內容。首先是 global.h。這個檔案定義商品結構 product,包含商品名稱、原價,以及含稅價。接下來,我們在同一個程式中加入 counttax 函式的宣告。在這個標頭檔中,我們特別加入以下條件式編譯指令來避免重複定義: #ifndef _GLOBAL_H_ #define _GLOBAL_H_
9-5 專案程式簡介 專案實作 以下為【global.h】內容: 01 #ifndef _GLOBAL_H_ 02 #define _GLOBAL_H_ 03 typedef struct{ 04 char name[20]; 05 int org_price; 06 float tax_price; 07 }product; 08 void counttax(product *); /* 函式宣告 */ 09 #endif
9-5 專案程式簡介 專案實作 接下來是 main.c。這個程式是專案的主要流程,首先必須引入 global.h 檔案,才能獲得 product 結構的定義。 在主程式中,我們使用一個指向 product 型態的指標變數 *list 作為稍後商品資料之陣列。程式一開始執行時,會要求使用者輸入商品種類數,以及各項商品名稱及商品單價。當輸入完成後,主程式會呼叫 counttax 函式並計算各商品之含稅價。
9-5 專案程式簡介 專案實作 以下為【main.c】內容:
9-5 專案程式簡介 專案實作
9-5 專案程式簡介 專案實作 接下來是 counttax.c。 為了要能夠獲得 product 結構的定義,這個程式也需要引入 global.h 檔案。 由於要使用稅率變數 tax_rate,但此變數已於 main.c 中定義過,所以使用extern float tax_rate; 來宣告。利用已知的稅率,這個函式主要的功能就是將各商品的單價乘上稅率以求得含稅價格。
9-5 專案程式簡介 專案實作 以下為【counttax.c】內容:
9-5 專案程式簡介 專案實作 最後請選擇【執行】→【重新編譯所有檔案】,並且執行這個專案,程式執行結果如下:
本章結束 Q&A討論時間