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

源代码在文末。推荐阅读本文PDF版,格式更好看。

在上一篇《用C表达面向对象语言的机制——C#版》中,我们获知了如何用C表达面向对象语言的机制,证明了面向对象语言是对面向过程语言的封装。今天有幸看到《颠覆你对方法调用的看法!》,于是继续用C来模拟此文中的代码,看看“颠覆”的背后是什么。

1. 目标

本文展示用C的union来模拟C#的一些代码的写法。

2. 用union代替FieldOffset

例如如下的C#代码。

Manager    class Derived : Base
{
int d = 11;
public new void Print()
{
Console.WriteLine("in derived ({0})", this.d);
}
} [StructLayout(LayoutKind.Explicit)]
class Manager
{
[FieldOffset(0)]
public Base b = new Base();
[FieldOffset(0)]
public Derived derived;
}

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

1) 用union代替FieldOffset

用C的union代替FieldOffset等属性。

FrancisYoungBasetypedef struct _FrancisYoungBase
{
// basic info
Metadata * metaInfo;
// fields
int b;
// virtual methods } FrancisYoungBase; // type id
static int FrancisYoungBaseTypeId = 11; // method declarations // the new method
FrancisYoungBase * NewFrancisYoungBase()
{
// alloc for space
FrancisYoungBase * pResult = (FrancisYoungBase *)malloc(sizeof(FrancisYoungBase)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungBaseTypeId,
NULL);
// initialize fields
pResult->b = 10; // initialize virtual methods // return result
return pResult;
} void Print4FrancisYoungBase(FrancisYoungBase * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ } printf("in base (%d)\n", pThis->b);
}

FrancisYoungDerivedtypedef struct _FrancisYoungDerived
{
// basic info
Metadata * metaInfo; // fields
int d;
// virtual methods } FrancisYoungDerived; // type id
static int FrancisYoungDerivedTypeId = 12; // method declarations
void Print4FrancisYoungDerived(FrancisYoungDerived * pThis); // the new method
FrancisYoungDerived * NewFrancisYoungDerived()
{
// initialize base class
FrancisYoungBase * pBase = NewFrancisYoungBase(); // alloc for space
FrancisYoungDerived * pResult = (FrancisYoungDerived * )malloc(sizeof(FrancisYoungDerived));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungDerivedTypeId,
pBase->metaInfo); // initialize fields
pResult->d = 11; // initialize virtual methods // return result
return pResult;
} void Print4FrancisYoungDerived(FrancisYoungDerived * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ } printf("in derived (%d)\n", pThis->d);
}

FrancisYoungManagertypedef struct _FrancisYoungManager
{
Metadata * metaInfo;
union
{
FrancisYoungBase * pBase;
FrancisYoungDerived * pDerived;
} obj;
} FrancisYoungManager; // type id
static int FrancisYoungManagerTypeId = 13; // method declarations // the new method
FrancisYoungManager * NewFrancisYoungManager()
{
// initialize base class // alloc for space
FrancisYoungManager * pResult = (FrancisYoungManager * )malloc(sizeof(FrancisYoungManager));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungManagerTypeId,
NULL); // initialize fields
pResult->obj.pBase = NewFrancisYoungBase(); // initialize virtual methods // return result
return pResult;
}

就是说,union实现了将父类和子类指针都指向父类的实例的功能。

2) 使用Manager类

C#版的Manager,其典型的使用方式如下。

Manager m = new Manager();
m.derived.Print();// In Derived

对应的C代码是什么样的?

FrancisYoungManager * m = NewFrancisYoungManager();
Print4FrancisYoungDerived(m->obj.pDerived);/* In Derived */

与C#版的使用方法异曲同工。
从C版代码可以看到,编译器根据pDerived的类型,选择了调用pDerived的类型中的方法。观察对C#转换为C的代码,可以发现,本质上同名的Print方法,对编译器而言的代号是不同的,所以编译器根据pDerived的类型,可以立即确定调用哪个方法。

3. 变为虚函数之后

下面展示将上文中的函数变为虚函数后的处理。

C#代码如下。

