LW_OOPC学习02

文章目录

  • 参考
  • LW_OOPC配置
  • LW_OOPC宏说明
    • (1)INTERFACE
    • (2)ABS_CLASS
    • (3)CLASS
    • (4)EXTENDS 和 IMPLEMENTS
    • (5)ABS_CTOR 和 END_ABS_CTOR
    • (6)FUNCTION_SETTING
    • (7)CTOR 和 END_CTOR
    • (8)SUPER_CTOR
    • (9)DTOR 和 END_DTOR
    • (10)SUPER_PTR
    • (11)SUPER_PTR_2 和 SUPER_PTR_3
    • (12)SUB_PTR
    • (13)SUB_PTR_2 和 SUB_PTR_3
    • (14)INHERIT_FROM

参考

LW_OOPC宏配置及使用指南
金永华

LW_OOPC配置

在lw_oopc.h中,有这么几行代码:

// 配置宏(两种配置选其一):
#define LW_OOPC_USE_STDDEF_OFFSETOF     // 表示使用C标准定义的offsetof
// #define LW_OOPC_USE_USER_DEFINED_OFFSETOF // 表示使用用户自定义的lw_oopc_offsetof宏

// 是否支持内存泄露检测,缺省不支持
// #define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR

// 是否支持调试信息打印(内存分配和释放的详细信息),缺省关闭打印
// #define LW_OOPC_PRINT_DEBUG_INFO

从上边的注释,我们可以看出,LW_OOPC需要使用offsetof宏,如果你的开发环境能够支持C标准定义的offsetof宏,那么什么都不需要动。如果你的开发环境不能支持C标准定义的offsetof宏,那么可以选择使用用户自定义的lw_oopc_offsetof宏(如果你的开发环境连用户自定义的offsetof宏都不支持,在这种情形下,LW_OOPC将无法很好的支持多态特性,很遗憾,你只能与LW_OOPC失之交臂)。

LW_OOPC宏说明

(1)INTERFACE

INTERFACE(IMoveable)
{
    void (*move)(IMoveable* t);     // Move行为
};

在LW_OOPC中,声明接口、抽象类和具体类的方法成员比较特殊,均是函数指针类型的成员。事实上,LW_OOPC正是借助了函数指针的特性,完成了多态功能的模拟。

(2)ABS_CLASS

ABS_CLASS用于声明抽象类,譬如:

ABS_CLASS(Animal)
{
    char name[128];     // 动物的昵称(假设小于128个字符)
    int age;            // 动物的年龄

    void (*setName)(Animal* t, const char* name);   // 设置动物的昵称
    void (*setAge)(Animal* t, int age);             // 设置动物的年龄
    void (*sayHello)(Animal* t);                    // 动物打招呼
    void (*eat)(Animal* t);                         // 动物都会吃(抽象方法,由子类实现)
    void (*breathe)(Animal* t);                     // 动物都会呼吸(抽象方法,由子类实现)
    void (*init)(Animal* t, const char* name, int age);		// 初始化昵称和年龄
};

(3)CLASS

CLASS用于声明具体类,譬如:

CLASS(Fish)
{
    EXTENDS(Animal);		// 继承Animal抽象类
    IMPLEMENTS(IMoveable);	// 实现IMoveable接口

    void (*init)(Fish* t, const char* name, int age);		// 初始化昵称和年龄
};

在该例中,我们声明了Fish类,并让该类继承Animal抽象类,并且实现IMoveable接口。

(4)EXTENDS 和 IMPLEMENTS

在介绍CLASS宏的时候,我们在代码中看到有两个宏:EXTENDS和IMPLEMENTS,如果你查看lw_oopc.h的源码,你将会发现他们是一模一样的:

#define IMPLEMENTS(type)	struct type type
#define EXTENDS(type)		struct type type

之所以同时提供继承和实现关键字,仅仅是为了让熟悉Java的人更加容易理解LW_OOPC宏。(注意,在LW_OOPC中,建议将继承和实现声明写在结构体的开头,把继承和实现声明摆在显眼的位置,有助于阅读代码的人更好的理解代码)

(5)ABS_CTOR 和 END_ABS_CTOR

ABS_CTOR和END_ABS_CTOR用于定义抽象类的构造函数,例如:

/* 设置动物的昵称*/
void Animal_setName(Animal* t, const char* name)
{
    // 这里假定name不会超过128个字符,为简化示例代码,不做保护(产品代码中不要这样写)
    strcpy(t->name, name);
}
/* 设置动物的年龄*/
void Animal_setAge(Animal* t, int age)
{
    t->age = age;
}
/* 动物和我们打招呼*/
void Animal_sayHello(Animal* t)
{
    printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);
}
/* 初始化动物的昵称和年龄*/
void Animal_init(Animal* t, const char* name, int age)
{
    t->setName(t, name);
    t->setAge(t, age);
}

