object oriented programming with ansi c中文




你能用C編寫面向對象的代碼嗎? (20)

你能用C編寫面向對象的代碼嗎? 特別是在多態性方面。

另請參閱堆棧溢出問題C中的面向對象


哪些文章或書籍可以在C中使用OOP概念?

Dave Hanson的C接口和實現在封裝和命名上非常出色,並且非常適合使用函數指針。 Dave不會嘗試模擬繼承。


C stdio FILE子庫是如何在未摻雜的C中創建抽象,封裝和模塊化的極好例子。

繼承和多態 - 其他方面通常被認為是面向對象所不可或缺的 - 並不一定能夠提供他們承諾的生產力增益,並且有reasonable arguments認為它們實際上可能會阻礙對問題領域的發展和思考。


Yes, but I have never seen anyone attempt to implement any sort of polymorphism with C.


你可以使用函數指針來偽造它,事實上,我認為理論上可以將C ++程序編譯為C.

然而,強迫一種語言的範式而不是選擇使用範式的語言是很有意義的。


命名空間通常是通過做:

stack_push(thing *)

代替

stack::push(thing *)

要將C結構變成類似C++類的東西,您可以轉向:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

並做:

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

我沒有做析構或刪除,但它遵循相同的模式。

this_is_here_as_an_example_only就像一個靜態類變量 - 在一個類型的所有實例中共享。 所有的方法都是靜態的,除了有些需要這個*



您可能想要做的一件事是查看用於X WindowXt工具包的實現。 當然,它在牙齒上變得很長,但許多所使用的結構被設計成在傳統的C中以OO方式工作。通常這意味著在這里和那裡增加額外的間接層並且設計結構以相互疊置。

你可以用C這種方式做很多事情,儘管它有時候會有這種感覺,OO的概念並沒有從#include<favorite_OO_Guru.h>的思想中完全形成。 他們確實構成了當時最佳的實踐。 面向對象的語言和系統僅僅是對當日編程時代精神的一部分進行了精煉和放大。


您可能會發現查看Apple Core基礎API集的文檔很有幫助。 它是一個純粹的C API,但許多類型被橋接到Objective-C對象。

您可能還會發現查看Objective-C本身的設計很有幫助。 它與C ++有點不同,對象系統是用C函數定義的,例如objc_msg_send來調用對像上的方法。 編譯器將方括號語法翻譯為那些函數調用,所以你不必知道它,但考慮到你的問題,你可能會發現它有助於了解它在底層如何工作。


我對這個派對有點遲,但我想分享一下我的經驗:我現在使用嵌入式的東西,而我擁有的唯一(可靠的)編譯器是C,所以我想要應用面向對象在我用C編寫的嵌入式項目中

到目前為止,我所見過的大多數解決方案都大量使用類型轉換,所以我們失去了類型安全性:如果你犯了一個錯誤,編譯器不會幫助你。 這是完全不能接受的。

我擁有的要求:

  • 盡可能避免類型轉換,所以我們不會丟失類型安全;
  • 多態性:我們應該能夠使用虛擬方法,並且類的用戶不應該意識到某種特定方法是否為虛擬方法;
  • 多重繼承:我不經常使用它,但有時候我真的想讓某個類實現多個接口(或者擴展多個超類)。

我在本文中詳細解釋了我的方法: C中的面向對象編程 ; 此外,還有一個用於自動生成基類和派生類的樣板代碼的實用程序。


我已經看到它完成了。 我不會推薦它。 C ++最初是以這種方式作為預處理器開始的,該預處理器生成C代碼作為中間步驟。

實質上,你最終要做的是為存儲函數引用的所有方法創建一個調度表。 如果派生類需要調用基方法,則派生類將需要復制此派發表並替換您想要覆蓋的條目,並使用新的“方法”必須調用原始方法。 最終,你最終重寫C ++。


我相信,除了自己有用之外,用C語言實現OOP是學習 OOP並理解其內部工作的絕佳方法。 許多程序員的經驗表明,要有效和自信地使用技術,程序員必須了解底層概念如何最終實現。 模擬C中的類,繼承和多態性教導了這一點。

為了回答最初的問題,下面是一些教程如何在C中執行OOP的資源:

EmbeddedGurus.com博客文章“C語言中基於對象的編程”展示瞭如何在可移植C中實現類和單一繼承: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/ : http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/

應用筆記“”C +“ - C語言中的面向對象編程”展示瞭如何使用預處理器宏在C中實現類,單繼承和後期綁定(多態): http://www.state-machine.com/resources/cplus_3.0_manual.pdf : http://www.state-machine.com/resources/cplus_3.0_manual.pdf ,示例代碼可從http://www.state-machine.com/resources/cplus_3.0.zip


