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 << "\r\n" << std::endl;
      }
    
      CTest(int n)
      {
        std::cout << "CTest(int n)" << std::endl;
        std::cout << "this:" << std::hex << this << std::endl;
        std::cout << "\r\n" << std::endl;
      }
    
      CTest(const CTest &)
      {
        std::cout << "CTest(const CTest &)" << std::endl;
        std::cout << "this:" << std::hex << this << std::endl;
        std::cout << "\r\n" << std::endl;
      }
    
      ~CTest()
      {
        std::cout << "~CTest()" << std::endl;
        std::cout << "this:" << std::hex << this << std::endl;
        std::cout << "\r\n" << 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指针,所有只要通过"类名::静态函数名称"这种方式即可调用

 

posted @ 2019-07-05 10:33  CodeMaker+  阅读(253)  评论(0编辑  收藏  举报