• 正确理解Widget::Widget(QWidget *parent) :QWidget(parent)这句话(初始化列表中无法直接初始化基类的数据成员,所以你需要在列表中指定基类的构造函数)


    最近有点忙,先发一篇我公众号的文章,以下是原文。

    /********原文********/

    最近很多学习Qt的小伙伴在我的微信公众号私信我,该如何理解下面段代码的第二行QWidget(parent)

    1 Widget::Widget(QWidget *parent) :
    2    QWidget(parent)
    3 {
    4 }
    

    为了统一回复大家,小豆君特意写了这篇文章,方便初学者们学习。

    在讲解原因之前,先请大家看下面的一个例子

    #include <iostream>
    using namespace std;
    class Base
    {
    public:
        Base() :m_num(0){
            cout << "this is Base()" << endl;
        }
        Base(int val):m_num(val){
            cout << "this is Base(int val)" << endl;
        }
    private:
        int m_num;
    };
    

    1 上方代码定义了一个基类Base,并且有两个构造函数,一个是默认构造函数,一个是有一个整型参数的构造函数。

    class BaseChild: public Base
    {
    public:
        BaseChild(){
            cout << "this is BaseChild()" << endl;
        }
        BaseChild(int val): Base(val){
            cout << "this is BaseChild(val)" << endl;
        }
    private:
        int m_num;
    };
    

    2 上方代码定义了一个BaseChild类,并继承Base类,同样的,它也定义了两个构造函数,一个默认,一个有整型参数。

    int main(int argc, char *argv[])
    {
        BaseChild child1;
        BaseChild child2(5);
    
        return 0;
    }
    

    3 main函数实例化了两个子类实例,child1,child2。child1调用默认构造函数。child2调用有整型参数的构造函数。

    现在,我们运行程序,会有如下打印:

    看到了吗,我们发现:

    • 创建child1时,是先调用了Base的默认构造函数,再调用自己的默认构造函数
    • 创建child2时,是先调用了Base(int)这个构造函数,再调用自己的整型参数构造函数。

    所以我们回头看BaseChild的构造函数

    BaseChild(int val): Base(val){
            cout << "this is BaseChild(val)" << endl;
        }
    

    细心的同学,可能早就发现了,初始化列表中的Base(val)正是调用了我们Base基类的有参构造函数,而这样的写法就刚好是我们开头代码中的那段

    Widget::Widget(QWidget *parent) :QWidget(parent)
    

    所以Widget是调用了QWidget下面的构造函数

    QWidget(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
    

    所以得出如下总结:

    总结: · 如果不指定构造函数,则派生类会调用基类的默认构造函数 · 派生类构造函数的初始化列表只能初始化派生类成员,不能直接初始化继承成员,如果想 要调用基类的有参构造函数,则可以在派生类的初始化列表中显示指定

    以上总结,也告诉我们,当定义一个类时,最好为该类定义默认构造函数。

    至此,我们明白了这个写法为什么会这样写。

    好的,那么我们又提出一个问题,“调用QWidget(parent)这个构造函数,QWidget父类都做了哪些动作呢?”

    下面是QWidget源码中的一部分节选:

    QWidget::QWidget( QWidget *parent, const char *name, WFlags f )
        : QObject( parent, name ), QPaintDevice( PDT_WIDGET ),
          pal( parent ? parent->palette()		// use parent's palette
               : *qApp->palette() )			// use application palette
    {
        if ( parent ) {
    	QChildEvent *e = new QChildEvent( Event_ChildInserted, this );
    	QApplication::postEvent( parent, e );
        }
    }
    

    大家从上面可以看出,如果parent参数非空的话,那么该构造函数使用了其父窗口的调色板,并且发送了QChildEvent事件,这会让新的窗口成为parent所指窗口的子窗口,那么当父窗口被删除时,子窗口也会自动的被删除。

    这其实是用到了Qt对象树的概念,关于对象树,小豆君会在后面的分享中为大家介绍。

    如果你想要获得更多干货,请关注我的微信公众号:小豆君,只要关注,便可加入小豆君为大家创建的C++Qt交流群,方便讨论学习。

    https://zhuanlan.zhihu.com/p/31310536

    在上一篇文章“正确理解Widget::Widget(QWidget *parent) :QWidget(parent)这句话”中,小豆君讲了为什么要这样写的原因,后来很多朋友给我发私信,问我初始化列表的事情。

    所以,小豆君今天给大家总结下C++中的初始化列表。

    下面我们先看例子

    #include <iostream>
    using namespace std;
    class Base
    {
    public:
        Base(int val)
       {
           m_num = 0;
            cout << "create Base(int val)" << endl;
        }
    private:
        int m_num;
    };
    

    上边的代码,我先定义了一个Base类,并且定义了有一个整型实参的构造函数Base(int val)

    class BaseChild: public Base
    {
    public:
        BaseChild()
        {
            m_num = 0;
            cout << "create is BaseChild()" << endl;
        }
    
    private:
        int m_num;
    };
    
    int main(int argc, char *argv[])
    {
        BaseChild child;
    }
    

    上边的代码继承Base,定义了它的默认构造函数

    并且在主函数中创建BaseChild的对象child

    编译但报如下错误:

    这意思是说,没有Base的默认构造函数。

    结论1:如果没有定义任何构造函数,C++编译器会自动创建一个默认构造函数。
    结论2:如果已经定义了一个构造函数,编译器不会自动创建默认构造函数,只能显式调用该构造函数。

    在C++中,当创建一个对象时,编译器要保证调用了所有子对象的构造函数,这是C++强制要求的,也是它的一个机制。

    因为在Base中没有定义默认构造函数,只定义了一个有整型参数的构造函数,因此编译器并不会再去生成一个默认的构造函数,而BaseChild继承Base时,又没有显式地指定Base的构造函数,所以编译报错。

    如果我们不修改Base,那么,我们用什么办法不去调用默认构造函数,而是显式的调用Base带参构造函数呢。答案就是初始化列表。

    C++就为我们提供了这样的语法。即在冒号和这个构造函数定义体的左括号之间可指定基类构造函数,如下:

    BaseChild():Base(1)
    {
        cout << "create is BaseChild()" << endl;
    }
    

    现在,再编译程序,轻松通过。

    当然,初始化列表还可以对类本身的数据成员进行初始化,如对BaseChild成员m_num进行初始化:

    BaseChild():Base(1), m_num(0){...}
    

    中间要以逗号隔开。

    细心的同学,可能会提问,我们平常见到的都是

    int m_num = 0;

    而刚刚的代码是m_num(0),这是正确的,我们可以认为这就是调用了int类型的构造函数。类似的,new int(2)是一样的道理。

    上面是整数类型的赋值,那么,如果是对象之间的赋值呢,例如:

    BaseChild child = BaseChild();

    其实,这又涉及了另外一个话题,赋值构造函数和编译器的优化。

    其具体执行顺序是:

    1调用BaseChild构造函数,生成一个临时对象

    2给child成员赋值

    3创建child对象后,删除临时对象

    那么,针对上面的顺序,编译器有可能会优化代码为BaseChild child()直接创建child对象。

    最后,总结一下初始化列表吧:

    1 因为初始化列表中无法直接初始化基类的数据成员,所以你需要在列表中指定基类的构造函数,如果不指定,编译器则会调用基类的默认构造函数。

    2 推荐使用初始化列表,它会比在函数体内初始化派生类成员更快,这是因为在分配内存后,在函数体内又多进行了一次赋值操作。

    3 初始化列表并不能指定初始化的顺序,正确的顺序是,首先初始化基类,其次根据派生类成员声明次序依次初始化。

    好了,今天分享的内容就到这里吧,如果你想要获得更多干货,可关注我的微信公众号:小豆君,只要关注,便可加入我的C++Qt交流群,一起学习。

    https://zhuanlan.zhihu.com/p/33004628

  • 相关阅读:
    ESlint 格式化代码 备忘
    css 点击样式,水波纹(记录备用)
    RabbitMq 报错记录
    sql For xml path('') 备忘
    .net core Area独立成单独的dll文件
    刷shipid 简便方法
    实际操作--create DB link
    POS VB
    设置PL/SQL 快捷键
    要开始学习C#
  • 原文地址:https://www.cnblogs.com/findumars/p/9231742.html
Copyright © 2020-2023  润新知