doubango 3GPP IMS/LTE Framework v1.0.0
Programmer’s Guide
正如你知道的,C语言不是一个面向对象的语言。今天,OOP(面向对象编程)是编写精心设计的软件的最好方式。
在这份文档中“被定义的对象”是指一个特殊的C语言结构体。本文中的所有功能是tinySAK项目的一部分。为了解释如何定义对象及如何使用对象,我会使用一个基于“人”的对象的例子。“人”对象被声明是这样的:
typedef struct person_s
{
TSK_DECLARE_OBJECT; /* Mandatory */
char* name;
struct person_s* girlfriend;
}
person_t;
对象定义可以被视为一个类的定义。该定义包括对象的必须具备(强制性)的功能,对象的大小和一个引用计数器。强制性的功能包括:构造函数,析构函数和比较函数。C结构体通过在其体内的TSK_DECLARE_OBJECT宏#define为对象。
对象定义的指针应指向一个结构体 tsk_object_def_s。
typedef struct tsk_object_def_s
{
//! The size of the object.
size_t size;
//! Pointer to the constructor.
tsk_object_t* (* constructor) (tsk_object_t *, va_list *);
//! Pointer to the destructor.
tsk_object_t* (* destructor) (tsk_object_t *);
//! Pointer to the comparator.
int (*comparator) (const tsk_object_t *, const tsk_object_t *);
}
tsk_object_def_t;
创建一个对象分为两个阶段。第一阶段是动态在堆上分配对象; 这就是为什么在对象定义结构内它的大小是强制性的原因。当一个新的对象是在堆上分配,其所有成员(char*,void*,int,long...)将被清零。在第二阶段,将新创建的对象通过调用提供的构造函数初始化。为了执行这两个阶段,你应该调用tsk_object_new()或tsk_object_new_2()。
该对象被销毁分两个阶段进行。第一阶段释放其成员(void*,char* ...)。它的析构函数是负责这项任务的。在第二阶段中,对象本身被销毁。因为对象不能销毁自己,你应使用tsk_object_unref或tsk_object_delete,执行这两个阶段。接下来的部分对这两个函数之间的差异进行了解释。
下面是如何声明一个对象定义的例子:
//(Object defnition)
static const tsk_object_def_t person_def_t =
{
sizeof(person_t),
person_ctor,
person_dtor,
person_cmp
}
构造函数是只负责初始化,并不会分配对象。当传递给构造方法,
对象已经被分配。
下面是一个例子:
// (constructor)
static tsk_object_t* person_ctor(tsk_object_t * self, va_list * app)
{
person_t *person = self;
if(person){
person->name = tsk_strdup(va_arg(*app, const char *));
}
return self;
}
析构函数会释放该对象的成员,并不会破坏对象本身(第一阶段)。析构函数必须返回一个指针允许调用者执行第二阶段。
// (destructor)
static tsk_object_t * person_dtor(tsk_object_t * self)
{
person_t *person = self;
if(person){
TSK_FREE(person->name);
tsk_object_unref(person->girlfriend);
}
return self;
}
比较函数是用来比较两个定义的对象。比较的对象应具有相同的定义(或类型)。
// (comparator)
static int person_cmp(const tsk_object_t *_p1, const tsk_object_t *_p2)
{
const person_t *p1 = _p1;
const person_t *p1 = _p2;
int ret;
// do they have the same name?
if((ret = tsk_stricmp(p1->name, p2->name))){
return ret;
}
// do they have the same girlfriend?
if((ret = tsk_object_cmp(p1->girlfriend, p2->girlfriend))){
return ret;
}
// they are the same
return 0;
}
引用计数用来模拟垃圾收集。每个被定义的对象包含一个引用计数器的字段,这表明有多少对象对该对象进行引用。
当一个对象被创建(见下文)计数器的值被初始化为1,这是自动完成的,你不用做任何事情。计数器加1,当你调用tsk_object_ref和递减(1)当你调用tsk_object_unref。当计数器的值达到零,然后对象被回收(释放)。
正如你认为的那样,ANSI-C是不支持继承的。因为任何C结构可以强制转换为它的第一个元素的指针,继承可以这样实现:
#include "tsk.h"
// (a student is a person)
typedef struct student_s
{
struct person_s* person; // Must be the first element
char* school;
}
student_t;
// (as a student is a person you can do)
student_t* s;
((person_t*)s)->name = tsk_strdup("bob");
因为person_t是个定义的对象,那么student_t也是。
一旦声明了 对象的 定义 以及实现了对象 所有 的 强制性 功能 ,它可以 这样被使用:
// creates a person: will call the constructor
person_t* bob = tsk_object_new(&person_def_t, "bob");
// creates bob's girlfriend
bob->girlfriend = tsk_object_new(&person_def_t, "alice");
// deletes bob: will delete both bob and bob's girlfriend field by calling their destructors
tsk_object_unref(bob);
由于很难记住构造函数需要哪个参数(As it’s hard to guest which parameters the construct expects),一般我们用宏或函数来帮助。宏定义是这样的:
// create a person
#define PERSON_CREATE(name) tsk_object_new(&person_def_t, (const char*)name)
由于
析
构
函数
有固定
的
参数
,
有一个共同的
宏来
销毁所有
定义的对象
。
TSK_OBJECT_SAFE_FREE
()可用来
销毁
任何对象
。
只有
当
对象
的
引用计数
减1并
等于零时,对象将被释放
。
在所有
情况
下
(
释放或不释放
)
的指针值
将
设置为NULL。
上面的例子 可以 写成 这样 :
#include "tsk.h"
// create a person: will call the constructor
person_t* bob = PERSON_CREATE("bob");
// create bob's girlfriend
bob->girlfriend = PERSON_CREATE("alice");
// delete bob: will delete both bob and bob's girlfriend field by calling their destructors
TSK_OBJECT_SAFE_FREE(bob);