C++中(int&)和(int)的区别

在说这个问题之前,先说两个需要知道的背景知识:

(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类型的存储方式double类型数据的存储方式

以单精度的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。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注