1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一步步分析-C語言如何面向?qū)ο缶幊?/h1>

        共 6314字,需瀏覽 13分鐘

         ·

        2021-03-03 12:29

        一、前言

        在嵌入式開發(fā)中,C/C++語言是使用最普及的,在C++11版本之前,它們的語法是比較相似的,只不過C++提供了面向?qū)ο?/span>的編程方式。

        雖然C++語言是從C語言發(fā)展而來的,但是今天的C++已經(jīng)不是當(dāng)年的C語言的擴(kuò)展了,從2011版本開始,更像是一門全新的語言。

        那么沒有想過,當(dāng)初為什么要擴(kuò)展出C++?C語言有什么樣的缺點導(dǎo)致C++的產(chǎn)生?

        C++在這幾個問題上的解決的確很好,但是隨著語言標(biāo)準(zhǔn)的逐步擴(kuò)充,C++語言的學(xué)習(xí)難度也逐漸加大。沒有開發(fā)過幾個項目,都不好意思說自己學(xué)會了C++,那些左值、右值、模板、模板參數(shù)、可變模板參數(shù)等等一堆的概念,真的不是使用2,3年就可以熟練掌握的。

        但是,C語言也有很多的優(yōu)點:

        其實最后一個優(yōu)點是最重要的:使用的人越多,生命力就越強(qiáng)。就像現(xiàn)在的社會一樣,不是優(yōu)者生存,而是適者生存。
        這篇文章,我們就來聊聊如何在C語言中利用面向?qū)ο蟮乃枷雭砭幊?。也許你在項目中用不到,但是也強(qiáng)烈建議你看一下,因為我之前在跳槽的時候就兩次被問到這個問題。

        二、什么是面向?qū)ο缶幊?/span>

        有這么一個公式:程序=數(shù)據(jù)結(jié)構(gòu)+算法

        C語言中一般使用面向過程編程,就是分析出解決問題所需要的步驟,然后用函數(shù)把這些步驟一步一步調(diào)用,在函數(shù)中對數(shù)據(jù)結(jié)構(gòu)進(jìn)行處理(執(zhí)行算法),也就是說數(shù)據(jù)結(jié)構(gòu)和算法是分開的

        C++語言把數(shù)據(jù)和算法封裝在一起,形成一個整體,無論是對它的屬性進(jìn)行操作、還是對它的行為進(jìn)行調(diào)用,都是通過一個對象來執(zhí)行,這就是面向?qū)ο缶幊趟枷搿?/span>

        如果用C語言來模擬這樣的編程方式,需要解決3個問題:

        1. 數(shù)據(jù)的封裝
        2. 繼承
        3. 多態(tài)

        第一個問題:封裝

        封裝描述的是數(shù)據(jù)的組織形式,就是把屬于一個對象的所有屬性(數(shù)據(jù))組織在一起,C語言中的結(jié)構(gòu)體類型天生就支持這一點。

        第二個問題:繼承

        繼承描述的是對象之間的關(guān)系,子類通過繼承父類,自動擁有父類中的屬性和行為(也就是方法)。這個問題只要理解了C語言的內(nèi)存模型,也不是問題,只要在子類結(jié)構(gòu)體中的第一個成員變量的位置放置一個父類結(jié)構(gòu)體變量,那么子類對象就繼承了父類中的屬性。

        另外補(bǔ)充一點:學(xué)習(xí)任何一種語言,一定要理解內(nèi)存模型!

        第三個問題:多態(tài)

        按字面理解,多態(tài)就是“多種狀態(tài)”,描述的是一種動態(tài)的行為。在C++中,只有通過基類引用或者指針,去調(diào)用虛函數(shù)的時候才發(fā)生多態(tài),也就是說多態(tài)是發(fā)生在運行期間的,C++內(nèi)部通過一個虛表來實現(xiàn)多態(tài)。那么在C語言中,我們也可以按照這個思路來實現(xiàn)。

        如果一門語言只支持類,而不支持多態(tài),只能說它是基于對象的,而不是面向?qū)ο蟮摹?/span>

        既然思路上沒有問題,那么我們就來簡單的實現(xiàn)一個。

        三、先實現(xiàn)一個父類,解決封裝的問題

        Animal.h

        #ifndef _ANIMAL_H_#define _ANIMAL_H_
        // 定義父類結(jié)構(gòu)typedef struct { int age; int weight;} Animal;
        // 構(gòu)造函數(shù)聲明void Animal_Ctor(Animal *this, int age, int weight);
        // 獲取父類屬性聲明int Animal_GetAge(Animal *this);int Animal_GetWeight(Animal *this);
        #endif
        Animal.c
        #include "Animal.h"
        // 父類構(gòu)造函數(shù)實現(xiàn)void Animal_Ctor(Animal *this, int age, int weight){ this->age = age; this->weight = weight;}
        int Animal_GetAge(Animal *this){ return this->age;}
        int Animal_GetWeight(Animal *this){ return this->weight;}

        測試一下:

        #include <stdio.h>#include "Animal.h"#include "Dog.h"
        int main(){ // 在棧上創(chuàng)建一個對象 Animal a; // 構(gòu)造對象 Animal_Ctor(&a, 1, 3); printf("age = %d, weight = %d \n", Animal_GetAge(&a), Animal_GetWeight(&a)); return 0;}

        可以簡單的理解為:在代碼段有一塊空間,存儲著可以處理Animal對象的函數(shù);在棧中有一塊空間,存儲著a對象。

        與C++對比:在C++的方法中,隱含著第一個參數(shù)this指針。當(dāng)調(diào)用一個對象的方法時,編譯器會自動把對象的地址傳遞給這個指針。

        所以,在Animal.h中函數(shù)我們就模擬一下,顯示的定義這個this指針,在調(diào)用時主動把對象的地址傳遞給它,這樣的話,函數(shù)就可以對任意一個Animal對象進(jìn)行處理了。

        四、 實現(xiàn)一個子類,解決繼承的問題

        Dog.h

        #ifndef _DOG_H_#define _DOG_H_
        #include "Animal.h"
        // 定義子類結(jié)構(gòu)typedef struct { Animal parent; // 第一個位置放置父類結(jié)構(gòu) int legs; // 添加子類自己的屬性}Dog;
        // 子類構(gòu)造函數(shù)聲明void Dog_Ctor(Dog *this, int age, int weight, int legs);
        // 子類屬性聲明int Dog_GetAge(Dog *this);int Dog_GetWeight(Dog *this);int Dog_GetLegs(Dog *this);
        #endif

        Dog.c
        #include "Dog.h"
        // 子類構(gòu)造函數(shù)實現(xiàn)void Dog_Ctor(Dog *this, int age, int weight, int legs){ // 首先調(diào)用父類構(gòu)造函數(shù),來初始化從父類繼承的數(shù)據(jù) Animal_Ctor(&this->parent, age, weight); // 然后初始化子類自己的數(shù)據(jù) this->legs = legs;}
        int Dog_GetAge(Dog *this){ // age屬性是繼承而來,轉(zhuǎn)發(fā)給父類中的獲取屬性函數(shù) return Animal_GetAge(&this->parent);}
        int Dog_GetWeight(Dog *this){ return Animal_GetWeight(&this->parent);}
        int Dog_GetLegs(Dog *this){ // 子類自己的屬性,直接返回 return this->legs;}

        測試一下:

        int main(){    Dog d;    Dog_Ctor(&d, 1, 3, 4);    printf("age = %d, weight = %d, legs = %d \n",             Dog_GetAge(&d),            Dog_GetWeight(&d),            Dog_GetLegs(&d));    return 0;}

        在代碼段有一塊空間,存儲著可以處理Dog對象的函數(shù);在棧中有一塊空間,存儲著d對象。由于Dog結(jié)構(gòu)體中的第一個參數(shù)是Animal對象,所以從內(nèi)存模型上看,子類就包含了父類中定義的屬性。

        Dog的內(nèi)存模型中開頭部分就自動包括了Animal中的成員,也即是說Dog繼承了Animal的屬性。

        五、利用虛函數(shù),解決多態(tài)問題

        在C++中,如果一個父類中定義了虛函數(shù),那么編譯器就會在這個內(nèi)存中開辟一塊空間放置虛表,這張表里的每一個item都是一個函數(shù)指針,然后在父類的內(nèi)存模型中放一個虛表指針,指向上面這個虛表。

        上面這段描述不是十分準(zhǔn)確,主要看各家編譯器的處理方式,不過大部分C++處理器都是這么干的,我們可以想這么理解。

        子類在繼承父類之后,在內(nèi)存中又會開辟一塊空間來放置子類自己的虛表,然后讓繼承而來的虛表指針指向子類自己的虛表。

        既然C++是這么做的,那我們就用C來手動模擬這個行為:創(chuàng)建虛表和虛表指針。

        1. Animal.h為父類Animal中,添加虛表和虛表指針

        #ifndef _ANIMAL_H_#define _ANIMAL_H_
        struct AnimalVTable; // 父類虛表的前置聲明
        // 父類結(jié)構(gòu)typedef struct { struct AnimalVTable *vptr; // 虛表指針 int age; int weight;} Animal;
        // 父類中的虛表struct AnimalVTable{ void (*say)(Animal *this); // 虛函數(shù)指針};
        // 父類中實現(xiàn)的虛函數(shù)void Animal_Say(Animal *this);
        #endif

        2. Animal.c

        #include <assert.h>#include "Animal.h"
        // 父類中虛函數(shù)的具體實現(xiàn)static void _Animal_Say(Animal *this){ // 因為父類Animal是一個抽象的東西,不應(yīng)該被實例化。 // 父類中的這個虛函數(shù)不應(yīng)該被調(diào)用,也就是說子類必須實現(xiàn)這個虛函數(shù)。 // 類似于C++中的純虛函數(shù)。 assert(0); }
        // 父類構(gòu)造函數(shù)void Animal_Ctor(Animal *this, int age, int weight){ // 首先定義一個虛表 static struct AnimalVTable animal_vtbl = {_Animal_Say}; // 讓虛表指針指向上面這個虛表 this->vptr = &animal_vtbl; this->age = age; this->weight = weight;}
        // 測試多態(tài):傳入的參數(shù)類型是父類指針void Animal_Say(Animal *this){ // 如果this實際指向一個子類Dog對象,那么this->vptr這個虛表指針指向子類自己的虛表, // 因此,this->vptr->say將會調(diào)用子類虛表中的函數(shù)。 this->vptr->say(this);}

        在??臻g定義了一個虛函數(shù)表animal_vtbl,這個表中的每一項都是一個函數(shù)指針,例如:函數(shù)指針say就指向了代碼段中的函數(shù)_Animal_Say()。  > 對象a的第一個成員vptr是一個指針,指向了這個虛函數(shù)表animal_vtbl。

        3.  Dog.h不變

        4. Dog.c中定義子類自己的虛表

        #include "Dog.h"
        // 子類中虛函數(shù)的具體實現(xiàn)static void _Dog_Say(Dog *this){ printf("dag say \n");}
        // 子類構(gòu)造函數(shù)void Dog_Ctor(Dog *this, int age, int weight, int legs){ // 首先調(diào)用父類構(gòu)造函數(shù)。 Animal_Ctor(&this->parent, age, weight); // 定義子類自己的虛函數(shù)表 static struct AnimalVTable dog_vtbl = {_Dog_Say}; // 把從父類中繼承得到的虛表指針指向子類自己的虛表 this->parent.vptr = &dog_vtbl; // 初始化子類自己的屬性 this->legs = legs;}

        5. 測試一下

        int main(){    // 在棧中創(chuàng)建一個子類Dog對象    Dog d;      Dog_Ctor(&d, 1, 3, 4);
        // 把子類對象賦值給父類指針 Animal *pa = &d;
        // 傳遞父類指針,將會調(diào)用子類中實現(xiàn)的虛函數(shù)。 Animal_Say(pa);}

        內(nèi)存模型如下:

        對象d中,從父類繼承而來的虛表指針vptr,所指向的虛表是dog_vtbl。

        在執(zhí)行Animal_Say(pa)的時候,雖然參數(shù)類型是指向父類Animal的指針,但是實際傳入的pa是一個指向子類Dog的對象,這個對象中的虛表指針vptr指向的是子類中自己定義的虛表dog_vtbl,這個虛表中的函數(shù)指針say指向的是子類中重新定義的虛函數(shù)_Dog_Say,因此this->vptr->say(this)最終調(diào)用的函數(shù)就是_Dog_Say。

        基本上,在C中面向?qū)ο蟮拈_發(fā)思想就是以上這樣。這個代碼很簡單,自己手敲一下就可以了。如果想偷懶,請在后臺留言,我發(fā)給您。

        六、C面向?qū)ο笏枷朐陧椖恐械氖褂?/span>

        1. Linux內(nèi)核

        看一下關(guān)于socket的幾個結(jié)構(gòu)體:

        struct sock {    ...}
        struct inet_sock { struct sock sk; ...};
        struct udp_sock { struct sock sk; ...};

        sock可以看作是父類,inet_sock和udp_sock的第一個成員都是是sock類型,從內(nèi)存模型上看相當(dāng)于是繼承了sock中的所有屬性。

        2. glib庫

        以最簡單的字符串處理函數(shù)來舉例:

        GString *g_string_truncate(GString *string, gint len)GString *g_string_append(GString *string, gchar *val)GString *g_string_prepend(GString *string, gchar *val)


        API函數(shù)的第一個參數(shù)都是一個GString對象指針,指向需要處理的那個字符串對象。

        GString *s1, *s2;s1 = g_string_new("Hello");s2 = g_string_new("Hello");
        g_string_append(s1," World!");g_string_append(s2," World!");

        3. 其他項目

        還有一些項目,雖然從函數(shù)的參數(shù)上來看,似乎不是面向?qū)ο蟮?,但是在?shù)據(jù)結(jié)構(gòu)的設(shè)計上看來,也是面向?qū)ο蟮乃枷耄热纾?/span>

        1. Modbus協(xié)議的開源庫libmodbus
        2. 用于家庭自動化的無線通訊協(xié)議ZWave
        3. 很久之前的高通手機(jī)開發(fā)平臺BREW

        瀏覽 36
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報

        評論
        圖片
        表情
        推薦
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产一级毛片爽 | 色哟哟啪啪成人网站免费 | 天天狠操 | 美女被艹爽 | 男人捅女人捅到爽 | 免费国产精品视频 | 欧美黑人巨大久久久精品一区 | 短裙公车被强好爽h500 | 国产后进白嫩翘臀在线欧美 | 丰满美女av |