目錄:
C/C++面向對象編程之封裝
C/C++面向對象編程之繼承
C/C++面向對象編程之多態
前言:
什麼是對象:
世界萬物皆可稱爲對象,對象的含義是指具體的某一個事物,即在現實生活中能夠看得見摸得着的事物。在面向對象程序設計中,對象包含兩個含義,其中一個是數據,另外一個是方法。對象則將一類事物的的特性和行爲抽象出來,並封閉起來對待,在計算機裏就是是數據和方法的結合體。
什麼是面向對象:
簡單來說就是以特性和行爲去分析一個東西的思想叫面向對象,關注事物的整體屬性和行爲。
什麼是面向對象編程:
就是採用某種編程語言按照面向對象的思想去編程,從而解決問題,就叫做面向對象編程。
1、面向對象編程
面向對象編程思想的三大特點是:封裝,繼承和多態。這三種機制能夠有效提高程序的可讀性、可擴充性和可重用性。
嵌入式Linux操作系統雖然是使用 C 語言作爲主要的編寫語言,但裏面的設計大部分都使用了面向對象的編程思想。
編程語言只是一種工具,編程思想纔是用好這個工具的關鍵。 類和對象是面向對象編程的特有屬性,雖然C語言不支持類和對象的概念,但是面向對象的編程思想,依然可以指導我們更好的用好C語言這個工具。
注:以下內容把C語言中的結構體和C++的類放在一起對比學習,有助於有C語言基礎的同學更好的理解C++中類的概念,另外本文的目的只在於抽取面向對象編程的思想,不對C++的內容做過多講解。
2、什麼是類和對象
在C語言中,結構體是一種構造類型,可以包含若干成員變量,每個成員變量的類型可以不同;可以通過結構體來定義結構體變量,每個變量擁有相同的性質。
在C++語言中,類也是一種構造類型,但是進行了一些擴展,可以將類看做是結構體的升級版,類的成員不但可以是變量,還可以是函數;不同的是,通過結構體定義出來的變量還是叫變量,而通過類定義出來的變量有了新的名稱,叫做對象(Object)
在 C++ 中,通過類名就可以創建對象,這個過程叫做類的實例化,因此也稱對象是類的一個實例(Instance)
類的成員變量稱爲屬性(Property),將類的成員函數稱爲方法(Method)。
在C語言中的使用struct這個關鍵字定義結構體,在C++ 中使用的class這個關鍵字定義類。
C語言中的結構體:
//通過struct 關鍵字定義結構體
struct rt_object
{
char name[8];
char type;
char flag;
//指向函數的指針類型
void (*display)(void);
};
C++語言中的類:
//通過class關鍵字類定義類
class rt_object{
public:
char name[8];
char type;
char flag;
//類包含的函數
void display(){
printf("123456789");
}
void display(int a){
printf("a=%d",a);
}
};
注意到類中有兩個同名函數display,一個帶參,一個不帶參,這在C++中叫函數的重載。
函數的重載的規則:
- 函數名稱必須相同。
- 參數列表必須不同(個數不同、類型不同、參數排列順序不同等)。
- 函數的返回類型可以相同也可以不相同。
- 僅僅返回類型不同不足以成爲函數的重載。
C++和C語言的編譯方式不同。C語言中的函數在編譯時名字不變,或者只是簡單的加一個下劃線_(不同的編譯器有不同的實現),例如,func() 編譯後爲 func() 或 _func()。
而C++中的函數在編譯時會根據它所在的命名空間、它所屬的類、以及它的參數列表(也叫參數簽名)等信息進行重新命名,形成一個新的函數名。這個新的函數名只有編譯器知道,對用戶是不可見的。對函數重命名的過程叫做名字編碼(Name Mangling),是通過一種特殊的算法來實現的。當發生函數調用時,編譯器會根據傳入的實參去逐個匹配,以選擇對應的函數,如果匹配失敗,編譯器就會報錯,這叫做重載決議(Overload Resolution)。
函數重載僅僅是語法層面的,本質上它們還是不同的函數,佔用不同的內存,入口地址也不一樣。
3、內存分佈的對比
不管是C語言中的結構體或者C++中的類,都只是相當於一個模板,起到說明的作用,不佔用內存空間;結構體定義的變量和類創建的對象纔是實實在在的數據,要有地方來存放,纔會佔用內存空間。
對單片機內存分配不熟悉的可以看這篇文章:單片機堆棧分配
結構體變量的內存模型:
結構體的內存分配是按照聲明的順序依次排列,涉及到內存對齊問題。
爲什麼會存在內存對齊問題,引用傻孩子公衆號裸機思維的文章《漫談C變量——對齊》加以解釋:
在ARM Compiler裏面,結構體內的成員並不是簡單的對齊到字(Word)或者半字(Half Word),更別提字節了(Byte),結構體的對齊使用以下規則:
- 整個結構體,根據結構體內最大的那個元素來對齊。比如,整個結構體內部最大的元素是WORD,那麼整個結構體就默認對齊到4字節。
- 結構體內部,成員變量的排列順序嚴格按照定義的順序進行。
- 結構體內部,成員變量自動對齊到自己的大小——這就會導致空隙的產生。
- 結構體內部,成員變量可以通過 attribute ((packed))單獨指定對齊方式爲byte。
strut對象的內存模型:
//通過struct 關鍵字定義結構體
struct {
uint8_t a;
uint16_t b;
uint8_t c;
uint32_t d;
};
對象的內存模型:
假如創建了 10 個對象,編譯器會將成員變量和成員函數分開存儲:分別爲每個對象的成員變量分配內存,但是所有對象都共享同一段函數代碼,放在code區。如下圖所示:
成員變量在堆區或棧區分配內存,成員函數放在代碼區。對象的大小隻受成員變量的影響,和成員函數沒有關係。對象的內存分佈按照聲明的順序依次排列,和結構體非常類似,也會有內存對齊的問題。
可以看到結構體和對象的內存模型都是非常乾淨的,C語言裏訪問成員函數實際上是通過指向函數的指針變量來訪問(相當於回調),那麼C++編譯器究竟是根據什麼找到了成員函數呢?
實際上C++的編譯代碼的過程中,把成員函數最終編譯成與對象無關的全局函數,如果函數體中沒有成員變量,那問題就很簡單,不用對函數做任何處理,直接調用即可。
如果成員函數中使用到了成員變量該怎麼辦呢?成員變量的作用域不是全局,不經任何處理就無法在函數內部訪問。
C++規定,編譯成員函數時要額外添加一個參數,把當前對象的指針傳遞進去,通過指針來訪問成員變量。
這樣通過傳遞對象指針完成了成員函數和成員變量的關聯。這與我們從表明上看到的剛好相反,通過對象調用成員函數時,不是通過對象找函數,而是通過函數找對象。
這在C++中一切都是隱式完成的,對程序員來說完全透明,就好像這個額外的參數不存在一樣。
4、封裝的思想
C和C++項目組織方式的對比:
不要小看類(Class)這一層封裝,它有很多特性,極大地方便了中大型程序的開發,它讓 C++ 成爲面向對象的語言。
面向對象編程在代碼執行效率上絕對沒有任何優勢,它的主要目的是方便程序員組織和管理代碼,快速梳理編程思路,帶來編程思想上的革新。
面向對象編程是針對開發中大規模的程序而提出來的,目的是提高軟件開發的效率。不要把面向對象和麪向過程對立起來,面向對象和麪向過程不是矛盾的,而是各有用途、互爲補充的。對於C語言同樣可以利用面向對象的編程思想開發中大規模的程序。
4.1 C++語言中類的定義和對象的創建
類的定義:
在 C++裏 struct 關鍵字與 class 關鍵字一般可以通用,只有一個很小的區別。 struct 的成員默認情況下屬性是 public 的,而 class 成員卻是 private 的。既然 struct 關鍵字與 class 關鍵字可以通用,所以在C++的編譯環境下,結構體內也是可以放函數的。
一個簡單的類的定義:
class Student{
public:
public:
void setname(char *name);
void setage(int age);
void setscore(float score);
void say();
//聲明構造函數
Student();
Student(char *name, int age, float score);
private:
//成員變量
char *name;
int age;
float score;
};
//定義構造函數
Student::Student(){
name = NULL;
age = 0;
score = 0.0;
}
//定義構造函數
Student::Student(char *name, int age, float score){
this->name = name;
this->age = age;
this->score = score;
}
void Student::setname(char *name){
this->name = name;
}
void Student::setage(int age){
this->age = age;
}
void Student::setscore(float score){
this->score = score;
}
void Student::say(){
cout<<this->name<<"的年齡是"<<this->age<<",成績是"<<this->score<<endl;
}
創建對象:
創建對象和結構體定義變量非常類似,也可以使用指向對象的指針,不同是class 關鍵字可要可不要。以下幾種方式都可以創建對象:
class Student stu; //在棧上創建對象
Student stu; //在棧上創建對象
Student stu("小明", 15, 92.5f);//在棧上創建對象,並初始化成員變量
Student *pStu = &stu;//在棧上創建對象
Student *pStu = new Student;//在堆上創建對象
訪問類的成員:
通過對象名字訪問成員使用點號.,通過對象指針訪問成員使用箭頭->,這和結構體非常類似:
int main(){
//創建對象
Student stu("小明", 15, 92.5f);
stu.say();
return 0;
}
int main(){
Student *pStu = new Student;
pstu -> setname("李華");
pstu -> setage(16);
pstu -> setscore(96.5);
pstu -> say();
delete pStu; //刪除對象
return 0;
4.2 C語言實現類的定義和對象的創建
在C語言的編譯環境下,不支持結構體內放函數類型,除了函數外,就和C++語言裏定義類和對象的思路完全一樣了。
以下暫且把結構體稱之爲類,把結構體定義的變量稱之爲對象,以方便於理解面向對象的編程思想。
類的定義:
struct Student{
//成員變量
char *name;
int age;
float score;
//成員函數用指向函數的指針替代
void (*say)(struct Student * stu);
};
void show(struct Student * stu)
{
printf("%s的年齡是%d,成績是%f\n",stu->name,stu->age,stu->score);
}
void setname(struct Student * stu,char *m_name)
{
if(NULL != stu){
stu->name = m_name;
}
}
void setage(struct Student * stu,int m_age)
{
if(NULL != stu){
stu->age= m_age;
}
}
void setscore(struct Student * stu,float m_score)
{
if(NULL != stu){
stu->score= m_score;
}
}
void setsay(struct Student * stu,void (*say)(struct Student * stu))//設置回調
{
if(NULL != stu){
stu->say= say;
}
}
創建對象:
以下幾種方式都可以創建對象:
struct Student stu; //在棧上創建對象
struct Student stu = {"小明",15,92.5f,show}; //在棧上創建對象,並初始化成員變量
struct Student *pStu = &stu;//在棧上創建對象
struct Student *pStu = (struct Student *)malloc(sizeof(struct Student));//在堆上創建對象
訪問類的成員:
通過對象名字訪問成員使用點號.,通過對象指針訪問成員使用箭頭->
int main(){
//創建對象
Student stu= {"小明",15,92.5f,show};
stu.say(&stu);
setname(&stu,"小王");
setage(&stu,18);
setscore(&stu,99.0f);
stu.say(&stu);
return 0;
}
int main(){
//創建對象
struct Student *pStu = (struct Student *)malloc(sizeof(struct Student));//在堆上創建對象
setname(pStu,"小王");
setage(pStu,18);
setscore(pStu,99.0f);
setsay(pStu,show);
pStu->say(pStu);
free(pStu); //刪除對象
return 0;
}
參考資料:http://c.biancheng.net/cplus/
聯繫作者:
歡迎關注本人公衆號: