C++ Полиморфизм
ПолиморфизмБуквально это означает множество форм.
Когда между классами существует иерархическая структура и классы связаны через наследование, используется полиморфизм.
В C++ полиморфизм является одной из важных особенностей объектно-ориентированного программирования.
Полиморфизм в C++ позволяет использовать указатели или ссылки на базовый класс для вызова переопределённых методов производных классов, так что один и тот же интерфейс может демонстрировать различное поведение.
Полиморфизм делает код более гибким и универсальным. Программа может работать с объектами разных типов через указатели или ссылки на базовый класс, не различая явно типы объектов. Это делает код более расширяемым; например, при добавлении нового класса фигуры главную программу не нужно изменять.
Вот несколько ключевых моментов о полиморфизме:
Виртуальные функции:
- Объявление функции в базовом классе как виртуальной использует ключевое слово
virtual。 - Производный класс может переопределить эту виртуальную функцию.
- При вызове виртуальной функции то, какая версия функции будет вызвана, определяется фактическим типом объекта.
Динамическое связывание:
- Также известно как позднее связывание: конкретная реализация вызова функции определяется во время выполнения.
- Для вызова виртуальных функций требуется использовать указатель или ссылку на базовый класс; во время выполнения компилятор решает, какую функцию вызвать, основываясь на фактическом типе объекта.
Чистые виртуальные функции:
- Класс, содержащий чистые виртуальные функции, называется абстрактным классом, и его нельзя напрямую создавать как экземпляр.
- Чисто виртуальная функция не имеет тела функции; при объявлении она использует ключевое слово
= 0。 - Она заставляет производные классы предоставлять конкретные реализации.
Механизм реализации полиморфизма:
- Таблица виртуальных функций (V-Table): среда выполнения C++ использует таблицу виртуальных функций для реализации полиморфизма. Каждый класс, содержащий виртуальные функции, имеет таблицу виртуальных функций, которая хранит указатели на все виртуальные функции класса.
- Указатель на виртуальную функцию (V-Ptr): объект содержит указатель на таблицу виртуальных функций класса.
Преимущества использования полиморфизма:
- Повторное использование кодаЧерез указатели или ссылки на базовый класс можно работать с объектами различных типов производных классов, обеспечивая повторное использование кода.
- РасширяемостьПри добавлении нового производного класса код, зависящий от базового класса, не нужно изменять; достаточно лишь убедиться, что новый класс правильно переопределяет виртуальные функции.
- Ослабление связанностиПолиморфизм позволяет сделать проектирование программы более модульным и снижает связанность между классами.
Примечания:
- Полиморфизм возникает только тогда, когда виртуальная функция вызывается через указатель или ссылку на базовый класс.
- Если объект производного класса используется напрямую для вызова функции, то вызывается версия из производного класса, а не версия из базового класса.
- Полиморфизм требует информации о типах во время выполнения (RTTI), что может увеличить накладные расходы программы.
Пример 1
Давайте рассмотрим применение полиморфизма на простом примере:
Пример 1
using namespace std;
// Базовый класс Animal
class Animal {
public:
// Виртуальная функция sound, предоставляющая интерфейс для разных животных, чтобы издавать звуки
virtual void sound() const {
cout << "Животное издаёт звук" << endl;
}
// Виртуальный деструктор гарантирует, что объекты производного класса корректно уничтожаются
virtual ~Animal() {
cout << "Животное уничтожено" << endl;
}
};
// Производный класс Dog, наследующий Animal
class Dog : public Animal {
public:
// Переопределение метода sound
void sound() const override {
cout << "Собака лает" << endl;
}
~Dog() {
cout << "Собака уничтожена" << endl;
}
};
// Производный класс Cat, наследующий от Animal
class Cat : public Animal {
public:
// Переопределение метода sound
void sound() const override {
cout << "Кошка мяукает" << endl;
}
~Cat() {
cout << "Кошка уничтожена" << endl;
}
};
// Проверка полиморфизма
int main() {
Animal* animalPtr; // Указатель на базовый класс
// Создать объект Dog и указать на него с помощью указателя Animal
animalPtr = new Dog();
animalPtr->sound(); // Вызов метода sound объекта Dog
delete animalPtr; // Освобождение памяти, вызов деструкторов Dog и Animal
// Создать объект Cat и указать на него указателем Animal
animalPtr = new Cat();
animalPtr->sound(); // Вызвать метод sound класса Cat
delete animalPtr; // Освобождение памяти, вызов деструкторов Cat и Animal
return 0;
}
Вывод выполнения программы:
Dog barks Dog destroyed Animal destroyed Cat meows Cat destroyed Animal destroyed
Объяснение кода
базовый классAnimal:
AnimalКласс определяет виртуальную функциюsound(), которая является виртуальной функцией (virtual), используемой для представления поведения животных, издающих звуки.~Animal()Наличие виртуального деструктора гарантирует, что когда объект производного класса, на который указывает указатель базового класса, освобождается, деструктор производного класса вызывается правильно, предотвращая утечки памяти.
производный классDogиCat:
DogиCatВсе классы наследуют отAnimalклассы наследуются от него, и каждый реализуетsound()метод.Dogизsound()Выводит "Dog barks";Catизsound()Выводит "Cat meows". Это заставляет один и тот же метод (sound()) демонстрировать разное поведение в разных классах.
функция mainmain():
- Создайте указатель на базовый класс
animalPtr。 - Использование
new Dog()СоздайтеDogобъект, присвойте его адресanimalPtr. На этом этапе вызовanimalPtr->sound()Он выведет "Dog barks", потому чтоanimalPtrфактически указывает наDogОбъект. - освободить
Dogкогда объект, сначала вызватьDogдеструктор, затем вызватьAnimalдеструктор для. - Использование
new Cat()СоздайтеCatобъект и присвоитьanimalPtr, затем вызватьanimalPtr->sound(), выводит "Cat meows", демонстрируя полиморфное поведение.
Ключевые концепции
виртуальная функция: Используя в базовом классе
virtualключевое слово для объявления виртуальных функций, производные классы могут переопределить эту функцию, чтобы во время выполнения вызывалась правильная функция в зависимости от типа объекта.динамическое связывание: Полиморфизм C++ реализуется через динамическое связывание. Во время выполнения указатель базового класса
animalPtrбудет, исходя из фактического типа объекта, на который он указывает (DogилиCat) вызывать соответствующуюsound()метод.виртуальный деструкторВ базовом классе с полиморфным поведением деструктор должен быть объявлен как
virtual, чтобы гарантировать, что при удалении объекта производного класса вызывается деструктор производного класса, предотвращая утечки ресурсов.
Пример 2
В приведённом ниже примере мы реализуем общий базовый класс Shape и два производных класса, Rectangle и Triangle, посредством полиморфизма.
Вызов различных методов производных классов через указатель на базовый класс демонстрирует механизм динамического связывания, характерный для полиморфизма.
Пример 2
Когда приведенный выше код скомпилирован и выполнен, он выдает следующие результаты:
Rectangle Area: Rectangle class area: 70 Triangle Area: Triangle class area: 25
Анализ кода
Определение класса Shape:
Shapeявляется абстрактным базовым классом, который определяет виртуальную функциюarea()。area()является виртуальной функцией, используемой для вычисления площади, и используетvirtualключевое слово, чтобы в производных классах функцию можно было переопределить, тем самым достигая полиморфизма.widthиheightДаprotectedсвойство, доступ к которому возможен толькоShapeв классе и его производных классах.
// 基类 Shape,表示形状
class Shape {
protected:
int width, height; // 宽度和高度
public:
// 构造函数,带有默认参数
Shape(int a = 0, int b = 0) : width(a), height(b) { }
// 虚函数 area,用于计算面积
virtual int area() {
cout << "Shape class area: " << endl;
return 0;
}
};
Определение класса Rectangle:
RectangleнаследуетShapeкласс и переопределилarea()метод, вычисляет площадь прямоугольника.area()Метод используетoverrideключевое слово, указывающее, что это переопределение базового классаShapeизarea()Переопределение методов.Rectangle::area()returnwidth * height, то есть площадь прямоугольника.
// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Rectangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算矩形面积
int area() override {
cout << "Rectangle class area: " << endl;
return width * height;
}
};Определение класса Triangle:
Triangleкласс также наследует отShape, и переопределяетarea()метод, используемый для вычисления площади треугольника.Triangle::area()returnwidth * height / 2, это формула площади треугольника.
// 派生类 Triangle,表示三角形
class Triangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Triangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算三角形面积
int area() override {
cout << "Triangle class area: " << endl;
return (width * height / 2);
}
};
Полиморфное поведение в функции main:
- Определяется указатель на базовый класс
shape, этот указатель может указывать на любойShapeобъект класса или объект его производных классов. - Сначала направьте
shapeуказатель наRectangleObjectrec, затем вызватьshape->area(). Посколькуarea()является виртуальной функцией, и в этот момент она динамически привяжется кRectangle::area(), выводя площадь прямоугольника. - Затем направьте
shapeуказатель наTriangleObjecttri, вызовитеshape->area()будет динамически связываться сTriangle::area(), выводящая площадь треугольника.
// 主函数
int main() {
Shape *shape; // 基类指针
Rectangle rec(10, 7); // 矩形对象
Triangle tri(10, 5); // 三角形对象
// 将基类指针指向矩形对象,并调用 area 函数
shape = &rec;
cout << "Rectangle Area: " << shape->area() << endl;
// 将基类指针指向三角形对象,并调用 area 函数
shape = &tri;
cout << "Triangle Area: " << shape->area() << endl;
return 0;
}
Ключевые концепции
виртуальная функция: в базовом классе
Shapeвиртуальные функции определяются вarea(). Роль виртуальной функции заключается в том, чтобы позволить производным классам переопределить эту функцию и во время выполнения вызывать соответствующую функцию в зависимости от типа объекта, на который указывает указатель.динамическое связывание: потому что
area()является виртуальной функцией,shape->area()при вызове, во время выполнения, в зависимости отshapeфактического типа объекта, на который он указывает (RectangleилиTriangle) чтобы вызвать соответствующуюarea()реализацию. Этот механизм определения, какую функцию вызвать во время выполнения, называется динамическим связыванием и является основой полиморфизма.полиморфизм указателей базового класса: указатель базового класса
shapeможет указывать на любой объект, производный отShapeобъекта. Когдаshapeпри указании на объекты разных производных классов, вызовshape->area()будет давать различное поведение, что отражает характеристики полиморфизма.- может иметь реализацию в базовом классе. Обычно виртуальные функции предоставляют реализацию по умолчанию в базовом классе, но производные классы могут по желанию переопределять их.
- динамическое связывание: во время выполнения вызывать соответствующую версию функции на основе фактического типа объекта.
- Необязательное переопределениеПроизводные классы могут при желании переопределять виртуальные функции, но это не обязательно.
- должна быть объявлена в базовом классе как
= 0, указывая на отсутствие реализации, а производные классы должны её переопределить. - Абстрактный класс: Класс, содержащий чистые виртуальные функции, не может быть создан напрямую; объект может быть создан только тогда, когда все чистые виртуальные функции реализованы производным классом.
- Определение интерфейсаЧистые виртуальные функции обычно используются для определения интерфейсов, позволяя производным классам реализовывать конкретное поведение.
виртуальная функция
Виртуальная функция— это использование ключевого слова в базовом классеvirtualдля объявления функции.
Виртуальная функцияпозволяет подклассам переопределять её, так что во время выполнения переопределённая версия подкласса вызывается через указатель или ссылку на базовый класс, реализуя динамическое связывание.
Мы хотим, чтобы в любой точке программы вызываемая функция могла выбираться в соответствии с типом вызываемого объекта. Эта операция называетсядинамическое связывание, илипозднее связывание.
Возможности:
Пример
using namespace std;
class Animal {
public:
virtual void sound() { // Виртуальная функция
cout << "Животное издаёт звук" << endl;
}
};
class Dog : public Animal {
public:
void sound() override { // Переопределение виртуальной функции
cout << "Собака лает" << endl;
}
};
int main() {
Animal *animal = new Dog();
animal->sound(); // Вывод: Dog barks
delete animal;
}
В приведённом выше коде sound — это виртуальная функция класса Animal. При вызове sound() через указатель Animal* animal программа выбирает Dog::sound() в зависимости от фактического типа объекта (Dog).
Чисто виртуальная функция
Вы можете захотеть определить виртуальную функцию в базовом классе, чтобы затем переопределить её в производном классе для лучшего соответствия объекту, но вы не можете предоставить осмысленную реализацию этой виртуальной функции в базовом классе. В этом случае используется чисто виртуальная функция.
Чисто виртуальная функция — это виртуальная функция без реализации; в базовом классе она объявляется с помощью= 0, чтобы объявить её.
Чисто виртуальная функция означает, что базовый класс определяет интерфейс, но конкретная реализация возлагается на производный класс.
Чисто виртуальные функции делают базовый класс абстрактным классом, который нельзя инстанцировать.
Возможности:
Мы можем переписать виртуальную функцию area() в базовом классе следующим образом:
= 0сообщает компилятору, что функция не имеет тела; вышеуказанная виртуальная функция являетсячистой виртуальной функцией。
Сравнение виртуальных функций и чистых виртуальных функций
| Особенности | Виртуальная функция | Чистая виртуальная функция |
|---|---|---|
| определение | Используется в базовом классеvirtualОбъявление с реализацией | Используется в базовом классе= 0Объявление без реализации |
| Переопределяется в производном классе | Подклассы могут переопределить по своему выбору | Подкласс должен реализовать |
| Абстрактность | может создать экземпляр класса | делает класс абстрактным классом, экземпляр которого нельзя создать |
| Назначение | предоставляет поведение по умолчанию, позволяя подклассам переопределять его | определяет интерфейс, вынуждая производные классы реализовывать конкретные модели поведения |