打印

OOPC -- 用C语言玩出花

[复制链接]
1087|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
vsfopen|  楼主 | 2020-2-12 14:53 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 vsfopen 于 2020-2-12 15:47 编辑

首先,最终要的一点,OO的概念是流淌在开发者的血液里的(我的一个朋友的原话),并不是在语言层面的概念。也就是说,用C++的OO外壳,同样可以做出非OO的代码;用C语言,也一样可以写出OO的代码。这里说一下OOPC的发展,有遗漏的可以回帖指正。
一、基本实现原理
1. 直接结构体
xxx.h
struct some_class_t {
    // public member
    int a;
    // private member
    int b;
};
extern int some_class_func(struct some_class_t *class, ......);

这里,相当于人工传入OO代码里的this指针。
这里,对于some_class_t,用户完全可见,不存在实质对private和public成员的保护。然后,由于用户可以知道大小信息,所以,也可以不用动态分配。

优点:简单,具备思想就可以开发。代价非常小
缺点:无实质保护

2. typedef隐藏结构体信息
xxx.h
typedef struct some_class_t some_class_t;
extern some_class_t * some_class_new(......);
extern int some_class_func(struct some_class_t *class, ......);

xxx.c:
struct some_class_t {
    // public member
    int a;
    // private member
    int b;
};
some_class_t * some_class_new(......)
{
    some_class_t *xxx = malloc(...);
    ....
    return xxx;
}


这里,可以通过typedef隐藏结构体力的所有信息,相当于全私有,成员都通过getter和setter访问。

优点:具备保护能力

缺点:用户层只能调用new来动态分配,因为不知道隐藏的结构体大小信息。代价较大

3. 掩码结构体
xxx.h:
struct some_class_t {
    int a;
#ifdef SOME_CLASS_IMPLEMENT
    struct {
        int b;
    };
#else
    uint8_t __private_member[
        sizeof(struct {
            int b;
        })
    ];
#endif
};
#undef SOME_CLASS_IMPLEMENT



xxx.c
#define SOME_CLASS_IMPLEMENT
#include "./xxx.h"
......


这里的代码只是简化实现,这个实现对编译支持来说是有问题的。这里只是为了比较清楚的展示原理。
xxx.c的实现代码里,通过定义SOME_CLASS_IMPLEMENT,来得到private成员的可见性。用户代码里,不会定义这个,所以用户看到的只是一个__private_member数组,看不到私有成员b。

优点:用户可以看到类结构的大小,不必须动态分配,成员具备访问控制
缺点:需要辅助语法糖来简化用户开发,引入额外的结构体,会有内存对齐时候,额外的内存间隙。

使用特权

评论回复

相关帖子

沙发
vsfopen|  楼主 | 2020-2-12 15:14 | 只看该作者
本帖最后由 vsfopen 于 2020-2-12 15:41 编辑

二、语法糖,基本用OOPC都会选择语法糖来简化开发
1. lw_oopc,简单的宏封装,比如:
#define CLASS(type)                 \
typedef struct type type;           \
type* type##_new(lw_oopc_file_line_params); \
void type##_ctor(type* t);          \
int type##_dtor(type* t);           \
void type##_delete(type* t);        \
struct type
CLASS(some_class_t) {
    // public
    int a;
    // private
    int b;
};
这里的原理是前面说的第一种,通过一些宏语法糖来做简化。

2. plooc的2种封装,我就只讲一下simple的版本:
xxx.h
#if defined(SOME_CLASS_IMPLEMENT)
#    define __PLOOC_CLASS_IMPLEMENT
#elif defined(SOME_CLASS_INHERIT)
#    define __PLOOC_CLASS_INHERIT
#endif
declare_simple_class(some_class_t)
def_simple_class(some_class_t) {
    public_member(
        int a;
    )
    private_member(
        int b;
    )
};

extern int some_class_init(some_class_t *xxx);
#if defined(SOME_CLASS_INHERIT)
// protected APIs
extern int some_class_protected_func(some_class_t *xxx, ......);
#endif

#undef SOME_CLASS_IMPLEMENT
#undef SOME_CLASS_INHERIT

xxx.c
#define SOME_CLASS_IMPLEMENT
#include "./xxx.h"

......


使用特权

评论回复
板凳
vsfopen|  楼主 | 2020-2-12 15:29 | 只看该作者
本帖最后由 vsfopen 于 2020-2-14 12:47 编辑

三、继承
1. struct内的父类struct变量
struct xxx_child_t {
    struct xxx_parent_t parent;
    ......
};
这里,子类访问父类成员的话,需要用"child.parent.父类成员"来访问。这里多重继承的时候,父类们的成员同名不会引起问题。

2. 匿名结构体 -- 微软的贡献(一些编译器需要使能ms-extension才能使用,不过大部分编译器都支持)
#define implement(__parent_class_t)        \
    union {                                               \
        __parent_class_t parent;                  \
        __parent_class_t;
    };
struct xxx_child_t {
    implement(xxx_parent_t);
    ......
};

这里,由于implement里,有一个parent和一个匿名成员。所以,可以通过"child.parent.父类成员"或者直接"child.父类成员"来访问。
这里,多重集成的时候,父类们的成员不能重名。


使用特权

评论回复
地板
vsfopen|  楼主 | 2020-2-12 15:38 | 只看该作者
本帖最后由 vsfopen 于 2020-2-18 18:57 编辑

四、重载

1. 有1个参数,或者没有参数
#include <stdio.h>

#define my_printf(...)  printf(("default\r\n", ##__VA_ARGS__))

int main(void)
{
    my_printf();
    my_printf("over-write defult\r\n");
}
这里,使用了C99的规范。在没有参数的时候,##__VA_ARGS__会吃掉前面的逗号,相当于使用默认参数。
当有参数的时候,由于形成逗号表达式,所以,去最后一个作为结果,由于前面的是常量,所以一般编译器并不会去计算这个逗号表达式。

2. 多于1个的不同数量参数


3. 使用C11中的_Generic


使用特权

评论回复
5
vsfopen|  楼主 | 2020-2-12 15:39 | 只看该作者
预留

使用特权

评论回复
6
vsfopen|  楼主 | 2020-2-12 15:39 | 只看该作者
FAQ:

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

90

主题

325

帖子

8

粉丝