C语言 C 语言中的 OOP 和接口

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6304794/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 08:53:18  来源:igfitidea点击:

OOP and interfaces in C

coop

提问by dubbeat

Straight of the bat I understand that ANSI C is not an object orientated programming language. I want to learn how to apply a particular oo technique using c.

一言以蔽之,我明白 ANSI C 不是面向对象的编程语言。我想学习如何使用 c 应用特定的 oo 技术。

For example, I want to create several audio effect classes that all have the same function names but different implementations of those functions.

例如,我想创建几个具有相同函数名称但这些函数的实现不同的音频效果类。

If I was making this in a higher level language I would first write an interface and then implement it.

如果我用更高级的语言来做这个,我会先写一个接口,然后实现它。

AudioEffectInterface

-(float) processEffect 



DelayClass

-(float) processEffect

{
 // do delay code

  return result

}

FlangerClass

-(float) processEffect

{
 // do flanger code

  return result

}



-(void) main

{
   effect= new DelayEffect()
   effect.process()

   effect = new FlangerEffect()
   effect.process()


}

How can I achieve such flexibility using C?

如何使用 C 实现这种灵活性?

回答by cmaster - reinstate monica

There are three distinct ways you can achieve polymorphism in C:

您可以通过三种不同的方式在 C 中实现多态:

  1. Code it out
    In the base class functions, just switchon a class type ID to call the specialized versions. An incomplete code example:

    typedef enum classType {
        CLASS_A,
        CLASS_B
    } classType;
    
    typedef struct base {
        classType type;
    } base;
    
    typedef struct A {
        base super;
        ...
    } A;
    
    typedef struct B {
        base super;
        ...
    } B;
    
    void A_construct(A* me) {
        base_construct(&me->super);
        super->type = CLASS_A;
    }
    
    int base_foo(base* me) {
        switch(me->type) {
            case CLASS_A: return A_foo(me);
            case CLASS_B: return B_foo(me);
            default: assert(0), abort();
        }
    }
    

    Of course, this istedious to do for large classes.

  2. Store function pointers in the object
    You can avoid the switch statements by using a function pointer for each member function. Again, this is incomplete code:

    typedef struct base {
        int (*foo)(base* me);
    } base;
    
    //class definitions for A and B as above
    
    int A_foo(base* me);
    
    void A_construct(A* me) {
        base_construct(&me->super);
        me->super.foo = A_foo;
    }
    

    Now, calling code may just do

    base* anObject = ...;
    (*anObject->foo)(anObject);
    

    Alternatively, you may use a preprocessor macro along the lines of:

    #define base_foo(me) (*me->foo)(me)
    

    Note that this evaluates the expression metwice, so this is really a bad idea. This may be fixed, but that's beyond the scope of this answer.

  3. Use a vtable
    Since all objects of a class share the same set of member functions, they can all use the same function pointers. This is very close to what C++ does under the hood:

    typedef struct base_vtable {
        int (*foo)(base* me);
        ...
    } base_vtable;
    
    typedef struct base {
        base_vtable* vtable;
        ...
    } base;
    
    typedef struct A_vtable {
        base_vtable super;
        ...
    } A_vtable;
    
    
    
    //within A.c
    
    int A_foo(base* super);
    static A_vtable gVtable = {
        .foo = A_foo,
        ...
    };
    
    void A_construct(A* me) {
        base_construct(&me->super);
        me->super.vtable = &gVtable;
    };
    

    Again, this allows the user code to do the dispatch (with one additional indirection):

    base* anObject = ...;
    (*anObject->vtable->foo)(anObject);
    
  1. Code it out
    在基类函数中,只需switch对一个类类型ID 调用专门的版本。一个不完整的代码示例:

    typedef enum classType {
        CLASS_A,
        CLASS_B
    } classType;
    
    typedef struct base {
        classType type;
    } base;
    
    typedef struct A {
        base super;
        ...
    } A;
    
    typedef struct B {
        base super;
        ...
    } B;
    
    void A_construct(A* me) {
        base_construct(&me->super);
        super->type = CLASS_A;
    }
    
    int base_foo(base* me) {
        switch(me->type) {
            case CLASS_A: return A_foo(me);
            case CLASS_B: return B_foo(me);
            default: assert(0), abort();
        }
    }
    

    当然,这对于大班级来说很乏味的。

  2. 在对象中存储函数指针
    您可以通过为每个成员函数使用函数指针来避免 switch 语句。同样,这是不完整的代码:

    typedef struct base {
        int (*foo)(base* me);
    } base;
    
    //class definitions for A and B as above
    
    int A_foo(base* me);
    
    void A_construct(A* me) {
        base_construct(&me->super);
        me->super.foo = A_foo;
    }
    

    现在,调用代码可能只是做

    base* anObject = ...;
    (*anObject->foo)(anObject);
    

    或者,您可以按照以下方式使用预处理器宏:

    #define base_foo(me) (*me->foo)(me)
    

    请注意,这会对表达式求值me两次,因此这确实是一个坏主意。这可能是固定的,但这超出了本答案的范围。

  3. 使用 vtable
    由于一个类的所有对象共享同一组成员函数,它们都可以使用相同的函数指针。这与 C++ 在幕后所做的非常接近:

    typedef struct base_vtable {
        int (*foo)(base* me);
        ...
    } base_vtable;
    
    typedef struct base {
        base_vtable* vtable;
        ...
    } base;
    
    typedef struct A_vtable {
        base_vtable super;
        ...
    } A_vtable;
    
    
    
    //within A.c
    
    int A_foo(base* super);
    static A_vtable gVtable = {
        .foo = A_foo,
        ...
    };
    
    void A_construct(A* me) {
        base_construct(&me->super);
        me->super.vtable = &gVtable;
    };
    

    同样,这允许用户代码执行调度(通过一个额外的间接方式):

    base* anObject = ...;
    (*anObject->vtable->foo)(anObject);
    

