C++特性之多态

本文详细探讨了C++中的多态性,包括静态多态和动态多态的实现方式,以及如何通过虚函数实现运行时的多态。通过实例展示了多态在不同场景下的应用,并解释了虚函数、抽象类、虚表等概念,同时提醒在使用多态时应注意的事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >




多态:向不同的对象发送同一个消息,不同的对象在接收时产生不同的行为(方法)

C++中,就是:不同功能的函数可以用同一函数名,这样就可以实现用一个函数名调用不同内容的函数。

例:学校校长通知:9.1日开学。不同的对象会做出不同的反应:教师需备好课;学生需按时到校上课;家长需备好学费 。由于事先对各种人的任务已做了规定,因此,在得到同一个消息时,各种人都知道怎么做。这就是多态。

  一、对象的类型

     1、静态类型:对象声明时的类型,在编译时确定。

     2、动态类型:目前所指对象的类型,在运行时确定。

class Base
{
};
 
class C:public Base
{
};
 
void Funtest()
{
Base b;
C d;
Base*pb1=&b;//编译器编译时就知道b的类型
pb1= &d;//只有程序运行后才知道d的类型
}
 
int main()
{
Funtest();
system("pause");
return 0;
}


  二、多态类型

      1、静态多态性(静态链编,早绑定):通过函数重载实现,要求程序在编译时就知道调用函数的全部信息。故又称为编译时多态性。

         优点:静态多态性的函数调用速度快、效率高

         缺点:缺乏灵活性,在程序运行前就已经决定了执行的函数和方法。

例:通过函数重载来实现:

int Add(int left, int right)
{
return left + right;
}
 
float Add(float left, float right)
{
return left + right;
}
 
void Funtest()
{
cout << Add(4, 6) << endl;//两个整数相加
cout << Add(2.32f,3.45f) << endl;//两个浮点数相加
}


如上代码,Add(4,6)Add(2.32f,3.45f)被分别编译为对Add(int ,int)Add(float,float)的调用。编译器会根据不同的参数列表对同名函数进行名字重整,之后这些同名函数就会变成拥有不同功能的函数。

运行结果如下:

带变量的宏也可以实现多态:

#define Add(a,b) (a)+(b)
int main()
{
int x = 4;
int y = 6;
float e = 2.32f;
float f = 3.45f;
cout << Add(x, y) << endl;
cout << Add(e, f) << endl;
system("pause");
return 0;
}


