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

第3章 C语言与面向对象

3.2.1 C与模块化

以经典栈的实现为例,代码如下:

#ifndef _STACK_H_
#define _STACK_H_

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    int top;
    const size_t size;
    int * const pBuf;
} Stack;

bool push(Stack *p, int val);
bool pop(Stack *p, int *pRet);

#define newStack(buf) {                 \
    0, sizeof(buf) / sizeof(int), (buf) \
} 

#ifdef __cplusplus
}
#endif

#endif

//stack.c
#include <stdbool.h>
#include "stack.h"

static bool isStackFull(const Stack *p) {
    return p->top == p->size;
}

static bool isStackEmpty(const Stack *p) {
    return p->top == 0;
}

// true: 成功, false: 失敗
bool push(Stack *p, int val) {
    if (isStackFull(p)) return false;
    p->pBuf[p->top++] = val;
    return true;
}

// true: 成功, false: 失敗
bool pop(Stack *p, int *pRet) {
    if (isStackEmpty(p)) return false;
    *pRet = p->pBuf[--p->top];
    return true;
}

  • 在函数名和变量名前面制定static修饰符,使函数和变量只在该编译单位内部有效,可以避免命名冲突,如下:
static bool isStackFull(const Stack *p) {
    return p->top == p->size;
}

static bool isStackEmpty(const Stack *p) {
    return p->top == 0;
}

  • 使用newStack的宏可以方便地将结构体初始化,如下:
#define newStack(buf) {                 \   
    0, sizeof(buf) / sizeof(int), (buf) \
}

//使用宏初始化
int buf[16];
Stack stack = newStack(buf);

//宏展开
Stack stack = {0, sizeof(buf)/sizeof(int), (buf)};
  • 使用extern "C"可使C++顺利调用C函数,同时#ifdef __cplusplus是为了告诉编译器,extern "C" 仅在C++中编译时才有效。C++标准规定了在C++中编译时,__cplusplus标示符才会被定义,如下:
#ifdef __cplusplus
extern "C" {
#endif

//中间代码

#ifdef __cplusplus
}
#endif

3.2.3 使用C进行面向对象编程

带检查功能的栈,实现两个校验功能:

1、只允许将固定范围内的值push至栈中

bool validateRange(Validator *pThis, int val);

2、要求每次push到栈中的值都必须比上次的值大

bool validatePrevious(Validator *pThis, int val);
#ifndef _STACK_H
#define _STACK_H

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct _Validator {
    bool (* const validate)(struct _Validator *pThis, int val);    //注释1
    void * const pData;    //注释2
} Validator;
    
typedef struct {    //注释3
    const int min;
    const int max;
} Range;
    
typedef struct {    //注释4
    int previousValue;
} previousValue;

typedef struct {
    int top;
    const size_t size;
    int * const pBuf;
    Validator * const pValidator;
} Stack;

#define newStack(buf) {                 \
    0, sizeof(buf)/sizeof(int), (buf),  \
    NULL                                \
}

#define rangeValidator(pRange) {    \    //注释6
    validateRange, pRange           \
}

#define previousValidator(pPrevious) {  \
        validatePrevious,               \
        pPrevious                       \
}

#define newStackWithValidator(buf, pValidator) { \
    0, sizeof(buf) / sizeof(int), (buf),         \
    pValidator                                   \
}

bool validateRange(Validator *pThis, int val);
bool validatePrevious(Validator *pThis, int val);

bool push(Stack *p, int val);
bool pop(Stack *p,  int *pRet);

#ifdef __cplusplus
}
#endif

#endif
#include <stdbool.h>
#include "stack.h"

static bool isStackFull(const Stack *p)
{
    return p->top == p->size;
}

static bool isStackEmpty(const Stack *p)
{
    return p->top == 0;
}

bool validateRange(Validator *pThis, int val)    //注释5
{
    Range *pRange = (Range *)(pThis->pData);
    return pRange->min <= val && val <= pRange->max;
}

bool validatePrevious(Validator *pThis, int val)    //注释5
{
    previousValue *pPrevious = (previousValue *)pThis->pData;
    if (val < pPrevious->previousValue) return false;
    pPrevious->previousValue = val;
    
    return true;
}

bool validate(Validator *p, int val)
{
    if (!p) return true;
    return p->validate(p, val);
}

bool push(Stack *p, int val)
{
    if (!validate(p->pValidator, val) || isStackFull(p)) return false;
    p->pBuf[p->top++] = val;
    
    return true;
}

bool pop(Stack *p, int *pRet)
{
    if (isStackEmpty(p)) return false;
    *pRet = p->pBuf[--p->top];
    
    return true;
}

注释1:validator结构体中的第一个成员是函数指针,该函数的参数为指向validator结构体的指针和需要校验的值,返回bool类型的检验结果。

注释2:第二个成员是函数指针,为了能够保存任意类型数据,使用了void指针。

注释3:如果是范围检查,则注释2中的void指针接受的数据为Range结构体。

