如何在C語言中實現C++的Class呢?
一些低級設備好比嵌入式,或者一些底層驅動、操做系統中,是不能使用C++語言的。網上有不少對這方面的解釋,除非對編譯器作一些設置或者修改,但這樣大大增長了開發的難度。而且靠修改編譯器參數來編譯,仍舊不能利用到C++的優勢。好比C++的虛函數,垃圾回收,異常,在底層開發中使用,反而會形成不少沒必要要的麻煩。好比C++編譯器爲了重載函數,其編譯出來的函數名會被改爲包括參數的形式,並且每一個編譯器都有本身的內部前綴和後綴,這一點尤爲在操做系統編寫中會形成麻煩,由於操做系統的系統調用使用匯編,好比中斷的實現中,就須要調用匯編中斷服務,而後在其中回調操做系統內核的C函數,若是使用C++函數,就不能正確指定回調函數名。那麼在只能使用C語言來實現的狀況下,如何讓這種過程式語言更具封裝性,而不讓代碼看起來「懶散」呢?
C語言中能夠和class類比的類型就是struct了,另外還有union, 但union並不具有class的條件。在struct中不能定義函數, 這一點能夠在Microsoft Visual Studio中和Linux GCC下作個比較:
typedef struct A {
int data;
int Val() {
return data;
}
}A;
A a;
a.Val();
在VS下這個struct能經過編譯,而且a.Val()能取到值, 這是由於C++編譯器在對兼容C語言的struct進行編譯時,是將struct按照public class來理解的,因此能支持內聯函數。但GCC是隻支持C語言的編譯器,編譯時就會報錯。那麼,若是使用C語言,如何才能讓struct媲美class呢?
其實C類語言都支持函數指針的定義,而且struct中也支持函數指針定義。好比
int func(int a, int b);
定義的函數指針可使這樣的:
int (*pfunc)(int, int);
當定義pfunc = func時,下面兩個調用是同樣的:
func(10, 20);和pfunc(10, 20);
那如上面所說,將函數指針定義到struct中:
typedef struct A {
int data;
int (*Val)(int a);
}A;
int Val(int a) {
return a;
}
A a;
a.Val = Val;
a.Val(10);
這樣能夠獲得10的結果。咱們知道class中隱含了一個this指針,那在Val函數中怎樣才能獲得this呢?對了,就經過參數傳遞進去:
typedef struct A A;
struct A{
int data;
int (*Val)(A* that, int a);
};
int Val(A* that, int a) {
return that->data + a;
}
A a;
a.Val = Val;
a.Val(&a, 10);
上面就能夠獲得a.data + 10的結果。咱們使用that來代替this,這樣若是這段代碼拿到C++編譯器下面時也不會跟struct中隱含的this衝突。這樣就定義了struct來代替class,惟一的缺點是定義好之後,每次調用函數須要將對象指針傳遞進去,這也是無可避免的。能夠定義下面的宏來防止兩次指定的對象不一致:
#define __CALL(o, f, ...) o.f(&o, __VA_ARGS__)
__CALL(a, Val, 10);
其中__VA_ARGS__是C語言關鍵字,用於將宏的變參傳遞到指定位置。在編譯期宏就已經被展開,所以Val已是a的成員(函數)了,因此不用擔憂Val這個參數在__CALL這個宏調用時沒有定義。
進階1: 構造函數
上一步中,a.Val = Val;寫在外面,若是有好幾個成員(函數),會形成代碼臃腫,須要定義一個構造函數。
A * _A(A* that, int data) {
that->data = data;
that->Val = Val;
return that;
}
A a;
_A(&a, 20);
a.Val(&a, 10);
這樣定義一個對象就須要兩行代碼,仍舊能夠定義宏來實現新建對象,不過若是是new對象徹底沒有必要。
A * a = _A(new A, 10);
另外這裏構造函數只能是一個普通函數,不能做爲成員(函數),由於構造函數只調用一次,沒有做爲成員的必要,而且構造函數若是是成員也無法在構造前知道構造函數是什麼,所以只能在外部指定。若是非要定義成成員(函數),這就變成了兩行代碼:
a.init = _A;
a.init(&a, 20);
哈哈,可是若是想要從新設置對象a的值, 定義init成員則另當別論,不過最好仍是在普通函數_A中定義。
進階2:繼承
咱們已經有了一個很好的"class"了:
typedef struct A A;
struct A{
int data;
int (*Val)(A* that, int a);
};
int Val(A* that, int a) {
return that->data + a;
}
A * _A(A * that, int data) {
that->data = data;
that->Val = Val;
return that;
}
A a;
_A(&a, 20);
a.Val(&a, 10);
接下來,咱們要實現繼承。由於若是隻是須要上面的代碼,就沒有使用類的必要,實現繼承纔是使用類的終極目標。這裏先暫時、並且也無法實現虛函數之類的,不用考慮這些。實現繼承須要用到上面提到的union,好比繼承上面的A:
typedef struct B B;
struct B {
union {
A super;
struct {
int data;
int (*Val)(A* that, int a);
};
};
int val;
};
B* _B(B* that, int val) {
_A(&that->super val);
that->val = val;
}
B b;
_B(&b, 30);
在union中,定義了基類A,以及將基類A中的成員都拷貝到了一個匿名struct中。在C規範中,union的匿名struct後面定義一個變量名那麼使用變量名.成員才能獲得變量,而若是沒有變量名,則直接使用成員名就能獲得變量,以下:
union {
float f[2];
struct {
float f1;
float f2;
}uf;
}um;
要獲得f[1]使用um.fu.f2能夠獲得,而
union {
float f[2];
struct {
float f1;
float f2;
};
}um;
只使用um.f2就能獲得f[1]。咱們的類就是利用了這點,可讓基類的成員變成繼承類的成員。繼承類中super部分是基類,而自身又定義了val這個成員變量,是屬於基類之外的,並且更有意思的是,在B的構造函數中,能夠直接經過that->super來構造a,而且構造函數完了之後,b.data和b.Val就是構造A之後的成員,它們分別等於b.super.data和b.super.Val。
進階3:部分繼承和重寫(重載)
另外咱們還可使用這種結構來實現部分繼承。來看下面A和B的定義(只有定義,前向聲明和調用省略):
struct A{
int data;
int (*Val)(A* that, int a);
int val;
};
struct B {
union {
A super;
struct {
int data;
int (*Val)(A* that, int a);
};
};
int val;
};
其中,B.val和A.val是不一樣的成員。若是要取得A.val使用b.super.val就能夠了, 這個是union的特性來決定的。這種沒有繼承基類的狀況叫部分繼承。
那麼怎麼實現重寫呢?來看B的構造函數:
B* _B(B* that, int val) {
_A(&that->super, val);
//override
that->Val = myVal;
that->val = val;
}
只要將繼承類的Val指針指向自定義的函數就能夠了。不過注意必須在A構造完成以後,不然會被覆蓋回來。
因此概括起來,構造函數的寫法順序爲:
基類構造
重寫函數
子類構造(函數指針初始化)
子類數據初始化
其餘初始化
其中,「其餘初始化」以前的全部均可以類比爲C++類的構造函數:
B : A(val), val(val) {}
而重載函數是在C++類中定義內聯函數時就已經直接重載了。
進階4:定義宏使結構更簡單
C語言模擬C++類的大致如上。不過若是想要建立類更簡便,還須要一些宏定義的幫助。
在任何狀況下,咱們都應該知道本身定義這個類未來是否是會成爲基類。因此,對於A,咱們知道它是基類,能夠將它改寫成「模板」,但這個模板非C++的template,只是宏定義,用於簡化繼承類中基類的書寫:
typedef struct A {
#define A_Template \
int data;\
int (*Val)(A* that, int a);
#define Template_A A_Template
Template_A
int val;
}A;
雖然多了兩個#define,可是A的定義並無變化。int val;不是由子類繼承的因此不用寫在#define裏面。#define包括在花括號內是爲了讓代碼更加美觀。#define將會在下面宏中使用:
#define __SUPER(Base) \
union {\
Base super; \
struct {\
Template_##Base\
}; \
}
這就是B的union部分,咱們將它提煉出來,使得之後全部的類均可以不失通常性地調用宏來繼承。
typedef struct B {
__SUPER(A);
int val;
}B;
看,這樣就不用再去從新寫基類的成員了,全部基類成員只在基類中定義一遍,在子類中經過宏來進行展開。
進階5:模板?
咱們經過上面的說明,可以很快寫出一個類的例子,這是一個能編譯運行的C代碼(linux gcc):
// Class.c
//
#include <stdio.h>
#include <stdlib.h>linux
//////////////////////////////////////////////////////
#define __SUPER(Base) \
union {\
Base super; \
struct {\
Template_##Base\
}; \
}
//////////////////////////////////////////////////////
typedef struct A A;
struct A {
#define A_Template \
int data;\
int (*Val)(A* that, int a);
#define Template_A A_Template
Template_A
int val;
};
int Val(A* that, int a) {
return that->data + a;
}
A * _A(A * that, int data) {
that->data = data;
that->Val = Val;
return that;
}
//////////////////////////////////////////////////////
typedef struct B {
__SUPER(A);
int val;
}B;
int myVal(B* that, int a) {
return that->data * a;
}
B* _B(B* that, int val) {
_A(&that->super, val);
that->Val = myVal;
that->val = val;
}
//////////////////////////////////////////////////////
int main() {
A a;
_A(&a, 10);
a.val = 1;
printf("%d %d\n", a.val, a.Val(&a, 20));編程
B b;
_B(&b, 20);
b.val = 2;
printf("%d %d\n", b.val, b.Val(&b, 30));
return 0;
}
能夠看到結果是1 20和2 600,說明成功了。可是編譯器報出警告:assignment from incompatible pointer type,這是由於基類Val是A類型參數,而B中重載時給的倒是B類型參數,因爲是繼承關係,而且是指針,因此不用去理會也不會有什麼問題。可是若是真的要較真的話,就須要更改#define了:
struct A {
#define A_Template(T) \
int data;\
int (*Val)(T* that, int a);
#define Template_A(T) A_Template(struct T)
Template_A(A)
int val;
};
上面_SUPER和下面B調用__SUPER的地方也一併改掉,不贅述。能夠看到,當在A中時,Val使用的A類型參數,而在B中時使用B類型參數,應該不會有問題了。--------但這並非模板!由於struct的侷限性,咱們經過添加參數爲函數傳入that指針代替this指針,咱們定義類型T是爲了除去繼承時指針類型不匹配的問題, 但並無引入模板。
咱們在繼承類中使用宏來展開基類,這一點跟模板很像,爲何不能作成模板呢?哈哈,咱們能夠模仿一下模板,但真正的模板並非這樣的:
// Class.c
//
#include <stdio.h>
#include <stdlib.h>ide
//////////////////////////////////////////////////////
#define __SUPER(Base, Type, ...) \
union {\
Base super; \
struct {\
Template_##Base(Type, __VA_ARGS__)\
}; \
}
//////////////////////////////////////////////////////
typedef struct A A;
struct A {
#define A_Template(T, Type) \
Type data;\
int (*Val)(T* that, int a);
#define Template_A(T, Type) A_Template(struct T, Type)
Template_A(A, int)
int val;
};
int Val(A* that, int a) {
return that->data + a;
}
A * _A(A * that, int data) {
that->data = data;
that->Val = Val;
return that;
}
//////////////////////////////////////////////////////
typedef struct B {
__SUPER(A, B, int);
int val;
}B;
int myVal(B* that, int a) {
return that->data * a;
}
B* _B(B* that, int val) {
_A(&that->super, val);
that->Val = myVal;
that->val = val;
}
//////////////////////////////////////////////////////
int main() {
A a;
_A(&a, 10);
a.val = 1;
printf("%d %d\n", a.val, a.Val(&a, 20));函數
B b;
_B(&b, 20);
b.val = 2;
printf("%d %d\n", b.val, b.Val(&b, 30));
return 0;
}
看,模板就是#define template那裏,而A在定義的時候就已經將模板特化成int類型,B在定義時將模板特化成int類型。結果仍舊同樣。但這個仍舊不是模板,模板是在定義對象的時候特化,而這個是在定義類型時已經特化。
因爲成員函數並非內聯的(所謂內聯,就是說每個對象包含的函數都是在對象內部擴展開的),因此這些函數必須寫在外部,而外部必須保證struct進行了徹底定義,因此C是沒有辦法作到真正的模板的。
6.繼承鏈
根據C的宏定義標準,宏是不能夠嵌套的。所以若是要實現繼承鏈,__SUPER會形成嵌套。所以,再增長一個___SUPER表示二級繼承鏈,定義和__SUPER如出一轍,這樣,___SUPER會調用Template而後調用__SUPER,構成繼承鏈。若是還有更多繼承,則繼續定義____SUPER。另外,因爲使用的是匿名union,所以基類的super都暴露在外面,繼承鏈中會出現重複super定義,能夠將super定義爲super+基類名來區別。二級繼承鏈定義以下,一級繼承鏈也修改成下面的定義:
#define ___SUPER(Base, ...)\
union {\
Base super##Base; \
struct {\
Template_##Base(__VA_ARGS__)\
}; \
}
好了,這樣就能夠實現多級繼承鏈了。在_B構造函數中將super改成superA,而後添加C類繼承B類(二級繼承):
struct C {
___SUPER(B, C);
};
C * _C(C* that) {
_B(&that->superB, 77);
}
這樣就構成了一個完整的繼承鏈。完整可運行代碼以下:
// Class.c
//
#include <stdio.h>
#include <stdlib.h>優化
typedef struct A A;
typedef struct B B;
typedef struct C C;
//////////////////////////////////////////////////////
#define __SUPER(Base, ...)\
union {\
Base super##Base; \
struct {\
Template_##Base(__VA_ARGS__)\
}; \
}
#define ___SUPER(Base, ...)\
union {\
Base super##Base; \
struct {\
Template_##Base(__VA_ARGS__)\
}; \
}
//////////////////////////////////////////////////////
struct A {
#define A_Template(T) \
int data;\
int (*Val)(T* that, int a);
#define Template_A(T) A_Template( T)
Template_A(A)
int val;
};
int Val(A* that, int a) {
return that->data + a;
}
A * _A(A * that, int data) {
that->data = data;
that->Val = Val;
return that;
}
//////////////////////////////////////////////////////
struct B {
#define B_Template( T) \
__SUPER(A, T);\
int val;
#define Template_B( T) B_Template( T)
Template_B( B)
};
int myVal(B* that, int a) {
return that->data * a;
}
B* _B(B* that, int val) {
_A(&that->superA, val);
that->Val = myVal;
that->val = val;
}
//////////////////////////////////////////////////////
struct C {
___SUPER(B, C);
};
C * _C(C* that) {
_B(&that->superB, 77);
}
//////////////////////////////////////////////////////
int main() {
A a;
_A(&a, 10);
a.val = 1;
printf("%d %d\n", a.val, a.Val(&a, 20));this
B b;
_B(&b, 20);
b.val = 2;
printf("%d %d\n", b.val, b.Val(&b, 30));
C c;
_C(&c);
c.val = 3;
printf("%d %d\n", c.val, c.Val(&c, 30));
return 0;
}
輸出結果:1 30 2 600 3 2310,結果正確。操作系統
7.結束語
雖然沒能作到模板很遺憾,可是可以保證一些class的元素可以被使用在C語言中,已經很不錯了。本次class的應用是在看操做系統內核時,尋找用C語言解決class問題時,其中的一些研究成果,而且在實踐中已經證明可使用----雖然有些晦澀並且並非一個很好的編程體驗(好比下面記載的,對於習慣了class的人來講真的很難改變)
可是須要強調幾點,這也是遺留的問題,以如今的能力仍是沒有辦法解決,期待高人來解決吧:
1.使用和定義類成員(函數)時必定要帶上類對象指針that以代替this
2.struct的繼承和C++的繼承有很大區別,由於struct是經過在子類中從新定義基類來進行的,所以,基類設計必定要注意成員順序,好比上面例子中A的val是放在最後而不是最前,這樣經過子類繼承時子類纔不會將val繼承過去。
3.因爲宏定義的一些特性,致使繼承鏈須要定義多個級別的__SUPER,須要明確當前繼承是第幾級,而後肯定使用哪一個__SUPER
4.第2點所帶來的麻煩並不止這些,若是是連續繼承,全部子類必須擁有基類的全部成員,繼承鏈越靠後類就會變得越臃腫,這僅僅是指類所佔的空間,然而在代碼中使用__SUPER並不會看到這種放大做用
5.如上看到的,全部的成員函數並非內聯函數,而僅僅是指向函數的指針,指向了一個struct外部的函數(在gcc中是不容許直接將函數寫在struct中的,所以只能定義函數指針,其實也至關於一個成員變量而已),因此成員函數沒有辦法作內聯優化。
完(*^_^*).net
stophin 2016/11/26
---------------------
做者:stophin
來源:CSDN
原文:https://blog.csdn.net/stophin/article/details/54646796
版權聲明:本文爲博主原創文章,轉載請附上博文連接!設計