float a; long b; 直接书写变量名也是一条语句,其导致编译器生成一条读取相应变量的内容的语句。即可以如下书写:
a;
上面将生成一条读取内存的语句,即使从内存中读出来的数字没有任何应用(当然,假如编译器开了优化选项,则上面的语句将不会生成任何代码)。从这一点以及上面的c = a / b * 120.4f;语句中,都可以看出一点——变量是可以返回数字的。而变量返回的数字就是按照变量的类型来解释变量对应内存中的内容所得到的数字。这句话也许不是那么轻易理解,在看过后面的类型转换一节后应该就可以理解了。 因此为了将数据写入一块内存,使用赋值语句(即等号);要读取一块内存,书写标识内存的变量名。所以就可以这样书写:a = a + 3; 假设a原来的值为1,则上面的赋值语句将a的值取出来,加上3,得到结果4,将4再写入a中去。由于C++使用“=”来代表赋值语句,很轻易使人和数学中的等号混淆起来,这点应注重。
而如上的float a;语句,当还未对变量进行任何赋值操作时,a的值是什么?上帝才知道。当时的a的内容是什么(对于VC编译器,在开启了调试选项时,将会用0xCCCCCCCC填充这些未初始化内存),就用IEEE的real*4格式来解释它并得到相应的一个数字,也就是a的值。因此应在变量定义的时候就进行赋值(但是会有性能上的影响,不过很小),以初始化变量而防止出现莫名其妙的值,如:float a = 0.0f;。
赋值操作符
上面的a = a + 3;的意思就是让a的值增加3。在C++中,对于这种情况给出了一种简写方案,即前面的语句可以写成:a += 3;。应当注重这两条语句从逻辑上讲都是使变量a的值增3,但是它们实际是有区别的,后者可以被编译成优化的代码,因为其意思是使某一块内存的值增加一定数量,而前者是将一个数字写入到某块内存中。所以假如可能,应尽量使用后者,即a += 3;。这种语句可以让编译器进行一定的优化(但由于现在的编译器都非常智能,能够发现a = a + 3;是对一块内存的增值操作而不是一块内存的赋值操作,因此上面两条语句实际上可以认为完全相同,仅仅只具有简写的功能了)。
对于上面的情况,也可以应用在减法、乘法等二元非逻辑操作符(不是逻辑值操作符,即不能a &&= 3;)上,如:a *= 3; a -= 4; a = 34; a >>= 3;等。
除了上面的简写外,C++还提供了一种简写方式,即a++;,其逻辑上等同于a += 1;。同上,在电脑编程中,加一和减一是经常用到的,因此CPU专门提供了两条指令来进行加一和减一操作(转成汇编语言就是Inc和Dec),但速度比直接通过加法或减法指令来执行要快得多。为此C++中也就提供了“++”和“—”操作符来对应Inc和Dec。所以a++;虽然逻辑上和a = a + 1;等效,实际由于编译器可能做出的优化处理而不同,但还是如上,由于编译器的智能化,其是有可能看出a = a + 1;可以编译成Inc指令进而即使没有使用a++;却也依然可以得到优化的代码,这样a++;将只剩下简写的意义而已。
a = 0; b = 1; ( a *= 2 ) && ( b += ++a ); 按照优先级的顺序,编译器发现要先计算a *= 2,再计算++a,接着“+=”,最后计算“&&”。然后编译器发现这个计算过程中,出现了“&&”左边的数字这个序列点,其要保证被优先计算,这样就有可能不用计算b += ++a了。所以编译器先计算“&&”的数字,通过上面的计算过程,编译器发现就要计算a *= 2才能得到“&&”左边的数字,因此将先计算a *= 2,返回a的地址,然后计算“&&”左边的数字,得a的值为0,因此就不计算b += ++a了。而不是最开始想象的由于优先级的关系先将a加一后再进行a的计算,以返回1。所以上面计算完毕后,a为0,b为1,返回0,表示逻辑假。
因此序列点的出现是为了保证一些非凡规则的出现,如上面的“&&”和“”。再考虑“,”操作符,其操作是计算两边的值,然后返回右边的数字,即:a, b + 3将返回b + 3的值,但是a依旧会被计算。由于“,”的优先级是最低的(但高于前面提到的“数字”操作符),因此假如a = 3, 4;,那么a将为3而不是4,因为先计算“=”,返回a的地址后再计算“,”。又:
a = 1; b = 0; b = ( a += 2 ) + ( ( a *= 2, b = a - 1 ) && ( c = a ) ); 由于“&&”左边数字是一个序列点,因此先计算a *= 2, b的值,但根据“,”的返回值定义,其只返回右边的数字,因此不计算a *= 2而直接计算b = a – 1得0,“&&”就返回了,但是a *= 2就没有被计算而导致a的值依旧为1,这违反了“,”的定义。为了消除这一点(当然可能还有其他应用“,”的情况),C++也将“,”的左边数字定为了序列点,即一定会优先执行“,”左边的数字以保证“,”的定义——计算两边的数字。所以上面就由于“,”左边数字这个序列点而导致a *= 2被优先执行,并导致b为1,因此由于“&&”是序列点且其左边数字非零而必须计算完右边数字后才恢复正常优先级,而计算c = a,得2,最后才恢复正常优先级顺序,执行a += 2和“+”。结果就a为4,c为2,b为5。
注重前面之所以会朝好的方向发展(即char转成float),完全是因为“==”的缘故,其要求这么做。下面考虑“=”:short b = 3543; char a = b;。因为b的值是short类型,而“=”的要求就是一定要将“=”右边的数字转成和左边一样,这样才能进行正确的内存的写入(简单地将右边数字返回的二进制数复制到左边的地址所表示的内存中)。因此a将为-41。但是上面是编译器按照“=”的要求自行进行了隐式转换,可能是由于程序员的疏忽而没有发现这个错误(以为b的值一定在-128到127的范围内),因此编译器将对上面的情况给出一个警告,说b的值可能被截断。为了消除编译器的疑虑,如下:char a = ( char )b;。这样称为显示类型转换,其告诉编译器——“我知道可能发生数据截断,但是我保证不会截断”。因此编译器将不再发出警告。但是如下:char a = ( char )3543;,由于编译器可以肯定3543一定会被截断而导致错误的返回值,因此编译器将给出警告,说明3543将被截断,而不管前面的类型转换操作符是否存在。