正確理解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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM