纯 C OO 编程( 操作指南)
纯 C 语言,不使用任何宏代码定义。C 语言的面向对象编程太棒了!
作者附言:
- 本文已被最新文章 《C 语言的面向对象编程:使用 ANSI C 进行面向对象模型定义、实现和应用》 所取代。
- 本文仅作为一种 消遣 (娱乐),或对该主题的简单介绍,但存在严重缺陷,离真正的 C# 等语言的面向对象编程还有很大距离。
- 若要了解严肃、真正且权威的内容,请跳转至最新文章。我(作者)强烈推荐给您。
引言:为什么还要介绍 C 语言的面向对象编程
我试图在这里从头开始展示直接、纯粹的 C 语言面向对象编程(**不使用任何宏代码定义,也不试图模仿传统的面向对象风格和术语,只为 C 语言开发者提供纯粹的标准 C**)。这样做的目的多种多样:
- 证明如果您知道如何去做,使用 C 语言进行面向对象编程是可能的,而且并不那么困难。
- 利用 C 语言中的面向对象编程的代码重用特性。
- 更清晰地认识到 C 语言面向对象编程与其他为面向对象而设计的语言之间的区别(在 C 语言中需要额外手工编写的代码)。
- 更清晰地阐述面向对象编程中继承和扩展的概念在 C 语言中的应用,以及多重继承的使用。
- 演示如何在 C 语言中实现封装。
- 展示 C 语言的内在分离声明与定义使得虚拟类(virtual class)的概念更直观(虚拟类声明允许多态)。
- 通过将面向对象概念应用于 C 语言等其他语言来加深理解。
- 让您在 C 语言面向对象编程中获得乐趣,并发现它在您使用 C 语言的任何地方都很有用。
这是优秀的代码,也是组织良好的代码。**此特定代码本身并非最终目标,而是向您展示如何以这种方式进行编码。**
一览内容,请注意以下列表,其中包含接下来的章节标题(直至末尾):
- 对象类型 1:最简单的对象,将一个变量变成一个对象。
- 对象类型 2:更多的实例方法和公共变量。
- 对象类型 3:继承,从之前的对象类型扩展。
- 对象类型 4:数据和其他内容的封装。
- 稍作休息...
- 对象类型 5:玩转,从更复杂的对象创建一个更简单的对象。
- 对象类型 6:多重继承。
- 对象类型 7:私有部分的重用继承和接口重做。
- 再次稍作休息...
- 对象类型 8:进入多态的前奏。
- 对象类型 9:多态。
- 最后的休息... 结论与评论。
注意:您必须以过程式的方式了解 C 语言(指针等)。我使用了 'stdlib' 中的 'malloc
' 函数和 'sizeof
' 运算符。我使用 MinGW 编译器进行编译。
对象类型 1:最简单的对象,将一个变量变成一个对象。
我将首先向您展示如何创建最简单的对象。将 C 语言中的一个 int 变量转换为 VarInt 类型对象。
首先,类(类型)声明
/* VarInt.h////////////////////////////////////////////////////
*/
#ifndef vARiNT
#define vARiNT
struct varInt;
typedef struct varInt *VarInt;
struct varInt
{ int a;
/* destructor
*/
int (*delete)(VarInt);
};
/* constructor
*/
VarInt varInt();
#endif
/* ////////////////////////////////////////////////////VarInt.h
*/
现在是类定义
/* VarInt.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "VarInt.h"
/* we define functions
*/
static int delete(VarInt vInt1)
{ /* there is no pointers to free in, so we free object.
*/
free(vInt1);
return ERR0;
}
/* define the constructor.
*/
VarInt varInt()
{ VarInt vInt1= malloc(sizeof(struct varInt));
/* initzialise pointers in its inside
*/
vInt1->delete= delete;
return vInt1;
}
/* ////////////////////////////////////////////////////VarInt.c
*/
接下来是定义 `constDef.h`,它将是所有文件的全局文件,将被广泛使用
/* constDef.h////////////////////////////////////////////////////
*/
#ifndef CONSTdEF
#define CONSTdEF
#define TRUE 1
#define FALSE 0
#define ERR0 0
#define ERR1 1
#endif
/* ////////////////////////////////////////////////////constDef.h
*/
现在是时候测试我们的对象了
/* testVarInt.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "VarInt.h"
int main()
{ VarInt vInt1= varInt();
vInt1->a= 4;
vInt1->a++;
printf("\na in vInt1 is %d", vInt1->a);
if(vInt1->delete(vInt1)== ERR0)
{ vInt1= NULL;
printf("\nvInt1 freed");
}
return ERR0;
}
/* ////////////////////////////////////////////////////testVarInt.c
*/
现在,让我们看看(使用 MinGW 在 WindowsXP 上,通过“cc testVarInt.c VarInt.c”编译并执行“a.exe”后)我们得到的结果
/* output
a in vInt1 is 5
vInt1 freed
*/
对象类型 2:更多的实例方法和公共变量。
我将创建一个包含两个公共变量和一个实例方法来查找其差值。首先是类型声明
/* Dif.h////////////////////////////////////////////////////////////////
*/
#ifndef dIF
#define dIF
struct dif;
typedef struct dif *Dif;
struct dif
{ int a, b;
int (*delete)(Dif);
int (*difference)(Dif);
};
Dif dif();
#endif
/* /////////////////////////////////////////////////////////Dif.h
*/
现在是类型定义
/* Dif.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "Dif.h"
/* define functions
*/
static int delete(Dif df1)
{ /* we free any pointer in its inside,
apart from function methods.
there are no, so we free object.
*/
free(df1);
return ERR0;
}
static int difference(Dif df1)
{ return df1->b- df1->a;
}
Dif dif()
{ Dif df1= malloc(sizeof(struct dif));
/* initzialize pointers
*/
df1->delete= delete;
df1->difference= difference;
return df1;
}
/* ////////////////////////////////////////////////////Dif.c
*/
最后是对对象类型的测试
/* testDif.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "Dif.h"
int main()
{ Dif df1= dif();
df1->a= 4;
df1->b= 6;
printf("\ndifference is %d", df1->difference(df1));
/* we free object
*/
if(df1->delete(df1)== ERR0)
{ df1= NULL;
printf("\nobject freed");
}
return ERR0;
}
/* ////////////////////////////////////////////////////testDif.c
*/
编译后,输出将是
/* output
difference is 2
object freed
*/
对象类型 3:继承,从之前的对象类型扩展。
现在我们进入扩展能力或继承的概念。我将通过扩展或利用之前的对象类型(Dif)来创建一个对象类型,该类型比前一个多一个方法。因此,我们将获得一个能够进行加法和减法的对象类型。
首先是类型声明
/* SumDif.h////////////////////////////////////////////////////
*/
#ifndef sUMdIF
#define sUMdIF
#include "Dif.h"
struct sumDif;
typedef struct sumDif *SumDif;
struct sumDif
{ Dif df;
int (*delete)(SumDif);
/* we add one more method
*/
int (*sum)(SumDif);
};
SumDif sumDif();
#endif
/* ////////////////////////////////////////////////////SumDif.h
*/
第二,类型定义
/* SumDif.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "SumDif.h"
/* define methods
*/
static int delete(SumDif sDf)
{ /* there is one pointer to free
*/
Dif df= sDf->df;
if(df->delete(df)== ERR0)
{ sDf->df= NULL;
}
/* now we free object itself
*/
free(sDf);
return ERR0;
}
static int sum(SumDif sDf)
{ Dif df= sDf->df;
return df->a+ df->b;
}
/* the constructor
*/
SumDif sumDif()
{ SumDif sDf= malloc(sizeof(struct sumDif));
/* initzialise pointers
*/
sDf->df= dif();
sDf->delete= delete;
sDf->sum= sum;
return sDf;
}
/* ////////////////////////////////////////////////////SumDif.c
*/
第三,测试扩展后的对象类型
/* testSumDif.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "SumDif.h"
int main()
{ SumDif sDf= sumDif();
Dif df= sDf->df;
df->a= 4;
df->b= 2;
printf("\na is\t\%d\nb is\t%d\t\ndifference is\t%d\nsum is\t%d",
df->a, df->b, df->difference(df), sDf->sum(sDf));
/* we free object
*/
if(sDf->delete(sDf)==ERR0)
{ sDf= NULL;
printf("\nobject freed");
}
return ERR0;
}
/* ////////////////////////////////////////////////////testSumDif.c
*/
第四,检查测试文件的输出
/* output
a is 4
b is 2
difference is -2
sum is 6
object freed
*/
请记住,在这种情况下,编译时必须包含 'Dif.c',即我们执行:'cc testSumDif.c SumDif.c Dif.c'。否则,在链接时会出现问题。
我们刚刚看到了一个 C 语言中继承的示例(演示)。事实上,C 语言支持多重继承,只需在其公共接口中使用更多对象即可。
对象类型 4:数据和其他内容的封装。
现在我将向您展示如何实现封装或私有数据(或其他对象)。为了向您展示这一点,我将创建第一个(最简单的)对象,但不允许直接访问该变量,而是将其放入私有区域,并通过实例方法(设置和获取)来访问它。
首先,类型声明
/* VarInt2.h////////////////////////////////////////////////////
*/
#ifndef vARiNT2
#define vARiNT2
struct varInt2;
typedef struct varInt2 *VarInt2;
struct varInt2
{ /* the methods, no public vars
*/
int (*delete)(VarInt2);
int (*set)(VarInt2, int);
int (*get)(VarInt2);
/* the private part, a pointer to void.
the user must not do nothing with it,
but the object must carry with it to operate.
*/
void *private;
};
VarInt2 varInt2();
#endif
/* ////////////////////////////////////////////////////VarInt2.h
*/
第二,类型定义
/* VarInt2.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "VarInt2.h"
/* we declare private data
*/
struct private;
typedef struct private *Private;
struct private
{ int a;
};
/* the methods
*/
static int delete(VarInt2 vInt)
{ /* we must free the private part (allocated dynamically, as it will see
in the constructor, more below).
*/
/* the private part it has no pointers in it to free, so we directly
free it. if no, firts free pointers.
*/
free(vInt->private);
/* now we are done and we free the object
*/
free(vInt);
return ERR0;
}
static int set(VarInt2 vInt, int a)
{ /* a handler to private
*/
Private pr= vInt->private;
pr->a= a;
return ERR0;
}
static int get(VarInt2 vInt)
{ Private pr= vInt->private;
return pr->a;
}
/* the constructor
*/
VarInt2 varInt2()
{ VarInt2 vInt= malloc(sizeof(struct varInt2));
/* initzialise pointers
*/
vInt->delete= delete;
vInt->get= get;
vInt->set= set;
Private pr= vInt->private= malloc(sizeof(struct private));
/* here we don't use pr because there are no pointers to
initialize inside private structure.
*/
return vInt;
}
/* ////////////////////////////////////////////////////VarInt2.c
*/
第三,我们的对象类型的测试
/* testVarInt2.c////////////////////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "VarInt2.h"
int main()
{ VarInt2 vInt= varInt2();
vInt->set(vInt, 2);
printf("\nvInt is\t%d", vInt->get(vInt));
vInt->set(vInt, 3);
printf("\nnow vInt is\t%d", vInt->get(vInt));
/* we free object.
*/
if(vInt->delete(vInt)==ERR0)
{ vInt= NULL;
printf("\n--object freed--");
}
return ERR0;
}
/* ////////////////////////////////////////////////////testVarInt2.c
*/
第四,编译命令('cc testVarInt2.c VarInt2.c')和执行('a.exe')
/* output
vInt is 2
now vInt is 3
--object freed--
*/
您看,我们已经实现了封装,而无需直接操作数据。
在私有部分,我们也可以封装其他对象的使用,以及私有函数的使用。但后者意义不大,因为打算私有的函数不会在每个实例中复制(与动态分配的变量和对象不同),并且由于它们与使用它们的其他函数定义在同一个文件中,因此其使用是可以保证的。因此,为私有函数在私有结构中定义处理程序意义不大,因为我们已经可以使用了,不需要这样的处理程序。处理程序在公共部分是必需的,因为否则将无法访问在单独文件中定义为静态的函数。因此,我认为展示私有函数的使用没有必要。只需在同一个单独的文件中定义它们,并在需要时在同一个文件中使用它们。也将其定义为静态,这样它们的名称就不会与其他文件中的函数名称冲突。
稍作休息...
到目前为止,我们已经看到了
- 最简单的对象(一个变量,一个构造函数和一个析构函数)。
- 其他实例方法的使用。
- 继承或扩展能力的使用(公共对象的使用)。
- 封装的使用(或私有部分)。
我们还需要展示私有对象(私有部分的对象)的使用以及如何实现多态。此外,一个私有函数的示例也会很有帮助。然后,我们就完成了 C 语言面向对象编程的初步介绍(即在私有和公共部分使用变量、对象和函数)。
对象类型 5:玩转,从更复杂的对象创建一个更简单的对象。
现在,假设我们想要 `Dif` 对象类型的加法功能,而不是 `Sum` 对象。我们将通过私有继承 `SumDif` 来实现,并只为 `sum` 方法创建接口。
首先,类型声明(接口)
/* Sum.h/////////////////////////////////////////////////
*/
#ifndef sUM
#define sUM
struct sum;
typedef struct sum *Sum;
struct sum
{ int (*delete)(Sum);
int (*setA)(Sum, int);
int (*setB)(Sum, int);
int (*getA)(Sum);
int (*getB)(Sum);
int (*sum)(Sum);
void *private;
};
/* constructor
*/
Sum sum();
#endif
/* /////////////////////////////////////////////////Sum.h
*/
第二,定义
/* Sum.c////////////////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "Sum.h"
#include "SumDif.h"
/* We declare the private structure
*/
struct private;
typedef struct private *Private;
struct private
{ SumDif sD;
};
/* define methods
*/
static int delete(Sum s)
{ Private pr= s->private;
pr->sD->delete(pr->sD);
free(pr);
free(s);
return ERR0;
}
static int setA(Sum s, int a)
{ Private pr= s->private;
SumDif sD= pr->sD;
Dif df= sD->df;
df->a= a;
return ERR0;
}
static int setB(Sum s, int b)
{ Private pr= s->private;
SumDif sD= pr->sD;
Dif df= sD->df;
df->b= b;
return ERR0;
}
static int getA(Sum s)
{ Private pr= s->private;
SumDif sD= pr->sD;
Dif df= sD->df;
return df->a;
}
static int getB(Sum s)
{ Private pr= s->private;
SumDif sD= pr->sD;
Dif df= sD->df;
return df->b;
}
static int sumInner(Sum s)
{ Private pr= s->private;
return pr->sD->sum(pr->sD);
}
/* constructor
*/
Sum sum()
{ Sum s= malloc(sizeof(struct sum));
s->delete= delete;
s->setA= setA;
s->setB= setB;
s->getA= getA;
s->getB= getB;
s->sum= sumInner;
s->private= malloc(sizeof(struct private));
Private pr= s->private;
pr->sD= sumDif();
return s;
}
/* /////////////////////////////////////////////////Sum.c
*/
第三,测试
/* testSum.c///////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "Sum.h"
int main()
{ Sum s1= sum();
s1->setA(s1, 4);
s1->setB(s1, 4);
printf("\na: %d, b: %d, sum: %d", s1->getA(s1), s1->getB(s1), s1->sum(s1));
if(s1->delete(s1)== ERR0)
{ s1= NULL;
printf("\n// object type Sum freed //");
}
return ERR0;
}
/* testSum.c////////////////////////////////////////////
*/
/* output
a: 4, b: 4, sum: 8
// object type Sum freed //
*/
我必须做一个重要的说明。编译时,命令将是 'cc testSum.c Sum.c SumDif.c Dif.c'。
如您所见,我们提到了 `Sum` 类型最终派生的所有对象文件定义。`Sum` 派生自 `SumDif`,而 `SumDif` 又派生自 `Dif`。
对象类型 6:多重继承。
现在,我们可以创建一个对象类型 `SumDif2`,它将等于 `SumDif`,但它是通过对 `Sum` 和 `Dif` 对象类型进行多重继承而获得的。这种多重继承将在公共部分实现。
类型声明
/* SumDif2.h/////////////////////////////////////////////////////
*/
#ifndef sUMdIF2
#define sUMdIF2
#include "Sum.h"
#include "Dif.h"
struct sumDif2;
typedef struct sumDif2 *SumDif2;
struct sumDif2
{ Sum sum;
Dif dif;
int (*delete)(SumDif2);
};
SumDif2 sumDif2();
#endif
/* //////////////////////////////////////////////////////SumDif2.h
*/
类型(或类)定义
/* SumDif2.c///////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "SumDif2.h"
static int delete(SumDif2 sD2)
{
if(sD2->sum->delete(sD2->sum)== ERR0)
{ sD2->sum= NULL;
}
if(sD2->dif->delete(sD2->dif)== ERR0)
{ sD2->dif= NULL;
}
free(sD2);
}
SumDif2 sumDif2()
{ SumDif2 sD2= malloc(sizeof(struct sumDif2));
sD2->delete= delete;
sD2->sum= sum();
sD2->dif= dif();
return sD2;
}
/* //////////////////////////////////////SumDif2.c
*/
测试我们的对象
/* testSumDif2.c/////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "SumDif2.h"
int main()
{
SumDif2 sD= sumDif2();
sD->dif->a= 3;
sD->dif->b= 1;
sD->sum->setA(sD->sum, 3);
sD->sum->setB(sD->sum, 1);
printf("\na: %d, b: %d, sum: %d, dif(b- a): %d",
sD->dif->a, sD->dif->b, sD->sum->sum(sD->sum), sD->dif->difference(sD->dif));
if(sD->delete(sD)== ERR0){
sD= NULL;
printf("\n object freed");
}
return ERR0;
}
/* /////////////////////////////////////////testSumDif2.c
*/
/* output
a: 3, b: 1, sum: 4, dif(b- a): -2
*/
编译此命令是 'C:\...>cc Sum.c testSumDif2.c SumDif2.c Dif.c SumDif.c'。
多重继承是
Dif --> SumDif --> Sum --> |
Dif --> |
| --> SumDif2。
对象类型 7:私有部分的重用继承和接口重做
正如我们所见,如果我们想采用这种多重继承,更合理的方式是在私有部分进行。在这种情况下,我们只创建一次接口来设置 a 和 b,而不是像前一个示例那样重复两次(这意义不大)。
类型声明
/* SumDif2Bis.h//////////////////////////////////////////
*/
#ifndef sUMdIF2bIS
#define sUMdIF2bIS
struct sumDif2Bis;
typedef struct sumDif2Bis *SumDif2Bis;
struct sumDif2Bis
{ void *private;
int(*delete)(SumDif2Bis);
int (*setA)(SumDif2Bis, int);
int (*setB)(SumDif2Bis, int);
int (*getA)(SumDif2Bis);
int (*getB)(SumDif2Bis);
int (*bMinusA)(SumDif2Bis);
int (*bPlusA)(SumDif2Bis);
};
SumDif2Bis sumDif2Bis();
#endif
/* ///////////////////////////////////////////SumDif2Bis.h
*/
类型(或类)定义
/* SumDif2Bis.c///////////////////////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "Sum.h"
#include "Dif.h"
#include "SumDif2Bis.h"
struct private;
typedef struct private *Private;
struct private
{ Sum sum;
Dif dif;
};
static int delete(SumDif2Bis sD)
{ Private pr= sD->private;
pr->sum->delete(pr->sum);
pr->dif->delete(pr->dif);
free(pr);
free(sD);
}
static int setA(SumDif2Bis sD, int a)
{ Private pr= sD->private;
pr->sum->setA(pr->sum, a);
pr->dif->a= a;
return ERR0;
}
static int setB(SumDif2Bis sD, int b)
{ Private pr= sD->private;
pr->sum->setB(pr->sum, b);
pr->dif->b= b;
return ERR0;
}
static int getA(SumDif2Bis sD)
{ Private pr= sD->private;
return pr->dif->a;
}
static int getB(SumDif2Bis sD)
{ Private pr= sD->private;
return pr->dif->b;
}
static int bMinusA(SumDif2Bis sD)
{ Private pr= sD->private;
return pr->dif->difference(pr->dif);
}
static int bPlusA(SumDif2Bis sD)
{ Private pr= sD->private;
return pr->sum->sum(pr->sum);
}
SumDif2Bis sumDif2Bis()
{ SumDif2Bis sD= malloc(sizeof(struct sumDif2Bis));
sD->delete= delete;
sD->setA= setA;
sD->setB= setB;
sD->getA= getA;
sD->getB= getB;
sD->bMinusA= bMinusA;
sD->bPlusA= bPlusA;
sD->private= malloc(sizeof(struct private));
Private pr= sD->private;
pr->dif= dif();
pr->sum= sum();
return sD;
}
/* ///////////////////////////////////////////////SumDif2Bis.c
*/
测试
/* testSumDif2Bis.c////////////////////////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "SumDif2Bis.h"
int main()
{
SumDif2Bis sD= sumDif2Bis();
sD->setA(sD, 1);
sD->setB(sD, 4);
printf("\na: %d, b: %d, b- a: %d, b+ a: %d",
sD->getA(sD), sD->getB(sD), sD->bMinusA(sD), sD->bPlusA(sD));
if(sD->delete(sD)== ERR0)
{ sD= NULL;
printf("\nobject freed");
}
return ERR0;
}
/* ////////////////////////////////////////testSumDif2Bis.c
*/
编译:'C:\...>cc testSumDif2Bis.c SumDif2Bis.c Sum.c Dif.c SumDif.c'。
/* output:
a: 1, b: 4, b- a: 3, b+ a: 5
*/
再次稍作休息....
正如我们所见,结论是:在处理多重继承时,最好使用私有的多重继承,并在公共部分重做接口,前提是多重继承共享数据。否则,我们可以直接在公共部分进行。
此外,我们还看到,C 语言的面向对象编程在这项工作中是可行且出色的。唯一需要注意的是,在(作为实例方法时)始终将对象本身作为参数传递,因为我们没有 'this' 指针。
还要记住,在构造函数和析构函数中,释放和实例化指针时存在重复的结构。因此,我们肯定可以定义一个宏来完成这项工作。
当使用长串指针来解引用适当的继承对象时,长串可以一次性设置为一个短串(例如 JavaScript 中的指针),然后在其余代码中使用短串。
最后,我们还有多态(其使用或实现)要向您展示。在这种情况下,由于我们单独进行声明和接口定义,因此会很简单。我们只需要声明一个处理程序接口,然后为每种类型提供特定的定义。在使用时,我们将每种类型的实例关联到它们共享的同一个处理程序类型,从而允许我们实现多态。
到目前为止,我们一直在私有或公共部分使用变量、对象和函数。严格来说并不完全正确。对于对象和变量是这样,但对于函数不是,因为函数在公共部分声明,在私有部分定义。但是,如果我们不在公共部分声明函数,而是在单独的文件中定义它呢?它会自动转换为私有函数。不需要处理程序在私有部分声明来使用它,因为它们定义在同一个单独的文件中,其他函数可以使用它们。因此,为该私有函数在私有结构中定义处理程序意义不大,因为我们已经可以使用它们了,不需要这样的处理程序。处理程序在公共部分是必需的,因为否则将无法访问在单独文件中定义为静态的函数。因此,我认为展示私有函数的使用没有必要。只需在同一个单独的文件中定义它们,并在需要时在同一个文件中使用它们。也将其定义为静态,这样它们的名称就不会与其他文件中的函数名称冲突。
对象类型 8:进入多态的前奏。
因此,多态:为了向您展示多态(其使用),我需要两种共享相同接口的对象类型。为此,我将创建 `Dif` 类型(最初创建的)的对应物,即 `Sum` 对象类型,但具有与 `Dif` 类型相同的结构(接口)。然后,我将创建一个虚拟类型、类或接口,这意味着它将是一个结构类型,用于定义这些其他类型对象(`Dif` 和 `Sum`)的处理程序。这将允许我们实现多态。
类型声明
/* Sum2.h/////////////////////////////
*/
#ifndef sUM2
#define sUM2
struct sum2;
typedef struct sum2 *Sum2;
struct sum2{
int a, b;
int (*delete)(Sum2);
int (*sum)(Sum2);
};
Sum2 sum2();
#endif
/* //////////////////////////////Sum2.h
*/
类型定义
/* Sum2.c///////////////////////////////
*/
#include <stdio.h>
#include <stdlib.h>
#include "constDef.h"
#include "Sum2.h"
static int delete(Sum2 s)
{ free(s);
return ERR0;
}
static int sum(Sum2 s)
{ return s->a+ s->b;
}
Sum2 sum2()
{ Sum2 s= malloc(sizeof(struct sum2));
s->delete= delete;
s->sum= sum;
return s;
}
/* //////////////////////////////Sum2.c
*/
测试
/* testSum2.c//////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "Sum2.h"
int main()
{ Sum2 s= sum2();
s->a= 2;
s->b= 3;
printf("\na: %d, b: %d, a+ b: %d", s->a, s->b, s->sum(s));
if(s->delete(s)== ERR0)
{ s= NULL;
printf("\nobject freed");
}
return ERR0;
}
/* ////////////////////////////testSum2.c
*/
/* output:
a: 2, b: 3, a+ b: 5
object freed
*/
对象类型 9:多态。
现在,在创建了 `Sum2` 对象类型(并对其进行了测试),它是 `Dif` 对象类型的对应物之后,我们声明我们的虚拟接口(但我们不定义它):
/* VOperate.h//////////////////////////////
*/
#ifndef voPERATE
#define voPERATE
struct vOperate;
typedef struct vOperate *VOperate;
struct vOperate
{
int a, b;
int (*delete)(VOperate);
int (*operate)(VOperate);
};
/* there is no constructor
*/
#endif
/* //////////////////////////VOperate.h
*/
最后,我们通过使用刚才定义的(或声明的)虚拟类型以及符合所声明虚拟类型结构的 2 号和 8 号对象类型来测试我们的论断:
/* testVOperate.c/////////////////////
*/
#include <stdio.h>
#include "constDef.h"
#include "Dif.h"
#include "Sum2.h"
#include "VOperate.h"
int main()
{ Sum2 s= sum2();
Dif d= dif();
VOperate vOp[]= {(VOperate)s, (VOperate)d, NULL};
int i;
for(i= 0; vOp[i]; i++)
{ vOp[i]->a= 2;
vOp[i]->b= 4;
printf("\na: %d, b: %d, a op b: %d", vOp[i]->a,
vOp[i]->b, vOp[i]->operate(vOp[i]));
}
for(i= 0; vOp[i]; i++)
{ if(vOp[i]->delete(vOp[i])== ERR0)
{ vOp[i]= NULL;
printf("\nobject freed");
}
}
return ERR0;
}
/* ///////////////////////testVOperate.c
*/
/* output:
a: 2, b: 4, a op b: 6
a: 2, b: 4, a op b: 2
object freed
object freed
*/
您看,我们已经实现了多态的特性(或行为)。甚至不需要方法名称一致,只要求内存空间结构一致(首先是一个 int,然后是另一个 int,然后是一个指针,然后是另一个指针)。当然,指针的实际含义必须一致。也就是说,当解引用第一个指针时,意图是释放而不是操作,等等。
这就是声明结构(接口、类型)一方面,定义另一方面的好处,多态是直接且简单的(直接且简单的就是虚拟类概念)。
最后的休息... 结论与评论
到目前为止,我们已经以直接、简单的方式,从零开始,使用纯 C 语言,将面向对象概念应用于 C 语言。宏的使用可能可用于构造函数和析构函数的用法或定义,因为这在某种程度上是一种自动化的编程过程,但并非严格必需。
这是 C 语言与面向对象编程的其他语言不同的最后一部分。C 语言没有自动将函数定义与其处理程序耦合,我们必须一方面以某种私有、封闭的方式(使用静态概念)定义函数,然后在结构接口中定义处理程序,然后在构造函数中将它们连接起来。不仅如此,在使用指针时,还需要在构造函数中相应地对其进行处理,并在析构函数中释放(如果是其他对象使用的对象)。因此,这基本上是区别所在,即声明结构接口一方面,定义元素另一方面,并将它们连接在一起的过程,这在其他面向对象语言中是自动化的(当您在类定义内部定义函数时,自动会有处理程序可以使用它们(点语法),并且它们会被连接或耦合在一起)。这种分离结构接口和定义的概念也允许最直接地应用虚拟类或类型的概念,在 C 语言中这更直接。
您已经看到,我们如何使用几种对象类型来处理相同类型的概念或操作(在这种情况下是两个数字的加法和减法),并尝试通过组合先前定义的各种对象类型来创建新的对象类型。因此,我们在这里清楚地看到了代码重用或对象类型重用的概念,这是面向对象编程的固有特性。我们可以随时使用已有的任何对象类型,并通过组合所需的其他对象类型来扩展它们的功能,无论是在公共部分还是在私有部分。我们还看到了数据的封装或对象功能的封装和重用(黑盒)。我们还看到了多重继承,通过从(一个以上的)先前对象进行扩展或重用,以及当这种多重继承在不同扩展类型之间共享数据时,在公共部分进行不是一个好主意,而最好在私有部分进行,并为公共部分定义结构。当从不共享数据概念的不同类型的对象继承时,则不需要这一步。
总而言之,这是真正的 C 语言编程和真正的面向对象编程,都是纯粹的、从零开始的。这是我第一次看到 C 语言的面向对象编程以这种方式呈现,而那些声称可以用 C 语言或其他不适合的语言进行面向对象编程的人,也许是因为他们真的不知道如何做。我在这里向您展示。这是可能的。它并不那么难。它有它的好处(允许面向对象的优势在 C 语言中体现)。真正困难的是要知道如何去做。