c++中的多态
什么是多态
多态是指有多种形态,具体来说就是一种行为有多种不同的状态。当有不同的对象完成同一个行为时,会产生不同的结果。在c++的调用同一个基类函数,会根据不同的对象的类型来调用不同的成员函数。多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
多态的构成条件
在c++中要构成多态,必须要满足两个条件:
- 调用函数的对象必须是引用或指针
- 被调用的函数必须是虚函数,且完成了虚函数的重写。
多态是基于继承实现的,在继承关系下,不同类的对象,通过调用同一个基类的接口函数,可以根据不同的对象调用到不同的成员函数。
要构成多态则在调用时,调用的函数由对象来决定,而不根据调用的对象的类型来决定。
简单的多态的例子
#include <iostream>
using namespace std;
class Father{
public:
virtual void Eat(){
cout << "吃两碗饭" << endl;
}
protected:
int count;
};
class Son : public Father{
public:
virtual void Eat(){
cout << "吃一碗饭" << endl;
}
private:
int count;
};
int main()
{
Father* father1;
Father father;
Son son;
// 此时的调用是根据对象来决定的,而不是根据类型来决定的
//根据对象来决定,不同的对象调用会产生不同的结果
father1 = &father;
father1->Eat(); //吃两碗饭
father1 = &son;
father1->Eat(); //吃一碗饭
//根据类型来决定
father2 = father;
father2.Eat(); //吃两碗饭
father2 = son;
father2.Eat(); //吃两碗饭
return 0;
}
重写
虚函数:在函数前加virtual即为虚函数。
虚函数的重写: 在继承关系中派生类中有一个和基类完全相同的虚函数,即函数名, 参数类型, 参数个数,返回值(协变时可以不相同)都相同。此时称该虚函数重写了基类中的虚函数,重写也叫覆盖。此时在派生类中只能访问到派生类中的该函数,从基类中继承的该函数以经被派生类中的该函数覆盖。
协变:在重写的过程中有一个例外,其返回值可以不同,此时也构成重写。但此时返回值必须是派生类或基类指针,派生类或基类的引用。
不规范的重写行为:当派生类对基类的虚函数进行重写时,可以不用加virtual ,因为从基类继承下来的而虚函数,其默认的属性仍为虚函数,我们只对其进行重写。但这是一种不规范的写法
派生类的析构函数与基类的析构函数构成了重写。
派生类的析构函数与基类的析构函数尽管函数名不相同,但在编译器的特殊处理下,两个析构函数被处理成了同名函数destructor。所以要将基类的析构函数做好写成虚函数。
为什么将析构函数写成虚函数
如果不使用在析构函数的时候不使用virtual,使用动态绑定,则在析构的时候就会忽略掉派生类的部分,如果我们派生类中进行了空间的开辟,而在派生类的析构中对其进行释放,如过不调用派生类析构,会造成内存泄漏。
class Father {
public:
virtual ~Father() { cout << "~Father()" << endl; }
};
class Son : public Father {
public:
~Son() { cout << "~Son()" << endl; }
};
//只有基类析构函数与派生类的析构函数构成多态时,在析构时才能正确的析构不同对象
int main()
{
Father* father1 = new Father;
Father* father2 = new Son;
delete father1;
delete father2;
// 结果 ~Father()
// ~Son()
// ~Father
return 0;
}
//当基类的析构函数不是虚函数时,两个析构函数没有构成多态,
//所以在析构的时候,根据类型析构,忽略了派生类的部分,会造成内存泄漏
class Father {
public:
~Father() { cout << "~Father()" << endl; }
};
class Son : public Father {
public:
~Son() { cout << "~Son()" << endl; }
};
int main()
{
Father* father1 = new Father;
Father* father2 = new Son;
delete father1;
delete father2;
/* 结果: ~Father()
~Father()
//可以看到只对基类不部分进行了析构,而并没有对派生类部分进行析构
*/
return 0;
}
关于重载 隐藏 重写三者之间的关系
重载:在同一个作用域中即同一个类中,函数名相同,参数个数或类型不同就构成了重载。
隐藏:在两个不同的作用域中,即在继承关系中的基类和派生类两个作用域中,如果派生类与基类中有同名函数,如果不是重写就一定构成了隐藏。隐藏也叫重定义。
重写:在两个不同的作用域中,即派生类与基类中如果函数名,参数,返回值(协变除外)都相同,就构成了重写。两个函数都必须是虚函数。重写称为覆盖。
实现继承与接口继承
- 实现继承:即为一般的继承,派生类继承基类的函数进行使用。
- 接口继承:派生类继承基类的函数后对其进行重写,相当于仅继承了其接口。
抽象类
纯虚函数:
纯虚函数即值在函数后面添加 = 0 ,只对函数进行声明,并不对函数进行实现。而在派生类中对于函数进行实现。
- 包含纯虚函数即的类为抽象类
- 抽象类不能实例化出对象
- 如果继承了抽象类的派生类不重写纯虚函数,该派生类仍为抽象类,也不能实例化对象
- 当派生类中将继承的抽象类的纯虚函数都重写实现了,才可以实例化出对象。
- 抽象类的纯虚函数必须被重写,可以在派生类或派生类的派生类,可以一直被继承下去在,直到被重写实现为止。
- 纯虚函数相当于接口,体现出了接口继承
#include <iostream>
using namespace std;
class Person{
public:
virtual void Drink() = 0;
};
class Children : public Person{
public:
virtual void Drink() {
cout << "drinking juice " << endl;
}
};
class Parents : public Person{
public:
virtual void Drink() {
cout << "drinking wine" << endl;
}
};
int main()
{
Person* per = new Person; //会报错,无法实例化对象
Children* chil = new Children;
return 0;
c++11 中的两个关键字
final
- 被final修饰的类不能被继承
- 被final修饰的函数不能被重写
- 被final修饰的变量不能被修改
override:
在派生类中显示的声明,那些函数需要重写,如果没有重写,编译器就会报错。
class Parents : public Person{
public:
virtual void Drink() override {
cout << "drinking wine" << endl;
}
};