C++学习笔记day48-----C++98-多态,动态转换,typeid操作符

多态
通过指向子类对象的基类类型指针访问虚函数,会访问到子类中重写的虚函数。
通过子类对象的基类类型引用访问虚函数,会访问到子类中重写的虚函数。
多态的形成条件
首先必须保证子类对基类的虚函数有效的重写,其次,只能通过子类对象的基类类型指针,或者子类对象的基类引用才可以形成多态。
虚函数的有效重写
1)虚函数的声明一定要在基类中,基类中的虚函数可以没有定义(纯虚函数)
2)子类中重写基类虚函数时,要保证函数名,形参列表,常属性和基类中的虚函数保持一致;对于返回值为基本类型的虚函数,子类的虚函数返回值要和基类保持一致;对于返回值为自定义类类型的引用或指针的基类虚函数,子类的虚函数返回值类型可以是基类的虚函数的返回值类型的子类引用或指针。
3)静态函数,全局函数,构造函数,没有虚函数形式
4)析构函数可以是虚函数,能解决内存泄漏的问题。
多态和this的结合
这是实际开发中使用最多的多态的场景。通过以下例子来演示:

#include <iostream>
using namespace std;
class Base{
    protected:
        virtual void run(void){
            cout << "Base::run" << endl;
        }
    public:
        //void start(Base *this)
    void start(void){
        run();
    }
};
class Derived:public Base{
    protected:
        void run(void){
            cout << "Derived::run" << endl;
        }
};
int main(void){
    Derived d;
    d.start();
}

创建一个子类对象d,d调用父类中的start函数。
d.start();这个语句可以翻译成以下形式:
Base::start(&d);
Base中的start函数,可以翻译成以下形式:
void start(Base *this);
所以当d.start();执行的时候,其实是通过了this满足了指向子类对象的基类类型指针,这个条件。随后在start()函数中,this->run();就形成了多态。
这种用法是比较隐蔽的,但也是实际开发中用的最多,能经常在模板中见到。
纯虚函数
就是虚函数没有定义部分,通过 0 来表示。
virtual void run(void) = 0;
抽象类
含有纯虚函数的类,称为抽象类
纯抽象类
所有的函数都是纯虚函数,称为纯抽象类。
多态原理
多态原理
当一个类中含有虚函数(可以是继承基类的,也可以是本类中定义的)的时候,编译器会为类增加一个虚表指针,占一个指针类型大小的空间。这个指针指向一张虚标的中间部分,表的前半部分记录了偏移量,后半部分存放着一些指向该类的虚函数的指针,当子类没有对基类的虚函数进行重写的时候,这个指针里存放着基类虚函数的地址;当子类对基类的虚函数进行重写之后,指针内的内容被替换成子类虚函数的地址。
—————————————————-
回顾:
1) 子类构造函数
–>分配内存
–>构造基类子对象
–>构造成员子对象
–>执行子类构造函数代码
2) 子类析构函数
–>执行子类析构函数代码
–>析构成员子对象
–>析构基类子对象
–>释放内存
3) 子类拷贝构造和拷贝赋值

4 多重继承
1) 一个子类继承多个基类
2) 向上造型 //自动偏移计算
3) 名字冲突问题,解决–”类名::”

5 钻石继承问题
虚继承 virtual

