前文回顾:

《用CIL写程序:你好,沃尔德》

《用CIL写程序:写个函数做加法》

前言:

今天是乙未羊年的第一天,小匹夫先在这里给各位看官拜个年了。不知道各位看官是否和匹夫一样,摸键盘的手都已经有点生疏了呢?所以,为了不忘却程序猿的使命,不冷落程序猿最好的伙伴--键盘。匹夫决定来写《用CIL写程序》的最新一篇文章。可是写什么主题呢?之前匹夫也介绍过CIL其实也是面向对象的,所以寻思着大过年的,不如就写一个类,一个用来抽象化小匹夫的类吧,既可以介绍下小匹夫,小匹夫也可以借这个类给各位拜年。那么顺序由上到下,无外乎如何声明一个类类成员如何定义,以至于到后来如何实例化一个类,并且调用实例的各个方法,当然本文的完整CIL代码各位可以在附录部分看到。

披着慕容小匹夫的外衣

OK,那么声明一个类的第一步是什么呢?定义一个类的名字?写构造函数?NO,NO。

声明一个类的第一步,自然是要告诉别人你声明的是一个类!不是方法,不是变量,而是一个类。那么我们仿照前两篇文章中介绍的声明方法的方式:.method,来声明我们的类。那么各位是不是已经想到了呢?对,我们直接使用“.class”指令来声明一个类。

那么第二步呢?对,既然知道它是一个类了,那么我们显然还可以指定关键字比如abstract、sealedpublicprivate或者interface等等,而在CIL中很多都是可以省略的,所以这里我们也就省略了。之后我们还需要给它起个名字来标识它,既然题目已经叫做《这个叫慕容小匹夫的类》了,那么我们的类的名字就叫做MurongXiaoPiFu。

之后第三步呢?对啊,貌似应该有个构造函数啊。但有时候我们写C#代码的时候,并不需要自己手动写构造函数呀。可为什么到了CIL里就需要自己手动写呢?因为C#的编译器会自动为我们生成构造函数,而CIL的编译器则不会。所以构造函数这个环节我们一定不要忘记。

所以类的声明部分我们就写出来了:

//MurongXiaoPiFu类的声明
.class MurongXiaoPiFu
{
.method public void .ctor()
{
//TODO
}
}

OK,需要的三个步骤走完了。这里总结一下:

  1. .class指令标识我们声明的是一个类
  2. 这个类的关键字和名字紧跟.class之后。这里小匹夫写的类是MurongXiaoPiFu。
  3. 必须要写构造函数”.method public void .ctor()“

好了,要写构造函数这一点我们已经明确了。我们声明完.ctor()之后,该如何实现它呢?那么我们首先考虑一下一个没有参数的构造函数都可能要做一些什么。

匹夫在上一篇文章中详细的介绍过CIL是如何使用堆栈的,包括值类型是如何使用堆栈以及引用类型是如何使用堆栈。此处的MurongXiaoPiFu类其实就是一个引用类型,所以呢?不错,它需要把自己的引用压栈。

那么还有呢?所有的引用类型都派生自System.Object,所以生成一个新的类实例其实是通过System.Object的构造函数来实现的。那么基于以上2点,我们的构造函数的实现也就十分清楚了:

//构造函数.ctor的实现
.method public void .ctor()
{
.maxstack
ldarg. //1.将实例的引用压栈
call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
ret
}

但是小匹夫觉得写到这里还有点不完整,还缺点啥呢?对啊,面向对象的三大法宝:封装、继承、多态嘛。其中继承和多态都和所谓的继承有很大的关系。所以说到类,不说继承似乎有点不专业。为了不给各位留下匹夫不专业的印象,继承还是不得不说。但是看匹夫你定义的这个类MurongXiaoPiFu貌似没有用上继承啊?