当程序被编译时,表达式Add(x,y)Add(e,f)被自动替换为两个整数相加和两个浮点数相加的具体表达式。运行结果如下:  

  

    动态多态性(动态链编,晚绑定):在程序执行期间判断所引用对象的实际类型,根据实际类型调用相应的方法。故又称为运行时的多态性。

     实现动态多态的条件:

  1.   (1)、派生类必须重写基类的虚函数。

      (2)、通过基类的指针/引用调基类的虚函数(该虚函数必须在派生类中被重写)

    class B
    {
    public:
    virtual void test1()
    {
    cout << "B::test1()" << endl;
    }
    virtual void test2()
    {
    cout << "B::test2()" << endl;
    }
        void test3()
    {
    cout << "B::test3()" << endl;
    }
    /*virtual void test4(int)
    {
    cout << "B::test4(int)" << endl;
    }*/
    virtual int test5()
    {
    cout << "B::test5()" << endl;
    }
    };
    class C :public B
    {
    public:
    virtual void test1()
    {
    cout << "C::test1()" << endl;
    }
        void test2()//重写虚函数,基类中的函数必须有virtual,派生类可以不加virtual
    {
    cout << "C::test2()" << endl;
    }
    virtual void test3()//没有构成重写
    {
    cout << "C::test3()" << endl;
    }
    /*virtual void test4()//重写:函数名、返回值类型、参数列表必须相同
    {
    cout << "C::test4()" << endl;
    }*/
    virtual void test5()//重写虚函数返回类型有误(协变)
    {
    cout << "C::test5()" << endl;
    }
    };
    void Funtest(B &b)
    {
    b.test1();
    b.test2();
    b.test3();
    //b.test4();
    b.test5();
    }


    调用test4时编译器提示错误如下:

     

    调用test5时编译器提示错误如下:

     

    小结:

         类中哪些可以作为虚函数?

     (1)、普通函数     (2)、析构函数    (3)、赋值运算符重载(但最好不要)

     以下均不可作为虚函数:

      (1)、静态成员函数,没有this指针,只有一份  

         (2)、构造函数(默认内联)     

         (3)、友元函数,不是类的成员函数,不能被继承

    继承体系同名成员函数重载、重写(隐藏)、隐藏(重定义)的区别是什么?

       重载必须是在同一作用域内,函数名相同,参数和返回值类型可以不同。

       

    int Add(int left, int right)
    {
    return left + right;
    }
     
    float Add(float left, float right)
    {
    return left + right;
    }
    void Funtest()
    {
    Add(3,5);
    Add(4.56f, 8.65f);
    }

        

       重写不在同一作用域内(分别在基类和派生类内),函数名、参数和返回值类型必须相同(协变除外),基类函数必须有关键字virtual,访问修饰符可以不同。

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


    协变:基类的成员返回基类的指针/引用,派生类的成员返回派生类的指针/引用。

    class B
    {
    virtual B& operator=(B &b)//基类的成员返回基类的引用
    {
    return *this;
    }
    };
    class C :public B
    {
    C& operator=(B &c)//派生类的成员返回派生类的引用
    {
    return *this;
    }
    };
    class B
    {
    virtual B* Funtest()//基类的成员返回基类的指针
    {
    return this;
    }
    };
    class C :public B
    {
    C* Funtest()//派生类的成员返回派生类的指针
    {
    return this;
    }
    };


     

       隐藏不在同一作用域内(分别在基类和派生类内),函数名相同,与参数和返回值类型无关,在基类和派生类中只要不构成重写就是隐藏。

    lass B
    {
    public:
       void test1()
    {
    cout<< "B::test1()" << endl;
    }
    };
    class C :public B
    {
    public:
    void test1()
    {
    cout << "C::test1()" << endl;
    }
    };


    建议:

    (1)、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中对象是不完整的,可能胡出现为定义的行为。

    (2)、最好将基类的析构函数声明为虚函数,因为析构函数比较特殊,派生类中的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理。

  2. 虚表剖析

       例如:

     

     class B
    {
    virtual void Funtest() = 0;//纯虚函数,该类称为抽象类
    (接口类)
    };

    注:抽象类不能实例化对象,只有在派生类重写以后,派生类才能实例化出对象。

    虚表:虚表指针存在于对象前4个字节。

    class B
    {
    public:
    B()//基类构造函数
    {}
    virtual void test1()//基类虚函数
    {
    cout << "B::test1()" << endl;
    }
    virtual void test2()
    {
    cout << "B::test2()" << endl;
    }
    virtual void test3()
    {
    cout << "B::test3()" << endl;
    }
    virtual void test4()
    {
    cout << "B::test4()" << endl;
    }
    private:
    int _data;
    };


    求该基类大小,sizeof(B)=8;在B类成员_data之前有一个指向四个虚函数的指针,该指针即为虚表指针。

     

    派生类中,重写基类中的test1()和test3():

    class C :public B
    {
    public:
    virtual void test1()
    {
    cout << "C:test1()" << endl;
    }
    virtual void test3()
    {
    cout << "C:test3()" << endl;
    }
    private:
    int _data1;
    };


    求派生类大小sizeof(C)=12;派生类同样有一个虚表指针,只是其中的虚函数顺序不同于基类。派生类中的虚表:首先复制基类中的虚表,确定需对基类中的哪个虚函数重写,确定后,用派生类中重写后的替换基类中的虚函数。如下图所示:

     

    单继承

    class B
    {
    public:
    virtual void test1()//基类虚函数
    {
    cout << "B::test1()" << endl;
    }
    virtual void test2()
    {
    cout << "B::test2()" << endl;
    }
    virtual void test3()
    {
    cout << "B::test3()" << endl;
    }
     
    virtual void test4()
    {
    cout << "B::test4()" << endl;
    }
     
    int _data1;
    };
    class C :public B
    {
    public:
     
    virtual void test3()//重写基类test3()
    {
    cout << "C:test3()" << endl;
    }
    virtual void test5()//派生类自己的虚函数
    {
    cout << "C:test5()" << endl;
    }
    int _data2;
    };
    void Funtest()
    {
    C d;
    d._data1 = 5;
    d._data2 = 4;
    }


     

    基类的大小sizeof(B)=8;派生类的大小sizeof(C)=12;

     

     

     

    多继承

    class B1
    {
    public:
    virtual void test1()//基类B1虚函数
    {
    cout << "B::test1()" << endl;
    }
    int _data1;
    };
    class B2
    {
    public:
    virtual void test2()//基类B2虚函数
    {
    cout << "B::test2()" << endl;
    }
    virtual void test3()
    {
    cout << "B::test3()" << endl;
    }
    int _data2;
    };
    class C :public B1, public B2
    {
    public:
    virtual void test4()
    {
    cout << "B::test4()" << endl;
    }
    int _data3;
    };
    void Funtest()
    {
    C d;
    d._data1 = 4;
    d._data2 = 5;
    d._data3 = 6;
    }


    基类B1、B2和派生类C的大小分别为8,8,20


  3.  

     

     

     

    菱形继承

    class B
    {
    public:
    virtual void test1()
    {
    cout << "B::test1()" << endl;
    }
    int _data1;
    };
    class C1:public B
    {
    public:
    virtual void test1()
    {
    cout << "C1::test2()" << endl;
    }
    virtual void test3()
    {
    cout << "B::test3()" << endl;
    }
    int _data2;
    };
    class C2 :public B
    {
    public:
    virtual void test4()
    {
    cout << "B::test4()" << endl;
    }
    int _data3;
    };
    class D :public C1, public C2
    {
    public:
    virtual void test4()
    {
    cout << "D::test4()" << endl;
    }
    virtual void test5()
    {
    cout << "D::test5()" << endl;
    }
    int _data4;
    };
    void Funtest()
    {
    D d;
    d.C1::_data1 = 2;
    d.C2::_data1 = 6;
    d._data2 = 3;
    d._data3 = 4;
    d._data4 = 5;
    }


    B类、C1类、C2类、D类的大小分别为8、12、12、28

     

     

     

    虚继承

    class B
    {
    public:
    B()
    {}
    ~B()
    {}
    virtual void test1()
    {
    cout << "B::test1()" << endl;
    }
    int _data1;
    };
    class C :virtual public B
    {
    public:
    virtual void test1()
    {
    cout << "C::test1()" << endl;
    }
    virtual void test2()
    {
    cout << "C::test2()" << endl;
    }
    int _data2;
    };
    void Funtest()
    {
    C d;
    d._data1 = 7;
    d._data2 = 9;
    }


    B类和C类的大小分别为:8,24;

     

    去掉C类中的构造函数和析构函数其大小分别为8,20

     

     

     

     

    虚继承派生类构造函数做的事:

  1. 、填写偏移量表格地址

  2. 、调用基类的构造函数

  3. 、填写派生类虚表地址

  4. 、重写属于基类对象部分的虚表地址

  5. 、派生类对象和基类对象之间用0分割(只是猜测)

    菱形虚拟继承

    class B
    {
    public:
    virtual void test1()
    {
    cout << "B::test1()" << endl;
    }
    int _data1;
    };
    class C1 :virtual public B
    {
    public:
    virtual void test1()
    {
    cout << "C1::test2()" << endl;
    }
    virtual void test3()
    {
    cout << "B::test3()" << endl;
    }
    int _data2;
    };
    class C2 :virtual public B
    {
    public:
    virtual void test4()
    {
    cout << "B::test4()" << endl;
    }
    int _data3;
    };
    class D :public C1, public C2
    {
    public:
    virtual void test4()
    {
    cout << "D::test4()" << endl;
    }
    virtual void test5()
    {
    cout << "D::test5()" << endl;
    }
    int _data4;
    };
    void Funtest()
    {
    D d;
    d.C1::_data1 = 3;
    d.C2::_data1 = 4;
    d._data2 = 5;
    d._data3 = 6;
    d._data4 = 7;
    }


    B类、C1类、C2类和D类的大小分别为:8,20,20,36

     

     


 

 

 