注释4:如果是范围检查,则注释2中的void指针接受的数据为保存上次push的值得结构体。

注释5:函数指针所指向的校验函数。validator的pData成员是void指针,因此先通过类型转换(Cast)取出校验时所必需的值,然后进行校验处理。

注释6:生成结构体的宏。

3.2.4 面向对象与多态性

    面向对象的基本思考方式是将数据和处理数据的行为放到一起,降低耦合性,其特点就是不要将数据和处理数据的行为分开。多态性是指从调用者的角度看对象,会发现它们非常相似,难以区分,但是这些被调用对象的内部处理实际上各不相同。如上述程序中,在校验push函数的值时,调用者只会调用validator的validate函数,从外部并不知道其中到底进行了怎么样的校验处理。

bool validate(Validator *p, int val)
{
    if (!p) return true;
    return p->validate(p, val);
}

    通过将数据和处理成对分离,并使用结构体和函数指针实现多态性,就将校验职责分离至了validator中,而被分离出来的部分也可以作为组件被重复利用。

3.2.5 继承

    继承在上述例子中已经有所体现,首先有一个校验器,之后拓展出了范围检查校验器和push值递增校验器这两个校验器。我们称第一个校验器为父类,拓展出的两个校验器为子类。通过继承,在使用父类的地方,都可以直接使用子类。上述例子中,可以将没有设定pData的validator看作父类,将设定了Range、PreviousValue的validator看作子类。两个子校验器虽然处理内容不同,但是调用规则(validator函数的参数类型、返回值类型等)与父类校验器一样,因此可以在栈中调用时方便地替换。但是自校验器所需的数据不同,我们可以使用void指针回避这个问题。

typedef struct _Validator {
    bool (* const validate)(struct _Validator *pThis, int val);
    void * const pData;    //使用void类型
} Validator;
    然而这一方法有局限性,如要拓展范围检查校验器的功能,使得栈中只能接受奇数或偶数,若是仅在Range内简单拓展的话,Range的程序角色就会变得模糊,而且在不需要使用奇偶检验时,结构体中的两个多余的成员会浪费内存。C语言用稍好一点的办法解决这个问题。

    重构校验器代码,使用C实现对象继承,如下:

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

typedef struct {    //注释2
    Validator base;
    const int min;
    const int max;
} RangeValidator;

typedef struct {    //注释3
    Validator base;
    int previousValue;
} PreviousValueValidator;

typedef struct {
    int top;
    const size_t size;
    int * const pBuf;
    Validator * const pValidator;
} Stack;

bool validateRange(Validator *pThis, int val);
bool validatePrevious(Validator *pThis, int val);

#define newRangeValidator(min, max) \    //注释4
    {{validateRange}, (min), (max)}

#define newPreviousValueValidator \
    {{validatePrevious}, 0}

注释1:validator结构体定义中删除了void指针;

注释2:范围校验器定义中增加了父类validator成员;

注释3:push值递增校验器定义中也增加了父类validator成员;

注释4:生成校验器的宏。

bool validateRange(Validator *p, int val) {
    RangeValidator *pThis = (RangeValidator *)p;    //注释1
    return pThis->min <= val && val <= pThis->max;
}

bool validatePrevious(Validator *p, int val) {
    PreviousValueValidator *pThis = (PreviousValueValidator *)p;    //注释2
    if (val < pThis->previousValue) return false;
    pThis->previousValue = val;
    return true;
}

注释1:validateRange函数中使用的是RangeValidator,所以对父校验器进行类型转换,转换为子校验器RangValidator后访问其中的内部成员。

注释2:同样在validatePrevious函数内也需要对父类校验器进行类型转换。

在Stack的的定义中:

typedef struct {
    int top;
    const size_t size;
    int * const pBuf;
    Validator * const pValidator;
} Stack;

#define newStackWithValidator(buf, pValidator) { \
    0, sizeof(buf) / sizeof(int), (buf),         \
    pValidator                                   \
}

可以按照如下方式生成使用校验器的栈:

RangeValidator validator = newRangeValidator(0, 9);

Stack stack = newStackWithValidator(buf, &validator.base);    \\注释1

注释1:newStackWithValidator的第2个参数是指向validator的指针,这里传递的是RangeValidator结构体中保存的指向base成员的指针,利用了结构体中起始地址与第一个成员的内存地址相同的特性。

    这样,以后需要validator的地方都可以使用RangeValiator。在进行校验的validateRange函数中对Validator进行类型转换后,返回RangeValidator,如下。

bool validateRange(Validator *p, int val) {
    RangeValidator *pThis = (RangeValidator *)p;
    return pThis->min <= val && val <= pThis->max;
}

    按照此方法,可以解决之前提出的拓展Range的问题。只需将RangeValidator作为base使用即可拓展RangeValidator,如下:

typedef struct {
    rangeValidator base;
    const bool needOddEvenCheck;
    const bool needToBeOdd;
}OddEvenRangeValidator;


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值