C++ 多态相关

什么是多态?虚函数的实现原理是什么?

多态分为静态多态和动态多态

  • 静态多态:发生在编译时,主要有函数重载,运算符重载
  • 动态多态:发生在运行时,主要通过虚函数的形式实现
    多态性可以概括为"一个接口,多种方法",程序在运行时才决定调用的函数,多态性是oop编程的核心,C++的多态性通过虚函数实现,基类中定义虚函数,允许子类继承并且可以重新定义该函数的实现,这也就是覆盖。多态的目的就是为了接口的重用。也就是说,无论传递过来的是哪个类的对象,函数都可以通过接口调用到适合各自对象的实现方法。
    最常见的用法就是声明一个基类指针或者引用,然后该指针或者引用绑定一个派生类的对象,调用相应对的虚函数,可以根据指向的派生类的不同而实现不同的方法。

虚函数实现原理:当在类中定义一个虚函数,则该类所实例化的对象的存储位置将会多一个vfptr指针,该指针指向一个虚函数表,虚函数表中存放的就是虚函数的入口地址


构造函数能设置为虚函数吗?为什么?

构造函数不能设置为虚函数,虚函数就是为实现C++的OOP编程中的多态性而设定的,而动态多态性的激活条件是:

  • 基类中有定义虚函数,同时其派生类中有定义该函数的覆盖函数;
  • 创建派生类对象
  • 定义一个基类的指针或者引用,同时绑定到其派生类的对象中; 通过已经定义的基类指针或者引用调用相应的虚函数

由上面动态多态性的激活条件可以知道,要使用虚合理函数,则必须先创建派生类的对象,而在创建派生类对象的过程中必将调用其基类的构造函数,而如果基类中的构造函数是虚函数,而此时派生类对象又没有创建,陷入了矛盾的境地,所以构造函数不能设置为虚函数,同时也是没有任何意义的一件事


在什么情况下析构函数要设置成虚函数?为什么?

当基类中的数据成员申请了堆空间,同时该基类中也定义了析构函数用于销毁该空间和虚函数。如果该基类有一个派生类,该派生类中也申请了一块堆空间。这时我们如果new一个派生类中空间,把new表达式返回的派生类指针赋给一个基类指针。如果我们使用delete表达式来释放刚才申请的派生类空间,这时只会调用基类的析构函数,不会调用派生类的析构函数,因为已经把派生类的指针赋给了基类指针。这时我们应该把析构函数设置为虚函数,在调用派生类的析构函数的同时,也将会调用基类的析构函数,这时所有申请的资源都得到了释放。代码如下:

#include <string.h>

#include <iostream>

using std::cout;
using std::endl;

class Base
{
public:
    Base(const char * base)
    : _base(new char[strlen(base) + 1]())
    {
        strcpy(_base, base);
        cout << "Base(const char * base)" << endl;
    }

    virtual void print(void) const
    {
        cout << "base = " << _base << endl;
    }

    ~Base()
    {
        if (_base)
            delete [] _base;
        cout << "~Base()" << endl;
    }

private:
    char * _base;
};

class Derived
: public Base
{
public:
    Derived(const char * base, const char * derived)
    : Base(base)
    , _derived(new char[strlen(base) + 1]())
    {
        strcpy(_derived, derived);
        cout << "Derived(const char * base, const char * derived)" << endl;
    }
    
    void print(void) const 
    {
        cout << "derived = " << _derived << endl;
    }

    ~Derived()
    {
        if (_derived)
            delete [] _derived;
        cout << "~Derived()" << endl;
    }
private:
    char * _derived;

};

int main(void)
{
    Base * base = new Derived("hello", "world");
    base->print();

    delete base;

    return 0;
}

运行结果

Base(const char * base)
Derived(const char * base, const char * derived)
derived = world
~Base()

分析:对于虚函数的调用这里就不再分析,就是派生类将基类的虚函数给覆盖了,这时从基类对象调用基类中的虚函数,将会调用派生类的虚函数。这里分析析构函数,通过运行结果可知,在delete base时,基类的析构函数被调用了,但是派生类的析构函数没有被调用,也就是说,基类中申请的堆资源得到了释放,而派生类中的资源没有得到释放,这不是我们想要的结果,原因上面已经分析了。要解决这个问题,应该想到,在调用派生了析构函数时,基类的析构函数也会被自动调用,我们可以尝试调用派生类的析构函数,那么怎么调用派生类的析构函数呢?这里可以利用虚函数的覆盖特性,我们把基类的析构函数设置为virtual,这时delete base时将调用派生类的析构函数,达到了我们的目的,代码修改如下:

