天天看點

C語言應用(1)——利用C語言模拟面向對象語言中的特性

其實早就想寫這篇文章很久了,由于先前有個項目需要用到C來制作其系統架構,後來由于其他原因一直沒有時間寫出來,現在将把我那裡學習到的東西一一道上。

應該很多人都認為面向對象的語言開發起來十分友善。由于其封裝、繼承等特性使面向對象語言更容易明确其結構,使開發過程中的結構清晰。從另一方面看面向對象語言在運作時将比非面向對象語言更耗資源、運作性能上也有所下降,這可能是導緻現有的驅動程式大多使用C語言開發的原因。也許有人會問,現在IC能力都這麼強,為什麼還會執着着那個資源及運算能力呢?對于消費類電子可能這個影響不大,但對于工業方面來說這可能是緻命的(實時要求較高)。好了,不扯遠了,現在開始逐漸進入正題。

重溫面向對象語言:

在說到這裡時,我當作大家都了解并開發過C++(及類似的面向對象的語言)。面向對象語言中,有幾個重要概念:繼承、多态、重載、封裝。

繼承主要是為了得到父類中的一些變量及函數(或方法),這樣可以抽象出類的共性,當子類繼承後可以減少一些變量和函數的重複聲明,達到類的複用,也可以減少類與變量和函數的硬性綁定(全寫在一個類中)。

多态是指利用父類指針/名稱(C++中是指針,Java是名稱)可以引用子類對象,來達到調用子類的函數。

重載是指一個類中的方法與另一個方法同名,但是參數表不同。

封裝主要是通過确定變量和函數的通路類型來達到哪些變量函數可以對外使用(public),哪些變量函數不能對外使用(private),以及哪些變量函數可以被繼承(protected)。

技術應用:

Unix和Linux都是利用C語言來開發的,但完全使用結構化式程式設計會使其結構混亂,據說開發者也把C語言“類面向對象化”,使其結構更加清晰(具體我沒有研究過),大家可以看看Linux核心代碼來了解這種程式設計方式。

應用實況:

由于本人是在ARM中開發,而且沒有使用作業系統,是以為了防止記憶體洩漏等問題,是以我沒有使用動态建立(malloc、calloc、realloc)。是以有些模仿可能叙述不到。

執行個體叙述:

要利用C語言模仿面向對象的功能是可以的,但并不能與面向對象的概念相混淆,隻能說是功能相近,并不是相同。

下面将講述本人的見解,如果不當之處,請高手指點!!

一、C語言中的“類”

1)定義“類”的成員變量

C語言沒有類,但可以使用結構體充當一個類:

type struct CClass
{
struct CClass this; //擷取結構體自身指針
int m_nNum;
char *p;
...    //自行添加更多變量
}CClass;      

與類不同,結構體隻能定義變量,不能夠定義函數,可以通過函數指針的方法來實作其功能。

注:函數指針在很多情況下都不建議使用,這并不是它不好用,而是容易出錯,而且出錯後也難道測試出出錯的位置。其實指針在C語言中是最強大的東西(個人認為),但由于容易出錯才有那麼多人反對使用。在使用指針(不管什麼指針)都要注意,以防出錯。

2)定義“類”的成員變量及函數

typedef struct CClass
{
struct CClass this;
int m_nNum;
char *p;
...    //自行添加更多變量
void (*MyFunction)(struct CClass this,int Num);
...    //自行添加更多函數
}CClass;      

好了,這裡定義了函數指針,感覺是不是很像一個類?也許有人會問,為什麼加入一個struct CClass this 在函數第一個參數呢?

原因是這樣的,由于結構體不像類,類中可以随意調用類中的成員,但是結構體卻不一樣,它無法得知結構體中的變量,是以可能通過參數形式傳入。這裡struct CClass this也友善了函數的調用。

3)定義“類”的構造函數

與面向對象不同,C語言的“類”的構造函數不能放在“類”中,隻能放在“類”外。

CClass * CClassCtor(CClass *this);

其中函數名可以任意取(隻要一看就明白,就行了),構造函數主要是變量的初始化,以及函數指針的指派。

4)“類”中函數的綁定

void MyFunction(struct CClass this,int Num)
{
     //具體操作
}
CClass * CClassCtor(CClass *this)
{
     this->m_nNum = 0;
     this->p = 'a';
     this->MyFunction = MyFunction;
}      

這裡MyFunction函數名稱可以任取,主要通過指派來比對函數指針。

當調用“類”CClass中的MyFunction時,隻要->MyFunction()就可以了,如下。

CClass *pClass,objClass; //定義一指針、一結構體變量

pClass = CClassCtor(&objClass); //調用構造函數

pClass->MyFunction(pClass,10); //調用MyFunction函數

5)“類”中的不足

C語言中的“類”并不能夠像面向對象語言那樣,通過public、protected、private來控制變量、函數的通路。

二、C語言中“類”的“繼承”

其實結構體并沒有繼承可言,隻是盡量做到類似繼承。

1)“繼承”方式1

由于沒有繼承機制,隻能簡單地重寫變量函數(我覺得這方法很蠢,但是最直覺,但也最容易出錯,而且一但父類修改後,其所有子類都得修改)

typedef struct CFather
{
      struct CFather *this
      char m_Name[10];
      int m_Age;
      void (*Walk)();
}CFather;
typedef struct CSon
{
      struct CSon *this    //這個來自父“類”,但要改為目前“類”指針類型
      //來自父“類”的變量及函數
      char m_Name[10];
      int m_Age;
      void (*Walk)();
      //子“類”定義的函數
      int m_PocketMoney;
      void (*UsePocketMoney)(int nNum);
}CSon;      

可能有些人在編寫面向對象語言時,會把變量和函數分開。但在C語言的“類”中,如果想達到“繼承”,必須先寫父“類”變量函數,再寫新增的變量函數,而且變量及函數上的數量及順序得完全一緻(後面将講述原因)。

注:“繼承”時,子“類”應包含父“類”的是以變量及函數,而且順序上完全一緻,當寫完父“類”變量及函數時,再寫子“類”的變量和函數。

2)“繼承”方式2

這種方式主要通過内嵌結構體指針來達到“繼承”。這樣可以減少重複的代碼,而且可以減少漏寫的問題。(這種方法當修改父類時,并不影響子類結構,隻要修改相應函數功能即可。但是調用父類函數時,顯得很臃腫)

typedef struct CFather
{
      struct CFather *this
      char m_Name[10];
      int m_Age;
      void (*Walk)();
}CFather;
typedef struct CSon
{
      //來自父“類”的變量及函數
      CFather *m_pfather;
      //(或CFather m_father;)
      //子“類”定義的函數
      int m_PocketMoney;
      void (*UsePocketMoney)(int nNum);
}CSon;      

當使用指針時,可以通過malloc、calloc等來動态建立父結點,也可以在全局中定義再向指針賦位址。

三、C語言中“類”的“多态”

前面說過,父“類”的順序要與子“類”上的數量及順序一緻,主要是為了實作“多态”。那麼C語言怎麼實作“多态”呢?

由于結構體是一片連續的記憶體區域,是以當結構體被聲明時,将配置設定特定的順序,再加上各個類型有各自的大小(通過sizeof()可以得出),可以通過大小要擷取某片區域的内容。

父“類”指針就像一個記憶體映射表,當父類指向子“類”時,父“類”會按其順序從頭到尾與子“類”開始對應,但通常子“類”聲明的都比父類多,是以子“類”的部分變量及函數并沒有被父“類”指針是以映射,也是以達到了“多态”的效果。

隻有在使用“繼承”方式1 和 “繼承”方式2中的非指針父“類”時,才能夠使用其“多态”特性。

使用“繼承”方式1 好還是使用“繼承”方式2,這就得看具體需求了。

1)方式1雖然比較蠢,但是這樣隻要“指針->函數()”即可調用其函數。

2)使用“繼承”方式2,當子“類”還有多個子“類”時,将使用多個“指針->father.[father. ···]函數()”來調用。

下圖為方式一的“類”定義,其中“父類”中的成員,“子類”要全部重複寫一遍,當“父類”指針指向“子類”時,可以通過調用printAge();來調用“子類”中對應的函數。

C語言應用(1)——利用C語言模拟面向對象語言中的特性

下圖為方式二的“類”定義,其中“子類”包含了“父類”中的成員,使用些方法不利于“多态”,因為不管體積指針,都會占用相同大小的記憶體空間,導緻記憶體位址并不對應,即當調用printAge()時,将會出現指針異常。

C語言應用(1)——利用C語言模拟面向對象語言中的特性

如果想使用類似上面的包含關系,而且又想利用“多态”,可以寫成如下:

C語言應用(1)——利用C語言模拟面向對象語言中的特性

這樣使用包含非指針的方法,可以達到又包含,又“多态”的效果,但是有沒有不良效果,本人并沒有仔細研究。

四、C語言中函數的重載

其實在C語言中,并不可以函數同名,因為重載在C語言中也成為不可能的事件了(可能隻是我不會實作而已)。

五、C語言中的封裝性

C語言中也并沒有像C++那樣的封裝性,因為隻要變量或函數在頭檔案中聲明了,就可以被随意調用。如果不想變量或函數被調用,可以在.c檔案中聲明靜态變量或函數。這樣,即使頭檔案被包含了,也可以把資料封裝在.c檔案中,即使它沒有像C++那優秀的封裝性。

總結:

利用上面方法,可以提高代碼的利用性,可以類似C++等面向對象語言一樣,通過加“類”,來建構自己想要的效果。以上皆是本人學習時使用的方法及自身了解的一些觀點。如果有不當之處請提出,友善我去改正。

轉載于:https://www.cnblogs.com/bakasen/archive/2012/08/20/2647561.html

繼續閱讀