Note: This article is a post, which is well written and easy to understand. Put it here so that you can learn more in the future.
1. What is polymorphism?
Polymorphism is an important basis in C ++. It can be said that the out-of-the-box C ++ is not a good choice for polymorphism. However, the C ++ community has been arguing over the connotation and extension of polymorphism for a long time. There is a trend that only trees do not see the forest. What is polymorphism? To be honest, I think the name of polymorphism is not very good (or not very good ). If I name it, I will give it a name like this-"calling the 'same name function' will have a different implementation mechanism because of different contexts ". This name is longer, but it is much clearer than "polymorphism. Looking at this long definition, we can find three important parts of polymorphism. The first is "Same function name", the second is "based on context", and the third is "implementation but different ". Hey, it's easy. Let's call them three elements of polymorphism.
2. Benefits of Polymorphism
Polymorphism brings two obvious advantages: first, there is no need to remember a large number of function names, and second, it will be determined based on the context during the call. The implementation process is completed by C ++ itself. Another obvious but important benefit is that it brings object-oriented programming.
3. Implementation of polymorphism in C ++
There are three methods for implementing polymorphism in C ++. From "Easy to White" to "not easy to white. The first is function overload, the second is template function, and the third is virtual function.
4. The polymorphism implemented by function overload is described in detail.
Function Overloading is a mechanism that allows functions with different parameters to have the same name.
Specifically, we suppose there are three functions:
Void test (INT Arg) {}// function 1
Void test (char Arg) {}// function 2
Void test (INT arg1, int arg2) {}// function 3
If it is compiled in C, A Name Conflict error is returned and cannot be compiled. This is legal in C ++. However, when we call test, which of the above three functions will be called? This is determined based on the parameters you provided during the call. As follows:
Test (5); // call function 1
Test ('C'); // call function 2
Test (); // call function 3
How does C ++ achieve this? The original smart C ++ compiler quietly made some different marks on our function names during compilation based on different function parameters.. The details are as follows:
Void test (INT Arg) // It is marked as 'test has an int parameter'
Void test (char Arg) // It is marked as 'test has a char-type parameter'
Void test (INT arg1, int arg2) // It is marked as 'test. The first parameter is 'int' and the second parameter is 'int'
In this way, when we call test, C ++ can determine which test function should be used based on the parameters in the call. Oh, a smart C ++ compiler. In fact, C ++ makes the mark more intelligent than I did above. The mark above is too long. The C ++ compiler uses a much shorter mark than mine. Let's take a look at the true C ++ markup for these three functions:
Test @ yaxd @ Z
Test @ yaxh @ Z
Test @ yaxhh @ Z
Is it too short. But it is hard to understand. Fortunately, this is for the computer. It is understandable that people do not understand it.
Remember cout. We can use <to allow it to output any type of data. For example, it can be as follows:
Cout <1; // output int type
Cout <8.9; // output Double Type
Cout <'a'; // output char type
Cout <"ABC"; // output char array type
Cout <Endl; // output a function
The reason why cout can use a function name <(<is a function name) is that all these functions are function overload functions. If there is no function overload, we may use cout as follows:
Cout int <1; // output int type
Cout double <8.9; // output Double Type
Cout char <'a'; // output char type
Cout chararray <"ABC"; // output char array type
Cout function (...) <Endl; // output function
It is not difficult to create a function name for each type to be output.
However, function overloading does not overload functions with different return values. This is because people often do not specify the return value for function calls. It is not technically impossible to reload data by returning values.
5. The polymorphism implemented by using template Functions
The so-called template function (also called a function template) is like this: the function content is available, but the parameter type of the function is to be determined (note: the number of parameters is not to be determined ). For example, a (accurately speaking, a class or a group) function has two parameters, and its function is to return the response. Such functions can be implemented using template functions. As follows.
Template <typename T>
T getmax (T arg1, t arg2)
{
Return arg1> arg2 arg1: arg2; // code segment 1
}
Is this template-based polymorphism. Now, whether we call getmax (1, 2) or getmax (3.0, 5.0), we use the above function definition. It does not execute different implementations based on the context of the call. Therefore, at best, a template function is used, which is independent from polymorphism. How can we stick to the edge with polymorphism? Use templates for special purposes! Like this:
Template <>
Char * getmax (char * arg1, char * arg2)
{
Return (strcmp (arg1, arg2)> 0) arg1: arg2; // code segment 2
}
In this way, when we call getmax ("ABC", "EFG"), we will execute code segment 2 instead of code segment 1. This is polymorphism.
What's more interesting is that if we write another function:
Char getmax (char arg1, char arg2)
{
Return arg1> arg2arg1: arg2; // code segment 3
}
When we call getmax ('A', 'B'), code segment 3 is executed, not code segment 1 or code segment 2. C ++ allows function overloading for template functions, just as this template function is a common function. So we can immediately think of writing the following function for processing the number of workers in three steps:
Int getmax (INT arg1, int arg2, int arg3)
{
Return getmax (arg1, max (arg2, arg3); // code segment 4
}
We can also write as follows:
Template <typename T>
T getmax (T arg1, t arg2, t arg3)
{
Return getmax (arg1, getmax (arg2, arg3); // code segment 5
}
Now we can see the power of polymorphism combined with templates. It is much more powerful than just using function overloading.
6. Summary
The two polymorphism mentioned above has a general term in C ++: static polymorphism. They are called static polymorphism because their polymorphism is determined during compilation. That is to say, the code segments 1, 2, 3, 2, 3, 4, and 5 mentioned above are determined in the context of the call after compilation. For example, if getmax (0.1, 0.2, 0.3) is called, code segment 5 is executed. If test (5) is called, function 1 is executed. These can be determined during compilation.
Static polymorphism also has a feature: "The sum parameter is strong ".
One of the following polymorphism is that the function to be actually executed must be determined during the execution of the program. Therefore, such polymorphism is also called dynamic polymorphism in C ++.
7. The polymorphism implemented by virtual functions
7. 1. What is the virtual function?
First, let's talk about virtual functions. The so-called virtual functions are like this: there are some functions in the base class that allow their implementations in the derived class to be different from those in the base class. In C ++, the keyword virtual is used to indicate that a function is a virtual function.
In C ++, the term "Overwrite" is closely related to virtual functions. The so-called override means that the declaration of a function in a derived class is exactly the same as that of a function in the base class, including the return value, function name, number of parameters, parameter type, and Parameter order. (Note 1) There are two reasons for the close relationship between coverage and virtual functions: one is that only virtual functions that overwrite the base class are safe. The second reason is that to implement virtual function-based polymorphism, you must overwrite the virtual function of the base class in the derived class.
Next, let's talk about why there are virtual functions and analyze why the derived classes must overwrite the virtual functions of the base class in some cases. Let's look at the example of drawing a very famous image. Suppose we are programming for a graphics system. We may have the following class structure.
Figure 7-1
The shape exposes a function to draw it out. This is reasonable, and the shape should be drawn out, right? Because of inheritance, polygon and circular shapes also have their own function.
Now we will discuss how to implement the function of plotting in these three classes. In shape, just do nothing. In a polygon, you just need to connect all its vertices to the beginning and end. In the circle, draw an arc of 360 degrees based on its center and its radius.
But now the problem is: the polygon and the circle draw their own functions are inherited from the shape, and it is impossible to connect the vertex and draw the arc.
What should I do? overwrite it and overwrite the function of drawing itself in the shape. Therefore, we create a function in each polygon and circle to draw our own function in the shape. To implement overwriting, we need to modify the rendering function in the shape using virtual. In addition, if you draw your own function in the shape, we will make it a pure virtual function. A pure virtual function also plays a role in making its class an abstract class. The shape should be an abstract class, isn't it? So we soon wrote the code for these three classes as follows:
Class shape // shape
{
Public:
Virtualvoid drawself () // draw yourself
{
Cout <"I Am a drawing that cannot be painted" <Endl;
}
};
Class polygo: Public shape // Polygon
{
Public:
Void drawself () // draw yourself
{
Cout <"connect each vertex" <Endl;
}
};
Class CIRC: Public shape // circle
{
Public:
Void drawself () // draw yourself
{
Cout <"draw an Arc Based on the center and radius" <Endl;
}
};
Below, we will illustrate the dynamic polymorphism based on the above three classes. Before further explanation, let's talk about two concepts that must be mentioned: subtype and upward transformation ".
. Upward Transformation
Child types are easy to understand. For example, the polygon and circle above are child types of shapes. There is also an exact definition of the child type: If the Type X is expanded or the Type Y is implemented, then X is the child type of Y.
The upward transformation means to replace a child-type object with a parent-type object. It is like converting a Polygon into a shape. The meaning of upward transformation is so simple, but it has far-reaching significance. We need to pay special attention to three points in the upward transformation. First, the upward transformation is safe. Second, the upward transformation can be completed automatically. Third, the subtype information will be lost during the upward transformation process. These three factors play an important role in the whole dynamic polymorphism.
Assume that we have the following function:
Void outputshape (shape Arg) // specifically responsible for calling the shape to draw its own function
{
Arg. drawself ();
}
Now we can use the outputshape function as follows:
Polygon shape1;
CIRC shape2;
Outputshape (shape1 );
Outputshape (shape2 );
We can use the outputshape function in this way because the upward transformation is safe (there will be no compilation warning ), it is because the upward rotation is automatic (we didn't convert shape1 and shape2 into the shape type and then pass it to the outputshape function ). However, the output result of the above program is as follows:
I can't draw anything.
I can't draw anything.
It is clearly a polygon and a circle. It is reasonable to output the following!
Connect each vertex
Draw an Arc Based on the center and radius
The culprit of the above unreasonable output is 'loss of child type information in upward Transformation '. To get a reasonable output, you have to find a way to retrieve the lost child type information. C ++ uses a clever method to retrieve lost child types. This method uses pointers or references.
. Why use pointers or references to achieve dynamic Polymorphism
For an object, no matter how many pointers direct to it, these pointers refer to the same object. (Even if you use a void pointer to point to an object, isn't it?) The same applies to references.
How deep is this? The deep meaning here is as follows: subtype information already exists in itself, so we use a base class pointer to point out it, the subtype information is also found, and the reference is the same. C ++ uses the pointer feature. To achieve dynamic polymorphism. Note 2 now let's rewrite the outputshape function as follows:
Void outputshape (shape & Arg) // specifically calls the shape to draw its own function
{
Arg. drawself ();
}
The output of our program is:
Connect each vertex
Draw an Arc Based on the center and radius
This output is what we really want. What we really want to achieve is the essence of dynamic polymorphism.
. Why do dynamic polymorphism inherit from public?
In the code above, the circle and polygon are all inherited from the public shape. What if we change the circle inheritance to private or protected? Let's give it a try. Wow, we get a compilation error. The general meaning of this error is: "Please do not use a private method ". What's going on?
Yes. It means that the following statement is unreasonable.
All shapes can be drawn, and circles cannot be drawn.
Is this reasonable. Therefore, use public inheritance in polymorphism.
8. Summary
The idea of polymorphism came into existence before the emergence of object-oriented programming. For example, the + operator in C language. This operator can sum two int-type variables or two char-type variables or two int-type and char-type variables. This characteristic of addition operations is a typical polymorphism. So the essence of polymorphism is that the same usage is different in implementation.
9. Appendix:
Note 1: strictly speaking, the return values can be different, but such differences are limited. For more information, see the content of the change.
NOTE 2: C ++ will quietly add a pointer to the class containing virtual functions. Use this pointer to point to a table. This table contains the index of each virtual function. Use this index to find the entry address of the corresponding virtual function. For the shape example, C ++ quietly creates three tables: shape, polygon, and CIRC. They respectively record the entry address of a drawself function. In the process of running the program, C ++ will first find the table through the pointer in the class. Find the drawself entry address from this table. Then, the endpoint address is used to call the upright drawself. It is precisely because the search process is completed at runtime. Therefore, such polymorphism is called dynamic polymorphism (Runtime polymorphism)
Article Source: feino Network (www.firnow.com): http://dev.firnow.com/course/3_program/c++/cppjs/2008828/138380.html