C + + Learning note 5: constructors and destructors

Constructor

Definition: the name is the same as the class name, with parameters and return values( void Not too)
//Function: initializes objects, such as assigning initial values to member variables
//Nature:
     (1)If a class is defined without a constructor, the compiler generates a default parameterless constructor that does nothing
     (2)If a constructor is defined, the compiler does not generate a default parameterless constructor
     (3)The constructor is called automatically when the object is generated. Once the object is generated, the constructor can no longer be executed
     (4)A class can have multiple constructors
//Why a constructor is needed:
    (1)Perform the necessary initialization
    (2)Sometimes objects are used without being initialized, which can lead to program errors.

eg:1 uses default constructor 
    class Complex{
        private:
            double real, imag;
        public:
            void Set(double r, double i);
    };  // Compiler generates default constructor automatically
    Complex c1; // Default constructor called
    Complex* pc = new Complex;  // Default constructor called

eg:2 Use a custom constructor
    class Complex{
        private:
            double real, imag;
        public:
            Complex(double r, double i = 0);    // Custom constructor
    };  // Compiler generates default constructor automatically

    Complex::Complex(double r, double i)
    {
        real = i; imag = r;
    }

    Complex c1; // error missing argument to constructor
    Complex* pc = new Complex;  // error, no parameters
    Complex c1(2);  // ok the second parameter can be defaulted
    Complex c1(2, 4), c2(3, 5);
    Complex* pc = new Complex(3, 4);

eg:3 There can be multiple constructors, and the number or type of parameters are different (see this project Complex Class)
//The use of constructors in arrays
    class Test
    {
        public:
            Test(int n){}   // (1)
            Test(int n, int m){}    // (2)
            Test(){}    // (3)
    };

    Test array1[3] = {1, Test(1, 2)};
    // Three elements are initialized with (1) (2) (3)

    Test array2[3] = {Test(2, 3), Test(1, 2), 1};
    // Three elements are initialized with (2) (2) (1)

    Test* pArray[3] = {new Test(4), new Test(1, 2)};
    // Two elements are initialized with (1) (2)

copy constructor

1 Definition: there is only one parameter, that is, reference to similar objects

2 Format: X::X(X&) perhaps X::X(const X&)  The latter takes constant objects as parameters

3 If no copy constructor is defined, the compiler automatically generates the default copy constructor. The default copy constructor completes the assignment function.

4 eg:
    class Complex{
        private:
            double real, imag;
    };
    Complex c1; // Call the default parameterless constructor
    Complex c2(c1); // Call the default copy constructor to initialize c2 to be the same as c1.

5 If you define your own copy constructor, the default copy constructor does not exist
    class Complex{
        public:
            double real, imag;
        Complex(){}
        // Custom copy constructor
        Complex(const Complex& c){
            real = c.real;
            imag = c.imag;
            cout << "Copy Constructor called";
        }
    };
    Complex c1;
    Complex c2(c1);

6 It is not allowed to be tangible X::X(X)Constructor for
    class CSample{
        CSample(CSample c){}    // Error, this is not a copy constructor
    }

7 Three situations in which copy constructors work
    (1)When one object initializes another of the same kind
        Complex c2(c1);
        Complex c2 = c1;    // Initialization statement, non assignment statement
    (2)If a function has a parameter that is a class A When the function is called, the A The copy constructor of will be called
        eg:
            class A
            {
                public:
                    A(){};
                    A(A& a){
                        cout << "Copy constructor called" << endl;
                    }
            };
            void Func(A a1);
            int main(){
                A a2;
                Func(a2);   // Call copy constructor initialization
                return 0;
            }
    (3)If the return value of a function is a class A When the function returns, A Copy constructor of is called
        eg:
            class A
            {
                public:
                    int v;
                    A(int n) {v = n;}
                    A(const A& a){
                        v = a.v;
                        cout << "Copy constructor called";
                    }
            }
            A Func(){
                A b(4);
                return b;
            }
            int main(){
                // Call the copy constructor, because the return value of Func() function is B, the parameter of copy constructor is b
                cout << Func().v << endl; return 0;
            }

8 assignment among objects does not cause the copy constructor to be called (see cmyclass.cpp of this project)

//
// Assignment between presentation objects does not cause the copy constructor to be called
//

#include <iostream>

using namespace std;

class CMyclass{
public:
    int n;
    CMyclass()// Default constructor 
    {
        cout << "Default constructor called" << endl;
    }
    CMyclass(CMyclass& c)   // copy constructor 
    {
        n = 2 * c.n;
        cout << "Copy constructor called"<< endl;
    }
};

int main()
{
    CMyclass c1, c2;
    c1.n = 5;
    c2 = c1;    // Call default constructor on behalf of assignment
    CMyclass c4 = c1;   // Delegate initialization requires a call to the copy constructor
    CMyclass c3(c1);
    cout << c2.n << endl;
    cout << c3.n << endl;
    cout << c4.n << endl;
}
9 Use of constant reference parameters
    eg:
    void func(CMyclass obj_){
        cout << "fun" << endl;
    }
    (1)In such a function, generating parameters when calling will cause a copy constructor call, which is expensive
    (2)Consider using CMclass&Reference type as parameter
    (3)If you want to ensure that the value of an argument is not changed in a function, you can add const Keyword
    eg:
        void func(cosnt CMyclass& obj)
        {
            // Any statement in the function that attempts to change the obj value will be illegal
        }

type conversion constructor

