1.
多态的概念
1.1
概念
多态的概念:通俗来说,就是多种形态,
具体点就是去完成某个行为,当不同的对象去完成时会
产生出不同的状态
。
举个栗子:比如
买票这个行为
,当
普通人
买票时,是全价买票;
学生
买票时,是半价买票;
军人
买票时是优先买票。
再举个栗子:
最近为了
争夺在线支付市场
,支付宝年底经常会做诱人的
扫红包
-
支付
-
给奖励金
的
活动。那么大家想想为什么有人扫的红包又大又新鲜
8
块、
10
块
...
,而有人扫的红包都是
1
毛,
5
毛
....
。其实这背后也是一个多态行为。支付宝首先会分析你的账户数据,比如你是新用户、比如
你没有经常支付宝支付等等,那么你需要被鼓励使用支付宝,那么就你扫码金额
=
random()%99
;比如你经常使用支付宝支付或者支付宝账户中常年没钱,那么就不需要太鼓励你
去使用支付宝,那么就你扫码金额
= random()%1
;总结一下:
同样是扫码动作,不同的用户扫
得到的不一样的红包,这也是一种多态行为。
ps
:支付宝红包问题纯属瞎编,大家仅供娱乐。
2.
多态的定义及实现
2.1
多态的构成条件
多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如
Student
继承了
Person
。
Person
对象买票全价,
Student
对象买票半价。
那么在继承中要
构成多态还有两个条件
:
1.
必须通过基类的指针或者引用调用虚函数
2.
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
2.2
虚函数
虚函数:即被
virtual
修饰的类成员函数称为虚函数。
2.3
虚函数的重写
虚函数的重写
(
覆盖
)
:
派生类中有一个跟基类完全相同的虚函数
(
即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同
)
,称子类的虚函数重写了基类的虚函数。
class
Person
{
public
:
virtual
void
BuyTicket
() {
cout
<<
"
买票
-
全价
"
<<
endl
;}
};
class
Person
{
public
:
virtual
void
BuyTicket
() {
cout
<<
"
买票
-
全价
"
<<
endl
; }
};
class
Student
:
public
Person
{
public
:
virtual
void
BuyTicket
() {
cout
<<
"
买票
-
半价
"
<<
endl
; }
/*
注意:在重写基类虚函数时,派生类的虚函数在不加
virtual
关键字时,虽然也可以构成重写
(
因
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性
),
但是该种写法不是很规范,不建议
这样使用
*/
/*void BuyTicket() { cout << "
买票
-
半价
" << endl; }*/
};
void
Func
(
Person
&
p
)
{
p
.
BuyTicket
(); }
int
main
()
{
Person ps
;
Student st
;
Func
(
ps
);
Func
(
st
);
return
0
;
}
虚函数重写的两个例外:
1.
协变
(
基类与派生类虚函数返回值类型不同
)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。(了解)
class
A
{};
class
B
:
public
A
{};
class
Person
{
public
:
virtual
A
*
f
() {
return new
A
;}
};
class
Student
:
public
Person
{
public
:
virtual
B
*
f
() {
return new
B
;}
};
2.
析构函数的重写
(
基类与派生类析构函数的名字不同
)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加
virtual
关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成
destructor
。
class
Person
{
public
:
virtual
~Person
() {
cout
<<
"~Person()"
<<
endl
;}
};
class
Student
:
public
Person
{
public
:
virtual
~Student
() {
cout
<<
"~Student()"
<<
endl
; }
};
//
只有派生类
Student
的析构函数重写了
Person
的析构函数,下面的
delete
对象调用析构函
数,才能构成多态,才能保证
p1
和
p2
指向的对象正确的调用析构函数。
int
main
()
{
Person
*
p1
=
new
Person
;
Person
*
p2
=
new
Student
;
delete
p1
;
delete
p2
;
return
0
;
}
2.4 C++11 override
和
fifinal
从上面可以看出,
C++
对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来
debug
会得不偿失,因此:
C++11
提供了
override
和
fifinal
两个关键字,可以帮
助用户检测是否重写。
1.
fifinal
:修饰虚函数,表示该虚函数不能再被重写
2.
override:
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
2.5
重载、覆盖
(
重写
)
、隐藏
(
重定义
)
的对比
3.
抽象类
class
Car
{
public
:
virtual
void
Drive
()
final
{}
};
class
Benz
:
public
Car
{
public
:
virtual
void
Drive
() {
cout
<<
"Benz-
舒适
"
<<
endl
;}
};
3.1
概念
在虚函数的后面写上
=0
,则这个函数为纯虚函数。
包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象
。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。