virtual
    ~Base()
    {
        if (_base)
            delete [] _base;
        cout << "~Base()" << endl;
    }

运行结果:

Base(const char * base)
Derived(const char * base, const char * derived)
derived = world
~Derived()
~Base()

当基类析构函数设置为虚函数后,其派生类析构函数会自动成为虚析构函数,即使没有加上virtual关键字,如果接下来要调用析构函数,就会走虚函数的调用机制


在基类的普通成员函数内部调用虚函数时的情况

#include <iostream>

using std::cout;
using std::endl;

class Base 
{
public:
    Base() = default;

    Base(int base_val)
    : _base_val(base_val)
    {
        cout << "Base(int base_val)" << endl;
    }

    void func1(void)
    {
        //this->display();
        display();
    }

    void func2(void)
    {
        Base::display();
    }

    virtual void display(void) const
    {
        cout << "base_val = " << _base_val << endl;
    }

private:
    int _base_val;

};

class Derived
: public Base
{
public:
    Derived(int base_val, int derived_val)
    : Base(base_val)
    , _derived_val(derived_val)
    {
        cout << "Derived(int base_val, int derived_val)" << endl;
    }

    void display(void) const
    {
        cout << "derived_val = " << _derived_val << endl;
    }
private:
    int _derived_val;
};

int main(void)
{
    Base base(12);
    base.func1();

    Derived derived(34, 56);
    derived.func1();

    cout << "-----------" << endl;
    base.func2();
    derived.func2();

    return 0;
}

运行结果:

Base(int base_val)
base_val = 12
Base(int base_val)
Derived(int base_val, int derived_val)
derived_val = 56
-----------
base_val = 12 base_val = 34

分析:由上面的代码可知,display()函数定义为了虚函数,同时在基类中的func1 和 func2 函数中通过两种方式来访问该虚函数; Base base(12); base.func1();对于这段代码很好理解,就是基本的创建对象,然后访问对象中的成员,只不过在该成员中访问了该对象的虚函数,但由于没有继承关系,所以和普通的访问没有区别;

Derived derived(34, 56);
derived.func1();

对于这段代码,由于 Derived类继承自Base类,在继承了基类中的 func1、func2和虚函数后,由于Derived类中重新定义了该虚函数,这时子类的同名函数会覆盖基类中的虚函数,所以这时func1调用的就是子类中重定义的虚函数;

base.func2();
derived.func2();

上面的代码通过运行结果可知,虽然是derived对象调用的func2函数,但是该函数内部通过作用域运算符调用了虚函数,这时的调用将会直接调用基类中的虚函数,而不会调用派生类中的虚函数


在基类的构造函数和析构函数内部调用虚函数时的情况

#include <iostream>

using std::cout;
using std::endl;

class Grandpa 
{
public:
    Grandpa(int grandpa_val)
    : _grandpa_val(grandpa_val)
    {
        cout << "Grandpa(int grandpa_val)" << endl;
    }

    virtual
    void func1(void)
    {
        cout << "Grandpa: func1()" << endl;
    }

    virtual
    void func2(void)
    {
        cout << "Grandpa: func2()" << endl;
    }

    ~Grandpa()
    {
        cout << "~Grandpa()" << endl;
    }
private:
    int _grandpa_val;

};

class Son
: public Grandpa
{
public:
    Son(int grandpa_val, int son_val)
    : Grandpa(grandpa_val)
    , _son_val(son_val)
    {
        cout << "Son(int grandpa_val, int son_val)" << endl;
        func1();
    }

#if 1
    void func1(void)
    {
        cout << "Son: func1()" << endl;
    }

    void func2(void)
    {
        cout << "Son: func2()" << endl;
    }

#endif 

    ~Son()
    {
        cout << "~Son()" << endl;
        func2();
    }
private:
    int _son_val;
};

class Grandson
: public Son
{
public:
    Grandson(int grandpa_val, int son_val, int grandson_val)
    : Son(grandpa_val, son_val)
    , _grandson_val(grandpa_val)
    {
        cout << "Grandson(int grandpa_val, int son_val, int grandson_val)" << endl;
    }

    void func1(void)
    {
        cout << "Grandson: func1()" << endl;
    }

    void func2(void)
    {
        cout << "Grandson: func2()" << endl;
    }

    ~Grandson()
    {
        cout << "~Grandson()" << endl;
    }

private:
    int _grandson_val;
};

