小心VB.NET中的除运算符"/"和"\"

前端之家收集整理的这篇文章主要介绍了小心VB.NET中的除运算符"/"和"\"前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

转载于:http://www.cnblogs.com/freshman0216/archive/2008/08/27/1276991.html

VB.NET中除运算符有两种,普通除"/"和整数除"\",如果我们写程序时不注意两者的区别,很容易造成潜在的错误,这种错误很隐蔽,不容易被发现。而且VB.NET中类型转换和C#差别很大,应该引起我们足够的重视,这些看似微不足道的细节却直接关系都我们代码的健壮性。

1.问题的引出

下面是开发中遇到问题代码的简化部分,输入大部分数据都没问题,但当输入数字为18时会抛出异常“System.ArgumentException: 偏移量和长度超出数组的界限,于从索引到源集合结尾处的元素数量在 System.Collections.ArrayList.GetRange(Int32 index,Int32 count)”。是什么原因使ArrayList集合越界呢?这和VB.NET中的除运算符有什么关系呢?当我们理解了VB.NET中两种除的区别以及类型转换(Double—>Integer)的实质后,问题的答案也就不言自明了。

  1. Sub Main()
  2. Console.WriteLine("请输入一个整数:")
  3. F1(Console.ReadLine())
  4. Console.ReadLine()
  5. End Sub
  6.  
  7. Public Sub F1(ByVal times As Integer)
  8. Dim list As ArrayList = New ArrayList()
  9. '填充数据
  10. For i As Integer = 0 To 29
  11. list.Add(i)
  12. Next
  13. '把集合list中元素分成times份进行处理
  14. Dim oneTimeNum As Integer = list.Count / times
  15. For i As Integer = 0 To times - 1
  16. Dim length As Integer = oneTimeNum
  17. If i = times - 1 Then
  18. '最后一次循环取集合中所有剩余数据
  19. length = list.Count - oneTimeNum * i
  20. End If
  21. '实际中这里是启动线程处理,这里简化只是来说明问题
  22. F2(list.GetRange(oneTimeNum * i,length))
  23. Next
  24. End Sub
  25.  
  26. Private Sub F2(ByVal al As ArrayList)
  27. '对ArrayList集合al进行处理
  28. End Sub


2.普通除"/"和整数除"\"

1)普通除:expression1 / expression2

结果是 expression1 除以 expression2 的完整的商,包括任何余数。执行除法之前,任何整数数值表达式(除数和被除数)都会被扩展为 Double。如果将结果赋给整数数据类型,Visual Basic 会试图将结果从 Double 转换成这种类型。

举例说明:30 / 18 =1.6666666666666667,执行除法前被除数30和除数18都扩展为Double类型,结果也为Double类型。

2)整数除:expression1\ expression2

结果是 expression1 除以 expression2 的整数商,它丢弃了所有余数,只保留整数部分(称为截断)。结果数据类型是数值类型,对应于 expression1 和 expression2 的数据类型。值得注意的一点,如果除数或被除数为浮点数,在执行除法前,编译器会采用“四舍六入五成双”的规则将其转换成Long类型,再执行除法。

举例说明:30 / 18 =1,只保留结果的整数部分。

3.VB.NET中的类型转换(Double—>Integer)

