开发者

C++编程之多态的使用

目录
  • 1. 多态的基本概念
  • 2. 静态多态
    • 2.1 函数重载
    • 2.2 运算符重载
  • 3. 动态多态(运行时多态)
    • 3.1 虚函数
    • 3.2 纯虚函数和抽象类
  • 4. 虚析构函数
    • 5. override和final关键字(C++11)
      • 6. 多态的实现原理
        • 1. 虚函数表(vtable)机制
        • 2. 多态调用的底层过程
        • 3. 构造和析构过程中的vptr
        • 4. 多继承下的虚函数表
        • 5. 虚继承的虚函数表
        • 6. RTTI(运行时类型信息)
      • 7. 总结

        多态是面向对象编程的三大特性之一(封装、继承、多态),它允许使用统一的接口来处理不同类型的对象。在C++中,多态主要通过虚函数和继承机制来实现。

        1. 多态的基本概念

        多态分为两种:

        • 编译时多态(静态多态):通过函数重载和运算符重载实现
        • 运行时多态(动态多态):通过虚函数和继承实现

        2. 静态多态

        2.1 函数重载

        class Print {
        public:
            void show(int i) {
                cout << "整数: " << i << endl;
            }
            void show(double f) {
                cout << "浮点数: " << f << endl;
            }
            void show(string s) {
                cout << "字符串: " << s << endl;
            }
        };
        
        int main() {
            Print obj;
            obj.show(5);       // 调用show(int)
            obj.show(3.14);    // 调用show(double)
            obj.show("Hello"); // 调用show(string)
            return 0;
        }

        2.2 运算符重载

        class Complex {
        private:
            double real, imag;
        public:
            Complex(double r = 0, double i = 0) : real(r), imag(i) {}
            
            Complex operator + (const Complex& obj) {
                return Complex(real + obj.real, imag + obj.imag);
            }
            
            void display() {
                cout << real << " + " << imag << "i" << endl;
            }
        };
        
        int main() {
            Complex phpc1(3, 4), c2(5, 6);
            Complex c3 = c1 + c2; // 运算符重载
            c3.display(); // 输出: 8 + 10i
            return 0;
        }

        3. 动态多态(运行时多态)

        动态多态通过虚函数和继承实现,是C++中最常用的多态形式。

        3.1 虚函数

        class Base {
        public:
            virtual void show() { // 虚函数
                cout << "Base class show()" << endl;
            }
            void print() { // 非虚函数
                cout << "Base class print()" << endl;
            }
        编程客栈};
        
        class Derived : public Base {
        public:
            void show() override { // 重写虚函数
                cout << "Derived class show()" << endl;
            }
            void print() { // 隐藏基类的print()
                cout << "Derived class print()" << endl;
            }
        };
        
        int main() {
            Base* bptr;
            Derived d;
            bptr = &d;
            
            // 运行时多态,根据实际对象类型调用函数
            bptr->show(); // 输出: Derived class show()
            
            // 非虚函数,根据指针类型调用函数
            bptr->print(); // 输出: Base class print()
            
            return 0;
        }

        3.2 纯虚函数和抽象类

        class Shape { // 抽象类
        public:
            virtual float area() = 0; // 纯虚函数
            virtual void draw() = 0;  // 纯虚函数
        };
        
        class Circle : public Shape {
        private:
            float radius;
        public:
            Circle(float r) : radius(r) {}
            float area() override {
                return 3.14 * radius * radius;
            }
            void draw() override {
                cout << "Drawing Circle" << endl;
            }
        };
        
        class Square : public Shape {
        private:
            float side;
        public:
            Square(float s) : side(s) {}
            float area() override {
                return side * side;
            }
            void draw() override {
                cout << "Drawing Square" << endl;
            }
        };
        
        int main() {
            Shape* shapes[2];
            shapes[0] = new Circle(5);
            shapes[1] = new Square(4);
            
            for (int i = 0; i < 2; i++) {
                shapes[i]->draw();
                cout << "Area: " << shapes[i]->area() << endl;
            }
            
            delete shapes[0];
            delete shapes[1];
            return 0;
        }

        4. 虚析构函数

        当基类指针指向派生类对象时,如果基类析构函数不是虚函数,删除该指针只会调用基类的析构函数,可能导致内存泄漏。

        class Base {
        public:
            Base() { cout << "Base constructor" << endl; }
            virtual ~Base() { cout << "Base destructor" << endl; } // 虚析构函数
        };
        
        class Derived : public Base {
        public:
            Derived() { cout << "Derived constructor" << endl; }
            ~Derived() { cout << "Derived destructor" << endl; }
        };
        
        int main() {
            Base* b = new Derived();
            delete b; // 会调用Derived的析构函数,然后是Base的析构函数
            return 0;
        }

        5. override和final关键字(C++11)

        • override:显式指明函数重写基类虚函数
        • final:禁止派生类重写虚函数或禁止类被继承
        class Base {
        publandroidic:
            virtual void foo() {}
            virtual void bar() final {} // 不能被子类重写
        };
        
        class Derived : public Base {
        public:
            void foo() override {} // 正确:重写基类虚函数
            // void bar() override {} // 错误:bar是final的
        };
        
        class FinalClass final {}; // 不能被继承
        // class TryInherit : public FinalClass {}; // 错误

        6. 多态的实现原理

        多态是C++面向对象编程的核心特性之一,其底层实现机制非常精妙。

        C++通过虚函数表(virtual table,简称vtable)虚指针(vptr)实现运行时多态:

        • 每个包含虚函数的类都有一个虚函数表
        • 每个对象有一个指向虚函数表的指针(vptr)
        • 调用虚函数时,通过vptr找到虚函数表,再找到实际要调用的函数

        这种机制虽然有一定开销,但提供了强大的运行时多态能力,是C++面向对象编程的基石,现代CPU的预测执行可以部分缓解这种开销。

        1. 虚函数表(vtable)机制

        1.1 基本结构

        虚函数表(vtable):

        • 编译器为每个包含虚函数的类创建一个虚函数表
        • 表中按声明顺序存储该类所有虚函数的地址
        • 是一个静态数组,在编译时确定

        虚指针(vptr):

        • 每个对象内部包含一个隐藏的指针成员(vptr)
        • vptr指向该对象所属类的虚函数表
        • 由编译器自动添加和维护

        1.2 内存布局示例

        class Base {
        public:
            virtual void func1() { cout << "Base::func1" << endl; }
            virtual void func2() { cout << "Base::func2" << endl; }
            void func3() { cout << "Base::func3" << endl; }
            int a;
        };
        
        class Derived : public Base {
        public:
            void func1() override { cout << "Derived::func1" << endl; }
            virtual void func4() { cout << "Derived::func4" << endl; }
            int b;
        };

        内存布局示意图:

        Base类对象内存布局:

        +----------------+

        | vptr           | --> 指向Base的vtable

        +----------------+

        | int a          |

        +----------------+

        Base的vtable:

        +----------------+

        | &Base::func1   |

        +----------------+

        | &Base::func2   |

        +----------------+

        Derived类对象内存布局:

        +-javascript---------------+

        | vptr           | --> 指向Derived的vtable

        +----------------+

        | int a (继承)   |

        +----------------+

        | int b          |

        +----------------+

        Derived的vtable:

        +----------------+

        | &Derived::func1| // 重写的func1

        +----------------+

        | &Base::func2   | // 未重写的func2

        +----------------+

        | &Derived::func4| // 新增的func4

        +----------------+

        2. 多态调用的底层过程

        当通过基类指针或引用调用虚函数时:

        Base* ptr = new Derived();
        ptr->func1(); // 多态调用

        实际执行步骤:

        • 通过ptr找到对象的vptr(编译器知道vptr在对象中的偏移量)
        • 通过vptr找到虚函数表
        • 在虚函数表中找到func1对应的条目
        • 调用该地址处的函数

        3. 构造和析构过程中的vptr

        3.1 构造函数中的vptr初始化

        • 在进入构造函数体之前,编译器插入代码初始化vptr
        • 在构造过程中,vptr会随着构造的进行而改变
        Derived::Derived() {
            // 1. 首先初始化Base部分,此时vptr指向Base的vtable
            // 2. 然后初始化Derived成员,vptr改为指向Derived的vtable
            // 3. 最后执行构造函数体
        }

        3.2 析构函数中的vp编程tr处理

        • 在进入析构函数体之后,vptr首先指向当前类的vtable
        • 析构完成后,vptr会被设置为指向基类的vtable
        Derived::~Derived() {
            // 1. 执行析构函数体(此时vptr指向Derived的vtable)
            // 2. 析构Derived特有成员
            // 3. vptr改为指向Base的vtable
            // 4. 调用Base的析构函数
        }

        4. 多继承下的虚函数表

        多继承情况下,虚函数表会更复杂:

        class Base1 {
        public:
            virtual void f1() {}
            int a;
        };
        
        class Base2 {
        public:
            virtual void f2() {}
            int b;
        };
        
        class Derived : public Base1, public Base2 {
        public:
            void f1() override {}
            void f2() override {}
            virtual void f3() {}
            int c;
        };

        内存布局:

        Derived对象:

        +----------------+

        | Base1::vptr    | --> 指向Derived的Base1 vtable

        +----------------+

        | Base1::a       |

        +----------------+

        | Base2::vptr    | --> 指向Derived的Base2 vtable

        +----------------+

        | Base2::b       |

        +----------------+

        | Derived::c     |

        +----------------+

        Derived的Base1 vtable:

        +----------------+

        | &Derived::f1   |

        +----------------+

        | &Derived::f3   |

        +----------------+

        Derived的Base2 vtable:

        +----------------+

        | &Derived::f2   |

        +----------------+

        5. 虚继承的虚函数表

        虚继承(virtual inheritance)会使得虚函数表更加复杂,通常会引入额外的虚基类指针。

        6. RTTI(运行时类型信息)

        dynamic_cast和typeid也依赖于虚函数表,通常vtable的第一个条目指向类型信息。

        7. 总结

        C++多态的实现依赖于:

        • 每个类有自己的虚函数表
        • 每个对象有自己的虚指针
        • 调用虚函数时通过虚指针间接查找
        • 继承关系反映在虚函数表的布局中

        性能考虑:

        多态调用相比普通函数调用有以下开销:

        • 通过vptr间接访问虚函数表
        • 通过虚函数表间接调用函数
        • 通常无法内联

        多态的应用场景:

        • 实现接口与实现的分离
        • 设计模式(如工厂模式、策略模式等)
        • 回调函数
        • 容器存储不同类型的对象但统一处理

        多态是C++面向对象编程中非常强大的特性,合理使用可以提高代码的灵活性和可扩展性。

        以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

        0

        上一篇:

        下一篇:

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        最新开发

        开发排行榜