看到有些留言有问关于虚函数和 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 指针传进去。

实际上,这是一个很有用的技术。几乎所有的设计模式都是用这种技术,如果你希望再去深入学习各种设计模式,就要好好理解这种技术了。