用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. 不同包中继承关系访问protected内部类问题

    有两个包pack1和pack2,pack1中是父类,pack2中子类继承自pack1中的父类.这里主要探索一下子类访问父类中protected内部类的问题: 第一个类: package pack1; ...

  2. block反向界面传值

    1.在第二个界面的.h文件中申明block @property(nonatomic,copy)void(^myBlock)(NSString * str); 2.在返回第一个界面的点击事件中赋值要传递 ...

  3. 异常:System.Data.EvaluateException: 未找到列[District].

    异常:System.Data.EvaluateException: 未找到列[District]. 这里存在的问题不一定是说,数据源表没有该字段.此问题在于数据库字段包含空格字符.

  4. beanstalkd----安装启动

    1. 安装This is beanstalkd, a fast, general-purpose work queue.See http://kr.github.io/beanstalkd/ for ...

  5. 【洛谷P2866】Bad Hair Day

    单调栈版子 #include<cstdio> #include<cstring> using namespace std; ; ,zh[N]; int read(){ ; ch ...

  6. 视图合并、hash join连接列数据分布不均匀引发的惨案

    表大小 SQL> select count(*) from agent.TB_AGENT_INFO; COUNT(*) ---------- 1751 SQL> select count( ...

  7. 51NOD算法马拉松11 B君的竞技场

    传送门 这题我在比赛的时候竟然没有想出来,真是-- 这道题我们可以想一想怎么搞定获胜的概率p. 我们发现再怎么这个p都是搞不了的.所以我们可以积一下分,然后就可以不用去管p了.我们要做的就是求出一个关 ...

  8. [置顶]PADS PCB功能使用技巧系列之NO.006- 如何实现OrCAD与PADS Layout同步?

    很多同仁都喜欢用OrCAD画原理图,而PCB Layout则用PADS/PowerPCB,这两者被有些人誉为“黄金组合”,但由于两者并非一套软件,因此如何实现同步亦是需要急待解决的问题... (未完待 ...

  9. Mac OS 系统工具使用

    1. 截屏的处理 Command + Shift + 4

  10. [转] MySQL 查询表数据大小的总结

    一:关于mysql表数据大小 我们知道mysql存储数据文件一般使用表空间存储 当mysql使用innodb存储引擎的时候,mysql使用表存储数据分为共享表空间和独享表空间两种方式 ·共享表空间:I ...