PS:本文PDF版在这里(格式更好看一些)。最新的源代码请在本页面文末下载,PDF中的链接不是最新的。

用C表达面向对象语言的机制——C#版

我一直认为,面向对象语言是对面向过程语言的封装。如果是这样,那么就应该能够用C来模拟C#的代码风格,写出面向对象形式的代码。本文逐步展示了与C#对应的C代码是如何实现的。

1. 目标

面向对象语言的三大特性(封装、继承、多态)中,封装和继承的C版写法需要研究,而多态似乎不关乎新的写法。

所以本文就展示如何用C来模拟C#的封装、继承、虚方法、关键字as和interface的写法。

2. 封装字段和方法

例如如下的C#代码,写了一个典型的类。

class BaseClass
{
public int baseField1 = ; public void BaseMethod1()
{
Console.WriteLine("BaseClass.BaseMethod1()");
}
}

其典型的使用方式如下。

// create a BaseClass object
BaseClass pBaseClassObj = new BaseClass();
// use BaseClass object's field
Console.Write("BaseClass obj : pBaseClassObj->baseField1({0})\n", pBaseClassObj.baseField1);
// use BaseClass object's method
pBaseClassObj.BaseMethod1();

如果想用C来实现类似的使用方式,应该如何写呢?

1) 用struct代替class

用C的struct代替C#的class的字段

typedef struct _BaseClass
{
int baseField1; } BaseClass;

但是struct没有“在类型里面写方法”这种功能。我们用变通的方法来实现。

void BaseMethod1(BaseClass * pThis)
{
printf("BaseClass.BaseMethod1()\n");
}

就是说,每个方法都加上指向这个结构体的指针。

2) 用New[ClassName]代替new

一个class类型都有至少一个构造方法,是专门给new关键字用的。所以我们写一个C版的new方法。

BaseClass * NewBaseClass()
{
// initialize base class
// alloc for space
BaseClass * pResult = (BaseClass * )malloc(sizeof(BaseClass));
// initialize fields
pResult->baseField1 = ;
// return result
return pResult;
}

C版的New方法用malloc来申请内存空间,这样就把对象new到了堆上,和面向对象语言的处理方式相同。

其使用方法如下。

// create a BaseClass object
BaseClass * pBaseClassObj = NewBaseClass();
// use BaseClass object's field
printf("BaseClass obj : pBaseClassObj->baseField1(%d)\n", pBaseClassObj->baseField1);
// use BaseClass object's method
BaseMethod1(pBaseClassObj);

与C#版的使用方法异曲同工。

3. 实现继承

在上文的BaseClass基础上,来展示如下代码的实现。

class DerivedClass : BaseClass
{
public int derivedField1 = ; public void DerivedMethod1()
{
Console.WriteLine("DerivedClass.DerivedMethod1()");
}
}

3) 用组合代替继承

仍然用struct来代替class,但DerivedClass继承了BaseClass这一性质,如何实现呢?C语言虽然没有“继承”的概念,但是struct可以“组合”(与C#的组合概念一样),我们用一个BaseClass的指针来表示“继承”的概念。

用组合模拟继承的机制typedef struct _DerivedClass
{
// base type
BaseClass * pBase;
// own fields
int derivedField1; } DerivedClass; //DerivedClass * pDerivedClassNULL = NULL; DerivedClass * NewDerivedClass()
{
// initialize base class
BaseClass * pBase = NewBaseClass();
// alloc for space
DerivedClass * pResult = (DerivedClass * )malloc(sizeof(DerivedClass));
pResult->pBase = pBase;
// initialize fields
pResult->derivedField1 = 0;
// return result
return pResult;
} void DerivedMethod1(DerivedClass * pThis)
{
printf("DerivedClass.DerivedMethod1()\n");
}

在C 版的构造函数NewDerivedClass()里,首先构造了一个“基类”BaseClass的对象,然后赋值给DerivedClass对象的pBase指针。这里也符合面向对象语言“先调用父类的构造函数,再调用子类的构造函数”的特点。具体地说,是“首先,子类的构造函数调用父类的构造函数;然后,完成对父类构造函数的调用;最后,完成对子类函数的调用”。

子类的典型使用方式是:

// create a DerivedClass object
DerivedClass pDerivedClassObj = new DerivedClass();
// use DerivedClass object's field
Console.Write("DerivedClass obj : pDerivedClassObj->derivedField1({0})\n", pDerivedClassObj.derivedField1);
// use DerivedClass object's base class' field
Console.Write("DerivedClass obj : pDerivedClassObj->baseField1({0})\n", pDerivedClassObj.baseField1);
// use DerivedClass object's method
pDerivedClassObj.DerivedMethod1();
// use DerivedClass object's base class' method
pDerivedClassObj.BaseMethod1();

C版的DerivedClass使用方式,仍然与此类似。

// use DerivedClass object's field
DerivedClass * pDerivedClassObj = NewDerivedClass();
// use DerivedClass object's field
printf("DerivedClass obj : pDerivedClassObj->derivedField1(%d)\n", pDerivedClassObj->derivedField1);
// use DerivedClass object's base class' field
printf("DerivedClass obj : pDerivedClassObj->baseField1(%d)\n", pDerivedClassObj->pBase->baseField1);
// use DerivedClass object's method
DerivedMethod1(pDerivedClassObj);
// use DerivedClass object's base class' method
BaseMethod1(pDerivedClassObj->pBase);

这里可以看到,“子类”DerivedClass能够调用自身的字段和方法,也能够借助pBase指针调用父类的字段和方法,完全达到了面向对象语言的要求。

4) 用函数指针代替virtual

