一、什么是“多态”从字面上理解多态就是“多种形态”。在程序设计里它指的是使用统一的接口却可以对不同类型的对象做出不同的具体行为。更具体一点静态多态编译期多态编译器在编译阶段就能决定到底调用哪一个函数、用哪个版本的代码。代表形式函数重载、运算符重载、模板泛型。动态多态运行期多态编译时先只“知道有这个虚函数”真正要调用哪个实现要到运行时根据对象的实际类型来决定。代表形式virtual虚函数 继承 基类指针/引用。二、静态多态编译期就决定一切1. 静态多态的特点“静态”的含义是绑定发生在编译期。编译器在编译阶段就根据实参类型、模板参数等把要调用的函数、生成的代码都选好、生成好。运行时不会再为了“选函数”去查表因此不需要虚表vtable也没额外的间接调用开销。代价是泛型代码会在编译期生成多个实例代码体积可能增长另外有些行为必须在编译期就能确定。常见的静态多态形式有三个函数重载、运算符重载、模板。2. 函数重载同名函数根据参数列表的不同进行区分1234567891011121314151617voidprint(intx) {std::cout int: x std::endl;}voidprint(doublex) {std::cout double: x std::endl;}voidprint(conststd::string s) {std::cout string: s std::endl;}intmain() {print(10);// 调用 print(int)print(3.14);// 调用 print(double)print(hello);// 字面量转成 std::string调用 print(const std::string)}在这里“多态”的表现是同一个名字print可以处理不同的类型。编译器会在编译期进行“重载决议”选出最合适的一个版本。这就是静态多态。补充一个和继承相关的点如果派生类中重新定义了与基类同名但参数不同的函数会发生“名字隐藏”。要想保留基类的其他重载可以用using Base::func;把基类同名重载导入作用域。3. 运算符重载运算符重载本质上也是一种函数重载区别只是语法形式更自然。编译器在编译期决定调用哪个重载所以它也是静态多态。123456789101112131415structPoint {intx, y;Point(intx,inty) : x(x), y(y) {}Point operator(constPoint other)const{returnPoint(x other.x, y other.y);}};intmain() {Point a(1, 2), b(3, 4);Point c a b;// 实际是调用 a.operator(b)std::cout c.x , c.y std::endl;// 4, 6}“同一个运算符” 对于不同类型例如int int、Point Point会产生不同的行为同样属于静态多态。4. 模板与泛型编程模板是 C 中实现静态多态最强大的工具。函数模板和类模板都属于参数化多态在编译期根据类型参数生成具体代码。12345678910templatetypenameTT add(T a, T b) {returna b;// 只要求 T 支持 operator}intmain() {std::cout add(1, 2) std::endl;// 实例化出 addintstd::cout add(1.5, 2.5) std::endl;// 实例化出 adddoublestd::cout add(std::string(a),b) std::endl;// 实例化出 addstd::string}这里的add在源代码里只写了一份但编译器会根据实际调用自动生成多个版本。本质上它也是一种“接口相同add但根据类型不同产生不同行为”的多态只是全部发生在编译期。模板和函数重载还可以配合使用例如std::sort接受不同类型的迭代器、不同的比较器本质上依然是静态多态的一种组合形式。三、动态多态运行期由对象说了算静态多态的“主角”是“类型”和“模板参数”它解决的是“类型不一样怎么共享代码”。动态多态的“主角”是“对象的实际类型”解决的是“一群有共同接口的对象具体用哪个实现要到运行期才知道”。1. 动态多态的三个要素C 中要用到动态多态基本需要三个条件继承有一个基类和若干派生类虚函数基类中把要多态调用的函数声明为virtual通过基类指针或引用来操作派生类对象经典例子12345678910111213141516171819202122232425262728293031323334classShape {public:virtualvoiddraw() {// 虚函数std::cout Shape::draw std::endl;}virtual~Shape() default;// 虚析构后面会讲};classCircle :publicShape {public:voiddraw() override {// override 明确表明“重写基类虚函数”std::cout Circle::draw std::endl;}};classRect :publicShape {public:voiddraw() override {std::cout Rect::draw std::endl;}};voidrender(Shape s) {s.draw();// 这里发生动态绑定}intmain() {Circle c;Rect r;render(c);// 调用 Circle::drawrender(r);// 调用 Rect::draw}复制讲解这里render只认识Shape这个“统一接口”但传入不同的实际对象Circle或Rect时会在运行期调用不同版本的draw。这就是运行期多态。