前两天面试的时候,刚好多态没看到。。。。多年没有用过(其实也就两年。。。QAQ),全线崩盘,so,认认真真的开始了复习。
首先,多态解决的问题就是派生类和基类有同名函数的情况下,如果都用基类的指针来指向二者的对象,那么该函数都会执行基类的函数。
举个栗子:
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle() {}
void ShowMember()
{
cout << "Parent"<<endl;
}
};
class Car:public Vehicle
{
public:
Car() { }
void ShowMember()
{
cout<<"child" << endl;
}
};
void test(Vehicle &temp)
{
temp.ShowMember();
}
int main()
{
Car c;
Vehicle v;
test(c);
test(v);
return 0;
}
此处Vehicle是基类,Car是派生类,两者都有同样的方法——ShowMember(),如果用一个基类指针指向这两个类的实例,会出现以下的情况:
就是派生类对象在执行这个函数的时候,选择了基类的版本而没有选择自己的。
所以,为了让它在被基类指针指向的时候仍然能够保持自己特性,选择采用多态的方法~
所以呢,要在基类的那个函数那里加上virtual关键字使它成为一个虚函数,然后就看看现在怎么样了:
成功了!
需要注意的问题有:
虽然方法在基类中声明了它自己是虚的以后在派生类中会自动成为虚方法。However,在派生类中强调一下也是一个好习惯吧~
另外一个问题,基类应该包含一个虚析构函数(注意构造函数好像不能为虚),因为如果使用了new和delete的话,析构函数不为虚,只会调用基类的析构函数,尽管它是一个派生类对象。
Dynamic binding and Static binding
Static binding:编译时确定将源代码中的函数调用解释为执行特定函数代码块。
Dynamic binding:运行时。(eg.多态)
隐式强制向上转换使基类指针或引用可以指向积累对象或者派生类对象,因此需要dynamic binding。
注意:如果是按值传递的话,比如,
void test(Vehicle v1) {
v1.ShowMember();
}
Car c;
Vehicle v;
test(c); //uses Vehicle::ShowMember();
所以,按值传递只将Car对象的Vehicle部分传递给了函数test()。
编译器对非虚方法采用的是static binding。
虚函数的工作原理(P504)
C++规定了虚函数的行为,但是将实现方法留给了编译器
通常,实现方法为:
给每个对象添加一个隐藏的成员,隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表.虚函数表中存储了为类对象进行声明的虚函数的地址。基类对象包含一个指针,指向基类中所有虚函数的地址表,派生类如果重新定义了虚函数,那么就把自己虚函数表中的函数地址改了(直接替换),如果新定义了新的虚函数,也把地址加入数组中。调用虚函数是,程序先查看存储在对象中的虚函数表的地址,然后转向相应的函数地址表。
使用虚函数时,
每个对象将增加存储地址的空间
对于每个类,编译器都创建一个虚函数地址表(数组);
对于每个函数调用,都需要执行一项额外的操作,在表中查找地址。
注意:以上成本均为使用虚函数时产生的。
再次声明:构造函数不能为虚函数,析构函数则需要定义为虚函数。要不然派生类中的对象就无法释放了。
只有成员函数才可以被声明为虚函数,友元不可以。
虚函数重新定义只能修改返回值,不能修改参数(修改参数不如直接重载,orz…)