目 录CONTENT

文章目录

C++特性:多态

TalentQ
2025-07-30 / 0 评论 / 0 点赞 / 11 阅读 / 0 字

1 什么是多态

多态(Polymorphism)是C++面向对象编程的三大特性(“封装”、“继承”、“多态”)之一。它允许同一个接口在不同对象上展现不同的行为。多态的核心价值在于接口复用、代码解耦、扩展性强,实现了“对扩展开放,对修改封闭”的设计原则。

C++中的多态分为两类:

  • 静态多态(编译时多态):如函数重载、模板、运算符重载等,编译期决定调用关系。

  • 动态多态(运行时多态):如虚函数、接口继承等,运行期决定调用关系。

2 动态多态(运行时多态)

2.1 动态多态的原理

动态多态依赖于虚函数和指针/引用。其核心机制是虚函数表(vtable)。每个含虚函数的类都有一张vtable,表中存储虚函数的地址。通过基类指针或引用调用虚函数时,实际会查表并调用派生类的实现。

2.2 虚函数与重写

示例:

#include <iostream>

// 动物基类
class Animal {
 public:
  // 虚函数:允许子类提供自己的实现
  virtual void Speak() const {
    std::cout << "Animal makes a sound." << std::endl;
  }
  // 虚析构函数:确保delete基类指针时能正确析构派生类对象
  virtual ~Animal() = default;
};

// 狗类,继承自Animal
class Dog : public Animal {
 public:
  // 重写Speak方法
  void Speak() const override {
    std::cout << "Dog barks." << std::endl;
  }
};

// 猫类,继承自Animal
class Cat : public Animal {
 public:
  void Speak() const override {
    std::cout << "Cat meows." << std::endl;
  }
};

// 接口演示
void MakeAnimalSpeak(const Animal& animal) {
  animal.Speak();  // 实际调用的版本取决于animal的真实类型
}

int main() {
  Dog dog;
  Cat cat;
  MakeAnimalSpeak(dog);  // 输出:Dog barks.
  MakeAnimalSpeak(cat);  // 输出:Cat meows.
  return 0;
}
  • virtual关键字声明虚函数,子类用override修饰重写。

  • 通过基类引用MakeAnimalSpeak调用Speak,实现了接口统一和动态分派。

2.3 纯虚函数与抽象类

纯虚函数用来定义接口,不能被实例化,只能被继承实现。

// 图形基类,抽象类
class Shape {
 public:
  // 纯虚函数:无实现,派生类必须实现
  virtual double Area() const = 0;
  virtual ~Shape() = default;
};

class Circle : public Shape {
 public:
  explicit Circle(double r) : radius_(r) {}
  double Area() const override {
    return 3.14159 * radius_ * radius_;
  }
 private:
  double radius_;
};
  • 纯虚函数= 0,Shape不可实例化。

  • Circle实现了Area,才能被实例化。

2.4 虚析构函数的重要性

若基类指针指向派生类对象,析构时需虚析构,否则只会调用基类析构,造成资源泄露。

class Base {
 public:
  virtual ~Base() { std::cout << "Base destroyed" << std::endl; }
};

class Derived : public Base {
 public:
  ~Derived() override { std::cout << "Derived destroyed" << std::endl; }
};

int main() {
  Base* ptr = new Derived();
  delete ptr;  // 先调用Derived析构,再调用Base析构
  return 0;
}
  • virtual ~Base()保证delete时能调用到~Derived()

2.5 动态多态的适用场景

  1. 接口统一:如操作不同类型的设备、文件、UI控件等。

  2. 便于扩展:新增功能只需继承基类并实现虚函数。

  3. 设计模式:如工厂、策略、观察者等。

2.6 动态多态的注意事项

  • 构造函数不可为虚函数:对象构造时类型未定。

  • 避免对象切片:用指针/引用传递,值传递会丢失子类特性。

  • 虚函数的默认参数由静态类型决定:避免混淆。

3 静态多态(编译时多态)

3.1 静态多态的定义

静态多态在编译期决定调用关系,常见有:

  • 函数重载(Overloading)

  • 运算符重载(Operator Overloading)

  • 模板(Templates)

  • CRTP(Curiously Recurring Template Pattern)

3.2 函数重载

同名函数不同参数,编译器自动选择合适的重载版本。

#include <iostream>

void Print(int value) {
  std::cout << "int: " << value << std::endl;
}

void Print(double value) {
  std::cout << "double: " << value << std::endl;
}

int main() {
  Print(42);     // int: 42
  Print(3.14);   // double: 3.14
  return 0;
}
  • Print(int)Print(double)由编译器根据参数类型自动选择。

