1.虚函数的基础知识
1.1虚函数属于多态的内容之一
1.2满足动态多态的条件
要有继承关系(虚函数出现的地方,一定有类的继承关系)
重写父类中的虚函数(返回值,函数名,传入值都一样),此时子类的函数前面的virtual可以不写
1.3多态的使用
父类指针或者引用 指向 子类的对象,反之不行,下面会有案例
class Animal
{
public:
Animal(){}
void speak()
{
cout<<"动物在说话"<<endl;
}
virtual void sleep()
{
cout<<"动物在睡觉"<<endl;
}
};
class Cat:public Animal
{
public:
Cat(){}
void speak()
{
cout<<"小猫在说话"<<endl;
}
virtual void sleep()
{
cout<<"小猫在睡觉"<<endl;
}
};
void doSpeak(Animal &animal)//输入参数为父类,但子类也可以调用此函数
{
animal.speak();//输出 "动物在说话"
}
void doSleep(Animal &animal)
{
animal.sleep();//输出 "动物在说话"
}
void test01()
{
Cat cat;Animal anim;
doSpeak(cat);//输出 动物在说话 备注:speak()是普通函数
doSleep(cat);//输出 小猫在睡觉 备注:sleep()是虚函数
Animal *anP=& anim;
// Animal &animal=cat; 执行上面的doSpeak(Animal &animal)函数
// Animal *anP=& anim;
Cat *caP=&cat;
anP=&cat;
anP->speak();//动物在说话 备注:speak()是普通函数
anP->sleep();//小猫在睡觉 备注:sleep()是虚函数
//下面的表达错误
//caP=&anim; 报错
//caP->speak();
//caP->sleep();
}
//对比一下,发现只能是父类的指针变量,子类的对象的地址才行,反之不行
同名虚函数 virtual void sleep() 存在与子类和父类时,doSleep(cat);函数输出如上。
如果子类中没有这个同名虚函数,则执行父类的,结果为动物在睡觉
如果 父类中没有这个虚函数,则执行 doSleep(cat);报错,显示 animal.sleep();函数有错
1.4 虚函数的实现
记住,重点 父类的指针或者引用,指向子类的对象。
// Animal &animal=cat; 父类的类型,子类的变量
// Animal *anP=& anim;父类的类型,子类的地址
//Animal *ptr =new Cat(); 在此步,执行了Cat的构造函数
class Father;//基类(父类)
class Son:public Father{.....}//Son是Father的派生类
Son son;//son是类Son的一个实例
Father &ptr=son;//用派生类的对象初始化基类对象的使用
我们需要什么?
我们需要的是,父类的数据类型 定义的对子类引用 或者 指向子类的指针,例如函数 void doSpeak(Animal &animal)
用引用或者指针操作子类的虚函数。所以,父类的虚函数里面的内容其实无意义,因为不会用到的,我们需要的只是一个虚函数框架。
子类的虚函数要写实实在在的内容,因为关乎功能的实现!
3.C++纯虚函数及虚函数
3.1纯虚函数的结构
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” 。("=0"左边的是纯虚函数,不是包含"=0")
包含 纯虚函数 的类,已经不是普通类了,它是抽象类!
简单的纯虚函数:
virtual void func( )=0;
复杂的纯虚函数:
virtual bool Plan(const vector<PointXYZT>&obstacle_points,
const PointXYZT& start_xyz,
const PointXYZT& end_xyz,
vector<PointXYZT>*path_xyz)=0;//函数声明时,末尾加上‘=0'
3.2纯虚函数的功能功能
纯虚函数所在的类就是抽象类,它无法实例化对象,它只能被继承。这样做的目的也是为了让它被继承,由子类必须重写虚函数,并自定义虚函数要实现的功能。
抽象类可以包含具有实现的普通成员函数,不一定只能包含纯虚函数。而且抽象类的指针和引用类型可以指向派生类对象,但不能直接使用抽象类对象。
抽象类不能创建对象,但是可以创建对象指针!
派生类必须实现抽象类中的所有纯虚函数,否则该派生类仍然被认为是抽象类。
父类中的虚函数只有其名,子类中的虚函数有实际功能,这样做,只是为了方便扩展程序的功能,要扩展功能时,原有的功能不受影响,程序员只需要新写一个类继承父类即可,再重写虚函数和其它内容。
4.虚析构与纯虚析构
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() = 0; // 纯虚析构函数(只声明)
};
Base::~Base() {} // 纯虚析构函数的定义(必须提供定义,大括号里面什么都不需要写)
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor" << endl;
}
};
int main() {
Base* obj = new Derived();
delete obj;
return 0;
}
需要注意的是,纯虚析构函数和纯虚函数一样,在基类中不需要提供具体的实现,但必须提供定义(有函数体框架,不写内容即可)。这是为了告诉编译器如何在派生类的析构函数中调用正确的析构函数。如果没有提供纯虚析构函数的定义,编译器将无法通过链接该程序。
父类(基类)的析构函数必须为虚析构函数。
父类指针被子类对象说初始化时,如果父类析构函数前面不加virtual时,那么就会调用父类的析构函数,子类的析构函数不会被调用,造成内存泄漏。加了以后,父子两类的析构函数就都会被调用。
虚函数与指针:有了虚函数,看对象类型不看指针类型,对象类型是谁,就调用谁。
calss B :public A ;
A * pA= new B; 对象类型是B 调用B的同名函数;
B b;
A * pA= & b; 对象类型是B 调用B的同名函数;
虚函数的继承性
如果有类A B C D他们逐个继承前一个 ,B中的函数print() 是虚函数,那么及时C 和D中的同名print() 函数即使没写virtual他们也是虚函数。
一个类的类名末尾加 final 可防止被其它类继承!