看到有些留言有問關於虛函數和 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。爲了說明這個問題,我們先來看這個例子:
- class CityModel : public QAbstractTableModel
- {
- Q_OBJECT
- public:
- CityModel(QObject *parent = 0);
- void setCities(const QStringList &cityNames);
- int rowCount(const QModelIndex &parent) const;
- int columnCount(const QModelIndex &parent) const;
- QVariant data(const QModelIndex &index, int role) const;
- bool setData(const QModelIndex &index, const QVariant &value, int role);
- QVariant headerData(int section, Qt::Orientation orientation, int role) const;
- Qt::ItemFlags flags(const QModelIndex &index) const;
- private:
- int offsetOf(int row, int column) const;
- QStringList cities;
- QVector<int> distances;
- };
CityModel 繼承自 QAbstractTableModel。下面我們去看看 QAbstractTableModel 的代碼,位於 src/corelib/kernel/qabstractitemmodel.h。我們發現,除去第一個 setCities(const QStringList &) 函數,其他的函數在其基類中都標有 virtual 關鍵字。
在面向對象設計中有一個概念是多態。多態的實現可以有很多種。例如,我們可以以父類的指針去指向一個子類的對象。爲什麼呢?因爲子類和父類是 is-a 的關係,也就是說,如果 B 是 A 的子類,那麼可以看成,B 是一個 A。我們就可以用父類的指針去指向子類的對象,例如下面的代碼:
- class Parent
- {
- public:
- virtual void func() { cout << "parent"; }
- void func2() { cout << "parent"; }
- };
- class Child : public Parent
- {
- public:
- virtual void func() { cout << "child"; }
- void func2() { cout << "child"; }
- };
- Parent *p = new Child;
- p->func();
- 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 設計人員,也面臨着這個問題。怎麼解決呢?來看一下下面的代碼:
- class AbstarctModel
- {
- public:
- virtual void setData();
- virtual int rowCount();
- virtual int columnCount();
- };
- class View
- {
- public:
- void setModel(AbstractModel *m) { model = m; }
- void showView()
- {
- int r = model->rowCount();
- int col = model->columnCount();
- // ...
- }
- private:
- AbstractModel *model;
- };
- class MyModel : public AbstractModel
- {
- public:
- void setData();
- int rowCount();
- int columnCount();
- };
- View *view = new View;
- 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 指針傳進去。
實際上,這是一個很有用的技術。幾乎所有的設計模式都是用這種技術,如果你希望再去深入學習各種設計模式,就要好好理解這種技術了。