继承还有一个特性,就是虚方法。C语言如何做到呢?答案就是使用函数指针。

为展示清楚,我们重新定义两个类。

class VirtualClass
{
public virtual void VirtualMethod1()
{
Console.Write("VirtualClass.VirtualMethod1()\n");
}
}
class OverrideClass : VirtualClass
{
public override void VirtualMethod1()
{
Console.Write("OverrideClass.VirtualMethod1()\n");
//base.VirtualMethod1();
}
}

其典型的使用方式如下。

// create a VirtualClass object
VirtualClass virtualClassObj = new VirtualClass();
virtualClassObj.VirtualMethod1();
// create a OverrideClass object
OverrideClass overrideClassObj = new OverrideClass();
overrideClassObj.VirtualMethod1();
// OverrideClass object assigned to VirtualClass object
VirtualClass pVirtualClass = new OverrideClass();
pVirtualClass.VirtualMethod1();

其输出应该是:

VirtualClass.VirtualMethod1()
OverrideClass.VirtualMethod1()
OverrideClass.VirtualMethod1()

虚函数的关键性质,在于父类知道子类的override函数在哪儿,这只能在创建子类的时候,改变父类能够调用的函数。所以很自然就需要函数指针帮忙。按照上文的方法,在New[ClassName]创建父类的时候,让父类对象的函数指针指向父类的virtual方法。等父类对象创建完毕,继续创建子类对象的时候,修改父类对象的函数指针,使其指向子类的override方法。(这就要求父类和virtual方法和子类的override方法的声明完全相同)

其C版代码如下。

VirtualClasstypedef struct _VirtualClass
{
void (* pVirtual1)(_VirtualClass * );
} VirtualClass; void VirtualMethod1_VirtualClass(VirtualClass * pThis); VirtualClass * NewVirtualClass()
{
// initialize base class
// alloc for space
VirtualClass * pResult = (VirtualClass * )malloc(sizeof(VirtualClass));
// initialize fields
// initialize virtual methods
pResult->pVirtual1 = VirtualMethod1_VirtualClass;
// return result
return pResult;
} void VirtualMethod1_VirtualClass(VirtualClass * pThis)
{
printf("VirtualClass.VirtualMethod1()\n");
}

OverrideClasstypedef struct _OverrideClass
{
VirtualClass * pBase;
} OverrideClass; void VirtualMethod1_OverrideClass(VirtualClass * pThis); OverrideClass * NewOverrideClass()
{
// alloc for space
OverrideClass * pResult = (OverrideClass * )malloc(sizeof(OverrideClass));
// initialize base class
pResult->pBase = NewVirtualClass();
// initialize fields
// initialize virtual methods
pResult->pBase->pVirtual1 = VirtualMethod1_OverrideClass;
// return result
return pResult;
} void VirtualMethod1_OverrideClass(VirtualClass * pThis)
{
printf("OverrideClass.VirtualMethod1()\n");
}

C版的使用方法如下。

// create a VirtualClass object
VirtualClass * virtualClassObj = NewVirtualClass();
virtualClassObj->pVirtual1(virtualClassObj);
// create a OverrideClass object
OverrideClass * overrideClassObj = NewOverrideClass();
overrideClassObj->pBase->pVirtual1(overrideClassObj->pBase);
// OverrideClass object assigned to VirtualClass object
VirtualClass * pVirtualClass = NewOverrideClass()->pBase;
pVirtualClass->pVirtual1(pVirtualClass);

其使用方式异曲同工,而输出和C#版是一样的。

如果基类有多个virtual方法,其声明相同,就可以使用函数指针数组;有几种不同声明的virtual方法,基类就要有几个函数指针(或数组)指着他们。

4. 用Convert2Type代替as

5) 使用关键字as

看如下的例子。

class FullClassBase
{
}
class FullClassDerived : FullClassBase
{
public int fcdField1 = ; public void fcdMethod1()
{
Console.Write("FullClassDerived.fcdMethod1()\n");
}
}

其使用方式如下。

FullClassBase baseObj = new FullClassDerived();
FullClassDerived derivedObj = baseObj as FullClassDerived;
if (null != derivedObj)
{
derivedObj.fcdMethod1();
}

上一节展示了在C中,如何用父类的指针指向子类的对象。而这里的C#的关键字“as”将父类的指针还原为子类的指针,这是如何实现的?

答案:as可以用一个函数代替。函数名叫做Convert2Type(随便你喜欢什么名字)。as的本质就是一个函数。

