深入理解 OOC

1 简介

1.1 什么是 OOC?

OOC(Object Oriented C) 是一个高效、轻量的面向对象的 ANSI-C 扩展,用于支持ANSI-C面向对象的软件开发。它支持

  • 封装:隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别。
  • 单继承,多接口继:根据现有类定义新类和行为的能力。
  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。。

1.2 为何要用 OOC?

“Program to interfaces, not to implementations” 已经成为面向对象编程的共识,它可以有效的减小系统之间的依赖,提高软件的开发质量。然而目前依然有很多嵌入式的平台缺乏面向对象语言的工具链,ANSI-C 依然是很对嵌入式平台的主流开发语言,因此,如果能在c语言中找到一种模拟继承和多态性的方法,就可在这种非面向对象的语言中实现 OO 的设计模式,提升软件的开发质量。

2 OOC 如何实现?

简而言之,OOC使用宏来描述和使用类。下面深入讲解 OOC 的具体实现。

2.1 封装

在本节中,我将解释OOC框架如何实现封装。我将介绍一个简单类的创建过程,它的数据隐藏是通过命名约定执行的

假设我们想要写一个程序来打印某个公司员工的信息,因此我们定义一个类来收集员工的所有信息。

1
2
3
4
5
6
7
8
9
10
11
12
// employee.h
#ifndef __EMPLOYEE_H__
#define __EMPLOYEE_H__

struct Employee
{
    const char *szName;
    float salary;
};

void PrintEmployee(struct Employee *pEm);
#endif

它的简单实现如下:

1
2
3
4
5
6
7
8
9
 
// employee.c
#include <stdio.h>
#include "employee.h"

void PrintEmployee(struct Employee *pEm)
{
    printf("Name: %s, Salary: %f", pEm->szName, pEm->salary);
}

很显然,上面的实现由几个缺点:

  • 缺乏构造和析构函数,需要使用者自己初始化和清理结构体里的成员,从而导致代码中可能会有多处构造和析构的代码,增加维护的难度。
  • 一个好的C开发人员会添加函数来初始化结构体并在结束的时候清理它,但这仍然不能保证用户一定会调用它。

因此,封装是我们所需要的。通过使用统一的编码规则和命名约定,我们可以再在C 中模拟类的实现。

  • 类属性都是结构中的成员。
  • 类方法 是 C 函数, 它第一个参数是指向该属性结构的指针,称它为self。
  • 为了进一步加强属性、类方法和类之间的关系,我们使用统一的命名规则。类方法是类名与操作名称的连接,用下划线分隔,例如Employee_Print。这个简单的命名约定还可以防止不同类方法之间的命名冲突。
  • 访问控制是使用命名约定处理的另一方面。大多数OO语言提供了以下级别的保护:
    • private: 只能从类中访问,变量名以两个下划线”__“开始。
    • protected: 由类及其后代访问,变量名以单个下划线”_“开始。
    • public: 均可访问

采用上述规则,再应用到我们的例子中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
// employee.h
#ifndef __EMPLOYEE_H__
#define __EMPLOYEE_H__

typedef struct Employee
{
    const char *__szName;
    float __salary;
} Employee;
typedef Employee* PEmployee;

EM_Stat Employee_Con(PEmployee self, const char *szName, float salary);
void Employee_Print(PEmployee self);
EM_Stat Employee_Des(PEmployee self);

#define OOC_Employee_Print(self) Employee_Print(self)
#define OOC_Employee_GetSalary(self) (((PEmployee)(self))->__salary)
#define OOC_Employee_GetName(self) (((PEmployee)(self))->__szName)
#define OOC_Employee_Des(self) Employee_Des(self)
#endif

上述类的定义中,使用Employee_Con来构造 Employee 对象,使用Employee_Des来析构它。每个类必须至少有一个构造函数。析构函数是可选的,但即使没有声明析构函数,也必须使用OOC_ClassName_Des函数来析构类的实例。这个类的简单实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// employee.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "employee.h"

