用C表达面向对象语言的机制2——颠覆你对方法调用的看法!
用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——颠覆你对方法调用的看法!的更多相关文章
- 用C表达面向对象语言的机制——C#版
PS:本文PDF版在这里(格式更好看一些).最新的源代码请在本页面文末下载,PDF中的链接不是最新的. 用C表达面向对象语言的机制——C#版 我一直认为,面向对象语言是对面向过程语言的封装.如果是这样 ...
- go 学习笔记之go是不是面向对象语言是否支持面对对象编程?
面向对象编程风格深受广大开发者喜欢,尤其是以 C++, Java 为典型代表的编程语言大行其道,十分流行! 有意思的是这两中语言几乎毫无意外都来源于 C 语言,却不同于 C 的面向过程编程,这种面向对 ...
- C#学习-面向对象语言都有类
面向对象语言的一个基本特征是它们都有类,类是C#(这类语言)中的一种复杂数据类型. 类代表一组具有公共属性和行为的对象. 在C#中定义一个类是非常简单的,只需使用class关键字并按格式来定义即可. ...
- javascript是一种面向对象语言吗?如果是,您在javascript中是如何实现继承的呢
·oop(面向对象程序设计)中最常用到的概念有 1.对象,属性,方法 1>(对象:具体事物或抽象事物,名词) 2>(属性:对象的特征,特点,形容词) 3>(方法:对象的动作,动词) ...
- java反射并不是什么高深技术,面向对象语言都有这个功能,而且功能也很简单,就是利用jvm动态加载时生成的class对象
java反射并不是什么高深技术,面向对象语言都有这个功能. 面向对象语言都有这个功能,而且功能也很简单,就是利用jvm动态加载时生成的class对象,去获取类相关的信息 2.利用java反射可以调用类 ...
- java类加载机制及方法调用
类加载机制 概述 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resoluti ...
- JVM垃圾回收机制总结:调优方法
转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍
- python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样
python与java的内存机制不一样;java的方法会进入方法区直到对象消失 方法才会消失;python的方法是对象每次调用都会创建新的对象 内存地址都不i一样
- Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用
在上一篇文章Android IPC机制(二)用Messenger进行进程间通信中我们介绍了使用Messenger来进行进程间通信的方法.可是我们能发现Messenger是以串行的方式来处理client ...
随机推荐
- IOS 本地推送 IOS10.0以上 static的作用 const的作用
//需要在AppDelegate里面启动APP的函数 加上 UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNot ...
- ubuntu 12.10 sourcelist软件更新源列表(zz)
ubuntu 12.10正式版已经发布了,国内各大开源软件源也陆续更新了资源.今天分享一下ubuntu 12.10 软件更新源列表. 首先,备份一下ubuntu 12.04 原来的源地址列表文 ...
- 模拟XShell的小项目
不知道大家有没有用过XShell这款工具,这款工具通过windows可以远程操作处于开机状态的linux操作系统,也就是说把你的电脑和一台服务器连入网络,你通过输入服务器所在的IP地址建立一个会话就可 ...
- 简单的dp
有趣的数:(动态规划,状态转移) #include<stdio.h> ][]; int main() { int n,i; ; i<; i++) dp[i][]=; while(~s ...
- STM32之PWM波形输出配置总结
一. TIMER分类: STM32中一共有11个定时器,其中TIM6.TIM7是基本定时器:TIM2.TIM3.TIM4.TIM5是通用定时器:TIM1和TIM8是高级定时器,以及2个看门狗定时器 ...
- [fortify] 变量覆盖漏洞
一.全局变量覆盖当register_global=ON时,变量来源可能是各个不同的地方,比如页面的表单,Cookie等. <?php echo "Register_globals: & ...
- JSF中使用jquery拦截ajax请求
jsf提供一个内置的jsf.ajax.request方法给我们使用,如果在jquery中使用,则需要做一些更改. 此处因为使用jquery,所以可以不必在控件中添加onclick方法了,可以给控件配 ...
- Eclipse JAVA项目的 目录结构 和 导入
说明:本文所有测试以java工程为例: 1. Eclipse下的java工程目录 eclipse的基本工程目录叫做workspace,每个运行时的eclipse实例只能对应一个workspace,也就 ...
- .NET 4.5+项目迁移.NET Core的问题记录
.NET 4.5+项目迁移.NET Core的问题记录 这几天试着把目前的开发框架迁移到新的.net core平台,中间遇到的问题在这里简单记录一下. 迁移过程遇到的最大的问题IOC容器.我目前使用的 ...
- Log4j 与 Logback的ConversionPattern对比
为了能将log4j的配置无缝转到logback,需要了解其中ConversionPattern的差异,以下是对比表格,内容来自: log4j官网 logback官网 其中可能需要转换的地方主要有两块: ...