既然你在談論多態,那麼是的,你可以,我們在C ++出現之前幾年就做了這樣的事情。

基本上你使用一個struct來保存數據和一系列函數指針,以指向數據的相關函數。

因此,在通信類中,您可以進行開放,讀取,寫入和關閉調用,這些調用將作為結構中的四個函數指針以及對象的數據一起維護,如下所示:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

當然,上面那些代碼段實際上是在“構造函數”中,比如rs232Init()

當你從這個類“繼承”時,你只需將指針改為指向你自己的函數。 每個調用這些函數的人都可以通過函數指針來實現,為您提供多態性:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

有點像手動vtable。

甚至可以通過將指針設置為NULL來實現虛擬類 - 行為與C ++(運行時的核心轉儲而不是編譯時的錯誤)略有不同。

這是一段演示它的示例代碼。 首先是頂級的班級結構:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

然後我們擁有TCP'子類'的功能:

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

和HTTP一樣:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

最後是一個測試程序來展示它的行動:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

這產生輸出:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

所以你可以看到不同的函數被調用,這取決於子類。


是的你可以。 在C ++或Objective-C出現之前,人們正在編寫面向對象的C語言。 C ++和Objective-C都部分地試圖採用C語言中使用的一些OO概念,並將它們形式化為語言的一部分。

這是一個非常簡單的程序,它展示瞭如何創建一個看起來像/是方法調用的東西(有更好的方法可以做到這一點,這只是證明語言支持這些概念):

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}

有幾種技術可以使用。 最重要的是更多如何拆分項目。 我們在我們的項目中使用了一個在.h文件中聲明的接口以及.c文件中對象的實現。 最重要的部分是包含.h文件的所有模塊僅將對像看作void * ,而.c文件是知道結構內部的唯一模塊。

對於我們以FOO命名的課程來說,就像這樣:

在.h文件中

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

C實現文件就是這樣的。

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

所以我將指針明確地賦予一個對像給該模塊的每個函數。 C ++編譯器會隱式執行它,而在C中,我們會明確寫出它。

我真的在我的程序中使用this ,以確保我的程序不會在C ++中編譯,並且在我的語法高亮編輯器中具有另一種顏色的優良屬性。

FOO_struct的字段可以在一個模塊中修改,另一個模塊甚至不需要重新編譯為可用。

用這種風格,我已經處理了OOP(數據封裝)的很大一部分優點。 通過使用函數指針,甚至可以很容易地實現類似繼承的東西,但實際上,它實際上只是很少有用。


當然,它不會像使用內置支持的語言那麼漂亮。 我甚至寫過“面向對象的彙編器”。



請參閱http://slkpg.byethost7.com/instance.html ,以了解C中面向對象的又一次轉折。它僅使用本地C強調重入實例數據。使用函數包裝器手動完成多重繼承。 類型安全得到維護。 這是一個小樣本:

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define push(self,a)            (*self).push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->push = push;
    peeker->get = get;
    peeker->peek = peek;
}

讀起來很有趣。 我一直在思考同一個問題,思考它的好處是:

在他的情況下,用C語言分析和調整面向對象的概念是一種有效的追求。 由於嘗試在C中實現它們導致性能開銷,他似乎願意犧牲一些OOP概念。

我採取的教訓是,是的,它可以在一定程度上完成,是的,有一些很好的理由來嘗試它。

最後,機器正在調試堆棧指針位,使程序計數器跳轉併計算內存訪問操作。 從效率的角度來看,您的計劃所做的這些計算越少越好......但有時我們必須支付這筆稅款,以便我們可以以最不易受人為錯誤影響的方式來組織我們的計劃。 OOP語言編譯器致力於優化這兩個方面。 程序員必須更加小心地使用C語言來實現這些概念。


這個問題的答案是'是的,你可以'。

面向對象的C(OOC)套件適用於那些希望以面向對象的方式進行編程的人,但也會堅持使用優秀的C語言。 OOC實現類,單一和多重繼承,異常處理。

特徵

•僅使用C宏和函數,不需要任何語言擴展! (ANSI-C)

•易於閱讀的應用程序源代碼。 小心謹慎,盡可能簡單。

•類的單一繼承

•通過接口和mixins進行多重繼承(從版本1.3開始)

•實現異常(純C!)

•類的虛擬功能

•輕鬆實現班級的外部工具

有關更多詳細信息,請訪問http://ooc-coding.sourceforge.net/


面向對像只是一種將數據放在程序中比代碼更重要的範例。 OOP不是一種語言。 所以,就像純C是一種簡單的語言一樣,C中的OOP也很簡單。





object