用C编写面向对象代码有哪些方法?特别是在多态性方面。


另请参阅此堆栈溢出问题C中的面向对象。


当前回答

我建立了一个小图书馆,在那里我尝试了,对我来说,它真的很有效。所以我想和你分享这段经历。

https://github.com/thomasfuhringer/oxygen

使用结构并将其扩展到每个其他子类,可以很容易地实现单继承。对父结构进行简单的强制转换,就可以在所有后代上使用父方法。 只要知道变量指向持有此类对象的结构体,就可以始终将其转换为根类并进行自省。

正如前面提到的,虚拟方法有点棘手。但它们是可行的。为了保持简单,我只是在类描述结构中使用了一个函数数组,每个子类在需要的地方复制和重新填充单独的插槽。

多重继承实现起来相当复杂,并且会对性能产生重大影响。所以我离开了。我确实认为,在相当多的情况下,清晰地模拟现实生活环境是可取和有用的,但在可能90%的情况下,单一继承可以满足需求。而且单一继承很简单,没有成本。

我也不关心类型安全。我认为你不应该依赖编译器来防止你的编程错误。而且无论如何,它只能保护您避免相当小部分的错误。

通常,在面向对象的环境中,您还希望实现引用计数以尽可能地自动化内存管理。因此,我还在“Object”根类中放入了引用计数,并使用了一些功能来封装堆内存的分配和释放。

它非常简单和精简,给了我OO的基本要领,而不强迫我去处理c++这个怪物。而且我保留了留在C领域的灵活性,这使得集成第三方库更加容易。

其他回答

哪些文章或书籍适合在C语言中使用面向对象的概念?

Dave Hanson的《C接口与实现》在封装和命名方面非常出色,在函数指针的使用方面也非常出色。Dave没有尝试模拟继承。

你可以使用函数指针来伪装它,事实上,我认为理论上可以将c++程序编译成C语言。

然而,将一种范式强加于一种语言,而不是选择一种使用范式的语言,很少有意义。

我建立了一个小图书馆,在那里我尝试了,对我来说,它真的很有效。所以我想和你分享这段经历。

https://github.com/thomasfuhringer/oxygen

使用结构并将其扩展到每个其他子类,可以很容易地实现单继承。对父结构进行简单的强制转换,就可以在所有后代上使用父方法。 只要知道变量指向持有此类对象的结构体,就可以始终将其转换为根类并进行自省。

正如前面提到的,虚拟方法有点棘手。但它们是可行的。为了保持简单,我只是在类描述结构中使用了一个函数数组,每个子类在需要的地方复制和重新填充单独的插槽。

多重继承实现起来相当复杂,并且会对性能产生重大影响。所以我离开了。我确实认为,在相当多的情况下,清晰地模拟现实生活环境是可取和有用的,但在可能90%的情况下,单一继承可以满足需求。而且单一继承很简单,没有成本。

我也不关心类型安全。我认为你不应该依赖编译器来防止你的编程错误。而且无论如何,它只能保护您避免相当小部分的错误。

通常,在面向对象的环境中,您还希望实现引用计数以尽可能地自动化内存管理。因此,我还在“Object”根类中放入了引用计数,并使用了一些功能来封装堆内存的分配和释放。

它非常简单和精简,给了我OO的基本要领,而不强迫我去处理c++这个怪物。而且我保留了留在C领域的灵活性,这使得集成第三方库更加容易。

这本书读起来很有趣。我自己也在思考同样的问题,思考这个问题的好处是:

Trying to imagine how to implement OOP concepts in a non-OOP language helps me understand the strengths of the OOp language (in my case, C++). This helps give me better judgement about whether to use C or C++ for a given type of application -- where the benefits of one out-weighs the other. In my browsing the web for information and opinions on this I found an author who was writing code for an embedded processor and only had a C compiler available: http://www.eetimes.com/discussion/other/4024626/Object-Oriented-C-Creating-Foundation-Classes-Part-1

在他的案例中,用普通C语言分析和调整面向对象的概念是一种有效的追求。他似乎愿意牺牲一些面向对象的概念,因为尝试用C语言实现它们会带来性能开销。

我得到的教训是,是的,在某种程度上它是可以做到的,是的,有一些很好的理由去尝试。

In the end, the machine is twiddling stack pointer bits, making the program counter jump around and calculating memory access operations. From the efficiency standpoint, the fewer of these calculations done by your program, the better... but sometimes we have to pay this tax simply so we can organize our program in a way that makes it least susceptible to human error. The OOP language compiler strives to optimize both aspects. The programmer has to be much more careful implementing these concepts in a language like C.

关于c语言OOP的另一个变化,请参阅http://slkpg.byethost7.com/instance.html。它强调仅使用本机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;
}