Skip to main content

All about structs

關於Heading first C的心得筆記,還有改編書中的範例當練習。
擁有多種型別成員的struct
在撰寫程式時會發現,我們常會利用相同片段的資料來處理不同的事,如下範例:
/* Print out the personal informaion */void personal_info(const char *name, const char *sick_type, float days, int age){ printf("%s got %s for %.1f days. She/he is %d.\n", name, sick_type, days, age); } /* Print the label for the document */void label(const char *name, const char *sick_type, float days, int age){ printf("Name: %s\nType of sick: %s, %.1f days\nAge: %d\n", name, sick_type, days, age); } /* const char*: 將傳入一字串的指標,而非字串值,並且無法改動指向位址上的數值 *//* 若想以字串的方式傳入,則將型別定義為char array,如char[20]即可 */
當實際使用函數時,我們的程式碼將得重複輸入相同的4個參數,使得程式碼顯得有些凌亂,如下面這樣:
int main(){ personal_info("John", "fever", 3.5, 23); label("John", "fever", 3.5, 23); return 0; }
但是其實兩段函數的輸入參數,都是屬於同一位病人所擁有的資料片段,然而由於這些資料片段並不一定為相同型別,因此僅能放入單一型別變數的Array將無法滿足我們的使用情境,此時可以考慮使用struct結構來將這些變數集結起來。
struct的定義、宣告
struct這個關鍵字的含意是結構化的資料類型(structured data type),因此struct讓程式設計師可以將這些不同型別的資料通通綁成一塊,變成一個新的資料類型,範例如下:
struct patient{ const char *name; const char *sick_type; float days; int age; };
透過上述程式就可以創建由數個資料片段所組成的一個自定義的資料類型,struct與array是有點類似的,其不同之處在於(1)struct為固定長度,(2)每一個結構內的成員都有各自作為辨識的名字。初始化結構的程式碼與array相似,唯一需要注意的是需要按照結構內的順序,將變數填入,如patient結構內的順序則為name、sick_type、days以及age:
struct patient p1 = {"John", "fever", 3.5, 23};
還有另外一種指定成員的初始化方式,適用於需要對大型的結構進行賦值,或是只需要初始化某幾個成員變數的情況,此時使用如下的程式碼將會讓人更容易理解:
struct patient p1 = {.name = "John", .age = 23};
使用指定成員的方式的賦值順序並不受限制,任意安排順序也可以成功指定。
使用struct後的程式碼 - 改寫(上)
知道如何自定義struct,現在回頭來改寫我們的函數以及main()。在main()內加入patient結構的定義,並使用patient這個結構作為函數參數,代替重複傳入四個片段的資料,使得main()看起來更清爽。
#include <stdio.h> struct patient{ const char *name; const char *sick_type; float days; int age; }; /* Print out the personal informaion */void personal_info(struct patient p){ ... } /* Print the label for the document */void label(struct patient p){ ... } int main(){ struct patient John = {"John", "fever", 3.5, 23}; personal_info(John); label(John); return 0; }
取得stuct成員變數數值
以Array的方式來取得struct成員變數是行不通的,在編譯的過程中將會出現error:
struct patient John = {"John", "fever", 3.5, 23}; printf("Name: %s\n", John[0]);
Alt text
正確的取值方法是透過"."運算子搭配其成員名稱,執行如下的程式碼後,便能成功取得並輸出Name成員的內容:
struct patient John = {"John", "fever", 3.5, 23}; printf("Name: %s\n", John.name);
使用struct後的程式碼 - 改寫(下)
了解如何取得結構內成員的數值後,便來著手修改personal_info()以及label()函數內容的部分。修改後完整的程式碼如下:
#include <stdio.h> struct patient{ const char *name; const char *sick_type; float days; int age; }; /* Print out the personal informaion */void personal_info(struct patient p){ printf("%s got %s for %.1f days. She/he is %d.\n", p.name, p.sick_type, p.days, p.age); } /* Print the label for the document */void label(struct patient p){ printf("Name: %s\nType of sick: %s, %.1f days\nAge: %d\n", p.name, p.sick_type, p.days, p.age); } int main(){ struct patient John = {"John", "fever", 3.5, 23}; personal_info(John); label(John); return 0; }
總結
使用struct結構可以讓程式碼較為簡潔且更容易理解。使用結構化資料類型另外的好處為,可以在不更動到函數參數的狀況下,修改其成員的變數,舉例來說,若在patient結構內新增了一個叫做int gender的成員,對personal_info()以及label()來說,傳入的參數為整個patient結構,函數並不需要知道結構成員使否有修改,函數只在乎他所使用到的變數是否仍然存在。因此使用結構除了可以讓我們的程式碼更好閱讀之外,也讓程式碼更方便進行維護與修正。
插曲1:使用typedef讓struct的定義與宣告變得簡潔
目前看來使用struct會讓程式語言在撰寫上顯得有點繁瑣,首先在定義結構時會需要使用到一次'struct'這個關鍵字(keyword),然後為結構定義實體時又需要再一次的使用到'struct',範例如下:
struct todo_item{ int item_no; const char *title; float time_to_spend; }; ... struct todo_item m1 = {1, "Clean table", 30};
這時候可以使用typedef,藉由為struct取一個別名(alias)的方式來簡化整個步驟所需要撰寫的程式碼。首先在定義結構的struct關鍵字之前之前加入typedef,然後在結構定義的大括號之後加上自定義的別名名稱,上述之程式碼將修改成如下的形式:
typedef struct todo_item{ int item_no; const char *title; float time_to_spend; } item; ... item m1 = {1, "Clean table", 30};
此時編譯器將會視item為struct todo_item。
只是這樣一來我們的結構便有了兩個名字,一個是宣告時給的名字,即結構名稱(todo_item),另一個則是透過typedef給的別名(item),然而我們並不需要同時給編譯器這兩個名字,因此便有了更簡化的作法,將定義結構時的名字也省略(struct todo_item),僅透過typedef給予別名,此後在程式碼中使用別名即可,簡化後程式碼如下:
typedef struct { int item_no; const char *title; float time_to_spend; } item; ... item m1 = {1, "Clean table", 30};
以typedef定義結構的原始碼將如下:
#include <stdio.h> typedef struct { const char *name; const char *sick_type; float days; int age; }patient; /* Print out the personal informaion */void personal_info(patient p){ printf("%s got %s for %.1f days. She/he is %d.\n", p.name, p.sick_type, p.days, p.age); } /* Print the label for the document */void label(patient p){ printf("Name: %s\nType of sick: %s, %.1f days\nAge: %d\n", p.name, p.sick_type, p.days, p.age); } int main(){ patient John = {"John", "fever", 3.5, 23}; personal_info(John); label(John); return 0; }
插曲2:函數內的只是副本,使用指標保留變數異動
在先前的patient範例中增加seeDoctorAgain()函數,並在main()中呼叫,程式執行後如下圖,我們發現雖然在seeDoctorAgain()中patient.days有被增加到6.5,但是回到main()時,卻又是原本的3.5。
void seeDoctorAgain(patient p){ p.days = p.days + 3; printf("\nToo Bad, She/he have been sick for %.1f days.\n", p.days); } int main(){ patient Jahn = {"John", "fever", 3.5, 23}; personal_info(John); label(John); seeDoctorAgain(John); label(John); return 0; }

