隐藏与覆盖
- 隐藏:父子关系、成员同名
class Person
{
public:
void play();
void work();
}
class Soldier:public Person
{
public:
void play(); // 隐藏
void work(int x);
}
int main(void)
{
Soldier s;
s.play();// 调用子类的成员函数
s.Person::play();// 调用父类的成员函数
s.work(7);// 调用子类的成员函数
s.work();// 错误!不能形成重载
return 0;
}
- 覆盖:当子类重新定义了父类的虚函数时,子类的虚函数表中该虚函数的入口地址就会覆盖掉父类的虚函数的地址,从而实现多态。
重载与重写
-
从定义上来说:
- 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
- 重写:是指子类重新定义父类虚函数的方法。
-
从实现原理上来说:
- 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
- 重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
抽象类、接口类
- 抽象类:含有纯虚函数的类为抽象类。抽象类不能实例化对象。抽象类的子类也可以是抽象类。
- 接口类:仅含有纯虚函数的类为接口类。接口类更多的表达一种能力或协议。
使用
class Flyable // 会飞的
{
public:
virtual void takeoff()=0; // 起飞
virtual void land()=0; // 降落
}
class CanShot // 会射击的
{
public:
virtual void aim()=0; // 瞄准
virtual void reload()=0; // 装弹
}
class Plane : public Flyable // 飞机
{
...
virtual void takeoff(){
...}
virtual void land(){
...}
}
class FighterJet : public Plane, public Canshot // 战斗机
{
...
virtual void aim(){
...}
virtual void reload(){
...}
}
void airBattle(FighterJet *a, FighterJet *b) // 两架战斗机空战
{
...
// 调用Flyable中约定的函数
// 调用CanShot中约定的函数
}
运行时类型识别RTTI
作用
- 通过基类的指针或引用来检索其所指对象的实际类型
- 通过typeid和dynamic_cast来实现
- typeid:返回指针或引用所指对象的实际类型
- dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用
使用
class Bird : public Flyable // 鸟
{
public:
void foraging(){
...} // 觅食
virtual void takeoff(){
...}
virtual void land(){
...}
}
class Plane : public Flyable // 飞机
{
public:
void carry(){
...} // 运输
virtual void takeoff(){
...}
virtual void land(){
...}
}
void doSomething(Flyable *obj)
{
obj->takeoff();
// 如果是Bird,则觅食
if(typeid(*obj) == typeid(Bird))
{
Bird *bird = dynamic_cast<Bird *>(obj);
bird->foraging();
}
// 如果是Plane,则运输
if(typeid(*obj) == typeid(Plane))
{
Bird *plane = dynamic_cast<Plane *>(obj);
plane->carry();
}
obj->land();
}
-
dynamic_cast:
-
只能用于指针和引用的转换
-
要转换的类型中必须包含虚函数,即用于多态类型的转换
-
转换成功返回子类地址,失败返回nullptr
-
-
typeid:
-
可以查看任何一个变量的数据类型,包括基本数据类型
-
typeid返回一个type_info对象的引用
扫描二维码关注公众号,回复: 12686126 查看本文章 -
如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数,即用于多态类型的转换
-
只能获取对象的实际类型
-
Flyable *p = new Bird();
cout<< typeid(p).name()<<endl; // class Flyable *
cout<< typeid(*p).name()<<endl; // class Bird
异常处理
-
异常处理:对有可能发生异常的地方做出预见性的安排
-
基本思想:主逻辑与异常处理分离
-
关键字:
- try:尝试运行正常的逻辑。(主逻辑)
- catch:如果出现异常,则捕获。(异常处理逻辑)
- throw:抛出异常
-
常见异常:数组下标越界、除数为0、内存不足
使用
- Exception类是一个接口类,HardwareErr、SizeErr、MemoryErr、NetworkErr均继承于Exception类,都分别实现了父类中的异常处理函数。
void fun1()
{
throw new SizeErr();
}
void fun2()
{
throw new MemoryErr();
}
int main()
{
try
{
fun1();
}
catch(Exception &e)// 均可以使用父类来捕获异常
{
e.xxx();
}
}
友元函数与友元类
友元函数
- 类具有封装、继承、多态、信息隐藏的特性,只有类的成员函数才可以访问类的私有成员,非成员函数只能访问类的公有成员。为了使类的非成员函数访问类的成员,唯一的做法就是将成员定义为public,但这样做会破坏信息隐藏的特性。基于以上原因,引入友元函数解决。
- 如果一个全局函数被定义为友元函数,则称为友元全局函数。
- 如果一个类的成员函数被定义为友元函数,则称为友元成员函数。
- 友元函数定义在类外部,声明在类内部,有权访问类的私有和保护成员。
使用
class Circle
{
public:
// 友元成员函数定义在其他类中
void printXY(Coordinate &c)
{
cout<<c.m_iX<<c.m_iY<<endl;// 可直接访问类的任何成员
}
}
class Coordinate
{
friend void printXY(Coordinate &c); // 友元全局函数
friend void Circle::printXY(Coordinate &c);// 友元成员函数
public:
Coordinate(int x, int y);
private:
int m_iX;
int m_iY;
}
// 友元全局函数定义在全局
void printXY(Coordinate &c)
{
cout<<c.m_iX<<c.m_iY<<endl;// 可直接访问类的任何成员
}
int main()
{
Coordinate coor(3,5);
Circle circle;
printXY(coor); // 使用友元全局函数
circle.printXY(coor); // 使用友元成员函数
return 0;
}
友元类
使用
class Circle;
class Coordinate
{
friend Circle;
public:
Coordinate(int x, int y);
private:
int m_iX;
int m_iY;
}
class Circle
{
public:
void printXY()
{
cout<<m_coor.m_iX<<m_coor.m_iY<<endl;
}
private:
Coordinate m_coor;
}
- 友元函数不是类的成员函数,因此类与类之间的友元关系不能被继承,即友元关系不可传递
- 友元关系具有单向性
- 友元声明的形式及数量不受限制
- 友元是不得已而为之的办法,破坏了封装性,同时体现了定向暴露的思想。