switch语句(上)(转载)
switch语句是C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码。switch语句可以具备多个分支,也就是说,根据参数的N种取值,可以跳转到N个代码段去运行。这不同于if语句,一条单独的if语句只具备两个分支(这是因为if语句的参数只能具备true或false两种取值),除非使用嵌套if语句。
switch语句能够接受的参数是有限制的,简单来说,只能是整数类型、枚举或字符串。本文就从整数、枚举和字符串这三种类型的switch语句进行介绍。
switch指令
在进入正题之前,先为大家简要介绍一下IL汇编语言中的switch指令。switch指令(注意和C#中的switch语句区分开)是IL中的多分支指令,它的基本形式如下:
switch (Label_1, Label_2, Label_3…)
其中switch是IL关键字,Label_1~Label_N是一系列标号(和goto语句中用到的标号一样),标号指明了代码中的位置。这条指令的运行原理是,从运算栈顶弹出一个无符号整数值,如果该值是0,则跳转到由Label_1指定的位置执行;如果是1,则跳转到Labe_2;如果是2,则跳转到Label_3;以此类推。
如果栈顶弹出的值不在标号列表的范围之内(0~N-1),则忽略switch指令,跳到switch指令之后的一条指令开始执行。因此,对于switch指令来说,其 “default子句”是在最开头的。
此外,Label_x所引用的标号位置只要位于当前方法体就可以,不必非要在switch指令的后面。
好了,后面我们会看到switch指令的实例的。
使用整数类型的switch语句
代码1 - 使用整数类型参数的switch语句,取值连续
代码1中的switch语句接受的参数n是int类型的,并且我们观察到,在各个case子句中的取值都是连续的。将这段代码写在一个完整的程序中,并进行编译。之后使用ildasm打开生成的程序集,可以看到对应的IL代码如代码2所示。
代码2 – 代码1生成的IL代码
我们可以看到,首先IL_0000和IL_0001两行代码将参数n存放到一个局部变量中,然后IL_0002到IL_0004三行将这个变量的值减去1,并将结果留在运算栈顶。啊哈,参数值减去1,要进行判断的几种情况不就变成了0、1、2了么?是的。在接下来的switch指令里,针对这三种取值给出了三个地址IL_0017、IL_0022和IL_002d。这三个地址处的代码,分别就是取值为1、2、3时需要执行的代码。
以上是取值连续的情形。如果各个case子句中给出的值并不连续呢?我们来看一下下面的C#代码:
代码3 – 使用整数类型参数的switch语句,取值不连续
代码3编译生成的程序集中,编译器生成的IL代码如下:
代码4 – 代码3生成的IL代码
看到代码4,第一感觉就是switch指令中跳转地址的数量和C#程序中switch语句中的取值数不相符。但仔细观察后可以发现,switch指令中针对0、2、4(即switch语句中的case 1、3、5)这三种取值给出了不同的跳转地址。而对于1、3这两种取值(在switch语句中并没有出现)则给出了同样的地址IL_003f,看一下这个地址,是语句ret。
也就是说,对于取值不连续的情况,编译器会自动用“default子句”的地址来填充switch指令中的“缝隙”。当然,代码4因为过于简单,所以“缝隙值”直接跳转到了方法的结尾。
那么,如果取值更不连续呢?那样的话,switch指令中就会有大量的“缝隙值”。要知道,switch指令和之后的跳转地址列表都是指令的一部分,缝隙值的增加势必会导致程序集体积的增加啊。呵呵,不必担心,编译器很聪明,请看下面的代码:
代码5 – 使用整数类型参数的switch语句,取值非常不连续
在代码5中,switch语句的每个case子句中给出的取值之间都相差20,这意味着如果再采用前面所述“缝隙值”的做法,switch指令中将有多达41个跳转地址,而其中有效的只有3个。但现代的编译器明显不会犯这种低级错误。下面给出编译器为代码5 生成的IL:
代码6 – 代码5生成的IL代码
从代码6中我们会发现,switch指令不见了,在IL_0005、IL_000a和IL_000f三处分别出西安了beq.s指令,这个指令是beq指令的简短形式。当跳转位置和当前位置之差在一个sbyte类型的范围之内时,编译器会自动选择简短形式,目的是缩小指令集的体积。而beq指令的作用是从运算栈中取出两个值进行比较,如果两个值相等,则跳转到目标位置(有beq指令后面的参数指定)执行,否则继续从beq指令的下一条指令开始执行。
由此可见,当switch语句的取值非常不连续时,编译器会放弃使用switch指令,转而用一系列条件跳转来实现。这有点类似于if-else if-...-else语句。
使用枚举类型的switch语句
.NET中的枚举是一种特殊的值类型,它必须以某一种整数类型作为其底层类型(underlying type)。因此在运算时,枚举都是按照整数类型对待的,switch指令会将栈顶的枚举值自动转换成一个无符号整数,然后进行判断。
因此,在switch语句中使用枚举和使用整数类型没有太大的区别。请看下面一段代码:
代码7 - 在switch语句中使用枚举类型
其中的Num类型是一个枚举,定义为public enum Num { One, Two, Three }
下面是编译器为代码7生成的IL代码:
代码8 - 代码7生成的IL代码
可以看到,代码8和代码2没有什么本质区别。这是因为枚举值就是按照整数对待的。并且,如果枚举定义的成员取值不连续,生成的代码也会和代码4、代码6类似。
小结
本文介绍了编译器如何翻译使用整数类型的switch语句。如果你很在乎微乎其微的效率提升的话,应记得:
- 尽量在switch中使用连续的取值;
- 如果取值不连续,则使用尽量少的case子句,并将出现频率高的case放在前面(因为此时switch语句和if-else if-else语句是类似的)。
switch语句(上)(转载)的更多相关文章
- java基础面试题:switch语句能否作用在byte上,能否作用在long上,能否作用在String上?
package com.swift; public class Switch_Test { public static void main(String[] args) { /* * switch语句 ...
- switch语句(下)(转载)
之前我们介绍了在switch语句中使用整数类型和枚举类型的情况.这一部分继续介绍使用string类型的情况.string类型是switch语句接受的唯一一种引用类型参数. 下面来看一段C#代码. 代码 ...
- switch语句能否作用在byte,long,string上
switch是java中的多分支结构.在switch(expr)中,expr只能是一个整数表达式,或者是枚举常量,整数表达式可以是int基本类型也可以是Integer包装类型,由于byte,short ...
- 利用switch语句计算特定的年份的月份共有几天。
//利用switch语句计算特定的年份的月份共有几天. let year =2015 let month =2 //先判断闰年中二月份的情况 ifmonth ==2 { if (year %400 = ...
- android官方技术文档翻译——switch 语句转换
本文译自androd官方技术文档<Switch Statement Conversion>,原文地址:http://tools.android.com/tips/non-constant- ...
- switch语句的妙用
switch语句的普通用法很简单,如下: var a = 3; switch (a) { case 1: console.log(a); break; case 2: case 3: console. ...
- 透过IL看C#:switch语句(转)
透过IL看C# switch语句(上) 摘要: switch语句是 C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码.本文介绍了当向 switch语句中传入不同类型的参数时,编译器为其生 ...
- switch语句
应用条件语句可以很方便地使程序实现分支,但是出现分支比较多的时候,虽然可以用嵌套的if语句来解决,但是程序结构会显得复杂,甚至凌乱.为方便实现多情况选择,C++提供了一种switch开关语句. 一 ...
- java switch语句注意的事项
1.switch语句使用的变量只能是byte.char.short.string数据类型. 2.case后面gender数据必须是一个常量. 3.switch的停止条件: switch语句一旦比配上了 ...
随机推荐
- vue教程(一)-html使用vue
前后端分离.微服务框架是当下比较流行的词汇,而vue就是前端框架的佼佼者.下面重点介绍一下vue的用法: vue起步:1.引包 2.启动new Vue({el:目的地,template:模板内容 ...
- Spring Cloud 之 Config与动态路由.
一.简介 Spring Cloud Confg 是用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端与客户端两个部分.其中服务端也称为分布式配置中心,它是一个独立的微服务 ...
- java - 如何使一个类不能被继承
使用final关键字: 使用私有构造器: public final class InitTest{ private InitTest(){} }
- http的无状态
无状态协议是指协议对务处理没有记忆能力.缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大.另一方面,在服务器不需要先前信息时它的应答就较快. Http协议不 ...
- 第二章 javaScript操作BOM
什么是BOM BOM(Browser Object Model)即浏览器对象模型. BOM提供了独立于内容 而与浏览器窗口进行交互的对象: 由于BOM主要用于管理窗口与窗 ...
- python语言快捷注释
1.注释单行 (1)方法1:直接在单行代码前边加 # (2)方法2:选中需要注释的代码,Ctrl+/ 即可注释 2.注释多行代码 选中想要注释的N行代码,直接Ctrl+/ 即可注释 3.取消注释多行代 ...
- IDEA下基于MAVEN的SSM框架整合
源码可以以上传github https://github.com/ingxx/ssm_first 最近把用IDEA把SSM框架整合一遍遇到了不少坑,在这里写出来 这里maven我使用的是自己下载的3. ...
- 【WPF】大量Canvas转换为本地图片遇到的问题
原文地址:https://www.cnblogs.com/younShieh 项目中遇到一个难题,需要将上百个没有显示出来的Canvas存储为图片保存在本地. 查阅资料后(百度一下)后得知保存为本 ...
- 前端工程师和设计师必备的chrome插件
Google Chrome是最好用的几个浏览器之一,今天我来分享下自己收集的一系列Chrome插件,希望对大家的学习和工作有帮助. 注:你可以通过复制链接或者在谷歌商店搜索相应插件的名称来获取以下插件 ...
- 免安装版tomcat安装成服务
安装方式(前提已经安装好了jdk,并配置了环境量): (1) 下载非exe的tomcat zip包: (2) 解压缩,如:D:\tomcat: (3) 进入D:\tomcat\bin,修改servic ...