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 語言實現(xiàn)面向?qū)ο蟮谝徊?-對象模型

        共 5977字,需瀏覽 12分鐘

         ·

        2021-01-26 14:52

        首先申明下,看完這篇文章的一些做法,你可能會覺得很傻x,但是我僅僅是抱著一種嘗試和學習的態(tài)度,實際中可能也并不會這么去用。

        什么是 OOP(Object-oriented Programming, OOP)?

        OOP 這種編程范式大概起源于 Simula。

        它依賴于:

        • 封裝(encapsulation)
        • 繼承(inheritance)
        • 多態(tài)(polymorphism)。

        就 C++、Java 而言,OOP 的意思是利用類層級(class hierarchies)及虛函數(shù)進行編程。

        從而可以通過精制的接口操作各種類型的對象,并且程序本身也可以通過派生(derivation)進行功能增量擴展。

        舉個 Bjarne Stroustrup FAQ 用過的栗子:

        比如可能有兩個(或者更多)設(shè)備驅(qū)動共用一個公共接口:

        class?Driver?{?//?公共驅(qū)動接口
        ??public:
        ??virtual?int?read(char*?p,?int?n)?=?0;?//?從設(shè)備中讀取最多?n?個字符到?p
        ??//?返回讀到的字符總數(shù)
        ??virtual?bool?reset()?=?0;?//?重置設(shè)備
        ??virtual?Status?check()?=?0;?//?讀取狀態(tài)
        };

        Driver 僅僅是一個接口。

        沒有任何數(shù)據(jù)成員,而成員函數(shù)都是純虛函數(shù)。

        不同類型的驅(qū)動負責對這個接口進行相應(yīng)的實現(xiàn):

        class?Driver1?:?public?Driver?{?//?某個驅(qū)動
        ??public:
        ??Driver1(Register);?//?構(gòu)造函數(shù)
        ??int?read(char*,?int?n);
        ??bool?reset();
        ??Status?check();
        ??//?實現(xiàn)細節(jié)
        };
        class?Driver2?:?public?Driver?{?//?另一個驅(qū)動
        ??public:
        ??Driver2(Register);
        ??int?read(char*,?int?n);
        ??bool?reset();
        ??Status?check();
        ??//?實現(xiàn)細節(jié)
        };

        這些驅(qū)動含有數(shù)據(jù)成員,可以通過它們創(chuàng)建對象。它們實現(xiàn)了 Driver 中定義的接口。不難想象,可以通過這種方式使用某個驅(qū)動:

        ??void?f(Driver&?d)?//?使用驅(qū)動
        ??
        {
        ????Status?old_status?=?d.check();
        ????//?...
        ????d.reset();
        ????char?buf[512];
        ????int?x?=?d.read(buf,512);
        ????//?...
        ??}

        這里的重點是,f() 不需要知道它使用的是何種類型的驅(qū)動;

        它只需知道有個 Driver 傳遞給了它;

        也就是說,有一個接口傳遞給了它。

        我們可以這樣調(diào)用 f() :

        void g() {
        Driver1 d1(Register(0xf00)); // create a Driver1 for device
        // with device register at address 0xf00
        Driver2 d2(Register(0xa00)); // create a Driver2 for device
        // with device register at address 0xa00
        // ...
        int dev;
        cin >> dev;
        if (dev==1)
        f(d1); // use d1
        else
        f(d2); // use d2
        // ...
        }

        當 f() 使用某個驅(qū)動時,與該驅(qū)動相對應(yīng)的操作會在運行時被隱式選擇。

        例如,當 f() 得到 d1 時,d.read() 使用的是 Driver1::read();

        而當 f() 得到 d2 時,d.read() 使用的則是 Driver2::read()。

        這被稱為運行時綁定,在一些動態(tài)語言中,鴨子類型(duck typing) 常用來實現(xiàn)這種“多態(tài)”— 不關(guān)心是什么東西,只要覺得它可以run,就給他寫個叫 run的函數(shù)即可。

        當然 OOP 也并非萬能藥。

        不能簡單地把 “OOP” 等同于“好”。

        OOP 的優(yōu)勢在于類層級可以有效地表達很多問題;OOP 的主要弱點在于太多人設(shè)法強行用層級模式解決問題。

        并非所有問題都應(yīng)該面向?qū)ο?。也可以考慮使用普通類(plain class)(也就是常說的 C With Class)、泛型編程和獨立的函數(shù)(就像數(shù)學、C,以及 Fortran 中那樣)作為解決問題的方案。

        當然,OOP != 封裝、繼承、多態(tài)。

        本文僅僅是想討論下在 C 中如何實現(xiàn)封裝、繼承、多態(tài)。

        封裝可以借助 struct,將數(shù)據(jù)和方法都放到一個結(jié)構(gòu)體內(nèi),使用者可以無需關(guān)注具體的實現(xiàn)。

        一種很直白簡單的方式,就是使用函數(shù)指針表示成員方法和數(shù)據(jù)放在一個struct 內(nèi)。

        比如在搜狗開源的服務(wù)端框架 Workflow 中就大量使用了這種方式:

        這里可以看下 __poller_message這個結(jié)構(gòu)體:

        struct?__poller_message
        {

        ?int?(*append)(const?void?*,?size_t?*,?poller_message_t?*);
        ?char?data[0];?
        };

        這里 append 函數(shù)指針就算是一個成員方法,這樣會非常靈活,你可以給它賦任何一種具體實現(xiàn)。

        (PS: char[0] 數(shù)組是一種 C 語言中常用技巧,通常放在結(jié)構(gòu)體的最后,常用來構(gòu)成緩沖區(qū)。

        使用這樣的寫法最適合制作動態(tài) buffer,可以這樣分配空間:malloc(sizeof(struct XXX)+ buff_len); 這樣就直接把 buffer 的結(jié)構(gòu)體和緩沖區(qū)一塊分配了**。**

        用起來也非常方便,因為現(xiàn)在空數(shù)組其實變成了buff_len長度的數(shù)組了。

        感興趣的可以去看下源碼(學習分支):https://github.com/sogou/workflow/tree/study

        當然了,這里我選擇了模仿 C++ 對象模型,在《Inside the C++ Object Model》中提到了三種對象模型設(shè)計思路:

        • 簡單對象模型: 對象中只存儲每個成員(包括函數(shù)和數(shù)據(jù))的指針
        • 表格驅(qū)動對象模型: 對象中存儲兩個指針,一個指向存儲數(shù)據(jù)的表,一個指向存儲函數(shù)指針的表(虛函數(shù)的解決方案)
        • C++ 實際對象模型: 對象存儲 non-static 數(shù)據(jù),static成員(數(shù)據(jù)和函數(shù)) 和 non-static 函數(shù)都單獨存放(注意,并沒有指針指向它們,這可以在編譯時自動確定地址), 還有一個虛表指針指向存儲虛函數(shù)指針的表格(這個表第一個元素可能存放的是 type_info object 以支持RTTI)

        那這里選擇對象只存儲數(shù)據(jù)本身和函數(shù)指針。

        我們需要一個創(chuàng)建對象和回收資源的方法,可以抄抄 C++ 的作業(yè),C++ 中構(gòu)造對象使用的是new運算符,new運算符完成了 內(nèi)存分配 + 調(diào)用類構(gòu)造函數(shù)兩件事。

        delete則回收資源,主要是調(diào)用類的析構(gòu)函數(shù) + 釋放內(nèi)存。

        new()方法必須知道當前正在創(chuàng)建的是什么類型的對象,在 C++ 中,編譯器會自動識別,并生成對應(yīng)的匯編。

        但是在 C 中我們只能手動將類型相關(guān)的信息作為參數(shù)。

        然后在 new 方法內(nèi)使用一系列的 if 去分別處理每種類型?

        這種方法顯然不合適,每個對象應(yīng)該知道怎么構(gòu)造自己以及如何析構(gòu),也就是類型信息應(yīng)該自帶構(gòu)造和析構(gòu)函數(shù)。

        所以設(shè)計了一個 Class 類,Class 類包含類的元信息,比如類的大?。ǚ峙鋬?nèi)存時會用)、構(gòu)造、析構(gòu)函數(shù)等。

        其它所有的類都繼承自這個類。

        所謂的繼承實際上就是將一個Class類型指針放在第一字段。

        很簡單,因為只有統(tǒng)一放在對象開頭,new 方法內(nèi)才能識別出這個 Class 類型指針。

        所以整個對象模型大概是這個樣子:

        struct?Class?{
        ????size_t?size;????/*?size?of?an?object?*/
        ????void?*?(*?ctor)?(void?*?this,?va_list?*?vl);
        ????void?*?(*?dtor)?(void?*?this);
        ????//....?clone?等
        };

        我們來實現(xiàn)以下newdelete:

        //?要將參數(shù)透傳給對象的構(gòu)造函數(shù),所以使用?C?語言變長參數(shù)
        //?type?是具體的類類型參數(shù)
        void?*?new?(const?void?*?type,?...)?{
        ??//?因為?Class?放在第一個字段,所以可以直接做截斷,轉(zhuǎn)為?Class
        ????const?struct?Class?*class?=?type;
        ????//?分配對象內(nèi)存
        ????void?*this?=?calloc(1,?class->size);
        ????*(struct?Class**)this?=?class;??????//?這一步實際上是將每一個類構(gòu)造出的對象,填充上指向類類型的指針
        ????//?執(zhí)行構(gòu)造函數(shù)
        ????if(class->ctor)?{
        ??????//?變長參數(shù),C?語法
        ????????va_list?vl;
        ????????va_start(vl,?type);
        ????????this?=?class->ctor(this,?&vl);
        ????????va_end(vl);
        ????}
        ????return?this;
        }
        //?傳入待析構(gòu)的對象指針
        void?delete?(void?*?self)?{
        ??//?獲取?Class?類型指針
        ????const?struct?Class?**this?=?self;
        ????//?如果有析構(gòu)函數(shù),?就執(zhí)行析構(gòu)
        ????if(self?&&?*this?&&?(*this)->dtor)?{
        ????????self?=?(*this)->dtor(self);
        ????}
        ????//?釋放內(nèi)存
        ????free(self);
        }

        接著,我們基于這個Class來實現(xiàn)一個 String。

        //?string.h

        //?這就是需要傳入?new?函數(shù)的第一個參數(shù),類型指針
        extern?const?void?*?StringNew;

        struct?String?{
        ????const?void?*class;???????/*?父類,?都是?Class?*/
        ????char?*?content;????????????/*?字符串內(nèi)容?*/
        ????char?*(*get_content)(struct?String*);?????//?獲取
        ????void?(*set_content)(struct?String*,?const?char?*);?//?設(shè)置
        };

        這是String的實現(xiàn):

        //?string.c

        //?getter
        static?char?*get_content(struct?String?*str)?{
        ????return?str->content;
        }

        //?setter
        static?void?set_content(struct?String?*str,?const?char?*newcontent)?{
        ????if(str->content)?{
        ????????free(str->content);
        ????}
        ????str->content?=?strdup(newcontent);
        }

        //?構(gòu)造函數(shù)
        static?void*??string_ctor(void?*_this,?va_list?*args)?{
        ????struct?String?*?this?=?_this;
        ????//?初始化內(nèi)容
        ????const?char?*content?=?va_arg(*args,?const??char*);
        ????this->content?=?strdup(content);
        ????//?設(shè)置成員函數(shù)指針
        ????this->get_content?=?get_content;
        ????this->set_content?=?set_content;
        ????return?this;
        }

        //?析構(gòu)函數(shù)
        static?void*?string_dtor(void?*_this)?{
        ????struct?String*?this?=?_this;
        ????//?釋放字符串內(nèi)存
        ????if(this->content)?{
        ????????free(this->content);
        ????????this->content?=?NULL;
        ????}
        ????return?this;
        }

        //?定義一個?Class?變量,即?String?類型的?Class
        static?const?struct?Class?_String?=?{
        ????????sizeof(struct?String),
        ????????string_ctor,
        ????????string_dtor
        };
        //?然后將?_String?變量取地址賦值給定義在?string.h?的?StringNew
        //?StringNew?就相當于構(gòu)造字符串的類模板了,以后需要將這個指針傳遞給?new?函數(shù)
        const?void?*StringNew?=?&_String;

        來看下怎么用吧:

        void?test_str()?{
        ????//?構(gòu)造
        ????struct?String?*str?=?new(StringNew,?"test");


        ????printf("%s\n",?str->get_content(str));

        ????str->set_content(str,?"newtest");

        ????printf("%s\n",?str->get_content(str));


        ????//?析構(gòu)
        ????delete(str);
        }

        是不是有點那味了?

        就是每次都得顯示的傳 this參數(shù),這個沒辦法,語法不支持。

        不過應(yīng)該是可以用宏包一下。

        好了,整體的框架已經(jīng)搭好了,可以基于這種模式去實現(xiàn)繼承、多態(tài)了。

        這部分我就放在第二篇寫了,可以自己先去試下,達到大概這種效果:

        Circle 繼承自Graph,然后可以將 Circle 對象向上轉(zhuǎn)型為 Graph,但是Graph去調(diào)用具體 draw方法的時候,還是執(zhí)行的 Circledraw方法。

        瀏覽 35
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        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>
            青榴网站 | 性色情爱一二三区A级高清视 | 亚洲激情二区 | 午夜精品成人毛片非洲 | 国产一级内射视频 | 一边摸一边吃奶一边透 | 在线观看欧美日韩 | 黄色床上无遮挡 | 四虎最新网站 | 免费看嫩逼|