cpp_11_虚函数_多态_纯虚函数

发布时间:2024年01月14日

????????????????编译器根据指针的类型来确定调用哪个类的普通成员函数?

????????????????编译器根据基类类型指针指向的对象类型,来确定调用哪个类的虚函数?

0? 非虚的世界(全普通函数)

? ? ? ? 对象的自洽性:

? ? ? ? 1)对同样的函数调用,各个类的对象都会做出恰当的响应。

????????

? ? ? ? 编译器仅根据指针的类型来确定调用哪个类的普通成员函数

? ? ? ? 即,通过基类类型指针调用普通成员函数,只能调用基类的成员函数:

? ? ? ? 1)即便这个基类类型的指针指向了子类对象,调用的也为基类的成员函数。

? ? ? ? 2)一旦调用子类所特有的成员函数,将引发编译错误

// selfconst.cpp 非虚的世界(没有虚函数的程序)
#include <iostream>
using namespace std;

class Shape {
public:
    void Draw( ) {  cout << "Shape::Draw" << endl;  }
private:
    int m_x;
    int m_y;
};

class Rect : public Shape {
public:
    void Draw( ) {  cout << "Rect::Draw" << endl;   }
private:
    int m_rx;
    int m_ry;
};

class Circle : public Shape {
public:
    void Draw( ) {  cout << "Circle::Draw" << endl; }
    void foo( ) { }
private:
    int m_radius;
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    cout << "--------利用 对象 调用 非虚的成员函数---------" << endl;
    // 哪个类对象 就调用 哪个类的普通成员函数(对象的自恰性)
    Shape s;
    s.Draw( ); // Shape::Draw
    Rect r;
    r.Draw( ); // Rect::Draw
    Circle c;
    c.Draw( ); // Circle::Draw
    cout << "------利用 指针 调用 非虚的成员函数----------" << endl;
    // 利用 基类类型的指针 只能调用 基类的普通成员函数
    Shape* ps = &s;
    ps->Draw( ); // Shape::Draw
    // 即便 基类类型指针 指向的是子类对象,调用仍然为基类的普通成员函数
    ps = &r;
    ps->Draw( ); // Shape::Draw
    ps = &c;
    ps->Draw( ); // Shape::Draw
    // 如果调用 子类所特有成员函数,将报告编译器错误
//  ps->foo( );

    // 编译器 简单而粗暴根据 指针本身的类型 来确定到底调用哪个类的普通成员函数
    return 0;
}

1? 虚函数(虚方法)

? ? ? ? class? 类名 {

? ? ? ? ? ? ? ? virtual? 返回类型? 函数名 ( 形参表 ) { ... }

? ? ? ? };

? ? ? ? 覆盖:

? ? ? ? 1)如果子类的成员函数和基类的虚函数具有相同的函数签名,则该成员函数就也是虚函数,无论其是否带有virtual关键字。

? ? ? ? 2)与基类的虚函数构成覆盖关系。

? ? ? ? 通过基类类型指针调用虚函数:

? ? ? ? 1)如果基类型指针指向基类对象,则调用基类的原始版本虚函数。

? ? ? ? 2)如果基类型指针指向子类对象,则调用子类的覆盖版本虚函数。

// bdv.cpp 虚的世界(有虚函数的程序)
#include <iostream>
using namespace std;

class Shape {
public:
    virtual void Draw( ) = 0; // 虚函数(原始版本)
private:
    int m_x;
    int m_y;
};

class Rect : public Shape {
public:
    void Draw( ) {  cout << "Rect::Draw" << endl;   } // 虚函数(编译器补virtual),
                                              //与基类Draw函数构成覆盖关系(覆盖版本)
private:
    int m_rx;
    int m_ry;
};