FrancisYoungManagerVirtual    [StructLayout(LayoutKind.Explicit)]
class ManagerVirtual
{
[FieldOffset(0)]
public BaseVirtual b = new BaseVirtual(); [FieldOffset(0)]
public DerivedVirtual derived;
} class BaseVirtual
{
int b = 12;
public virtual void Print()
{
Console.WriteLine("in base ({0})", this.b);
}
} class DerivedVirtual : BaseVirtual
{
int d = 13;
public override void Print()
{
Console.WriteLine("in derived ({0})", this.d);
}
}

3) 用函数指针代替虚函数

仍然用上一篇文章的方法,用函数指针来实现虚函数的功能。

FrancisYoungBaseVirtualtypedef struct _FrancisYoungBaseVirtual
{
// basic info
Metadata * metaInfo;
// fields
int b;
// virtual methods
void (* pPrint4FrancisYoungBaseVirtual)(_FrancisYoungBaseVirtual *); } FrancisYoungBaseVirtual; // type id
static int FrancisYoungBaseVirtualTypeId = 14; // method declarations
void Print4FrancisYoungBaseVirtual(FrancisYoungBaseVirtual * pThis); // the new method
FrancisYoungBaseVirtual * NewFrancisYoungBaseVirtual()
{
// alloc for space
FrancisYoungBaseVirtual * pResult = (FrancisYoungBaseVirtual *)malloc(sizeof(FrancisYoungBaseVirtual)); // initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungBaseVirtualTypeId,
NULL);
// initialize fields
pResult->b = 12; // initialize virtual methods
pResult->pPrint4FrancisYoungBaseVirtual = Print4FrancisYoungBaseVirtual; // return result
return pResult;
} void Print4FrancisYoungBaseVirtual(FrancisYoungBaseVirtual * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ } printf("in base (%d)\n", pThis->b);
}

FrancisYoungDerivedVirtualtypedef struct _FrancisYoungDerivedVirtual
{
// basic info
Metadata * metaInfo; // fields
int d;
// virtual methods } FrancisYoungDerivedVirtual; // type id
static int FrancisYoungDerivedVirtualTypeId = 15; // method declarations
void Print4FrancisYoungDerivedVirtual(FrancisYoungBaseVirtual * pThis); // the new method
FrancisYoungDerivedVirtual * NewFrancisYoungDerivedVirtual()
{
// initialize base class
FrancisYoungBaseVirtual * pBase = NewFrancisYoungBaseVirtual(); // alloc for space
FrancisYoungDerivedVirtual * pResult = (FrancisYoungDerivedVirtual * )malloc(sizeof(FrancisYoungDerivedVirtual));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungDerivedVirtualTypeId,
pBase->metaInfo); // initialize fields
pResult->d = 13; // initialize virtual methods
pBase->pPrint4FrancisYoungBaseVirtual = Print4FrancisYoungDerivedVirtual;
// return result
return pResult;
} void Print4FrancisYoungDerivedVirtual(FrancisYoungBaseVirtual * pThis)
{
if (pThis == NULL) { return;/* throw exception in C# */ }
FrancisYoungDerivedVirtual * thisObj = (FrancisYoungDerivedVirtual*)Convert2Type(
pThis->metaInfo, FrancisYoungDerivedVirtualTypeId);
if (thisObj == NULL) { return;/* throw exception in C# */ } printf("in derived %d\n", thisObj->d);
}

FrancisYoungManagerVirtualtypedef struct _FrancisYoungManagerVirtual
{
Metadata * metaInfo;
union
{
FrancisYoungBaseVirtual * pBase;
FrancisYoungDerivedVirtual * pDerived;
} obj;
} FrancisYoungManagerVirtual; // type id
static int FrancisYoungManagerVirtualTypeId = 16; // method declarations // the new method
FrancisYoungManagerVirtual * NewFrancisYoungManagerVirtual()
{
// initialize base class // alloc for space
FrancisYoungManagerVirtual * pResult = (FrancisYoungManagerVirtual * )malloc(sizeof(FrancisYoungManagerVirtual));
// initialize basic info
pResult->metaInfo = NewMetadata(
pResult,
FrancisYoungManagerVirtualTypeId,
NULL); // initialize fields
pResult->obj.pBase = NewFrancisYoungBaseVirtual(); // initialize virtual methods // return result
return pResult;
}

4) 使用ManagerVirtual类

C#代码的调用方式如下。

ManagerVirtual m = new ManagerVirtual();
m.derived.Print();// In Base

