關於虛函數和 Qt 的 Event 函數的簡單說明

看到有些留言有問關於虛函數和 Qt 中的各種 event 的相關問題,考慮到留言回覆中的種種侷限,這裏先另起一篇吧。說明一下,這些都是 C++ 面向對象的特性,如果你不明白,應該考慮再多看看 C++ 哦~

1. QAbstractTableModel 例子中有很多定義的函數都並未看到被調用,我注意到了這一句話“這個函數在用戶編輯數據時會自動調用”說的是 setData() 函數,但是其他的難道也都是?可是這些都是自己定義的函數?系統怎麼會知道?

2. 像void MyTableWidget::mouseMoveEvent(QMouseEvent *event) 這類的事件到底是誰調用它的?就是說我不明白那個event的參數是誰傳給它的?

這個問題來自於 http://devbean.blog.51cto.com/448512/267972 和 http://devbean.blog.51cto.com/blog/448512/288742。爲了說明這個問題,我們先來看這個例子:

  1. class CityModel : public QAbstractTableModel   
  2. {   
  3.         Q_OBJECT   
  4.    
  5. public:   
  6.         CityModel(QObject *parent = 0);   
  7.    
  8.         void setCities(const QStringList &cityNames);   
  9.         int rowCount(const QModelIndex &parent) const;   
  10.         int columnCount(const QModelIndex &parent) const;   
  11.         QVariant data(const QModelIndex &index, int role) const;   
  12.         bool setData(const QModelIndex &index, const QVariant &value, int role);   
  13.         QVariant headerData(int section, Qt::Orientation orientation, int role) const;   
  14.         Qt::ItemFlags flags(const QModelIndex &index) const;   
  15.    
  16. private:   
  17.         int offsetOf(int row, int column) const;   
  18.    
  19.         QStringList cities;   
  20.         QVector<int> distances;   
  21. }; 

CityModel 繼承自 QAbstractTableModel。下面我們去看看 QAbstractTableModel 的代碼,位於 src/corelib/kernel/qabstractitemmodel.h。我們發現,除去第一個 setCities(const QStringList &) 函數,其他的函數在其基類中都標有 virtual 關鍵字。

在面向對象設計中有一個概念是多態。多態的實現可以有很多種。例如,我們可以以父類的指針去指向一個子類的對象。爲什麼呢?因爲子類和父類是 is-a 的關係,也就是說,如果 B 是 A 的子類,那麼可以看成,B 是一個 A。我們就可以用父類的指針去指向子類的對象,例如下面的代碼:

  1. class Parent  
  2. {  
  3. public:  
  4.     virtual void func() { cout << "parent"; }  
  5.     void func2() { cout << "parent"; }  
  6. };  
  7.  
  8. class Child : public Parent  
  9. {  
  10. public:  
  11.     virtual void func() { cout << "child"; }  
  12.     void func2() { cout << "child"; }  
  13. };  
  14.  
  15. Parent *p = new Child;  
  16. p->func();  
  17. p->func2(); 

最後一行,看似語句兩邊類型不同,實際上,由於 Child 是 Parent 的子類,父類的指針可以指向子類對象,因此這裏是合法的。這麼做有什麼好處呢?請看我們的 func() 函數是 virtual 的。而子類也有一個同名的 func() 函數構成了重寫的關係(注意,子類在重寫父類 virtual 函數時不需要寫出 virtual 關鍵字,這裏我們只是爲了明顯才寫出來)。virtual 關鍵字保證,在父類指針指向子類對象的情況下,正如我們這裏看到的,使用這個父類指針調用 virtual 函數,會執行子類的代碼。也就是說,我們的 p->func(); 會輸出 child。但是對於普通函數,例如這裏的 func2(),就沒有這種關係。因此,p->func2(); 還是輸出 parent。這就是 virtual 的作用。要理解爲什麼我們寫的函數有很多並沒有被我們調用,或者是 Qt event 函數的參數是被誰傳進來的,是被誰調用的,就得理解 virtual 的含義。

下面試想一下 Qt 的設計。比如我們的 model。你怎麼能知道用戶究竟需要什麼樣的 model 呢?難道你能夠窮盡世界中所有的 model,並且每一個給出一個類嗎?當然不可能。那麼怎麼辦呢?我們的 view 就是需要有 model 啊!對於 Qt 設計人員,也面臨着這個問題。怎麼解決呢?來看一下下面的代碼:

  1. class AbstarctModel  
  2. {  
  3. public:  
  4.     virtual void setData();  
  5.     virtual int rowCount();  
  6.     virtual int columnCount();  
  7. };  
  8.  
  9. class View  
  10. {  
  11. public:  
  12.     void setModel(AbstractModel *m) { model = m; }  
  13.     void showView()  
  14.     {  
  15.         int r = model->rowCount();  
  16.         int col = model->columnCount();  
  17.         // ...  
  18.     }  
  19. private:  
  20.     AbstractModel *model;  
  21. };  
  22.  
  23. class MyModel : public AbstractModel  
  24. {  
  25. public:  
  26.     void setData();  
  27.     int rowCount();  
  28.     int columnCount();  
  29. };  
  30.  
  31. View *view = new View;  
  32. view->setModel(new MyModel); 

AbstractModel 裏面有三個 virtual 函數。View 需要一個 AbstractModel 的指針用來在 showView() 函數中使用。我們怎麼讓用戶能夠簡單的使用 View 類呢?我們要求用戶去自定義一個 model,叫做 MyModel,這個 model 要求繼承 AbstractModel,並且必須重新它的三個函數。這樣,在我們建立 View 對象的時候,將這個 MyModel 的指針傳給 View 的 setModel() 函數。注意,這個函數的參數要求是 AbstractModel *,而由於 MyModel 是 AbstractModel 的子類,因此二者構成 is-a 的關係,所以這個函數也可以接受一個 MyModel 指針。這樣一來,我們就讓 View 和我們自己的 MyModel 協同工作起來。

從這個簡單的例子可以看出,我們自定義的 model 其實就是爲了提供我們自己的幾個函數,讓 Qt 在使用其父類指針調用 virtual 函數的時候,實際執行的是我們自己的代碼。這類似與一種運行時的代碼替換的功能。我們再仔細思考下 event 函數,其實也是類似的。注意,所有的 event 函數也是 virtual 的哦!當 Qt 去調用這些 virtual 函數的時候,就會把需要的 event 指針傳進去。

實際上,這是一個很有用的技術。幾乎所有的設計模式都是用這種技術,如果你希望再去深入學習各種設計模式,就要好好理解這種技術了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章