Hello, C ++ (34) has a copy constructor named Dolly 6.2.4, 6.2.4 constructor.
6.2.4 copy constructor
In the C ++ world, in addition to using constructors to directly create a new object, you sometimes need to create a copy of an existing object, just like the sheep called Dolly, we hope to create another identical goat based on one sheep. For example:
// Call the constructor to create a new object shMotherSheep shMother; // perform some operations on shMother... // Use the shMother object to create an identical new object shDolly as its copy Sheep shDolly (shMother );
Here, we first create a new shMother object of the Sheep class, then perform some operations on it to change its member variables, and then use this object as the parameters of the Sheep class constructor, create a copy shDolly identical to the shMother object. The constructor that accepts an object as a parameter and creates a new object as its copy is called the copy constructor. The copy constructor is actually the cousin of the constructor. In terms of syntax format, the two are basically similar. The two have the same function name, but the parameters of the copy constructor are references to this class object, the object it creates is a copy of the object that is used as the parameter and a copy of it. Similar to constructor, by default, if a class does not explicitly define its copy constructor, the compiler will create a default copy constructor for it, copy the data in the memory space of the old object to the memory space of the new object to complete the creation of the new object. Because the Sheep class above does not define the copy constructor, The shDolly object created in the code above is done through this default copy constructor method.
Use the default and delete keywords to control the default behavior of the class.
To improve development efficiency, for some special functions necessary in the class, such as constructor, destructor, and value assignment operator, if we do not explicitly define these special functions in the class, the compiler will generate the default versions of these functions for us. Although this mechanism can save us a lot of time writing special functions, in some special cases, for example, we do not need to copy the constructor generated by the compiler if we do not want the object to be copied. At this time, this mechanism has become a superfluous addition.
To cancel this default behavior of the compiler, we can use the delete keyword to disable a default special function. For example, by default, even if we do not define a copy constructor in the class, the compiler will generate a default copy constructor for us to copy and copy objects. Sometimes, if we do not want an object to be copied, we need to use the copy constructor and value assignment operator in the delete disable class to prevent the compiler from generating default functions for it:
Class Sheep {//... // disable the default value assignment operator Sheep & operator = (const Sheep &) = delete; // disable the default copy constructor Sheep (const Sheep &) = delete ;};
Now, the Sheep class does not have the default value assignment operator and the copy constructor implementation. If you want to copy the object at this time, it will lead to compilation errors, so as to prohibit the object from being copied. For example:
// Incorrect object replication behavior Sheep shDolly (shMother); // error: the copy constructor of the Sheep class is disabled. Sheep shDolly = shMother; // error: the value assignment operator of the Sheep class is disabled.
In contrast to disabling default functions with the delete keyword, you can also use the default keyword, explicitly indicating that we want to use the default version generated by the compiler for these special functions. In the above example, if we want the object to be copied by default:
Class Sheep {//... // use the default value assignment operator and copy constructor Sheep & operator = (const Sheep &) = default; Sheep (const Sheep &) = default ;};
Explicit use of the default keyword indicates the default behavior of the class, which is obviously redundant for the compiler, because it will do so even if we do not describe it. However, for readers of the Code, the use of the default keyword explicitly indicates the default version of the special function, which means we have considered that the default versions of these special functions have met our requirements, you do not need to define it yourself. Leaving default operations to the compiler for implementation not only saves time to improve efficiency, but also reduces the chance of errors and usually produces better target code.
In most cases, the default version of the copy constructor can meet the needs of copying a copy object. We do not need to explicitly define the copy constructor. However, in some special cases, especially when there are pointer-type member variables in the class, the default copy constructor implemented in the copy memory mode can only copy the value of the pointer member variable, instead, we can't copy the content pointed to by the pointer. In this way, the two pointers of the New and Old objects point to the same content, which is obviously unreasonable. The default copy constructor cannot copy such objects correctly. In this case, you need to define the copy constructor of the class to complete the copy of the content that requires special processing like the pointer member variable in a custom way. For example, a Computer class has a pointer type member variable m_pKeyboard pointing to an independent Keboard object, in this case, you need to define the copy constructor of the Compuer class to complete the special copy work:
// Keyboard class, because the structure is simple, we use struct to define the struct Keyboard {// string m_strModel;} type of the Keyboard ;}; // defines the Computer class Computer {public: // default constructor Computer (): m_pKeyboard (nullptr), m_strModel ("") for copying Constructors ("") {} // copy the constructor. The parameter is the reference Computer (const Computer & com): m_strModel (com. m_strModel) // use the initialization attribute list to copy the m_strModel member variable of the object type {// create a new object, copy the m_pKeyboard member variable of the pointer type // obtain the m_pKeyboard Keyboard * pOldKeyboard = com member variable of the existing object com.. GetKeyboard (); // use the Keyboard object pointed to by pOldKeyboard as the blueprint, // create a new Keyboard object, and let m_Keyboard point to this object if (nullptr! = POldKeyboard) // here, the Keyboard object is copied using the default copy constructor m_pKeyboard = new Keyboard (* (pOldKeyboard); else m_pKeyboard = nullptr; // if no keyboard} // destructor, // The member variable m_strModel of the object type will be automatically destroyed, you do not need to process it in the Destructor // For the m_pKeyboard member variable of the pointer type, you need to manually destroy it in the Destructor ~ Computer () {delete m_pKeyboard; m_pKeyboard = nullptr;} // member function, which sets or obtains the void SetKeyboard (Keyboard * pKeyboard) {m_pKeyboard = pKeyboard ;} keyboard * GetKeyboard () const {return m_pKeyboard;} private: // Keyboard * m_pKeyboard, a member variable of the pointer type; // string m_strModel ;};
In this Code, we created a custom copy constructor for the Computer class. In this copy constructor, for the object type member variable m_strModel, we directly use the initialization attribute list to copy the member variables. For the m_pKeyboard member variable of the pointer type, copying it is not copying the value of the pointer itself, but copying the object pointed to by the pointer. Therefore, member variables of the pointer type cannot be copied directly in the form of memory copy. In this way, the pointer value is copied, And the content pointed to by the pointer is not copied. To copy a pointer type member variable, you must first obtain the pointer type member variable of an existing object and then use it to obtain the object to which it points, then create a copy and point the corresponding pointer type member variable in the new object to this object. For example, we use "m_pKeyboard = new Keyboard (* (pOldKeyboard ));", in this way, the replication of pointer type member variables is completed. The copy constructor defined by ourselves can not only copy the m_strModel, a member variable of the object type of the Computer class, but also copy the m_pKeyboard of the pointer type member variable correctly, the Computer object can be copied. For example:
// Introduce the header file where the assertions are located # include <assert. h> //... // Create a Computer object oldcomComputer oldcom; // create the Keyboard object of oldcom and modify its properties Keyboard keyboard; keyboard. m_strModel = "Microsoft-101"; // assemble the keyboard into oldcom. setKeyboard (& keyboard); // use oldcom as the blueprint, use the copy constructor of the Computer class to create a new object newcom // The New newcom object is a copy of the oldcom object Computer newcom (oldcom); // Use assertions to determine whether the two Computer objects are the same, // The computer model must be the same as assert (newcom. getModel () = oldcom. getModel (); // different Computer objects should have different Keyboard objects assert (newcom. getKe Yboard ()! = Oldcom. getKeyboard (); // because it is a copy, different Keyboard objects should be of the same type assert (newcom. getKeyboard ()-> m_strModel = oldcom. getKeyboard ()-> m_strModel );
In C ++, apart from using the copy constructor to create a copy of an object as a new object, after creating a new object, an existing object is often assigned to it to complete initialization of the new object. For example:
// Create a new Computer newcom object; // assign values to an existing object to initialize newcom = oldcom;
The assignment process is actually a copy process, that is, copying the object on the right of the equal sign to the object on the left of the equal sign. Similar to the copy constructor of a class, if a value assignment operator is not explicitly defined for the class, the compiler will generate a default value assignment operator for it, the assignment of objects is completed in the memory copy mode. Because the object is also copied in memory, when there is a pointer member variable in the class, you may also encounter the problem that you can only copy the pointer value but cannot copy the content pointed to by the pointer. Therefore, to assign values to class objects with pointer-type member variables, you must customize the class assignment operators, complete the replication of pointer-type member variables in a custom manner. For example, the Computer class contains the pointer type member variable m_pKeybard. You can customize its value assignment operator to complete the value assignment operation.
// The Computer class Computer {public: // custom value assignment operator Computer & operator = (const Computer & com) that defines the value assignment operator "=) {// determine whether to assign a value to yourself. // if it is an auto-assigned value, the object itself will be directly returned. // The this pointer here is an implicit pointer to its own object in the class. If (this = & com) return * this; // directly assign values to the object member variable m_strModel = com. m_strModel; // create a copy of the object referred to by the pointer member variable of the old object // point the pointer member variable corresponding to the assigned object to the copy object m_pKeyboard = new Keyboard (* (com. getKeyboard ()));}//...};
In the above assignment operator function, we first determine whether this is an auto-assigned operation. Self-assignment means assigning values to yourself. For example:
// Use newcom to assign newcom = newcom;
Strictly speaking, this assignment operation is meaningless and should be considered a programmer's mistake. However, as a well-designed value assignment operator, we should be able to detect such errors and handle them appropriately, saving the program from the programmer's mistakes:
// Determine whether it is an auto-assigned operation // compare the this pointer with the passed pointer to a com Object // if it is equal, it is an auto-assigned operation, directly return the object itself if (this = & com) return * this;
If this auto-assigned operation is detected in the value assignment operator function, it directly returns the object itself to avoid subsequent copy operations. If it is not a self-assignment operation, the "=" operator is used to directly assign values to the object-type member variables. For pointer-type member variables, the method is similar to that of the copy constructor, create a copy of the object to which it points, and point the corresponding pointer member variable of the object on the left to this copy object to complete the assignment.
In addition, it is worth noting that the return value type of the value assignment operator is not necessarily a reference of this class. We can also use void instead. For example:
Class Computer {public: // use void as the return value type assignment operator void operator = (const Computer & com ){//... }//...};
Although the above Code is correct in syntax, it can also realize the assignment operation for a single object, but it cannot implement the following form of continuous assignment operation:
Computer oldcom; // Computer newcom1, newcom2; // continuous value assignment newcom1 = newcom2 = oldcom;
The continuous value assignment operator assigns values from right to left. Therefore, the above Code is actually:
newcom1 = (newcom2 = oldcom );
That is, assign oldcom to newcom2 first (this step can be completed if the return value type is void), and then assign the calculation result of "newcom2 = oldcom" to newcom1, if the return value type of the value assignment operator is void, it means that the calculation result of "newcom2 = oldcom" is void. We obviously cannot assign a void type data value to a Computer object. Therefore, in order to realize the continuous value assignment in the above form, we usually use the reference of this class (Computer &) as the return value type of the value assignment operator, and return the object itself ("return * this") in it. It is used to continue the next assignment operation.
Initialize list constructor
In addition to the common constructor and copy constructor described above, to make object creation more flexible, C ++ also provides a constructor that accepts an initialization list (initializer list) as a parameter. Therefore, this constructor is also called an initialization list constructor. The initialization list is constructed by a pair of braces ("{}") and can contain any number of data elements of the same type. If we want to create an object with the same data type of an indefinite number, for example, a wage object manages an indefinite number of wage items, including basic salary, bonus, commission, and subsidy, some people only have basic wages, while others all have. In order to create a unified wage object form, we hope that these indefinite wage items can be used to create wage objects. In this case, we need to implement the initialization list constructor of the wage class to complete this task:
# Include <iostream> # include <vector> # include <initializer_list> // introduce the header file using namespace std where the initialization list is located; // Salary class Salary {public: // initialization list constructor // The wage data is of the int type, so its parameter type is initializer_list <int> Salary (initializer_list <int> s) {// access the initialization list in the form of a container // obtain the wage items and save them to the vector container of the wage class for (int I: s) m_vecSalary.push_back (I );} //.. // get the total salary int GetTotal () {int nTotal = 0; for (int I: m_vecSalary) nTotal + = I; return nTotal;} private: // vector container vector <int> m_vecSalary ;}; int main () {// instructor Chen has only basic salary, "{}" indicates the initialization list Salary sChen {2200}; // Mr. Wang has both basic Salary and bonus and subsidy Salary sWang {1003 }; // output result cout <"instructor Chen's salary:" <sChen. getTotal () <endl; cout <"instructor Wang's salary:" <sWang. getTotal () <endl; return 0 ;}
It can be seen from this that, although the salary items of Teacher Chen and Mr. Wang are different, their salary objects can be created in a unified form through the initialization list constructor. This is exactly the meaning of the initialization list. It allows different numbers of data of the same type to be used as function parameters in the same form. In other words, if we want a function to accept an indefinite number of data of the same type as the parameter, we can use the initialization list as the parameter type. For example, we can add an AddSalary () function to the Salary class and use the initialization list as a parameter to add an indefinite number of Salary items to the Salary object:
// Void AddSalary (initializer_list <int> s) {for (int I: s) m_vecSalary.push_back (I );}//... // Later I found that Mr. Chen forgot to calculate the bonus and subsidy, and added the braces {} to him to form the initialization list sChen. AddSalary ({, 6500 });
6.2.5 operator overload
If you want to operate on two objects, such as adding two objects, the most intuitive way is to connect two objects with operators indicating their meanings like mathematical formulas, to express the operations on these two objects. In essence, an operator is equivalent to a function. It has its own parameters and can be used to receive the data operated by the operator. It also has its own function name, that is, the operator symbol. It also has a return value, return result data. In terms of usage, you only need to use operators to connect the two objects to be operated. This is much simpler and more intuitive than function calls, and the code is more readable. Therefore, when expressing some common operations, such as addition and subtraction operations on two objects, we often do this by reloading operators of the corresponding meanings of this class. In C ++, there are many built-in data types, including int, char, and string. These built-in data types have many defined operators that can be used to express the operations between them. For example, we can use the "+" operator to express the "addition" operation between two objects and use it to connect two int objects, the result of the "add" operation is the sum of the two numbers, and it is used to connect two string objects. The "add" operation result is to connect the two strings together. For example:
Int a = 3; int B = 4; // use the addition operator "+" to obtain the sum of two int type variables and int c = a + B; cout <a <"+" <B <"=" <c <endl; string strSub1 ("Hello "); string strSub2 ("C ++"); // use the addition operator "+" to obtain the connection result of two string-type variables string strCombin = strSub1 + strSub2; cout <strSub1 <"+" <strSub2 <"=" <strCombin <endl;
In this way, operators are used to express the operational relationship between objects. Abstract operators are used to express specific operational processes, so as to hide the specific details of operational processes, which is both intuitive and natural, this makes it easier to use. For the built-in data types, C ++ has provided a wide range of operators for us to choose to use to complete common operations, such as "+" (plus) representing mathematical operations), "-" (subtraction), "*" (multiplication), and "/" (Division ). But for the newly defined class, the two objects cannot be operated directly using these operators. For example, two objects of the Father class and the Mother class are defined respectively. We hope that the two objects can be connected by the addition operator "+", and then an object of the Baby class can be obtained through calculation:
// Define the Father class and the Mother class respectively. Father father; Mother mother; // use the addition operator "+" to connect two objects, obtain the Baby class Object Baby baby = father + mother;
The preceding statement expresses an obvious thing. However, if the addition operator "+" of the Father class is not defined, the Father class does not know how to create a Baby class object with a Mother Class Object. Such statements may cause compilation errors, one obvious thing does not work in C ++. Fortunately, C ++ allows us to overload these operators so that we can customize the operator behavior. Since it is user-defined, it is natural to do whatever you want to do. Naturally, you can add a Mother Class Object to the Father class object to get the Baby class object, making the above Code possible.
In terms of function, the overload operator is equivalent to the member function of the class. There is no essential difference between the two. You can simply regard the overload operator as a special member function. Although member functions can provide the same functions as operators, using operators can make the statements more concise and readable. For example. add (B) "calls the add () function to add two objects a and B, but the" a + B "statement that expresses the same meaning is far more than". add (B) "is more intuitive and easy to understand.
In C ++, the syntax format for declaring overload operators is as follows:
Class name {public: Return Value operator (parameter list) {// specific operation process of the operator }};
It can be seen from this that the reload operator and the class member function are essentially the same, but there are still some minor differences in form. A common member function uses an identifier (a string not headed by a number) as the function name, while a overload operator uses the operator as the function name. Operator indicates that this is an overloaded operator function, and the subsequent operator is the symbol we want to define.
In terms of use, when two objects are connected by operators for operation, it is actually equivalent to calling the operator function of the first object, and the second object is used as the parameter of this operator function. For example, an addition operator is used to calculate two objects:
a + b;
This statement is actually equivalent:
a.operator + (b);
"A + B" indicates that the operator "operator +" of object a is called, and object B is the parameter of this operator function. To get the baby object from "father + mother", you only need to define the "+" operator function of the Father class (because father is located before the operator, so we define the father class operator to which Father belongs) so that it can accept a Mother class object as a parameter and return a Baby class object:
// Mother class Mother {// omitting the specific definition}; // Child class Baby {public: // Child class constructor Baby (string strName): m_strName (strName) {} private: // child name string m_strName;}; // parent class Father {public: // The overloaded operator "+". The returned value is of the Baby type, the parameter is Mother type Baby operator + (const Mother & mom) {// create a Baby object and return it, omitting the creation process... Return Baby ("MiaoMiao ");}};
In the overload operator "+" of the Father class, it can accept an object of the Mother class as a parameter, and create an object of the Baby class in it as the return value of the operator. This completely expresses the meaning of a Father class object and a Mother class object to get a Baby class object, now we can easily use the "+" operator to add the Father class object and the Mother class object to get a Baby class object. It should be noted that here we only define the "+" operator of the Father class, so when using it for computation, only the objects of the Father class can be placed before "+, if you want the objects of the Mother class to be placed before "+", you also need to define the "+" operator of the Mother class.