第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;