<think>嗯,用户想了解C++中的继承、多态和接口相关的用法,特别是实现和使用教程。首先,我需要回忆一下C++的这些概念。继承是面向对象编程中的一个核心概念,允许子类继承父类的属性和方法。多态则是通过虚函数实现的,使得不同子类对象可以对同一方法有不同的实现。接口在C++中通常通过纯虚函数和抽象类来实现,类似于Java中的接口。 用户提供的三个引用里提到了一些关键点。引用1提到多态性允许根据对象实际类型调用不同函数版本,这涉及到虚函数表和动态绑定。引用2给出了一个具体的代码示例,展示了基类A1和派生类B1、C1中的虚函数重写。引用3指出子类可以拥有父类的非私有属性和方法,并可以扩展或重写它们。 用户可能需要一个结构化的教程,涵盖基本概念、语法、接口实现方法以及实际应用示例。首先应该解释继承的基本用法,比如公有继承、保护继承和私有继承的区别,但通常主要使用公有继承。然后是多态,需要详细说明虚函数的作用,以及如何通过基类指针或引用调用派生类的方法。接口的实现部分需要介绍纯虚函数和抽象类,因为C++没有专门的接口关键字,但可以通过纯虚类模拟接口。 接下来要考虑用户可能的疑问点。例如,虚函数表的工作原理,这在引用1和2中都有涉及,可能需要简要解释。另外,构造函数和析构函数在继承中的注意事项,特别是虚析构函数的重要性,避免资源泄漏。还有override和final关键字的用法,这些C++11引入的特性可以帮助避免错误,提高代码可读性。 实际应用场景方面,可以举例说明多态在框架设计中的用途,比如插件系统或GUI事件处理,这些地方需要统一的接口但不同的实现。性能方面,可能需要提到虚函数调用会有一定的开销,但通常可以忽略,除非在极端性能敏感的场景。 最后,生成相关问题时,需要考虑用户可能进一步想知道的内容,比如虚函数表的具体结构,多态的性能影响,设计模式中的应用,以及如何避免常见的陷阱如对象切片等。这些问题可以帮助用户深入理解并正确应用这些概念。</think>### C++继承与多态详解:接口实现与应用 #### 一、继承基础 1. **核心概念** - 子类继承父类非`private`成员属性和方法[^3] - 继承类型:`public`/`protected`/`private`(常用`public`) - 代码示例: ```cpp class Animal { public: void eat() { cout << "Eating" << endl; } }; class Cat : public Animal { public: void meow() { cout << "Meow!" << endl; } }; ``` 2. **内存结构** 子类对象包含父类子对象,内存布局为:父类成员在前,子类新增成员在后[^1] #### 二、多态实现 1. **虚函数机制** - 虚函数声明:`virtual void func()` - 虚函数表(vtable)存储函数指针 - 代码示例: ```cpp class Shape { public: virtual void draw() = 0; // 纯虚函数 virtual ~Shape() {} // 虚析构函数 }; class Circle : public Shape { public: void draw() override { cout << "Drawing circle" << endl; } }; ``` 2. **多态调用** 通过基类指针/引用实现动态绑定: ```cpp Shape* shape = new Circle(); shape->draw(); // 输出"Drawing circle" delete shape; ``` #### 三、接口实现 C++通过纯虚函数实现接口: ```cpp class IPrintable { public: virtual void print() const = 0; virtual ~IPrintable() = default; }; class Document : public IPrintable { public: void print() const override { cout << "Printing document" << endl; } }; ``` #### 四、关键实践技巧 1. **override关键字** 明确表示覆盖父类虚函数,增强代码可读性: ```cpp class Derived : public Base { public: void func() override { ... } }; ``` 2. **虚析构函数** 必须为多态基类声明虚析构函数,防止内存泄漏: ```cpp class Base { public: virtual ~Base() = default; }; ``` 3. **final关键字** 禁止类被继承或方法被重写: ```cpp class FinalClass final { ... }; class Base { public: virtual void func() final { ... } }; ``` #### 五、典型应用场景 1. GUI框架:不同控件统一事件处理接口 2. 插件系统:通过基类接口扩展功能 3. 算法策略:运行时选择不同算法实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值