我们要做的是:已知一个对象的指针,已知要转换出来的目标类的类型,求出该对象指向目标类的指针。若不存在,则返回NULL。

我们需要一些准备工作。

6) 准备类型标识结构

1

每个类型都要有一个标识符(用int即可),用以区分不同的类型

2

基类对象要有一个指向子类对象的指针,由于子类可能有多种,显然只能用void *类型

每一个类型都要记录父类指针、子类指针、唯一标识符等内容。另外,Convert2Type函数只能有一个声明,不可能把所有类型的指针都传给他。为了给他尽可能多的数据,我们需要将类型的父类指针、子类指针、唯一标识符等信息封装为一个单独的struct Metadata。具有继承关系的类型,其Metadata对象也相互指向。这样,Metadata就包含了描述全部继承关系的数据。

typedef struct _Metadata Metadatatypedef struct _Metadata
{
void * pThis;
int typeId;
_Metadata * pBaseIdentifier;
_Metadata * pDerivedIdentifier;
LinkNode * pInterfaceList;
} Metadata; //this class object, type id, info struct of base class
Metadata * NewMetadata(
void *pThis, // this class object
int typeId, // type id
Metadata *pBaseId) // info struct of base class
{
Metadata * result = (Metadata *)malloc(sizeof(Metadata)); result->pThis = pThis;
result->typeId = typeId;
result->pBaseIdentifier = pBaseId;
result->pDerivedIdentifier = NULL;
result->pInterfaceList = NewLinkNode(); if (NULL != pBaseId)
{
pBaseId->pDerivedIdentifier = result;
} return result;
}

为方便说明Convert2Type的实现原理,我们再定义C版的FullClassBase和FullClassDerived。

FullClassBase in Ctypedef struct _FullClassBase
{
// basic info
Metadata * metaInfo;
// fields // virtual methods } FullClassBase; // type id
static int FullClassBaseTypeId = 4; // method declarations // the new method
FullClassBase * NewFullClassBase()
{
// alloc for space
FullClassBase * pResult = (FullClassBase *)malloc(sizeof(FullClassBase)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FullClassBaseTypeId,
NULL);
// initialize fields // initialize virtual methods // return result
return pResult;
}

FullClassDerived in Ctypedef struct _FullClassDerived
{
// basic info
Metadata * metaInfo; // fields
int fcdField1; // virtual methods } FullClassDerived; // type id
static int FullClassDerivedTypeId = 5; // method declarations
void fcdMethod1(FullClassDerived * pThis); // the new method
FullClassDerived * NewFullClassDerived()
{
// initialize base class
FullClassBase * pBase = NewFullClassBase(); // alloc for space
FullClassDerived * pResult = (FullClassDerived * )malloc(sizeof(FullClassDerived));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FullClassDerivedTypeId,
pBase->metaInfo); // initialize fields
pResult->fcdField1 = 0; // initialize virtual methods // return result
return pResult;
} void fcdMethod1(FullClassDerived * pThis)
{
printf("FullClassDerived.fcdMethod1()\n");
}

现在,Metadata持有父类、自身、子类的信息,将其作为参数传入Convert2Type函数,是足够找到目标类型的。

7) 实现Convert2Type

Convert2Typevoid * Convert2Type(Metadata * pThisMetaInfo, int targetTypeId)
{
if (NULL == pThisMetaInfo || targetTypeId < 0) { return NULL; } void * result = NULL; if (pThisMetaInfo->typeId == targetTypeId)
{
result = pThisMetaInfo->pThis;
}
else
{
Metadata * pCurrent = pThisMetaInfo; while (NULL == result && NULL != pCurrent->pBaseIdentifier)
{
result = Convert2Type(pCurrent->pBaseIdentifier, targetTypeId);
pCurrent = pCurrent->pBaseIdentifier;
} pCurrent = pThisMetaInfo; while (NULL == result && NULL != pCurrent->pDerivedIdentifier)
{
result = Convert2Type(pCurrent->pDerivedIdentifier, targetTypeId);
pCurrent = pCurrent->pDerivedIdentifier;
}
} return result;
}

C版的使用方式如下。

FullClassBase * baseObj = (FullClassBase *)(NewFullClassDerived()->metaInfo->pBaseIdentifier->pThis);
FullClassDerived * derivedObj = (FullClassDerived *)Convert2Type(baseObj->metaInfo, FullClassDerivedTypeId);
if (NULL != derivedObj)
{
fcdMethod1(derivedObj);
}

关键字“as”通过Convert2Type函数和一个C语言的强制类型转换实现了。

5. 用链表代替interface

C#中的一类可以实现多个interface,这在C中如何表达?

答案是:首先,interface也用struct代替。Interface只不过是一个特殊的类类型,其内部字段都是函数指针。C#的类实现interface,实际上仍然是继承了这个interface类型。这和继承父类类型没有区别。但一个类可以实现多个interface,这就需要用链表来记录这些interface了。

8) 创建链表类型LinkNode