ABS_CTOR(Animal)
FUNCTION_SETTING(setName, Animal_setName);
FUNCTION_SETTING(setAge, Animal_setAge);
FUNCTION_SETTING(sayHello, Animal_sayHello);
FUNCTION_SETTING(init, Animal_init);
END_ABS_CTOR

前面,我们声明Animal是一个抽象类,对应的构造函数定义需要使用ABS_CTOR和END_ABS_CTOR。ABS_CTOR是Abstract Constructor的缩写。

(6)FUNCTION_SETTING

在介绍ABS_CTOR和END_ABS_CTOR宏的时候,我们在代码中又发现一个陌生的宏:
FUNCTION_SETTING,这个宏在LW_OOPC中的地位非同凡响,没有它,LW_OOPC就不可能存在。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct)分配内存,然后,最重要的一个步骤是为结构体中的函数指针成员赋值,这一过程,也可以称为函数绑定(有点类似C++中的动态联编)。函数绑定的过程由FUNCTION_SETTING宏来完成。
我们来看看FUNCTION_SETTING宏是如何实现的:

#define FUNCTION_SETTING(f1, f2)	cthis->f1 = f2;

看到这里,想必读者应该会心一笑了。:)

(7)CTOR 和 END_CTOR

CTOR和END_CTOR用于定义具体类的构造函数,例如:

/* 鱼的吃行为 */
void Fish_eat(Animal* t)
{
    printf("鱼吃水草!\n");
}
/* 鱼的呼吸行为 */
void Fish_breathe(Animal* t)
{
    printf("鱼用鳃呼吸!\n");
}
/* 鱼的移动行为 */
void Fish_move(IMoveable* t)
{
    printf("鱼在水里游!\n");
}
/* 初始化鱼的昵称和年龄 */
void Fish_init(Fish* t, const char* name, int age)
{
    Animal* animal = SUPER_PTR(t, Animal);
    animal->setName(animal, name);
    animal->setAge(animal, age);
}

CTOR(Fish)
SUPER_CTOR(Animal);
FUNCTION_SETTING(Animal.eat, Fish_eat);
FUNCTION_SETTING(Animal.breathe, Fish_breathe);
FUNCTION_SETTING(IMoveable.move, Fish_move);
FUNCTION_SETTING(init, Fish_init);

END_CTOR

从代码上看,CTOR/END_CTOR 与 ABS_CTOR/END_ABS_CTOR 的使用方式完全相同,的确是,不过,背后,这两对宏的实现方式略有差异,建议有兴趣的读者,认真研究一下LW_OOPC的源码。这里,简单地说明如下:我们希望明确区分抽象类和具体类的概念,抽象类是不可以创建对象的,而具体类则可以。前面,我们声明了Animal是抽象类,Fish类是具体类,那么,我们希望:

Animal* animal = Animal_new();		// 不允许这样写!
Fish* fish = Fish_new();			// 允许这样写!

(8)SUPER_CTOR

在讲解CTOR/END_CTOR宏的时候,又出现一个陌生的宏:SUPER_CTOR。它的功能与Java中的super关键字非常类似。
SUPER_CTOR(Animal);
意为:调用Animal类的构造函数。(建议将SUPER_CTOR写在“构造函数”体的开头)

(9)DTOR 和 END_DTOR

DTOR和END_DTOR用于定义“析构函数”,例如:
// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)

DTOR(Expr_node)
	if (--cthis->use == 0)      // 递减引用计数,如果计数为,释放自己
	{
		cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)
		lw_oopc_free(cthis);
	}
END_DTOR

这里,特别说明一点,为了模拟C++中的this指针,我们允许用户在ABS_CTOR/END_ABS_CTOR、CTOR/END_CTOR、DTOR/END_DTOR定义块中可以直接使用cthis。

(10)SUPER_PTR

SUPER_PTR用于“向上转型”,将对象指针向上转型成直接父类或者直接接口:

Fish* fish = Fish_new();    // 创建鱼对象

// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
fish->init(fish, "小鲤鱼", 1);          

// 将fish指针转型为Animal类型指针,并赋值给animals数组的第一个成员
Animal* animal = SUPER_PTR(fish, Animal);  

// 将fish指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第一个成员
IMoveable* moveFish = SUPER_PTR(fish, IMoveable);

这里,直接父类很容易理解,直接接口,呵呵,暂且认为我是首创的吧。
我们再来看一下Fish类的声明代码:

