zoukankan      html  css  js  c++  java
  • C++类

    this指针

    当一个类对象(或者指向类对象的指针)调用成员函数时,编译器会生成额外的代码,将对象的首地址(this指针)隐式的传给被调用的成员函数,使得在成员函数的内部能够访问到类对象的数据成员.
    下面举例观察下this在成员函数调用时传递过程:

    class CTest
    {
      int m_nNum;
    public:
      void print();
      CTest(int nNum);
    };
    
    CTest::CTest(int nNum) :m_nNum(nNum)
    {
    
    }
    
    void CTest::print()
    {
      printf("%d", m_nNum);
    }
    
    int main()
    {
      CTest t(1);
      t.print();
      return 0;
    }
    

    main函数对应汇编代码如下:

      CTest t(1);
      003914B8  push        1  
      003914BA  lea         ecx,[t]  
      003914BD  call        CTest::CTest (03910B4h)  
        t.print();
      003914C2  lea         ecx,[t]  
      003914C5  call        CTest::print (0391172h)  
        return 0;
      003914CA  xor         eax,eax  
      }
    

    可以看出CTest的构造函数和成员函数print的被调用前的最后一条指令lea ecx,[t]把
    对象的地址取出并放到ecx寄存器中,来看看构造函数的汇编代码:

    003913D0  push        ebp  
    003913D1  mov         ebp,esp  
    003913D3  sub         esp,0CCh  
    003913D9  push        ebx  
    003913DA  push        esi  
    003913DB  push        edi  
    003913DC  push        ecx  
    003913DD  lea         edi,[ebp-0CCh]  
    003913E3  mov         ecx,33h  
    003913E8  mov         eax,0CCCCCCCCh  
    003913ED  rep stos    dword ptr es:[edi]  
    003913EF  pop         ecx  
    003913F0  mov         dword ptr [this],ecx  
    003913F3  mov         eax,dword ptr [this]  
    003913F6  mov         ecx,dword ptr [nNum]  
    003913F9  mov         dword ptr [eax],ecx  
    003913FB  mov         eax,dword ptr [this]  
    003913FE  pop         edi  
    003913FF  pop         esi  
    00391400  pop         ebx  
    00391401  mov         esp,ebp  
    00391403  pop         ebp  
    00391404  ret         4  
    

    mov dword ptr [this],ecx 这句将调用对象的首地址赋值给this,
    this指针也位于当前调用函数的栈帧上:
    this指针.png

    print的汇编代码:

    00391420  push        ebp  
    00391421  mov         ebp,esp  
    00391423  sub         esp,0CCh  
    00391429  push        ebx  
    0039142A  push        esi  
    0039142B  push        edi  
    0039142C  push        ecx  
    0039142D  lea         edi,[ebp-0CCh]  
    00391433  mov         ecx,33h  
    00391438  mov         eax,0CCCCCCCCh  
    0039143D  rep stos    dword ptr es:[edi]  
    0039143F  pop         ecx  
    00391440  mov         dword ptr [this],ecx  
    00391443  mov         esi,esp  
    00391445  mov         eax,dword ptr [this]  
    00391448  mov         ecx,dword ptr [eax]  
    0039144A  push        ecx  
    0039144B  push        395858h  
    00391450  call        dword ptr ds:[399114h]  
    00391456  add         esp,8  
    00391459  cmp         esi,esp  
    0039145B  call        __RTC_CheckEsp (039113Bh)  
    00391460  pop         edi  
    00391461  pop         esi  
    00391462  pop         ebx  
    00391463  add         esp,0CCh  
    00391469  cmp         ebp,esp  
    0039146B  call        __RTC_CheckEsp (039113Bh)  
    00391470  mov         esp,ebp  
    00391472  pop         ebp  
    00391473  ret  
    

    mov dword ptr [this],ecx
    mov eax,dword ptr [this]
    mov ecx,dword ptr [eax]
    第一条指令是将调用对象的首地址赋值给this指针,因为CTest类中没有虚函数,所以数据成
    员m_nNum的内存地址与类对象的首地址重合, mov eax,dword ptr [this]和
    mov ecx,dword ptr [eax] 指令就可以将调用对象的m_nNum数据成员取出。

    至此是不是对this指针有一个清晰认识呢?其实上面的C++代码和下面的C代码类似:

    struct Test
    {
      int m_nNum;
    };
    
    void Construct(Test obj,int nNum)
    {
      obj.m_nNum = nNum;
    }
    
    void print(Test obj)
    {
      printf("%d", obj.m_nNum);
    }
    

    构造函数:

    构造函数特点:构造函数函数名和类名相同,构造函数没有返回值,构造函数不能显示调用。
    默认的构造函数:当类中没有声明构造函数时,C++标准规定编译器要为类定义一个默认的构造函数,
    当类中声明了构造函数,编译器不再生成默认的,默认的构造函数啥也不干
    注:VC++编译器一般不会为一个没有声明构造函数的类生成默认的构造函数,除非这个类中有继承关系
    或者类成员是一个类对象。

    析构函数:

    析构函数特点:函数名为”~类名”,无返回值,
    构造函数作用:当类对象生命周期到达终点时做一些清理工作,释放占用的资源

    构造函数和析构函数的调用时机:

    • 局部对象:在定义时调用普通构造函数,在出作用域时调用析构函数
    • 全局对象:在main函数之前调用普通构造函数,在main函数结束之后调用析构函数,生命周期几乎和进程一样长
    • 对象作为函数参数:
      在进如函数之前传参时调用拷贝构造函数,在函数返回时调用析构函数。
      注意:如果类中没有定义拷贝构造函数,编译器将自动生成默认的拷贝构造函数,其行为和memcpy相似,当类成员 中有指向动态分配资源的指针时,默认的拷贝构造函数是浅拷贝,仅仅复制了指针值,并没有申请资源,在 析构时可能会导致重复释放问题,因此需要自己实现拷贝拷贝构造函数。

    VC++无名对象的优化

    • 当函数的参数传入一个无名对象时,会优化掉拷贝构造函数的调用.
      例:

      class CTest
      {
        int nTest;
      public:
        CTest()
        {
          std::cout << "CTest()" << std::endl;
          std::cout << "this:" << std::hex << this << std::endl;
          std::cout << "
      " << std::endl;
        }
      
        CTest(int n)
        {
          std::cout << "CTest(int n)" << std::endl;
          std::cout << "this:" << std::hex << this << std::endl;
          std::cout << "
      " << std::endl;
        }
      
        CTest(const CTest &)
        {
          std::cout << "CTest(const CTest &)" << std::endl;
          std::cout << "this:" << std::hex << this << std::endl;
          std::cout << "
      " << std::endl;
        }
      
        ~CTest()
        {
          std::cout << "~CTest()" << std::endl;
          std::cout << "this:" << std::hex << this << std::endl;
          std::cout << "
      " << std::endl;
        }
      };
      

      进行如下测试:

      void funTest1(CTest t)
      {
        return;
      }
      
      int main(int argc, char **argv)
      {
        CTest t(2);
        funTest1(t);
        std::cout << "====================" << std::endl;
        funTest1(CTest(2));
        return 0;
      }
      

      运行结果:
      参数是无名对象时的优化1.png
      第一种情况下,先构造一个对象,然后将此对象作为参数传递给funTest1函数,传参过程中调用如果调用在传参数过程中调用了类的拷贝构造函数;
      第二种情况下,在调用funTest1函数时,我使用CTest(int n)构造了一个无名对象作为参数传递,此时VC++编译器为了优化,不在生成调用拷贝构造函数的代码;

    • 函数返回值为无名对象时,也会优化掉拷贝构造函数的调用,函数调用完成后如果有接收返回值的代码,那么无名对象
      的生命周期被延长,如果没有接收返回对象的代码,那么这个无名对象在这行代码执行完成后就被析构了

      例:

      CTest funTest2()
      {
        return CTest(1); 
      }
      
      int main(int argc, char **argv)
      {
        CTest t = funTest2();
        std::cout << "==================" << std::endl;
        funTest2();
        system("pause");
        return 0;
      }
      

      运行结果:
      参数是无名对象时的优化2.png

      可以看出并未调用拷贝构造函数,第二次调用funTest2()时由于没有接收返回对象,无名对象就被析构了

    const类对象

    • 在定义类对象时,可以使用关键字const修饰,表示这个对象是个常量,该对象的数据成员一经构造函数的初始化 后,便不能再在经过其它成员函数来修改数据成员

    • const类对象只是编译器在编译时保证其数据数据成员不被修改,但是运行时还是可以修改其数据成员的值

    • const类对象不能调用普通成员函数,只能调用使用const关键字修饰的常量成员函数,因为成员函数的调用时会隐 式传递this指针,但指向const类对象的指针是const classtype类型的,而普通类对象的this指针是classtype
      类型的,在传递this指针时,const classtype类型指针是无法转换为classtype类型指针

    • const成员函数
      如果一个成员函数不应该修改对象的数据成员,那么可以在函数尾部使用const修饰;const类对象可以调用const
      成员函数,非const类对象也可以调用const成员函数

    const数据成员

    如果类的常量数据成员只能在构造函数的初始化列表中进行初始化

    使用mutable修饰的可变数据成员

    该数据成员可以在任意的成员函数中修改,即使该函数是常量成员函数

    静态成员变量

    定义方式:在定义类的数据成员时使用static关键字修饰即可
    初始化方式:在源文件中单独初始化,不需要再次使用static关键字
    静态成员变量本质:受类域限制的全局变量,减少了和全局变量的名称冲突所有类对象共享一个静态成员变量,
    静态成员变量存放在数据区中,所以静态成员变量不参与类大小的计算

    静态成员函数

    定义方式:在函数的返回值前面加上static关键字即可,静态成员函数是类的组成部分,被限制在类域中
    注意:静态成员函数调用时不会隐式地传递this指针,所有只要通过"类名::静态函数名称"这种方式即可调用

  • 相关阅读:
    3、spring注解注入
    2、spring注入のArrayList数组对象注入方式
    1、基本知识
    1、log4j的配置与使用
    3、在Eclipse中使用JUnit4进行单元测试(高级篇)
    2、在Eclipse中使用JUnit4进行单元测试(中级篇)
    1、JUnit4简介
    使用BroadcastReceiver实现系统对手机电量进行提示
    使用BroadcastReceiver监听系统接收的短信
    使用BroadcastReceiver实现开机自动运行的Service
  • 原文地址:https://www.cnblogs.com/UnknowCodeMaker/p/11136838.html
Copyright © 2011-2022 走看看