typedef struct _LinkNode LinkNodetypedef struct _LinkNode
{
void * pValue;
_LinkNode * pNext;
} LinkNode; LinkNode * NewLinkNode()
{
LinkNode * result = (LinkNode *)malloc(sizeof(LinkNode)); result->pValue = NULL;
result->pNext = NULL; return result;
}

其中的void * pValue;用于保存这个类型实现的接口的Metadata。

9) 为Metadata添加用于记录interface的链表指针

新的Metadatatypedef struct _Metadata
{
void * pThis;
int typeId;
_Metadata * pBaseIdentifier;
_Metadata * pDerivedIdentifier;
LinkNode * pInterfaceList;
} Metadata; //base class object, this class object, type id, info struct of base class
Metadata * NewMetadata(
void *pThis, // base class object, this class object
int typeId, // type id
Metadata *pBaseId) // info struct of base class
{
Metadata * result = (Metadata *)malloc(sizeof(Metadata)); result->pThis = pThis;
result->typeId = typeId;
result->pBaseIdentifier = pBaseId;
result->pDerivedIdentifier = NULL;
result->pInterfaceList = NewLinkNode(); if (NULL != pBaseId)
{
pBaseId->pDerivedIdentifier = result;
} return result;
}

10) 修改Convert2Type函数

新的Convert2Type函数void * Convert2Type(Metadata * pThisMetaInfo, int targetTypeId)
{
if (NULL == pThisMetaInfo || targetTypeId < 0) { return NULL; } void * result = NULL; if (pThisMetaInfo->typeId == targetTypeId) // this type is target type?
{
result = pThisMetaInfo->pThis;
}
else // some interface type of this object is target type?
{
LinkNode * pCurrent = pThisMetaInfo->pInterfaceList;
while (NULL == result && NULL != pCurrent)
{
void * pValue = pCurrent->pValue;
if (NULL != pValue)
{
Metadata * pInterface = (Metadata *)pValue;
if (pInterface->typeId == targetTypeId)
{
result = pInterface->pThis;
break;
}
}
pCurrent = pCurrent->pNext;
}
} if (NULL == result) // look into its derived type and base type
{
Metadata * pCurrent = pThisMetaInfo; while (NULL == result && NULL != pCurrent->pDerivedIdentifier)
{
result = Convert2Type(pCurrent->pDerivedIdentifier, targetTypeId);
pCurrent = pCurrent->pDerivedIdentifier;
} pCurrent = pThisMetaInfo; while (NULL == result && NULL != pCurrent->pBaseIdentifier)
{
result = Convert2Type(pCurrent->pBaseIdentifier, targetTypeId);
pCurrent = pCurrent->pBaseIdentifier;
}
} return result;
}

11) 验证

下面就来验证一下。

我们创建一个interface类型,让FullClassBase实现他。

interface InterfaceClass
{
void Method4InterfaceClass();
}
class FullClassBase : InterfaceClass
{
public void Method4InterfaceClass()
{
Console.Write("FullClassBase.Method4InterfaceClass()\n");
}
}

对应的C代码如下。

typedef struct _InterfaceClass
{
Metadata * metaInfo;
void (*pInterfaceMethod1)(_InterfaceClass *);
} InterfaceClass; static int InterfaceClassTypeId = ; InterfaceClass * NewInterfaceClass()
{
InterfaceClass * result = (InterfaceClass *)malloc(sizeof(InterfaceClass)); result->metaInfo = NewMetadata(result, InterfaceClassTypeId, NULL);
result->pInterfaceMethod1 = NULL; return result;
}
typedef struct _FullClassBase
{
// basic info
Metadata * metaInfo;
// fields // virtual methods } FullClassBase; // type id
static int FullClassBaseTypeId = ; // method declarations
void Method4InterfaceClass(InterfaceClass * pInterfaceClass); // the new method
FullClassBase * NewFullClassBase()
{
// alloc for space
FullClassBase * pResult = (FullClassBase *)malloc(sizeof(FullClassBase)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FullClassBaseTypeId,
NULL);
InterfaceClass * interfacePart = NewInterfaceClass();
interfacePart->metaInfo->pDerivedIdentifier = pResult->metaInfo;
interfacePart->pInterfaceMethod1 = Method4InterfaceClass;
LinkNode * nextNode = NewLinkNode();
nextNode->pValue = interfacePart->metaInfo;
pResult->metaInfo->pInterfaceList->pNext = nextNode;
// initialize fields // initialize virtual methods // return result
return pResult;
} void Method4InterfaceClass(InterfaceClass * pInterfaceClass)
{
printf("FullClassBase.Method4InterfaceClass()\n");
}

C#版的使用方式如下。

FullClassBase obj = new FullClassBase();
obj.Method4InterfaceClass();
InterfaceClass interfaceObj = obj as InterfaceClass;
interfaceObj.Method4InterfaceClass();

对应的C版代码的使用方式如下。

FullClassBase * obj = NewFullClassBase();
Method4InterfaceClass((InterfaceClass *)Convert2Type(obj->metaInfo, InterfaceClassTypeId));
InterfaceClass * interfaceObj = (InterfaceClass *)Convert2Type(obj->metaInfo, InterfaceClassTypeId);
Method4InterfaceClass(interfaceObj);

12) 关于interface的性质

