Consider the classic example with graphic forms.
You can define a Shape class that contains common methods for all geometric shapes that you intend to use in your application.
This class defines a common interface for all geometric shapes.
And let's say you have a form on which you want to place geometric shapes. The form does not know in advance what geometric shapes it will have to include. It refers to geometric shapes, as some abstract objects that are endowed with some methods that a form can use to bring these shapes to the console.
Since any number of geometric shapes of different types can be added to the form, the question arises, how to store them in the form? If there is no general abstract representation of these figures, then they cannot be stored in a form, since one specific type is needed for objects, so that they can all be stored in any one container and not worry that the figures are actually different. .
This is easy to do if you inherit all the shapes from one class, as in this case, from the Shape class, and in this class you define virtual methods with which the form can work, regardless of what particular object the form deals with.
Below is a simple demo program that implements the ideas described.
There is one Form class that stores all geometric shapes (in this case, objects of the classes LeftTriangle , RightTriangle and Rectangle ) in a standard container std::vector , and which has a display method that allows you to output all forms to the console, delegating to each shape the output process yourself
// Shape.cpp: определяет точку входа для консольного приложения. // // #include "stdafx.h" #include <iostream> #include <iomanip> #include <vector> #include <memory> struct Point { int x; int y; }; class Shape { protected: Point upper_left; char pixel = '*'; public: explicit Shape(Point p = { 0, 0 }) : upper_left(p) { } virtual ~Shape() = default; char set_pixel(char pixel) { char old_pixel = this->pixel; this->pixel = pixel; return old_pixel; } virtual std::ostream & draw(std::ostream &os = std::cout) const = 0; Point move(int dx = 0, int dy = 0) { Point old_upper_left = this->upper_left; this->upper_left.x += dx; this->upper_left.y += dy; if (this->upper_left.x < 0) this->upper_left.x = 0; if (this->upper_left.y < 0) this->upper_left.y = 0; return old_upper_left; } }; class Triangle : public Shape { protected: unsigned int height; public: explicit Triangle(unsigned int height = 1) : height(height) { } }; class LeftTriangle : public Triangle { public: explicit LeftTriangle(unsigned int height = 1) : Triangle(height) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = 0; i < height; i++) { os << std::setw( upper_left.x ) << std::setfill( ' ' ) << "" << std::setw(i + 2) << std::setfill(pixel) << '\n'; } return os; } }; class RightTriangle : public Triangle { public: explicit RightTriangle( unsigned int height = 1) : Triangle( height) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = height; i != 0; i-- ) { os << std::setw(upper_left.x + i - 1 ) << std::setfill( ' ' ) << "" << std::setw( height - i + 2 ) << std::setfill( pixel ) << '\n'; } return os; } }; class Rectangle : public Shape { protected: unsigned int height; unsigned int width; public: explicit Rectangle( unsigned int height = 1, unsigned int width = 1 ) : height(height), width( width ) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = 0; i < height; i++) { os << std::setw(upper_left.x ) << std::setfill( ' ' ) << "" << std::setw( width + 1 ) << std::setfill(pixel) << '\n'; } return os; } }; class Form { public: Form() = default; void add( Shape * &&shape ) { shapes.push_back(std::unique_ptr<Shape>( shape )); } std::ostream & display(std::ostream &os = std::cout) const { const int Step = 10; int dx = 0; for (auto &p : shapes) { p->move(dx); p->draw(os) << std::endl; dx += Step; } return os; } private: std::vector<std::unique_ptr<Shape>> shapes; }; int main() { Form form; form.add(new RightTriangle(5)); form.add(new LeftTriangle(5)); form.add(new Rectangle(5, 5)); form.display(); return 0; }
Output of the program to the console
* ** *** **** ***** * ** *** **** ***** ***** ***** ***** ***** *****
Virtual methods define a common interface for all derived classes, allowing them to determine the implementation of this interface themselves. In order to be able to refer to objects of derived classes, as objects of the same type, endowed with common properties, they must be reduced to some general type. This general type can be one of the common base classes of these objects. Thus, polymorphism is achieved, that is, objects that look like objects of the same type have many forms of behavior and representations.
Of course, each derived class can additionally define its data members and methods. But in this case, this is what distinguishes them from objects of other derived classes.
For example, you can say that every woman and every man is a man. But you cannot say, for example, that every person is a woman, or every person is a man. If you consider women and men as people, then you can address them regardless of gender, sending them, as they say in the PLO, various messages. For example, if you are a bus conductor, you can ask for a ticket to be presented. For you, women and men on the bus are passengers, and they should have common properties, such as having a ticket. To do this, you must treat men and women as objects of some general type, in this case, as passengers. Nevertheless, men and women as objects of their individual class are different. For example, women can give birth, but men cannot (unless men are not women who have formally changed their gender according to documents).