C/C++面向對象編程之封裝

目錄:

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/

聯繫作者:
歡迎關注本人公衆號:

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章