面向對象的三個特征:封裝,繼承,多态。但是C語言不是面向對象程式設計語言,是以需要借助一些技巧來實作這三個特征:(1)C語言沒有成員函數,struct隻能封裝資料,不能封裝方法,可以在struct裡使用函數指針;(2)C語言不支援繼承,可以在一個struct裡包含另一個struct;(3)C語言也沒有虛函數,實作多态就更麻煩了,我不會。舉個例子:現在有Point結構體定義如下
struct Point{ float x; float y;};
如果想從Point派生出Circle,可以這麼寫:
struct Circle{ struct Point o; //圓心 float r; //半徑};struct Circle C;struct Point *pPoint = (struct Point*)(&C);
此時如果将Circle類型的指針強制轉換成Point類型指針,因為記憶體是順序連續的,是以沒問題,pPoint->x通路的是C.o.x,pPoint->y通路的是C.o.y.也就是說在需要基類指針的地方可以傳入派生類的指針。
但是如果将o和r的順序換一下就錯了。
struct Circle{ float r; //半徑 struct Point o; //圓心};struct Circle C;struct Point *pPoint = (struct Point*)(&C);//這麼轉會出問題
簡單來說就是如果想使用C語言的繼承,那麼基類對象一定要寫在派生類的最前面!但是對C語言程式設計而言,不建議使用繼承和多态,使用封裝就可以了。對封裝的了解可以退化為:不直接通路結構體的成員變量而是通過函數去通路(C語言沒有private屬性,直接通路成員變量總是可以的,但是不建議這麼做);此外C語言結構體沒有this指針,是以使用函數指針封裝方法也用的比較少,更多的是提供一些全局函數,将結構體指針作為參數傳進去操作。
假設有個Moubus資料包的結構體:
#define PACK_SIZE 256struct ModbusPack{ uint8_t data[PACK_SIZE]; uint8_t len;};
還有和它對應的操作函數:
void Modbus_init(struct ModbusPack *pThis);void Modbus_append(struct ModbusPack *pThis,uint8_t v);void Modbus_appends(struct ModbusPack *pThis,uint8_t *vs,uint8_t len);uint8_t Modbus_len(struct ModbusPack *pThis);void Modbus_append_crc(struct ModbusPack *pThis);uint8_t Modbus_check(struct ModbusPack *pThis);void Modbus_init_query(struct ModbusPack *pThis,uint8_t addr,uint8_t fc,uint16_t regBase,uint16_t regNum);void Modbus_print(struct ModbusPack *pThis);
對于Modbus_len這個函數的實作如下:
uint8_t Modbus_len(struct ModbusPack *pThis) { //省略對指針是否為空的判斷 return pThis->len;}
看起來比直接通路成員變量複雜,好處在于當修改了ModbusPack的實作,将len改為m_len,則隻需要修改Modbus_xxx函數就行了,不影響其他地方對這個函數的調用,因為調用的地方隻依賴于這個函數的名字,而不需要知道相應結構體的具體實作。
struct ModbusPack{ uint8_t m_data[PACK_SIZE]; uint8_t m_len;};uint8_t Modbus_len(struct ModbusPack *pThis) { //省略對指針是否為空的判斷 return pThis->m_len;}
簡單的使用例子如下:
#include "modbus.h"int main(){ struct ModbusPack pack; Modbus_init(&pack);//C語言沒有構造函數,通過初始化函數初始化對象 Modbus_init_query(&pack,0x01,0x03,0x0000,0x0002); Modbus_print(&pack); return 0;}
運作結果:
友情提示:找對象雖易,面向對象不易,且行且珍惜。