Explain the concept of polymorphism in C++ programming

  • 2020-05-05 11:34:56
  • OfStack

Polymorphism (polymorphism) is an important feature of object-oriented programming. If a language only supports classes and not polymorphism, it cannot be called an object-oriented language, but can only be said to be object based. Ada and VB belong to this category. C++ supports polymorphism, which can be realized in C++ programming. Polymorphism allows you to design and implement a system that is easy to scale.

Polymorphism, as the name suggests, means that something has many forms. The English words polymorphism for polymorphism are derived from the Greek roots poly (" a lot ") and morph (" form "). In C ++ programming, polymorphism means that functions with different functions can be called with the same function name, so that functions with different contents can be called with the same function name. Polymorphism is typically expressed in object-oriented methods in such a way that the same message is sent to different objects, and different objects behave differently when it is received (that is, methods). That is, each object can respond to a common message in its own way. The so-called message, is called function, different behavior refers to different implementation, that is, the execution of different functions.

In fact, we have been exposed to the phenomenon of polymorphism many times, such as function overload, operator overload are polymorphic phenomenon. It was just that the term polymorphism was not used. For example, adding two values using the operator "+" sends a message that calls the operator + function. In fact, the addition operation process of integer, single precision and double precision is different from each other and is realized by different functions. Obviously, they respond to the same message with different behaviors or methods.

Many examples of polymorphism can be seen in real life. : the school principal announces a message to the society: the new school year begins on September 1. Different people respond differently: students need to get their textbooks ready for class on time; Parents have to fund tuition; Teachers should prepare their lessons; The back office has to prepare the classrooms, dormitories and canteens... Polymorphism is a condition in which each person knows what to do when they get the same message because their tasks have been defined in advance. If polymorphism is not used, it can be imagined that the principal will send separate notices to many different objects such as students, parents, teachers, and logistics departments, specifying what each type of person should do after receiving the notice. Obviously this is a very complicated and detailed work. One man does all the work. Now, with polymorphism, principals don't have to think about how different types of people perform when they publish a message. As for what all kinds of people should do after receiving the news, it is not a temporary decision, but the school's working mechanism in advance. As long as the principal keeps releasing information, all kinds of people will work in an orderly manner according to the plan.

Similarly, in C++ programming, methods for responding to messages are defined in different classes, so when you use these classes, you don't have to worry about what type they are, just publish the message. Just as the operator "" does not have to consider whether the added values are integer, single precision or double precision, the use of" + "directly, no matter which type of values can be added. This is, so to speak, an immutable way of calling objects with the same form of information to make them respond to a prior arrangement, regardless of their variety.

From the perspective of system implementation, polymorphism is divided into two types: static polymorphism and dynamic polymorphism. The polymorphism realized by function overloading and operator overloading that we have learned before belongs to the static polymorphism, and the system can decide which function is called when the program is compiled. Therefore, the static polymorphism is also called the polymorphism at compile time. Static polymorphism is achieved by overloading functions (operator overloading is essentially function overloading). Dynamic polymorphism is the dynamic determination of the object for which the operation is targeted during the program run. It is also called runtime polymorphism. Dynamic polymorphism is achieved through virtual functions (Virtual fiinction).

Here is an example of a link. On the one hand, it is an example of a comprehensive application of inheritance and operator overloading, which can further illustrate what we have learned, and on the other hand, it is a basic use case for discussing polymorphism.

I hope you will read and digest this program patiently and deeply, and make sure you understand every detail of it.

First, create an Point(point) class that contains the data members x and y(coordinate points). Based on it, an Circle(circle) class is derived, the data member r(radius) is added, the data member Cylinder(cylinder) class is derived, and the data member h(height) is added. Requires writing a program that overloads the operator "< < "And" > > "So that it can be used to output the above class objects.

This example is not difficult, but it's a long procedure. For a larger program, there should be several steps. Declaration of the derived class, step by step, step by step debugging.

1) declare the base class Point

The class can be written as part of the open base class Point as follows:


#include <iostream>
// Class declarations Point
class Point
{
public:
 Point(float x=0,float y=0); // Constructor with default parameters 
 void setPoint(float ,float); // Set the coordinate value 
 float getX( )const {return x;} // read x coordinates 
 float getY( )const {return y;} // read y coordinates 
 friend ostream & operator <<(ostream &,const Point &); // Overloaded operator" << " 
protected: // Protected member 
 float x, y;
};
// The following definitions Point class 
Point::Point(float a,float b) //Point Constructor of 
{ // right x,y Initialize the 
 x=a;
 y=b;
}
void Point::setPoint(float a,float b) // Set up the x and y The coordinate values of 
{ // for x,y Assign a new value 
 x=a;
 y=b;
}
// Overloaded operator" << "To output the coordinates of the points 
ostream & operator <<(ostream &output, const Point &p)
{
 output<<"["<<p.x<<","<<p.y<<"]"<<endl;
 return output;
}