此言差矣。殊不知,MurongXiaoPiFu这个类默认是继承自System.Object这个基类的。只不过当我们省略掉继承的语句时,CIL的编译器会自动将我们的类识别为从System.Object派生而来的,换言之,会被当做是引用类型。那么如果我们不省略继承语句,我们的类该如何显式地实现继承呢?没错,用extend关键字。那么加上继承自System.Object的语句(虽然这是多余的,但是为了演示继承的用法这里还是很low的这样写了)之后,我们完整的声明代码如下:

//MurongXiaoPiFu类的声明
.class MurongXiaoPiFu
extends [mscorlib]System.Object //同样System.Object来自mscorlib程序集
{
//构造函数.ctor的实现
.method public void .ctor()
{
.maxstack
ldarg. //1.将实例的引用压栈
call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
ret
}
}

好了,行文至此,各位对CIL中如何声明一个类应该就有了一个直观的印象了吧。不过这还不够啊,没有类中的各个成员,这个类也就没有什么实际的用途呀。所以接下来匹夫继续完善这个叫慕容小匹夫的类,来介绍一下小匹夫自己,同时也为各位拜个年。

我的成员我做主

说到类的成员无非就是类的成员变量和类的成员函数

成员变量

那么具体到我们的类,它的成员变量无非就是介绍一个人所需要的信息。无非就是姓名(name),年龄(age),性别(sex),职业(job)这些咯。匹夫相信在C#中,各位都知道如何声明一个成员变量。但是在CIL的世界中呢?应该如何声明一个变量呢?

和声明方法的.method指令类似,这里匹夫再引入一个指令.field用来声明变量。同样.field之后也可以加一些修饰符。

但是写代码对这些变量赋值之前,匹夫还是要强调一下CIL的执行都是依托于堆栈的,也就是要把堆栈记心中。这里我们需要用到stfld指令,这个指令是什么意思呢?简短截说就是把栈中的数据弹出,并赋值给这个类实例中对应的字段。那么之前,显然我们要将那个值先入栈,这样才能出栈赋值给实例中的字段嘛。

所以咯,匹夫的基本信息就是这样的了:


//声明各个变量
.field  public  string name
.field  public  int32  age
.field  public  string sex
.field  public  string job
//为各个成员变量赋值
ldstr "陈嘉栋"
stfld string MurongXiaoPiFu::name
ldc.i4.s 0x19   //25岁
stfld int32 MurongXiaoPiFu::age
ldstr "男人"
stfld string MurongXiaoPiFu::sex
ldstr "程序猿"
stfld string MurongXiaoPiFu::job

成员函数

匹夫的基本资料有了,但是匹夫还想向大伙拜年和介绍自己呢啊。所以光有成员变量还是不够的,我们还需要几个成员函数。说到一个类的函数,首先要关注点什么呢?对啊,一个类中的函数究竟是静态函数呢还是实例函数呢?所以接下来我们要去实现的函数既要包括静态函数,还要有实例函数。当然,如果要全面一些,我们还要考虑进去虚函数。比如这三个函数:

  1. 问好(SayHi)静态函数
  2. 介绍自己(Introduce)实例函数
  3. 拜年(HappyNewYear)实例函数,虚函数

同时,这两个实例函数还会用到刚才定义的几个成员变量,所以这里我们会引入另一个新的指令ldfld。这个指令是啥意思呢?别忘了堆栈额~所有被操作的数据都需要通过堆栈,所以各位明白了吧?这个指令就是将字段中值压入堆栈中供之后使用的。

首先,我们使用static关键字实现静态函数SayHi:

//问好,静态函数
.method static void SayHi()
{
   .maxstack
ldstr "你好!"
call void [mscorlib]System.Console::WriteLine(string)
   ret
}

然后,我们实现那两个实例函数,与静态函数使用static相反,实例函数使用instance关键字。不过在CIL中类的成员函数默认是实例函数,因此我们可以省略instance关键字。同时,由于是实例函数,因此在获取实例的各个变量之前肯定要先知道该实例的引用。所以每一步取值,我们首先要”ldarg.“将实例的引用压栈,之后再使用”ldfld“去取值压栈。