這是因為傳入函數的是patient結構的副本,一份複製出來的John的資料,當seeDoctorAgain()結束時,這個複製的John也會跟著一起消失,此時較好的方式是將參數改成指標,讓函數前往該指標位址尋找John的資料,修改完成後執行程式將可以發現結構成員days的數值已經成功地被修改。
void seeDoctorAgain(patient *p){ p->days = p->days +3; printf("\nToo Bad, She/he have been sick for %.1f days.\n", p->days); } ... seeDoctorAgain(&John); /* (*p).days = p->days *//* the computer evaluates the dot operator before it evaluates the * */
Alt text

Comments

Popular posts from this blog

淺介I2C

I 2 C 起源 內部整合電路( Inter-Integrated Circuit, I 2 C, 讀做 I-square-C )是由飛利浦半導體公司開發的一種專用介面。 I 2 C 是以最少的連接線進行硬體佈線還要有靈活擴充的特性為目標而設計,最後出現了只有以序列資料線 SDA ( Serial DAta )及序列時脈線 SCL ( Serial CLock )來進行所有通訊的 I 2 C 介面, I 2 C 允許多主( master )多僕( slave )系統,其傳輸系統內每一個裝置都有唯一的地址可供辨識。資料的寫入和讀取都是由 master 主動發起, slave 無法主動向 master 回報,除非使用中斷腳通知 master 。 I 2 C 傳輸速度有慢(小於 100Kbps )、快( 400Kbps )及高速( 3.4Mbps )三種,每一種均可向下相容。 I 2 C 電路配置 如前所述 I 2 C 為兩線式,一為時脈線 SCL ,另一條為資料線 SDA ,硬體線路如圖 1 ,兩線皆為雙向性,且都需要透過高接電阻( pull-up, 對岸說的上拉電阻)接電。平常不使用時, SCL 與 SDA 的訊號都處於高電位。為了多裝置共線的功能,裝置的 SCL 和 SDA 腳位要為 開洩極( open-drain ) 或 開集極( open-collector ) 。一旦有一個腳位的開洩極導通接地,則整條線都為低電位,這種現象稱作 wired-AND 運作 ;如同邏輯 AND 運算,需要共接的腳位都是 1 (開洩極斷路),該條線的電位才是 1 。如果沒有開洩極的腳位,可以使用具內部高接電阻的腳位,當要輸出 1 時,則設定該腳位為高接型輸入腳;而輸出為 0 時,則改設定為輸出腳並輸出 0 的值。 圖 1. I 2 C 傳輸裝置接線 [1] I 2 C 通訊協定 為使說明部分更簡潔,首先介紹幾個名詞: 位元傳輸協定 當 master 要跟 slave 溝通時,會先有個起始條件( start condition )的訊號,結束時也會送出終止條件( stop condition )訊號。起始條件訊號

【Arduino】Timers, Registers, and Fast PWM Mode

由於Arduino預設的PWM控制方法僅有500Hz(好像還有另一個),想要知道怎樣才可以調整成其他頻率,以此做記錄。 先說明Timer設定方式、PWM Mode,之後會在Arduino上實做。 如果對_BV()沒有概念,可以參考 【Arduino I/O Ports】Control under avr-libc 這篇文章。

Emart Sunny Sale: 3D Shadow QR Code

  Emart是韓國連鎖超市,近期他們發現中午12:00到下午1:00的銷售量會明顯減少,為了提高銷售量他們製作出了帶有日晷概念的3D QR code。