C++入门——多态
目录
多态的概念
多态的定义以及实现
多态的构成条件
虚函数
虚函数的重写/覆盖
虚函数重写的一些其他问题
协变
析构函数的重写
override和final关键字
纯虚函数和抽象类
多态的概念
多态通俗来说就是多种形态。多态分为编译时多态(静态多态)和运行时多态(动态多态)。
静态多态就是我们之前讲的函数重载和函数模版,传不同类型的参数就可以调用不同的函数,通过参数不同达到多种形态。之所以叫做编译时多态,是因为他们实参传给形参的参数匹配是在编译时完成的。
动态多态,就是去完成某个行为,可以传不同的对象就会完成不同的行为。例如都是买票,普通人买票就是全价买票;学生买票就是打折。
多态的定义以及实现
多态的构成条件
多态是一个继承关系下的类对象,去调用同一函数,产生了不同的行为。
实现多态还必须有两个重要条件:
- 必须是基类的指针或者引用调用虚函数。
因为只有基类的指针或者引用才能既指向基类对象又指向派生类对象。
- 被调用的函数必须是虚函数,并且完成了虚函数重写/覆盖。
只有完成虚函数的重写/覆盖,基类和派生类之间才能有不同的函数,多态的不同类型效果才能达到。
#include<iostream>
using namespace std;
class person
{
public:virtual void buytic(){cout<<"全价"<<endl;}
};
class student:public person
{
public:virtual void buytic(){cout<<"打折"<<endl;}};
void func(person& p){p.buytic();}
int main()
{person p;student s;func(p);func(s);return 0;
}
虚函数
类成员函数前面加virtual修饰,那么这个成员函数被称为虚函数。注意:非成员函数不能加virtual修饰。
class person
{
public:virtual void buytic(){cout<<"全价"<<endl;}
};
虚函数的重写/覆盖
- 虚函数的重写/覆盖:派生类中有一个跟基类完全相同的虚函数。要求三同:即返回值类型,函数名字,参数列表相同。
- 如果派生类不重写基类的虚函数,则会直接继承并使用基类的虚函数实现。
- 重写可以被当做函数内容的重写!!
注意:在重写虚函数时,派生类的虚函数不加virtual关键字时也可以构成重写,但这种写法不是很规范,不建议用。
猜猜下面的代码输出的结果是什么:
class A
{
public:virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}virtual void test(){ func();}
};
class B : public A
{
public:void func(int val = 0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{B*p = new B;p->test();return 0;
}
结果是:
是不是很惊讶,原因是:
虚函数重写的一些其他问题
协变
派生类重写基类虚函数时,与基类虚函数返回值不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的引用或者指针时成为协变。
class person
{
public:virtual person* buytic(){cout << "全价" << endl;return nullptr;}void func(){buytic();}};
class student :public person
{
public:virtual student* buytic(){cout << "打折" << endl;return nullptr;}};
int main()
{person p;student s;p.func();s.func();return 0;
}
析构函数的重写
基类的析构函数为虚函数,此时派生类虚构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。
虽然基类和派生类的析构函数名字不一样,看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,都统一处理成了~destructor。
知识回顾
delete的原理:
1.在空间上执行析构函数,完成对象中资源的清理工作。
2.再用operator delete函数释放对象的空间。
class person
{
public://virtual~person(){cout << "~person()" << endl;}
};
class student :public person
{
public:~student(){cout << "~student()" << endl;}
};
int main()
{person* p=new person;person* s=new student;delete p;//~person+delete pdelete s;return 0;
}
如果不加virtual的话,可以看出只析构了student类中person的部分,会造成内存泄漏。但是加上virtual的话,我们就没有这个烦恼了。
override和final关键字
- override:可以帮助用户检测虚函数是否重写
当我们不小心拼写错误时,编译器并不能直接检查出来,加上override皆可以检测出来了。
- final:我们不想让派生类重写虚函数时可以用。
纯虚函数和抽象类
- 纯虚函数:在虚函数的后面写上=0,这个函数就是纯虚函数了。
virtual void buytic()=0
{cout << "全价" << endl;}
- 抽象类:包含纯虚函数的类叫做抽象类。
抽象类不能实例化出对象,所以在某种程度上也强制了派生类重写虚函数。因为不重写,派生类也是抽象类,不能实例化出对象