• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • OO in C

    發表于:2007-05-25來源:作者:點擊數: 標簽:OOinC作者模擬實現中的
    OOinC C語言中的類模擬與實現 作者:liyuming1978 文章來源: csdn C語言中的類模擬(C++編程思想) 在 面向對象 的語言里面,出現了類的概念。這是編程思想的一種進化。所謂類:是對特定數據的特定操作的集合體。所以說類包含了兩個范疇:數據和操作。而C語
    OO in C
    C語言中的類模擬與實現
    作者:liyuming1978 文章來源:csdn

    C語言中的類模擬(C++編程思想)
           在面向對象的語言里面,出現了類的概念。這是編程思想的一種進化。所謂類:是對特定數據的特定操作的集合體。所以說類包含了兩個范疇:數據和操作。而C語言中的struct僅僅是數據的集合。(liyuming1978@163.com)
     
    1.實例:下面先從一個小例子看起
     
    #ifndef C_Class
           #define C_Class struct
    #endif
     
    C_Class A {
           C_Class A *A_this;
           void (*Foo)(C_Class A *A_this);
           int a;
           int b;
    };
     
    C_Class B{               //B繼承了A
           C_Class B *B_this;  //順序很重要
           void (*Foo)(C_Class B *Bthis);              //虛函數
           int a;
           int b;
     
           int c;
    };
     
     
    void B_F2(C_Class B *Bthis)
    {
           printf("It is B_Fun\n");
    }
     
    void A_Foo(C_Class A *Athis)
    {
           printf("It is A.a=%d\n",Athis->a);//或者這里
    //     exit(1);
    //     printf("純虛 不允許執行\n");//或者這里
    }
     
    void B_Foo(C_Class B *Bthis)
    {
           printf("It is B.c=%d\n",Bthis->c);
    }
     
    void A_Creat(struct A* p)
    {
           p->Foo=A_Foo;
           p->a=1;
           p->b=2;
           p->A_this=p;
    }
     
     
    void B_Creat(struct B* p)
    {
           p->Foo=B_Foo;
           p->a=11;
           p->b=12;     
           p->c=13;
           p->B_this=p;
    }
     
     
    int main(int argc, char* argv[])
    {
           C_Class A *ma,a;
           C_Class B *mb,b;
     
           A_Creat(&a);//實例化
           B_Creat(&b);
     
           mb=&b;
           ma=&a;
     
           ma=(C_Class A*)mb;//引入多態指針
           printf("%d\n",ma->a);//可惜的就是 函數變量沒有private
           ma->Foo(ma);//多態
           a.Foo(&a);//不是多態了
           B_F2(&b);//成員函數,因為效率問題不使用函數指針
           return 0;
    }
    輸出結果:
    11
    It is B.c=13
    It is A.a=1
    It is B_Fun
     
    2.類模擬解說:
           我在網上看見過一篇文章講述了類似的思想(據說C++編程思想上有更加詳細的解說,可惜我沒空看這個了,如果有知道的人說一說吧)。但是就象C++之父說的:“C++和C是兩種語言”。所以不要被他們在語法上的類似就混淆使用,那樣有可能會導致一些不可預料的事情發生。
           其實我很同意這樣的觀點,本文的目的也不是想用C模擬C++,用一個語言去模擬另外一個語言是完全沒有意義的。我的目的是想解決C語言中,整體框架結構過于分散、以及數據和函數脫節的問題。
           C語言的一大問題是結構松散,雖然現在好的大型程序都基本上按照一個功能一個文件的設計方式,但是無法做到更小的顆?;D―原因就在于它的數據和函數的脫節。類和普通的函數集合的最大區別就在于這里。類可以實例化,這樣相同的函數就可以對應不同的實例化類的變量。
           自然語言的一個特點是概括:比如說表??梢哉f手表,鐘表,秒表等等,這樣的描述用面向對象的語言可以說是抽象(繼承和多態)。但是我們更要注意到,即使對應于手表這個種類,還是有表鏈的長度,表盤的顏色等等細節屬性,這樣細微的屬性如果還用抽象,就無法避免類膨脹的問題。所以說類用成員變量來描述這樣的屬性。這樣實例并初始化不同的類,就描述了不同屬性的對象。
           但是在C語言中,這樣做是不可能的(至少語言本身不提供這樣的功能)。C語言中,如果各個函數要共享一個變量,必須使用全局變量(一個文件內)。但是全局變量不能再次實例化了。所以通常的辦法是定義一個數組。以往C語言在處理這樣的問題的時候通常的辦法就是這樣,比如說socket的號,handel等等其實都是數組的下標。(不同的連接對應不同的號,不同的窗口對應不同的handel,其實這和不同的類有不同的成員變量是一個意思)
           個人認為:兩種形式(數組和模擬類)并無本質的區別(如果不考慮虛函數的應用的話),它們的唯一區別是:數組的辦法將空間申請放在了“模塊”內,而類模擬的辦法將空間申請留給了外部,可以說就這一點上,類模擬更加靈活。
     
    3.其他的話:
           我的上述思想還是很不成熟的,我的目的是想讓C語言編程者能夠享受面向對象編程的更多樂趣。我們僅僅面對的是浩瀚的“黑箱”,我們的工作是堆砌代碼,而且如果要更改代碼功能的時候,僅僅換一個黑箱就可以了。
           而更大的目的是促使這樣的黑箱的產生?;蛟S有一天,一種效率很好,結構很好的語言將會出現。那個時候編程是不是就會象說話一樣容易了呢?

     
    C語言的多態實現
           相信很多人都看過設計模式方面的書,大家有什么體會呢?Bridge,Proxy,Factory這些設計模式都是基于抽象類的。使用抽象對象是這里的一個核心。
          
           其實我覺得框架化編程的一個核心問題是抽象,用抽象的對象構建程序的主體框架,這是面向對象編程的普遍思想。用抽象構建骨架,再加上多態就形成了一個完整的程序。由于C++語言本身實現了繼承和多態,使用這樣的編程理念(理念啥意思?跟個風,嘿嘿)在C++中是十分普遍的現象,可以說Virtual(多態)是VC的靈魂。
     
           但是,使用C語言的我們都快把這個多態忘光光了。我常聽見前輩說,類?多態?我們用的是C,把這些忘了吧。很不幸的是,我是一個固執的人。這么好的東西,為啥不用呢。很高興的,在最近的一些純C代碼中,我看見了C中的多態!下面且聽我慢慢道來。
     
    1.         VC中的Interface是什么
           Interface:中文解釋是接口,其實它表示的是一個純虛類。不過我所要說的是,在VC中的Interface其實就是struct,查找Interface的定義,你可以發現有這樣的宏定義:
           #Ifndef Interface
                  #define Interface struct
           #endif
    而且,實際上在VC中,如果一個類有Virtual的函數,則類里面會有vtable,它實際上是一個虛函數列表。實際上C++是從C發展而來的,它不過是在語言級別上支持了很多新功能,在C語言中,我們也可以使用這樣的功能,前提是我們不得不自己實現。
     
    2.C中如何實現純虛類(我稱它為純虛結構)
           比較前面,相信大家已經豁然開朗了。使用struct組合函數指針就可以實現純虛類。
    例子: typedef struct {
                         void  (*Foo1)();
                         char  (*Foo2)();
                         char*  (*Foo3)(char* st);
                  }MyVirtualInterface;
          
           這樣假設我們在主體框架中要使用橋模式。(我們的主類是DoMyAct,接口具體實現類是Act1,Act2)下面我將依次介紹這些“類”。(C中的“類”在前面有說明,這里換了一個,是使用早期的數組的辦法)
     
    主類DoMyAct: 主類中含有MyVirtualInterface* m_pInterface; 主類有下函數:
                                DoMyAct_SetInterface(MyVirtualInterface* pInterface)
                                {
                                       m_pInterface= pInterface;
                                }
                                DoMyAct_Do()
                                {
                                       if(m_pInterface==NULL) return;
                                       m_pInterface->Foo1();
                                       c=m_pInterface->Foo2();
                                }
    子類Act1:實現虛結構,含有MyVirtualInterface  st[MAX]; 有以下函數:
                        MyVirtualInterface* Act1_CreatInterface()
                         {
                                index=FindValid() //對象池或者使用Malloc !應該留在外面申請,實例化
                                if(index==-1) return NULL;
                                St[index].Foo1=Act1_Foo1; // Act1_Foo1要在下面具體實現
                                St[index].Foo2=Act1_Foo2;
                                St[index].Foo3=Act1_Foo3;
                                Return &st [index];
                         }
    子類Act2同上。
     
    在main中,假設有一個對象List。List中存貯的是MyVirtualInterface指針,則有:
           if( (p= Act1_CreatInterface()) != NULL)
           List_AddObject(&List, p); //Add All
     
           While(p=List_GetObject()){
                  DoMyAct_SetInterface(p);//使用Interface代替了原來大篇幅的Switch Case
                  DoMyAct_Do();//不要理會具體的什么樣的動作,just do it
           }
     
           FREE ALL。
           在微系統里面,比如嵌入式,通常使用對象池的技術,這個時候可以不用考慮釋放的問題(對象池預先沒有空間,使用Attach,在某個函數中申請一個數組并臨時為對象池分配空間,這樣函數結束,對象池就釋放了)
     
           但是在Pc環境下,由于程序規模比較大,更重要的是一些特殊的要求,使得對象的生命周期必須延續到申請的那個函數體以外,就不得不使用malloc,實際上即使在C++中,new對象的自動釋放始終是一個令人頭疼的問題,新的標準引入了智能指針。但是就我個人而言,我覺得將內存釋放的問題完全的交給機器是不可信任的,它只能達到準最佳。
     
           你知道設計Java的垃圾回收算法有多困難嗎?現實世界是錯綜復雜的,在沒有先驗條件下,要想得到精確的結果及其困難。所以我說程序員要時刻將free記在心上,有關程序的健壯性和自我防御將在另外一篇文章中講述。
     
    3.純虛結構的退化
           下面我們來看看如果struct里面僅僅有一個函數是什么? 這個時候如果我們不使用struct,僅僅使用函數指針又是什么? 我們發現,這樣就退化為普通的函數指針的使用了。
     
           所以說,有的時候我覺得面向對象僅僅是一種形式,而不是一種技術。是一種觀點,而不是一種算法。但是,正如炭,石墨和鉆石的關系一樣,雖然分子式都是C,但是組成方法不一樣,表現就完全不一樣了!
           有的時候,我們經常被編程中瑣碎的事情所煩惱,而偏離了重心,其實程序可進化的特性是很重要的。有可能,第一次是不成功的,但是只要可進化,就可以發展。
     
    4.進階――類結構樹,父類不是純虛類的類
           前面僅僅講的是父類是純虛結構的情況 (面向對象建議的是所有類的基類都是從純虛類開始的), 那么當類層次比較多的情況下,出現父類不是純虛結構怎么辦呢。嘿嘿,其實在C中的實現比C++要簡單多了。因為C中各個函數是分散的。
     
    在這里使用宏定義是一個很好的辦法:比如兩個類Act1,ActByOther1“繼承”Act1:
                        MyVirtualInterface* ActByOther1_CreatInterface()
                         {
                                index=FindValid() //對象池或者使用Malloc
                                if(index==-1) return NULL;
                                St[index].Foo1= ActByOther1_Foo1; // Act1_Foo1要在下面具體實現
                                St[index].Foo2= ActByOther1_Foo2;
                                St[index].Foo3= ActByOther1_Foo3;
                                Return &st [index];
                         }
     
           #define ActByOther1_Foo1 Act1_Foo1  //這就是繼承 嘿嘿
           ActByOther1_Foo2(){}                    //  可以修改其實現
        ActByOther1_DoByOther() {}         //當然就可以添加新的實現咯
     
    5.實例――可以參見H264的源碼,其中NalTool就是這樣的一個純虛結構。
     

    類模擬的性能分析
           類模擬中使用了大量的函數指針,結構體等等,有必須對此進行性能分析,以便觀察這樣的結構對程序的整體性能有什么程度的影響。
     
    1.函數調用的開銷
    #define COUNTER XX
    void testfunc()
    {
           int i,k=0;
           for(i=0;i<YY;i++)
    }
     
           在測試程序里面,我們使用的是一個測試函數,函數體內部可以通過改變YY的值來改變函數的耗時。測試對比是 循環調用XX次函數,和循環XX次函數內部的YY循環。
           結果發現,在YY足夠小,X足夠大的情況下,函數調用耗時成為了主要原因。所以當一個“簡單”功能需要“反復”調用的時候,將它編寫為函數將會對性能有影響。這個時候可以使用宏,或者inline關鍵字。
           但是,實際上我設置XX=10000000(1千萬)的時候,才出現ms級別的耗時,對于非實時操作(UI等等),即使是很慢的cpu(嵌入式10M級別的),也只會在XX=10萬的時候出現短暫的函數調用耗時,所以實際上這個是可以忽略的。
     
    2.普通函數調用和函數指針調用的開銷
    void (*tf)();
    tf=testfunc;
     
           測試程序修改為一個使用函數調用,一個使用函數指針調用。測試發現對時間基本沒有什么影響。(在第一次編寫的時候,發現在函數調用出現耗時的情況下(XX=1億),函數指針的調用要慢(release版本),調用耗時350:500。后來才發現這個影響是由于將變量申請為全局的原因,全局變量的訪問要比局部變量慢很多)。
     
    3.函數指針和指針結構訪問的開銷
    struct a {
           void (*tf)();
    };
     
           測試程序修改為使用結構的函數指針,測試發現對時間基本沒有什么影響。其實使用結構并不會產生影響,因為結構的訪問是固定偏移量的。所以結構變量的訪問和普通變量的訪問對于機器碼來說是一樣的。
     
    測試結論:使用類模擬的辦法對性能不會產生太大的影響。

     

    CMILY: 宋體; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">語言中的面向對象思想

     

    經常聽見別人說面向對象的程序設計,以前在學校上課的時候,也有開面向對象程序設計這門課??墒遣恍业氖?,這些都是以C++,甚至VC++為基礎的。而更加不幸的是,多年以來我一直是一個C的使用者。在學校的時候,我主要做的是硬件上的驅動層,和底層功能層。在工作以后,又做的是手機上的軟件開發,所有這些都是和C離不開的。雖然我不得不說,C++是一門很好的語言,但是它的編譯速度,代碼效率,編譯后的代碼大小都限制了它在嵌入式上的應用。(但現在的嵌入式CPU越來越快,內存容量變大。我覺得用C++也應該沒有什么問題。這使我覺得似乎是嵌入式編譯器的限制。雖然菲利普和TI好像都有C++的編譯器,但是似乎沒人用這個。難道是太貴了? 但不管怎么說,嵌入式應用中,C語言的普遍使用是肯定的)

    那么在面向過程的時代產生的C語言能否使用面向對象的思想呢?我認為是肯定可以的,C++不過是在語言級別上加入了對對象的支持,同時提供了豐富的對象庫。而在C語言下,我們只好自力更生了。

    一、           面向對象思想的目的是框架化,手段是抽象

    相信很多人都明白面向對象講了什么:類,抽象類,繼承,多態。但是是什么原因促使這些概念的產生呢?

    打個比方說:你去買顯示器,然而顯示器的品牌樣式是多種多樣的,你在買的過程中發生的事情也是不可預測的。對于這樣的事情,我們在程序語言中如何去描述呢。面向對象的思想就是為了解決這樣的問題。編寫一個程序(甚至說是一個工程),從無到用是困難的,從有到豐富是更加困難的。面向對象將程序的各個行為化為對象,而又用抽象的辦法將這些對象歸類(抽象),從而將錯綜復雜的事情簡化為幾個主要的有機組合(框架化)。

    其實我們的身邊很多東西都是這樣組成的:比如說電腦:電腦是由主板,CPU加上各種卡組成的。這就是一個框架化。而忽略不同的CPU,不同的主板,不同的聲卡,網卡,顯卡的區別,這就是抽象。再比如說現在的教育網:是由主核心節點:清華,北大,北郵等幾個,然后是各個子節點,依次組成了整個教育網網絡。

    所以我覺得面向對象的編程思想就是:一個大型工程是分層次結構的,每層又由抽象的結構連接為整體(框架化),各個抽象結構之間是彼此獨立的,可以獨立進化(繼承,多態)。層次之間,結構之間各有統一的通訊方式(通常是消息,事件機制)。

    二、           以前C語言編程中常用的“面向對象”方法

    其實C語言誕生以來,人們就想了很多辦法來體現“面向對象”的思想。下面就來說說我所知道的方法。先說一些大家熟悉的東東,慢慢再講詭異的。呵呵

     

    1.  宏定義:
      
    有的人不禁要問,宏定義怎么扯到這里來了,我們可以先看一個簡單的例子:
    define MacroFunction  Afunction
      
    然后在程序里面你調用了大量的AFunction,但是有一天,你突然發現你要用BFunction了,(不過AFunction又不能不要,很有可能你以后還要調用),這個時候,你就可以#define MacroFunction  Bfunction來達到這樣的目的。

      
    當然,不得不說這樣的辦法是too simple,sometime na&iuml;ve的,因為一個很滑稽的問題是如果我一般要改為BFunction,一半不變怎么辦? 那就只好查找替換了。

     

    2.  靜態的入口函數,保證函數名相同,利用標志位調用子函數:
      
    這樣的典型應用很多,比如說網卡驅動里面有一個入口函數Nilanint FunctionCode,Para*)。具體的參數是什么記不清楚了。不過NiLan的主體是這樣的:
    Long Nilan
    int FunctionCode,Para*{

    Switch(FunctionCode){

           Case SendPacket:         send(….)

           Case ReceivePacket:      receive(…)

           …..

    }

    寫到這里大家明白什么意思了吧。保證相同的函數名就是說:網卡驅動是和pNA+協議?;ミB的,那么如何保證pNA+協議棧和不同的驅動都兼容呢,一個簡單的辦法就是僅僅使用一個入口函數。通過改變如果函數的參數值,來調用內部的各個函數。這樣的做法是可以進化的:如果以后想調用新的函數,增加相應的函數參數值就好了。如果我們將網卡驅動和pNA+協議??醋鲀蓚€層的話,我們可以發現:

    層與層之間的互連接口是很小的(這里是一個入口函數),一般是采用名字解析的辦法而不是具體的函數調用(利用FunctionCode調用函數,Nilan僅僅實現名字解析的功能)――!接口限制和名字解析

    接口限制:層與層之間僅僅知道有限的函數

    名字解析:層與層之間建立共同的名字與函數的對應關系,之間利用名字調用功能。

    3CALLBACK函數。

    我覺得這是C語言的一個創舉,雖然它很簡單,就象如何把雞蛋豎起來一樣,但是你如果沒想到的話,嘿嘿。如果說靜態入口函數實現了一個可管理的宏觀的話,CallBack就是實現了一個可進化的微觀:它使得一個函數可以在不重新編譯的情況下實現功能的添加!但是在最最早期的時候,也有蠻多人持反對態度,因為它用了函數指針。函數指針雖然靈活,但是由于它要訪問內存兩次才可以調用到函數,第一次訪問函數指針,第二次才是真正的函數調用。它的效率是不如普通函數的。但是在一個不太苛刻的環境下,函數調用本身就不怎么耗時,函數指針的性能又不是特別糟糕,使用函數指針其實是一個最好的選擇。但是函數指針除了性能,最麻煩的地方就是會導致程序的“支離破碎”。試想:在程序中,你讀到一個函數指針的時候,如果你愣是不知道這個函數指針指向的是哪個函數,那個感覺真的很糟糕。(可以看后面的文章,要使用先進的程序框架,避免這樣的情況)

    三、           EventMessage

    看了上面的描述,相信大家多少有些明白為什么要使用EventMessage了。具體的函數調用會帶來很多的問題(雖然從效率上講,這樣做是很好的)。為了提高程序的靈活性,EventMessage的辦法產生了。用名字解析的辦法代替通常的函數調用,這樣,如果雙方對這樣的解析是一致的話,就可以達到一個統一。不過EventMessage的作用還不僅僅是如此。

    EventMessage還有建立進程間通信的功能。進程將自己的消息發給“控制中心”(簡單的就是一個消息隊列,和一個while循環不斷的取消息隊列的內容并執行),控制程序得到消息,分發給相應的進程,這樣其他進程就可以得到這個消息并進行響應。

    EventMessage是很靈活的,因為你可以隨時添加或者關閉一個進程,(僅僅需要添加分發消息的列表就可以了)EventMessage從程序實現上將我覺得是一樣的,只不過概念不同。Event多用于指一個動作,比如硬件發生了什么事情,需要調用一個什么函數等等。Message多用于指一個指示,比如什么程序發生了什么操作命令等等。

    四、           小結

    其實編程序和寫文章一樣,都是先有一個提綱,然后慢慢的豐富。先抽象化得到程序的骨架,然后再考慮各個方面的其他內容:程序極端的時候會發生什么問題?程序的這個地方的功能現在還不完善,以后再完善會有什么問題?程序是不是可以擴展的?

    本系列文章是我這些階段的一些心得,目的是拋磚引玉,希望能和大家交流,得到更多的知識。Liyuming1978@163.com

    原文轉自:http://www.kjueaiud.com

    評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>