3.3 运算符重载

为自定义类型赋予类似内置类型的运算能力。

#include <iostream>

class Vector2D {
 public:
  Vector2D(double x, double y) : x_(x), y_(y) {}

  // 重载+运算符
  Vector2D operator+(const Vector2D& other) const {
    return Vector2D(x_ + other.x_, y_ + other.y_);
  }

  void Print() const {
    std::cout << "(" << x_ << ", " << y_ << ")" << std::endl;
  }
 private:
  double x_;
  double y_;
};

int main() {
  Vector2D a(1.0, 2.0);
  Vector2D b(3.0, 4.0);
  Vector2D c = a + b;
  c.Print();  // (4, 6)
  return 0;
}
  • 通过重载operator+,Vector2D对象可以直接相加,增强了代码的可读性和可维护性。

3.4 模板与泛型编程

模板允许编写与类型无关的代码,编译器根据实际类型实例化代码。

#include <iostream>
#include <string>

// 函数模板
template<typename T>
void Print(const T& value) {
  std::cout << value << std::endl;
}

int main() {
  Print(123);         // 123
  Print(3.14);        // 3.14
  Print("hello");     // hello
  return 0;
}
  • Print模板可接受任意类型参数,提升代码复用性。

类模板示例:

#include <iostream>

template<typename T>
class Box {
 public:
  explicit Box(const T& value) : value_(value) {}
  void Print() const {
    std::cout << value_ << std::endl;
  }
 private:
  T value_;
};

int main() {
  Box<int> int_box(10);
  Box<std::string> str_box("C++");
  int_box.Print();    // 10
  str_box.Print();    // C++
  return 0;
}
  • Box<T>可存储任意类型的数据,类型安全且无需重复实现。

3.5 CRTP(奇异递归模板模式)

CRTP是一种高级静态多态技术,允许在编译期实现类似虚函数的行为,无虚表开销。

#include <iostream>

// 基类模板,T为派生类类型
template <typename T>
class Base {
 public:
  void Interface() const {
    // 强制将this转换为派生类指针,调用派生类的实现
    static_cast<const T*>(this)->Implementation();
  }
};

// 派生类
class Derived : public Base<Derived> {
 public:
  void Implementation() const {
    std::cout << "Derived Implementation" << std::endl;
  }
};

int main() {
  Derived d;
  d.Interface();  // Derived Implementation
  return 0;
}
  • CRTP在编译期绑定,实现了零运行时开销的多态。

3.6 静态多态的适用场景

  1. 泛型算法与容器:如STL。

  2. 性能敏感场合:如数值计算、图像处理。

  3. 类型集合固定:如编译期已知所有类型。

4 动态多态与静态多态对比

特性

静态多态(编译时)

动态多态(运行时)

机制

模板、重载、CRTP

虚函数、继承

决定时机

编译期

运行期

性能

高(无虚表)

略低(虚表查找)

灵活性

一般(类型固定)

高(可运行时扩展)

代码复用

泛型复用

接口复用

典型应用

STL、数值计算

工厂、策略、插件系统

举例说明:

  • 静态多态适合已知类型、性能敏感的场景,如STL容器、算法库。

  • 动态多态适合类型未知、需运行时扩展的场景,如插件系统、UI组件库。

5 多态与设计模式实战

多态是设计模式的基础,工厂模式即是经典应用。

#include <memory>
#include <string>

// 工厂类
class AnimalFactory {
 public:
  static std::unique_ptr<Animal> CreateAnimal(const std::string& type) {
    if (type == "dog") {
      return std::make_unique<Dog>();
    } else if (type == "cat") {
      return std::make_unique<Cat>();
    }
    return nullptr;
  }
};

int main() {
  auto animal = AnimalFactory::CreateAnimal("dog");
  if (animal) {
    animal->Speak();  // Dog barks.
  }
  return 0;
}
  • 工厂模式结合动态多态,屏蔽了对象创建细节,便于扩展新类型。

6 总结与建议

  1. 多态是C++高内聚、低耦合的核心特性,分为静态多态和动态多态。

  2. 动态多态依赖虚函数与继承,适合接口抽象、运行时扩展。

  3. 静态多态依赖模板、重载、CRTP,适合泛型和高性能场景。

  4. 合理选择多态类型:性能敏感选静态多态,灵活扩展选动态多态。

  5. 注意虚析构、对象切片、默认参数等细节问题,确保代码健壮。

  6. 多态是设计模式的基础,理解其原理和用法,是成为C++高手的必经之路。

0

评论区