C++ 默认构造函数

文章讲述了如何在C++编程中控制构造函数的行为,包括使用explicit消除默认构造函数的隐式类型转换,以及构造函数何时调用基类和成员的默认构造函数,特别强调了虚函数和虚拟基类对构造函数合成的影响。

我们在定义一个类的时候,编译器会帮助我们完成一些工作,其中一项就是生成默认的构造函数。

先看一下下面的程序,能够将一个int 型的变量赋值给到一个class Animal,这种变换很多时候会引起不必要的错误。

class Animal
{

  public:
  //  head head_;
    Animal(int a) {
        age = a;
    }
    int age;
};
int __cdecl main(int argv,wchar_t * pArgs[])
{
    Animal animal_=0;

    return 0;
}

为了解决这个错误我们需要消除这种编译器的自动行为,让类型转换由程序员来控制,加上explicit关键字就可以消除这个错误。

class Animal
{

public:
  //  head head_;
   explicit Animal(int a) {
        age = a;
    }
    int age;
};

如果我们没有定义一个构造函数,这个时候会出现什么情况,从下面的图片可以看出构造函数没有做任何事情,连变量age 都没有初始化为0。

这个时候我们可以认为编译器自动生成了一个构造函数,但是没有很好的用途。那什么时候编译器自动生成的构造函数时有实际的用途的呢。下面这些情况下是有实际的用途的。

类的成员变量带default Constructor

class head
{
public:
    head() { cout << "head construct" << endl; };
};
class Animal
{

public:
    head head_;

    int age;
}

运行程序可以发现hea的constructor 有被调用,但是实际上age 还是没有被初始化,编译器生成的构造函数只是调用了head的默认构造函数。

如果我们自己定义一个构造函数并且在里面给age 赋值

class Animal
{
public:
    explicit Animal(int a) 
    { 
        age = a;
        cout << "Animal Construc" << endl;
    }
public:
    head head_;

    int age;
};

运行程序发现head的构造函数还是会被调用。

编译器会自动扩张类中的构造函数,把成员的default  constructors 安插进去,user code 调用之前先调用default constructors。Animal 的构造函数变成了大概下面这个样子。

    explicit Animal(int a) 
    { 
        head_.head::head();
        age = a;
        cout << "Animal Construc" << endl;
    }

如果类中有多个带有default constructor 的成员, 按照成员的定义顺序来调用constructor。好了

类的Base Class 带 default Constructor

如果一个没有default constructor 的类派生自一个带有default constructor 的base class,编译器会自动生成一个,并且调用基类的构造函数,如果有多个基类,根据声明的顺序调用他们的基类。

class head
{
public:
    head() { cout << "head construct" << endl; };
};
class foot
{
public:
    foot() { cout << "foot construct" << endl; }
};
class Animal:public foot, head
{

public:

    int age;
};

从运行的结果可以看出来基类的构造函数foot 和head 分别被调用了 

如果派生类中定义了构造函数那结果会是什么样的,我们在Animal 中定义一个构造函数。

    Animal(int a)
    {
        age = a;
        cout << "Animal Construc" << endl;
    }

从运行的结果中可以看出,构造函数中调用了基类的构造函数。实际上编译器会扩张user 的constructor 在里面加入基类的构造函数。如果派生类中的成员带有default constructor,成员的default constructor 会在base class 的constructor 调用之后调用。

class head
{
public:
    head() { cout << "head construct" << endl; };
};
class foot
{
public:
    foot() { cout << "foot construct" << endl; }
};
class eye
{
public:
    eye() { cout << "eye construct" << endl; }
};
class Animal:public foot, head
{

public:
    Animal(int a)
    {
        age = a;
        cout << "Animal Construc" << endl;
    }
    eye eye_;

    int age;
};
int __cdecl main(int argv,wchar_t * pArgs[])
{

    Animal animal_(0);


    return 0;

}

类中带有 virtual function

一个类中定义了一个虚函数或者基类中有虚函数这两种情况都需要编译器来合成default constructor来完成一些虚函数调用相关的工作。

从中可以看到_vfptr这个指针被赋值指向了类的虚函数列表中的虚函数 0x00007FF796BACF58这个是虚函数指针的值,它指向的内存值是 30 12 ba 96 f7 7f 00 00 00 00 转换出来0x00007FF796BA1230,通过 0x00007FF796BA1230 可以找到对应的执行函数ConsoleApplication3.exe!Bell::flip(void)

为了完成虚函数的功能,编译器必须为每一个带有虚函数的class 对象或者派生类的对象设定 _vfptr初值,放置虚函数列表的地址。

1:一个virtual function table 会被编译器生成,里面放置虚函数的地址

2:每一个class object 中会产生一个虚函数指针,指向virtual function table

类 中带有一个 virtual base class

class x { public: int i; };
class A :public  virtual x { public:int j; };
class B :public virtual  x { public: double  d; };
class C :public A, public B { public: int k; };
void foo( A* pa) { pa->i = 1024; }
int __cdecl main(int argv,wchar_t * pArgs[])
{
    A* pa = new A;
    C* pc = new C;
    foo(pa);
    foo(pc);

    return 0;
}

编译器在构建的时候生成一个指针指向x 的i,在运行的时候通过具体的值来对应上相关的变量。对于class 所定义的每一个constructor,编译器会安插那些“允许每一个virtual base class 的执行期存取操作”的码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值