第15章 多态 多态的概念与原理 多态是面向对象编程的核心特性之一,它允许使用统一的接口处理不同类型的对象,实现”一个接口,多种实现”的设计理念。
多态的核心原理 接口与实现分离 :通过抽象接口定义行为,具体实现由派生类提供运行时绑定 :调用哪个具体实现由对象的实际类型决定,而非声明类型代码复用 :统一的接口使得代码可以处理不同类型的对象扩展性 :新的派生类可以无缝集成到现有系统中多态的类型 C++支持两种类型的多态:
编译时多态(静态多态) :在编译阶段确定调用哪个函数
运行时多态(动态多态) :在运行阶段确定调用哪个函数
运行时多态的实现 虚函数 核心特性 :虚函数是在基类中声明并在派生类中重写的成员函数,通过虚函数表(vtable)实现运行时多态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Base {public : virtual void doSomething () { std::cout << "Base::doSomething()" << std::endl; } virtual ~Base () { std::cout << "Base::~Base()" << std::endl; } }; class Derived : public Base {public : void doSomething () override { std::cout << "Derived::doSomething()" << std::endl; } ~Derived () { std::cout << "Derived::~Derived()" << std::endl; } }; void usePolymorphism (Base* ptr) { ptr->doSomething (); } int main () { Base* basePtr = new Base (); Base* derivedPtr = new Derived (); usePolymorphism (basePtr); usePolymorphism (derivedPtr); delete basePtr; delete derivedPtr; return 0 ; }
虚函数表(vtable)与虚指针(vptr) 核心原理 :虚函数的实现依赖于虚函数表和虚指针机制。
虚函数表(vtable) :每个包含虚函数的类都有一个虚函数表,存储该类所有虚函数的地址虚指针(vptr) :每个对象都有一个虚指针,指向其所属类的虚函数表运行时绑定 :当调用虚函数时,通过对象的虚指针找到虚函数表,再根据函数在表中的位置找到并调用相应的函数1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Base {public : virtual void func1 () {} virtual void func2 () {} void func3 () {} }; class Derived : public Base {public : void func1 () override {} virtual void func4 () {} };
纯虚函数与抽象类 核心特性 :纯虚函数是在基类中声明但不提供实现的虚函数,包含纯虚函数的类称为抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class AbstractShape {public : virtual ~AbstractShape () = default ; virtual void draw () const = 0 ; virtual double area () const = 0 ; virtual std::string name () const { return "AbstractShape" ; } }; class Circle : public AbstractShape {private : double radius; public : explicit Circle (double r) : radius(r) { } void draw () const override { std::cout << "Drawing a circle with radius " << radius << std::endl; } double area () const override { return M_PI * radius * radius; } std::string name () const override { return "Circle" ; } }; void processShape (const AbstractShape& shape) { shape.draw (); std::cout << "Area: " << shape.area () << std::endl; std::cout << "Name: " << shape.name () << std::endl; }
接口 核心特性 :接口是只包含纯虚函数的抽象类,用于定义行为契约。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Drawable {public : virtual ~Drawable () = default ; virtual void draw () const = 0 ; virtual void resize (double factor) = 0 ; }; class Colorable {public : virtual ~Colorable () = default ; virtual void setColor (const std::string& color) = 0 ; virtual std::string getColor () const = 0 ; }; class ColoredCircle : public Drawable, public Colorable {private : double radius; std::string color; public : ColoredCircle (double r, std::string c) : radius (r), color (std::move (c)) {} void draw () const override { std::cout << "Drawing a " << color << " circle with radius " << radius << std::endl; } void resize (double factor) override { radius *= factor; } void setColor (const std::string& c) override { color = c; } std::string getColor () const override { return color; } };
编译时多态的实现 函数重载 核心特性 :函数重载允许在同一个作用域内定义多个同名函数,通过参数列表的不同来区分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Calculator {public : int add (int a, int b) { return a + b; } double add (double a, double b) { return a + b; } std::string add (const std::string& a, const std::string& b) { return a + b; } };
运算符重载 核心特性 :运算符重载允许为自定义类型定义运算符的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Vector2D {private : double x, y; public : Vector2D (double x = 0 , double y = 0 ) : x (x), y (y) {} Vector2D operator +(const Vector2D& other) const { return Vector2D (x + other.x, y + other.y); } Vector2D operator -(const Vector2D& other) const { return Vector2D (x - other.x, y - other.y); } Vector2D operator *(double scalar) const { return Vector2D (x * scalar, y * scalar); } friend std::ostream& operator <<(std::ostream& os, const Vector2D& vec) { os << "(" << vec.x << ", " << vec.y << ")" ; return os; } }; void useVector2D () { Vector2D v1 (1 , 2 ) ; Vector2D v2 (3 , 4 ) ; Vector2D v3 = v1 + v2; Vector2D v4 = v1 - v2; Vector2D v5 = v1 * 2.0 ; std::cout << "v1: " << v1 << std::endl; std::cout << "v2: " << v2 << std::endl; std::cout << "v1 + v2: " << v3 << std::endl; std::cout << "v1 - v2: " << v4 << std::endl; std::cout << "v1 * 2: " << v5 << std::endl; }
模板 核心特性 :模板允许定义通用的函数或类,在编译时根据实际类型生成具体的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 template <typename T> T maximum (T a, T b) { return a > b ? a : b; } template <typename T> class Stack {private : std::vector<T> elements; public : void push (const T& value) { elements.push_back (value); } T pop () { if (elements.empty ()) { throw std::runtime_error ("Stack is empty" ); } T value = elements.back (); elements.pop_back (); return value; } bool isEmpty () const { return elements.empty (); } }; void useTemplates () { int maxInt = maximum (10 , 20 ); double maxDouble = maximum (3.14 , 2.71 ); std::string maxString = maximum (std::string ("apple" ), std::string ("banana" )); Stack<int > intStack; intStack.push (1 ); intStack.push (2 ); intStack.push (3 ); while (!intStack.isEmpty ()) { std::cout << intStack.pop () << " " ; } std::cout << std::endl; Stack<std::string> stringStack; stringStack.push ("hello" ); stringStack.push ("world" ); while (!stringStack.isEmpty ()) { std::cout << stringStack.pop () << " " ; } std::cout << std::endl; }
多态的高级应用 虚函数与默认参数 核心原则 :虚函数的默认参数是静态绑定的,即使用基类声明中的默认参数值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Base {public : virtual void doSomething (int value = 10 ) { std::cout << "Base::doSomething() with value " << value << std::endl; } }; class Derived : public Base {public : void doSomething (int value = 20 ) override { std::cout << "Derived::doSomething() with value " << value << std::endl; } }; void testDefaultArguments () { Base* basePtr = new Base (); Base* derivedPtr = new Derived (); basePtr->doSomething (); derivedPtr->doSomething (); delete basePtr; delete derivedPtr; }
虚函数与const限定符 核心特性 :const和非const版本的成员函数可以重载,虚函数的const属性是其签名的一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Base {public : virtual void doSomething () { std::cout << "Base::doSomething() (non-const)" << std::endl; } virtual void doSomething () const { std::cout << "Base::doSomething() (const)" << std::endl; } }; class Derived : public Base {public : void doSomething () override { std::cout << "Derived::doSomething() (non-const)" << std::endl; } void doSomething () const override { std::cout << "Derived::doSomething() (const)" << std::endl; } }; void testConstPolymorphism () { Derived d; const Derived& constRef = d; d.doSomething (); constRef.doSomething (); Base* basePtr = new Derived (); const Base* constBasePtr = new Derived (); basePtr->doSomething (); constBasePtr->doSomething (); delete basePtr; delete constBasePtr; }
虚函数与引用 核心特性 :多态同样适用于引用,引用的静态类型决定了可访问的接口,而动态类型决定了实际调用的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Base {public : virtual void doSomething () { std::cout << "Base::doSomething()" << std::endl; } }; class Derived : public Base {public : void doSomething () override { std::cout << "Derived::doSomething()" << std::endl; } void doSomethingElse () { std::cout << "Derived::doSomethingElse()" << std::endl; } }; void testReferencePolymorphism () { Derived d; Base& baseRef = d; baseRef.doSomething (); Derived& derivedRef = static_cast <Derived&>(baseRef); derivedRef.doSomethingElse (); }
多态与继承层次 核心特性 :在复杂的继承层次中,多态允许通过基类指针或引用调用任何派生类的虚函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 class Animal {public : virtual ~Animal () = default ; virtual void makeSound () const = 0 ; virtual void move () const = 0 ; virtual std::string name () const = 0 ; }; class Mammal : public Animal {public : void move () const override { std::cout << "Mammal moves by walking" << std::endl; } }; class Bird : public Animal {public : void move () const override { std::cout << "Bird moves by flying" << std::endl; } }; class Fish : public Animal {public : void move () const override { std::cout << "Fish moves by swimming" << std::endl; } }; class Dog : public Mammal {public : void makeSound () const override { std::cout << "Dog barks" << std::endl; } std::string name () const override { return "Dog" ; } }; class Cat : public Mammal {public : void makeSound () const override { std::cout << "Cat meows" << std::endl; } std::string name () const override { return "Cat" ; } }; class Eagle : public Bird {public : void makeSound () const override { std::cout << "Eagle screeches" << std::endl; } std::string name () const override { return "Eagle" ; } }; class Salmon : public Fish {public : void makeSound () const override { std::cout << "Salmon makes no sound" << std::endl; } std::string name () const override { return "Salmon" ; } }; void processAnimal (const Animal& animal) { std::cout << "Processing " << animal.name () << ":" << std::endl; animal.makeSound (); animal.move (); std::cout << std::endl; } void testAnimalPolymorphism () { Dog dog; Cat cat; Eagle eagle; Salmon salmon; processAnimal (dog); processAnimal (cat); processAnimal (eagle); processAnimal (salmon); }
多态的优缺点 优点 代码复用 :统一的接口可以处理不同类型的对象,减少重复代码扩展性 :新的派生类可以无缝集成到现有系统中,无需修改现有代码灵活性 :通过基类指针或引用可以动态选择合适的实现可维护性 :代码结构清晰,易于理解和维护抽象能力 :通过抽象类和接口可以定义清晰的行为契约缺点 性能开销 :虚函数调用需要通过虚函数表查找,比普通函数调用慢内存开销 :每个包含虚函数的类都需要虚函数表,每个对象都需要虚指针复杂性 :复杂的继承层次可能导致代码难以理解和维护向下转型风险 :需要小心使用类型转换,避免运行时错误设计难度 :正确设计抽象接口需要丰富的经验多态的最佳实践 1. 正确使用虚函数 核心原则 :只对需要多态的函数使用虚函数,避免不必要的性能开销。
最佳实践 :
基类析构函数应该声明为virtual 派生类重写虚函数时使用override关键字 考虑使用final关键字防止不必要的覆盖 避免在构造函数和析构函数中调用虚函数 2. 合理设计抽象接口 核心原则 :抽象接口应该简洁明了,只包含必要的方法。
最佳实践 :
使用纯虚函数定义接口 接口应该符合单一职责原则 避免在接口中包含实现细节 考虑使用组合而非继承来扩展功能 3. 处理多态与异常 核心原则 :异常处理应该与多态设计相结合,确保异常能够正确传播。
最佳实践 :
异常类型应该有合理的层次结构 虚函数可以抛出异常,但派生类抛出的异常应该与基类兼容 析构函数不应该抛出异常 考虑使用RAII模式管理资源 4. 性能优化 核心原则 :在性能关键的代码中,需要平衡多态的便利性和性能开销。
最佳实践 :
对于性能关键的代码,考虑使用编译时多态(模板) 避免深层的继承层次 合理使用内联函数 考虑使用CRTP(奇异递归模板模式)实现静态多态 5. 类型转换 核心原则 :在多态系统中,类型转换应该安全可靠。
最佳实践 :
尽量避免向下转型 使用dynamic_cast进行安全的向下转型 考虑使用工厂模式或策略模式减少类型转换的需要 对于编译时已知的类型转换,使用static_cast 多态的实际应用场景 1. 图形用户界面(GUI) 核心需求 :处理不同类型的UI组件,如按钮、文本框、列表等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 class Widget {public : virtual ~Widget () = default ; virtual void draw () const = 0 ; virtual void resize (int width, int height) = 0 ; virtual void setPosition (int x, int y) = 0 ; virtual void handleEvent (const Event& event) = 0 ; }; class Button : public Widget {private : std::string label; int x, y, width, height; public : Button (const std::string& label, int x, int y, int width, int height) : label (label), x (x), y (y), width (width), height (height) {} void draw () const override { std::cout << "Drawing button with label '" << label << "' at (" << x << ", " << y << ")" << std::endl; } void resize (int width, int height) override { this ->width = width; this ->height = height; } void setPosition (int x, int y) override { this ->x = x; this ->y = y; } void handleEvent (const Event& event) override { if (event.type == EventType::CLICK) { std::cout << "Button clicked: " << label << std::endl; } } }; class TextBox : public Widget {private : std::string text; int x, y, width, height; public : TextBox (const std::string& text, int x, int y, int width, int height) : text (text), x (x), y (y), width (width), height (height) {} void draw () const override { std::cout << "Drawing text box with text '" << text << "' at (" << x << ", " << y << ")" << std::endl; } void resize (int width, int height) override { this ->width = width; this ->height = height; } void setPosition (int x, int y) override { this ->x = x; this ->y = y; } void handleEvent (const Event& event) override { if (event.type == EventType::KEY_PRESS) { std::cout << "Text box key press: " << event.key << std::endl; } } }; class UIManager {private : std::vector<std::unique_ptr<Widget>> widgets; public : void addWidget (std::unique_ptr<Widget> widget) { widgets.push_back (std::move (widget)); } void drawAll () const { for (const auto & widget : widgets) { widget->draw (); } } void handleEvent (const Event& event) { for (const auto & widget : widgets) { widget->handleEvent (event); } } };
2. 数据库访问层 核心需求 :支持不同类型的数据库,如MySQL、PostgreSQL、SQLite等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class Database {public : virtual ~Database () = default ; virtual void connect (const std::string& connectionString) = 0 ; virtual void disconnect () = 0 ; virtual std::unique_ptr<ResultSet> executeQuery (const std::string& query) = 0 ; virtual int executeUpdate (const std::string& query) = 0 ; }; class MySQLDatabase : public Database {public : void connect (const std::string& connectionString) override { std::cout << "Connecting to MySQL database: " << connectionString << std::endl; } void disconnect () override { std::cout << "Disconnecting from MySQL database" << std::endl; } std::unique_ptr<ResultSet> executeQuery (const std::string& query) override { std::cout << "Executing MySQL query: " << query << std::endl; return nullptr ; } int executeUpdate (const std::string& query) override { std::cout << "Executing MySQL update: " << query << std::endl; return 0 ; } }; class PostgreSQLDatabase : public Database {public : void connect (const std::string& connectionString) override { std::cout << "Connecting to PostgreSQL database: " << connectionString << std::endl; } void disconnect () override { std::cout << "Disconnecting from PostgreSQL database" << std::endl; } std::unique_ptr<ResultSet> executeQuery (const std::string& query) override { std::cout << "Executing PostgreSQL query: " << query << std::endl; return nullptr ; } int executeUpdate (const std::string& query) override { std::cout << "Executing PostgreSQL update: " << query << std::endl; return 0 ; } }; class DatabaseFactory {public : static std::unique_ptr<Database> createDatabase (const std::string& type) { if (type == "mysql" ) { return std::make_unique <MySQLDatabase>(); } else if (type == "postgresql" ) { return std::make_unique <PostgreSQLDatabase>(); } else { throw std::invalid_argument ("Unknown database type: " + type); } } }; void useDatabase () { try { auto db = DatabaseFactory::createDatabase ("mysql" ); db->connect ("host=localhost;port=3306;dbname=test;user=root;password=123456" ); db->executeQuery ("SELECT * FROM users" ); db->disconnect (); auto pgDb = DatabaseFactory::createDatabase ("postgresql" ); pgDb->connect ("host=localhost;port=5432;dbname=test;user=postgres;password=123456" ); pgDb->executeQuery ("SELECT * FROM users" ); pgDb->disconnect (); } catch (const std::exception& e) { std::cerr << "Error: " << e.what () << std::endl; } }
3. 日志系统 核心需求 :支持不同类型的日志输出,如控制台、文件、网络等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 class Logger {public : virtual ~Logger () = default ; virtual void log (const std::string& message) = 0 ; virtual void logError (const std::string& message) = 0 ; virtual void logWarning (const std::string& message) = 0 ; virtual void logInfo (const std::string& message) = 0 ; }; class ConsoleLogger : public Logger {public : void log (const std::string& message) override { std::cout << "[LOG] " << message << std::endl; } void logError (const std::string& message) override { std::cerr << "[ERROR] " << message << std::endl; } void logWarning (const std::string& message) override { std::cout << "[WARNING] " << message << std::endl; } void logInfo (const std::string& message) override { std::cout << "[INFO] " << message << std::endl; } }; class FileLogger : public Logger {private : std::ofstream file; public : explicit FileLogger (const std::string& filename) { file.open (filename, std::ios::out | std::ios::app); if (!file) { throw std::runtime_error ("Failed to open log file: " + filename); } } ~FileLogger () override { if (file.is_open ()) { file.close (); } } void log (const std::string& message) override { file << "[LOG] " << message << std::endl; } void logError (const std::string& message) override { file << "[ERROR] " << message << std::endl; } void logWarning (const std::string& message) override { file << "[WARNING] " << message << std::endl; } void logInfo (const std::string& message) override { file << "[INFO] " << message << std::endl; } }; class LogManager {private : std::vector<std::unique_ptr<Logger>> loggers; public : void addLogger (std::unique_ptr<Logger> logger) { loggers.push_back (std::move (logger)); } void log (const std::string& message) { for (const auto & logger : loggers) { logger->log (message); } } void logError (const std::string& message) { for (const auto & logger : loggers) { logger->logError (message); } } void logWarning (const std::string& message) { for (const auto & logger : loggers) { logger->logWarning (message); } } void logInfo (const std::string& message) { for (const auto & logger : loggers) { logger->logInfo (message); } } }; void useLogger () { LogManager logManager; logManager.addLogger (std::make_unique <ConsoleLogger>()); logManager.addLogger (std::make_unique <FileLogger>("application.log" )); logManager.logInfo ("Application started" ); logManager.logWarning ("Low memory warning" ); logManager.logError ("Failed to connect to database" ); logManager.logInfo ("Application exited" ); }
总结 多态是C++面向对象编程的核心特性之一,它通过接口与实现分离、运行时绑定、代码复用和扩展性,为构建复杂的软件系统提供了强大的工具。
关键要点 :
理解编译时多态和运行时多态的区别与应用场景 掌握虚函数、纯虚函数和抽象类的使用方法 了解虚函数表和虚指针的实现原理 合理设计抽象接口,遵循单一职责原则 平衡多态的便利性和性能开销 遵循多态的最佳实践,提高代码的可维护性和可扩展性 通过合理应用多态,开发者可以创建更加灵活、可扩展和可维护的C++代码,充分发挥面向对象编程的优势。