int main(void)
{
    Grandson test(1, 2, 3);
    //cout << "-------------/\n";
    Grandpa  & tt = test;
    cout << "-------------/\n";
    tt.func1();
    return 0;
}

运行结果

Grandpa(int grandpa_val)
Son(int grandpa_val, int son_val)
Son: func1()
Grandson(int grandpa_val, int son_val, int grandson_val)
-------------/
Grandson: func1()
~Grandson()
~Son()
Son: func2()
~Grandpa()

由代码和运行结果可知,当我们在构造函数或者析构函数内部调用虚函数,虽然表面上看和动态联编没说区别,但是,在这两个函数体内调用虚函数,其实本质上调用的是自身类中的函数(不是虚函数),或者说没有从虚函数的机制进行调用,而是按照普通函数的机制进行调用(静态联编)


验证虚函数表的存在

我们知道,如果一个基类中定义了虚函数,同时派生类中覆盖了该虚函数,这时实例化派生类时创建的派生类对象的存储空间当中将会多一个指向虚函数表的指针,使得能够表现为动态多态的性质,这里就对虚函数表的存在进行验证,代码如下:

#include <iostream>

using std::cout;
using std::endl;

class Base
{
public:
    Base(long base_val)
    : _base_val(base_val)
    {
        cout << "Base(int base_val)" << endl;
    }

    virtual 
    void func1(void)    {   cout << "Base::func1()" << endl;    }

    virtual 
    void func2(void)    {   cout << "Base::func2()" << endl;    }

    virtual 
    void func3(void)    {   cout << "Base::func3()" << endl;    }

private:
    long _base_val;

};

class Derived
: public Base
{
public:
    Derived(long base_val, long derived_val)
    : Base(base_val)
    , _derived_val(derived_val)
    {
        cout << "Derived(long base_val, long derived_val)" << endl;
    }

    virtual 
    void func1(void)    {   cout << "Derived::func1()" << endl;    }

    virtual 
    void func2(void)    {   cout << "Derived::func2()" << endl;    }

    virtual 
    void func3(void)    {   cout << "Derived::func3()" << endl;    }

private:
    long _derived_val;
    
};

int main(void)
{
    Base base(10);
    cout << "sizeof(Base) = " << sizeof(Base) << endl;  

    long * p = (long *)&base;
    cout << p[0] << endl;
    cout << p[1] << endl; 

    typedef void (*Function)(void);

    long * p2 = (long *)p[0];
    Function f = (Function)p2[0];
    //Function f = (Function)p[0][0];
    f();

    f = (Function)p2[1];
    f();

    f = (Function)p2[2];
    f();

    return 0;
}

运行结果

Base(int base_val)
sizeof(Base) = 16
94272637283664
10
Base::func1()
Base::func2()
Base::func3()

编译和运行环境是gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1) ,所以系统的是64位的,所以在指针的大小是8个字节(64位),所以为了方便验证,这里用long类型的来存储数据。我们知道,对象base的在栈中的大小是16个字节,其中8个字节的long类型数据,8个字节的指针,指针也就是vfptr指针,指向虚函数表的,这里就是验证虚函数表是否真的存在。对对象base取地址,强制转换为long类型,看 p[1]数据,就是我们一开始初始化base对象时赋的值, p[0] 则是一个指针,指向虚函数表,因为指针的大小是8个字节,所以我们再一次把 p[0]强制转换为long* 类型,从程序中我们可以看出来,p[0] 中共有3个指针,也就是 p2中的数据,因为 p2中的数据是8个字节的指针,但编译器无法识别这个地址数据,所以我们把该数据强制转换为指针函数,这样就可以调用虚函数了。从而验证了虚函数表哦的存在性


什么是纯虚函数?什么是抽象类?抽象类的作用是什么?

  • 纯虚函数指的在基类中的是一个虚函数没有定义,而是在声明该纯虚函数的时候在后面添加 = 0;即可,这时基类中的虚函数就是纯虚函数
  • 如果在基类中定义纯虚函数,则该基类就是抽象基类,抽象基类只向外提供接口,而不能实例化对象,当该基类的派生类可以实例化对象,不过前提是这些派生类定义了覆盖这些纯虚函数的函数,通过这里我们就一目了然了,抽象基类只负责提供接口的定义,不负责接口的实现,接口的实现由该基类的派生类来完成。代码如下:
