用CIL写程序:定义一个叫“慕容小匹夫”的类
前文回顾:
《用CIL写程序:你好,沃尔德》
《用CIL写程序:写个函数做加法》
前言:
今天是乙未羊年的第一天,小匹夫先在这里给各位看官拜个年了。不知道各位看官是否和匹夫一样,摸键盘的手都已经有点生疏了呢?所以,为了不忘却程序猿的使命,不冷落程序猿最好的伙伴--键盘。匹夫决定来写《用CIL写程序》的最新一篇文章。可是写什么主题呢?之前匹夫也介绍过CIL其实也是面向对象的,所以寻思着大过年的,不如就写一个类,一个用来抽象化小匹夫的类吧,既可以介绍下小匹夫,小匹夫也可以借这个类给各位拜年。那么顺序由上到下,无外乎如何声明一个类,类成员如何定义,以至于到后来如何实例化一个类,并且调用实例的各个方法,当然本文的完整CIL代码各位可以在附录部分看到。
披着慕容小匹夫的外衣
OK,那么声明一个类的第一步是什么呢?定义一个类的名字?写构造函数?NO,NO。
声明一个类的第一步,自然是要告诉别人你声明的是一个类!不是方法,不是变量,而是一个类。那么我们仿照前两篇文章中介绍的声明方法的方式:.method,来声明我们的类。那么各位是不是已经想到了呢?对,我们直接使用“.class”指令来声明一个类。
那么第二步呢?对,既然知道它是一个类了,那么我们显然还可以指定关键字比如abstract、sealed、public、private或者interface等等,而在CIL中很多都是可以省略的,所以这里我们也就省略了。之后我们还需要给它起个名字来标识它,既然题目已经叫做《这个叫慕容小匹夫的类》了,那么我们的类的名字就叫做MurongXiaoPiFu。
之后第三步呢?对啊,貌似应该有个构造函数啊。但有时候我们写C#代码的时候,并不需要自己手动写构造函数呀。可为什么到了CIL里就需要自己手动写呢?因为C#的编译器会自动为我们生成构造函数,而CIL的编译器则不会。所以构造函数这个环节我们一定不要忘记。
所以类的声明部分我们就写出来了:
//MurongXiaoPiFu类的声明 .class MurongXiaoPiFu {.method public void .ctor(){//TODO } }
OK,需要的三个步骤走完了。这里总结一下:
- .class指令标识我们声明的是一个类
- 这个类的关键字和名字紧跟.class之后。这里小匹夫写的类是MurongXiaoPiFu。
- 必须要写构造函数”.method public void .ctor()“
好了,要写构造函数这一点我们已经明确了。我们声明完.ctor()之后,该如何实现它呢?那么我们首先考虑一下一个没有参数的构造函数都可能要做一些什么。
匹夫在上一篇文章中详细的介绍过CIL是如何使用堆栈的,包括值类型是如何使用堆栈以及引用类型是如何使用堆栈。此处的MurongXiaoPiFu类其实就是一个引用类型,所以呢?不错,它需要把自己的引用压栈。
那么还有呢?所有的引用类型都派生自System.Object,所以生成一个新的类实例其实是通过System.Object的构造函数来实现的。那么基于以上2点,我们的构造函数的实现也就十分清楚了:
//构造函数.ctor的实现 .method public void .ctor() {.maxstack 1ldarg.0 //1.将实例的引用压栈call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数 ret }
但是小匹夫觉得写到这里还有点不完整,还缺点啥呢?对啊,面向对象的三大法宝:封装、继承、多态嘛。其中继承和多态都和所谓的继承有很大的关系。所以说到类,不说继承似乎有点不专业。为了不给各位留下匹夫不专业的印象,继承还是不得不说。但是看匹夫你定义的这个类MurongXiaoPiFu貌似没有用上继承啊?
此言差矣。殊不知,MurongXiaoPiFu这个类默认是继承自System.Object这个基类的。只不过当我们省略掉继承的语句时,CIL的编译器会自动将我们的类识别为从System.Object派生而来的,换言之,会被当做是引用类型。那么如果我们不省略继承语句,我们的类该如何显式地实现继承呢?没错,用extend关键字。那么加上继承自System.Object的语句(虽然这是多余的,但是为了演示继承的用法这里还是很low的这样写了)之后,我们完整的声明代码如下:
//MurongXiaoPiFu类的声明 .class MurongXiaoPiFuextends [mscorlib]System.Object //同样System.Object来自mscorlib程序集 {//构造函数.ctor的实现.method public void .ctor(){.maxstack 1ldarg.0 //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
成员函数
匹夫的基本资料有了,但是匹夫还想向大伙拜年和介绍自己呢啊。所以光有成员变量还是不够的,我们还需要几个成员函数。说到一个类的函数,首先要关注点什么呢?对啊,一个类中的函数究竟是静态函数呢还是实例函数呢?所以接下来我们要去实现的函数既要包括静态函数,还要有实例函数。当然,如果要全面一些,我们还要考虑进去虚函数。比如这三个函数:
- 问好(SayHi)静态函数
- 介绍自己(Introduce)实例函数
- 拜年(HappyNewYear)实例函数,虚函数
同时,这两个实例函数还会用到刚才定义的几个成员变量,所以这里我们会引入另一个新的指令ldfld。这个指令是啥意思呢?别忘了堆栈额~所有被操作的数据都需要通过堆栈,所以各位明白了吧?这个指令就是将字段中值压入堆栈中供之后使用的。
首先,我们使用static关键字实现静态函数SayHi:
//问好,静态函数 .method static void SayHi() { .maxstack 1ldstr "你好!"call void [mscorlib]System.Console::WriteLine(string) ret }
然后,我们实现那两个实例函数,与静态函数使用static相反,实例函数使用instance关键字。不过在CIL中类的成员函数默认是实例函数,因此我们可以省略instance关键字。同时,由于是实例函数,因此在获取实例的各个变量之前肯定要先知道该实例的引用。所以每一步取值,我们首先要”ldarg.0“将实例的引用压栈,之后再使用”ldfld“去取值压栈。
//实例函数介绍自己.method void Introduce() {.maxstack 7ldstr "我叫{0},今年{1}岁,是一个{2}"ldarg.0 ldfld string MurongXiaoPiFu::nameldarg.0 ldfld int32 MurongXiaoPiFu::agebox int32 //对int型的年龄有一个装箱ldarg.0 ldfld string MurongXiaoPiFu::jobcall void [mscorlib]System.Console::WriteLine(string, object, object, object) ret}//实例函数,虚函数,过年好.method public virtual void HappyNewYear(){.maxstack 1ldstr "过年好"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 4:0:0:0.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. } .assembly 'HelloWorld' { } .method static void Fanyou() {.entrypoint.maxstack 4.locals init (class MurongXiaoPiFu Murong)newobj instance void class MurongXiaoPiFu::'.ctor'()stloc Murongcall void MurongXiaoPiFu::SayHi()ldloc Murongcall instance void class MurongXiaoPiFu::Introduce()ldloc Murongcallvirt instance void class MurongXiaoPiFu::HappyNewYear()// ldstr "Hello World!"// call void [mscorlib]System.Console::WriteLine(string) ret } .method static void AddLife() {.maxstack 4 //局部变量 .locals init (int32 num1,int32 num2,int32 result)//第一个功能:显示提示输入加数,并获取输入的值//在屏幕上显示“请输入第一个加数”ldstr "请输入第一个加数"call void [mscorlib]System.Console::WriteLine(string)//获取用户的输入值call string [mscorlib]System.Console::ReadLine()//将输入的字符串转化成intcall int32 [mscorlib]System.Int32::Parse(string)//值出栈,赋给局部变量num1 stloc num1//num2ldstr "请输入第二个加数"call void [mscorlib]System.Console::WriteLine(string)call string [mscorlib]System.Console::ReadLine()call int32 [mscorlib]System.Int32::Parse(string)stloc num2//第二个功能:相爱相杀,不对,应该是相爱相加...//将值从变量中压入堆栈 ldloc num1ldloc num2//求和 add//将结果赋值给result stloc result//最后一个功能,关键的其实是装箱//显示的格式ldstr "{0} + {1} = {2}"//将num1,num2,result装箱,供之后的writeLine使用。 ldloc num1box int32ldloc num2box int32ldloc resultbox int32//将算式显示出来call void [mscorlib]System.Console::WriteLine(string, object, object, object)ret }//MurongXiaoPiFu类的声明 .class MurongXiaoPiFuextends [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 10//定义各个成员变量ldarg.0 ldstr "陈嘉栋"stfld string MurongXiaoPiFu::nameldarg.0 ldc.i4.s 0x19 //25岁 stfld int32 MurongXiaoPiFu::ageldarg.0 ldstr "男"stfld string MurongXiaoPiFu::sexldarg.0 ldstr "程序猿"stfld string MurongXiaoPiFu::jobldarg.0 //1.将实例的引用压栈call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数 ret}//问好,静态函数.method static void SayHi() {.maxstack 1ldstr "你好!"call void [mscorlib]System.Console::WriteLine(string) ret}//实例函数介绍自己.method void Introduce() {.maxstack 7ldstr "我叫{0},今年{1}岁,是一个{2}"ldarg.0 ldfld string MurongXiaoPiFu::nameldarg.0 ldfld int32 MurongXiaoPiFu::agebox int32 //对int型的年龄有一个装箱ldarg.0 ldfld string MurongXiaoPiFu::jobcall void [mscorlib]System.Console::WriteLine(string, object, object, object) ret}//实例函数,虚函数,过年好.method public virtual void HappyNewYear(){.maxstack 1ldstr "过年好"call void [mscorlib]System.Console::WriteLine(string)ret} }
用CIL写程序:定义一个叫“慕容小匹夫”的类相关推荐
- python面向对象课程大作业 定义一个描述学生基本情况的类,数据成员至少包括 “姓名、性别、学号、年级、所在院系、面向对象的考试日期”
python面向对象课程大作业 按下列要求编写一个完整的程序: 定义一个描述学生基本情况的类,数据成员至少包括"姓名.性别.学号.年级.所在院系.面向对象的考试日期",成员函数至少 ...
- 编写程序定义一个有 10 个 int 型元素的数组,并以其在数组中的位置作为各元素的初值。
*编写程序定义一个有 10 个 int 型元素的数组,并以其 在数组中的位置作为各元素的初值.*/ int main(void) { int array[10]; for (int i ...
- 使用小程序制作一个飞机大战小游戏
此文主要基于微信小程序制作一个飞机大战小游戏,上手即用,操作简单. 一.创建小程序 二.页面实现 三.代码块 一.创建小程序 访问微信公众平台,点击账号注册. 选择小程序,并在表单填写所需的各项信息进 ...
- (C++题目)定义一个描述学生基本情况的类Student,数据成员包括姓名、学号、英语成绩和高数成绩;成员函数包括构造函数、析构函数、获取姓名、获取学号、求出平均成绩,以及显示各
定义一个描述学生基本情况的类Student,数据成员包括姓名.学号.英语成绩和高数成绩:成员函数包括构造函数.析构函数.获取姓名.获取学号.求出平均成绩,以及显示各科成绩和平均成绩的显示函数.编写ma ...
- 微信小程序|使用小程序制作一个时间管理小工具
适时而学,适时而息,张弛有度的生活态度才能让我们走得更远.此文使用小程序制作一个日程管理小工具,将时间进行分解以实现有效管理. 开发步骤 一.创建小程序 二.功能实现 2.1.首页 2.2.记录页 3 ...
- 1、猜数字游戏:一个类A有两个成员变量v、num,v有一个初值100。定义一个方法guess,对A类的成员变量v,用num进行猜。如果大了则提示大了,小了则提示小了。等于则提示猜测成功。在main方法
定义一个类,其中封装成员变量v-猜数的目标, num-猜测的数据 guess方法根据num和v比对结果生成对应的提示信息 import java.util.Scanner; public class ...
- 1.JAVA猜数字游戏: 一个类A有两个成员变量v、num,v有一个初值100。 定义一个方法guess,对A类的成员变量v,用num进行猜。 *如果num比v大则提示大了,反之则提示小了.
思路: * 1.创建一个类,定义两个成员变量v和num,v是固定值所以直接赋值,num是键盘录入的. * 2.定义一个setNum类,将来通过对象调用将键盘录入接收的数据进行设置 * 3.创建一 ...
- C++学习记录2:定义一个描述学生基本情况的类
定义一个描述学生基本情况的类,数据成员包括姓名.学号.英语.数学.计算机成绩.成员函数包括设置姓名.学号和三门课程的成绩.输出数据.以及求平均值.设计主函数,在主函数里调用设置"姓名.学号和 ...
- 定义一个点(Point)的类,x和y分别为第一象限的像素点,输入一些这样的点,然后按照升序输出这些点集(比较x2+y2的大小)。
Point类 import java.util.ArrayList;//定义一个点(Point)的类,x和y分别为第一象限的像素点,输入一些这样的点,然后按照升序输出这些点集(比较x2+y2的大小). ...
最新文章
- 通过了OCP的全部考试后的感受(ZT)
- 【 FPGA 】UltraFast设计方法学笔记(RTL代码风格1)
- 【信息化】CIO议题营销模型
- 浅谈equals和==的区别
- python开发测试岗_作为测试开发岗的面试官,我都是怎么选人的?
- C#自定义控件:WinForm将其它应用程序窗体嵌入自己内部
- easyui被activeX控件挡住的解决方法
- ubuntu-Linux系统读取USB摄像头数据(uvc)
- jquery easyui datagrid 获取Checked选择行(勾选行)数据
- 2022年全球市场雷达目标模拟器总体规模、主要生产商、主要地区、产品和应用细分研究报告
- 当前的安全设置不允许从该位置下载文件。 的解决办法
- 空指针异常NullPointerException(小结)
- 虚拟化(六):vmware 桌面虚拟化 horizon view 介绍
- SlidingMenu初识
- trinity运行原理及常见报错(四)
- Xshell下载安装教程和使用教程(超详细)
- iOS UIDocumentPickerViewController页面列表底部有一截空白【已解决】
- 【回归分析】confounding effect 与 interaction
- 我允许失败,但是我不允许不努力!
- 怎样剪切歌曲 剪切歌曲用什么音频转换器
热门文章
- 脑电波也能卖萌 这样的传感器好特别
- laravel框架搭建voyager
- oracle实体视图日志,Oracle案例02——ORA-12034: SCOTT.USER_TABLE 上的实体化视图日志比上次刷新后的内容新...
- Hackthebox:Arctic Walkthrough
- 不使用microscale库从siwarex ms读重量值
- 单片机红外通信c语言,用51单片机实现红外通讯源码
- 17、TWS API和IB中的财务顾问
- Java 银联支付官网demo测试及项目整合代码
- Mackey-Glass时滞混沌系统
- 太极英语笔记-前传语法-代词