在说这个问题之前,先说两个需要知道的背景知识:
(1)语言的类型的强制转换不会修改原来的数据,会另外的开辟一个临时的或者程序中指定的空间来存储强制转换后的值。
(2)C++引用的实现是在符号表中动了手脚,把自己的变量符号对应的内存地址写成了它所引用的那个变量的内存地址了。
(3)C++的cout函数的执行,是根据变量的名称找到对应的内存地址,然后根据变量的类型从内存中抓取相应的数据。
有了上面的两个知识点,看下面的程序:
#include <iostream> using namespace std; int main) { float a = 1.0f; cout << int)a << endl; cout << int &)a << endl; cout << endl; float b = 0.0f; cout << int)b << endl; cout << int &)b << endl; return 0; }
程序执行结果:
首先先来看(int&)a是什么意思:
这句话的意思就是给a声明了一个匿名的引用,并且这个引用是临时的,int类型的。会再符号表中增加这么一个条目
(int&)a这个表达式就返回这个临时的变量temp,它是a的引用,只不过类型是int的。
所以在执行
cout << int &)a << endl;
这句话的时候,cout就根据temp的地址和类型抓取数据。
看完了(int&)a,那么(int)a呢?似乎很熟悉,但是真的知道具体的过程吗。也许吧,看下面:
前面说到,对数据的类型强制转换,不会修改原来的数据的内容。所以(int)a这个表达式会再符号表中产生这样一个条目:
看到了,这里的临时变量的内存地址不是原来的地址,是操作系统又重新分配了一块临时的内存地址。这块内存地址的值就是变量类型强制转换后的值。
这样可以看出来这两个语句一个是在原来的内存基础上,把float类型的数据以int输出,一个是强制转转数据类型,从新开辟了一块新的存储空间放转换后的值,然后再输出。
上面分析的是程序的原理,应该是这么实现的。但是编译器为了减少访问内存的次数(符号表也在内存中的哦~),经常用寄存器来处理这些临时的变量,看这个程序的汇编代码:
--- C:Program FilesMicrosoft Visual StudioMyProjectsTestThread estThread.cpp ----------------------------------------------------------------- 1: #include <iostream> 2: using namespace std; 3: 4: int main) 5: { 00401780 push ebp 00401781 mov ebp,esp 00401783 sub esp,48h 00401786 push ebx 00401787 push esi 00401788 push edi 00401789 lea edi,[ebp-48h] 0040178C mov ecx,12h 00401791 mov eax,0CCCCCCCCh 00401796 rep stos dword ptr [edi] 6: float a = 1.0f; 00401798 mov dword ptr [ebp-4],3F800000h//这里看到,1.0在内存中的存储方式,是单精度浮点数的存储方式 7: cout << int)a << endl; 0040179F push offset @ILT+195std::endl) 004010c8) 004017A4 fld dword ptr [ebp-4]//把这个内存单元中的数据以浮点数方式加载到浮点寄存器中 004017A7 call __ftol 0042133c)//这个函数就把浮点数寄存器中的数据转换成了int类型,并把结果放在了eax寄存器中。转换完之后的结果00000001h 004017AC push eax//输出eax中的数据 004017AD mov ecx,offset std::cout 0047ff88) 004017B2 call @ILT+245std::basic_ostream<char,std::char_traits<char> >::operator<<) 004010fa)//ILT是Debug模式下函数的入口表,Release下就直接调用函数了,245是int类型数据的输出函数序号 004017B7 mov ecx,eax 004017B9 call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 8: cout << int &)a << endl; 004017BE push offset @ILT+195std::endl) 004010c8) 004017C3 mov eax,dword ptr [ebp-4]//直接把a的值原封不动的copy到eax寄存去中 004017C6 push eax//输出eax中的数据 004017C7 mov ecx,offset std::cout 0047ff88) 004017CC call @ILT+245std::basic_ostream<char,std::char_traits<char> >::operator<<) 004010fa) 004017D1 mov ecx,eax 004017D3 call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 9: cout << a << endl; 004017D8 push offset @ILT+195std::endl) 004010c8) 004017DD mov ecx,dword ptr [ebp-4] 004017E0 push ecx 004017E1 mov ecx,offset std::cout 0047ff88) 004017E6 call @ILT+285std::basic_ostream<char,std::char_traits<char> >::operator<<) 00401122)//285是float类型数据的输出函数序号 004017EB mov ecx,eax 004017ED call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 10: 11: cout << endl << endl; 004017F2 push offset @ILT+195std::endl) 004010c8) 004017F7 push offset @ILT+195std::endl) 004010c8) 004017FC mov ecx,offset std::cout 0047ff88) 00401801 call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 00401806 mov ecx,eax 00401808 call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 12: 13: float b = 0.0f; 0040180D mov dword ptr [ebp-8],0 14: cout << int)b << endl; 00401814 push offset @ILT+195std::endl) 004010c8) 00401819 fld dword ptr [ebp-8] 0040181C call __ftol 0042133c) 00401821 push eax 00401822 mov ecx,offset std::cout 0047ff88) 00401827 call @ILT+245std::basic_ostream<char,std::char_traits<char> >::operator<<) 004010fa) 0040182C mov ecx,eax 0040182E call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 15: cout << int &)b << endl; 00401833 push offset @ILT+195std::endl) 004010c8) 00401838 mov edx,dword ptr [ebp-8] 0040183B push edx 0040183C mov ecx,offset std::cout 0047ff88) 00401841 call @ILT+245std::basic_ostream<char,std::char_traits<char> >::operator<<) 004010fa) 00401846 mov ecx,eax 00401848 call @ILT+470std::basic_ostream<char,std::char_traits<char> >::operator<<) 004011db) 16: 17: return 0; 0040184D xor eax,eax 18: }
View Code
从汇编语言中看到,(int)a是要经过类型强制转换的,并且把转换后的值放在寄存器中输出,(int&)a直接把原来的数据copy到一个寄存器中输出。
重要的说明一下:
符号表是在编译阶段产生的,上面说的temp和temp1这样的临时的变量也是在编译的时候都已经弄到了符号表中,只不过它 的作用域仅仅的就是那句话。不是在执行阶段在往符号表中增加的条目。
最后再说一个知识点:单精度浮点数、双精度浮点数的存储。
单精度和双精度浮点数的存储方式和int类型的存储方式是完全不同的,int的1在内存中的存储方式是00000001h,int的0是00000000h,但是浮点数要用符号位+阶码+尾数的方式存储。
以单精度的float为例:
它的形式是1.M * 2E-127,其中E是指数为的移码形式。所以对于float的1.0,尾数M=0,阶码E=127,符号位是0,所以对应的机器码是:3F800000h。
提示:为什么浮点数阶码部分要用移码?
(1)使用移码方便运算。2的指数部分有正有负,使用了移码之后,2的指数依然有正有负,但是数据的真正的存储位E就完全是正的值了,没有了负值,这样能加快运算。
(2)为了统一浮点数的0和整数的0。整数0的各个二进制位是全0(公认的了),但是如果不用移码,浮点数的全0是1,用了移码之后,这个是就是1.0 * 20-127,由于这个数太小了,这时会发生溢出,也就是0。