EM_Stat Employee_Con(PEmployee self, const char *szName, float salary)
{
    self->__szName = malloc(strlen(szName)+1);
    if(self->__szName == NULL)
        return EM_ERR;
    strcpy((char *)self->__szName, szName);
    self->__salary = salary;

    return EM_OK;
}

void Employee_Print(PEmployee self)
{
    printf("Name: %s, Salary: %f", self->__szName, self->__salary);
}

EM_Stat Employee_Des(PEmployee self)
{
    free((void *)self->__szName);
    return EM_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.c

#include <stdio.h>
#include "util.h"
#include "employee.h"

int main()
{
    Employee em;

    if(Employee_Con(&em, "Bob", 10000.0))
    {
        float salary;
        salary = OOC_Employee_GetSalary(&em);
        OOC_Employee_Print(&em);
    }
    OOC_Employee_Des(&em);

    return 0;
}

编译运行,输出如下:

1
Name: Bob, Salary: 10000.000000

2.2 单继承

类的封装是以一些编程规则和命名约定来实现的,那么类的继承该如何实现呢?在 OOC 框架中,多重继承无法实现,但是我们可以实现单继承和多接口继承,本节我们将详细介绍单继承的实现。

回到刚才的例子,我们实现了一个可以打印员工信息的类,经理作为一个特殊的员工,他具有和普通员工相同的属性,但他有一个额外的属性:级别(level),如果要打印经理的信息,很显然,我们需要创建一个从 Employee 继承的类,因此借鉴 C++中虚函数的实现,并且重新定义 Print 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// manager.h
#ifndef __MANAGER_H__
#define __MANAGER_H__

typedef struct Manager
{
    Employee super;
    int __level;
} Manager;
typedef Manager* PManager;

EM_Stat Manager_Con(PManager self, const char *szName, float salary, int level);
void Manager_Print(PManager self);

#define OOC_Manager_Print(self) Manager_Print(self)
#define OOC_Manager_GetSalary(self) (((PManager)(self))->super.__salary)
#define OOC_Manager_GetName(self) (((PManager)(self))->super.__szName)
#define OOC_Manager_Des(self) Employee_Des(self)
#endif

类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// manager.c

#include <stdio.h>
#include "util.h"
#include "employee.h"
#include "manager.h"


EM_Stat Manager_Con(PManager self, const char *szName, float salary, int level)
{
    if(EM_ERR == Employee_Con(&self->super, szName, salary))
    {
        return EM_ERR;
    }
    self->__level = level;

    return EM_OK;
}

void Manager_Print(PManager self)
{
    Employee_Print(&self->super);
    printf(", Level: %d", self->__level);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.c

#include <stdio.h>
#include "util.h"
#include "employee.h"
#include "manager.h"

int main()
{
    Manager ma;

    if(Manager_Con(&ma, "Bob", 10000.0, 7))
    {
        float salary;
        salary = OOC_Employee_GetSalary(&ma);
        printf("Inheritance test: Salary--%f\n", salary);
        OOC_Manager_Print(&ma);
    }
    OOC_Manager_Des(&ma);

    return 0;
}

编译运行,输出为

1
2
Inheritance test: Salary--10000.000000
Name: Bob, Salary: 10000.000000, Level: 7

上述例子中,可以看出,Manager 这个类它继承了 Employee 的所有属性和方法,并且重新定义了 Print 函数,增加了打印 level 的功能。

OOC 中继承实现的关键:父类的结构体定义必须是子类结构体的第一个成员,这样父类的结构体和子类结构体就有相同的首地址,从而保证将子类指针赋给父类指针时对成员访问的正确性。

2.3 多态

上个例子的实现中,虽然实现单继承,但是依然存在一个严重的问题:将子类的指针赋给父类指针的调用有问题,例如:将 Manager 的指针赋给 Employee 时,如果调用 Print ,调用的是父类的方法,而非子类的方法。

1
OOC_Employee_Print(&ma);

输出为

1
Name: Bob, Salary: 10000.000000

那么多态该如何实现呢?C++的多态是通过虚表来实现的,OOC也是通过类似的方法类实现多态。

首先,我们定义一个全局的父类,即所有的类都必须继承于它,它只包含一个成员:虚表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ooc.h
#ifndef __OOC_H__
#define __OOC_H__

typedef struct Object * PObject;
typedef struct ObjectClass *PObjectClass;

struct Object
{
    PObjectClass __vptr;
};

struct ObjectClass
{
    EM_Stat (*Des)(PObject);
};

EM_Stat Object_Con(PObject self);
EM_Stat Object_Des(PObject self);

#define OOC_VCALL(OX, CX, MX)\
    (*((P##CX##Class)(((PObject)(OX))->__vptr))->MX)(/*(P##CX)*/(OX))

#endif

上述代码中,Object 是全局的父类,而 ObjectClass是 Object 类中虚表的具体定义,它其实就是由一堆函数指针构成。而 Employee 继承于 Object,因此也就继承了 Object 的虚表,它的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// employee.h
#ifndef __EMPLOYEE_H__
#define __EMPLOYEE_H__

typedef struct Employee* PEmployee;
typedef struct EmployeeClass * PEmployeeClass;

struct Employee
{
    struct Object super;
    const char *__szName;
    float __salary;
};

struct EmployeeClass
{
    struct ObjectClass super;
    void (*Print)(PEmployee );
    float (*GetSalary)(PEmployee );
};


EM_Stat Employee_Con(PEmployee self, const char *szName, float salary);
void Employee_Print(PEmployee self);
float Employee_GetSalary(PEmployee self);
EM_Stat Employee_Des(PEmployee self);

#define OOC_Employee_Print(self) OOC_VCALL(self, Employee, Print)
#define OOC_Employee_GetSalary(self) OOC_VCALL(self, Employee, GetSalary)
#define OOC_Employee_Des(self) OOC_VCALL(&(self)->super, Object, Des)

#endif

EmployeeClass继承于ObjectClass,并扩展了一些自己的方法。类在实例化时,构造函数应该初始化它的虚表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// employee.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
#include "ooc.h"
#include "employee.h"

const struct EmployeeClass __employee={
    {
        Employee_Des
    },
    Employee_Print,
    Employee_GetSalary
};

EM_Stat Employee_Con(PEmployee self, const char *szName, float salary)
{
    EM_Stat err = EM_OK;
    err = Object_Con(&self->super);
    if(EM_ERR == err)
        return err;
    ((PObject)self)->__vptr = (PObjectClass)&__employee;

    self->__szName = malloc(strlen(szName)+1);
    if(self->__szName == NULL)
        return EM_ERR;
    strcpy((char *)self->__szName, szName);
    self->__salary = salary;

    return EM_OK;
}

void Employee_Print(PEmployee self)
{
    printf("Name: %s\n\tSalary: %f\n", self->__szName, self->__salary);
}

float Employee_GetSalary(PEmployee self)
{
    return self->__salary;
}

EM_Stat Employee_Des(PEmployee self)
{
    free((void *)self->__szName);
    return EM_OK;
}

类的实现中,我们首先应该实现该类的虚表__employee,并在构造函数中初始化。那么在调用 OOC_CLASS_METHOD 时,通过函数指针,就可以调用虚表中指定的函数。

下面给出 Manager 的定义和实现以及相应的测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// manager.h
#ifndef __MANAGER_H__
#define __MANAGER_h__

typedef struct Manager* PManager;
typedef struct ManagerClass* PManagerClass;

struct Manager
{
    struct Employee super;
    int __level;
};

struct ManagerClass
{
    struct EmployeeClass super;
    int (*GetLevel)(PManager );
};

EM_Stat Manager_Con(PManager self, const char *szName, float salary, int level);
void Manager_Print(PManager self);
int Manager_GetLevel(PManager self);
EM_Stat Manager_Des(PManager self);

#define OOC_Manager_Print(self) OOC_VCALL(&(self)->super, Employee, Print)
#define OOC_Manager_GetLevel(self) OOC_VCALL(self, Manager, GetLevel)
#define OOC_Manager_GetSalary(self) OOC_VCALL(&(self)->super, Employee, GetSalary)
#define OOC_Manager_Des(self) OOC_VCALL(&(self)->super.super, Object, Des)

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// manager.c

#include <stdio.h>
#include "util.h"
#include "ooc.h"
#include "employee.h"
#include "manager.h"

const struct ManagerClass __manager={
    {
        {
            Employee_Des
        },
        Manager_Print,
        Employee_GetSalary
    },
    Manager_GetLevel
};

EM_Stat Manager_Con(PManager self, const char *szName, float salary, int level)
{
    if(EM_ERR == Employee_Con(&self->super, szName, salary))
    {
        return EM_ERR;
    }
    ((PObject)self)->__vptr = (PObjectClass)&__manager;

    self->__level = level;

    return EM_OK;
}

void Manager_Print(PManager self)
{
    Employee_Print(&self->super);
    printf("\tLevel: %d\n", self->__level);
}

int Manager_GetLevel(PManager self)
{
    return self->__level;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// main.c

#include <stdio.h>
#include "util.h"
#include "ooc.h"
#include "employee.h"
#include "manager.h"

int main()
{
    EM_Stat err;
    struct Employee em;
    struct Manager ma;
    PEmployee pEm[2];

    err = Employee_Con(&em, "Jim", 5000.0);
    err = Manager_Con(&ma, "Bob", 10000.0, 7);

    pEm[0] = &em;
    pEm[1] = &ma;
    {
        int i;
        for(i = 0; i < 2; i++)
            OOC_Employee_Print(pEm[i]);
    }
    OOC_Manager_Des(&ma);
    OOC_Employee_Des(&em);

    return 0;
}

编译运行,得到如下结果:

1
2
3
4
5
Name: Jim
	Salary: 5000.000000
Name: Bob
	Salary: 10000.000000
	Level: 7

可以看到,我们使用父类的指针,实现了子类方法的调用。

2.4 多接口继承

从2.2小节我们知道,类的继承中,父类的结构体必须是子类结构体的第一个成员,这也就决定了 OOC 中类只能单继承。

但如果一个类只定义了抽象方法(纯虚函数),没有定义任何属性,那么这样的类可以只用它的虚表来表示,从这个类继承也就只需要维护它的虚表,当然,还需要实现所有的抽象方法。而一个对象可以很容易地维护许多这样的指针,因此实现这种特定类(接口)的多继承是很容易实现的,无非就是多维护几个虚表。

回到我们的例子,假设我们的打印员工信息的程序非常成功,我们被要求把打印函数做成通用的接口,从而供其他程序使用,并且需要提供一个比较员工薪资的接口,作为一个优秀的程序员,我们希望写一个通用的比较和打印函数,它们可以处理不同类型的类。

所以我们需要重写 Employee 类,它需要继承于 Object 类,同时继承于 IPrint 和 IComparable这两个接口类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// employee.h
#ifndef __EMPLOYEE_H__
#define __EMPLOYEE_H__

typedef struct Employee* PEmployee;
typedef struct EmployeeClass * PEmployeeClass;

struct Employee
{
    struct Object super;
    PIComparable IComparable;
    PIPrint IPrint;
    const char *__szName;
    float __salary;
};

struct EmployeeClass
{
    struct ObjectClass super;
    struct IComparable IComparable;
    struct IPrint IPrint;
    float (*GetSalary)(PEmployee );
    const char *(*GetName)(PEmployee );
};


EM_Stat Employee_Con(PEmployee self, const char *szName, float salary);
int Employee_Compare(PEmployee self, PEmployee other);
void Employee_Print(PEmployee self);
float Employee_GetSalary(PEmployee self);
const char * Employee_GetName(PEmployee self);
EM_Stat Employee_Des(PEmployee self);

#define OOC_Employee_Print(self) OOC_ICALL(&(self)->IPrint, Print))
#define OOC_Employee_Compare(self, other) OOC_ICALL(&(self)->IComparable, Compare), (other))
#define OOC_Employee_GetSalary(self) OOC_VCALL(self, Employee, GetSalary)
#define OOC_Employee_GetName(self) OOC_VCALL(self, Employee, GetName)
#define OOC_Employee_Des(self) OOC_VCALL(&(self)->super, Object, Des)

#endif

同时,我们给出 IPrint 和 IComparable以及相关的宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
// iprint.h
#ifndef __IPRINT_h__
#define __IPRINT_h__

typedef struct IPrint * PIPrint;
struct IPrint
{
    void (*Print)(PObject);
    int __offset;
};

#define OOC_IPrint_Print(self) OOC_ICALL((self), Print) OOC_END_ICALL
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// icomparable.h
#ifndef __ICOMPARABLE_H__
#define __ICOMPARABLE_H__

typedef struct IComparable * PIComparable;
struct IComparable
{
    int (*Compare)(PObject self, PObject other);
    int __offset;
};

#define OOC_IComparable_Compare(self, other) OOC_ICALL((self), Compare), (other) OOC_END_ICALL

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ooc.h
#ifndef __OOC_H__
#define __OOC_H__

typedef struct Object * PObject;
typedef struct ObjectClass *PObjectClass;

struct Object
{
    PObjectClass __vptr;
};

struct ObjectClass
{
    EM_Stat (*Des)(PObject);
};

EM_Stat Object_Con(PObject self);
EM_Stat Object_Des(PObject self);

#define OOC_VCALL(OX, CX, MX)\
    (*((P##CX##Class)(((PObject)(OX))->__vptr))->MX)(/*(P##CX)*/(OX))
#define OOC_ICALL(IPX, MX) (*(*(IPX))->MX)(OOC_I_TO_OBJ(IPX)
#define OCC_END_ICALL )

#define OOC_I_TO_OBJ(IPX) ((PObject)(void*)((char*)(IPX)-(*(IPX))->__offset))

#endif

此时,假如我们调用OOC_Employee_Compare,传入 Compare 函数的是 IComparable 类型的指针,那么该如何正确的访问到 Employee 的类成员呢?很简单,通过 IComparable 里的__offset 成员,它记录了 Employee 类中,IComparable 成员首地址与 Employee 首地址的偏差,因此将 IComparable 的指针地址减去__offset 就可以得到 Employee 的首地址,下面这条宏定义就是用于实现此功能:

1
#define OOC_I_TO_OBJ(IPX) ((PObject)(void*)((char*)(IPX)-(*(IPX))->__offset))

理解多接口继承的原理后,我们给出对应的接口实现和测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// employee.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include "util.h"
#include "ooc.h"
#include "icomparable.h"
#include "iprint.h"
#include "employee.h"

const struct EmployeeClass __employee={
    {
        Employee_Des
    },
    {
        Employee_Compare,
        offsetof(struct Employee, IComparable)
    },
    {
        Employee_Print,
        offsetof(struct Employee, IPrint)
    },
    Employee_GetSalary,
    Employee_GetName
};

EM_Stat Employee_Con(PEmployee self, const char *szName, float salary)
{
    EM_Stat err = EM_OK;
    err = Object_Con(&self->super);
    if(EM_ERR == err)
        return err;
    ((PObject)self)->__vptr = (PObjectClass)&__employee;
    self->IComparable = (struct IComparable *) &__employee.IComparable;
    self->IPrint = (struct IPrint *) &__employee.IPrint;

    self->__szName = malloc(strlen(szName)+1);
    if(self->__szName == NULL)
        return EM_ERR;
    strcpy((char *)self->__szName, szName);
    self->__salary = salary;

    return EM_OK;
}

int Employee_Compare(PEmployee self, PEmployee other)
{
    int res = 0;
    if(self->__salary > other->__salary)
        res = 1;
    else if(self->__salary < other->__salary)
        res = -1;

    return res;
}

void Employee_Print(PEmployee self)
{
    printf("Name: %s\n\tSalary: %f\n", self->__szName, self->__salary);
}

float Employee_GetSalary(PEmployee self)
{
    return self->__salary;
}

const char * Employee_GetName(PEmployee self)
{
    return self->__szName;
}

EM_Stat Employee_Des(PEmployee self)
{
    free((void *)self->__szName);
    return EM_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// manager.c

#include <stdio.h>
#include <stddef.h>
#include "util.h"
#include "ooc.h"
#include "icomparable.h"
#include "iprint.h"
#include "employee.h"
#include "manager.h"

const struct ManagerClass __manager={
    {
        {
            Employee_Des
        },
        {
            Employee_Compare,
            offsetof(struct Manager, super.IComparable)
        },
        {
            Manager_Print,
            offsetof(struct Manager, super.IPrint)
        },
        Employee_GetSalary,
        Employee_GetName
    },
    Manager_GetLevel
};

EM_Stat Manager_Con(PManager self, const char *szName, float salary, int level)
{
    if(EM_ERR == Employee_Con(&self->super, szName, salary))
    {
        return EM_ERR;
    }
    ((PObject)self)->__vptr = (PObjectClass)&__manager;
    self->super.IComparable = (PIComparable) &__manager.super.IComparable;
    self->super.IPrint = (PIPrint) &__manager.super.IPrint;

    self->__level = level;

    return EM_OK;
}

void Manager_Print(PManager self)
{
    Employee_Print(&self->super);
    printf("\tLevel: %d\n", self->__level);
}

int Manager_GetLevel(PManager self)
{
    return self->__level;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// main.c

#include <stdio.h>
#include "util.h"
#include "ooc.h"
#include "icomparable.h"
#include "iprint.h"
#include "employee.h"
#include "manager.h"

int main()
{
    EM_Stat err;
    struct Employee em;
    struct Manager ma;
    PEmployee pEm[2];

    err = Employee_Con(&em, "Jim", 5000.0);
    err = Manager_Con(&ma, "Bob", 10000.0, 7);

    pEm[0] = &em;
    pEm[1] = &ma;
    {
        int i;
        for(i = 0; i < 2; i++)
        {
            OOC_Employee_Print(pEm[i]);
        }
    }
    int res = OOC_Employee_Compare(pEm[0], pEm[1]);
    printf("%s's salary(%f) is",
            OOC_Employee_GetName(pEm[0]),
            OOC_Employee_GetSalary(pEm[0]));
    if(res > 0)
    {
        printf(" higher than ");
    }
    else if(res < 0)
    {
        printf(" lower than ");
    }
    else
    {
        printf(" equal to ");
    }
    printf("%s(%f)\n",
        OOC_Employee_GetName(pEm[1]),
        OOC_Employee_GetSalary(pEm[1]));
    OOC_Manager_Des(&ma);
    OOC_Employee_Des(&em);

    return 0;
}

编译运行,输出为:

1
2
3
4
5
6
Name: Jim
	Salary: 5000.000000
Name: Bob
	Salary: 10000.000000
	Level: 7
Jim's salary(5000.000000) is lower than Bob(10000.000000)

上述的多接口继承还存在一个问题:假设类 C 继承了两个接口 IA 和 IB,但是 IA 和 IB 都有一个相同名称的函数 Foo,此时 类 C 在接口实现的时候就会有命名冲突, 出现两个 C_Foo 函数,这该如何解决呢?很简单,我们可以在类 C的实现中重命名 FOO 函数,将从 IA 继承的 Foo 接口重命名为 C_FooFromA,从 IB 继承的 Foo 接口重命名为 C_FooFromB,在对应接口虚表初始化的时候,初始化为对应的函数即可。

3 总结

OOC 的本质就是 C 语言里的强制类型转换和指针的巧妙运用,特别是函数指针,它可以说是 OOC 实现的核心所在,理解了函数指针,OOC 也就很容易理解了。

第一篇博文,文章某些地方描述可能不够清晰、准确,有不当之处,欢迎指正。

Reference

  1. Booch: Object-Oriented Analysis and Design With Applications, Second Edition. Addison-Wesley, 1994
  2. Gamma, R. Helm, R. Johnson and J. Vlissides: Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995
  3. Meyer: Object-Oriented Software Construction. Second Edition, Prentice Hall, 1997
  4. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal : Pattern–Oriented Software Architecture, a System of Patterns. Wiley 1996
  5. Portable Inheritance and Polymorphism in C : The original document from Miro Samek that appeared on the Embedded.com website in February 1997(详细描述了 OOC 宏的实现)