有了interface以后,实际上就是出现了“多继承”。

一个接口可以被多个类型继承,所以C版代码里,interface里的函数指针类型的第一个参数只能是interface本身。即:一个类型A,实现了interface X里的方法,就意味着这个方法的第一个参数类型变成了X的指针(而不再是A的指针)。

基于这样的C版设计,在调用接口函数时,一定会发生类型转换(调用Convert2Type函数)。

所以,如果在interface里的方法,需要用this指针的时候,会发生从X的指针到A的指针的类型转换。

6. 虚函数与接口与多次继承

如果接口的方法在基类里标记为virtual,并且在子类和孙类里都override了,会怎么样?

虚函数与接口与多次继承    interface VirtualAndInterfaceInterface
{
void InterfaceMethod();
} class VirtualAndInterfaceBase : VirtualAndInterfaceInterface
{
public virtual void InterfaceMethod()
{
Console.WriteLine("VirtualAndInterfaceBase.InterfaceMethod()");
}
} class VirtualAndInterfaceDerived : VirtualAndInterfaceBase
{
public override void InterfaceMethod()
{
Console.WriteLine("VirtualAndInterfaceDerived.InterfaceMethod()");
}
} class VirtualAndInterfaceDerived2 : VirtualAndInterfaceDerived
{
public override void InterfaceMethod()
{
Console.WriteLine("VirtualAndInterfaceDerived2.InterfaceMethod()");
}
}

对于这样的情况,下面的情形会输出什么?

VirtualAndInterfaceDerived2 obj = new VirtualAndInterfaceDerived2();
VirtualAndInterfaceBase baseObj = obj;
VirtualAndInterfaceInterface interfaceObj = obj;
obj.InterfaceMethod();
baseObj.InterfaceMethod();
interfaceObj.InterfaceMethod();

答案是三行“VirtualAndInterfaceDerived2.InterfaceMethod()”,即全部调用了最后override的方法。就是说,转换出来的C代码中,除了基类的virtual方法的函数指针外,接口对象的函数指针也要修改为指向最后override的函数。

这又带来一个问题。如果我们定义一个新的接口。

interface VirtualAndInterfaceInterface2
{
void InterfaceMethod();
}

这个新接口和VirtualAndInterfaceInterface所拥有的方法声明相同。现在让VirtualAndInterfaceDerived2实现这个接口。

class VirtualAndInterfaceDerived2 : VirtualAndInterfaceDerived, VirtualAndInterfaceInterface2
{
public override void InterfaceMethod()
{
Console.WriteLine("VirtualAndInterfaceDerived2.InterfaceMethod()");
}
}

我们发现无需其他改动即可编译。

根据之前的结论,在C版代码中,接口类型的函数指针的第一个参数类型只能是接口类型本身。这也使得class类型的C版类型中,其函数类型的第一个参数只能是接口类型。但是在这个例子里,InterfaceMethod方法同时成为VirtualAndInterfaceInterface和VirtualAndInterfaceInterface2的方法。那么InterfaceMethod的第一个参数类型就无从选择了。

为解决这个问题,我们重新审视接口的定义。在C版代码中,InterfaceMethod第一个参数类型,就应该是接口本身,这无法改变。实现了此接口的类类型,应该单独为InterfaceMethod添加一个声明和InterfaceMethod相同的函数,以满足该接口的需要。在类类型里的InterfaceMethod,借助Convert2Type函数,直接调用自己内部的InterfaceMethod函数。

C版的代码如下。

VirtualAndInterfaceInterfacetypedef struct _VirtualAndInterfaceInterface
{
Metadata * metaInfo;
void (*pInterfaceMethod)(_VirtualAndInterfaceInterface *);
} VirtualAndInterfaceInterface; static int VirtualAndInterfaceInterfaceTypeId = 8; VirtualAndInterfaceInterface * NewVirtualAndInterfaceInterface()
{
VirtualAndInterfaceInterface * result = (VirtualAndInterfaceInterface *)malloc(sizeof(VirtualAndInterfaceInterface)); result->metaInfo = NewMetadata(result, VirtualAndInterfaceInterfaceTypeId, NULL);
result->pInterfaceMethod = NULL; return result;
}

VirtualAndInterfaceInterface2typedef struct _VirtualAndInterfaceInterface2
{
Metadata * metaInfo;
void (*pInterfaceMethod)(_VirtualAndInterfaceInterface2 *);
} VirtualAndInterfaceInterface2; static int VirtualAndInterfaceInterface2TypeId = 10; VirtualAndInterfaceInterface2 * NewVirtualAndInterfaceInterface2()
{
VirtualAndInterfaceInterface2 * result = (VirtualAndInterfaceInterface2 *)malloc(sizeof(VirtualAndInterfaceInterface2)); result->metaInfo = NewMetadata(result, VirtualAndInterfaceInterface2TypeId, NULL);
result->pInterfaceMethod = NULL; return result;
}