对应的C代码如下。

FrancisYoungManagerVirtual * m = NewFrancisYoungManagerVirtual();
FrancisYoungBaseVirtual * pBase = (FrancisYoungBaseVirtual*)Convert2Type(m->obj.pDerived->metaInfo, FrancisYoungBaseVirtualTypeId);
pBase->pPrint4FrancisYoungBaseVirtual(pBase);

仍然与之神似。

我们知道,虚方法在C代码中,变成了一个函数指针。根据上一篇文章的分析,我们在这里创建的是父类的对象,所以Print的函数指针指向的是父类的Print方法,即C版代码中的Print4FrancisYoungBaseVirtual方法。所以derived调用的只能是父类的Print4FrancisYoungBaseVirtual方法。

4. 结论

对《颠覆你对方法调用的看法!》的分析结果和原作者是一样的,这也印证了我们上一篇文章的结论的正确性。

感想

上一篇文章我提到,为什么要把C封装为面向对象语言?面向对象语言是如何从无到有的?最开始的那个人是怎么设计出这样一套机制的?他之前没有面向对象的任何概念,他的思路是什么?

通过本文的分析,我的感觉是,面向对象语言的创始人一定是在大量的编写面向过程语言代码的时候,逐渐感受到了现实世界和程序语言的严重脱节,也发现了两者之间可能靠近一些的途径。一个函数写出来,如果几乎不可能被其他人用在其他地方,那应该把它隐藏起来,这就是封装的思想。

而继承的思想是如何产生的,我还是不得而知。

源代码在这里

用C表达面向对象语言的机制2——颠覆你对方法调用的看法!的更多相关文章

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

    PS:本文PDF版在这里(格式更好看一些).最新的源代码请在本页面文末下载,PDF中的链接不是最新的. 用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类加载机制及方法调用

    类加载机制 概述 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resoluti ...

  7. JVM垃圾回收机制总结:调优方法

    转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍

  8. python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样

    python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样

  9. Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用

    在上一篇文章Android IPC机制(二)用Messenger进行进程间通信中我们介绍了使用Messenger来进行进程间通信的方法.可是我们能发现Messenger是以串行的方式来处理client ...

随机推荐

  1. ASP.NET MVC分页组件MvcPager 2.0版发布暨网站全新改版

    MvcPager分页控件是在ASP.NET MVC Web应用程序中实现分页功能的一系列扩展方法,该分页控件的最初的实现方法借鉴了网上流行的部分源代码, 尤其是ScottGu的PagedList< ...

  2. html5新特性之画布

    1.canvas的理解 canvas是一个矩形区域,在这个区域内,通过js可以对区域内的每一帧像素控制 2.js操作canvas对象 canvas对象.getContext("2d" ...

  3. OS 系统下安装MySql 配置MySql环境变量

    学习Hive需要,闲话不说 本文的内容: 下载Mysql for Mac 下载Mysql Workbench 安装 Mysql 和 Mysql Workbench 配置Mysql在OS 系统上的环境变 ...

  4. android.util.TypedValue.applyDimension

    先看一个例子: int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, context.getResourc ...

  5. angular.element的常用方法

    addClass()-为每个匹配的元素添加指定的样式类名after()-在匹配元素集合中的每个元素后面插入参数所指定的内容,作为其兄弟节点append()-在每个匹配元素里面的末尾处插入参数内容att ...

  6. JAVA单例的三种实现方式

    1. public class MySingleton { private MySingleton() {} private MySingleton instance = new MySingleto ...

  7. Windows 10 for phone 离我们不远了

    今天登录Windows Insider终于看到更多机型可以更新预览版了,看来Windows phone 10离我们不远了!

  8. 一步一步hadoop安装

    部署hadoop集群 1.下载jdk1.6,从http://www.oracle.com/technetwork/java/javase/downloads/java-archive-download ...

  9. iOS 时间戳的转换

    在开发iOS程序时,有时候需要将时间格式调整成自己希望的格式,这个时候我们可以用NSDateFormatter类来处理.例如: //实例化一个NSDateFormatter对象 NSDateForma ...

  10. 扫描二维码判断移动设备(Android/ios),以及判断是否微信端扫描

    <section class="download"> <a href="apk地址" class="android" st ...