根据第二部分对普通除的解释,当CLR执行Dim oneTimeNum As Integer =30 / 18时,首先将被除数30和除数18都扩展为Double类型,进行除运算得到Double类型的结果1.6666666666666667,因为要将Double类型数据赋值给Integer类型变量,此时要执行强制类型转换,得到最终结果oneTimeNum = 2(可能很多人和我一样会奇怪结果为什么不是1)。我们从IL代码中查看VB.NET中从Double—>Integer类型转换的实质。

  1. .method public static void F1(int32 times) cil managed
  2. {
  3. // 代码大小 123 (0x7b)
  4. .maxstack 3
  5. .locals init ([0] class [mscorlib]System.Collections.ArrayList list,[1] int32 oneTimeNum,[2] int32 i,[3] int32 V_3,[4] int32 length,[5] int32 VB$t_i4$L0,[6] int32 VB$CG$t_i4$S0,[7] bool VB$CG$t_bool$S0)
  6. IL_0000: nop
  7. IL_0001: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
  8. IL_0006: stloc.0
  9. IL_0007: ldc.i4.0
  10. IL_0008: stloc.2
  11. IL_0009: ldloc.0
  12. IL_000a: ldloc.2
  13. IL_000b: Box [mscorlib]System.Int32
  14. IL_0010: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
  15. IL_0015: pop
  16. IL_0016: nop
  17. IL_0017: ldloc.2
  18. IL_0018: ldc.i4.1
  19. IL_0019: add.ovf
  20. IL_001a: stloc.2
  21. IL_001b: ldloc.2
  22. IL_001c: ldc.i4.s 29
  23. IL_001e: stloc.s VB$CG$t_i4$S0
  24. IL_0020: ldloc.s VB$CG$t_i4$S0
  25. IL_0022: ble.s IL_0009
  26. IL_0024: ldloc.0
  27. IL_0025: callvirt instance int32 [mscorlib]System.Collections.ArrayList::get_Count()
  28. IL_002a: conv.r8
  29. IL_002b: ldarg.0
  30. IL_002c: conv.r8
  31. IL_002d: div
  32. IL_002e: call float64 [mscorlib]System.Math::Round(float64) //重点看这句
  33. IL_0033: conv.ovf.i4
  34. IL_0034: stloc.1
  35. IL_0035: ldc.i4.0
  36. IL_0036: ldarg.0
  37. IL_0037: ldc.i4.1
  38. IL_0038: sub.ovf
  39. IL_0039: stloc.s VB$t_i4$L0
  40. IL_003b: stloc.3
  41. IL_003c: br.s IL_0070
  42. IL_003e: ldloc.1
  43. IL_003f: stloc.s length
  44. IL_0041: ldloc.3
  45. IL_0042: ldarg.0
  46. IL_0043: ldc.i4.1
  47. IL_0044: sub.ovf
  48. IL_0045: ceq
  49. IL_0047: stloc.s VB$CG$t_bool$S0
  50. IL_0049: ldloc.s VB$CG$t_bool$S0
  51. IL_004b: brfalse.s IL_0059
  52. IL_004d: ldloc.0
  53. IL_004e: callvirt instance int32 [mscorlib]System.Collections.ArrayList::get_Count()
  54. IL_0053: ldloc.1
  55. IL_0054: ldloc.3
  56. IL_0055: mul.ovf
  57. IL_0056: sub.ovf
  58. IL_0057: stloc.s length
  59. IL_0059: nop
  60. IL_005a: ldloc.0
  61. IL_005b: ldloc.1
  62. IL_005c: ldloc.3
  63. IL_005d: mul.ovf
  64. IL_005e: ldloc.s length
  65. IL_0060: callvirt instance class [mscorlib]System.Collections.ArrayList [mscorlib]System.Collections.ArrayList::GetRange(int32,int32)
  66. IL_0065: call void VBTest.Module1::F2(class [mscorlib]System.Collections.ArrayList)
  67. IL_006a: nop
  68. IL_006b: nop
  69. IL_006c: ldloc.3
  70. IL_006d: ldc.i4.1
  71. IL_006e: add.ovf
  72. IL_006f: stloc.3
  73. IL_0070: ldloc.3
  74. IL_0071: ldloc.s VB$t_i4$L0
  75. IL_0073: stloc.s VB$CG$t_i4$S0
  76. IL_0075: ldloc.s VB$CG$t_i4$S0
  77. IL_0077: ble.s IL_003e
  78. IL_0079: nop
  79. IL_007a: ret
  80. } // end of method Module1::F1


从IL代码可以看出,VB.NET中执行类型转换实际上是调用函数[mscorlib]System.Math::Round(float64),MSDN中对这个函数的解释:将双精度浮点值舍入为最接近的整数,如果参数为两个整数的中值,这两个整数一个为偶数,另一个为奇数,则返回偶数(也就是我们常说的“四舍六入五成双”)。
现在,可以很好的解释文章开始提出的问题了:由于输入18时,oneTimeNum的值为2,当循环到第16次时i = 15,此时执行list.GetRange(oneTimeNum * i,length)list.GetRange(30,2),已经超出了list的长度范围,所以会抛出异常。

4.C#和VB.NET的区别

1)C#中的除运算"/"符相当于VB.NET的整数除"\"运算符;
2)C#中从Double—>Integer类型转换必须要采用显示方式,且转换规则为直接舍弃小数位。

总结这次出现问题的根源是用C#语法去推断VB.NET语法,因为接触C#较早,而C#和VB.NET语法又大同小异,忽略了对VB.NET基本语法的学习,以后应多注意两种语言的差别,尽量减少类似的错误。还有一点需要注意,遇到问题的时候多查MSDN,似乎现在都习惯从网上寻求答案,但网上关于VB.NET除运算符的内容并不多,找了半天,才发现MSDN上写的很详细,我想查找资料的顺序应该是:MSDN—>CNBlogs找找看—>Google/Baidu。

猜你在找的VB相关文章