OO C 编程( 操作指南)
使用纯 ANSI C 进行 OO 编程
引言
C 语言与 OOP 的关系是,C 语言是一种只允许定义一个纯静态类(即只有静态成员(函数和变量)的类)的语言。这将是全局作用域。但在面向对象编程语言中,我们需要能够定义任意数量的命名类,以及一个实例化机制(即不只是纯静态类),还需要一个继承机制。
通过 OOCX,您将能够用 C 语言进行面向对象编程,其手动编写代码的工作量与任何其他支持面向对象的语言相同。
这是通过理解类中静态信息和非静态信息之间的区别来实现的。静态是指所有类型或类的对象(实例)共享的信息,而非静态则是为每个对象(实例)分配内存,以便存储特定于每个对象的信息(属于每个对象)。类的非静态元素将是变量,而函数、代码或进程将是静态的,即只分配一次,每个对象在需要执行该进程时都拥有指向代码定义位置的引用。
在 OOP 中,您通过对象调用函数。这样做时,对象本身会作为隐式参数传递给正在调用的函数。在 C 语言中,这是不可能的。在 C 语言中,我们会这样做
a->doSome(a)
来通过对象 a
调用函数 doSome
。但是,如果我们也有一个与 a
类型相同的对象 b
,这也会是正确的
b->doSome(a)
并且会产生完全相同的效果,因为我们访问的是相同的函数(因为 a
和 b
是同一类型的对象,因此共享相同的静态信息,例如功能),并将相同的对象(a
)作为参数传递给它。
为避免这种重复,我们可以定义一个函数,让它为我们执行调用,如下所示
int f(Obj *obj, char *action, ...){
/*get function ref from "action" arg and "obj" arg*/
/*call function ref passing to it as a parameter "obj" arg and rest of possible arguments*/
}
此函数的作用是调用通过对象类型访问的函数引用,并将对象本身作为参数传递给正在调用的函数。它还接受可变参数列表(这是 C 标准库的一个特性),因此我们可以传递函数所需的任何额外参数。
在 OOCX 中,我们有两个(至少在概念上)层,一个层是对象所在的层,另一个层是类型(类)所在的层。它们之间的连接是通过一个桥梁或结构连接起来的。对象将以指向该桥梁或结构的指针作为其第一个成员,该指针将对象本身与其类型连接起来。在类型中将驻留所有静态信息,包括函数引用。因此,当我们想从对象获取函数引用时,我们会通过其类型进行。这是因为静态信息和非静态信息之间的区别,这是此实现开发的关键点。
因此,在 OOCX 中,我们将始终使用相同的接口调用对象中的函数,即函数 f()
,如下所示
f((Obj *)obj1, "doSome");
f((Obj *)obj1, "doThisOther");
f((Obj *)obj2, "doSome");
正如您所见,我们将特定的对象 obj1
和 obj2
转换为通用的对象类型 Obj *
。这是因为使用相同的接口来与所有类型的对象进行“通信”。f()
只需知道它作为第一个参数接收一个对象,它不关心对象是什么类型。在 OOCX 中,对象将是任何数据结构,其第一个成员是指向 struct obj
的指针,该结构是连接对象及其相应类型的桥梁。在 C 语言中,内存中数据结构的地址就是其第一个成员在内存中的地址。因此,如果我们告诉 f()
它将作为第一个参数接收一个指向 Obj
的指针(其中 Obj
是指向 struct obj
的指针),我们就告诉 f()
它将接收一个对象的地址(指向以指向 struct obj
的指针开头的结构的地址),这足以让它访问其类型,而这正是它最终需要获取函数引用的。
如果我们想要任何输出,必须通过可变参数列表中的指针传递来获取。也就是说,例如,如果我们期望函数调用的计算结果是一个整数,我们必须传递一个指向 int
变量的指针,以便在此存储计算结果。在此简陋实现中就是这样。正如您将看到的,在后续开发中,我们将最终解决这个问题。
继承
继承是如何工作的?在 OOCX 中,对象可以以树状分层方式连接,以开发树状复合对象,每个对象都连接到其相应的类型或类。这样,通过给这个树状复合对象的顶点对象一个命令,函数 f()
将负责查看对象所附加的类型的树状结构,以找到我们请求的命令。当它在给定的命令参数和构成树的对象的任何类型中存储的命令之间找到匹配项时,它将执行该函数引用,并将找到命令的树中的对象作为参数传递。请记住,我说过我们有两个(概念上)层,一个层是对象所在的层,另一个层是类型所在的层。它们之间的连接是通过每个对象的 struct obj
桥梁实现的。
回到继承的概念,在开发类型或类时,我们会告诉我们正在开发的类型或类将与哪些其他类型或类连接(它将有哪些子类型)。此信息或连接我们在类型(类)定义中进行,实例化函数将用于一次性开发复合对象的树状结构。因此,如果我们开发一个类型 car
并声明它将具有已开发或存在的类型 engine
和已存在的类型(类)wheel
作为子项,那么在实例化 car
类型时,我们将获得一个树状复合对象,其顶点将是 car
类型的一个实例,然后连接到 engine
类型的一个实例和 wheel
类型的一个实例。最后两个将是 car
类型对象的子项,并且它们之间是兄弟关系。因此,当 f()
被要求执行 car
类型对象中的一个命令时,它将开始在 car
类型中查找匹配项,如果未找到,它将继续在 engine
类型中查找,如果未找到,它将继续在 wheel
类型中查找,依此类推。每个已开发类型中使用的功能标识符仅限于该类型定义。这意味着我们可以在定义一个类型中的函数和另一个类型中的函数时重复使用标识符。在这种情况下,我们必须知道 f()
函数将执行第一个匹配项,因此子项规范的顺序很重要。我们将在后续开发中看到如何解决这个问题。
因此,在 OOCX 中,我们有效地实现了继承,因为我们可以将代码和数据定义以及功能分布在一系列类型中,当其中一个类型被请求实例化时,我们将获得一个树状复合对象结构,其中每个对象都将连接到其类型,同时也以树状方式连接到其他对象,因此,借助 f()
,我们可以从结构的最顶层访问所有这些功能和数据分布。我们不必重新编码或重新输入任何接口,什么都不用,只需指定我们继承自谁(子项)。这是多重继承,因为是树状结构。
从这个角度来看,考虑到的对象(复合对象)就像自然界中的真实对象。例如,人体和所有有生命的对象都是树状复合对象。我们可以想象一个头部连接到一个胸部,胸部连接到两个手臂,等等。手臂连接到一个手,手连接到五个手指……人体的神经系统具有树状结构。信号在顶层发出,然后沿着其路径到达目的地(例如手指)。这在此实现中也是相同的。
从这个观点来看,另一个重要事实是,我们可以指定一个类型(类)被多次指定为另一个类型的子项。例如,一辆汽车有四个轮子。那么在 OOCX 中,在定义类型(类)car
时,我们将告诉或指定它有四个 wheel
类型的子项,这样在实例化 car
类型时,我们将获得四个 wheel
类型实例连接到 car
类型的一个父实例。
这种继承方式与使用指针作为对象结构的数据成员之间的区别在于,第一种方式是自动化的(实例化、释放内存、通过 f()
在树状结构中查找命令),而如果我们声明并使用指针作为对象的结构数据成员,我们必须手动编写代码将实例与这些指针关联起来,然后我们必须以编程方式重定向对这些实例的命令调用等……因此,由于手动编码工作量,这不算继承。
在继承或扩展这一概念时,还需要注意的另一个重要方面是“使用”和“演化”这两个概念之间的微妙区别。例如,我们有一个类型或类为 fingerA
的手指,但我们想制作一个更好的手指类型,例如更能抵抗高温。那么我们可以开发一个类型为 fingerB
的手指,它使用或将其作为子项拥有 fingerA
类型,在 fingerB
层中覆盖与抵抗高温相关的的方法。那么,通过这种单一(非多重)继承,我们进行了一种演化类型继承。而另一方面,当我们把一个 hand
类型连接到 finger
类型四次,告诉 hand
类型将有四个 finger
类型子项时,这是因为我们希望在实例化 hand
类型时也获得四个 finger
类型实例作为 hand
实例的子项。因此,在后一种情况下,我们更倾向于将继承(多重)的概念用于“使用”或构建复合对象,而不是纯粹的“演化”。
现在是时候看看一些图片,它们反映了到目前为止所讲的内容。
演示
我将在这里使用 OOCX 实现或框架来用 C 语言进行一些面向对象编程。
我将使用 mingw 作为编译器。编辑器可以是任何一个。我还会使用一个 make 工具进行编译,但这并非必需(之后您可以自行查阅有关 make 的教程)。我的文件夹结构将如下所示。对于每个开发的类型或类,我将有两个文件,一个以 .c 扩展名结尾,另一个以 .h 扩展名结尾。例如,假设我开发了一个名为 car
的类型。那么我将有两个源文件,car.c 和 Car.h。请注意,头文件以大写字母开头,而 .c 源文件以小写字母开头。这是约定俗成的,因为实际上在此实现或框架中,类型也是对象,是名为 type
的类型的一个实例。也就是说,在此实现中,有一个名为 type
的类型,我已开发了它,并且它作为所有其他要用 OOCX 开发的类型的类型。即使是 type
本身,它的类型也是 type
(您可以在之前的图片中看到这一点)。在源文件 .c 中使用类型时,必须在使用该类型之前包含其头文件。例如,假设我将在一个 .c 源文件中使用一个名为 car
的类型,那么我会这样做
#include <stdio.h>
#include "Car.h"
int main (){
/*code that uses "car" type (class)*/
return 0;
}
因此,在我们的开发文件夹中,我们将拥有每个类型(类)的所有 .c 源文件和 .h 头文件。测试文件或 main
函数也将定义在同一文件夹中的一个 .c 源文件中。此外,在此文件夹中,我们将有另一个名为 oocx 的文件夹,其中包含此实现,它是一堆(少量)源文件。因此,实际上,在使用此实现进行开发时,我们还必须包含 oocx.h 头文件,如下所示
#include <stdio.h>
#include "oocx/oocx.h"
#include "Car.h"
int main (){
/*code that uses "car" type (class)*/
return 0;
}
当您用 C 语言进行开发时,使用 gdb 调试器也是个好主意。它包含在 mingw 中,是一个命令行调试工具(请查阅教程)。正如您所见,我正在 Windows 上开发。对于其他 OS,您将使用其他编译器。
好了。我现在用一个实际示例开始编写代码。我将开发一个名为 person
的类型。将有两个源文件,person.c 和 Person.h,以及第三个测试源文件,其中将包含一个 main
函数来测试该类型(类)。我们先看看头文件(Person.h)。
Person.h
#ifndef pERSON_H
#define pERSON_H
#include "oocx/oocx.h"
typedef struct person{
Obj obj;
int age;
char *name;
} *Person;
extern Type const person;
#endif
/*
cmds:
"getThoughts", char **thoughts
"setThoughts", char *thoughts
"setName", char *name
"getName", char **name
"setAge", int age
"getAge", int *age
childs:
*/
您在注释中看到的是该类型的接口,如何与该类型的对象进行“对话”。如果我们不提供此信息,该类型的用户将不知道该类型的一个对象能理解哪些命令。头文件显示公共信息,而 .c 源文件将包含私有信息。那一行
extern Type const person;
代码意味着存在一个对象,该对象是一个类型,名为 person
,并且已在别处定义或声明。请记住,此头文件将在其他 .c 源文件中用于实例化 person
类型对象(正如您稍后将在测试文件中看到的)。现在我给您展示 person.c 源文件,我们在其中正确定义该类型、其函数等。
person.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Person.h"
typedef struct this{
struct person public;
struct{
char *thoughts;
} private;
} *This;
static err setAge(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.age= va_arg(vAL, int);
return err0;
}
static err getAge(Obj *obj, va_list vAL){
This this= (This)obj;
int *age= va_arg(vAL, int *);
*age= this->public.age;
return err0;
}
static err setName(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.name= va_arg(vAL, char *);
return err0;
}
static err getName(Obj *obj, va_list vAL){
This this= (This)obj;
char **name= va_arg(vAL, char **);
*name= this->public.name;
return err0;
}
static err getThoughts(Obj *obj, va_list vAL){
This this= (This)obj;
char **thoughts= va_arg(vAL, char **);
*thoughts= this->private.thoughts;
return err0;
}
static err setThoughts(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.thoughts= va_arg(vAL, char *);
return err0;
}
TYPE(person, this, F(getThoughts) F(setThoughts) F(setAge) F(getAge) F(setName) F(getName) F(NULL), CH(NULL), NULL)
让我们分部分进行分析。首先是include部分。
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Person.h"
它总是相同的。前两个属于 C 标准库,是必需的,因为我们使用了 NULL
,而且我们还使用了函数定义中的可变参数列表特性。第三个是我们的实现,是必需的,因为我使用了 Obj
标识符等。然后是我们要定义的源文件类型的头文件,在本例中是类型 person
和 Person.h,这是我们上面已经看到的头文件。然后是其他头文件,用于我们可能在此 .c 源文件中使用的其他类型(可以是函数定义内部使用的类型,也可以是正在定义的类型的子类型,用于声明继承)。在本例中,我们既不使用任何其他类型,也不继承任何其他类型(此类型没有子类型)。因此,我们以头文件结束。
现在我将分析下一部分代码
typedef struct this{
struct person public;
struct{
char *thoughts;
} private;
} *This;
static err setAge(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.age= va_arg(vAL, int);
return err0;
}
static err getAge(Obj *obj, va_list vAL){
This this= (This)obj;
int *age= va_arg(vAL, int *);
*age= this->public.age;
return err0;
}
第一部分是关于数据的。正如我们在头文件(上一个文件)中看到的那样,我们有这个
typedef struct person{
Obj obj;
int age;
char *name;
} *Person;
这表示我们定义了两个公共数据信息,这意味着数据将直接通过对象访问(不使用“get”或“set”函数,而是使用指针解引用,如您将看到的)。第一个将始终存在,并表示我们正在处理一个对象。因此,至少在任何头文件中,我们都将拥有这个
typedef struct any{
Obj obj;
/*possible public data*/
} *Any;
现在,在 .c 源文件中,我们声明私有数据。我们实际上声明了将在后面定义的函数以及文件末尾对宏 TYPE
的最后一次调用中使用的所有对象数据结构。我们总是这样做
typedef struct this{
struct any public;
struct{
/*possible private data variable declaration*/
} private;
} *This;
稍后,在函数定义中,您将看到我们如何使用它。因此,在此 .c 源文件中,我们为该类型定义了一个私有变量,即 thoughts
,它恰好是一个字符串或指向 char
的指针(通常一个人认为的事情本质上是私有的)。
接下来是我们第一个要看的功能定义。所有函数定义都相同。这是一个setter。
static err setAge(Obj *obj, va_list vAL){
This this= (This)obj;
this->public.age= va_arg(vAL, int);
return err0;
}
函数定义始终具有相同的签名。始终返回一个错误代码(整数),并始终接受两个参数,第一个是对象(未指定特定类型,这是因为 f
,请记住,它不关心类型,只关心它是一个对象,并且函数引用是由 f
调用的),以及一个可变参数列表(va_list
,在标准库中定义)。此外,我们使用关键字 static
来使此标识符(在此例中为 setAge
)仅限于此 .c 源文件,这意味着它可以自由地用于其他任何 .c 源文件以供其他类型或类定义使用。
函数定义中的第一行始终是将接收到的对象作为参数强制转换为正在定义的类型。我们声明一个名为 this
的指针,然后在函数定义代码的其余部分中使用它来访问对象所有私有和公共信息或数据。接下来总是从接收到的可变参数列表中获取所有参数。在本例中,我们获取一个 int
,并将其存储在对象的公共变量 age
中。当完成从可变参数列表中获取所有参数后,我们就处理正在定义的函数的代码或操作。在本例中,我们完成了。最后,我们总是返回一个错误代码,在本例中是 err0
,表示没有错误或成功。
现在我们来看getter
static err getAge(Obj *obj, va_list vAL){
This this= (This)obj;
int *age= va_arg(vAL, int *);
*age= this->public.age;
return err0;
}
正如您所见,签名是相同的。它包含我上面提到的所有元素,这些元素是我们在此 OOCX 实现的类型的 .c 源文件中的任何函数定义中始终处理的。在本例中,我们从 vAL
获取的参数是指向 int
的指针,因为此参数将用作输出变量,如您将在下一个 .c 源文件中看到的,该文件将定义 main
函数,并用作该类型的测试。我们访问对象的公共变量 age
,并将其值存储在指针变量指向的变量中。然后返回。
其余的是该类型(非静态数据)其余变量的setters和getters。最后,我们有了这个(始终出现在类型或类 .c 源文件定义的末尾)
TYPE(person, this, F(getThoughts) F(setThoughts) F(setAge) F(getAge) F(setName) F(getName) F(NULL), CH(NULL), NULL)
TYPE
是 OOCX 中定义的宏的名称(唯一的那个)。您可以看到它是一个宏,因为它后面的代码行不以 ;
结尾。C 语言中的宏是一段源代码,当找到它的标识符时将被替换。在这种情况下,它接受参数。第一个参数将始终是类型的名称(对象的名称,因此以小写字母开头)。然后是该文件中用于完整数据结构定义(私有和公共)的标签。在本例中是 this
标签(查看文件开头我们进行 struct this
的地方)。这用于宏中被替换的代码来计算该类型(类)对象的大小,这将在最后由实例化函数使用。接下来是一个非逗号分隔的列表,始终以 F(NULL)
结尾。F
代表函数。此列表用于将所有所需功能加载到正在定义的类型中。我们使用此 .c 源文件中定义的本地标识符来加载函数引用到类型中。此外,由宏替换的代码会自动生成一个命令列表,这些命令是存储为字符串的标识符,并将其也存储在类型中,因此当我们通过 f
请求对象的一个命令时,它可以根据命令调用函数引用。如果我们不想在此宏调用部分加载任何函数,我们至少必须将 F(NULL)
作为参数。然后是一个类似的列表,用于子项或继承。在本例中,我们不继承,所以只放 CH(NULL)
。最后一个参数用于类型中定义的静态变量,表示所有对象共享的数据变量。在本例中,我们放入 NULL
。
最后,我们必须处理测试文件,以测试刚定义的类型(类)。测试意味着使用和检查功能、错误、结果等。因此,我首先向您展示测试 .c 源文件
personT.c
#include <stdio.h>
#include "oocx/oocx.h"
#include "Person.h"
err main(){
/*i must init "type" object to be able to use it*/
typeInit();
/*i use "type" object to init rest of types, in this case "person" type*/
f((Obj *)type, "init", person);
/*i get an instance of "person" type, that is, an object of type "person"*/
Person p1= NULL;
f((Obj *)person, "new", &p1, NULL);
p1->age= 30;
p1->name= "roggc";
f((Obj *)p1, "setThoughts", "i want a big hamburguer");
int age;
char *name, *thoughts;
f((Obj *)p1, "getAge", &age);
f((Obj *)p1, "getName", &name);
f((Obj *)p1, "getThoughts", &thoughts);
printf("my name is %s and i am %d and now i am thinking: '%s'", name, age, thoughts);
return err0;
}
/*
this is the output when executing this console program:
my name is roggc and i am 30 and now i am thinking: 'i want a big hamburguer'
*/
首先要注意的是,在 OOCX 中,我们总是通过对话与对象进行交互(请记住,类型本身也是对象,是一种特殊的类型)。在我们可以告诉它们事情或给它们下达命令之前,必须先初始化类型。因此,我们首先做的是初始化类型 type
(通过使用已定义的全局函数 typeInit()
。这个 f()
是此实现中真正使用(调用)的唯一函数)。然后,通过给 type
类型(类)下达 "init"
命令并传递类型 person
作为该命令的参数来初始化类型 person
。完成这些之后,我们就获得了一个 person
类型(类)的实例。我们通过给 person
类型(记住它是一个 type
类型的对象,因此 f
将在 type
中查找该命令并找到它并执行它。所以所有类型,即 type
的实例,都将理解此命令)下达 "new"
命令来做到这一点。我们首先声明了一个 Person
变量来处理我们将要获得的实例。然后您将看到公共变量如何可以直接从对象句柄访问,但私有变量必须通过 setter 和 getter 来设置和获取。
所以这是可行的。接下来的类型旨在向您展示(证明)继承和多重继承。
Employee.h
#ifndef eMPLOYEE_H
#define eMPLOYEE_H
#include "oocx/oocx.h"
typedef struct employee{
Obj obj;
} *Employee;
extern Type const employee;
#endif
/*
cmds:
"setSalary", double salary
"getSalary", double *salary
childs:
person
*/
employee.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Employee.h"
#include "Person.h"
typedef struct this{
struct employee public;
struct{
double salary;
} private;
} *This;
static err setSalary(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.salary=va_arg(vAL,double);
return err0;
}
static err getSalary(Obj *obj, va_list vAL){
This this= (This)obj;
double *salary= va_arg(vAL,double *);
*salary= this->private.salary;
return err0;
}
TYPE(employee, this, F(setSalary) F(getSalary) F(NULL), CH(person) CH(NULL), NULL)
employeeT.c
#include <stdio.h>
#include "oocx/oocx.h"
#include "Employee.h"
err main(){
/*this always comes first, to init "type" type*/
typeInit();
/*we "init" type "employee" dialoging with type "type"*/
f((Obj *)type, "init", employee);
/*we get instance of type "employee" dialoging with type "employee"*/
Employee emp1= NULL;
f((Obj *)employee, "new", &emp1, NULL);
/*we dialog with object handled by "emp1": setters*/
f((Obj *)emp1, "setName", "rober");
f((Obj *)emp1, "setAge", 31);
f((Obj *)emp1, "setThoughts", "i have a lot of work to do...");
f((Obj *)emp1, "setSalary", 30000.);
char *name, *thoughts;
int age;
double salary;
/*we dialog with object handled by "emp1": getters*/
f((Obj *)emp1, "getName", &name);
f((Obj *)emp1, "getAge", &age);
f((Obj *)emp1, "getSalary", &salary);
f((Obj *)emp1, "getThoughts", &thoughts);
printf("my name is %s and i am %d and i earn %.2f and now i am thinking: '%s'", name, age, salary, thoughts);
}
/*
output when executing this program:
my name is rober and i am 31 and i earn 30000.00 and now i am thinking: 'i have a lot of work to do...'
*/
因此,结论是,在此实现中,继承是可行的,因为我们定义了一个类型,该类型继承了另一个类型的全部数据和函数功能,而无需进行任何手动编码,只需指定我们继承自哪个类型即可。
为了向您展示(证明)多重继承,我将开发一个类型 spyder
,然后创建一个类型 spyderEmployee
,它将从 spyder
和 employee
进行多重继承。
Spyder.h
#ifndef sPYDER_H
#define sPYDER_H
#include "oocx/oocx.h"
typedef struct spyder{
Obj obj;
} *Spyder;
extern Type const spyder;
#endif
/*
cmds:
"setPoisonLevel", double poisonLevel
"getPoisonLevel", double *poisonLevel
childs:
*/
spyder.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "Spyder.h"
typedef struct this{
struct spyder public;
struct{
double poisonLevel;
} private;
} *This;
static err getPoisonLevel(Obj *obj, va_list vAL){
This this= (This)obj;
double *poisonLevel= va_arg(vAL, double *);
*poisonLevel= this->private.poisonLevel;
return err0;
}
static err setPoisonLevel(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.poisonLevel= va_arg(vAL, double);
return err0;
}
TYPE(spyder, this, F(getPoisonLevel) F(setPoisonLevel) F(NULL), CH(NULL), NULL)
SpyderEmployee.h
#ifndef sPYDEReMPLOYEE_H
#define sPYDEReMPLOYEE_H
#include "oocx/oocx.h"
typedef struct spyderEmployee{
Obj obj;
} *SpyderEmployee;
extern Type const spyderEmployee;
#endif
/*
cmds:
"setSuperHeroName", char *name
"getSuperHeroName", char **name
childs:
spyder, employee
*/
spyderEmployee.c
#include <stdio.h>
#include <stdarg.h>
#include "oocx/oocx.h"
#include "SpyderEmployee.h"
#include "Spyder.h"
#include "Employee.h"
typedef struct this{
struct spyderEmployee public;
struct{
char *superHeroName;
} private;
} *This;
static err setSuperHeroName(Obj *obj, va_list vAL){
This this= (This)obj;
this->private.superHeroName= va_arg(vAL, char *);
return err0;
}
static err getSuperHeroName(Obj *obj, va_list vAL){
This this= (This)obj;
char **superHeroName= va_arg(vAL, char **);
*superHeroName= this->private.superHeroName;
return err0;
}
TYPE(spyderEmployee, this, F(setSuperHeroName) F(getSuperHeroName) F(NULL), CH(spyder) CH(employee) CH(NULL), NULL)
spyderEmployeeT.c
#include <stdio.h>
#include "oocx/oocx.h"
#include "SpyderEmployee.h"
err main(){
typeInit();
f((Obj *)type, "init", spyderEmployee);
SpyderEmployee spEmp1= NULL;
f((Obj *)spyderEmployee, "new", &spEmp1, NULL);
f((Obj *)spEmp1, "setAge", 24);
f((Obj *)spEmp1, "setName", "raul");
f((Obj *)spEmp1, "setThoughts", "I fill great!!!");
f((Obj *)spEmp1, "setSalary", 24000.);
f((Obj *)spEmp1, "setPoisonLevel", 56.);
f((Obj *)spEmp1, "setSuperHeroName", "the terrible employee");
char *name, *thoughts, *superHeroName;
int age;
double salary, poisonLevel;
f((Obj *)spEmp1, "getName", &name);
f((Obj *)spEmp1, "getAge", &age);
f((Obj *)spEmp1, "getThoughts", &thoughts);
f((Obj *)spEmp1, "getSalary", &salary);
f((Obj *)spEmp1, "getPoisonLevel", &poisonLevel);
f((Obj *)spEmp1, "getSuperHeroName", &superHeroName);
printf("i am %s and i am %d. my poison level is %.2f and my salary is %.2f. now i am thinking: '%s' and I am known as '%s'.\n", name, age, poisonLevel, salary, thoughts, superHeroName);
return err0;
}
/*
this is output when executing program:
i am raul and i am 24. my poison level is 56.00 and my salary is 24000.00. now i am thinking: 'I fill great!!!' and I am known as 'the terrible employee'.
*/
所以,在这里,多重继承已被证明。