class Circle : public Shape {
public:
    virtual void Draw( ) {  cout << "Circle::Draw" << endl; } // 虚函数(编译器
                        //不补virtual),与基类的Draw函数构成覆盖关系(覆盖版本)
private:
    int m_radius;
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    cout << "--------利用 对象 调用 虚的成员函数---------" << endl;
    // 哪个类对象 就调用 哪个类的虚成员函数(对象的自恰性)
//    Shape s;
//    s.Draw( ); // Shape::Draw
    Rect r;
    r.Draw( ); // Rect::Draw
    Circle c;
    c.Draw( ); // Circle::Draw

    
    cout << "------利用 指针 调用 虚的成员函数----------" << endl;
    
//    Shape* ps = &s;
//    ps->Draw( ); // Shape::Draw (不是多态)
    
    Shape* ps = &r;
    ps->Draw( ); // Rect::Draw(多态)
    
    ps = &c;
    ps->Draw( ); // Circle::Draw(多态)
    
    // 根据 指针指向的对象的类型 来确定到底调用哪个类的虚成员函数
    return 0;
}

2? 动态多态(虚函数的主要应用)

2.1? 静态多态和动态多态

????????静态多态:在编译阶段就已经绑定了函数地址,主要体现是重载、模板。

????????动态多态:利用虚函数实现,在运行期间绑定,主要体现是给父类指针传递不同的类型,调用的函数也会不同。

? ? ? ? 如果子类提供了对基类虚函数的有效覆盖,那么通过一个基类型指针指向子类对象),或者基类型引用引用子类对象),调用该虚函数,实际调用的将是子类中的覆盖版本,而非基类中的原始版本,这种现象称为动态多态。

? ? ? ? 动态多态的重要意义在于,一般情况下,调用哪个类的成员函数是由指针或引用本身的类型决定的,而当多态发生时,调用哪个类的成员函数是由指针或引用的实际目标对象的类型决定的。

2.2? 动态多态的必要条件

? ? ? ? 1)基类中定义虚函数,子类提供覆盖版本

? ? ? ? 2)基类型指针(指向子类对象)或基类型引用(引用子类对象),调用该虚函数

2.3? 动态多态和this指针

? ? ? ? 调用虚函数的指针也可以是基类中的this指针,同样能满足多态的2个必要条件,

????????但在构造析构函数中除外。

// this.cpp 多态 和 this指针
#include <iostream>
using namespace std;

class Base {
public:
    void foo( /* Base* this */ ) {
        cout << "foo函数中调用的为: ";
        this->vfun();
    }
    Base( /* Base* this */ ) {
        cout << "构造函数中调用的为: ";
        this->vfun();
    }
    ~Base( /* Base* this */ ) {
        cout << "析构函数中调用的为: ";
        this->vfun();
    }
    virtual void vfun() { cout << "Base::vfun()" << endl; } // 原始版本
};

class Derived : public Base {
public:
    Derived( ) {
        //【Base();】定义 基类子对象,利用 基类子对象.Base()
    }
    ~Derived() {
        // 对于 基类子对象,利用 基类子对象.~Base()
    }
    void vfun() { cout << "Derived::vfun()" << endl; } // 覆盖版本
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    Derived d; // 定义d,利用d.Derived()
    d.foo(); // foo( &d )
    return 0;
} // d.~Derived() 

2.4? 动态多态揭秘(虚函数表)

????????

????????动态绑定(非编译,而是程序执行时的操作):?

? ? ? ? 当编译器看到通过指针或引用,调用虚函数的语句时,并不急于生成有关函数跳转的指令,相反,编译器会用一段代码替代该语句,这段代码在运行时才能被执行,完成如下操作:

? ? ? ? 1)确定指针或引用的目标对象所占内存空间??

? ? ? ? 2)从目标对象所占内存中间中找到虚表指针??

? ? ? ? 3)利用虚表指针找到虚函数表??

? ? ? ? 4)从虚函数表中获取所调用虚函数的入口地址??

? ? ? ? 5)根据入口地址,调用该虚函数??

// vftable.cpp 多态揭秘 -- 虚函数表
#include <iostream>
using namespace std;

class A { // 编译器根据A类的信息,将制作一张虚函数表 A::foo的地址 A::bar的地址
public:
    virtual void foo() { cout << "A::foo" << endl; }
    virtual void bar() { cout << "A::bar" << endl; }
};

