1.1 overloading
Overloading falls into two broad categories: function overloading and operator overloading.
1.1.1 Function overloading and default parameters
C++ allows you to define several versions of the same function, called function overloading. Function overloading enables a function name to have multiple functions, that is, to have “multiple forms”, also known as polymorphism.
Examples of function overloading producing polymorphism:
// Use polymorphism of function overloading to design a maximum function
#include <iostream>
using namespace std;
double max(double.double); // Function prototype with 2 real parameters
int max(int.int); // Function prototype with 2 integer parameters
char max(char.char); // Function prototype for 2 character arguments
int max(int.int.int); // Function prototype with 3 integer parameters
void main( )
{
cout<<max(2.5.17.54) < <"" <<max(56.8) < <""<<max('w'.'p')<<endl;
cout<<"Max,9,4 (5) ="<<max(5.9.4) < <" max(5,4,9)= "<<max(5.4.9)<<endl;
}
// Function implementation
double max(double m1, double m2)
{
return(m1>m2)? m1:m2; }int max(int m1, int m2)
{
return(m1>m2)? m1:m2; }char max(char m1, char m2)
{
return(m1>m2)? m1:m2; }int max(int m1, int m2, int m3)
{
int t=max(m1,m2);
return max(t,m3);
}
Copy the code
C++ can call the corresponding function correctly, and the program output is as follows:
W Max (5,9,4)=9 Max (5,4,9)=9
As can be seen from the function prototype, their == difference ==: one is different parameter type, the other is different parameter number. At compile time, the compiler can call fixed function identifiers based on the source code, which are then taken over by a connector and replaced by a physical address. This is called == static linking == or == prior linking ==. Similarly, you can design a function that sums integers. However, if you want the sum of four integers, you need to write three functions using function overloading. You can write a function with default parameters.
Write a function with default arguments:
#include <iostream>
using namespace std;
int add(int m1=0.int m2=0.int m3=0.int m4=0)
{
return m1+m2+m3+m4;
}
void main(a)
{
cout<<add(1.3) < <","<<add(1.3.5) < <","<<add(1.3.5.7)<<endl;
}
Copy the code
The program output is as follows:
4,9,16
Precautions for using default parameters:
- == Cannot overload a function with fewer arguments.
For example, you can’t override the add function with three integer arguments because the compiler can’t decide whether to use three or four arguments. You can only override the add function with more than four arguments.
- The overloaded function == cannot be distinguished only by the return value of the function.
The constructor of the default parameter design class pays special attention to:
-
A class can have multiple constructors, which is also typical of function overloading.
-
You can use the field definition “: :” to explicitly indicate whether an overloaded function of a base or derived class is being called.
1.1.2 Differences between overloading and name domination
Function overloading is not the case if the member functions of the base and derived classes have the same argument list. This is called by name rule, and == uses the field definition “: :” to prevent ambiguity ==. Here is an example of using a function of the same name with the same parameters in both base and derived classes.
Example of using a function of the same name with the same arguments in a base class and a derived class:
#include <iostream>
using namespace std;
// Create the base class
class Point
{
private:
int x,y;
public:
Point(int a, int b)
{
x=a;
y=b;
}
void Show(a) // The base class Show() function
{
cout<<"x="<<x<<",y="<<y<<endl; }};// Public derived classes
class Rectangle : public Point
{
private:
int H, W;
public:
Rectangle(int.int.int.int); // Constructor prototype
void Show(a)
{
cout<<"H="<<H<<",W="<<W<<endl;
}
void Display(a)
{
Point::Show(a);// Use the base class Show() function
Rectangle::Show(a);// Use the Show() function of a derived class}}; Rectangle::Rectangle(int a, int b,int h, int w):Point(a,b) // Define the constructor
{
H=h;
W=w;
}
void main(a)
{
Point a(3.4);
Rectangle r1(3.4.5.6);
a.Show(a);// Base class objects call the base class Show() function
r1.Show(a);// A derived object calls the derived Show() function
r1.Point::Show(a);// Derived objects call the base class Show() function
r1.Display(a); }Copy the code
The program output is as follows;
x=3,y=4
H=5,W=6 x=3,y=4 x=3,y=4 H=5,W=6
The Display() function of a derived class uses the field definition “: :” to indicate whether the base or derived Show() function is called. To call the derived class’s own Show() function, Rectangle:: does not require “Rectangle::”; instead, it correctly calls its own Show() function using the rules of name dominance. “. If you do not need to display the values of H and W of derived classes separately, you can define the void() function directly as follows:
void Show(a)
{
Point::Show(a);// Display x and y values
cout<<"H="<<H<<",W="<<W<<endl;
}
Copy the code
If the same function has different parameter types or numbers, it is overloaded. You can also use a field definition to explicitly specify the function being called.
1.1.3 Operator overloading
Because any operation is performed by a function, operator overloading is essentially function overloading. To override an operator, you simply override the corresponding function.
Unlike overloaded functions:
You need to use the new keyword “operator”, which is often used with a C++ operator to form an operator function name.
For example, operator +.
This constructor allows you to override the operator function operator+() just as you would an ordinary function.
Since C++ already defines this operator function for various basic data types, you simply override operator + () for your own type.
Generally, overloading operators for user-defined types requires access to private members of that type. So there are only two ways to go:
- Override a member function of this type
- Overload to a friend of this type.
To distinguish the two cases, we call functions that are members of a class the == class operator ==, and operators that are friends of a class the == friend operator ==.
Most C++ operators can be overloaded. == cannot be overloaded. == has “. “, “::”, “*”, and “? : “.
The first three because in C++ have a specific meaning, no overloading can avoid unnecessary trouble; “? : “is because it’s not worth reloading. Also, “sizeof” and “#” are not operators and therefore cannot be overloaded.
The =, (), [], and -> operators can only be overloaded with the == class operator ==.
C++ restrictions on user defined operator overloading:
- Overloaded operators must have at least one operand of a user-defined type
- When using an operator, do not violate the original syntax rules of the operator. Do not change the priority of the operator.
- Cannot create a new operator
- Operators’. ‘, ‘::’, ‘*’ and ‘? ‘cannot be overloaded. : “.
- The =, (), [], and -> operators can only be overloaded with the == class operator ==.
Use the == friend function == to overload the operators “<<” and “>>” :
#include <iostream.h>
class test
{
private:
int i;
float f;
char ch;
public:
test(int a=0.float b=0.char c='\ 0')// constructor
{
i=a;
f=b;
ch=c;
}
friend ostream &operator << (ostream & , test); // Friend functions - overload <<
friend istream &operator >> (istream & , test &); // Friend function - overload >>
};
ostream &operator << (ostream & stream, test obj)
{
stream<<obj.i<<",";
stream<<obj.f<<",";
stream<<obj.ch<<endl;
return stream;
}
istream &operator >> (istream & t_stream, test&obj)
{
t_stream>>obj.i;
t_stream>>obj.f;
t_stream>>obj.ch;
return t_stream;
}
void main(a)
{
test A(45.8.5.'W');
operator<<(cout,A); test B,C; cout<<"Input as i f ch:";
operator >>(cin,B);
operator >>(cin,C);
operator << (cout,B);
operator << (cout,C);
}
Copy the code
The following is an example:
45,8.5, W
Input as I f CH :5 5.8a 2 3.4a // Assume Input two groups 5,5.8,A 2,3.4, A
The main function is written as a function call above to demonstrate that operators are function overloads. In general, the operator is used directly.
The following is the normal way of use:
void main(a)
{
test A(45.8.5.'W');
cout<<A;
test B,C;
cout<<"Input as i f ch:";
cin>>B>>C;
cout<<B<<C;
}
Copy the code
Obviously, the operator “<<” overloaded function takes two arguments, the first being a reference to the ostream class and the second an object of the custom type. The overload is == friend overload ==.
In addition, the return type of this function is a reference to ostream, which actually returns the function’s first argument, so that “<<” can be used continuously.
For example, for statements
cout << a << b; // Both a and B are user-defined objects
The first time, the system uses cout << a as the operator << (cout,a); Operator << (cout,b); cout (cout,b); Process, and return cout, thereby implementing the continuous use of the == operator “<<“.
Override the “++” operator with the class operator:
#include <iostream>
using namespace std;
class number
{
int num;
public:
number( int i )
{
num=i;
}
int operator ++ ( ); // Prefix: ++n. There is no pre-+ + int in the argument list
int operator+ + (int ); // suffix: n++. Int is a ++ in the argument list
void print( )
{
cout << "num="<<num << endl; }};int number :: operator ++ ( )
{
num ++;
return num;
}
int number :: operator+ + (int ) // Do not give the parameter name
{
int i=num;
num ++;
return i;
}
void main( )
{
number n(10);
int i = ++n; // i=11, n=11
cout <<"i="<<i<<endl; / / output I = 11
n.print(a);/ / output n = 11
i=n++; // i=11, n=12
cout <<"i="<< i << endl; / / output I = 11
n.print( ); / / output n = 12
}
Copy the code
Similarly, if statements 2 and 5 of the main function use function calls, then:
int i=n.operator ++ ( );
i=n.operator+ + (0);
Copy the code
So, as long as you define it correctly, you don’t have to use the function call mode anymore, you just use the operator
1.1.4 Friend operators, class operators and their parameters
If the operand required by an operator, especially the first operand, wants an implicit type conversion, the operator should be overloaded via friends. On the other hand, if an operator’s operation requires modifying the state of a class object, then a class operator should be used, which is more suitable for data encapsulation.
Example of using an object as a friend function argument to define the operator + :
#include <iostream.h>
class complex
{
private:
double real, imag;
public:
complex(double r=0.double i=0)// constructor
{
real=r;
imag=i;
}
// An example of using an object as a friend function argument to define the operator +
friend complex operator + (complex, complex);
void show(a)
{
cout<<real<<"+"<<imag<<"i"; }}; complexoperator + (complex a,complex b)
{
double r = a.real + b.real;
double i = a.imag + b.imag;
return complex(r,i);
}
void main(a)
{
complex x(5.3), y ;
y =x+7; //语句2
y =7+y; //语句3
y.show(a); }Copy the code
The program works fine because statements 2 and 3 can be interpreted as:
y =operator +(x,7);
y =operator+ (7,y);
Copy the code
All “7” can be converted into objects of complex type through the constructor to match its parameters and ensure normal operation. If the == class operator == is used, assume the form:
complex operator + (complex a)
{
double r = a.real + real;
double i = a.imag + imag;
return complex(r,i);
}
Copy the code
Because “y =7+y;” Equivalent to y=7.operator+(y); So the system can’t interpret what this means.
It follows that,
If an object is an argument to an overloaded operator function, you can use the constructor to convert a constant to an object of that type.
If you use references as arguments, these constants cannot be used as object names, and the compilation system will report an error.
If both the above friend and class operators take references as arguments, then “y =x+7;” And “y = 7 + y;” None of them will compile.
When using them, one must distinguish between the occasion and the method of use. Operator overloading is used extensively in MFC, including the conversion operator (which has no return value). Typical examples are the CPoint class and CString class. The CdumpContext and CArchive classes define a large number of overloaded versions of the “>>” and “<<” operators.
1.2 template
There are two types of templates: function templates and class templates.
1.2.1 Function templates and their explicit calling rules
In the program design, the actual type of the corresponding data is not given, but in the actual compilation, the compiler uses the actual type to give instantiation, so that it meets the needs. Thus, the compiler can be made into a function template under the guidance of the program design aid tool to produce code to meet the requirements.
The general way to declare a function template is as follows:
template< function template argument > Return type function name {/ / the function body};Copy the code
Specifies that the template begins with the template keyword and a parameter list.
You only need to specify the identifier of a type parameter in Angle brackets,
For example,
Define a maximum function:
template <class T>
T max(T m1, T m2)
{
return(m1>m2)? m1:m2; }Copy the code
Class means “user-defined or inherent type”.
The letter T identifies this template as having a parameter type.
When Max (2,5) is used in a program, the compiler can infer that T is an int and produce the specific template function using the following version:
int max(int m1, int m2)
{
return(m1>m2)? m1:m2; }Copy the code
Max (2.5,8.8) uses the following version:
double max(double m1, doublem2)
{
return(m1>m2)? m1:m2; }Copy the code
It follows that,
When a function template is called, the type of the function parameter determines which version of the template is used.
In other words,
The template parameters are inferred from the function,
This method of use is called == default ==.
Function template name (parameter list)
We can also use Max (2,5) to specify that the type is int,
This is called the == explicit parameter comparison criterion ==.
That is: function template name < template parameters >(parameter list)
The explicit comparison criteria given in each call can also be annoying. Explicit rules can be used in special cases, and the following defaults are generally preferred.
For a default call, the ability to infer template arguments from function arguments is one of the most critical.
The compiler can infer type and untype arguments from a call, saving explicit calls.
The condition is that a set of template parameters can be uniquely identified by the parameter list of the called function.
In addition,
C++ also defines a typename keyword that is only used in templates. One of its uses is to replace the class keyword in the template argument list.
1.2.2 Specialization of template functions and template overloading
1.2.2.1 Specialization of template functions
While the default is to define a template and allow users to use any template argument (or combination of template arguments) they can think of, some users prefer to implement it differently.
For example, the template function defined:
Max:template <typename T> // Declare the template
T max(T m1, T m2) // Find the maximum value of two numbers
{
return(m1>m2)? m1:m2; }Copy the code
It can handle strings, but users want a different approach.
The user’s scheme is:
If the template argument is not a pointer, use the template. If it is a pointer, use the following method:
char *max(char *a, char *b)
{
return (strcmp(a,b)>=0? a:b); }Copy the code
Since normal functions take precedence over template functions, the following statement is executed
cout<<max("ABC"."ABD") < <", "< < Max (' W ', 'T') < <" ";
Copy the code
, the first string argument calls a normal function, and the second single-character argument calls a template function.
However,
To form a complete template system for ease of management, and to ensure that no useless code is generated when no calls are made, it is desirable to still use template form.
This can be handled by providing several different definitions and allowing the compiler to choose among them based on the template parameters provided at the point of use.
These interchangeable definitions of a template are called user-defined specialization, or simply == User specialization ==.
The prefix “template <>” indicates that this is a specialization and is described without template parameters. It can be written as:
template <>char *max<char* > (char *a, char *b)
{
return (strcmp(a,b)>=0? a:b); }Copy the code
The <char*> following the function name indicates that this specialization should be used if the template argument is char*. Because the template argument can be inferred from the function’s actual argument list, there is no need to describe it explicitly. That is, it can be simplified as:
template <>char *max<>(char *a, char *b)
{
return (strcmp(a,b)>=0? a:b); }Copy the code
Given the template <> prefix, the second <> is also redundant and can simply be written as follows:
template <>char *max(char *a, char *b)
{
return (strcmp(a,b)>=0? a:b); }Copy the code
1.2.2.2 Template Overloading
The C++ template mechanism is also overloaded.
Templates provide a syntax that looks a lot like polymorphism, and when details are provided, templates can generate template functions.
Because the choice of which function to call is implemented at compile time, it is static wiring.
The following extends the scope of the defined template Max further with overloading.
Examples of specialization and overloading:
#include <iostream>
using namespace std;
template <typename T> // Declare the template
T max(T m1, T m2) // Find the maximum value of two numbers
{
return(m1>m2)? m1:m2; }template <typename T> // To declare a function template, we need to override template
T max(T a, T b, T c) / / overloaded
{
return max(max(a,b),c);
}
template <class T> // To declare a function template, we need to override template
T max(T a[], int n) // Overload the array to find the maximum value
{
T maxnum=a[0];
for(int i=0; i<n; i++)if (maxnum<a[i])
maxnum=a[i];
return maxnum;
}
template <> / / specialization
char *max(char *a, char *b) // Use Pointers
{
return (strcmp(a,b)>=0? a:b); }int max(int m1, double m2) // A normal function
{
int m3=(int)m2; return(m1>m3)? m1:m3; }void main( )
{
cout<<max("ABC"."ABD") < <""; / / 1
cout<<max("ABC"."ABD"."ABE") < <""; / / 2
cout<<max('W'.'T'.'K') < <""; / / 3
cout<<max(2.0.5..8.9) < <""; / / 4
cout<<max(2.6.7) < <""; / / 5
double d[]={8.2.2.2.3.2.5.2.7.2.9.2.15.6.4.5.1.1.2.5}; / / define
// Real array d
int a[]={- 5.4 -.- 3.2 -.- 1.- 11.9 -.- 8 -.7 -.- 6}; // Define an integer array a
char c[]="acdbfgweab"; // Define the string array c
cout<<"intMax="<<max(a,10) < <"DoubleMax =" < < Max (d, 10) < <" charMax="<<max(c,10)<<endl;
}
Copy the code
The output result of the program is:
ABD ABE W 8.9 6
IntMax doubleMax = 15.6 charMax = = – 1 w
Note the difference between statements 2 and 3: They perform the same overloading, but when overloaded function calls are made, the former uses the specialization (pointer arguments) template and the latter uses the definition (select single-character arguments) template. Statement 5 does not call the template and uses normal functions.
1.2.3 class template
If you think of a class as a framework that contains certain data types, think of the different operations that support that type as separating the data type from the class and allowing a single class to handle the common data type T.
In fact, this type is not a class, but merely a description of the class, often referred to as a class template.
At compile time, the compiler associates the class template with a specific data type to produce a real class.
It follows that,
Using class templates for programming, just like cooking food, as long as the purchase of ingredients, can make different flavors of dishes.
The general way to declare a class template is as follows:
template< class template parameters >classThe name of the class {
/ / the class body};Copy the code
Class templates are also called parameterized types.
When you initialize a class template and pass it the specific data type, the template class is created.
When using a template class, the compiler automatically generates all the members (data members and member functions) that deal with a specific data type.
Whenever a template is assigned an actual data type, a new class is created and the template parameters are replaced with a specific type.
The general format for defining objects is as follows:
Class name < template instantiation parameter type > object name (constructor argument list); Class name < template instantiation parameter type > object name;// Default or no argument constructor
Copy the code
The template instantiation parameter types include data types and values. The compiler cannot infer template instantiation parameter types from the constructor list, so it must explicitly give their parameter types.
When defining member functions outside the class body, you must override the template function declaration with template. The general format is as follows:
template< template parameter > Return type class name < template type parameter >:: member function name (function parameter list) {/ / the function body
}
Copy the code
< template type parameter > is a type parameter with a class(or typename) name in the “< >” of template, that is, <T1,T2… Tn> type parameter list. Constructors and destructors have no return types.
Class template program to demonstrate the sum of four numbers:
#include <iostream>
using namespace std;
template <class T.int size=4> // Can pass integer parameter values in the program
class Sum
{
T m[size]; // Data member
public:
Sum(T a, T b, T c, T d ) // constructor
{
m[0]=a; m[1]=b; m[2]=c; m[3]=d;
}
T S(a) // Sum member functions
{
return m[0]+m[1]+m[2]+m[3]; }};void main(a)
{
Sum<int.4>num1(- 23.5.8.2 -); // Integer summation
Sum<float.4>f1(3.5 f.8.5 f.8.8 f.9.7 f); // Single precision summation
// Use f to explicitly specify type float
Sum<double.4>d1(355.4.253.8.456.7.67.8);
Sum<char.4>c1(" W ",2 -.- 1.- 1); // Character subtraction is equivalent to
//'W'-4, result is S
cout<<num1.S() < <","<<f1.S() < <", "< < d1. S () < <"."<<c1.S()<<endl;
}
Copy the code
The output is:
– 12, 13.5, 998.1, S
A member function of a template class cannot be declared virtual, and its base and derived classes can be template (or non-template) classes.
We’ve already discussed the case of all non-template classes,
In the same way,
Class templates can also be inherited, as can inherited methods.
Before declaring template inheritance, you must redeclare the template.
Generally, the following processing methods can be adopted:
- A non-template class that provides a common implementation for a set of templates;
- Inheriting an instance of a class template from a non-template class;
- Derive a class template from a class template;
- The template class inherits the base class given by the template parameter.
The last case is illustrated below.
You can design a template class, but its derived class is not determined, but is determined at instantiation time, that is, the base class is determined by the template parameters.
Here is a simple form:
template <typename T>
class C:public T
{
public:
C(int a):T(a){}
};
Copy the code
Class C inherits the base class identified by T. C relates to the base class through constructors, as many as necessary. Here is an example of passing an integer data, then
C(base)b(85);
Copy the code
Is to take an ordinary (non-template) class base as the base class, while
C< Base<int> >y1(125.188);
Copy the code
Use the template class as the base class. This unknown base class can also be a derived class.
Design the non-template class A1 to derive from the non-template class A11. Design the class template B1 from which to derive the class template B11. Design the template class C so that it publicly inherits an unknown base class. For simplicity, use integer data directly.
#include <iostream>
using namespace std;
class A1
{
int x;
public:
A1(int c=8) :x(c)
{
cout<<"call A1 "<<x<<endl; }};class A11:public A1
{
int x1;
public:
A11(int a,int b):A1(a),x1(b)
{
cout<<"call A11 "<<x1<<endl; }};template <typename T>
class B1
{
T x;
public:
B1(T a=0) :x(a)
{
cout<<"call B1 "<<x<<endl; }};template <typename T>
class B11:public B1<T>
{
T x1;
public:
B11(T a,T b):B1<T>(a),x1(b)
{
cout<<"call A11 "<<x1<<endl; }};template <typename T>
class C:public T
{
public:
C(int a):T(a)
{
cout<<"call C "<<endl;
}
C(int a, int b):T(a,b)
{
cout<<"call C "<<endl; }};void main(a)
{
C<A1>x(90);
C<A11>x1(25.95);
C< B1<int> >y(189); // There must be at least one space between the two > signs on the right
C< B11<int> >y1(125.188);
}
Copy the code
The program output is as follows:
call A1 90
call C call A1 25 call A11 95 call C call B1 189 call C call B1 125 call A11 188 call C
Specialization of a class template
Class templates cannot be overloaded or use virtual member functions, but friends can be used.
It can also be specialized like a function template. Specialization also uses the prefix templete <>.
This uses a stack class template as an example to illustrate the specialization and methods used. When creating a template class of type int or double, use the class template Stack to instantiate it. When using a string Stack, use the specialization of Stack. The following does not involve the working principle, only from the programming and use of the method of demonstration.
#include <iostream>
using namespace std;
template <class T> // Class template
class Stack
{
int counter;
int max;
T *num;
public:
Stack(int a):counter(0),max(a),num(new T[a])
{
}
bool isEmpty(a)const
{// Check whether the stack is empty
return counter==0;
}
bool isFull(a)const
{// Check whether the stack is full
return counter==max;
}
int count(a)const
{// Return the number of stack types
return counter;
}
bool push(const T&data)
{ // Push data onto the stack
if(isFull())
return false;
num[counter++]=data;
return true;
}
bool pop(T&data)
{ // Pop data off the stack and store it to data
if(isEmpty())
return false;
data=num[--counter];
return true;
}
const T&top(a)const
{// Take the data from the top of the stack without leaving the stack
return num[counter- 1];
}
~Stack()
{
delete[]num; }};/ / specialization
template <> // Specialization prefix
class Stack<char* > {int counter;
int max;
char**num;
public:
Stack(int a):counter(0),max(a),num(new char*[a])
{
}
bool isEmpty(a)const
{
return counter==0;
}
bool isFull(a)const
{
return counter==max;
}
int count(a)const
{
return counter;
}
bool push(const char*data)
{
if(isFull())
return false;
num[counter]=new char[strlen(data)+1];
strcpy(num[counter++],data);
return true;
}
bool pop(char data[])
{
if(isEmpty())
return false;
strcpy(data, num[--counter]);
delete []num[counter];
return true;
}
const char* &top(a)const
{
return num[counter- 1];
}
~Stack()
{
while(counter) delete[]num[--counter];
delete[]num; }};void main(a)
{
Stack<int>st(8); // Integer stack st
int i=0;
while(! st.isFull())
{ // create a stack
st.push(10+i++); // Push data onto the stack
cout<<st.top() < <""; // Display top of stack data
}
cout<<endl;int data;
while(! st.isEmpty())
{ // Unstack
st.pop(data); // Pops the stack
cout<<data<<""; // Display stack data
}
cout<<endl;
Stack<double>st1(8); // Create a double stack st1
i=0;
while(! st1.isFull())
{
st1.push(0.5+i++);
cout<<st1.top() < <"";
}
cout<<endl;
double data1;
while(! st1.isEmpty())
{
st1.pop(data1);
cout<<data1<<"";
}
cout<<endl;char*str[]={"1st"."2nd"."3rd"."4th"."5th"."6th"."7th"."8th"};
Stack<char* >st2(8); // Create a string stack st2
i=0;
while(st2.push(str[i++]))
cout<<st2.top() < <"";
cout<<endl;
char strdata[8];
while(st2.pop(strdata))
cout<<strdata<<"";
cout<<endl;
}
Copy the code
The running results of the program are as follows:
10, 11, 12, 13, 14, 15, 16, 17
17 16 15 14 13 12 11 10 0.5 1.5 2.5 3.5 4.5 5.5 7.5 7.5 5.5 4.5 3.5 2.5 1.5 0.5 1st 2nd 3rd 4th 5th 7th 8th 8th 7th 6th 5th 4th 3rd 2nd 1st
Templates are widely used in MFC, such as Document Template, which is mainly used to build and manage Document files, view and frame image. CArray is an array collection template from the CObiect class.
1.3 Virtual functions and polymorphism
Polymorphism, also known as late binding or dynamic binding, is often implemented by virtualfunctions.
The polymorphism supported by dynamic linking is called runtime polymorphism and is supported by virtual functions.
Virtual functions are similar to overloaded functions, but have a different implementation strategy than overloaded functions, namely, calls to virtual functions use dynamic concatenation.
1.3.1 Compatibility of assignment and rule of name domination in static linking
Assume that the Point and Circle classes in the following example each have an area function that performs a different function.
Class objects correspond to functions called, and the call relationship is determined at compile time, resulting in compile-time polymorphism.
Analyze the output of the following program.
#include <iostream>
using namespace std;
const double PI=3.14159;
class Point
{
private:
double x,y;
public:
Point(double i, double j)
{
x=i;
y=j;
}
double area( )
{
return 0; }};class Circle : public Point
{
private:
double radius;
public:
Circle(double a, double b,double r):Point(a,b)
{
radius=r;
}
double area( )
{
returnPI*radius*radius; }};void main(a)
{
Point a(1.5.6.7);
Circle c(1.5.6.7.2.5);
cout<<"area of Point is "<<a.area()<<endl; / / (1)
cout<<"area of Circle is "<<c.area()<<endl; / / (2)
Point *p=&c; / / (3)
cout<<"area of Circle is "<<p->area()<<endl; / / (4)
Point &rc=c; / / (5)
cout<<"area of Circle is "<<rc.area()<<endl; / / (6)
}
Copy the code
For ease of understanding, corresponding numbers are given in the comments. According to the numbering, it is explained as follows:
The compiler interprets (1) as:
The explicit a.rea () expression explicitly tells the compiler that it calls area, a member function of object A, and outputs 0.
In the same way,
For (2), the explicit c.rea () expression makes it clear that area, a member function of object C, is called, with the output 19.6349.
The rule of name domination dictates that they call their respective functions of the same name, area.
The essence of the question in (3) and (4) is: if both base and derived classes define “member functions of the same name”, when a member function is called through an object pointer, does it determine the base class type of the pointer or the type to which the pointer actually refers?
In other words,
Should the expression “p->area()” call Point::area() or Circle::area()?
According to the assignment compatibility rules in Chapter 6, the area function of the base class should be called with an output of 0.
The same is true for (5) and (6). The output is 0.
Next, from the principle of memory allocation, in-depth discussion of assignment compatibility law.
Figure 1.1 is a schematic diagram of the memory allocation relationship between Point object A and Circle object C.Thus, the memory address space of an object contains only data members and does not store information about member functions. The address translation of these member functions is independent of the memory address of the object. The compiler interprets the address of a member function based only on the data type and determines the validity of the call.
If we declare two Pointers as follows:
Point * pPiont;
Circle* pCircle;
Copy the code
Figure 1.2 is the UML representation of the base class Point and the derived class Circle. Only the function of the same name is given in the class diagram, and the relationship between the declared class pointer and the class is given on the right side of them.
It follows that,
A declared pointer to a base class can only point to a base class, and a derived class pointer can only point to a derived class.
Their primitive type determines that they can only call their respective functions of the same name, area.
A pointer to a derived class calls a function of the base class only if the derived class has no function of the same name, but this is no longer a given condition.
For the following code segment in the program:
Point *p=&c; / / (3)
cout<<"area of Circle is "<<p->area()<<endl; / / (4)
Copy the code
Although p is initialized with c’s address, it is futile.
The primitive type of p is Point, and Pointers to Point can only be called to the area function of the base class of object C.
so
The output of PI (4) is 0.
References are the same as Pointers, so (6) also prints 0.
This is entirely consistent with the assignment compatibility rule. The compiler compiles member functions according to the data type, which is predetermined and therefore determined by static concatenation.
1.3.2 Polymorphism of dynamic linking
If you let the compiler link dynamically, you are compiling “Point*p=&c;”. Statement, it is checked only according to compatibility rules, that is, the address of a derived object can be assigned to a pointer to the base class.
Which function to call “p->area()” will be decided when the program runs inside.
Ultimately, you want the program to give the following output:
area of Point is 0
area of Circle is 19.6349
area of Circle is 19.6349
area of Circle is 19.6349
Copy the code
And to do that,
The pointer p to Point points to the address of the derived function area.
Clearly, it cannot be done at present.
These two functions must be given a new identifier to distinguish them from the member functions introduced so far
Assume that the area function of the Point class is declared using the keyword virtual. This function is called virtual. Here is the definition done using inline functions:
virtual double area( )
{
return 0.0;
}
Copy the code
When the compiling system compiles a class that contains virtual functions, it creates a table of virtual functions for it, with each element in the table pointing to the address of a virtual function.
In addition,
The compiler also adds a data member to the class, which is a pointer to the virtual table, usually called VPTR.
Point has only one virtual function, so there’s only one entry in the virtual list.
Figure 1.3 shows its object schematic.
If Circle does not override the area function, then the address of the element in the virtual table of the derived class is the address of the area function of Point.
Now rewrite it as follows:
virtual double area( )
{
return PI*radius*radius;
}
Copy the code
In this case, the compiler also points elements in the derived virtual list to Circle::area(), which points to the address of the derived area function.
Figure 1.3 illustrates the object address assignment diagram for object C of Circle and object A of PointIt follows that,
The address translation of the virtual function depends on the memory address of the object. **
The compiler creates an entry address for an object containing a virtual function class. This address is used to store the pointer to the virtual function table, VPTR. The function Pointers are then filled in in the order in which the virtual functions of the class are declared.
When a virtual function is called, the virtual function table is found through VPTR, and then the real address of the virtual function is found.
Derived classes can inherit the virtual function list of the base class, and as long as member functions with the same name (and arguments) as the base class, they automatically become virtual, whether or not they are declared by Virtua.
If the derived class does not overwrite the virtual function that inherits from the base class, the function pointer calls the virtual function of the base class.
If the derived class overwrites the base class’s virtual function, the compiler re-addresses the derived class’s virtual function, and the function pointer calls the overwritten virtual function.
As shown in Figure 10.3, the function of the Pie class Circle refers to the needle called
Circle::area(a)Copy the code
The rule for calling virtual functions is that, based on the current object, the virtual member function of the object itself is called first.
This is a bit like the name-governing rule, but virtual functions are dynamically bound, “indirectly” calling the actual function at execution time. Obviously, the program runs to the statement
p->area(a);Copy the code
, p can be determined to refer to the object of the pie class Circle, should call Circle::area() function.
1.3.3 Definition of virtual Functions
Functions that are assumed to perform a function are called virtual functions.
Virtual functions are the basis of polymorphism.
Once a base class defines a virtual function, a function of the same name in a derived class of that base class automatically becomes a virtual function.
A virtual function can only be a member function in a class, but not a static member. The keyword virtual is used in the declaration of the function in a class.
Such as:
class A
{
public:
virtual void fun( ); // Declare virtual functions
};
void A::fun( ) // Define virtual functions
{
/ /...
}
Copy the code
When a member function of the same name is defined in a derived class, it becomes a virtual function whether or not virtual is used for the member function, as long as the number of arguments and corresponding types and its return type are exactly the same as those of the virtual function of the same name in the base class (for example, the void Area (void) function).
In the example in the previous section, the base Point class declares the member function area as “virtual void area(void);” , the area function in the derived class Circle automatically becomes a virtual function.
1.3.4 Conditions for virtual functions to achieve polymorphism
The keyword == virtual== instructs the C++ compiler to dynamically wire calls to virtual functions.
This polymorphism is determined dynamically at this point in the program’s run and is called runtime polymorphism.
However,
Using virtual functions does not necessarily produce polymorphism, nor does it necessarily use dynamic linking.
For example,
By qualifying a virtual function with a member name in the call, you can force C++ calls to the function to use static binding.
The preconditions for this polymorphism are as follows: (1) The inheritance relationship between classes meets the assignment compatibility rule; (2) Rewrite the virtual function of the same name; (3) Use Pointers (or references) according to assignment compatibility rules.
Meeting the first two requirements does not necessarily result in dynamic linking. Article 3 is required to ensure dynamic linking.
There are two further cases of article 3.
The first is the assignment compatibility definition, already demonstrated, of using base class Pointers (or references) to access virtual functions.
The second way is to take a pointer (or reference) as a function argument.
The following example is a complete program that designs an external function display to achieve polymorphism through Pointers (or references).
Use the pointer and reference display functions respectively. Analyze the output of the following program:
#include <iostream>
using namespace std;
const double PI=3.14159;
class Point
{
private:
double x,y;
public:
Point(double i, double j)
{
x=i; y=j;
}
virtual double area( )
{
return 0; }};class Circle : public Point
{
private:
double radius;
public:
Circle(double a, double b,double r):Point(a,b)
{
radius=r;
}
double area( )
{
returnPI*radius*radius; }};void display(Point *p)
{
cout<<p->area()<<endl;
}
void display(Point&a)
{
cout<<a.area()<<endl;
}
void main(a)
{
Point a(1.5.6.7);
Circle c(1.5.6.7.2.5);
Point *p=&c;
Point &rc=c;
display(a);
display(p);
display(rc);
}
Copy the code
The program output is as follows:
19.6349
19.6349
Because dynamic programming is carried out at runtime, compared with static programming, its running efficiency is relatively low, but it can enable programmers to carry on a high degree of abstraction to the program, design a program with good scalability.
1.3.5 Further explore the difference between virtual function and real function
Assume that both base and derived classes have only one public data member, where class A has two virtual functions vfunc1 and vfunc2 and two real functions func1 and func2.
Class A publicly derives from class B, which overwrites the functions vfunc1 and func1. It in turn serves as the base of class C, which publicly derives from C.
Class C also overwrites the vfunc1 and func1 functions.
FIG. 1.4 shows the diagram of the relationship between VPTR and Vtable established by three classes and the difference between real and virtual functions.
The VPTR is first assigned an address, and the number of bytes it takes determines the length of the longest data member in the object.
Because the data members of all three classes are integers, VC allocates 4 bytes to VPTR. If there is a double, eight bytes are allocated.
Example 1.13 is a program that demonstrates this relationship.
As you can see from Figure 1.4, the starting address of the object is VPTR.
It points to vtable, which creates a pointer function for each virtual function, and if only the virtual functions of the base class are inherited, they call the virtual functions of the base class. This is the case described in the (*vfunc2)() item of THE Vtable of B and C.
If the derived class overwrites the virtual function of the base class, it calls its own virtual function, which is described by the (*vfunc1)() item in the Vtable of B and C.
Real functions are not called by address, they are represented by shaded boxes, and they are determined by the rule governing the name of the object.
[Example 1.13] is the program implementation. The process of calling real and virtual functions.
#include <iostream>
using namespace std;
class A
{
public:
int m_A;
A(int a)
{
m_A=a;
}
void func1(a)
{
cout<<"A::func1( )"<<endl;
}
void func2(a)
{
cout<<"A::func2( )"<<endl;
}
virtual void vfunc1(a)
{
cout<<"A::vfunc1( )"<<endl;
}
virtual void vfunc2(a)
{
cout<<"A::vfunc2( )"<<endl; }};class B:public A
{
public:
int m_B;
B(int a, int b):A(a),m_B(b)
{
}
void func1(a)
{
cout<<"B::func1( )"<<endl;
}
void vfunc1(a)
{
cout<<"B::vfunc1( )"<<endl; }};class C:public B
{
public:
int m_C;
C(int a, int b, int c):B(a,b),m_C(c)
{
}
void func1(a)
{
cout<<"C::func1( )"<<endl;
}
void vfunc1(a)
{
cout<<"C::vfunc1( )"<<endl; }};void main(a)
{
// The length of the output class in bytes
cout<<sizeof(A)<<","<<sizeof(B)<<"."<<sizeof(C)<<endl;
A a(11);
B b(21.22);
C c(31.32.33);
// Output the first address of the class and the address of the data member. Verify the first address is the VPTR address
cout<<&a<<","<<&(a.m_A)<<endl;
cout<<&b<<","<<&b.m_A<<","<<&b.m_B<<endl;
cout<<&c<<","<<&c.m_A<<","<<&c.m_B<<", "< < & c. _C < < endl; // Use base pointer A* pa=&a; //pa refers to base class A pa->vfunc1(); // call A::vfunc1() pa->vfunc2(); // call A::vfunc2() pa->func1(); // call A::func1() pa->func2(); // call A::vfunc2() cout<
vfunc1(); // call B::vfunc1() pa->vfunc2(); // call A::vfunc2() pa->func1(); Func1 () pa->func2(); func1() pa->func2(); Func2 () cout<
vfunc1(); // call C::vfunc1() pa->vfunc2(); // call A::vfunc2() pa->func1(); Func1 () pa->func2(); func1() pa->func2(); Func1 () cout<
vfunc1(); // call B::vfunc1() pb->vfunc2(); // call A::vfunc2() pb->func1(); Func1 () pb->func2(); Func2 () cout<
vfunc1(); // call C::vfunc1() pb->vfunc2(); // call A::vfunc2() pb->func1(); Func1 () pb->func2(); Func2 () cout<
vfunc1(); // call C::vfunc1() PC ->vfunc2(); // call A::vfunc2() PC ->func1(); Func1 () PC ->func2(); // static concatenation, only A::func2()}
;>
;>
;>
;>
;>Copy the code
Object A has one integer and should allocate 4 bytes, VPTR also 4 bytes for a total of 8 bytes. Objects B and C are incremented by an integer data member, and the memory allocation is incremented by 4 bytes. The following output is displayed:
8,12.16
Ff7c / / VPTR 0012 ff78, 0012, 0012 ff6c m_A, 0012 ff70, 0012 ff74 / / VPTR, m_A and m_B ff5c 0012, 0012 ff60, 0012 ff64, 0012 ff68 / / VPTR, m_A, // m_B, m_C // A* pa=&a; A::vfunc1() A::vfunc2() A::func1() A::func2() // pa=&b; B::vfunc1() A::vfunc2() A::func1() A::func2()//pa=&c; C::vfunc1() A::vfunc2() A::func1() A::func2() // B* pb=&b; B::vfunc1() A::vfunc2() B::func1() A::func2() // pb=&b; C::vfunc1() A::vfunc2() B::func1() A::func2() //C* pc=&c; C::vfunc1() A::vfunc2() C::func1() A::func2()
1.3.6 Pure Virtual Functions and Abstract Classes
In many cases, when a virtual function cannot be meaningfully defined in the base class, it can be specified as pure virtual. Its definition is left to derived classes.
The general form of pure virtual function is:
classThe name of the class {
virtualFunction type Function name (argument list) =0;
};
Copy the code
Point has no area, which can be expressed as:
virtual double area( )=0;
Copy the code
A class can represent more than one pure virtual function. Classes that contain pure virtual functions are called abstract classes.
An abstract class can only be used as a base class to derive new classes, and cannot account for the objects of the abstract class.
For example, declare the area() function of the Point class as a pure virtual function, then
Point a(1.5.6.7); // This is an error.
Copy the code
But a pointer (or reference) to an abstract class object,
Such as:
Point *pa;
Copy the code
A class derived from an abstract class must either provide the implementation code for a pure virtual function, or specify it as a pure virtual function in the derived class, or the compiler will give an error message.
Illustrates the
A derived class of a pure virtual function is still abstract.
A derived class is no longer abstract if it gives implementations of all pure virtual functions of the base class.
This feature of abstract classes ensures that every class entering the class hierarchy has (provides) the behavior required by pure virtual functions, which ensures that the software built around this class hierarchy can run properly, and prevents the system from being affected by accidental errors by users of this class hierarchy.
Abstract classes contain at least one virtual function, and at least one virtual function is pure virtual to distinguish it from empty virtual functions.
Here are two different ways to express it:
virtual void area( )=0; // A pure virtual function
virtual void area( ) {}// Empty virtual function
Copy the code
A pure virtual function can be called from inside a member function, but calling a pure virtual function from inside a constructor or destructor causes the program to run incorrectly because there is no code defined for the pure virtual function.
Write a program to calculate the total area of squares, rectangles, right triangles and circles.
class shape
{
public:
virtual double area( )=0; // A pure virtual function
};
class square : public shape
{
protected:
double H;
public:
square(double i)
{
H=i;
}
double area( )
{
returnH * H; }};class circle : public square
{
public:
circle(double r) : square(r)
{
}
double area( )
{
return H * H * 3.14159; }};class triangle : public square
{
protected:
double W;
public:
triangle(double h, double w):square(h)
{
W=w;
}
double area( )
{
return H * W * 0.5; }};class rectangle : public triangle
{
public:
rectangle(double h, double w) : triangle( h, w )
{
}
double area( )
{
returnH * W; }};double total(shape *s[],int n)
{
double sum=0.0;
for(int i=0; i<n; i++)
sum+=s[i]->area(a);return sum;
}
#include <iostream>
using namespace std;
void main( )
{
shape *s[5];
s[0] =new square(4);
s[1] =new triangle(3.6);
s[2] =new rectangle(3.6);
s[3] =new square(6);
s[4] =new circle(10);
for(int i=0; i<5; i++) cout<<"s["<<i<<"] ="<<s[i]->area()<<endl;
double sum=total(s,5);
cout<<"The total area is:"<<sum<<endl;
}
Copy the code
The program output is as follows:
s[0]=16
S [1]=9 s[2]=18 s[3]=36 s[4]=314.159 The total area is:393.159
The virtual area function in Shape only serves to provide a consistent interface to derived classes, where the redefined area is used to determine how to calculate the area.
Since this cannot be determined in shape, it is declared a pure virtual function.
It follows that,
Assignment compatibility rules allow people to treat squares, triangles, circles, etc., as shapes, and polymorphism ensures that the function total does not care about the area of the specific shape being calculated when it sums the areas of various shapes.
When needed, the function total can obtain the area of an object of these shapes. The member function area ensures this.
This is particularly important in MFC, which provides a virtual function framework for users to implement as they wish.
For example, the virtual function InitInstance of MFC CWinApp is initialized once for each routine. Therefore, when users derive CMyApp class from CWinApp, they only need to rewrite the virtual function InitInstance and generate the window in it.
1.3.7 Multiple Inheritance and virtual functions
Multiple inheritance can be thought of as a combination of multiple single inheritance,
As a result,
Analyzing virtual function calls in the case of multiple inheritance is similar to analyzing single inheritance.
In C++, if there is a junction on multiple inheritance paths, the base class at the junction is called a common base class.
Obviously,
The base class can be accessed through different access paths, resulting in multiple instances of the common base class, causing ambiguity.
If you want this common base class to produce only one instance, you can declare it as a virtual base class.
This requires that when a new class is derived from this public base class, the public base class is specified as a virtual base class using the keyword virtual.
The general declaration form is as follows:
classDerived class name:virtualName of the access control base classCopy the code
A derived class may inherit one or more virtual base classes, publicly or privately. The relative position of the keyword virtual and the keyword public or private is irrelevant, but precedes the base class name, and the keyword virtual only applies to the base class name immediately following it.
Such as:
class D : virtual public A, private B, virtual public{C.../ / the class body
};
Copy the code
Derived class D is derived from virtual base classes A and C and non-virtual base class B.
The keyword virtual can be placed after access control, but must come before the base class name,
Figure 1.5 is a UML diagram of the virtual base class.
The Base1 and Base2 classes, when derived from the Base class, use the keyword virtual to indicate that the base class is their virtual base class.
In this way,
In the Derived class, there is only one instance of the Base class.
Ambiguities no longer arise when objects or member functions of the Derived class use members of the virtual base class.
Such as:
derived d;
int i = d.b; / / right
Copy the code
Example of using virtual base classes.
class base{
public:
int b;
base1(int i, int j):base(i),b1(j)
{
cout<<"base1="<<b1<<",base="<<b<<endl; }};class base1 : virtual public base
{
public:
int b1;
base1(int i, int j):base(i),b1(j)
{
cout<<"base1="<<b1<<",base="<<b<<endl; }};class base2 : virtual public base
{
public:
base2(int i, int j):base(i),b2(j)
{
cout<<"base2="<<b2<<",base="<<b<<endl; }};class derived : public base1, public base2
{
float d1;
public:
derived(int a, int b, int c, float e):base1(a,b),base2(b,a),base(c),d1(e)
{
}
void display(a)
{
cout<<"derived="<<d1<<endl;
cout<<"base="<<b<<endl;
cout<<"base1="<<b1<<endl;
cout<<"base2="<<b2<<endl; }};void main( )
{
derived d(1.2.3.5.5);
d.display(a); cout<<d.b<<endl; cout<<d.base1::b<<endl; cout<<d.base2::b<<endl; }Copy the code
The execution result of the program is as follows:
base=3
Base1 =2, Base =3 Base2 =1, Base =3 Base =5.5 Base =3 Base1 =2 Base2 =1 3 3 3
Because there is only one instance of the Base class in the Derived class, members of the virtual Base class can be accessed from any inheritance path, using the same instance of the Base class,
For example,
D. base1 :: b and D. base2 :: b use the data member B of the same virtual base class base class and therefore have the same value.
In the constructor initializer list of derived classes, the constructor of the virtual base class must be called separately.
Such as:
derived(int a, int b, int c, float e):base1(a,b),base2(b,a),base(c),d1(e)
{
}
Copy the code
When calls to both virtual and non-virtual base constructors appear in the initializer list, the virtual base constructor is executed before the non-base constructor and is called only once, thus ensuring that only one virtual base object is created and eliminating ambiguities.
For the constructor derived above, base(c) takes precedence, and base1(a,b) no longer calls the base constructor to initialize the virtual base class when base1(a,b) is executed.
In the same way,
Base2 (b,a) also does not call the base constructor.
As you can see from the results of the execution, the effect is the same regardless of which path the base data member B is accessed from.
The address of an object of a derived class can be assigned directly to a pointer to the virtual base class,
Such as:
base *bptr = &d;
Copy the code
No casting is required at this point. A virtual base class can refer to an object of a derived class,
Such as:
base& ref = d;
Copy the code
The reverse is wrong: no matter what path is specified in the cast, a pointer or reference to a virtual base class cannot be converted to a pointer or reference to a derived class.
For example, the statement
derived *dptr = (derived *)(base1*)bptr;
Copy the code
A compilation error will be generated.
Virtual base class definitions are tricky to handle, which is why multiple inheritance was not supported in the original C++ language.
The special layout of a virtual base class in a derived class makes it impossible to reset a pointer to a virtual base class back to a derived class (the same goes for references).
Dominance rules can also be used to analyze ambiguity in the case of virtual base classes.
A class can be derived from more than one class, and multiple inheritance is used when a combination of concepts is required.
Virtual base classes provide a meeting point for multiple inheritance paths, allowing them to share information.
In most cases, using multiple inheritance requires virtual base classes, but the definition of virtual base classes is complex,
As a result,
Use multiple inheritance in your program with caution.
However, MFC uses multiple inheritance and virtual base classes in many places to improve the flexibility of programming.
Class architecture design instances using virtual base classes. This example can manage students, faculty, and professors at a university.
The problem it solves is a familiar real-world problem that makes it easy to identify objects and create class descriptions of objects. Figure 1.6 shows the class hierarchy established for this instance.The Person class is the common root of the established class system, encapsulating the commonalities of the various types of people in the problem domain.
To simplify the program, choose to describe only the person name and age in the Person class.
The student class is a description of a specific class of objects, where only one student’s major is concerned.
The faculty class describes faculty and cares about the department in which this person teaches.
A professor is a special faculty member who cares not only about the department in which he teaches, but also whether he is a first-class or second-class professor. Therefore, the “professor” class is derived from the “faculty” class, adding features that the “professor” class does not have.
The faculty for in-service learning is both a faculty member and a student, so the studentFaculty class should be derived from the Student and faculty classes.
In this class hierarchy, the studentFaculty class is created by merging the concepts of the two classes, and the Person class is the common meeting point of the two inheritance paths of the studentFaculty class.
Because objects of the studentFaculty class can have only one name and one age, the Person class should be declared virtual base.
Because this class system uses virtual base classes and string classes, it simplifies the definition of the class system.
The Person class defines a default constructor, which is not called, and is used only to simplify the definition of constructors for classes derived from virtual base classes.
The protection constructors of the Student and Faculty classes are not available to their derived classes, and the default constructor of the Person class does just that.
The protected constructor cannot be called when creating objects of that class, except when it can be called by a derived class, to prevent the objects of that class from being incorrectly initialized.
To prevent objects from being properly initialized when the Student and Faculty classes are created with a single string initializer, the constructors in these two classes that are used only by derived classes are protected.
The concept and definition of virtual base classes are complex, so when designing a class system with virtual base classes and handing the class system to other programmers, it is important to realize that the programmer needs to know the class definition of virtual base classes and use the class system carefully.
Therefore, the class system should be carefully planned.
//univ.h is the declaration of the class, and univ. CPP is the definition of the class.
//univ.h
#if ! defined(UNIV_H)
#defined UNIV_H
#include <iostream>
#include <string>
using namespace std;
class person
{
protected:
string name;
int age;
person( )
{
name=""; age=0;
}
person(string n, int a) : name(n), age(a)
{
}
};
class student : virtual public person
{
private:
string major;
protected:
student(string m) : major(m)
{
}
public:
student(string n, int a, string m) : person(n,a), major(m)
{
}
void print( );
};
class faculty : virtual public person
{
protected:
string dept;
faculty(string d) : dept(d)
{
}
public:
faculty(string n, int a, string d) : person(n,a), dept(d)
{
}
void print( );
};
class professor : public faculty
{
private:
int level;
public:
professor(string n, int a, string dept, int h): person(n,a), faculty(dept), level(h)
{
}
void print( );
};
class studentFaculty : public student, public faculty
{
public:
studentFaculty(string n, int a, string m, string d): person(n,a), student(m), faculty(d)
{
}
void print( );
};
//univ.cpp
#endif
#include "univ.h"
void student :: print( )
{
cout << name <<endl<< "Age: " << age<< "\tMajor: " << major << endl;
}
void faculty::print( )
{
cout << name <<endl<<"Age: " << age<< "\tDepartment: " << dept << endl;
}
void professor :: print( )
{
faculty :: print(a); cout <<"Level: " << level <<endl;
}
void studentFaculty :: print( )
{
student :: print(a); cout <<"Department: " << dept <<endl;
}
Copy the code
Here is a test program that uses this class architecture:
//main.ccp
#include "univ.h"
void main( )
{
student myStudent("Zhang Hong".25."Computer");
faculty myFaculty("Wang Yong".35."Engineering");
professor myProfessor("Li yu he".52."Management".2);
studentFaculty myStudentFaculty("Zhao xiao ming".22."English"."Robot");
myStudentFaculty.print( );
myStudent.print( );
myProfessor.print( );
myFaculty.print( );
}
Copy the code
Its running results:
Zhao xiao ming
Age: 22 Major: English Department: Robot Zhang Hong Age: 25 Major: Computer Li yu he Age: 52 Department: Management Level: 2 Wang Yong Age: 35 Department: Engineering
1.4 Function Pointers and class member Pointers
MFC uses function Pointers and Pointers to class members to perform certain operations, sometimes confusing for beginners. Such as window callback functions.
This section reviews function Pointers and class member Pointers for later sections.
1.4.1 Function Pointers
The function has a physical address in memory that can be assigned to a pointer.
This is because when a function is compiled, its source code is converted into object code and the entry address of the function is determined.
The program calls the function, which points to this entry address.
As a result,
A pointer to a function actually contains the entry address of the function, so the address assigned to the pointer is the entry address of the function, and the pointer is used instead of the function name.
This allows functions to be passed as arguments to other functions, so that a pointer to a function can be passed to the function, or placed in an array to be used by other objects.
1.4.1.1 Function pointer Definition
A function pointer is defined as follows:
Data type identifier (* pointer object name)(list of data types for function arguments);Copy the code
For example, the statement “int (*p)(int,int); It simply states that p is a pointer to a function that returns an integer value.
P is not a fixed reference to a function, but an object of type defined to hold the entry address of the function.
It refers to any function assigned the address of the program.
In a program, a function pointer object can point successively to different functions.
In this respect, it has the same properties as the pointer objects introduced in the past.
When declaring a function pointer, you only need to specify the data type of the function parameter, not the parameter name.
It works if given, but the compiler ignores the parameter name.
so
The following two forms are equivalent:
int( *p )(int ,int ); // Only parameter types are given
int( *p )(int a,int b ); // Give the parameter type and parameter name
Copy the code
A typedef definition can also be used, for example:
typedef int (*FUN) (int a, int b);
FUN p;
Copy the code
P is a pointer to a function whose prototype is int (int, int).
1.4.1.2 Function pointer Object Assignment
When assigning a value to a function pointer object, you only need to give the function name instead of the parameter.
because
Statement p= function name;
Is to assign the address of the function entry to the pointer object P, without involving the combination of the real participation parameters.
The array name represents the starting address of the array, and the function name represents the entry address of the function.
Because in C++, a single function name (followed by no parentheses) is automatically converted to a pointer to that function (the address of the first instruction of the function).
When the function address is placed in a pointer object, the function can be called from that pointer object.
Here p is the pointer object to the function, it and the function name point to the beginning of the function, call p is to call the function. But it can only point to the entrance of the function, not to specific instructions in the middle of the function. As a result,
The *(p+1), p+n, p–, and p++ operations are meaningless to it.
1.4.1.3 Calling the Pointing Function
The format of the call to the function is as follows:
(* pointer object name)(function argument list);Copy the code
When a function is called by a pointer object, it simply replaces the function name with (* pointer object name) and writes arguments in parentheses after (* pointer object name) as needed.
For example, the statement (*p)(a,b) calls the function pointed to by p, passing a and b as arguments to the function.
Parentheses also do not contain arguments, but parentheses must exist. The following statement calls a function without arguments in a similar manner:
(*p) ( );
Copy the code
P also needs to be enclosed in parentheses to force the operation “*” to be used before it is called.
If no parentheses are added, then
int *p( );
Copy the code
It becomes a function declaration that returns an integer pointer.
Indirectly called functions can be used in the normal way to return results.
For example,
The following statement assigns the result returned by the function call to the I object.
i = (*p) (a,b);
Copy the code
1.4.1.4 Function Declaration
You must declare a prototype of the function.
We now use function names as rvalues without parentheses and arguments. The compiler cannot determine whether they are object names or function names, so we declare them.
That is:
Data type function name (list of data types for parameters);Copy the code
This example is declared as follows:
int min(int.int);
Copy the code
Output all the results of the polynomials x2+5x+8 and x3-6x in the interval [-1, +1], growing at a step size of 0.1.
#include <iostream>
using namespace std;
double const STEP=0.1;
double f1(double ); // Prototype declaration of function f1
double f2(double ); // Prototype declaration of function f2
void main( )
{
double x, (*p)(double); Pfor (int I =0; i<2; i++)
for ( int i=0; i<2; i++)
{
if (i==0) p = f1; // p refers to function f1 when I is 0
else p = f2; // p refers to f2 when I is 1
for( x = - 1; x <= 1; x += STEP) // Completes the calculation of the specified function
cout<<x<<"\t"<<(*p)(x)<<endl; }}double f1(double x) // Define the function f1
{
return ( x*x + 5*x +8);
}
double f2(double x) // The definition of function f2
{
return( x*x*x- 6*x );
}
Copy the code
The above program uses a function pointer P to complete the calls to f1 and F2, which can greatly increase the processing power of the program in those regular multi-function call systems.
You can also use a function pointer object to a function as an argument to pass the function address (that is, the function name as an argument) and pass the function as an argument to other functions.
Here is an example of what this means. Suppose there are three functions Max, min and mean for finding the greater, the lesser and the mean of two numbers respectively.
Now define another function all as follows:
int all(int x, int y, int (*func)(int.int))
{
return (*func)(x,y);
}
Copy the code
The function all takes three parameters, including two int parameters, one of which is func, a pointer to the function.
This object is declared int (*func)(int,int), and func in all can be replaced by a defined function. For example, all(a,b,mean) executes mean(a,b) to print the average of a and b.
In the same way,
Min and Max can be called in the same way, while the all function has the same form, only changing the argument function name when called.
This increases the flexibility with which functions are used, which is especially useful in large-scale programming, especially module design.
Complete sample program.
#include <iostream>
using namespace std;
int all (int.int.int(*) (int.int)); // Function prototype declaration with function pointer
int max(int.int ).min(int.int ).mean(int.int ); // Function prototype declaration
void main( )
{
int a, b;
cin>>a>>b;
cout<<"max="<<all(a,b,max)<<endl;
cout<<"min="<<all(a, b, min)<<endl;
cout<<"mean="<<all(a,b,mean)<<endl;
}
int all(int x, int y, int (*func)(int.int))
{
return (*func)(x,y);
}
int max(int x, int y)
{
return(x>y)? x:y; }int min(int x, int y)
{
return(x<y)? x:y; }int mean(int x, int y)
{
return( (x+y)/2 );
}
Copy the code
Enter the 58, 62
Output Max =62 min=58 mean=60
Find the minimum of the function 10×2-9x+2 in increments of 0.01 in x over the interval [0, 1].
#include <iostream>
using namespace std;
double const s1=0.0;
double const s2=1.0;
double const step=0.01;
double func(double);
double value(double(*) (double));
void main ( )
{
double (*p)(double);
p=func; // point to the target function
cout<<"The minimum is :"<<value(p)<<endl;
}
double func(double x) // Target function
{
return (10*x*x9 -*x+2);
}
double value(double(*f)(double)) // Define the minimum function, which includes the function pointer
{
double x=s1, y=(*f)(x);
while( x <= s2 )
{
if( y > (*f)(x) )
y=(*f)(x);
x += step;
}
return y;
}
Copy the code
Run result: Minimum value: -0.025
The target function of a function pointer must already exist before it can be referenced.
In this case, func, the target function of the p function pointer, is declared before p is called and passed through the statement
p=func;
Point to its target function func
1.4.2 Pointers to class members
An object is a complete entity. To support this encapsulation, C++ includes Pointers to class members.
A normal pointer can be used to access any object of a given type in memory, and a pointer to a class member can be used to access any member of a given type in an object of a particular class.
C++ contains Pointers to both class data members and member functions.
C++ provides a special type of pointer that points to a member of a class, rather than to an instance of that member in an object of that class.
1.4.2.1 Pointers to class data members
Classes are not objects, but they can sometimes be used as objects. Pointers to data members or Pointers to object data members can be declared and used.
Pointers to objects are more traditional Pointers. Pointers to data members of class X of type type are declared as follows:
type X :: * pointer;
Copy the code
Statement if the data member of class X is of type type
pointer = &X :: member;
Copy the code
Store the member’s address in pointer. Note that the expression &x :: member is used to obtain the address of a class member. The resulting address is not the real address, but the offset of member member in all objects of class X.
As a result,
To access the member that pointer points to on an object, use the special operator. And the “- >”.
Pointers to class data members can access the public data members of any object of that class.
Consider the following classes:
class A
{
public:
int a,b,c;
};
Copy the code
The following declaration indicates that p is A pointer to an integer data member of class A:
int A :: *p; // pointer to integer data members (A,b,c) of class A
Copy the code
Although “::” is the scoped discriminator, in this case “A ::” is best read as “member of A”. When viewed from the inside out, the declaration reads: p is A pointer to the data members of class A, which are integers.
P can point to one of only three data members A, B, or C, the only set of integer data members of class A.
However,
P can only point to one data member at a time.
In this regard, it seems like a community.
P can point to any of the three appropriate data members of A with the following assignment.
P = &a :: data member name; At this point, no objects of class A have been created, which is the core point of Pointers to class data members.
P will access the a, B, and C member variables of any object.
To actually use Pointers to members, use “.” And the “->” operators, which are new to C++.
In the main program of the following example, we also define a pointer to the object X to see how their actions differ.
Comparison of Pointers to class data members with Pointers to class objects.
#include <iostream>
using namespace std;
void main(a)
{
int A :: *p; // point to class A
p = &A :: b; //p points to the data member b of class A
A x; // Object x of class A
A *px = &x; //px is a pointer to object x
x.*p = 1; // object x uses the pointer p with the operator ".* "to set x.b to 1
p = &A :: c; //p instead points to data member C of class A
x.*p=9; / / x.c = 9
px ->*p = 2; // the object's pointer px uses the pointer p to reset x.c=2
p = &A :: a; // change p to point to data member A of class A
x.*p = 8; / / x.a = 8
cout<<x.*p<<""<<px->b<<""<<px->c<<endl;
}
Copy the code
Operator ‘. ‘ Concatenate an lvalue with an rvalue. An lvalue must be an object of the class, and an rvalue is a specific data member of the class. The “->” operator has a similar effect.
Its lvalue must be a pointer to an object of the class, and its rvalue must be a specific data member of the class.
In the statement x.*p = 1; Where, x is an object of class A and P refers to the data member B of class A, representing the assignment of 1 to B. Statement “px ->*p = 2;” C; / / assign 2 to c; / / assign 2 to c;
The pointer P indicates the current data member, and it is easy to analyze the program’s output as: 8, 1, 2.
It follows that,
When a pointer to a class data member is used to access a data member of an object, an object must be specified.
If the object is identified by an object name or reference, the operator “.* “is used; If it is identified by a pointer to an object, the operator “->*” is used.
The limitation on data members is that they must be public.
Pointers to static data members of a class are defined and used in the same way as regular Pointers.
Use static data member Pointers to classes.
class A {
public:
A() {}static int num;
};
int A::num;
void main( )
{
int *p = &A :: num;
*p=56;
cout<<A::num<<endl; / / output 56
A a, b;
cout<<a.num<<endl; / / output 56
cout<<b.num<<endl; / / output 56
}
Copy the code
Since a static data member does not belong to any object, an object is not required to access a pointer to a static data member. Because a static data member is specified in a class, its address needs to be qualified by the member name.
1.4.2.2 Pointers to class member functions
Pointers to a class member function work in a similar way to Pointers to a class data member.
The main difference is that the syntax is more complex.
Suppose the member function of class A is void fa(void); To create A pointer to pafn, it can point to any member function of class A that has no arguments and no return value:
void( A ::* pafn )( void );
Copy the code
The following example shows how pafN can be assigned and used to call the function fa:
pafn = A :: fa; // pointer pafn to the member function fa of class A
A x; // Object x of class A
A *px = &x ; // pointer px to object x of class A
(x.*pafn)( ); // Call the member function fa of object X of class A
(px ->* pafn)( ); // Call the member function fa pointed to by the pointer px of object X of class A
Copy the code
A pointer to a member function of class X whose argument type is list and return type is type is declared as follows:
type( X ::* pointer)( list );
Copy the code
Statement if the prototype of fun, a member function of class X, is the same as the prototype of the function pointed to by pointer
pointer = X :: fun;
Copy the code
Sets the address of the function (that is, it is offset in all objects of the class) to a pointer pointer. Similar to Pointers to class data members, the operator “.* “is used when calling a function pointing to pointer using an object name or reference, and the operator” ->* “is used when calling a member function pointing to pointer using a pointer to an object.
Use Pointers to class member functions
#include <iostream>
using namespace std;
class A
{
private:
int val;
public:
A( int i ) { val = i; }
int value( int a ) { returnval + a; }};void main( )
{
int( A ::*pfun )( int ); // Declare A pointer to A member function of class A
pfun = A :: value; // A pointer to a specific member function value
A obj(10); // Create object obj
cout << (obj.*pfun)(15) << endl; // The object uses the functions of the class
// pointer, output 25A * PC = &obj; // PC is the pointer to object A
cout << (pc->*pfun)(15) << endl; // Object Pointers use classes
// function pointer, output 25
}
Copy the code
Note: (obj.* pfun) or (PC ->* pfun) must be parentheses. In a derived class, polymorphism still occurs when a pointer to a base-class member function points to a virtual function and that virtual function is accessed through a base-class pointer (or reference) to an object.
Produces polymorphisms using Pointers to base class member functions.
#include <iostream>
using namespace std;
class base
{
public:
virtual void print( )/ / virtual functions
{
cout << "In Base"<< endl; }};class derived : public base
{
public:
void print( ) / / virtual functions
{
cout << "In Derived"<< endl; }};void display( base *pb, void( base ::*pf )( ) )
{
( pb->*pf)( );
}
void main( )
{
base b;
derived d;
display( &b, base :: print ); // Output In Base
display( &d, base :: print ); // Output In Deri
}
Copy the code
1.5 Static Members
If a class’s data member or member function is decorated with the keyword static, such a member is called a static data member or a static member function, collectively known as a static member. Static members play an important role in MFC.
1.5.1 Usage Examples
Analyze the output of the following program
class Test
{
static int x; // Static data member
int n;
public:
Test() {}Test(int a, int b)
{
x=a; n=b;
}
static int func(a)// Static member functions
{
return x;
}
static void sfunc(Test&r,int a)// Static member functions
{
r.n=a;
}
int Getn(a)// Non-static member functions
{
returnn; }}; Int Test::x=25; // Initializes the static data member
#include <iostream>
using namespace std;
void main(a)
{
cout<<Test::func(a);//x is before the object is generated
// If yes, output 25
Test b, c;
b.sfunc(b,58); // Set the data member n of object B
cout<<""<<b.Getn(a); cout<<""<<b.func(a);//x belongs to all objects, output 25
cout<<""<<c.func(a);//x belongs to all objects, output 25
Test a(24.56); // Change the class x value to 24
cout <<""<<a.func() < <""<< b.func() < <""<<b.func()<<endl;
}
Copy the code
Static data members can only be specified once, and if only static data members are declared in a class, they must be defined somewhere in the file scope.
During initialization, the member name must be qualified.
Such as:
Test(int a, int b)
{
Test::x=a; n=b;
}
Copy the code
Except for the initialization of static data members, static members follow the same access restrictions as other members of the class, and static members already exist, although no object has been created.
Because of the need for data hiding, static data members are usually stated as private, and are accessed by defining public static member functions.
Note: Since static is not part of the function type, static is not used when defining static member functions outside of the class declaration.
Static member functions defined in a class are inline.
In general, it is better to qualify access to a static member by its name than by its object name, because static members are not members of the object.
A static member can be inherited, in which case the base and derived objects share the static member.
In addition to that,
Other features of static members (for example, access rights of static members in derived classes, overloading member functions in derived classes, and so on) are analyzed at the class level in a similar way to general members.
Any member function in a class can access a static member, but a static member function can only access a non-static member of that object by its name (or by a pointer to an object), because static member functions don’t have a this pointer, as defined by sfunc(). After constructing object A, change the x value of the class, that is, change the x value of all objects to this value.
The following output is displayed:
25 58 25 25 24 24 24
It follows that,
Static members and ordinary members have the following differences: ① can not point to a specific object, only with the name of the class. ② Static members exist before objects are created. ③ Static members are members of classes, not objects. (4) Static members are shared by all objects of this class and are stored in a common memory. ⑤ There is no this pointer, so the data members of the class cannot be accessed unless they are explicitly passed a pointer. ⑥ Static member functions cannot be declared as virtual functions. ⑦ Static member functions cannot access non-static functions directly.
Examples of using static class objects. Do not confuse static members of a class with static class objects.
A static class object is an object of a class declared with the keyword static, so a static class object is essentially a static class variable, but note the nature of its constructor and destructor calls.
This example illustrates the particularity of static class objects.
#include <iostream>
using namespace std;
class test
{
private:
int n;
public:
test(inti) { n=i; cout<<"constructor:"<<i<<endl;
}
~test()
{
cout<<"destructor:"<<n<<endl;
}
int getn(a)
{
return n;
}
void inc(a)
{ ++n; }};void main( )
{
cout <<"loop start:" << endl;
for(int i=0; i<3; i++) {static test a(3);
test b(3);
a.inc(a); b.inc(a); cout<<"a.n="<<a.getn()<<endl;
cout<<"b.n="<<b.getn()<<endl;
}
cout<<"loop end."<<endl;
cout<<"Exit main()"<<endl;
}
Copy the code
The program output is as follows:
loop start:
constructor:3 constructor:3 a.n=4 b.n=4 destructor:4 constructor:3 a.n=5 b.n=4 destructor:4 constructor:3 a.n=6 b.n=4 destructor:4 loop end. Exit main() destructor:6
The program creates two class objects: static class object A and ordinary class object B.
As can be seen from the program output,
For static class a, the first time it executes its definition, the constructor is called so that a.n=3 and a.inc() makes a.n=4. After printing its value of 4, the for loop goes to the next loop.
In subsequent loops, the constructor is not called again;
It does not call the destructor on exit until all program execution is complete.
It follows that,
It has the following properties: ① The constructor is called during code execution when it encounters its variable definition for the first time, but only once until the end of the program.
The destructor is called once before the entire program exits. For ordinary class b, because it is local to the for loop statement, the lifetime can only exist with the current loop, and it calls the destructor once each time the current loop in the body of the loop ends. It’s easy to see the difference by comparing their output.
1.5.2 Roles of Static Members in the MFC
You can use static member variables to store information common to all objects. Because both runtime class information (including base and derived class names, etc.) and class mapping tables (such as CWnd’s derived classes) are common to all objects, macro definitions that provide runtime class information, macro definitions of message mapping tables, etc., can be implemented by using static data member variables in classes. In fact, static const types are generally used. For example, the macro definition DECLARE_MESSAGE_MAP() uses the following definition to store the message mapping table:
ststic const AFX_MSGMAP_ENTRY_messageEntries[];
Copy the code
Static member functions of a class often perform functional operations related to the class, but without involving concrete class objects. The CFile class in MFC, for example, defines some system-wide file manipulation functions (file renaming, file deletion, area file information, etc.) as static member functions.
Functions that obtain or create (including temporary) wrapper objects (CWnd, CDC, CMenu, etc.) from handles (window handle HWND, device handle HDC, menu handle HMENU) are defined as static members of the corresponding wrapper class.
For example, the following member function is defined in the CWnd class:
static CWnd* PASCAL FormHandle(HWND hWnd);
Copy the code
The CWnd class also defines some function functions for system-wide window operations (get foreground window, get current focus window, etc.) as static member functions.
Here is the definition used by the CDC class:
static CDC* PASCAL FormHandle(HDC hDC);
static void PASCAL DeleteTempMap( );
Copy the code
1.6 Classification, aggregation, and nesting
There are two broad categories of relationships between classes. One is inheritance and derivation, and the other is the use of one class by another.
The latter is simply used to take an object of another class as an argument to its own data member or member function.
The former is called classification, and the application of classification principle also means the formation of base-derived structure (also known as classification structure) through different degrees of abstraction.
The classification principle can be used to describe the commonness of objects centrally, and clearly represent the relationship between objects and classes (i.e. “ISA”) and the relationship between derived classes and base classes (i.e. “IS-A-kind-of”), so as to control the complexity of the system.
The latter is called aggregation or composition, and C++ is sometimes called inclusion. Its principle is to treat a complex thing as an assembly of several relatively simple things, so as to simplify the description of complex things.
In object-oriented analysis (OOA), the principle of aggregation is to distinguish the whole and its components, and describe the whole object and part object respectively to form a whole-part structure, so as to clearly express the constituent relationship between them (called “HAS-A” relation, Or conversely, the “IS-A-part-of” relationship).
For example, one part of a car is an engine. In OOA, the car can be taken as a whole object and the engine as a part object, and the composition relationship between them can be expressed through the whole-part structure (the car has an engine, or the engine is a part of the car).
The terms “aggregate” and “inclusion” are interpreted and used slightly differently in some literature.
The former is used for looser and more flexible whole-part structures, while the latter is used for tight, fixed whole-part structures.
A class can also be embedded within a class. The embedded class is called an embedded class, and the class containing the class is called a containment class.
An inline class has its own member functions, encapsulated in a containment class, that are not directly accessible from the outside, and are not controlled by the containment class (that is, access control is completely independent of the two).
Nested classes are like a tool that allows users to manipulate data from contained classes.
A containment class can encapsulate different data sets for different processing objects without code changes affecting the original embedded class definition.
In the same way,
The definition of an embedded class can be modified, added, or removed according to the user’s needs, with no effect on the containment class.
It follows that,
Nested classes can encapsulate the user interface portion of a data processing, separating the processing from the presentation of the data.
Because an embedded class cannot directly access the non-public members of a containment class, the containment class does not have any access privileges for the nested class.
If you need to improve the relationship between an embedded class and a containment class, you can define one as a friend class of the other.
Examples of using inline and friend classes.
#include <iostream>
using namespace std;
class Contain
{
protected:
unsigned char m_link;
public:
// Define an embedded class as a public member
class Embed
{
private:
int m_flag;
public:
Embed() {}Embed(int flag)
{
m_flag=flag;
}
int GetValue(a)const
{
return m_flag;
}
Embed& operator= (const Embed& bed); // declare, leave external definition
void ShowLink(Contain* pCon);
};
Embed m_bed ; // Also define a public member variable of that type
// Define the embedded class as a friend of the parent class
friend class Embed;
public:
Contain() {}Contain(int flag,unsigned char cLink):m_bed(flag)
{
m_link=cLink;
}
// You can apply custom type members freely in a class
Embed GetEmbedMember(a)const
{
returnm_bed; }};// Define a nested class member function externally, with a double identifier to delimit the scope
Contain::Embed& Contain::Embed::operator=(constEmbed& bed)
{
m_flag=bed.m_flag;
return *this;
}
void Contain::Embed::ShowLink(Contain* pCon)
{
if(m_flag)
cout<<(int)pCon->m_link<<endl;
else
cout<<pCon->m_link<<endl;
}
void main( )
{
Contain::Embed bed,bed1,bed2(56);
Contain con(0.'A'); con.m_bed.ShowLink(&con);
bed=con.m_bed;
cout<<bed.GetValue()<<endl;
cout<<bed2.GetValue()<<endl;
bed2=bed;
cout<<bed2.GetValue()<<endl;
Contain con1(73.'A'), con2;
con1.m_bed.ShowLink(&con);
bed=con1.m_bed;
cout<<bed.GetValue()<<endl;
bed1=con1.GetEmbedMember(a); cout<<bed1.GetValue()<<endl;
bed2=con1.m_bed;
cout<<bed2.GetValue()<<endl;
}
Copy the code
The running results of the program are as follows:
A 0
56 0 65 73 73 73
Thus, if a class contains an inline class, the object of that inline class must be included as a member variable object; otherwise, the inline class loses its effect and becomes meaningless.
In this respect, it is similar to aggregation.