<C++> Classes and objects (medium) - default member functions of classes

1. The default member function of the class

Default member function: The member function generated by the compiler without explicit implementation by the user is called the default member function.

If there are no members in a class, it is simply called an empty class.

Is there really nothing in the empty class? No, when any class does not write anything, the compiler will automatically generate the following default member functions.

  1. Default constructor : If no constructor is explicitly defined for a class, the compiler generates a default constructor. This constructor has no parameters and is used to perform the necessary initialization operations when creating an object.
  2. Copy constructor : If no copy constructor is defined for the class, the compiler will generate a default copy constructor. This constructor is used to copy the values ​​of an existing object into the new object when the object is initialized .
  3. Assignment overloaded operator : If no assignment overloaded operator is defined for a class, the compiler generates a default copy assignment operator. This operator is used to assign the value of an existing object to another existing object.
  4. Destructor : If no destructor is defined for a class, the compiler generates a default destructor. The destructor is called when the object is destroyed to release the resources occupied by the object.

2. Constructor

2.1 The concept of constructor

For the following Date class:

#include <iostream>
using namespace std;
class Date {
    
    
public:
    void Init(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    void Print() {
    
    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();
    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();
    return 0;
}

For Dateclasses, you can set the date for the object through the Init public function, but if you call this method to set the information every time the object is created, it is a bit troublesome. Can you set the information when the object is created?

The answer is to use constructors.

The constructor is a special member function with the same name as the class name, which is automatically called by the compiler when creating a class type object to ensure that each data member has a suitable initial value, and is called only once in the entire life cycle of the object .

2.2 Features of the constructor

The constructor is a special member function. It should be noted that although the name of the constructor is called construction, the main task of the constructor is not to open space to create objects, but to initialize objects .

Its characteristics are as follows:

1. The function name is the same as the class name.

2. No return value

3. The compiler automatically calls the corresponding constructor when the object is instantiated . That is to say, when the object is created, the member variables are initialized.

4. The constructor can be overloaded. (A class can have multiple constructors, that is, multiple initialization methods)

Example:

#include <iostream>
using namespace std;
class Date {
    
    
public:
    Date() {
    
    
        _year = 1;
        _month = 1;
        _day = 1;
    }

    Date(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    
    void Print() {
    
    
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    Date d1;
    Date d2(2023, 2, 3); 
	
    d1.Print();
    d2.Print();   
	
    Date d3();
    //注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

    return 0;
}

5. Both the parameterless constructor and the full default constructor are called default constructors , and there can only be one default constructor . Note: No-argument constructors, full default constructors, and constructors that we did not write to be generated by the compiler by default can all be considered default constructors.

Why can there only be one default constructor? Because ambiguity occurs when calling

The constructor can be called without passing parameters. It is generally recommended that each class provide a default constructor

class Date {
    
    
public:
    Date() {
    
    
        _year = 1;
        _month = 1;
        _day = 1;
    }
	
    //全缺省构造函数
    Date(int year = 1, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    
    void Print() {
    
    
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

A full default constructor cannot coexist with a no-argument constructor - ambiguity arises, the compiler doesn't know which to call

insert image description here

6. If there is no constructor explicitly defined in the class, the C++ compiler will automatically generate a default constructor with no parameters. Once the user explicitly defines the compiler, it will no longer generate it.

#include <iostream>
using namespace std;
class Date {
    
    
public:
    // 如果用户显式定义了构造函数,编译器将不再生成
    // Date(int year, int month, int day) {
    
    
    //     _year = year;
    //     _month = month;
    //     _day = day;
    // }

    void Print() {
    
    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
    // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
    Date d1;
    d1.Print();
    return 0;
}

But we found that the output is a random value?

insert image description here

Many people have doubts about the default member functions generated by the compiler: if the constructor is not implemented, the compiler will generate a default constructor. But it seems that the default constructor is useless? The d object calls the default constructor generated by the compiler, but the value of the d object is still a random value. In other words , the default constructor generated by the compiler is useless here ?

Answer: C++ divides types into built-in types (basic types) and custom types. The built-in type is the data type provided by the language, such as: int/char..., the custom type is the type we define ourselves using class/struct/union, etc. If you look at the following program, you will find that the default constructor generated by the compiler will be Its default member function called on the custom type member _t.

C++ specifies the default generated constructor:

1. The built-in type members are not processed.
2. The members of the custom type will call the constructor of the class of the custom type.
Note: In C++11, a patch has been applied for the defect that the built-in type members are not initialized, that is, the built-in type member variables can be given a default value when declared in the class.

Scenarios for the default constructor:

#include <iostream>
using namespace std;

class Time {
    
    
public:
    Time() {
    
    
        cout << "Time()" << endl;
        _hour = 0;
        _minute = 0;
        _second = 0;
    }

private:
    int _hour;
    int _minute;
    int _second;
};

class Date {
    
    
private:
    // 基本类型(内置类型)
    int _year;
    int _month;
    int _day;
    // 自定义类型
    Time _t;
};

int main() {
    
    
    Date d;
    return 0;
}

insert image description here

It can be found that the default constructor generated by the d object calls the constructor of the custom type _t

Notice:

In C++11, a patch has been applied for the defect that the built-in type members are not initialized, that is, the built-in type member variables can be given default values ​​when they are declared in the class .

Example:

class Date {
    
    
private:
    // 基本类型(内置类型)
    int _year = 2023;
    int _month = 8;
    int _day = 4;
    // 自定义类型
    Time _t;
};

2.3 Initialization List

When creating an object, the compiler calls the constructor to give each member variable in the object an appropriate initial value.

But it cannot be called the initialization of the member variables in the object, and the statement in the constructor body can only be called the initial value assignment , not the initialization. Because the initialization can only be initialized once, and the constructor body can be assigned multiple times .

In terms of efficiency, initialization lists are usually more efficient than initializing member variables in the constructor body, because they initialize member variables at the same time as object creation, instead of creating objects first and then assigning member variables.

Initialization list : Starts with a colon , followed by a comma-separated list of data members , each " member variable " followed by an initial value or expression enclosed in parentheses .

class Date {
    
    
public:
    Date(int year, int month, int day)
        //初始化列表
        : _year(year), _month(month), _day(day) {
    
    }

private:
    int _year;
    int _month;
    int _day;
};

Notice:

1. Each member variable can only appear once in the initialization list (initialization can only be initialized once)

2. The class contains the following members, which must be placed in the initialization list for initialization:

  • reference member variable
  • const member variable
  • A custom type member (and the class has no default constructor)
class A {
    
    
public:
    A(int a)
        : _a(a) {
    
    }

private:
    int _a;
};

class B {
    
    
public:
    B(int a, int ref)
        : _aobj(a), _ref(ref), _n(10) {
    
    }

private:
    A _aobj;     // 没有默认构造函数
    int &_ref;   // 引用
    const int _n;// const
};

3. Try to use the initialization list to initialize, because no matter whether you use the initialization list or not, for custom type member variables, you must first use the initialization list to initialize.

class Time {
    
    
public:
    Time(int hour = 0)
        : _hour(hour) {
    
    
        cout << "Time()" << endl;
    }

private:
    int _hour;
};

class Date {
    
    
public:
    Date(int day) {
    
    }

private:
    int _day;
    Time _t;   //调用Time()的构造函数
};

int main() {
    
    
    Date d(1);
}

4. The initialization order in the initialization list is consistent with the declaration order of the member variables in the class, and has nothing to do with the order of the initialization list

#include <iostream>
using namespace std;

class A {
    
    
public:
    A(int a)
        : _a1(a), _a2(_a1) {
    
    
    }

    void Print() {
    
    
        cout << _a1 << " " << _a2 << endl;
    }

private:
    int _a2;   //随机值
    int _a1;   //随机值
};

int main() {
    
    
    A aa(1);
    aa.Print();
}

a2 is initialized first, a1 calls the initialization list at the time of initialization, and a1 is a random value at this time, so a2 is a random value after a2(a1)

2.4 explicit

explicitIs a keyword, usually used to modify the single-argument constructor , its purpose is to prevent implicit type conversion . It affects whether the compiler performs implicit type conversions when calling constructors.

When the constructor of a class has only one parameter and is not explicitmodified with the keyword, the compiler will automatically execute the constructor if necessary, convert the parameter type to the class type, and create a temporary object.

Decorating constructors with explicitkeywords can prevent this implicit type conversion, thereby preventing some unexpected behaviors and potential errors. This is useful to avoid unnecessary automatic type conversions by the compiler, which can sometimes lead to ambiguous code behavior.

Example 1:

class Date {
    
    
public:
    //1.单参构造函数,没有使用explicit修饰,具有类型转换作用
    /*Date(int year) : _year(year) {}*/

    // 2,虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用 - int转换为Date类
    Date(int year, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {
    
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    Date d1(2022);

    d1 = 2023;  //用一个整型变量给日期类型对象赋值
    //实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
    return 0;
}

Example 2:

Use of explicitforbidden type conversions

class Date {
    
    
public:
    // explicit修饰构造函数,禁止类型转换
    explicit Date(int year, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {
    
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    Date d1(2022);

    d1 = 2023;   //err 编译器报错,没有与这些操作数匹配的 "=" 运算符,操作数类型为:  Date = int
    return 0;
}

Example 3:

class MyClass {
    
    
public:
    explicit MyClass(int value) : val(value) {
    
    }

private:
    int val;
};

void func(MyClass obj) {
    
    
    // ...
}

int main() {
    
    
    MyClass obj1 = 42;  // 编译错误,因为构造函数是 explicit 的,禁止隐式类型转换
    MyClass obj2(42);   // 正确,显式调用构造函数
    func(42);           // 编译错误,因为构造函数是 explicit 的,禁止隐式类型转换
    func(obj2);         // 正确,调用 func 时显式地传递 MyClass 对象
    return 0;
}

Summary: Using explicitthe keyword can help avoid implicit type conversions, thereby improving code clarity and reliability.

3. Destructor

A destructor is a special member function that performs the necessary cleanup and resource release operations when an object is destroyed. It is the opposite of a constructor, which is called when an object is created, whereas a destructor is called automatically when an object is destroyed . The destructor has the same name as the class name, preceded by a tilde ~.

The main purpose of the destructor is to perform resource cleanup at the end of the object's lifetime, such as freeing dynamically allocated memory, closing files, releasing other resources, and so on. In C++, destructors ensure resource management on object destruction to prevent memory leaks and resource leaks.

The characteristics of the destructor:

  1. The destructor name is prefixed with the character ~ before the class name.
  2. No parameters and no return type.
  3. A class can have only one destructor. If not explicitly defined, the system will automatically generate a default destructor. Note: Destructors cannot be overloaded
  4. When the life cycle of the object ends, the C++ compilation system automatically calls the destructor.
  5. The ones constructed first are destructed, and the ones constructed later are destructed first , because the object is defined in the function, and the function call will create a stack frame, and the object construction and destruction in the stack frame must also conform to the principle of first in, last out.

Does something get done with regards to the destructors automatically generated by the compiler? In the following program, we will see that the default destructor generated by the compiler calls its destructor for the custom type member.

#include <iostream>
using namespace std;
class Time {
    
    
public:
    ~Time() {
    
    
        cout << "~Time()" << endl;
    }

private:
    int _hour;
    int _minute;
    int _second;
};

class Date {
    
    
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;   //释放Date的时候调用_t的析构函数
};

int main() {
    
    
    Date d;
    return 0;
}

Output result:

~Time()

If there is no resource application in the class, the destructor can not be written, and the default destructor generated by the compiler is used directly, such as the Date class; when there is a resource application, it must be written, otherwise it will cause resource leakage, such as the Stack class.

4. Copy constructor

A copy constructor is a special constructor used to create an object that is a copy of an existing object. It is usually used to create new objects that have the same values ​​as existing objects during object initialization and in situations such as function passing parameters. The copy constructor takes one parameter, a reference to the object to be copied.

The characteristics of the copy function are as follows:

1. The copy constructor is an overloaded form of the constructor.

2. The parameter of the copy constructor is only one and must be a reference to a class type object, and the compiler will report an error directly if the method of passing by value is used, because it will cause infinite recursive calls.

3. If not explicitly defined, the compiler will generate a default copy constructor. The default copy constructor object is copied in byte order according to memory storage. This kind of copy is called shallow copy , or value copy

Why does it cause infinite recursion?

Assuming the parameter of the copy constructor is a non-reference type:

class MyClass {
    
    
public:
    MyClass(MyClass other) {
    
      // 这里不是引用类型
        // ...
    }
};

When the copy constructor is called, it takes an object as a parameter. If the parameter is a non-reference type, then the parameter object passed to the copy constructor will create a temporary copy of the object through the copy constructor. But this will cause an infinite loop, because in the process of creating a temporary object, the copy constructor will be called to create another temporary object, and so on, resulting in infinite recursion.

Using parameters of reference type avoids this problem. When the argument is a reference type, what is passed to the copy constructor is a reference to the original object itself, not the temporary object created. This avoids an infinite loop situation.

Correct example:

#include <iostream>
using namespace std;
//正确的写法
class Date {
    
    
public:
    Date(int year = 2023, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date &d) {
    
    
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    void Print() {
    
    
        cout << _year << "/" << _month << "/" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    
    
    Date d1(2021, 2, 3);
    Date d2(d1);
    d2.Print();
    return 0;
}

Output result:

2021/2/3

4.1 Shallow copy and deep copy

Shallow copy refers to only copying the value of member variables of the object , including pointer variables. In a shallow copy, the copied object shares the same resources as the original object , which can lead to unintended side effects. If the resource is released or modified, both objects are affected.

The default copy constructor generated by the compiler can already copy byte-ordered values. Do I need to explicitly implement it myself? Of course classes like the Date class are unnecessary. What about the following classes? Try to verify it?

#include <iostream>
using namespace std;

//自动生成构造拷贝函数对自定义类型进行拷贝
typedef int DataType;
class Stack {
    
    
public:
    Stack(size_t capacity = 10) {
    
    
        cout << "Stack(size_t capacity = 10)" << endl;

        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array) {
    
    
            perror("malloc申请空间失败");
            exit(-1);
        }

        _size = 0;
        _capacity = capacity;
    }

    void Push(const DataType& data) {
    
    
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }

    ~Stack() {
    
    
        cout << "~Stack()" << endl;

        if (_array) {
    
    
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    DataType* _array; 
    size_t _size; 
    size_t _capacity; 
};

int main() {
    
    
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    st1.Push(3);

    Stack st2(st1);
    return 0;
}

insert image description here

It can be found that the program crashes directly. What is the reason?
insert image description here

Notice:

If resource application is not involved in the class, the copy constructor can be written or not; once resource application is involved, the copy constructor must be written, and it must be a deep copy!

Deep copy means that when an object is copied, not only the value of the member variable of the object is copied, but also the resource itself pointed to by the pointer is copied. This way the new object is completely independent from the original object, and modifications to one object will not affect the other.

Example:

#include <iostream>
using namespace std;

typedef int DataType;
class Stack {
    
    
public:
    Stack(size_t capacity = 10) {
    
    
        cout << "Stack(size_t capacity = 10)" << endl;

        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array) {
    
    
            perror("malloc申请空间失败");
            exit(-1);
        }

        _size = 0;
        _capacity = capacity;
    }

    void Push(const DataType& data) {
    
    
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }

    //stack类的拷贝构造深拷贝
    Stack(const Stack& st) {
    
    
        cout << "Stack(const Stack& st)" << endl;
        //深拷贝开额外空间,为了避免指向同一空间
        _array = (DataType*)malloc(sizeof(DataType) * st._capacity);
        if (nullptr == _array) {
    
    
            perror("malloc申请空间失败");
            exit(-1);
        }
        //进行字节拷贝
        memcpy(_array, st._array, sizeof(DataType) * st._size);
        _size = st._size;
        _capacity = st._capacity;
    }

    ~Stack() {
    
    
        cout << "~Stack()" << endl;

        if (_array) {
    
    
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    DataType* _array;
    size_t _size;
    size_t _capacity;
};

class MyQueue {
    
    
public:
    //MyQueue什么都不写,会调用默认的构造函数,也就是Stack类的构造函数
    // 默认生成构造
    // 默认生成析构
    // 默认生成拷贝构造

private:
    //默认构造函数初始化 - 默认析构函数
    Stack _pushST;
    //默认构造函数初始化 - 默认析构函数
    Stack _popST;
    int _size = 0;
};

int main() {
    
    
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    st1.Push(4);

    Stack st2(st1);
    cout << "=============================" << endl;

    MyQueue q1;
    //q1拷贝q2  q1中有两个Stack类和一个size,size直接拷贝,stack类是调用stack拷贝构造进行拷贝
    MyQueue q2(q1);

    return 0;
}

4.2 Typical calling scenarios of copy constructor:

a. Create a new object using an existing object

b. The function parameter type is a class type object

c. The return value type of the function is a class type object

Example:

#include <iostream>
using namespace std;
class Date {
    
    
public:
    Date(int year, int minute, int day) {
    
    
        cout << "Date(int,int,int):" << this << endl;
    }

    Date(const Date &d) {
    
    
        cout << "Date(const Date& d):" << this << endl;
    }
    
    ~Date() {
    
    
        cout << "~Date():" << this << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

Date Test(Date d) {
    
    
    Date temp(d);
    return temp;
}

int main() {
    
    
    Date d1(2022, 1, 13);
    Test(d1);
    return 0;
}

5. Assignment overloading

By default, C++ generates default assignment operators for classes, but if the class contains pointers or resources, custom assignment operators may be required to avoid shallow copy problems.

operator=To overload the assignment operator, you need to define a special member function called in the class .

Format:

Parameter type:const T& , passing references can improve the efficiency of parameter passing

Return value type : T&, return reference can improve the efficiency of return, and the purpose of return value is to support continuous assignment

Check if you assign a value to yourself

Return *this : to compound the meaning of continuous assignment

Example :

class Date {
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date &d) {
    
    
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    Date &operator=(const Date &d) {
    
    
        if (this != &d) {
    
    
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }

        return *this;
    }

private:
    int _year;
    int _month;
    int _day;
};

feature:

1. The assignment operator can only be overloaded as a member function of a class and cannot be overloaded as a global function

class Date {
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }
    int _year;
    int _month;
    int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date &operator=(Date &left, const Date &right) {
    
    
    if (&left != &right) {
    
    
        left._year = right._year;
        left._month = right._month;
        left._day = right._day;
    }
    return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

reason:

If the assignment operator is not explicitly implemented, the compiler will generate a default one. At this time, if the user implements a global assignment operator overload outside the class, it will conflict with the default assignment operator overload generated by the compiler in the class, so the assignment operator overload can only be a member function of the class.

3. When the user does not explicitly implement, the compiler will generate a default assignment operator overload, which is copied byte by byte in the form of value . Note: Built-in type member variables are directly assigned, while custom type member variables need to call the assignment operator overload of the corresponding class to complete the assignment.

Now that the default assignment operator overloading function generated by the compiler can already complete byte-ordered value copying, do I still need to implement it myself?

This problem is the same as the copy constructor, which involves deep copying.

If resource management is not involved in the class, it doesn't matter whether the assignment operator is implemented; once resource management is involved, it must be implemented.

Guess you like

Origin blog.csdn.net/ikun66666/article/details/132199905