《C现代编程》---读书笔记(2)

3.2.6 封装

    封装是指通过将对象的状态和行为集中在一起,并规定其与外部接口来进行抽象化的过程。以C语言角度理解,状态就是结构体中函数指针以外的成员,行为就是函数指针成员。假设以下列子:

typedef struct _Validator {
    bool (* const validate)(struct _Validator *pThis, int val);
} Validator;

    调用validate函数时,程序就会按照校验器内部的规则对输入值进行校验,假设validator的子校验器RangeValidator的定义如下:

typedef struct {
    Validator base;
    const int min;
    const int max;
} RangeValidator;

    这里对于调用方来说min、max成员是不需要在意的内部信息,调用者只需关注”将validate函数自身(pThis)和校验对象的值(val)传递过去,就会得到bool类型的校验结果“这个外部接口,而不用在意RangeValidator内部具体实现。

    在C语言中,我们无法提供访问控制功能,无法实现数据隐藏,但我们可以使用const修饰符,避免外部修改内部数据。此外,还可以规定命名规则来回避这个问题,如不允许直接访问的成员在命名时以下划线"_"开头。

3.2.7 虚函数表

    各对象都持有函数指针时,可能会导致内存浪费,例如下述定义:

typedef struct Foo {
    int count;
    void (* const func0)(struct Foo *pThis);
    void (* const func1)(struct Foo *pThis);
    void (* const func2)(struct Foo *pThis);
} Foo;
    根据定义生成对象如下:
Foo foo0 = {
    0, func0_impl, func1_impl, func2_impl
};

Foo foo1 = {
    1, func0_impl, func1_impl, func2_impl
};

Foo foo2 = {
    2, func0_impl, func1_impl, func2_impl
};

    以上可以看出在生成大量内容相同或相似的对象时,这种方法造成了内存浪费。此时,引入虚函数表就可以避免内存浪费问题。这里的”虚函数“是通过函数指针来实现的,实际被调用的函数会随着对象的不同而有不同的功能。可以用虚函数表来重构上述代码,如下:

#include <stdio.h>

typedef struct FooVtbl {
    void (* const func0)(struct Foo *pThis);
    void (* const func1)(struct Foo *pThis);
    void (* const func2)(struct Foo *pThis);
} FooVtbl;


typedef struct Foo {
    const int count;
    const FooVtbl * const pVtbl;
} Foo;


void func0_impl(struct Foo *pThis)
{
    printf("func0_impl\n");
}

void func1_impl(struct Foo *pThis)
{
    printf("func1_impl\n");
}

void func2_impl(struct Foo *pThis)
{
    printf("func2_impl\n");
}

static FooVtbl foo_vtbl = {func0_impl, func1_impl, func2_impl};

int main(void)
{
    Foo foo0 = {0, &foo_vtbl};
    Foo foo1 = {1, &foo_vtbl};
    Foo foo2 = {2, &foo_vtbl};
    
    foo0.pVtbl->func1(&foo0);
    
    return 0;
}
    这样的代码结构仅需要在对象中持有指向选函数表的指针,而无需持有函数指针,因此可以节省内存。但另一方面,由于函数调用必须经过虚函数表,所以调用过程和程序结构变得复杂了。

3.2.8 非虚函数

    通过在对象内持有函数指针,可以让对象的行为根据对象不同而发生变化。但是某些函数在不同对象中处理也可能是相同的,这时就无需再对象内持有函数指针了。如上述例子中,需要增加一个将count复位为0的函数,可以有两种实现,如下:

typedef struct Foo {
    int count;
    void (* const func0)(struct Foo *pThis);
    void (* const func1)(struct Foo *pThis);
    void (* const func2)(struct Foo *pThis);
    void (*reset_counter)(struct Foo *pThis);
} Foo;

typedef struct Foo {
    int count;
    void (* const func0)(struct Foo *pThis);
    void (* const func1)(struct Foo *pThis);
    void (* const func2)(struct Foo *pThis);
} Foo;

void reset_foo_counter(Foo *pThis) {
{
    pThis->count = 0;
}

    第二种方式由于没有使用函数指针,所以能够节省内存,但也意味着无法根据对象来动态地改变行为。同时还有可能遇到命名空间的问题,引起函数名冲突。第一种方式就可以避免这一问题,只需在函数名前加static修饰,由于函数功能只是清零,没有必要动态地根据对象不同改变函数行为。

    一般情况下,请优先使用函数指针,只有在内存非常有限、对象行为不会变化的情况下,或者是非常有把握的情况下才考虑使用非虚函数。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值