學習嵌入式,C語言是必須學習的一門語言,C語言在設計之初是為了提供一種能以簡易的方式編譯、處理低級存儲器、產生少量的機器碼以及不需要任何運行環境支持便能運行的編程語言。C語言不光提供了許多低級處理的功能,而且保持著良好跨平臺的特性,以一個標準規格寫出的C語言程序可在許多電腦平臺上進行編譯,甚至包含一些嵌入式處理器(單片機或稱MCU)以及超級電腦等作業平臺。
掌握了C語言,其他的語言也會很容易上手,一些面向對象的語言比如:C++、JAVA等都會很輕松的上手。
我們都知道面向對象的三大基本特征:封裝、繼承和多態,C++語言和編譯器都對這些特征有著強有力的支持,但是對于C這樣的函數式語言,如何實現面向對象?引用一句話:面向對象從來都是思想,而不是語言! 理解面向對象的編程思想,我們使用C語言這樣的較低級的語言也同樣可以實現OOP,里面具體用到的有C語言中的宏,結構體,函數指針, 聚合組合等知識。在C中有許多技巧可以實現多態。
本文的目的是使用C語言實現繼承和多態。通過創建一個VTable(virtual table)和在基類和派生類對象之間提供正確的訪問,我們能在C中實現繼承和多態。VTable能通過維護一張函數表指針表來實現。為了提供基類和派生類對象之間的訪問,我們可以在基類中維護派生類的引用和在派生類中維護基類的引用。
在C中實現繼承和多態之前,首先我們看看知道類(Class)在C++中如何表示。
1、類在C中的表示
//Person.h
class Person
{
private:
char* pFirstName;
char* pLastName;
public:
Person(constchar* pFirstName, constchar* pLastName); //constructor
~Person(); //destructorvoid displayInfo();
void writeToFile(constchar* pFileName);
};
在C++中的創建一個類"Person"。
用C語言來表示上面的類,我們可以使用結構體,并用操作結構體的函數表示成員函數。
//Person.h
typedef struct _Person
{
char* pFirstName;
char* pLastName;
}Person;
void new_Person(const char* const pFirstName, const char* const pLastName); //constructor
void delete_Person(Person* const pPersonObj); //destructor
void Person_DisplayInfo(Person* const pPersonObj);void Person_WriteToFile(Person* const pPersonObj, const char* const pFileName);
這里,定義的操作結構體Person的函數沒有封裝。為了實現封裝,即綁定數據、函數、函數指針。我們需要創建一個函數指針表。
構造函數new_Person()將設置函數指針值以指向合適的函數。這個函數指針表將作為對象訪問函數的接口。
2. 下面我們重新定義C中實現類Person。
//Person.h
typedef struct _Person Person;
//declaration of pointers to functions
typedef void (*fptrDisplayInfo)(Person*);typedef void (*fptrWriteToFile)( Person*, constchar*);typedef void (*fptrDelete)( Person *) ;
typedef struct _Person
{
char* pFName;
char* pLName;
//interface for function
fptrDisplayInfo Display;
fptrWriteToFile WriteToFile;
fptrDelete Delete;
}Person;
Person* new_Person(const char* const pFirstName,
const char* const pLastName); //constructor
void delete_Person(Person* const pPersonObj); //destructor
void Person_DisplayInfo(Person* const pPersonObj);void Person_WriteToFile(Person* const pPersonObj, const char* pFileName);
new_Person()函數作為構造函數,它返回新創建的結構體實例。它初始化函數指針接口去訪問其它成員函數。
這里要注意的一點是,我們僅僅定義了那些允許公共訪問的函數指針,并沒有給定私有函數的接口。
讓我們看一下new_Person()函數或C中類Person的構造函數。
//Person.c
person* new_Person(constchar* const pFirstName, constchar* const pLastName)
{
Person* pObj = NULL;
//allocating memory
pObj = (Person*)malloc(sizeof(Person));
if (pObj == NULL)
{
return NULL;
}
pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1));
if (pObj->pFirstName == NULL)
{
return NULL;
}
strcpy(pObj->pFirstName, pFirstName);
pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1));
if (pObj->pLastName == NULL)
{
return NULL;
}
strcpy(pObj->pLastName, pLastName);
//Initializing interface for access to functions
pObj->Delete = delete_Person;
pObj->Display = Person_DisplayInfo;
pObj->WriteToFile = Person_WriteToFile;
return pObj;
}
其他幾個方法的實現:
void delete_Person(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
free(pPersonObj);
}
void Person_DisplayInfo(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
printf("pFirstName:%s pLastName:%s\n",pPersonObj->pFirstName,pPersonObj->pLastName);
}
void Person_WriteToFile(Person* const pPersonObj, const char* pFileName)
{
printf("call Person_WriteToFile()\n");
}
創建完對象之后,我們能夠訪問它的數據成員和函數。
main()
{
Person* pPersonObj = new_Person("pengdan", "farsight");
pPersonObj->Display(pPersonObj);
pPersonObj->WriteToFile(pPersonObj, "persondata.txt");
pPersonObj->Delete(pPersonObj);
pPersonObj = NULL;
}
注意:不像C++,在C中我們不能在函數中直接訪問數據成員。在C++中,可以隱式通過“this”指針直接訪問數據成員。
我們知道C中是沒有“this”指針的,通過顯示地傳遞對象給成員函數。在C中為了訪問類的數據成員,
我們需要把調用對象作為函數參數傳遞。上面的例子中,我們把調用對象作為函數的第一個參數,通過這種方法,
函數可以訪問對象的數據成員。
3、在C中類的表現
Person類的表示——檢查初始化接口指向成員函數:
3.1、繼承和多態的簡單例子
繼承-Employee類繼承自Person類:
在上面的例子中,類Employee繼承類Person的屬性。因為DisplayInfo()和WriteToFile()函數是virtual的,
我們能夠從Person的實例訪問Employee對象中的同名函數。為了實現這個,我們創建Person實例的時候也初始化Employee類。
多態使這成為可能。 在多態的情況下,去解析函數調用,C++使用VTable——即一張函數指針表。
前面我們在結構體中維護的指向函數的指針接口的作用類似于VTable。
//Polymorphism in C++
Person PersonObj("pengdan", "farsight");
Employee EmployeeObj("kouxiaojuan", "liuyan", "HR", "TCS", 40000);
Person* ptrPersonObj = NULL;
//preson pointer pointing to person object
ptrPersonObj = &PersonObj;//displaying person info
ptrPersonObj ->Display();//writing person info in the persondata.txt file
ptrPersonObj ->WriteToFile("persondata.txt");
//preson pointer pointing to employee object
ptrPersonObj = &EmployeeObj;//displaying employee info
ptrPersonObj ->Display();//writing empolyee info in the employeedata.txt file
ptrPersonObj ->WriteToFile("employeedata.txt");
在C中,繼承可以通過在派生類對象中維護一個基類對象的引用來完成。在基類實例的幫助下,women可以訪問基類的數據成員和函數。然而,為了實現多態,基類對象應該能夠訪問派生類對象的數據。為了實現這個,基類應該有訪問派生類的數據成員的權限。
為了實現虛函數,派生類的函數簽名應該和基類的函數指針類似。即派生類函數將以基類對象的一個實例為參數。我們在基類中維護一個派生類的引用。在函數實現上,我們可以從派生類的引用訪問實際派生類的數據。
3.2、在C中結構體中的等效表示
C中的繼承-Person和Employee結構體:
如圖所示,我們在基類結構體中聲明了一個指針保存派生類對像,并在派生類結構體中聲明一個指針保存基類對象。
在基類對象中,函數指針指向自己的虛函數。在派生類對象的構造函數中,我們需要使基類的接口指向派生類的成員函數。這使我們可以通過基類對象(多態)靈活的調用派生類函數。更多細節,請檢查Person和Employee對象的構造函數。
當我們討論C++中的多態時,有一個對象銷毀的問題。為了正確的清楚對象,它使用虛析構函數。在C中,這可以通過使基類的刪除函數指針指向派生類的析構函數。派生類的析構函數清楚派生類的數據和基類的數據和對象。注意:檢查例子的源碼中,實現須構造函數和虛函數的實現細節。
//Person.h
typedef struct _Person Person;
//pointers to function
typedef void (*fptrDisplayInfo)(Person*);typedef void (*fptrWriteToFile)(Person*, const char*);typedef void (*fptrDelete)(Person*) ;
typedefstruct _person
{
void* pDerivedObj;
char* pFirstName;
char* pLastName;
fptrDisplayInfo Display;
fptrWriteToFile WriteToFile;
fptrDelete Delete;
}person;
Person* new_Person(constchar* const pFristName,
constchar* const pLastName); //constructor
void delete_Person(Person* const pPersonObj); //destructor
void Person_DisplayInfo(Person* const pPersonObj);void Person_WriteToFile(Person* const pPersonObj, constchar* const pFileName);
//Person.c//construction of Person object
Person* new_Person(constchar* const pFirstName, constchar* const pLastName)
{
Person* pObj = NULL;
//allocating memory
pObj = (Person*)malloc(sizeof(Person));
if (pObj == NULL)
{
return NULL;
}
//pointing to itself as we are creating base class object
pObj->pDerivedObj = pObj;
pObj->pFirstName = malloc(sizeof(char)*(strlen(pFirstName)+1));
if (pObj->pFirstName == NULL)
{
return NULL;
}
strcpy(pObj->pFirstName, pFirstName);
pObj->pLastName = malloc(sizeof(char)*(strlen(pLastName)+1));
if (pObj->pLastName == NULL)
{
return NULL;
}
strcpy(pObj->pLastName, pLastName);
//Initializing interface for access to functions//destructor pointing to destrutor of itself
pObj->Delete = delete_Person;
pObj->Display = Person_DisplayInfo;
pObj->WriteToFile = Person_WriteToFile;
return pObj;
}
創建Person對象
其他幾個方法的實現:
void delete_Person(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
free(pPersonObj);
}
void Person_DisplayInfo(Person* const pPersonObj)
{
printf("call Person_DisplayInfo()\n");
printf("pFirstName:%s pLastName:%s\n",pPersonObj->pFirstName,pPersonObj->pLastName);
}
void Person_WriteToFile(Person* const pPersonObj, const char* pFileName)
{
printf("call Person_WriteToFile()\n");
}
Person對象的結構
創建Employee對象
//Employee.h
#include "Person.h"
typedef struct _Employee Employee;
typedefstruct _Employee
{
Person* pBaseObj;
char* pDepartment;
char* pCompany;
int nSalary;
//If there is any employee specific functions; add interface here.
}Employee;
Person* new_Employee(const char* const pFirstName, const char* const pLastName,
const char* const pDepartment, const char* const pCompany,
int nSalary); //constructor
void delete_Employee(Person* const pPersonObj); //destructor
void Employee_DisplayInfo(Person* const pPersonObj);void Employee_WriteToFile(Person* const pPersonObj, const char* const pFileName);
//Employee.c
Person* new_Employee(const char* const pFirstName, const char* const pLastName,
const char* const pDepartment,
const char* const pCompany, int nSalary)
{
Employee* pEmpObj;
//calling base class construtor
Person* pObj = new_Person(pFirstName, pLastName);
//allocating memory
pEmpObj = malloc(sizeof(Employee));
if (pEmpObj == NULL)
{
pObj->Delete(pObj);
return NULL;
}
pObj->pDerivedObj = pEmpObj;
pEmpObj->pDepartment = malloc(sizeof(char)*(strlen(pDepartment)+1));
if(pEmpObj->pDepartment == NULL)
{
return NULL;
}
strcpy(pEmpObj->pDepartment, pDepartment);
pEmpObj->pCompany = malloc(sizeof(char)*(strlen(pCompany)+1));
if(pEmpObj->pCompany== NULL)
{
return NULL;
}
strcpy(pEmpObj->pCompany, pCompany);
pEmpObj->nSalary = nSalary;
pObj->Delete = delete_Employee;
pObj->Display = Employee_DisplayInfo;
pObj->WriteToFile = Employee_WriteToFile;
return pObj;
}
Employee對象的結構
注意:從基類函數到派生類函數改變了接口(VTable)中指針位置。現在我們可以從基類(多態)訪問派生類函數。我們來看如何使用多態。
main()
{
Person* PersonObj = new_Person("pengdan", "farsight");
Person* EmployeeObj = new_Employee("kouxiaojuan", "liuyan","HR", "TCS", 40000);
PersonObj->Display(PersonObj);
PersonObj->WriteToFile(PersonObj,"persondata.txt");
PersonObj->Delete(PersonObj);
EmployeeObj->Display(EmployeeObj);
EmployeeObj->WriteToFile(EmployeeObj, "employeedata.txt");
EmployeeObj->Delete(EmployeeObj);
}
結論:
通過以上例子,我們看到使用c語言也可以實現封裝、繼承、多態,學習好以上知識會有助于我們更好的學習面向對象的語言。
4.Linux內核當中很多機制也有向對象的思想。
4.1下面我們以platform總線來分析
以下是platform總線重要的兩個結構體platform_device 和 platform_driver。
其中platform_driver中有個很重要的成員 struct device_driver driver;
該結構體是通用的驅動結構體函數,我們可以理解為一個基類,
以下是基于該總線實現的定義的platform_driver結構體變量
static struct platform_driver driver={
.probe = hello_probe,
.driver.name = "fs4412-beep",
.remove = hello_remove,
};
在這里我們可以看到我們對繼承過來的driver成員進行了賦值,我們跟蹤內核源碼platform_driver_register( )
我們可以看到此處,對driver的成員做了進一步賦值,對于driver結構體中幾個函數實現方法進行了重新賦值,
因為對于不論屬于哪種總線的驅動程序,都需要定義一個driver專用的結構體來維護驅動程序,比如I2C、spi、USB等。
這些總線在實現上
是不同的,但是linux內核要求不論那種總線都要有device_driver類型的成員,有些屬性和函數的實現是由device_driver繼承來的,類似于類的繼承的概念;但是有些成員和函數是要重新編寫的,這就類似于面向對象的多態,如在不同的總線下,probe、remove、shutdown幾種實現方法不一樣。