嵌入式軟體開發中,雖然很多的開發工具已經支持C++的開發,但是因為有時考慮運行效率和編程習慣,還是有很多人喜歡用C來開發嵌入式軟體。Miro Samek說:“我在開發現場發現,很多嵌入式軟體開發者(絕大多數是電子工程師)都有工作背景而且他們常常不喜歡用C++。”【1】面向對象編程(OOP)是一種方法... ...
嵌入式軟體開發中,雖然很多的開發工具已經支持C++的開發,但是因為有時考慮運行效率和編程習慣,還是有很多人喜歡用C來開發嵌入式軟體。Miro Samek說:“我在開發現場發現,很多嵌入式軟體開發者(絕大多數是電子工程師)都有工作背景而且他們常常不喜歡用C++。”【1】
面向對象編程(OOP)是一種方法,而不依賴於某特定語言。一般認為,OOP只能使用面向對象的語言,如Smalltalk、C++或Java等固有支持OOP的語言。但是,OOP並不依賴於特殊的語言,只要實現了OOP的封裝、繼承和多態性這三大基本特性,使用非固有支持面向對象的語言,也可以進行面向對象的編程。
面向對象編程方法的三大基本特性是:
- 封裝性---封裝數據(屬性)與函數(方法)到類中的能力;
- 繼承性---基於已有的類定義新類的能力;
- 多態性---在運行時具有一致介面的對象相互替換的能力。
雖然這些特性與面向對象語言有關,但幾乎可以用任何語言實現面向對象的這三大基本特性,包括C語言和彙編語言等。事實上,不管實現語言為何,任何大的軟體系統幾乎都會以某種形式使用抽象(封裝)、繼承或多態性。
1.C語言的封裝
1.1 封裝的本質
在C++中,一個類中包括屬性和方法,聲明一個類例子如下:
class A {
int n; //屬性
A(); //構造函數
void function1(int x){...}; //方法
}
在C++中,用A生成兩個對象a1和a2時,如下:
A a1 = new A();
A a2 = new A();
對於非模板類A,在記憶體中,對所有的對象,方法function1只有一個拷貝,而對每個對象,都有自己獨立的一份屬性拷貝,也就是有多個屬性拷貝,所謂創建一個對象,也就是創建一套屬性。
調用a1.function1()和a2.function1()時,每個對象使用自己的屬性n1和n2,而方法代碼在記憶體中是同一套。這就相當於調用functin1(n1,x)和function1(n2,x)。
在C語言實現中,把所有的屬性放到一個結構中,所有方法中第一個參數用一個指向自己屬性結構的一個指針me, 如下:
function1(struct *me, ...);
這個方法對所有對象有用,具體對哪個對象的屬性操作,可以用me來區分。
1.2 具體例子
生成兩個文件person.h和person.c,定義一個Person結構類型(也就是類名稱),把屬性放到結構中,方法中的第一個參數me是Person的引用指針。
pers1.2on.h文件:
在頭文件中以結構定義類成員屬性,聲明構造方法和析構方法,聲明public方法。
#ifndef PERSON_H
#define PERSON_H
/*1.定義類成員屬性(沒有隱藏)*/
typedef struct PersonTag {
String name__; /*屬性*/
uint8_t age__; /*屬性*/
}Person;
/*2.聲明構造和析構函數*/
void Person_Ctor(Person *me, uint8_t age);/*第一個參數指向屬性*/
void Person_Xtor(Person *me);/*第一個參數指向屬性*/
/*3.聲明public方法*/
void Person_setName(Person *me, String name);/*第一個參數指向屬性*/
String Person_getName(Person *me);/*第一個參數指向屬性*/
/*4.聲明private方法(要在person.c文件中聲明)*/
#endif
person.c文件:
在.c文件中聲明private方法,並實現所有的方法。
#include "person.h"
/*1.聲明private方法, 用static修飾*/
static void Person_setAge(Person *me, uint8_t age);/*第一個參數指向屬性*/
static uint8_t Person_getAge(Person *me);/*第一個參數指向屬性*/
/*2.實現構造和析構函數*/
void Person_Ctor(Person *me, uint8_t age) {
me->age__ = age;
}
void Person_Xtor(Person *me) {
...
}
/*3.實現public方法*/
void Person_setName(Person *me, String name) {
me->name__ = name;
}
String Person_getName(Person *me) {
return me->name__;
}
/*4.實現private方法*/
void Person_setAge(Person *me, uint8_t age) {
me->age__ = age;
}
uint8_t Person_getAge(Person *me) {
return me->age__;
}
app.c中使用Person類:
#include "person.h" /*包含頭*/
void main() {
String tmp;
Person l_ps1; /*創建一個實例,實際就是一個屬性*/
Person l_ps2; /*創建另一個實例,實際就是另一個屬性*/
Person_Ctor(l_ps1, 30); /*顯式調用構造函數*/
Person_setName(l_ps1,"張三"); /*使用其public方法*/
tmp = Person_getName(l_ps1); /*使用其public方法*/
Person_Ctor(l_ps2, 30); /*使用其public方法*/
Person_setName(l_ps2, "李四"); /*使用其public方法*/
tmp = Person_getName(l_ps2); /*使用其public方法*/
}
2.C語言的繼承
Student繼承自Person, 繼承類屬性結構的第一個參數指向父類屬性,這樣可以滿足Liskov替換原則。
student.h文件:
在頭文件中以結構定義類成員屬性,並聲明構造、析構函數和public方法。
#ifndef STUDENT_H
#define STUDENT_H
#include "person.h" /*包含父類頭*/
/*1.定義類屬性(沒有隱藏)*/
typedef struct StudentTag {
Person super; /*第一個參數指向父類Person,繼承父類屬性*/
uint8_t code__; /*子類添加的屬性*/
}Student;
/*2.聲明構造和析構方法*/
void Student_Ctor(Student *me, uint8_t code);
void Student_Xtor(Student *me);
/*3.聲明public方法*/
void Student_setCode(Student *me, uint8_t code);
uint8_t Student_getCode(Student *me);
/*4.聲明private方法(在.c文件中聲明)*/
#endif
student.c文件:
在.c文件中聲明private方法,實現所有的構造方法和析構方法,實現所有的public和private方法。
#include "student.h"
/*1.聲明private方法*/
static void XXX(Student *me, ...);
/*2.構造和析構方法實現*/
void Student_Ctor(Student *me, String name, uint8_t code) {
Person_Ctor((Person*)me, name);/*先調用父類構造函數*/
me->code__ = code;
}
void Person_Xtor(Person *me) {
...
}
/*3.public方法實現*/
void Student_setCode(Student *me, uint8_t code) {
me->code__ = code;
}
app.c中使用Student類:
#include "Student.h" /*包含頭*/
void main() {
String tmp;
Student l_st1; /*創建一個實例,實際就是一個屬性*/
Student l_st2; /*創建另一個實例,實際就是另一個屬性*/
Student_Ctor(l_st1, "張三, 25); /*顯式調用構造函數*/
Student_setCode(l_st1, 30); //使用其public方法
tmp = Student_getCode(l_st1); //使用其public方法
}
3.C語言的多態性
利用C語言的萬能指針void *p可以實現多態。略。
4.總結
用C語言可以實現面向對象的三大特性,適合於嵌入式系統的編程,QP的C版本就是這樣實現的。現在很多的開源軟體,包括postgreSQL、GObject等都採用C語言實現面向對象的方法。採用這個方法,使程式具有更好的可讀性、可擴展性,同時保持了C語言的高效性。