#include <math.h>

#include <iostream>

using std::cout;
using std::endl;

class Figure            //定义一个抽象基类
{
public: 
    virtual void display(void) const = 0;   //定义纯虚函数
    virtual double area(void) const = 0;
};

void display(Figure & figure)
{
    figure.display(); 
    cout << ", the area is " << figure.area() << endl;
}

class Circle
: public Figure
{
public:
    Circle(double radius)
    : _radius(radius)
    {}

    void display(void) const
    {
        cout << "I am Circle";
    }

    double area(void) const
    {
        return 3.14 * _radius * _radius;
    }

private:
    double _radius;
};

class Rectangle
: public Figure
{
public:
    Rectangle(double length, double width)
    : _length(length)
    , _width(width)
    {}

    void display(void) const
    {
        cout << "I am Rectangle";
    }

    double area(void) const
    {
        return _length * _width;
    }

private:
    double _length;
    double _width;
};

class Triangle
: public Figure
{
public:
    Triangle(double a, double b, double c)
    : _a(a)
    , _b(b)
    , _c(c)
    {}

    void display(void) const
    {
        cout << "I am Triangle";
    }

    double area(void) const
    {
        double p = (_a + _b + _c)/2;
        return sqrt(p * (p - _a) * (p - _b) * (p - _c));
    }

private:
    double _a;
    double _b;
    double _c;
};

int main(void)
{
    Circle circle(10);
    Rectangle rec(10, 12);
    Triangle triangle(3, 4, 5);

    display(circle);
    display(rec);
    display(triangle);


    return 0;
}

运行结果:

I am Circle, the area is 314
I am Rectangle, the area is 120
I am Triangle, the area is 6

分析:从上面的代码可以知道,Figure为抽象基类,只定义向外提供的各种接口,而实现则由该基类的派生类来完成,在main函数中也可以看出来,调用同一个函数,但结果却是不一样的。这种抽象基类遵循的原则是:对修改关闭(不需要修改原来的代码),对扩展开放。
另:对于这种抽象基类应该注意的是,只要基类中有定义的纯虚函数,则其派生类中必须对基类中的所有纯虚函数给予实现,只要有一个纯虚函数没有实现,则该派生类也不能创建对象。
上面是按照定义纯虚函数来实现抽象类,这里还有另外一种抽象类,就是将基类的构造函数设置为protected,这时的基类是不能实例化对象的,要调用基类的构造函数,通过该基类派生出子类,实例化该子类,通过该对象来调用基类中可以访问的成员,代码如下:

#include <iostream>

using std::cout;
using std::endl;

class Base
{
public:
    virtual void print(void)
    {
        cout << "I am Base_class" << endl;
        cout << "base_val = " << _base_val << endl;
    }

protected:              //把基类的构造函数定义为protected性质,这时外部非派生类将不能对该类进行实例化,只能通过该类的派生来调用该基类的构造函数
    Base(int base_val)
    : _base_val(base_val)
    {
        
        cout << "Base(int base_val)" << endl;
    }

private:
    int _base_val;
};

class Inherit_cls
: public Base
{
public:
    Inherit_cls(int base, int inherit_val)      //通过派生类中的构造函数来调用基类中的构造函数
    : Base(base)
    , _inherit_val(inherit_val)
    {
        cout << "Inherit_cls(int base, int inherit_val) " << endl;
    }

    void print(void)
    {
        cout << "I am Inherit_cls" << endl;
        cout << "inherit_val = " << _inherit_val << endl;
    }

private:
    int _inherit_val;

};

int main(void)
{
    Inherit_cls test(12, 34);
    Base & base = test;
    base.print();

    return 0;
}

分析:上面的代码中是C++语言的动态多态性,但是把基类中的构造函数设置为了protected,这时是无法通过该基类来创建基类对象,只能通过基类的派生类创建对象,这种抽象类的功能相比较前一种而言弱了很多,对于抽象类,应该使用纯虚函数来实现。