今天:
eg:
实现图形库,用于现实各种图形
图形(位置/绘制)
/ \
矩形(宽/高/绘图) 圆形(半径/绘图)
二十二、多态
1 函数重写(虚函数覆盖)、多态的概念
如果将基类中某个成员函数声明为虚函数,那么子类与该函数具有相同原型的成员函数就也将变成虚函数,并且对基类中的版本形成覆盖,即函数重写。
这时,通过指向子类对象的基类指针,或者通过引用子类对象的基类引用,调用虚函数时,实际被执行的将是子类中的覆盖版本,而不再是基类中的原始版本,这种语法现象成为多态。
eg:
class Base{
public:
virtual void func(void){…}//虚函数
};
class Derived:public Base{
void func(void){…}//自动变虚
};
int main(void){
Derived d;
Base *pd = &d;//pb指向子类对象的基类指针
Base rb = d;//rb引用子类对象的基类引用
pb -> func();//Derived::func
rb.func();//Derived::func
}
2 函数重写的要求(虚函数覆盖的条件)
1) 只有类中的成员函数才能声明虚函数,全局函数、静态成员函数、构造函数都不能声明为虚函数。
注:析构函数可以虚(特殊)
2) 只有在基类中使用virtual关键字修饰的虚函数才能被子类的版本覆盖,而与子类版本中virtual关键字无关。

3) 虚函数在子类和基本的版本要具有相同的函数签名,即函数名、形参表、常属性一致

4) 如果基类的虚函数返回的是基本类型的数据,那么其子类的覆盖版本必须放回相同类型的数据

5) 如果基类的虚函数返回的是类类型的指针或引用,那么允许子类版本返回其子类类型的指针或引用—类型协变

3 多态条件
1) 多态的特性,必须要通过指针或者引用去调用虚函数才能表现出来

2) 调用虚函数的指针,可能是this指针,当使用子类对象调用基类中的成员函数时,该成员函数中的this指针将是一个指向子类对象的基类指针,通过它再去调用虚函数,可以表现出多态的语法特性//重要
子类和基类的成员函数关系只有,没关系,重写,隐藏,没有重载()

4 纯虚函数、抽象类和纯抽象类
1) 纯虚函数
虚函数 = 0;
不需要定义,只是表示抽象的行为
virtual 返回类型 函数名(形参表) = 0;

2) 抽象类,如果一个类中包含了纯虚函数,那么这个类就是抽象类。
抽象类不能创建对象
如果子类没有覆盖抽象基类中的纯虚函数,那么子类也是一个抽象类

3) 纯抽象类
如果一个类中的所有函数都是纯虚函数,那么该类就是一个纯类型对象(有名借口类)。

工厂设计模型(自己干自己的活)

5 多态语法的原理//了解
通过虚函数表和动态绑定来实现
1) 虚韩函数表会增加内存的开销
2) 动态绑定会增加时间的开销
总结:在实际开发时,如果没有多态的语法要求,最好不要使用虚函数。
虚函数,不能做内联优化

6 虚析构函数
3) 基类的析构函数不能调用子类的析构函数,delete一个指向子类对象的基类指针,实际被调用的仅仅只是基类的析构函数,子类的析构函数不会被执行,有内存泄露的风险。

解决方案
虚析构函数

二十三 运行时的类型信息 //了解
1) typeid操作符
#include
typeid(类型/对象),求内存信息,返回一个typeinfo类型对象,用于描述类型信息,在typeinfo类中,包含一个叫name的成员函数可以把类型信息转换成字符串的形式。
typeinfo类提供了 “==”,”!=”的操作附重载的支持,可以直接进行类型间的比较,如果类型之间存在多态的继承关系,typeid还可以根据动态的特性确定实际的目标对象类型。

2) dynamic_cast动态类型转换操作符
语法:
目标类型变量=dynamic_cast<目标类型>变量

适用场景:
用于具有多态继承关系的父子类指针或引用之间的显式转换。
注:在转换过程中,会检查目标类型和期望转换的对象类型是否一致,如果一致则转换成功,否则转换失败;如果转换的是指针,返回NULL表示失败,如果转换的是引用,抛出异常”bad_cast”表示失败。

include

using namespace std;

class A{ virtual void foo(void){} };
class B:public A{ void foo(void){} };
class C:public A{ void foo(void){} };
int main(void){
B b;
A *pa = &b;
B *pb = dynamic_cast

猜你喜欢

转载自blog.csdn.net/displaymessage/article/details/80601886