class B : public A { //编译器根据B类的信息,将制作一张虚函数表 B::foo的地址 A::bar的地址
public:
    void foo() { cout << "B::foo" << endl; }
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
    A a; // |虚表指针|-->编译器根据A类的信息制作的虚函数表
    cout << "a对象的大小:" << sizeof(a) << endl; // 8

    B b; // |虚表指针|-->编译器根据B类的信息制作的虚函数表
    cout << "b对象的大小:" << sizeof(b) << endl; // 8

    void(**pfunc)() = *((void(***)())&a); // 从a中获取虚表指针
    pfunc[0](); // A::foo
    pfunc[1](); // A::bar 

    void(**pfunc2)() = *((void(***)())&b); // 从b中获取虚表指针
    pfunc2[0](); // B::foo
    pfunc2[1](); // A::bar

    A* pa = &b;
    pa->foo(); // 编译器在编译期间 并不知道 调用 哪个类的foo函数

    // 1. 根据pa获取b对象所占内存空间
    // 2. 从b对象所占内存空间中 获取 虚表指针
    // 3. 根据 虚表指针 找到 编译器根据B类的信息制作的虚函数表
    // 4. 从 虚函数表中 获取 虚函数的入口地址
    // 5. 利用 函数指针 调用 虚函数
    

    // 调用普通成员函数执行效率高 

    return 0;
}

? ? ? ? 动态绑定对性能的拖累:

? ? ? ? 1)?虚函数表本身会增加进程内存空间的开销?

? ? ? ? 2)与普通函数调用相比虚函数调用要多出几个步骤,增加运行时间的开销

? ? ? ? 3)无法内联:动态绑定会妨碍编译器通过内联来优化代码

? ? ? ? 故,只有在确实需要多态特性的场合才建议使用虚函数,负责尽量使用普通函数

3? 纯虚函数(抽象方法)

? ? ? ? class? 类名{

? ? ? ? ? ? ? ? virtual? 返回类型? 函数名 ( 形参表 ) = 0;?

?????????}

4? 抽象类

? ? ? ? 拥有纯虚函数的类称为抽象类

? ? ? ? 抽象类不能实例化为对象

? ? ? ? 抽象类的子类如果不对基类中的全部纯虚函数有效覆盖,那么该子类也是抽象类

5? 纯抽象类(接口)

? ? ? ? 全部由纯虚函数构成的抽象类称为纯抽象类或接口。

// abstract.cpp 纯虚函数 和 抽象类
#include <iostream>
using namespace std;

class A { // 抽象类
public:
    void bar() {}
    virtual void foo() = 0; // 纯虚函数
};

class B : public A {
public:
    void foo() {
        // ...
    }
};

// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
//  A a;
//  new A;

    B b;
    new B;
    return 0;
}

6? 多态练习

? ? ? ? 练习:设计一个通用的线程类。

// tread.cpp 多态练习:设计一个通用的线程类
#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <unistd.h>
using namespace std;
// 设计一个“通用”的线程类
class Thread {
public:
    void start( /* Thread* this */ ) {
        pthread_create(&m_tid, NULL, threadfunc, this );
    }
    static void* threadfunc( void* arg ) {
        // 子线程需要的执行操作,就不应该有类的设计者提供
        // 子线程需要的执行操作,就应该由用户来提供
        // 我们线程处理函数 调用 用户提供的操作
        Thread* p = (Thread*)arg;
        p->run( );

    }
    virtual void run( ) { }
private:
    pthread_t m_tid;
};
// 以上代码模拟类的设计者
// -------------------------
// 以下代码模拟类的使用者
class MyThread : public Thread {
public:
    MyThread( int sec, char ch ) : m_sec(sec), m_ch(ch) {}
    void run( ) {
        for( ;; ) {
            usleep( 1000*m_sec );
            cout << m_ch << flush;
        }  
    }
private:
    int m_sec;
    char m_ch;
};
int main( void ) {
    MyThread t1(500,'+'), t2(1000,'*');
    t1.start( );
    t2.start( );
    getchar( );
    return 0;
}

文章来源:https://blog.csdn.net/qq_33331020/article/details/135572411
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。