目录
前文
一,什么是多态?
1.1 多态的概念
二, 多态的定义及实现
2.1 多态的构成条件
2.2 虚函数
2.3 虚函数的重写
2.3.1 虚函数重写的两个例外
2.4 C++ override 和 final
2.5 重载,重写(覆盖),隐藏(重定义)的区别
三,抽象类
3.1 概念
3.2 接口継承和实现継承
总结
前文
C++三大特性——封装,継承,多态,前两个我们都已经学习过了,现在让我们一起来学习最后一个特性——多态
一,什么是多态?
1.1 多态的概念
多态其实就是多种形态,实际上就是当完成某个行动时,不同的对象完成会产生不同的状况。如上图某景点门票,大学生,普通人,儿童所需要的票价都是不一样的,这就算是多态在实际生活的一种应用。
二, 多态的定义及实现
2.1 多态的构成条件
多态是在不同継承关系的类对象,去调用同一的函数,产生了不同的行为。如上面的Student继承了Person,Person对象买全价票,Student买半价票
但是我们需要主要构成多态要满足下面两个条件(后面会讲到两个特例)
1. 被调用的函数必须是虚函数,而且子类要对父类的虚函数进行重写
2. 必须通过父类的指针或者引用调用虚函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Person
{
public:
virtual void Buyticket()
{
cout << "全价票" << endl;
}
};
class Student :public Person
{
public:
virtual void Buyticket()
{
cout << "半价票" << endl;
}
};
void Func1(Person& p)
{
p.Buyticket();
}
int main()
{
Person p;
Student s;
Func1(p);
Func1(s);
return 0;
}
以上面为例,我们先简单使用一下多态,上述代码具体实现如下。
看到这里可能有的老铁很好奇,这怎么这么神奇,加一个virtual就可以实现,底层是怎么实现的?我们不要急,饭要一口一口吃,我们先来了解一下多态两个必要条件都离不开的虚函数
2.2 虚函数
虚函数:被virtual修饰的类成员函数称为虚函数。
class Person
{
public:
virtual void Buyticket()
{
cout << "全价票" << endl;
}
};
2.3 虚函数的重写
虚函数的重写是多态必不可少的条件之一,接下来我们先了解一下到底什么是虚函数的重写。
虚函数的重写(覆盖):子类中有一个和父类完全相同的虚函数(三同,即子类虚函数与父类虚函数的函数名,参数,返回值完全相同。),这样的称作子类的虚函数重写了父类的虚函数。
举个例子:
class Person
{
public:
virtual void Buyticket()
{
cout << "全价票" << endl;
}
};
class Student :public Person
{
public:
virtual void Buyticket()
{
cout << "半价票" << endl;
}
};
注意:这里还有另一种写法,就是在重写父类虚函数时,子类的虚函数前面可以不加virtual关键字,仍然构成重写,这是因为子类继承了父类的虚函数接口,即使不加virtual也保持虚函数属性,但是这种写法可读性差,还是建议用上面的写法。
class Person
{
public:
virtual void Buyticket()
{
cout << "全价票" << endl;
}
};
class Student :public Person
{
public:
//virtual void Buyticket()
void Buyticket()
{
cout << "半价票" << endl;
}
};
2.3.1 虚函数重写的两个例外
1. 协变(父类与子类虚函数的返回值不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。这个实际用到的不多,我们了解一下即可
class Person
{
public:
virtual Person* Buyticket()
{
cout << "全价票" << endl;
return this;
}
};
class Student :public Person
{
public:
virtual Student* Buyticket()
{
cout << "半价票" << endl;
return this;
}
};
2. 析构函数的重写(父类与子类析构函数名不同)
如果父类的析构函数是虚函数,那么子类的析构函数只要定义,无论加不加virtual关键字,都与父类的析构函数形成重写,这里父类和子类的析构函数名字不同,老铁们可能会觉得不满足上面多态的两个必要条件,实际上不是,在编译后,编译器会将类的析构函数统一命名为~destructor()
析构函数的应用场景也是有的,如下面的情况。
在上面例子中,只有Student的析构函数重写了Person的析构函数,delete对象调用析构函数,才能构成多态,才能保证p1,p2正确释放。
2.4 C++ override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。
1.final:修饰虚函数,表示该虚函数不能再被重写
class Person
{
public:
virtual void Buyticket final()
{
cout << "全价票" << endl;
}
};
class Student :public Person
{
public:
virtual void Buyticket()
{
cout << "半价票" << endl;
}
};
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Person
{
public:
virtual void Buyticket()
{
cout << "全价票" << endl;
}
};
class Student :public Person
{
public:
virtual void Buyticket override()
{
cout << "半价票" << endl;
}
};
2.5 重载,重写(覆盖),隐藏(重定义)的区别
三,抽象类
3.1 概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
上文我们提到了接口継承,那么什么是接口継承和实现継承
3.2 接口継承和实现継承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
总结
多态的基本内容到这里就告一段落了,下一篇文章讲解多态的底层原理,希望铁子们能有所收获