VirtualAndInterfaceBasetypedef struct _VirtualAndInterfaceBase
{
// basic info
Metadata * metaInfo;
// fields // virtual methods
void (* pInterfaceMethod)(_VirtualAndInterfaceBase *); } VirtualAndInterfaceBase; // type id
static int VirtualAndInterfaceBaseTypeId = 7; // method declarations
void InterfaceMethod_VirtualAndInterfaceInterface(VirtualAndInterfaceInterface * pThis);
void InterfaceMethod_VirtualAndInterfaceBase(VirtualAndInterfaceBase * pThis); // the new method
VirtualAndInterfaceBase * NewVirtualAndInterfaceBase()
{
// initialize base class and interfaces
VirtualAndInterfaceInterface * pBaseVirtualAndInterfaceInterface = NewVirtualAndInterfaceInterface(); // alloc for space
VirtualAndInterfaceBase * pResult = (VirtualAndInterfaceBase *)malloc(sizeof(VirtualAndInterfaceBase)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
VirtualAndInterfaceBaseTypeId,
NULL);
LinkNode * nextNode = NewLinkNode();
nextNode->pValue = pBaseVirtualAndInterfaceInterface->metaInfo;
pResult->metaInfo->pInterfaceList->pNext = nextNode;
pBaseVirtualAndInterfaceInterface->metaInfo->pDerivedIdentifier = pResult->metaInfo; pBaseVirtualAndInterfaceInterface->pInterfaceMethod = InterfaceMethod_VirtualAndInterfaceInterface; // initialize fields // initialize virtual methods
pResult->pInterfaceMethod = InterfaceMethod_VirtualAndInterfaceBase; // return result
return pResult;
} void InterfaceMethod_VirtualAndInterfaceInterface(VirtualAndInterfaceInterface * pThis)
{
VirtualAndInterfaceBase * pBase = (VirtualAndInterfaceBase *)Convert2Type(
pThis->metaInfo, VirtualAndInterfaceBaseTypeId);
pBase->pInterfaceMethod(pBase);
} void InterfaceMethod_VirtualAndInterfaceBase(VirtualAndInterfaceBase * pThis)
{
printf("VirtualAndInterfaceBase.InterfaceMethod()\n");
}