CLASS(Fish)
{
    EXTENDS(Animal);			// 继承Animal抽象类
    IMPLEMENTS(IMoveable);	// 实现IMoveable接口

    void (*init)(Fish* t, const char* name, int age);		// 初始化昵称和年龄
};

看:IMPLEMENTS(IMoveable); // 实现IMoveable接口
对Fish类来讲,IMoveable就是它的直接接口。

(11)SUPER_PTR_2 和 SUPER_PTR_3

SUPER_PTR_2和SUPER_PTR_3是SUPER_PTR的高级版本,它们的作用与SUPER_PTR是完全类似的,都是向上转型,只不过,SUPER_PTR_2是向上转两次,SUPER_PTR_3是向上转三次。也就是说,SUPER_PTR_2用于将自身的指针转型为爷爷辈指针,SUPER_PTR_3用于将自身的指针转型为曾祖辈指针。看看SUPER_PTR_2和SUPER_PTR_3的代码:

#define SUPER_PTR_2(cthis, father, grandfather)	\
	SUPER_PTR(SUPER_PTR(cthis, father), grandfather)

#define SUPER_PTR_3(cthis, father, grandfather, greatgrandfather)	\
	SUPER_PTR(SUPER_PTR_2(cthis, father, grandfather), greatgrandfather)

看到了吧,SUPER_PTR_2其实是两次SUPER_PTR的叠加。SUPER_PTR_3是三次SUPER_PTR的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。

(12)SUB_PTR

SUB_PTR用于“向下转型”,将父类指针向下转型成子类:

/* 鱼的吃行为*/
void Fish_eat(Animal* t)
{
    Fish* fish = SUB_PTR(t, Animal, Fish);
    ……	// 这里可以访问Fish类的成员
    printf("鱼吃水草!\n");
}

eat方法是Animal的一个方法,Fish类覆写了该方法,注意,由于该方法的第一个参数类型是:Animal*,在实现Fish类的eat方法Fish_eat时,如果想要访问到Fish类的成员,需要将第一个参数向下转型成Fish*,这就是SUB_PTR所完成的事情。

(13)SUB_PTR_2 和 SUB_PTR_3

SUB_PTR_2和SUB_PTR_3是SUB_PTR的高级版本,它们的作用与SUB_PTR是完全类似的,都是向下转型,只不过,SUB_PTR_2是向下转两次,SUB_PTR_3是向下转三次。也就是说,SUB_PTR_2用于将自身的指针转型为孙子辈指针,SUB_PTR_3用于将自身的指针转型为曾孙辈指针。看看SUB_PTR_2和SUB_PTR_3的代码:

#define SUB_PTR_2(selfptr, self, child, grandchild)     \
	SUB_PTR(SUB_PTR(selfptr, self, child), child, grandchild)

#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild)    \
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)

看到了吧,SUB_PTR_2其实是两次SUB_PTR的叠加。SUB_PTR_3是三次SUB_PTR的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。

(14)INHERIT_FROM

INHERIT_FROM用于访问直接父类的成员,例如:

Dog* dog = Dog_new();       // 创建狗对象

// 初始化狗对象的昵称为:牧羊犬,年龄为:2岁
dog->init(dog, "牧羊犬", 2);  
INHERIT_FROM(Animal, dog, age) = 3;		// 把牧羊犬的年龄修改为3岁
printf("狗的年龄是:%d岁!\n", INHERIT_FROM(Animal, dog, age));	// 打印狗的年龄

注意,LW_OOPC的上一个版本,我们同时提供了INHERIT_FROM_2和INHERIT_FROM_3这两个宏,INHERIT_FROM_2用于访问爷爷辈的成员,INHERIT_FROM_3用于访问曾祖辈的成员。我们认为应当尽量避免使用INHERIT_FROM_2和INHERIT_FROM_3宏,因为,这会导致类的继承关系中存在严重的数据耦合(自身类可以直接访问爷爷辈,甚至曾祖父辈的成员),这将导致程序难于理解,难于维护。因此,在当前版本中,删除了INHERIT_FROM_2和INHERIT_FROM_3宏,仅仅保留INHERIT_FROM。一般情况下,我们可以通过更加合理的函数封装,让当前类通过祖先类提供的方法间接地访问祖先类的成员。如果确实要在当前类中直接访问爷爷辈甚至曾祖辈的成员,我们可以先通过SUPER_PTR_2和SUPER_PTR_3将当前对象的指针转型为对应的祖先类指针,然后再通过其指针访问其成员。

你可能感兴趣的:(C设计技巧)