前文回顾:

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

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

前言:

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

披着慕容小匹夫的外衣

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

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

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

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

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

  1. //MurongXiaoPiFu类的声明
  2. .class MurongXiaoPiFu
  3. {
  4. .method public void .ctor()
  5. {
  6. //TODO
  7. }
  8. }

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

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

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

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

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

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

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

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

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

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

我的成员我做主

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

成员变量

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

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

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

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

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

成员函数

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

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

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

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

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

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

  1. //实例函数介绍自己
  2. .method void Introduce()
  3. {
  4. .maxstack
  5. ldstr "我叫{0},今年{1}岁,是一个{2}"
  6. ldarg.
  7. ldfld string MurongXiaoPiFu::name
  8. ldarg.
  9. ldfld int32 MurongXiaoPiFu::age
  10. box int32 //对int型的年龄有一个装箱
  11. ldarg.
  12. ldfld string MurongXiaoPiFu::job
  13. call void [mscorlib]System.Console::WriteLine(string, object, object, object)
  14. ret
  15. }
  16. //实例函数,虚函数,过年好
  17. .method public virtual void HappyNewYear()
  18. {
  19. .maxstack
  20. ldstr "过年好"
  21. call void [mscorlib]System.Console::WriteLine(string)
  22. ret
  23. }

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

实例化之后给各位拜年

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

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

  1. .locals init (
  2. class MurongXiaoPiFu Murong)

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

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

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

调用实例的方法

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

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

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

  1. //调用实例方法
  2. ldloc Murong
  3. call instance void class MurongXiaoPiFu::Introduce()
  4. ldloc Murong
  5. callvirt instance void class MurongXiaoPiFu::HappyNewYear()

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

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

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

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

附录

  1. .assembly extern mscorlib
  2. {
  3. .ver :::
  4. .publickeytoken = (B7 7A 5C E0 ) // .z\V.4..
  5. }
  6. .assembly 'HelloWorld'
  7. {
  8. }
  9. .method static void Fanyou()
  10. {
  11. .entrypoint
  12.  
  13. .maxstack
  14. .locals init (
  15. class MurongXiaoPiFu Murong)
  16. newobj instance void class MurongXiaoPiFu::'.ctor'()
  17. stloc Murong
  18. call void MurongXiaoPiFu::SayHi()
  19. ldloc Murong
  20. call instance void class MurongXiaoPiFu::Introduce()
  21. ldloc Murong
  22. callvirt instance void class MurongXiaoPiFu::HappyNewYear()
  23.  
  24. // ldstr "Hello World!"
  25. // call void [mscorlib]System.Console::WriteLine(string)
  26.  
  27. ret
  28. }
  29. .method static void AddLife()
  30. {
  31. .maxstack
  32. //局部变量
  33. .locals init (int32 num1,
  34. int32 num2,
  35. int32 result)
  36. //第一个功能:显示提示输入加数,并获取输入的值
  37. //在屏幕上显示“请输入第一个加数”
  38. ldstr "请输入第一个加数"
  39. call void [mscorlib]System.Console::WriteLine(string)
  40. //获取用户的输入值
  41. call string [mscorlib]System.Console::ReadLine()
  42. //将输入的字符串转化成int
  43. call int32 [mscorlib]System.Int32::Parse(string)
  44. //值出栈,赋给局部变量num1
  45. stloc num1
  46. //num2
  47. ldstr "请输入第二个加数"
  48. call void [mscorlib]System.Console::WriteLine(string)
  49. call string [mscorlib]System.Console::ReadLine()
  50. call int32 [mscorlib]System.Int32::Parse(string)
  51. stloc num2
  52.  
  53. //第二个功能:相爱相杀,不对,应该是相爱相加...
  54. //将值从变量中压入堆栈
  55. ldloc num1
  56. ldloc num2
  57. //求和
  58. add
  59. //将结果赋值给result
  60. stloc result
  61. //最后一个功能,关键的其实是装箱
  62. //显示的格式
  63. ldstr "{0} + {1} = {2}"
  64. //将num1,num2,result装箱,供之后的writeLine使用。
  65. ldloc num1
  66. box int32
  67. ldloc num2
  68. box int32
  69. ldloc result
  70. box int32
  71. //将算式显示出来
  72. call void [mscorlib]System.Console::WriteLine(string, object, object, object)
  73. ret
  74. }
  75.  
  76. //MurongXiaoPiFu类的声明
  77. .class MurongXiaoPiFu
  78. extends [mscorlib]System.Object //同样System.Object来自mscorlib程序集
  79. {
  80. .field public string name
  81. .field public int32 age
  82. .field public string sex
  83. .field public string job
  84.  
  85. //构造函数.ctor的实现
  86. .method public void .ctor()
  87. {
  88. .maxstack
  89. //定义各个成员变量
  90. ldarg.
  91. ldstr "陈嘉栋"
  92. stfld string MurongXiaoPiFu::name
  93. ldarg.
  94. ldc.i4.s 0x19   //25岁
  95. stfld int32 MurongXiaoPiFu::age
  96. ldarg.
  97. ldstr "男"
  98. stfld string MurongXiaoPiFu::sex
  99. ldarg.
  100. ldstr "程序猿"
  101. stfld string MurongXiaoPiFu::job
  102. ldarg. //1.将实例的引用压栈
  103. call instance void [mscorlib]System.Object::.ctor() //2.调用基类的构造函数
  104. ret
  105. }
  106.  
  107. //问好,静态函数
  108. .method static void SayHi()
  109. {
  110.    .maxstack
  111. ldstr "你好!"
  112. call void [mscorlib]System.Console::WriteLine(string)
  113. ret
  114. }
  115.  
  116. //实例函数介绍自己
  117. .method void Introduce()
  118. {
  119. .maxstack
  120. ldstr "我叫{0},今年{1}岁,是一个{2}"
  121. ldarg.
  122. ldfld string MurongXiaoPiFu::name
  123. ldarg.
  124. ldfld int32 MurongXiaoPiFu::age
  125. box int32 //对int型的年龄有一个装箱
  126. ldarg.
  127. ldfld string MurongXiaoPiFu::job
  128. call void [mscorlib]System.Console::WriteLine(string, object, object, object)
  129. ret
  130. }
  131. //实例函数,虚函数,过年好
  132. .method public virtual void HappyNewYear()
  133. {
  134. .maxstack
  135. ldstr "过年好"
  136. call void [mscorlib]System.Console::WriteLine(string)
  137. ret
  138. }
  139. }

