该如何理解下面段代码的第二行QWidget(parent)?
Widget::Widget(QWidget *parent) : QWidget(parent),//???? ui(new Ui::Widget) { ui->setupUi(this); }
一、派生类不能在成员初始化列表中直接初始化基类的成员
初始化基类成员
构造函数是不可继承的。因此,派生类的构造函数必须通过调用基类的构造函数初始化基类成员,不能够在派生类初始化列表直接初始化基类的成员,“越级初始化”。派生类的构造函数的一般格式为:
派生类名(形参表):基类名1(形参表1),基类名2(形参表2)···
{···}
注意事项:
(1)在创建派生类对象时,先调用基类的构造函数,然后调用派生类的构造函数;撤销对象时,析构函数被调用的顺序则相反。
(2)若派生类中包含对象成员,则派生类的构造函数初始化成员列表中既要列出基类的构造函数也要列出对象的构造函数。派生类定义对象时,先调用基类的构造函数,再调用对象的构造函数,最后调用派生类的构造函数。
二、派生类的构造函数的初始化列表问题
首先必须清楚一点:初始化与赋值是两码事!
派生类的构造函数的初始化列表可以包含基类的构造函数、派生类成员的初始化,但是不能有基类成员的初始化!
以下是错误的:
class A
{
public:
A(int i = 0):a(i){} //基类初始化成员变量
protected:
int a;
};
class B :public A
{
public:
B():a(5){}//派生类不能初始化基类成员变量
};
这样是正确的:
#include<iostream>
usingnamespace std;
class A
{
protected:
int a;
public:
A(int i = 0):a(i) {}
};
class B :public A
{
public:
B():A(5){}//基类的构造函数
void printinfo()
{
cout<< a<< endl;
}
};
这样也是可以的:
#include<iostream>
usingnamespace std;
class A
{
public:
A(int i = 0):a(i) {}
protected:
int a;
};
class B :publicA
{
public:
B() {a=5;}
};
第三种方式虽然也可以将B类对象的a设为5,但它的原理是改变a原本的值,而不是初始化!
当定义B类对象时,会去调用B的构造函数,派生类的构造函数会先调用基类A的构造函数。所以成员a是先被初始化为0,再被B的构造函数改为5(B可以访问A的公有及保护成员)。而第二种方式则是在A的构造函数初始化a时就把5传递了过去。
effictive C++ :尽量使用初始化列表而不是赋值。一个好的原则是,能使用初始化列表的时候尽量使用初始化列表。
三、关于C++初始化列表
C++初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。
主要是性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由测试可知,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。
除了性能问题之外,有些时场合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表
1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3. 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
四、书籍与Qt案例
书籍,C++ Primer4中的15.4.2派生类构造函数有提到相关知识。
案例,Qt源码有应用:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),//调用基类的构造函数,赋值父窗口指针
ui(new ui::MainWindow)
{
ui->setupUi(this);
}