<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C 語言實(shí)現(xiàn)面向?qū)ο蟮谝徊?-對(duì)象模型

          共 5977字,需瀏覽 12分鐘

           ·

          2021-01-26 14:52

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

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

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

          它依賴于:

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

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

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

          舉個(gè) Bjarne Stroustrup FAQ 用過的栗子:

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

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

          Driver 僅僅是一個(gè)接口。

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

          不同類型的驅(qū)動(dòng)負(fù)責(zé)對(duì)這個(gè)接口進(jìn)行相應(yīng)的實(shí)現(xiàn):

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

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

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

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

          它只需知道有個(gè) Driver 傳遞給了它;

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

          我們可以這樣調(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
          // ...
          }

          當(dāng) f() 使用某個(gè)驅(qū)動(dòng)時(shí),與該驅(qū)動(dòng)相對(duì)應(yīng)的操作會(huì)在運(yùn)行時(shí)被隱式選擇。

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

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

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

          當(dāng)然 OOP 也并非萬能藥。

          不能簡(jiǎn)單地把 “OOP” 等同于“好”。

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

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

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

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

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

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

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

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

          struct?__poller_message
          {

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

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

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

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

          用起來也非常方便,因?yàn)楝F(xiàn)在空數(shù)組其實(shí)變成了buff_len長(zhǎng)度的數(shù)組了。

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

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

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

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

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

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

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

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

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

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

          所以設(shè)計(jì)了一個(gè) Class 類,Class 類包含類的元信息,比如類的大小(分配內(nèi)存時(shí)會(huì)用)、構(gòu)造、析構(gòu)函數(shù)等。

          其它所有的類都繼承自這個(gè)類。

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

          很簡(jiǎn)單,因?yàn)橹挥薪y(tǒng)一放在對(duì)象開頭,new 方法內(nèi)才能識(shí)別出這個(gè) Class 類型指針。

          所以整個(gè)對(duì)象模型大概是這個(gè)樣子:

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

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

          //?要將參數(shù)透?jìng)鹘o對(duì)象的構(gòu)造函數(shù),所以使用?C?語言變長(zhǎng)參數(shù)
          //?type?是具體的類類型參數(shù)
          void?*?new?(const?void?*?type,?...)?{
          ??//?因?yàn)?Class?放在第一個(gè)字段,所以可以直接做截?cái)啵D(zhuǎn)為?Class
          ????const?struct?Class?*class?=?type;
          ????//?分配對(duì)象內(nèi)存
          ????void?*this?=?calloc(1,?class->size);
          ????*(struct?Class**)this?=?class;??????//?這一步實(shí)際上是將每一個(gè)類構(gòu)造出的對(duì)象,填充上指向類類型的指針
          ????//?執(zhí)行構(gòu)造函數(shù)
          ????if(class->ctor)?{
          ??????//?變長(zhǎng)參數(shù),C?語法
          ????????va_list?vl;
          ????????va_start(vl,?type);
          ????????this?=?class->ctor(this,?&vl);
          ????????va_end(vl);
          ????}
          ????return?this;
          }
          //?傳入待析構(gòu)的對(duì)象指針
          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);
          }

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

          //?string.h

          //?這就是需要傳入?new?函數(shù)的第一個(gè)參數(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的實(shí)現(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;
          }

          //?定義一個(gè)?Class?變量,即?String?類型的?Class
          static?const?struct?Class?_String?=?{
          ????????sizeof(struct?String),
          ????????string_ctor,
          ????????string_dtor
          };
          //?然后將?_String?變量取地址賦值給定義在?string.h?的?StringNew
          //?StringNew?就相當(dāng)于構(gòu)造字符串的類模板了,以后需要將這個(gè)指針傳遞給?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);
          }

          是不是有點(diǎn)那味了?

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

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

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

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

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

          瀏覽 35
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九九免费 | 少妇精品一区二区 | 奇米狠狠色777久久久欧美老妇 | 欧美三级中文 | 日韩激情综合网 |