C++怎么实现多态
多态指同一个“接口”或“调用语句”在不同类型上表现出不同的行为。C中主要有两种多态静态多态和动态多态静态多态函数在被调用时具体执行哪段代码在编译阶段就已经确定了。编译器根据函数参数的类型或数量直接把函数调用链接到具体的函数地址无需继承和虚函数。实现方式函数重载运算符重载模板代码示例函数重载#include iostream void print(int i) { std::cout 打印整数: i \n; } void print(double d) { std::cout 打印浮点数: d \n; } int main() { // 编译器根据参数是int绑定到 print(int) 的地址 print(10); // 编译器根据参数是double绑定到 print(double) 的地址 print(3.14); return0; }运算符重载#include iostream class Point { private: int x, y; public: Point(int x 0, int y 0) : x(x), y(y) {} // 重载 运算符 Point operator(const Point other) const { return Point(this-x other.x, this-y other.y); } // 友元函数重载 friendstd::ostream operator(std::ostream os, const Point p); }; // 全局重载 std::ostream operator(std::ostream os, const Point p) { os ( p.x , p.y ); return os; } int main() { Point p1(10, 20); Point p2(5, 5); // Point Point, 调用 Point::operator(const Point) Point p3 p1 p2; std::cout p3: p3 std::endl; // 输出: (15, 25) return0; }模版#include iostream template typename T void add(T a, T b) { std::cout 模板加法结果: a b std::endl; } int main() { // 编译器根据参数类型是int自动生成 addint 版本 add(1, 2); return 0; }优缺点优点编译器完成方法绑定编译器可进行内联优化不依赖继承体系组合更灵活。缺点必须在编译时就知道所有类型编译耗时、代码膨胀。动态多态函数在被调用时具体执行哪段代码在编译阶段无法确定只有在程序运行时根据对象的实际类型是基类对象还是派生类对象来决定。实现方式三条必须满足缺一不可有继承关系基类中有虚函数派生类重写了该虚函数通过基类的指针或引用去调用虚函数#include iostream // 基类 class Shape { public: // 虚函数 virtual void draw() { std::cout 绘制一个通用的形状 std::endl; } }; // 派生类 class Circle :public Shape { public: // 重写基类虚函数 void draw() override { std::cout 绘制一个圆形 O std::endl; } }; // 派生类 class Rectangle :public Shape { public: // 重写基类虚函数 void draw() override { std::cout 绘制一个矩形 [] std::endl; } }; // 注意这里函数参数是Shape*编译时不知道具体指向哪个类型 // 只有运行时才知道指针指向的具体类型 void startDrawing(Shape* shape) { shape-draw(); } int main() { Circle c; Rectangle r; startDrawing(c); // 输出绘制一个圆形 O startDrawing(r); // 输出绘制一个矩形 [] return0; }实现原理在编译含有 virtual 函数的类时编译器会自动添加虚函数表和虚指针通过查找虚函数表得到实际要调用函数的地址虚函数表vtable归属于类同一个类共享一个虚函数表。是一个函数指针数组存放该类所有虚函数地址。存储在只读数据段 .rodata程序运行时加载虚指针vptr归属于对象每个对象都有一个虚指针。是一个指针指向该类的vtable。存储在对象内存布局的最头部便于快速读取。假设有一个基类 Base含有两个虚函数 func1 和 func2Derived 类继承自 Base并重写了 func1 函数func2 函数则继承了 Base 的 func2class Base { public: virtual void func1() { cout Base::func1 endl; } // 虚函数 virtual void func2() { cout Base::func2 endl; } // 虚函数 virtual ~Base() default; // 虚析构函数 void normal() {} // 普通函数不占对象内存 private: int a; int b; }; class Derived :public Base { public: // 重写了 func1 void func1() override { cout Derived::func1 endl; } // func2继承了Base的func2 private: int c; int d; };优缺点1.优点灵活性高可以通过基类指针或引用操作派生类对象。2.缺点编译器无法优化虚函数运行时进行类型检查和方法绑定有一定开销。虚函数是怎么实现动态绑定的vptr 和 vtable是什么有虚函数的类中会有一个隐藏指针 vptr指向该类的虚函数表 vtablevtable 是一个函数指针数组存放该类所有虚函数地址。调用虚函数时编译器生成代码从对象取 vptr再在 vtable 的槽位中找到函数地址并间接调用从而在运行期决定调用哪个版本。一个对象里有几个 vptr单继承通常只有一个。多继承时每个含虚函数的基类子对象各自含有一个 vptr。普通成员函数占对象内存吗不占。成员函数代码存储在可执行文件的代码段每个对象只存数据成员和编译器需要的字段基类有一个 func(int)派生类写 func(double)会怎样会触发名字隐藏派生类的 func(double) 会把基类的所有同名函数隐藏掉导致无法通过派生类对象调用基类的 func(int)为什么基类析构函数要设为 virtual通过基类指针删除派生类对象时如果基类析构函数非 virtual会导致只调用基类的析构函数而不调用派生类的析构函数导致资源泄露