谈论那些老掉牙的东西似乎再也没有什么必要,我们更有兴趣关心那些微妙的存在。
---------本周心情
Java语言的执行不同于C++,C++一般被编译成本机直接可执行的二进制码,但是Java会先被编译成具有标准格式非本机二进制码,即众所周知的字节码,然后再被Java解释器(JVM)解释执行。机制的不同,造成二者在执行逻辑上一样的代码在处理浮点数时得到的结果却有着微妙的差异。
1、JVM对double类型的运算略显疲惫
实例
C++版本:
float x = 1.8f;
float y = 3.0f;
cout<<x / y <<endl;
相应汇编:
16 : float x = 1.8f;
fld DWORD PTR __real@3fe66666
fstp DWORD PTR _x$[ebp]
17 : float y = 3.0f;
fld DWORD PTR __real@40400000
fstp DWORD PTR _y$[ebp]
18 : float i = x / y;
fld DWORD PTR _x$[ebp]
fdiv DWORD PTR _y$[ebp]
fstp DWORD PTR _i$[ebp]
Java版本:
float x = 1.8f;
float y = 3.0f;
System.out.println(x / y);
相应汇编:
0: ldc #2 // float 1.8f
2: fstore_1
3: ldc #3 // float 3.0f
5: fstore_2
6: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
9: fload_1
10: fload_2
11: fdiv
12: invokevirtual #5 // Methodjava/io/PrintStream.println:(F)V
将结果的小数点后的数尽可能多的输出
C++:0.6
Java:0.59999996
这两种语言的内部原理不同导致了不一样的结果,Java虚拟机是基于栈的risc指令集体系,4字节的float入栈后还是用4字节的区域去存储,计算时的精度损失就会比较大。C++则视当前的机器而定,比如我的是inter x86机器,计算浮点的协处理器也是寄存器栈结构的,每个寄存器是10字节,就是说每个4字节的float进入栈之后会用一个10字节的区域去存储,所以C++计算的时候进度自然高,不过这个视当前的机器而定。
2、Java编译器对字符串的编译与C++的不同
在讲这个主题之前我需要先引入一个叫“原始串”的概念,在这种字符串里没有转义,编译器不再处理字符串中的转义符即‘\’,区别这种字符串与普通字符串的方式是给原始串加一个前缀r,例如“\\d”的原始串是r“\d”,“\d”这样的字符串放在C++和Java的编译器上,会得到两个让你意想不到而且完全不同的结果,C++编译器成功编译了这个字符串编译后的结果是r“\d”,但是放在Java编译器中将会提示是编译错误。
总结原因其实很简单:C++编译器和Java编译器对字符串中转义符的处理不同。C++编译器在编译转义字符的时候先看转义符‘\’后面紧跟的字符,如果是符合转义规则的,例如‘\n’、‘\\’、‘\t’会进行特殊处理分别编译为换行符(值为)、r‘\’(值为)、制表符(值为)。而那些不符合转义规则的,例如‘\d’,则不进行任何处理直接编译成r‘\d’。Java编译器则不同,它把所有‘\’和紧跟字符的整体都当成“转义字符”,如果这个“转义字符”不符合转义规则的,例如‘\d’,则判为语法错误。
你可能认为这是很简单的问题,不值得什么深入的探讨,但是这个简单地问题在遇到正则表达式后,就会变得复杂,经常出现一些很微妙的问题而不被人察觉。正则表达式是一种处理字符串模式匹配问题的很好的工具,是一种高于语言之上的一种字符串语法规则,所以即使在不同的语言中,正则表达式匹配一段字符串所需的模式串其实是一样的。当你需要匹配一串数字后紧跟字母i的字符串的时候,你需要给正则表达式传入r“\d+i”的模式串(正则表达式也是用r‘\’作为转义,例如r‘\’本身在正则中就有特殊的转义的意思,所以在正则中匹配r“\”,你需要模式串r“\\”),注意我这里使用的是一个原始串,也就表示在Java中你需要传入“\\d+i”,而在C++中你既可以传入“\\d+i”也可以是“\d+i”,但是习惯上你会选择后者。这里潜伏着的一个极大的问题是编译器和正则本身都是用r“\”作为转义,在C++中当你要匹配r“\\u”的时候,你是否会认为所需要的模式串是“\\\\u”,可是当你这样使用的时候会发现根本不起作用,这个模式串仅能匹配r”\u”。对于这个问题的处理,我们需要将问题分成两部,为了排除编译器对转义处理对我们的干扰,我们使用模式串的时候将使用原始串,那么匹配r“\\u”的模式串应该是r“\\\\u”,因为正则也把r“\”作为转义,这样你就发现其实“\\\\”即(r“\\”)只能匹配一个r‘\’。这种微妙错误的产生源于程序员分不清模式串所需要的格式是编译器的需要还是正则表达式本身的需要。还有便是C++编译器对于不符合转义规则的字符串(例如“\d”)的容错机制让程序员产生了许多的错觉,所以他们在使用正则的时候会天然的认为匹配一个数字的模式串是“\d”,但是准确来说是r“\d”即(“\\d”)。