VirtualAndInterfaceDerivedtypedef struct _VirtualAndInterfaceDerived
{
// basic info
Metadata * metaInfo;
// fields // virtual methods } VirtualAndInterfaceDerived; // type id
static int VirtualAndInterfaceDerivedTypeId = 9; // method declarations
void InterfaceMethod_VirtualAndInterfaceDerived(VirtualAndInterfaceDerived * pThis); // the new method
VirtualAndInterfaceDerived * NewVirtualAndInterfaceDerived()
{
// initialize base class and interfaces
VirtualAndInterfaceBase * pBase = NewVirtualAndInterfaceBase(); // alloc for space
VirtualAndInterfaceDerived * pResult = (VirtualAndInterfaceDerived *)malloc(sizeof(VirtualAndInterfaceDerived)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
VirtualAndInterfaceDerivedTypeId,
pBase->metaInfo); // initialize fields // initialize virtual methods
pBase->pInterfaceMethod = InterfaceMethod_VirtualAndInterfaceDerived; // return result
return pResult;
} void InterfaceMethod_VirtualAndInterfaceDerived(VirtualAndInterfaceBase * pThis)
{
printf("VirtualAndInterfaceDerived.InterfaceMethod()\n");
}

VirtualAndInterfaceDerived2typedef struct _VirtualAndInterfaceDerived2
{
// basic info
Metadata * metaInfo;
// fields // virtual methods } VirtualAndInterfaceDerived2; // type id
static int VirtualAndInterfaceDerived2TypeId = 9; // method declarations
void InterfaceMethod_VirtualAndInterfaceInterface2(VirtualAndInterfaceInterface2 *pThis);
void InterfaceMethod_VirtualAndInterfaceDerived2(VirtualAndInterfaceDerived2 * pThis); // the new method
VirtualAndInterfaceDerived2 * NewVirtualAndInterfaceDerived2()
{
// initialize base class and interfaces
VirtualAndInterfaceDerived * pBase = NewVirtualAndInterfaceDerived();
VirtualAndInterfaceInterface2 *pBaseVirtualAndInterfaceInterface2 = NewVirtualAndInterfaceInterface2(); // alloc for space
VirtualAndInterfaceDerived2 * pResult = (VirtualAndInterfaceDerived2 *)malloc(sizeof(VirtualAndInterfaceDerived2)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
VirtualAndInterfaceDerived2TypeId,
pBase->metaInfo);
LinkNode * nextNode = NewLinkNode();
nextNode->pValue = pBaseVirtualAndInterfaceInterface2->metaInfo;
pResult->metaInfo->pInterfaceList->pNext = nextNode;
pBaseVirtualAndInterfaceInterface2->metaInfo->pDerivedIdentifier = pResult->metaInfo; pBaseVirtualAndInterfaceInterface2->pInterfaceMethod = InterfaceMethod_VirtualAndInterfaceInterface2; // initialize fields // initialize virtual methods
VirtualAndInterfaceBase * pVirtualAndInterfaceBase = (VirtualAndInterfaceBase *)(pBase->metaInfo->pBaseIdentifier->pThis);
pVirtualAndInterfaceBase->pInterfaceMethod = InterfaceMethod_VirtualAndInterfaceDerived2; // return result
return pResult;
} void InterfaceMethod_VirtualAndInterfaceInterface2(VirtualAndInterfaceInterface2 *pThis)
{
VirtualAndInterfaceBase * pBase = (VirtualAndInterfaceBase *)Convert2Type(
pThis->metaInfo, VirtualAndInterfaceBaseTypeId);
pBase->pInterfaceMethod(pBase);
} void InterfaceMethod_VirtualAndInterfaceDerived2(VirtualAndInterfaceBase * pThis)
{
printf("VirtualAndInterfaceDerived2.InterfaceMethod()\n");
}

简单来说,类型C实现了接口IC,那么,C就要为IC的各个函数分别创建声明完全相同的函数,这些函数通过Convert2Type得到需要的类型指针,再调用C内部的函数。

7. public、protected和private

这三个关键字用C是无法实现的,他们是面向对象语言在编译器进行语义分析时进行处理的。如果不符合规定(例如类型外调用了private的字段),编译器就报错,不给你生成代码。仅此而已。

8. 结论

我们规定:

CA表示基类,CA实现的接口为ICA1,ICA2,。。。
CB继承自CA,CB实现的接口为ICB1,ICB2,。。。
CC继承自CC,CC实现的接口为ICC1,ICC2,。。。
某类型X包含的字段为XF1,XF2,。。。
某类型X包含的方法为XM1,XM2,。。。

那么,可以用如下规则将C#代码翻译为C代码。

类型X用struct代替,X中的字段用相应的字段代替;X中的方法用以X的指针为第一个参数的函数代替。

为X添加一个Metadata结构体的指针,用于记录X的实例的信息:基类实例、子类实例、接口实例(链表)、类型编号、实例本身。

为Metadata编写Convert2Type函数,返回给定实例关联的目标类型的实例。

为X分配一个唯一的整数作为类型编号。

为X编写NewX函数,返回X的实例。

CB的NewCB函数,

先创建CA的实例,

然后创建ICB1、ICB2。。。的实例,

然后创建CB的实例,

然后修改CA实例的子类实例指针(指向CB实例),

然后修改ICB1、ICB2。。。的函数指针(指向CB的函数),

然后修改CA中virtual方法的函数指针(指向CB的函数),若此virtual方法也是ICAn的方法,也要修改ICAn的函数指针(指向CB的函数)。

最后强调一下容易混淆的地方。

CA中的virtual方法,在CB中可以override掉,然后还可以继续在CC中override掉。(而不是不可以继续override)其函数指针指向最后override的方法。

CA中的virtual方法,如果恰好也是ICA1中的方法,则ICA1中的函数指针也要指向最后override的方法。(而不是CA中的virtual方法)

2016-05-15

感想

至此,本文展示了面向对象语言中的class、new、virtual/override、as、interface等关键字的实现机制,展示了将C#翻译为C的方法。

很早就在想,面向对象语言到底是如何实现的。封装还简单,用struct代替class即可。继承的虚函数特性,只听过是通过“晚绑定”实现的,然后就找不到其他资料了。这几天趁国庆假期好好想了想,边想边做,用C实现了面向对象的语言特性,也证实了“面向对象语言是对面向过程语言的封装”这句话。

现在有了把面向对象语言的代码翻译为面向过程语言的代码的途径。这让我开始反思,为什么要把C封装为面向对象语言?面向对象语言是如何从无到有的?最开始的那个人是怎么设计出这样一套机制的?他之前没有面向对象的任何概念,他的思路是什么?

最后贴上自己总结的一段话。

机器语言(01串)是对数字电路的计算和控制逻辑的封装,人可以用打孔纸带来控制计算机。汇编语言(指令代码)是对机器语言的封装,人可以用易于理解、记忆和维护的名称来(间接)写机器语言。面向过程语言(例如C)是对汇编语言的封装,人可以用模块化的设计思路编写代码。面向对象语言(例如C#)是对面向过程语言的封装,人可以用模拟现实世界的思路编写代码。

点此下载源代码

用C表达面向对象语言的机制——C#版的更多相关文章

  1. 用C表达面向对象语言的机制2——颠覆你对方法调用的看法!

    用C表达面向对象语言的机制2——颠覆你对方法调用的看法! 源代码在文末.推荐阅读本文PDF版,格式更好看. 在上一篇<用C表达面向对象语言的机制——C#版>中,我们获知了如何用C表达面向对 ...

  2. go 学习笔记之go是不是面向对象语言是否支持面对对象编程?

    面向对象编程风格深受广大开发者喜欢,尤其是以 C++, Java 为典型代表的编程语言大行其道,十分流行! 有意思的是这两中语言几乎毫无意外都来源于 C 语言,却不同于 C 的面向过程编程,这种面向对 ...

  3. C#学习-面向对象语言都有类

    面向对象语言的一个基本特征是它们都有类,类是C#(这类语言)中的一种复杂数据类型. 类代表一组具有公共属性和行为的对象. 在C#中定义一个类是非常简单的,只需使用class关键字并按格式来定义即可. ...

  4. javascript是一种面向对象语言吗?如果是,您在javascript中是如何实现继承的呢

    ·oop(面向对象程序设计)中最常用到的概念有 1.对象,属性,方法 1>(对象:具体事物或抽象事物,名词) 2>(属性:对象的特征,特点,形容词) 3>(方法:对象的动作,动词) ...

  5. java反射并不是什么高深技术,面向对象语言都有这个功能,而且功能也很简单,就是利用jvm动态加载时生成的class对象

    java反射并不是什么高深技术,面向对象语言都有这个功能. 面向对象语言都有这个功能,而且功能也很简单,就是利用jvm动态加载时生成的class对象,去获取类相关的信息 2.利用java反射可以调用类 ...

  6. Java环境变量,jdk和jre的区别,面向对象语言编程

    什么是java? java是一门面向对象的编程语言,包括java SE, java ME, Java EE . 广泛使用的是作为后端语言的Java EE开发, 面向对象和面向过程? java,C++ ...

  7. Scala初探:新潮的函数式面向对象语言

    Scala的基本概念 先讲讲Scala里头几个概念Classes, Traits, Objects and Packages. Class和Java中的很像,只不过Scala中Class不能有stat ...

  8. oop(面向对象语言的三大特征):封装,继承,多态; (抽象),函数绑定

    封装/隐藏 : 通过类的访问限定符实现的   private    public 继承的意义之一:代码的复用 类的继承是指在一个现有类的基础上去构建一个新的类,构造出来的新类被称为派生类(子类),现有 ...

  9. 【Go语言入门系列】(八)Go语言是不是面向对象语言?

    [Go语言入门系列]前面的文章: [Go语言入门系列](五)指针和结构体的使用 [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? 1. Go是面向对象的语言吗? 在[ ...

随机推荐

  1. 消息队列 Kafka 的基本知识及 .NET Core 客户端

    前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ...

  2. java字符乱码

    在java中处理字符时,经常会发生乱码,而主要出现的地方在读取文本文件时发生,或者是写入到文件中,在其他地方打开乱码. 如下例子: BufferedReader br = null; try { br ...

  3. redux学习

    redux学习: 1.应用只有一个store,用于保存整个应用的所有的状态数据信息,即state,一个state对应一个页面的所需信息 注意:他只负责保存state,接收action, 从store. ...

  4. Base64编码

    Base64编码 写在前面 今天在做一个Android app时遇到了一个问题:Android端采用ASE对称加密的数据在JavaWeb(jre1.8.0_7)后台解密时,居然解密失败了!经过测试后发 ...

  5. UWP开发之Mvvmlight实践六:MissingMetadataException解决办法(.Net Native下Default.rd.xml配置问题)

    最近完成一款UWP应用,在手机端测试发布版(Release)的时候应用莫名奇妙的强行关闭,而同样的应用包在PC端一点问题都没有,而且Debug版在两个平台都没有问题,唯独手机的Release版有问题. ...

  6. [转]Patch文件结构详解

    N久不来 于是不知道扔在哪儿于是放这里先 如果你觉得碍事的话 帮我扔到合适的版块去.. 导读这是一篇说明文 它介绍了标准冒险岛更新文件(*.patch;*.exe)的格式文章的最后附了一段C#的参考代 ...

  7. C#——传值参数(2)

    //我的C#是跟着猛哥(刘铁猛)(算是我的正式老师)<C#语言入门详解>学习的,微信上猛哥也给我讲解了一些不懂得地方,对于我来说简直是一笔巨额财富,难得良师! 这次与大家共同学习C#中的 ...

  8. ASP.Net MVC——使用 ITextSharp 完美解决HTML转PDF(中文也可以)

    前言: 最近在做老师交代的一个在线写实验报告的小项目中,有这么个需求:把学生提交的实验报告(HTML形式)直接转成PDF,方便下载和打印. 以前都是直接用rdlc报表实现的,可这次牵扯到图片,并且更为 ...

  9. SAP CRM 用户界面对象类型和设计对象

    在CRM中的用户界面对象类型的帮助下,我们可以做这些工作: 进行不同的视图配置 创建动态导航 从设计层控制字段标签.值帮助 控制BOL对象的属性的可视性 从导航栏访问自定义组件 一个用户界面对象类型之 ...

  10. Jenkins的一个bug-同时build一个项目两次导致失败

    我们有一个job A, A只是配置了一些参数,它会去触发模板job B. 我一开始点击构建A, 马上发现参数配置不对,于是撤消了构建,但是我没有发现B已经被触发,我重新配置参数,然后再次构建A,这个时 ...