//实例函数介绍自己
.method void Introduce()
{
.maxstack
ldstr "我叫{0},今年{1}岁,是一个{2}"
ldarg.
ldfld string MurongXiaoPiFu::name
ldarg.
ldfld int32 MurongXiaoPiFu::age
box int32 //对int型的年龄有一个装箱
ldarg.
ldfld string MurongXiaoPiFu::job
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
ret
}
//实例函数,虚函数,过年好
.method public virtual void HappyNewYear()
{
.maxstack
ldstr "过年好"
call void [mscorlib]System.Console::WriteLine(string)
ret
}

OK,这样我们的成员函数就定义完毕了。

实例化之后给各位拜年

好啦,既然我们的慕容小匹夫的类已经定义好了。那么就让我们来实例化一个实例出来给各位拜个年吧。

首先,我们还是使用第一篇文章中写的第一个函数Fanyou来作为我们程序的入口,控制整个流程。那么我们需要在Fanyou函数中声明一个局部变量来存放MurongXiaoPiFu这个类的实例引用。这个变量我们就叫做Murong吧。

.locals init (
class MurongXiaoPiFu Murong)

然后,我们需要实例化一个MurongXiaoPiFu类。所以此处匹夫要引入newobj指令了,newobj指令通过调用类的构造函数来创造一个新的实例。创造好实例之后,这个实例的引用还躺在栈中,所以为了给刚刚才声明的变量Murong赋值,我们需要将栈中的实例引用弹出赋值给Murong,因此需要用到stloc。

newobj instance void class MurongXiaoPiFu::'.ctor'()
stloc Murong

好了,到此我们已经将慕容小匹夫这个类实例化了。那么接下来呢?不错,该调用相应的方法给各位拜年啦!

调用实例的方法

首先我们要调用我们定义的静态函数SayHi。和上面定义成员函数不同而且也很有趣的一点,就是我们使用call指令默认调用的是静态函数。所以我们调用SayHi就变得十分简单了。

//调用静态函数
call void MurongXiaoPiFu::SayHi()

而如果要调用实例函数,则要先将实例的引用压栈,而且在call的后面还需要加上instance。所以我们调用Introduce和HappyNewYear需要分别指定它们的实例,也就是将实例的引用使用ldloc压栈,之后call的时候还需要指明调用的是实例方法。(当然,如果各位有过使用工具将程序集反编译成CIL代码的经历的话。在调用某个实例的方法的部分,各位十有八九看到的可能是callvirt而非call,这个话题本来小匹夫这篇文章也想讨论来着,不过实在有点太晚了。所以留个坑,日后再专门讨论)

//调用实例方法
ldloc Murong
call instance void class MurongXiaoPiFu::Introduce()
ldloc Murong
callvirt instance void class MurongXiaoPiFu::HappyNewYear()

那么编译,再执行的结果如图。

好,其实写到此处也算是一个good ending了。年也给大家拜了,CIL也和大家聊了。 那么~~~

如果各位看官觉得文章写得还好,那么就容小匹夫跪求各位给点个“推荐”,谢啦~

装模作样的声明一下:本博文章若非特殊注明皆为原创,若需转载请保留原文链接http://www.cnblogs.com/murongxiaopifu/p/4296151.html )及作者信息慕容小匹夫

附录

