c++多态

参考文章:http://t.csdnimg.cn/Lfhpr

一、值传递不满足多态,主要是因为这种方式限制了对象类型的动态绑定(值传递中,接收到的对象具有具体的类型(而非基类类型))和原始对象的修改能力。还产生切片问题。

二、C++的多态必须满足两个条件:
1 必须通过基类的指针或者引用调用虚函数
2 被调用的函数是虚函数,且必须完成对基类虚函数的重写

三、虚函数表

编译时,编译器会为每个包含虚函数的类自动生成一个虚函数表,当创建类的实例时会在对象内存中预留一个指针指向该虚函数表。
本质上虚函数表是一个虚函数指针数组,最后一个元素是nullptr,代表虚表的结束。
所以,如果继承了虚函数,那么
1 子类先拷贝一份父类虚表,然后用一个虚表指针指向这个虚表。
2 如果有虚函数重写,那么在子类的虚表上用子类的虚函数覆盖。
3 子类新增的虚函数按其在子类中的声明次序增加到子类虚表的最后。

 四、多态的原理

编译器"看到"满足多态,就会根据指针找到对应对象虚函数表,然后查找虚函数表找到虚函数地址,然后访问虚函数。

五、多继承中的虚函数表

 如果C自己有多余的虚函数,会按照继承顺序补在第一张虚表后面。

六、析构函数设为虚函数

析构函数要实现多态。But,析构函数名字天生不一样,怎么实现多态?
实际上,析构函数被编译器全部换成了Destructor,所以我们加上virtual就可以。
只要父类的析构函数用virtual修饰,无论子类是否有virtual,都构成析构。
这也解释了为什么子类不写virtual可以构成重写,因为编译器怕你忘记析构。

 一、如果基类的析构函数不是虚函数,那么通过基类指针或引用调用析构函数时只会调用基类的析构函数,而不会调用派生类的析构函数。导致派生类部分没有被正确清理。为了避免这种情况,我,我们通常在基类中将析构函数声明为虚函数。

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    Base()
        : _base(new int(10))
    {
        cout << "Base() " << endl;
    }
    virtual void display() const
    {
        cout << "*_base:" << *_base << endl;
    }
    virtual ~Base()
    {
        if (_base)
        {
            delete _base;
            _base = nullptr;
        }
        cout << "~Base()" << endl;
    }

private:
    int *_base;
};

class Derived
    : public Base
{
public:
    Derived()
        : Base(), _derived(new int(20))
    {
        cout << "Derived()" << endl;
    }
    virtual void display() const override
    {
        cout << "*_derived:" << *_derived << endl;
    }
    ~Derived()
    {
        if (_derived)
        {
            delete _derived;
            _derived = nullptr;
        }
        cout << "~Derived()" << endl;
    }

private:
    int *_derived;
};
int main()
{
    Base *pbase = new Derived();
    pbase->display();
    delete pbase;
    return 0;
}

运行结果:

 

当使用基类指针指向派生类对象并通过 delete 操作符删除该基类指针指向的对象时,如果基类的析构函数是虚函数,那么析构函数的多态性就会被触发。

在delete ptr;这一行会发生以下事情:

1、编译器首先会检查 ptr 所指向的对象的实际类型(在这种情况下是Derived类型)。

2、由于 Base 类的析构函数是虚函数,编译器会查找该对象的虚函数表(Vtable),以确定应该调用哪个析构函数。

3、编译器使用虚函数表找到Derived 类的析构函数,并首先调用它。

4、Derived 类的析构函数执行完毕后,会自动调用基类Base的析构函数。

这样,即使是通过基类指针ptr删除对象,也能确保Derived 类的析构函数被正确调用,从而避免了资源泄露等问题。

注:析构函数顺序:派生类析构函数的执行顺序与构造函数相反。首先调用派生类的析构函数,然后按照基类在继承列表中的顺序(如果有多个基类)或按照基类声明的顺序(如果有单个基类)调用基类的析构函数。这确保了在对象销毁时,首先清理派生类特有的资源,然后清理基类共有的资源。

七、虚函数的限制

1、构造函数不能设为虚函数

构造函数的作用是创建对象,完成数据的初始化,而虚函数机制被激活的条件之一就是要先创建对象,有了对象才能表现出动态多态。如果将构造函数设为虚函数,此时构造还未执行完,对象还没创建出来,存在矛盾。

2、静态成员函数不能设为虚函数

虚函数的实际调用:this->vfptr->vtable->virtual function,但是静态成员函数没有this指针,所以无法访问到vfptr。

3、inline函数不能设为虚函数

因为inline函数在编译期间完成替换,而在编译期间无法展现动态多态机制,所以效果是冲突的,如果同时存在,inline失效。

4、普通函数不能设为虚函数

虚函数要解决的是对象多态的问题,与普通函数无关。

八、重载、隐藏、覆盖的区分

重载:发生在同一个类中,当函数名称相同时,函数参数类型、顺序、个数任一不同。. 

隐藏:发生在基类派生类之间,函数名称相同时,就构成隐藏(参数不同也能构成隐藏)。它允许子类隐藏父类中的同名函数。

列如:

#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
    void print()
    {
        cout << "A" << endl;
    }
};
class B
    : public A
{
public:
    void print(int x)
    {
        cout << "B" << endl;
    }
};
int main()
{
    B b;
    b.print();
    return 0;
}

 运行结果:

在例子中,B类中的print(int x) 隐藏了A类中的print()。如果使用B类型的对象调用print(),编译器会报错,因为没有找到匹配的函数。

覆盖:发生在基类和派生类之间,基类和派生类中同时定义相同的虚函数,覆盖的是虚函数表的入口地址,并不是覆盖函数本身。

#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
    virtual void print() const
    {
        cout << "A" << endl;
    }
};
class B
    : public A
{
public:
void print() const override
    {
        cout << "B" << endl;
    }
};
int main()
{
    B b;
    A *P = &b;
    P->print();
    return 0;
}

运行结果: 

九、菱形继承

普通继承:派生类的内存布局中会直接包含基类的完整数据。虚继承:派生类通过虚基类指针间接访问基类数据,减少了内存占用并避免了数据冗余。 

菱形继承是指当类A和类B分别继承自类C时,而类D又同时继承A和B,从而面临了一种形如菱形的继承结构。

为了解决菱形继承带来的问题,C++提供了虚继承(virtual inheritance)的机制。通过虚继承,可以确保在继承体系中,公共基类只被实例化一次,并被子类共享。具体做法是将公共基类声明为虚基类,在继承时使用virtual关键字。

class C { // 公共基类  
public:  
    void show() {  
        cout << "this is C" << endl;  
    }  
};  
  
class A : virtual public C { // A类虚继承C类  
public:  
    void show() {  
        cout << "this is A" << endl;  
    }  
};  
  
class B : virtual public C { // B类虚继承C类  
public:  
    void show() {  
        cout << "this is B" << endl;  
    }  
};  
  
class D : public A, public B { // D类继承A和B类  
public:  
    void show() {  
        cout << "this is D" << endl;  
    }  
};

通过虚继承,类D中只会有一个C类的实例,从而避免了数据冗余和访问二义性的问题。

  • 2
    点赞
  • 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、付费专栏及课程。

余额充值