第17课 - 继承与多态 - 上
引入:
如果子类定义了与父类中原型相同的函数会发生什么?
1. 函数重写
在子类中定义与父类中原型相同的函数,函数重写只发生在父类与子类之间。
父类中被重写的函数依然会继承给子类,默认情况下子类中重写的函数将隐藏父类中的函数,通过作用域分辨符::可以访问到父类中被隐藏的函数。
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent
{
public:
void print()
{
cout<<"I'm Parent..."<<endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout<<"I'm Child..."<<endl;
}
};
int main(int argc, char *argv[])
{
Child child;
child.print();
child.Parent::print();
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
运行结果:
I'm Child...
I'm Parent...
当函数遇到兼容性问题的时候:
#include <cstdlib>
#include <iostream>
using namespace std;
class Parent
{
public:
void print()
{
cout<<"I'm Parent..."<<endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout<<"I'm Child..."<<endl;
}
};
void howToPrint(Parent* p)
{
p->print();
}
void run()
{
Child child;
Parent* pp = &child; //赋值兼容性原则,当使用父类的时候都能用子类来代替。
Parent& rp = child; //Parent类引用,应用Child类对象
child.print();
pp->print(); //指针打印
rp.print(); //引用的打印
}
int main(int argc, char *argv[])
{
run();
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
运行结果:
I'm Child...
I'm Parent...
I'm Parent...
分析:
pp执行child,rp引用child。但是后面的两条语句都没有打印I'm Child...,而是I'm Parent...。
l 问题所在:
(1) C++与C相同,是静态编译型语言。
(2) 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象。
(3) 所以编译器认为父类指针指向的是父类对象(根据赋值兼容性原则,这个假设合理)。
(4) 由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象。
(5) 从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。
对于下面的程序:
void howToPrint(Parent* p)
{
p->print();
}
在编译这个函数的时候,编译器不可能知道指针 p 究竟指向了什么。但是编译器没有理由报错。于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。
2. 程序—函数重写实例(江湖恩怨)
#include <cstdlib>
#include <iostream>
using namespace std;
/*大boss挑战庄主与少庄主*/
class Boss //大boss
{
private:
static Boss* cInstance; //单例模式,就一个 .声明
Boss()
{
}
public:
static Boss* GetInstance()
{
if( cInstance == NULL )
{
cInstance = new Boss();
}
return cInstance;
}
int fight()
{
cout<<"Boss::fight()"<<endl; //大boss出招
return 10;
}
};
Boss* Boss::cInstance = NULL;
//静态变量上面是声明,下面是定义,否则没有意思。
class Master //庄主
{
public:
virtual int eightSwordKill() //八剑齐飞
{
cout<<"Master::eightSwordKill()"<<endl;
return 8;
}
};
class NewMaster : public Master //少庄主
{
public:
virtual int eightSwordKill()
{
cout<<"NewMaster::eightSwordKill()"<<endl;
return Master::eightSwordKill() * 2;
}
};
void fieldPK(Master* master, Boss* boss)
{
int k = master->eightSwordKill();
int b = boss->fight();
if( k < b )
{
cout<<"Master is killed..."<<endl;
}
else
{
cout<<"Boss is killed..."<<endl;
}
}
int main(int argc, char *argv[])
{
Boss* boss = Boss::GetInstance();
cout<<"Master vs Boss"<<endl;
Master master;
fieldPK(&master, boss);
cout<<"New Master vs Boss"<<endl;
NewMaster newMaster;
fieldPK(&newMaster, boss);
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
运行结果:
Master vs Boss
Master::eightSwordKill()
Boss::fight()
Master is killed...
New Master vs Boss
NewMaster::eightSwordKill()
Master::eightSwordKill()
Boss::fight()
Boss is killed...
3. 多态的本质
l 面向对象的新需求
根据实际的对象类型来判断重写函数的调用。
如果父类指针指向的是父类对象则调用父类中定义的函数。
如果父类指针指向的是子类对象则调用子类中定义的重写函数。
l 面向对象中的多态
根据实际的对象类型决定函数调用语句的具体调用目标。
多态:同样的调用语句有多种不同的表现形态
l C++中的多态支持
C++中通过virtual关键字对多态进行支持。
使用virtual声明的函数被重写后即可展现多态特性。
这就是虚函数。虚函数的特点是,不再只是根据指针类型来判定要使用的函数,而是根据指针所指的内容来判断将要引用的函数。
小结
函数重写是面向对象中很可能发生的情形。
函数重写只可能发生在父类与子类之间。
需要根据实际对象的类型确定调用的具体函数。
virtual关键字是C++中支持多态的唯一方式。
被重写的虚函数即可表现出多态的特性。