Which method you should use depends on the task at hand. The switchbased approach is easy to whip up for two or three small classes, but is unwieldy for large classes and hierarchies. The second approach scales much better, but has a lot of space overhead due to the duplicated function pointers. The vtable approach requires quite a bit of additional structure and introduces even more indirection (which makes the code harder to read), but is certainly the way to go for complex class hierarchies.

您应该使用哪种方法取决于手头的任务。该switch基础的方法是很容易掀起了两三个小类,但笨重的大型类和层次结构。第二种方法的扩展性要好得多,但由于重复的函数指针,有很多空间开销。vtable 方法需要相当多的额外结构并引入更多的间接性(这使得代码更难阅读),但对于复杂的类层次结构来说无疑是一种方法。

回答by Michael Foukarakis

Can you compromise with the following:

你能不能在以下方面妥协:

#include <stdio.h>

struct effect_ops {
    float (*processEffect)(void *effect);
    /* + other operations.. */
};

struct DelayClass {
    unsigned delay;
    struct effect_ops *ops;
};

struct FlangerClass {
    unsigned period;
    struct effect_ops *ops;
};

/* The actual effect functions are here
 * Pointers to the actual structure may be needed for effect-specific parameterization, etc.
 */
float flangerEffect(void *flanger)
{
   struct FlangerClass *this = flanger;
   /* mix signal delayed by this->period with original */
   return 0.0f;
}

float delayEffect(void *delay)
{
    struct DelayClass *this = delay;
    /* delay signal by this->delay */
    return 0.0f;
}

/* Instantiate and assign a "default" operation, if you want to */
static struct effect_ops flanger_operations = {
    .processEffect = flangerEffect,
};

static struct effect_ops delay_operations = {
    .processEffect = delayEffect,
};

int main()
{
    struct DelayClass delay     = {.delay = 10, .ops = &delay_operations};
    struct FlangerClass flanger = {.period = 1, .ops = &flanger_operations};
    /* ...then for your signal */
    flanger.ops->processEffect(&flanger);
    delay.ops->processEffect(&delay);
    return 0;
}

回答by user2826084

You implement interfaces using structs of function pointers. You can then have the interface struct embedded in your data object struct and pass the interface pointer as first parameter of every interface member function. In that function you then get the pointer to your container class (which is specific to your implementation) using container_of () macro. Search for "container_of linux kernel" for an implementation. It is a very useful macro.

您可以使用函数指针结构来实现接口。然后,您可以将接口结构嵌入到数据对象结构中,并将接口指针作为每个接口成员函数的第一个参数传递。在该函数中,您然后使用 container_of () 宏获取指向容器类(特定于您的实现)的指针。搜索“container_of linux kernel”以获取实现。这是一个非常有用的宏。