2011-06-10 67 views
21

蝙蝠的直线我知道ANSI C不是面向对象的编程语言。我想学习如何使用c来应用特定的oo技术。OOP和C中的接口

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

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

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实现这种灵活性?

+0

请参阅http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c/351745#351745和http://stackoverflow.com/questions/4103704/experiment-面向对象的c/4103725#4103725为例。 – paxdiablo 2011-06-10 10:09:51

回答

10

你能有以下危害:

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

这很有趣。我不熟悉这种语法“struct DelayClass delay = {.delay = 10,.ops =&operations}; ”请您在这里解释点语法。 – dubbeat 2011-06-10 11:00:19

+3

C99允许您使用所谓的“指定初始化程序”来初始化聚合类型(数组,联合体,结构体)。语法是'.member = expression'。 – 2011-06-10 11:08:25

2

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

17

有三种不同的方式,你可以在C++中实现多态:

  1. 代码出来
    在基类的功能,只是一个类类型ID switch致电专门版本。一个不完整的代码示例:

    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. 使用虚函数表
    由于一类共享同一组成员函数,它们都可以使用相同的函数指针的所有对象。这是非常接近什么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方法需要相当多的附加结构,并引入了更多的间接性(这使得代码更难读),但它肯定是复杂类层次结构的一种方式。

+0

优秀的答案。这涵盖了所有的基础和叶子的实现细节,直到程序员选择。 +1 – slebetman 2014-12-17 09:50:49

+0

如何设置base_vtable.foo = A_foo?据推测A_foo的签名会将A的一个实例作为参数?你不是至少需要演出吗? – weberc2 2016-01-20 04:36:38

+1

@ weberc2是的,你需要演员。这通常是你在A_foo()中做的第一件事情:'A * me =(A *)super;'你也可以在赋值时强制转换函数的指针('.foo =(int(*)( base *))A_foo,'但我认为这样会更危险,因为它会隐藏其他参数与'A_foo()'不匹配。 – cmaster 2016-01-20 06:17:30