什么是重载?什么是隐藏?什么是覆盖?他们之前的区别是?

  • 重载:重载可以在同一个类中,也可以是普通函数,只要函数的名称相同但是函数的参数个数,参数类型,返回值不同,就可以实现重载
  • 隐藏:隐藏指的有继承关系的父子类中的同名函数,如果基类中有一个函数function,而子类中也有一个函数function,这时将会隐藏基类中的function函数,想要调用基类中的function函数,只需要加一个作用域限定符即可
  • 覆盖:指的是虚函数,如果基类中定义了一个虚函数,同时该基类的派生类也定义了与基类中同名、同返回值、同参数列表的函数(可以没有virtual修饰),这时实例化该派生类对象,这时派生类中的虚函数将会覆盖掉从基类中继承过来的虚函数

多重继承虚函数的情况

#include <iostream>

using std::cout;
using std::endl;

class A
{
public: 
    virtual
    void f(void)    {   cout << "A::f()" << endl;   }

    virtual 
    void g(void)    {   cout << "A::g()" << endl;   }

    virtual 
    void h(void)    {   cout << "A::h()" << endl;   }
};

class B
{
public: 
    virtual
    void f(void)    {   cout << "B::f()" << endl;   }

    virtual 
    void g(void)    {   cout << "B::g()" << endl;   }
 
    void h(void)    {   cout << "B::h()" << endl;   }

    void j(void)    {   cout << "B::j()" << endl;   }
};

class C 
: public A
, public B
{
public:
    virtual
    void f(void)    {   cout << "C::f()" << endl;   }

    void h(void)    {   cout << "C::g()" << endl;   }

    void j(void)    {   cout << "C::j()" << endl;   }
};


int main(void)
{
    C c;
    cout << "sizeof(A) = " << sizeof(A) << endl;

    A & a = c;
    a.f();
    a.g();
    a.h();
    cout << "-----------------" << endl;

    B & b = c;
    b.f();
    b.g();
    b.h();
    b.j();

    c.f();
    //c.g();
    c.h();
    c.j();

    return 0;
}

运行结果

sizeof(A) = 8
C::f()
A::g()
C::g()
/-----------------
C::f()
B::g()
B::h()
B::j()
/---------------
C::f()
C::g()
C::j()

由上面的代码可知,这里的多重继承虚函数指的就是C多重继承自A,B两个类;同时C也继承了A,B的虚函数表,如果C类中有定义覆盖A,B中的虚函数,则虚函数表中的虚函数将发生变化,将原来基类中的虚函数覆盖为派生类中的虚函数,这就表现了动态多态的性质。对于此类情况,把各种类的虚函数继承关系画出来,就一目了然。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的多态(Polymorphism)是指在父类和子类之间的相互转换,以及在不同对象之间的相互转换。 C++中的多态性有两种:静态多态和动态多态。 1. 静态多态 静态多态是指在编译时就已经确定了函数的调用,也称为编译时多态C++中实现静态多态的方式主要有函数重载和运算符重载。 函数重载是指在同一作用域内定义多个同名函数,但它们的参数列表不同。编译器根据传递给函数的参数类型和数量来确定调用哪个函数。例如: ```c++ void print(int num) { std::cout << "This is an integer: " << num << std::endl; } void print(double num) { std::cout << "This is a double: " << num << std::endl; } int main() { int a = 10; double b = 3.14; print(a); // 调用第一个print函数 print(b); // 调用第二个print函数 } ``` 运算符重载是指对C++中的运算符进行重新定义,使其能够用于自定义的数据类型。例如: ```c++ class Complex { public: Complex(double real, double imag) : m_real(real), m_imag(imag) {} Complex operator+(const Complex& other) const { return Complex(m_real + other.m_real, m_imag + other.m_imag); } private: double m_real; double m_imag; }; int main() { Complex a(1.0, 2.0); Complex b(3.0, 4.0); Complex c = a + b; // 调用Complex类中重载的+运算符 } ``` 2. 动态多态 动态多态是指在运行时根据对象的实际类型来确定调用哪个函数,也称为运行时多态C++中实现动态多态的方式主要有虚函数和纯虚函数。 虚函数是在父类中定义的可以被子类重写的函数,使用virtual关键字声明。当一个对象的指针或引用指向一个子类对象时,调用虚函数时会根据实际的对象类型来确定调用哪个函数。例如: ```c++ class Shape { public: virtual void draw() { std::cout << "Drawing a shape." << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ``` 纯虚函数是在父类中定义的没有实现的虚函数,使用纯虚函数声明(如virtual void func() = 0;)。父类中包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能作为基类来派生子类。子类必须实现父类的纯虚函数才能实例化。例如: ```c++ class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值