This completes the declaration of the base Point class.

In order to improve the efficiency of the program debugging, it is advocated to debug the program step by step, do not write a long program before the unified debugging, so that there may be a lot of compilation errors at the same time, in the face of a long program, the programmer is often difficult to quickly and accurately find the error location. Be good at breaking a large program into several files, compiling them separately, or debugging them step by step, first passing through the most basic parts, then gradually expanding.

Now, to debug the base class declaration written above and check for errors, write the main function. It's actually a test program.


int main( )
{
 Point p(3.5,6.4); // To establish Point Class object p
 cout<<"x="<<p.getX( )<<",y="<<p.getY( )<<endl; // The output p The coordinate values of 
 p.setPoint(8.5,6.8); // To reset p The coordinate values of 
 cout<<"p(new):"<<p<<endl; // Use the overloading operator" << "The output p Point coordinates 
 return 0;
}

The getX and getY functions are declared as constant member functions, which allow functions to refer to data in the class, but not to modify them, to keep the data in the class safe. Data members x and y are declared protected, which can be accessed by derived classes (if declared private, derived classes cannot be accessed).

The program is compiled and passed. The result is


x=3.5,y=6.4
p(new):[8.5,6.8]

The test program checks the function of each function in the base class and the function of operator overloading to prove that the program is correct.

2) declares a derived class Circle

On top of that, write the part that declares the derived class Circle:


class Circle:public Point //circle is Point A common derived class of the 
{
public:
Circle(float x=0,float y=0,float r=0); // The constructor 
void setRadius(float ); // Set the radius value 
float getRadius( )const; // Read radius value 
float area ( )const; // Calculate the circle area 
friend ostream &operator <<(ostream &,const Circle &); // Overloaded operator" << " 
private:
float radius;
};
// Defines a constructor to initialize the center coordinates and radius of a circle 
Circle::Circle(float a,float b,float r):Point(a,b),radius(r){}
// Set the radius value 
void Circle::setRadius(float r){radius=r;}
// Read radius value 
float Circle::getRadius( )const {return radius;}
// Calculate the circle area 
float Circle::area( )const
{
 return 3.14159*radius*radius;
}
// Overloaded operator" << To output the information of the circle in a specified form 
ostream &operator <<(ostream &output,const Circle &c)
{
 output<<"Center=["<<c.x<<","<<c.y<<"],r="<<c.radius<<",area="<<c.area( )<<endl;
 return output;
}

To test the above definition of the Circle class, write the following main function:


int main( )
{
Circle c(3.5,6.4,5.2); // To establish Circle Class object c And given the center coordinates and radius 
cout<<"original circle:\\nx="<<c.getX()<<", y="<<c.getY()<<", r="<<c.getRadius( )<<", area="<<c.area( )<<endl; // Output center coordinates, radius, and area 
c.setRadius(7.5); // Set the radius value 
c.setPoint(5,5); // Sets the center of the circle x,y
cout<<"new circle:\\n"<<c; // Use the overloading operator" << Output the information of the circle object 
Point &pRef=c; //pRef is Point Class reference variables, by c Initialize the 
cout<<"pRef:"<<pRef; // The output pRef The information of 
return 0;
}

The program is compiled and passed. The result is


original circle:( Output the data of the original circle )
x=3.5, y=6.4, r=5.2, area=84.9486
new circle:( Output the modified circle data )
Center=[5,5], r=7.5, area=176.714
pRef:[5,5] ( Output the data of the center "point" of the circle )

As you can see, the operator "<" is declared once in the Point class < "Overloaded function, declaring the operator" < "again in the Circle class < ", twice overloaded operator "< < "The content is different, and at compile time the compilation system determines which operator overload function to call based on the type of output item. Line 7 of the main function says "cout<" < "Output c, calling the operator overload function declared in the Circle class.

Note line 8 of the main function:


 Point & pRef = c;

The reference variable pRef of Point class is defined and initialized with the derived Circle object c. As we've said before, derived class objects can initialize or assign references to base class objects instead of base class objects (see C++ conversion between base and derived classes for details). Now Circle is a common derived class of Point, so pRef cannot be considered an alias of c, it gets the starting address of c, it is just an alias of the base class part of c, sharing the same segment of storage as the base class part of c. So use "cout< < When pRef is printed, instead of calling the operator overload function declared in Circle, the operator overload function declared in Point is called, and the "point" information is printed instead of the "circle" information.