用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. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  2. Git与Repo入门

    版本控制 版本控制是什么已不用在说了,就是记录我们对文件.目录或工程等的修改历史,方便查看更改历史,备份以便恢复以前的版本,多人协作... 一.原始版本控制 最原始的版本控制是纯手工的版本控制:修改文 ...

  3. Linux scp 设置nohup后台运行

    Linux scp 设置nohup后台运行 1.正常执行scp命令 2.输入ctrl + z 暂停任务 3.bg将其放入后台 4.disown -h 将这个作业忽略HUP信号 5.测试会话中断,任务继 ...

  4. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  5. 利用apply()或者rest参数来实现用数组传递函数参数

    关于call()和apply()的用法,MDN文档里写的非常清晰明白,在这里就不多做记录了. https://developer.mozilla.org/zh-CN/docs/Web/JavaScri ...

  6. js学习之变量、作用域和内存问题

    js学习之变量.作用域和内存问题 标签(空格分隔): javascript 变量 1.基本类型和引用类型: 基本类型值:Undefined, Null, Boolean, Number, String ...

  7. JavaWeb——Servlet

    一.基本概念 Servlet是运行在Web服务器上的小程序,通过http协议和客户端进行交互. 这里的客户端一般为浏览器,发送http请求(request)给服务器(如Tomcat).服务器接收到请求 ...

  8. 微信小程序(微信应用号)组件讲解

    这篇文章主要讲解微信小程序的组件. 首先,讲解新建项目.现在有句话:招聘三天以上微信小程序开发,这个估计只能去挖微信的工程师了.技术新,既然讲解,那我们就从开始建项目讲解. 打开微信web开发者工具, ...

  9. Html.DropDownLis绑定数据库

    效果: 方法一: View: <div class="col-md-md-4"> <div class="input-group"> & ...

  10. JAVA回调机制解析

    一.回调机制概述     回调机制在JAVA代码中一直遇到,但之前不懂其原理,几乎都是绕着走.俗话说做不愿意做的事情叫做突破,故诞生了该文章,算是新年的新气象,新突破!     回调机制是什么?其实回 ...