.assembly extern mscorlib
{
.ver :::
.publickeytoken = (B7 7A 5C E0 ) // .z\V.4..
}
.assembly 'HelloWorld'
{
}
.method static void Fanyou()
{
.entrypoint .maxstack
.locals init (
class MurongXiaoPiFu Murong)
newobj instance void class MurongXiaoPiFu::'.ctor'()
stloc Murong
call void MurongXiaoPiFu::SayHi()
ldloc Murong
call instance void class MurongXiaoPiFu::Introduce()
ldloc Murong
callvirt instance void class MurongXiaoPiFu::HappyNewYear() // ldstr "Hello World!"
// call void [mscorlib]System.Console::WriteLine(string) ret
}
.method static void AddLife()
{
.maxstack
//局部变量
.locals init (int32 num1,
int32 num2,
int32 result)
//第一个功能:显示提示输入加数,并获取输入的值
//在屏幕上显示“请输入第一个加数”
ldstr "请输入第一个加数"
call void [mscorlib]System.Console::WriteLine(string)
//获取用户的输入值
call string [mscorlib]System.Console::ReadLine()
//将输入的字符串转化成int
call int32 [mscorlib]System.Int32::Parse(string)
//值出栈,赋给局部变量num1
stloc num1
//num2
ldstr "请输入第二个加数"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
stloc num2 //第二个功能:相爱相杀,不对,应该是相爱相加...
//将值从变量中压入堆栈
ldloc num1
ldloc num2
//求和
add
//将结果赋值给result
stloc result
//最后一个功能,关键的其实是装箱
//显示的格式
ldstr "{0} + {1} = {2}"
//将num1,num2,result装箱,供之后的writeLine使用。
ldloc num1
box int32
ldloc num2
box int32
ldloc result
box int32
//将算式显示出来
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
ret
} //MurongXiaoPiFu类的声明
.class MurongXiaoPiFu
extends [mscorlib]System.Object //同样System.Object来自mscorlib程序集
{
.field public string name
.field public int32 age
.field public string sex
.field public string job //构造函数.ctor的实现
.method public void .ctor()
{
.maxstack
//定义各个成员变量
ldarg.
ldstr "陈嘉栋"
stfld string MurongXiaoPiFu::name
ldarg.
ldc.i4.s 0x19   //25岁
stfld int32 MurongXiaoPiFu::age
ldarg.
ldstr "男"
stfld string MurongXiaoPiFu::sex
ldarg.
ldstr "程序猿"
stfld string MurongXiaoPiFu::job
ldarg. //1.将实例的引用压栈
call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
ret
} //问好,静态函数
.method static void SayHi()
{
   .maxstack
ldstr "你好!"
call void [mscorlib]System.Console::WriteLine(string)
ret
} //实例函数介绍自己
.method void Introduce()
{
.maxstack
ldstr "我叫{0},今年{1}岁,是一个{2}"
ldarg.
ldfld string MurongXiaoPiFu::name
ldarg.
ldfld int32 MurongXiaoPiFu::age
box int32 //对int型的年龄有一个装箱
ldarg.
ldfld string MurongXiaoPiFu::job
call void [mscorlib]System.Console::WriteLine(string, object, object, object)
ret
}
//实例函数,虚函数,过年好
.method public virtual void HappyNewYear()
{
.maxstack
ldstr "过年好"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}

用CIL写程序:定义一个叫“慕容小匹夫”的类的更多相关文章

  1. 用CIL写程序:从“call vs callvirt”看方法调用

    前文回顾:<用CIL写程序系列> 前言: 最近的时间都奉献给了加班,距离上一篇文章也有半个多月了.不过在上一篇文章<用CIL写程序:定义一个叫“慕容小匹夫”的类>中,匹夫和各位 ...

  2. 用CIL写程序:你好,沃尔德

    前言: 项目紧赶慢赶总算在年前有了一些成绩,所以沉寂了几周之后,小匹夫也终于有时间写点东西了.以前匹夫写过一篇文章,对CIL做了一个简单地介绍,不过不知道各位看官看的是否过瘾,至少小匹夫觉得很不过瘾. ...

  3. 用CIL写程序:写个函数做加法

    前言: 上一篇文章小匹夫为CIL正名的篇幅比较多,反而忽略了写那篇文章初衷--即通过写CIL代码来熟悉它,了解它.那么既然有上一篇文章做基础(炮灰),想必各位对CIL的存在也就释然了,兴许也燃起了一点 ...

  4. 简单练习题2编写Java应用程序。首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”、“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能

    编写Java应用程序.首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”.“取款”和“余额查询”.其次, 编写一个主类,在主类中测试Account类的 ...

  5. 慕容小匹夫 Unity3D移动平台动态读取外部文件全解析

    Unity3D移动平台动态读取外部文件全解析   c#语言规范 阅读目录 前言: 假如我想在editor里动态读取文件 移动平台的资源路径问题 移动平台读取外部文件的方法 补充: 回到目录 前言: 一 ...

  6. 编写Java应用程序。首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”、“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能

    package com.hanqi.test; //银行账号 public class account { private String zhanghao;//账号 //私有余额 private do ...

  7. 3.编写Java应用程序。首先定义一个描述银行账户的Account类,包括成员变 量“账号”和“存款余额”,成员方法有“存款”、“取款”和“余额查询”。其次, 编写一个主类,在主类中测试Account类的功能。

    Account package com.hanqi.test; public class Account { private String zhanghao;private double yve; A ...

  8. 微信小程序生成小程序某一个页面的小程序码

    1 登录微信小程序后台,mp.weixin.qq.com 2 点击右上角工具->生成小程序码 3 填写小程序名称或appid 4 关键一步,下面页面填写用户微信号后,打开小程序到某一个页面,点击 ...

  9. svg如何用marker 定义一个黑色的小圆点

    <defs> <marker id="markerStartArrow" viewBox="0 0 30 30" refX="10& ...

随机推荐

  1. SQL Server 致程序员(容易忽略的错误)

    标签:SQL SERVER/MSSQL/DBA/T-SQL好习惯/数据库/需要注意的地方/程序员/容易犯的错误/遇到的问题 概述 因为每天需要审核程序员发布的SQL语句,所以收集了一些程序员的一些常见 ...

  2. IE10、IE11 User-Agent 导致的 ASP.Net 网站无法写入Cookie 问题

    你是否遇到过当使用一个涉及到Cookie操作的网站或者管理系统时,IE 6.7.8.9下都跑的好好的,唯独到了IE10.11这些高版本浏览器就不行了?好吧,这个问题码农连续2天内遇到了2次.那么,我们 ...

  3. web前端基础知识

    #HTML    什么是HTML,和他ML...    网页可以比作一个装修好了的,可以娶媳妇的房子.    房子分为:毛坯房,精装修    毛坯房的修建: 砖,瓦,水泥,石头,石子....    精 ...

  4. JavaScript 对象属性介绍

    本篇主要介绍JS中对象的属性,包括:属性的分类.访问方式.检测属性.遍历属性以及属性特性等内容. 目录 1. 介绍:描述属性的命名方式.查找路径以及分类 2. 属性的访问方式:介绍'.'访问方式.'[ ...

  5. 视频 - 在 VirtualBox 中部署 OpenStack

    大家新年好,CloudMan 今天给大家带来一件新年礼物. 一直以来大家都反馈 OpenStack 学习有两大障碍:1. 实验环境难搭2. 体系复杂,难道大今天我就先帮大家解决环境问题.前两天我抽空在 ...

  6. C#中如何在Excel工作表创建混合型图表

    在进行图表分析的时候,我们可能需要在一张图表呈现两个或多个样式的图表,以便更加清晰.直观地查看不同的数据大小和变化趋势.在这篇文章中,我将分享C#中如何在一张图表中创建不同的图表类型,其中包括如何在同 ...

  7. SQL Server2016升级前几点自检

    SQL Server2016已经出来一段时间了,而且最新的SP1包也于2016年11月18日正式发布,各种新的特性推出让我们跃跃欲试.那么对于我们真实的业务环境,特别是生产环境要不要"跟风& ...

  8. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  9. python 数据类型---文件二

    1.打印进度条 import sys,time for i in range(20): sys.stdout.write("#") sys.stdout.flush() #不等缓冲 ...

  10. webix前端架构的项目应用

    webix框架兼容javascript.HTML.CSS,应用比较灵活,应用框架时,配合后台webAPI,整个web项目里面,App文件夹保存前台的多语言文件,图片文件,webix原代码js.css, ...