3) declares the derived class Cylinder

of Circle

Having previously derived the Circle class from the base class Point, the Cylinder class is now derived from Circle.


class Cylinder:public Circle// Cylinder is Circle Is a common derived class 
{
public:
 Cylinder (float x=0,float y=0,float r=0,float h=0); // The constructor 
 void setHeight(float ); // Set cylinder height 
 float getHeight( )const; // Read column height 
 loat area( )const; // Calculate circular surface area 
 float volume( )const; // Calculating cylindrical volume 
 friend ostream& operator <<(ostream&,const Cylinder&); // Overload operator <<
protected:
 float height;// Cylindrical high 
};
// Define the constructor 
Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){}
// Set cylinder height 
void Cylinder::setHeight(float h){height=h;}
// Read column height 
float Cylinder::getHeight( )const {return height;}
// Calculate circular surface area 
float Cylinder::area( )const { return 2*Circle::area( )+2*3.14159*radius*height;}
// Calculating cylindrical volume 
float Cylinder::volume()const {return Circle::area()*height;}
ostream &operator <<(ostream &output,const Cylinder& cy)
{
 output<<"Center=["<<cy.x<<","<<cy.y<<"],r="<<cy.radius<<",h="<<cy.height <<"\\narea="<<cy.area( )<<", volume="<<cy.volume( )<<endl;
 return output;
} // Overloaded operator" << " 

You can write the following main function:


int main( )
{
 Cylinder cy1(3.5,6.4,5.2,10);// define Cylinder Class object cy1
 cout<<"\\noriginal cylinder:\\nx="<<cy1.getX( )<<", y="<<cy1.getY( )<<", r="
  <<cy1.getRadius( )<<", h="<<cy1.getHeight( )<<"\\narea="<<cy1.area()
  <<",volume="<<cy1.volume()<<endl;// With the system-defined operator" << "The output cy1 The data of 
 cy1.setHeight(15);// Set cylinder height 
 cy1.setRadius(7.5);// Set the circle radius 
 cy1.setPoint(5,5);// Sets the center of the circle x,y
 cout<<"\\nnew cylinder:\\n"<<cy1;// Use the overloading operator" << "The output cy1 The data of 
 Point &pRef=cy1;//pRef is Point A reference variable for a class object 
 cout<<"\\npRef as a Point:"<<pRef;//pRef Output as a "dot" 
 Circle &cRef=cy1;//cRef is Circle A reference variable for a class object 
 cout<<"\\ncRef as a Circle:"<<cRef;//cRef Output as a "circle" 
 return 0;
}

The result is as follows :


original cylinder:( The output cy1 The initial value of the )
x=3.5, y=6.4, r=5.2, h=10 ( Center coordinates x,y . The radius of r High, h)
area=496.623, volume=849.486 ( Cylindrical surface area area And volume volume)
new cylinder: ( The output cy1 The new value )
Center=[5,5], r=7.5, h=15 ( In order to [5,5] Output center coordinates )
area=1060.29, volume=2650.72( Cylindrical surface area area And volume volume)
pRef as a Point:[5,5] (pRef Output as a "dot" )
cRef as a Circle:Center=[5,5], r=7.5, area=176.714(cRef Output as a "circle" )

Note: the area function is defined in Cylinder class, which has the same name as the area function in Circle class. According to the principle of the same name coverage we explained earlier (see the ambiguity problem of C++ multiple inheritance), cy1.area () calls the area function of Cylinder class (find the cylinder surface area), not the area function of Circle class (circle area). Note that these two area functions are not overloaded functions, they are not only the same function name, but also the same function type and number of arguments, and the two functions of the same name are not in the same class, but in the base class and derived class, respectively, and are covered by the same name. The number and type of arguments to an overloaded function must be at least one different, or the system cannot determine which function to call.

Line 9 of the main function says "cout<" < cy1 "to output cy1, the overloaded operator" < "declared in the Cylinder class is called < ", output the relevant data of cylinder cy1 in the way specified when overloaded.

The last four lines in the main function have the same meaning as when you defined the Circle class. pRef is a reference variable of Point class, which is initialized with cy1, but it is not an alias of cy1, just an alias of Point part of cy1, which is output as an Point class object, that is, it is a "point". Again, cRef is a reference variable of the Circle class and is initialized with cy1, but it is just an alias for the Circle part of the direct base class cy1. It is output as an Circle class object when cRef is output. It is a "circle", not a "cylinder". The output shows which operator function is called.

In this case, there is a static polymorphism, which is caused by operator overloading (note that the three operator functions are overloaded rather than overridden by the same name, because there is a different parameter type). As you can see, compiling the system at compile time determines which overloaded operator function should be called.


Related articles: