知识储备:
重提切片行为:
1.子类对象赋值给父类对象
2.父类型的指针指向了子类对象
3.父类型的引用引用子类的对象
虚函数:类的成员函数前面加virtual关键字,则这个成员函数称为虚函数
虚函数重写:当在子类的定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数
构成多态需要满足的条件:
1.函数的形参是父类的指针或者引用
2.调用的Func必须是虚函数的重写
构成多态的函数的调用只与调用函数函数的对象有关,(父类对象还是子类对象)
常规的函数调用只与类型有关,(父类还是子类)
小记:
1>虚函数使用基类指针或者引用调用时会发生动态绑定,
2>动态绑定又称晚绑定(运行时绑定),函数运行时的版本由实参决定,即在运行时选择函数版本。
3>类成员函数没有声明为虚函数,则其解析过程发生在编译而非运行时。
class Person
{
public:
virtual void BuyTickets()
{
cout << " 全价" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
virtual void BuyTickets()
{
cout << "半价" << endl;
}
protected:
int _num; //学号
};
//void Fun(Peron p) //不构成多态,与类型有关,这里都调用的是基类的函数
//void Fun(Person* p) //构成多态
void Fun(Person& p) //构成多态
// 不够多态的常规调用 -- 跟类型有关
// 构成多态 -- 跟对象有关
//构成多态的行为
// 1.父类指针和引用
// 2.调用的func必须是虚函数的重写
{
p.BuyTickets();
}
void Test()
{
Person p;
Student s;
Fun(p);
Fun(s);
}
总结:
1. 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2. 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
3. 只有类的非静态成员函数才能定义为虚函数, 静态成员函数不能定义为虚函数
4. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual。
5. 构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但是最好不要将operator=定义为虚函数,因为容易使用时容易引起混淆。
6. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。
7. 最好把基类的析构函数声明为虚函数。(why?析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里是因为编译器做了特殊处理)
8.虚表是所有类对象实例共用的
class A //把基类的析构函数最好声明为虚函数
{
public :
virtual ~A()
{
cout << "~A()" << endl;
}
};
class B :public A
{
public:
~B()
{
cout <<"~B()" << endl;
}
};
int main()
{
B b;
A& ra = b;
A* p = new B;
delete p; // p->destructor() + free(p)----只析构了A没析构B,如果~B()中有释放内存的操作,就会造成内存泄漏
//解决:将父类的析构函数定义成虚函数
//这里先发生切片,A类型的指针p 指向了B对象,
//当将父类的析构写成虚函数,在实际调用的时候,构成多态与类型指针的类型无关,直接调B的析构
system("pause");
return 0;
}
纯虚函数:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
class Person
{
public:
virtual void Display() = 0; //纯虚函数
protected:
string _name;
};
class Teacher : public Person
{
virtual void Display()
{
cout << "Teacher" << endl;
}
protected:
string _name;
};
class Student : public Person
{
virtual void Display()
{
cout << "Student" << endl;
}
protected:
string _name;
};
int main()
{
//Person p; //出错-抽象类无法创建对象
Person* p = new Teacher;
p->Display();
p = new Student;
p->Display();
return 0;
}
解惑:
1.什么是协变?
函数的返回值类型可以不同,但返回值类型必须是本类的指针或者引用。
2.构造函数为什么不能声明称虚函数?
1>构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。
2>虚函数的执行依赖于虚函数表。而虚函数表在构造函数的初始化列表中进行初始化工作,即初始化vptr,让它指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。
多态原理:
将父类的函数声明成虚函数,子类继承时就会生成自己虚表,即父子类具有各自不同的虚表。
当指针(或引用)指向父类时,就在父类的虚表中查找指定的函数,当指针执行子类时,便在子类的虚表中查找。