struct在C語言中是一個很好用的關鍵字,基本就是要放不同型態的資料,但說到底,它其實就是表格上的標頭,在沒有實例化前,就只有標頭。以下說明三種狀況:
1.宣告struct真的只有標頭而已,你沒有實例化,就沒有辦法放資料,只是一個空白的表格。
2.實例化並且表格裡面只有放一組資料,這個就比較像物件,一個東西就只有一種屬性。
3.實例化並且表格裡面只有放多組資料,這就是我們平常看到的表格,我們若要設定規格書上的表格,用結構陣列可以輕鬆達成。
有了上面的觀念後,我們來說明繼承在現實生活怎麼實現,我們以表格來說明
繼承
想像公司就存在一些表格,之前維護表格的人離職了,或是主管要你優化原本的表格,無中生有最難了,一般的做法會去拿原本的表格,或是網路上找相關的表格來參考,這樣只要繼承原本表格的優點,再擴大為現有的需求,如下圖:
還有一種狀況很常見,公司提供的表格是基本款,不可能每一個部門就專門做一個表格,所以簡單的留下一個備註欄,可以讓底下各部門自由發揮,若只有簡單事項要備註,直接在備註欄填上資訊即可;若是備註欄不敷使用,底下的部門可以自製附件或表格,然後在備註欄寫下,附件#,這樣就會知道每一項其他需要知道的資訊,如下圖:
在「嵌入式Linux C語言程式設計實務」書中提到,實現繼承的兩種方式,分別為利用資料結構的包含實現繼承功能,以及利用私有指標實現繼承功能,專業上說得很拗口,實際上在生活上就是上面舉的例子。
* 利用資料結構的包含實現繼承功能
namePoint.hstruct NamedPoint; struct NamedPoint* makeNamedPoint(double x, double y, char* name); void setName(struct NamedPoint* np, char* name); char* getName(struct NamedPoint* np);namePoint.c ① namePoint結構體好像它是Point結構體的衍生結構體一樣,因為NamedPoint的前兩個欄位的順序與Point相同。值得注意的是,字元指標也需要依照字串長度,malloc一段再做strcpy。
#include "namedPoint.h" #include <stdlib.h> struct NamedPoint { double x,y; char* name; }; struct NamedPoint* makeNamedPoint(double x, double y, char* name) { struct NamedPoint* p = malloc(sizeof(struct NamedPoint)); p->x = x; p->y = y; p->name = malloc(strlen(name) + 1); strcpy(p->name, name); return p; } void setName(struct NamedPoint* np, char* name) { np->name = name; } char* getName(struct NamedPoint* np) { return np->name; }main.c 將NamedPoint參數(arguments)轉型為Point型態。在一個真正的OO語言中,這種向上轉型是隱式的(implicit)。
#include "point.h" #include "namedPoint.h" #include <stdio.h> int main(int ac, char** av) { struct NamedPoint* origin = makeNamedPoint(0.0, 0.0, "origin"); struct NamedPoint* upperRight = makeNamedPoint (1.0, 1.0, "upperRight"); printf("distance=%f\n", distance((struct Point*) origin, (struct Point*) upperRight)); }另外一種包含的寫法,可以注意到是直接在新的struct包原本的struct,而在建構子上賦予初始值,就需要轉兩層,才能存取到x及y值。 namePoint.c ②
#include "namedPoint.h" #include此包含的方法,namePoint結構體是對Point結構體的擴充,增加了name成員,本質上是利用資料結構儲存的實際位置相同的特性。如下圖:struct NamedPoint { struct Point point; char* name; }; struct NamedPoint* makeNamedPoint(double x, double y, char* name) { struct NamedPoint* p = malloc(sizeof(struct NamedPoint)); p->point.x = x; p->point.y = y; p->name = malloc(strlen(name) + 1); strcpy(p->name, name); return p; } void setName(struct NamedPoint* np, char* name) { np->name = name; } char* getName(struct NamedPoint* np) { return np->name; }
* 利用私有指標實現繼承功能
在原有的struct Point,加了一個void型別的指標,並名為private,就是像上面所提到的備註欄。此時不需要一個單獨的NamePoint資料結構,每一個生成的物件其區別在於私有資料指向的內容是不一樣的,所以可以任意擴充物件的特性。struct Point { double x,y; void* priv; };私有資料定義如下:
struct NamedPointPriv { char* name; };下面程式碼要注意的是會為私有指標分配記憶體。 namePoint.c ③
struct NamedPoint* makeNamedPoint(double x, double y, char* name) { struct NamedPoint* p = malloc(sizeof(struct NamedPoint)); p->x = x; p->y = y; p->priv = malloc(sizeof(struct NamedPointPriv)); (p->priv)->name = malloc(strlen(name) + 1); strcpy((p->priv)->name, name); return p; }私有指標是一個地址,32位元系統中,就是一個32位元的整數,指標所佔用的儲存空間也是32位元的整數,若擴充部份的資訊放在一個32位元整數的儲存空間內,就不需要再在heap上分配(malloc)出另一塊空間。這就是上面所說的,若不用附件,簡單寫在備註欄即可,不用malloc,也代表現實生活中不用再拿一張白紙印附件表格。 namePoint.c ④
struct NamedPoint* makeNamedPoint(double x, double y, char* name) { struct NamedPoint* p = malloc(sizeof(struct NamedPoint)); p->x = x; p->y = y; p->priv = 0; return p; }
Uncle Bob在繼承的結論說:OO對於繼承有半分貢獻,可以做多重繼承,使資料結構的偽裝變得更方便。
在linux中的struct file,可以看到這兩個技巧。
struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; /*指向file_operations結構*/ atomic_t f_count; unsigned int f_flags; mode_t f_mode; /*包含可讀可寫的權限資訊*/ int f_error; loff_t f_pos; /*目前的讀寫位置*/ struct fown_struct f_owner; unsigned int f_uid, f_gid; struct file_ra_state f_ra; unsigned long f_version; void *f_security; /* needed for tty driver, and maybe others */ void *private_data; /* 驅動程式可自行運用此指標,典型用法是讓他指向一塊私有資料區 如果有用到這個,release時要記得釋放此指標的記憶體 非常好用的功能 */ #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; spinlock_t f_ep_lock; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; };
參考資料:
Clean Architecture無瑕的程式碼嵌入式Linux上的C語言編程實踐
0 意見:
張貼留言