从Qt谈到C++(二):继承时的含参基类与初始化列表 提出疑问 C++继承与构造函数 初始化列表

当我们新建一个Qt的图形界面的工程时,我们可以看看它自动生成的框架代码,比如我们的主窗口名称为MainWindow,我们来看看mainwindow.cpp文件:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
}

不同于一般的继承,这里的父类的括号里带有参数,我们通常都使用过不带参数,甚至不带括号的基类名称。这里的基类为什么带着参数呢?

C++继承与构造函数

不能继承父类构造函数

C++中类的继承与Java中的不同,C++的派生类不能继承父类的构造函数和析构函数,只能继承父类的公有成员。所以这就会造成一种结果——我们无法使用基类的构造函数来对子类进行初始化!

类比Java

这里我假设大家都有Java的背景知识,没有也没关系,请听我讲。在Java中我们可以使用关键字super来直接调用父类的构造函数。比如我们定义两个类:Rectangle (矩形),Square (正方形)。从数学角度讲,正方形是一种特殊的矩形,所以我们的Square类继承自Rectangle类。在Java中像这样:

class Rectangle{
    public Rectangle(int x,int y){
        length = x;
        width = y;
    }
    public void area(){
        System.out.println("The area is "+length*width);
    }
    private int length;
    private int width;
}
class Square extends Rectangle{//Java中使用extends关键字表示继承

    public Square(int x) {
        super(x, x);
    }
}
在派生类Square的构造函数中我们使用了,super这一关键字,它会默认调用基类的构造函数来初始化派生类。所以它相当于用一个整型x来初始化长方形的长和宽,所以我们得到的是一个正方形。可以验证一下,我们再使用一个类来验证这个Square是否可用,关键代码如下:

	public static void main(String[] args) {
		Square s = new Square(4);
		s.area();
	}
打印结果就是  The area is 16

C++实现方案

先依样画葫芦,写个C++版Rectangle:

class Rectangle
{
public:
    Rectangle(int x,int y);
    void area();
private:
    int length;
    int width;
};
Rectangle::Rectangle(int x, int y)
{
    length = x;
    width = y;
}
void Rectangle::area()
{
    cout<<"The area is "<<length*width<<endl;
}
派生类的声明部分,我们也可以实现;

class Square:public Rectangle
{
public:
    Square(int x);
};

这只是声明了Square的构造函数,但是我们该如何实现呢?我们的C++可是没有super这一关键字的,而且C++派生类不会继承基类的构造函数。

有人说我可不可以这样:

Square::Square(int x):Rectangle
{
    Rectangle::Rectangle(x,x);
}

答案当然是 NO!!。为了解决这一矛盾,C++提供了继承含参基类的实现方法,实现构造函数的方法就是这样:

Square::Square(int x):Rectangle(x,x)
{

}
看懂没有,不要奇怪这个空函数体,我们所需要的初始化操作已经完成。在main函数中试试:

int main()
{
    Square s(4);
    s.area();
    return 0;
}
注意在声明部分就是和普通的继承声明是一样的,基类也不用加参。

By the Way,讲一下一个类的构造过程。

类的构造过程

  • 首先,它的基类(如果有)的构造函数被调用。
  • 然后,它的成员的构造函数被调用(如果有)。
  • 最后,它自己的构造函数被调用。

当然了,这是题外话。

初始化列表

我们再回过头来看看,最初的那段Qt代码:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
}
现在,前面的 QMainWindow(parent) 这部分我们刚才讲完了。接下来看看后面的
ui(new Ui::MainWindow)
其实这就是构造函数的初始化列表。其实理解起来要容易的多。我们的类MainWindow有一个成员ui,它是MainWindow类型(这里ui的类型和它所处的类是相同的,这是特殊的情况,我们不用计较)。举个更一般的例子。

还记得我们刚才的矩形么?我们可以使用这种方法来初始化它。

Rectangle::Rectangle(int x, int y):length(x),width(y)
{
}
效果等价于刚才的:

Rectangle::Rectangle(int x, int y)
{
    length = x;
    width = y;
}
明白了吧,但是其实两种有点不同,那就是系统先调用初始化列表来初始化,接着会调用构造函数体内部的代码来初始化,也就是说,后者会覆盖掉前者。
Rectangle::Rectangle(int x, int y):length(x),width(y)
{
    length = 2*x;
    width = 2*y;
}
然后main函数中:

int main()
{
    Rectangle r(3,4);
    r.area();
    return 0;
}
它的输出结果 是 The area is 48

顺便一提,在一个对象初始化的时候会先调用其基类的构造函数,其成员对象的构造函数,最后是自己的构造函数。而析构的时候顺序恰好相反。