【问题标题】:OOP and interfaces in CC中的OOP和接口
【发布时间】:2011-06-10 09:59:30
【问题描述】:

直截了当我知道 ANSI C 不是面向对象的编程语言。我想学习如何使用 c 应用特定的 oo 技术。

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

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

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()


}

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

【问题讨论】:

标签: c oop


【解决方案1】:

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

  1. 编码
    在基类函数中,只需 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);
    

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

【讨论】:

  • 优秀的答案。这涵盖了所有基础,并将实现细节留给程序员选择。 +1
  • 如何设置 base_vtable.foo = A_foo?大概 A_foo 的签名会将 A 的实例作为参数?您至少不需要以某种方式投射吗?
  • @weberc2 是的,你需要一个演员表。这通常是你在A_foo() 中做的第一件事:A* me = (A*)super; 你也可以在分配它时强制转换函数指针(.foo = (int (*)(base*))A_foo,,但我认为这更危险,因为它会隐藏@987654333 的其他参数中的不匹配@.
  • 创建 A_vtable 是否有原因? A.c 不能只使用基本 vtable 吗?
  • @Jin 是的:A_vtable 可以添加更多base_vtable 中不存在的指针,这些指针不同于B_vtable 可能为使用它自己的子类而定义的附加指针。当然,您可以将所有内容移至base_vtable,但随后您可能需要将一些函数指针设置为NULL。为每个类定义一个特殊的 vtable 结构使该方法更具可扩展性,并使与A 相关的内容更紧密地结合在一起。
【解决方案2】:

你能不能妥协:

#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;
}

【讨论】:

  • 这很有趣。我不熟悉这种语法“struct DelayClass delay = {.delay = 10, .ops = &operations};”你能解释一下这里的点语法吗?
  • C99 允许您使用所谓的“指定初始化程序”来初始化聚合类型(数组、联合、结构)。语法是.member = expression
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2014-04-29
    • 2013-02-08
    • 2016-11-15
    • 2015-10-12
    • 2012-05-30
    • 1970-01-01
    • 2011-08-24
    • 2016-11-28
    • 1970-01-01
    相关资源
    最近更新 更多