使用C語言實現面向對象編程
使用C語言實現面向對象編程 – tommwq.tech/blog
面向對象是一種程序設計方法。面向對象不是某種語法或語言特性,因此使用任何高級語言都可以實現面向對象程序設計。與之相似的,使用面向對象程序語言,也可以做出非面向對象的程序設計。這裏簡單介紹一種用C語言實現面向對象的方法。
面向對象的核心原則是使用對象來組織程序。對象是可以執行某些行爲的東西。爲了保證行爲是正確的,對象需要維護控制行爲的一組狀態。要避免狀態被外部代碼破壞,對象必須保護這些狀態,這就產生了面向對象的第一個特性:封裝。
在C語言中,結構體可以將一組相關的狀態(變量)保存在一起。這是否就是封裝呢?答案是否定的。結構體只是狀態的簡單聚合,無法起到保護狀態的作用。封裝的目的在於保護狀態的一致性,必須禁止外部代碼直接修改狀態。因此我們需要使用結構體和函數相結合的方法來實現封裝。這裏要注意,爲了避免客戶代碼直接修改結構體,需要將結構體定義保存在私有的.c文件中。頭文件保留前向聲明,在函數中使用結構體指針。下面看一個例子。
Listing 1: persion.h
struct Person; struct Person* Person_create(); void Person_destroy(struct Person*); int Person_set_age(struct Person *person, unsigned int age); int Person_set_name(struct Person *person, const char *name);
Listing 2: person.c
#define MAX_NAME_LENGTH 64 #define MAX_AGE 120 struct Person { char name[MAX_NAME_LENGTH]; unsigned int age; }; int Person_set_age(struct Person *person, unsigned int age) { if (age > MAX_AGE) { return -1; } person->age = age; return 0; } int Person_set_name(struct Person *person, const char *name) { if (strlen(name) > MAX_NAME_LENGTH) { return -1; } strncpy(person->name, name); return 0; }
上面的代碼實現了一個簡單的業務模型:一個人有名字和年齡。這個模型有兩個約束:
- 名字長度不超過MAX_NAME_LENGTH。
- 年齡不超過MAX_AGE。
如果把結構體Person直接暴露給客戶代碼,我們將無法保證對象狀態始終是符合約束的。因爲客戶代碼可能會設置一個錯誤的狀態,比如:
struct Person *person = Person_create(); person->age = MAX_AGE + 100; // 狀態約束被客戶代碼破壞。
而通過隱藏結構體定義和定義操作函數的方法,我們可以在操作函數中對狀態的轉移進行控制,保證狀態始終是滿足約束的,是合法的。
面向對象的第二個特性是繼承:子類可以繼承父類的狀態和行爲。繼承狀態可以通過將父類結構體包含在子類中實現。爲了讓子類繼承父類行爲,我們將父類的操作保存到一個接口結構體中,通過複製這個接口結構體實現行爲的繼承。父類需要作爲子類的第一個成員,這樣通過指針引用時,子類實例和父類實例可以使用同一個指針表示。
typedef void (*Student_print_name)(struct Student *); typedef void (*Student_set_name)(struct Student *, const char *); typedef void (*Student_print_score)(struct Student *s); typedef void (*Student_set_score)(struct Student *s, int score); typedef int (*Student_get_score)(struct Student *s); typedef int (*Student_compare_score)(struct Student *lhs, struct Student *rhs); typedef void (*Student_destroy)(struct Student *s); struct StudentInterface { Student_print_name print_name; Student_set_name set_name; Student_print_score print_score; Student_set_score set_score; Student_get_score get_score; Student_compare_score compare_score; Student_destroy destroy; }; struct StudentInterface _student_interface; struct Object { void* interface; }; struct Student { struct Object object; char name[16]; int score; }; struct CheatStudent { struct Student base; int cheated; }; struct CheatStudentInterface _cheatstudent_interface; void CheatStudent_class_init() { // 通過複製父類接口列表,實現行爲繼承。 memcpy(&_cheatstudent_interface.base, _student_interface, sizeof(_student_interface)); } struct CheatStudent* CheatStudent_create() { struct CheatStudent *s = (struct CheatStudent*) malloc(sizeof(struct CheatStudent)); if (s == NULL) { return NULL; } s->base.object.interface = &_cheatstudent_interface; s->cheated = 0; return s; }
面向對象的另一個特性是多態。爲了實現多態,我們需要根據實例的具體類型進行函數分派。我們的做法是將函數分派表保存到類實例中。每個類擁有自己的函數表。在初始化類的時候設置這個函數表。同一個類的實例共享這個函數表。每個實例中都包含一個指針指向類函數表。函數表也使用結構體表示。父類函數表是子類的函數表的第一個成員。在初始化子類(不是初始化子類實例)時,將父類的函數表複製到子類函數表起始的位置。然後初始化子類的特有函數。在分派函數時,根據實例內的指針找到函數表,然後根據函數表進行分派。
Listing 3: student.h
struct Student; typedef void (*Student_print_name)(struct Student *); typedef void (*Student_set_name)(struct Student *, const char *); typedef void (*Student_print_score)(struct Student *s); typedef void (*Student_set_score)(struct Student *s, int score); typedef int (*Student_get_score)(struct Student *s); typedef int (*Student_compare_score)(struct Student *lhs, struct Student *rhs); typedef void (*Student_destroy)(struct Student *s); struct StudentInterface { Student_print_name print_name; Student_set_name set_name; Student_print_score print_score; Student_set_score set_score; Student_get_score get_score; Student_compare_score compare_score; Student_destroy destroy; }; typedef void (*CheatStudent_cheat)(struct CheatStudent *cs); struct CheatStudentInterface { struct StudentInterface base; CheatStudent_cheat cheat; }; void Student_class_init(); struct Student* Student_create(); struct CheatStudent; void CheatStudent_class_init(); struct CheatStudent* CheatStudent_create(); #define AS_INTERFACE(INTERFACE, POINTER_TO_INSTANCE) ((INTERFACE *) *((void**)POINTER_TO_INSTANCE)) #define CREATE_DERIVED_INSTANCE(BASE_TYPE, CREATOR) (BASE_TYPE) (CREATOR())
Listing 4: student.c
#include <stdio.h> #include <stdlib.h> #include "student.h" struct Object { void* interface; }; struct Student { struct Object object; char name[16]; int score; }; struct StudentInterface _student_interface; void _student_print_name(struct Student *s) { printf("%s\n", s->name); } void _student_set_name(struct Student *s, const char *name) { strncpy(s->name, name, 16); } void _student_print_score(struct Student *s) { printf("%d\n", s->score); } void _student_set_score(struct Student *s, int score) { s->score = score; } int _student_get_score(struct Student *s) { return s->score; } int _student_compare_score(struct Student *lhs, struct Student *rhs) { int a = lhs->score; int b = rhs->score; if (a < b) { return -1; } else if (a == b) { return 0; } else { return 1; } } void _student_destroy(struct Student *student) { free(student); } struct Student* Student_create() { struct Student *s = (struct Student*) malloc(sizeof(struct Student)); if (s == NULL) { return NULL; } s->object.interface = &_student_interface; return s; } void Student_class_init() { _student_interface.print_name = _student_print_name; _student_interface.print_score = _student_print_score; _student_interface.set_name = _student_set_name; _student_interface.set_score = _student_set_score; _student_interface.get_score = _student_get_score; _student_interface.compare_score = _student_compare_score; _student_interface.destroy = _student_destroy; } struct CheatStudent { struct Student base; int cheated; }; void _cheat_student_cheat(struct CheatStudent* cs) { AS_INTERFACE(struct StudentInterface, cs)->set_score(cs, 100); cs->cheated = 1; } struct CheatStudentInterface _cheatstudent_interface; struct CheatStudent* CheatStudent_create() { struct CheatStudent *s = (struct CheatStudent*) malloc(sizeof(struct CheatStudent)); if (s == NULL) { return NULL; } s->base.object.interface = &_cheatstudent_interface; s->cheated = 0; return s; } void _cheatstudent_print_score(struct CheatStudent *s) { int score = AS_INTERFACE(struct StudentInterface, &(s->base))->get_score(&(s->base)); printf("%d%s\n", score, s->cheated ? "(cheat)" : ""); } void CheatStudent_class_init() { memcpy(&_cheatstudent_interface.base, _student_interface, sizeof(_student_interface)); _cheatstudent_interface.base.print_score = _cheatstudent_print_score; _cheatstudent_interface.cheat = _cheat_student_cheat; }
Listing 5: main.c
#include <stdio.h> #include <stdlib.h> #include "student.h" int main() { Student_class_init(); struct Student *student = Student_create(); AS_INTERFACE(struct StudentInterface, student)->set_name(student, "Alice"); AS_INTERFACE(struct StudentInterface, student)->print_name(student); AS_INTERFACE(struct StudentInterface, student)->set_score(student, 90); AS_INTERFACE(struct StudentInterface, student)->print_score(student); AS_INTERFACE(struct StudentInterface, student)->destroy(student); CheatStudent_class_init(); struct CheatStudent *cheat_student = CREATE_DERIVED_INSTANCE(struct Student*, CheatStudent_create); AS_INTERFACE(struct StudentInterface, cheat_student)->set_name(cheat_student, "Bob"); AS_INTERFACE(struct StudentInterface, cheat_student)->print_name(cheat_student); AS_INTERFACE(struct StudentInterface, cheat_student)->set_score(cheat_student, 90); AS_INTERFACE(struct StudentInterface, cheat_student)->print_score(cheat_student); AS_INTERFACE(struct CheatStudentInterface, cheat_student)->cheat(cheat_student); AS_INTERFACE(struct StudentInterface, cheat_student)->print_score(cheat_student); AS_INTERFACE(struct StudentInterface, cheat_student)->destroy(cheat_student); return 0; }
Listing 6: 輸出
Alice 90 Bob 90 100(cheat)