type conversion constructor
1 Purpose: to realize automatic conversion of types
Definition: it has only one parameter and is not the constructor of the copy constructor. It can be regarded as the conversion constructor‘
3 call: when necessary, the compiler will automatically call the conversion constructor to create an anonymous temporary object (or temporary variable)
4 examples (see switchConstructor.cpp of the project)

//
// Type conversion constructor example
//

#include <iostream>

using namespace std;

class Complex{
public:
    double real, imag;
    Complex(int i){
        cout << "Type conversion constructor called" << endl;
        real = i; imag = 0;
    }
    Complex(double r, double i){
        real = r;
        imag = i;
        cout << "Constructor called"<< endl;
    }
};

int main()
{
    Complex c1(7, 8);   // Call constructor
    Complex c2 = 12;    // Call type conversion constructor
    c1 = 9;             // Due to the existence of the type conversion constructor, 9 is automatically converted to a temporary Complex object, which is then assigned to c1
    cout << c1.real << "," << c1.imag << endl;
    cout << c2.real << "," << c2.imag << endl;
    return 0;
}

Destructor

1 Definition: the name is the same as the class name. Add "~" before it. There is no parameter and return value. A class can only have one destructor at most.
2 purpose: the destructor object is called automatically when it dies. Destructors can be defined to do some work before the objects die, such as releasing the allocated space.
3 if no destructor is written when defining a class, the compiler will automatically generate a default destructor, which does nothing.
4 if a destructor is defined, the compiler does not generate a default destructor.
5 eg: (see xigou.cpp of the project)

//
// Destructor use case
//

#include <iostream>

using namespace std;

class String{
private:
    char* p;
public:
    // Constructor
    String(){
        p = new char[10];
        cout << "Constructor called" << endl;
    }
    // Destructor
    ~String(){
        delete[] p;
        cout << "Destructor called" << endl;
    }
};

int main()
{

}

6 at the end of the life cycle, the destructor of each element of the object array is called.
7 delete operator causes destructor to be called
eg:
Ctest* pTest;
pTest = new Ctest; / / constructor called
delete pTest; / / destructor called
------------------------------------
pTest = new Ctest[3]; / / constructor called 3 times
delete[] pTest; / / destructor calls 3 times
8 if new is an array of objects, then [] should be written when releasing with delete. Otherwise, only one object is deleted (destructor is called once)
9 difficulties: destructor is called after the object is returned as function return value (see xigoudiaoyong.cpp of this project)

//
// Destructor is called after an object is returned as a function return value
//
#include <iostream>

using namespace std;

class Myclass{
public:
    ~Myclass(){
        cout << "Call destructor" << endl;
    }
};


Myclass obj;

Myclass fun(Myclass sobj)    // The death of parameter objects also causes destructors to be called
{
    return sobj;    // Generate temporary object return on function call return
}

int main()
{
    obj = fun(obj); // After the return value (temporary object) of the function call is used, the temporary object destructor is called
    return 0;
}

/**
 * Function call analysis:
 * First, call fun, enter the fun function, and a parameter object sobj will be generated, which is initialized with the copy constructor
 * Then return sobj. When a function returns, all local variables and parameters in the function will die, so sobj dies, and a destructor is called.
 * Continue to generate a return value object, which is a temporary object initialized with a copy constructor
 * Then assign the value of the temporary object to obj. The temporary object usually dies after the statement containing the temporary object is executed, so obj = fun(obj) is executed. After the statement is executed, the temporary object dies, and the destructor is called
 * After the end of the whole program, the global object will die automatically and the destructor will be called
 * */

Constructor and destructor call timing

1 note: the constructor is only used for initialization, not for allocating storage space. Destructor is not responsible for reclaiming the storage space occupied by the whole object, it only does the aftermath work before the object storage space is recycled by the operating system.
2. Copy the performance of constructors in different compilers (not important, just understand)
3. For the new object, only delete it will die. If you do not delete it, it will not die. Even if the whole program is over, it will not die.
4 the following is an example

//
// Constructor and destructor call timing cases
//
#include <iostream>

using namespace std;

class Demo{
    int id;
public:
    // type conversion constructor 
    Demo(int i){
        id = i;
        cout << "id = " << id << " constructed" << endl;
    }
    // Destructor
    ~Demo(){
        cout << "id = " << id << " destructed" << endl;
    }

};

Demo d1(1); // First output

void Func(){
    static Demo d2(2);  // Static local variable, calling constructor for initialization
    Demo d3(3); // Generated object
    cout << "func" << endl;
}   // At the end of the function, d2 will not die (reason: static local variable will not die at the end of the function, and it will maintain its original value, until the end of the whole program, static local variable will die), d3 will die

int main()
{
    Demo d4(4); // Second output
    d4 = 6; // Because of the existence of the type conversion constructor, you can directly convert the shape into a temporary object, and then assign the value of the temporary object to d4. After the temporary object executes this statement, it will die, causing the destructor call
    cout << "main" << endl;
    {
        Demo d5(5); // Local object generation call constructor
    }   // Curly braces point to end local object death call destructor
    Func(); // Enter Func
    cout << "main ends" << endl;
    return 0;
}   // At the end of the function, the local variable d4 dies, and the value of d4 is 6
// At the end of the whole function, the global variable d1 and the static variable d2 die out, and the order of die out (first constructed and then destructed) is d2 die out, and then d1 die out

Published 7 original articles, won praise 0, visited 53
Private letter follow

Posted on Sat, 01 Feb 2020 00:07:02 -0800 by chuckym7