[C++] Classes and objects (2) Constructor destructor copy function

foreword

In the previous chapter, we have introduced the basic knowledge of classes and objects, including the concept and definition of classes, as well as class access qualifiers, class instantiation, class size calculation, and the this pointer that C language must pass ( We don’t need to pass it in C++, the compiler will automatically implement it for us)

but these are not enough. Classes are the most important thing in object-oriented programming languages. We should continue to understand them in depth. The first three of the six default member functions of .


1. The 6 default member functions of the class

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 6 default member functions.

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

empty class:

class Date
{
    
    

};

insert image description here

Second, the constructor

1. Introduction

For the usage scenario of the constructor, let's first look at a simple piece of code:

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int DataType;
class Stack
{
    
    
public:
	void Init(int _capacity = 4)//缺省参数
	{
    
    
		DataType* tmp = (DataType*)malloc(sizeof(DataType) * _capacity);
		if (nullptr == tmp)
		{
    
    
			perror("malloc fail:");
			exit(-1);
		}
		_a = tmp;
		_Top = 0;
		_capacity = _capacity;
	}
	void Push(int num)
	{
    
    
		//判断是否应该扩容
		if (_Top - 1 == _capacity)
		{
    
    
			_capacity *= 2;
			DataType* tmp = (DataType*)realloc(_a,sizeof(DataType) * _capacity);
			if (nullptr == tmp)
			{
    
    
				perror("malloc fail:");
				exit(-1);
			}
			_a = tmp;
		}
		_a[_Top] = num;
		_Top++;
	}
private:
	DataType* _a;
	int _Top;
	int _capacity;
};
int main()
{
    
    
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	return 0;
}

It crashed after running, thinking about why?
insert image description here
The answer is: we didn't initialize our stack, we didn't give space, and naturally we couldn't insert data! This is normal. There are many examples of such uninitialized and then crashed. We have to initialize every time we want to use the stack, which makes us very uncomfortable. Can we automatically initialize it for us when we create an object? ? The answer is yes, that is the constructor !

2. Concept

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 .

3. Characteristics

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. Returns no value and does not allow us to write return values.
  3. The compiler automatically calls the corresponding constructor when the object is instantiated.
  4. Constructors can be overloaded.
  5. If there is no constructor explicitly defined in the class (in plain English: write the constructor yourself), the C++ compiler will automatically generate a default constructor with no parameters, and once the user explicitly defines the compiler, it will no longer generate it.

As we said earlier, when we don’t write anything in the class, the compiler will automatically generate a constructor for us. Of course, the constructor automatically generated by the compiler may not be what we want. We can also write the constructor ourselves. When If we write the constructor ourselves, the compiler will no longer generate the constructor for us.

So let's modify the above code

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int DataType;
class Stack
{
    
    
public:
	Stack(int capacity = 4)//缺省参数,此类构造函数可以传也可以不传递形参
	{
    
    
		DataType* tmp = (DataType*)malloc(sizeof(DataType) * capacity);
		if (nullptr == tmp)
		{
    
    
			perror("malloc fail:");
			exit(-1);
		}
		_a = tmp;
		_Top = 0;
		_capacity = capacity;
	}
	void Push(int num)
	{
    
    
		//判断是否应该扩容
		if (_Top - 1 == _capacity)
		{
    
    
			_capacity *= 2;
			DataType* tmp = (DataType*)realloc(_a, sizeof(DataType) * _capacity);
			if (nullptr == tmp)
			{
    
    
				perror("malloc fail:");
				exit(-1);
			}
			_a = tmp;
		}
		_a[_Top] = num;
		_Top++;
	}
private:
	DataType* _a;
	int _Top;
	int _capacity;
};
int main()
{
    
    
	Stack s1(20);//此处不是函数调用,而是类的实例化顺便给构造函数传参数
	//Stack s1;    //如果是这样则会采用缺省值,即默认开辟4个int类型的空间大小。
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	s1.Push(5);
	s1.Push(6);
	s1.Push(7);
	return 0;
}


insert image description here

insert image description here
The code runs successfully, indicating that the compiler automatically calls Stackthe function we wrote for us


Let's look at the constructor of a class

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date()//无参数的构造函数
	{
    
    
	}
	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();//报错,错误的调用无参构造函数,会被识别为函数声明!!!
	//warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
	Date d2;//正确的调用无参构造函数
	Date d3(2023,2,10);//正确的调用必须传参的构造函数
};

Note: If an object is created through a no-argument constructor, parentheses are not required after the object, otherwise it becomes a function declaration

After reading the constructors that need to pass parameters and the constructors that don’t need to pass parameters, let’s take a look at the constructors implemented by the compiler itself

//不写构造函数
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1;
	d1.Print();
	return 0;
};

insert image description here
The answer is very strange! Doesn't it mean that when we don't write a constructor, the compiler will generate a constructor by itself? And the role of the constructor is to give the object a reasonable initialization value? Why is the printed result a random value? The constructor generated by the system seems to be useless? ? ?

The answer is: C++ divides types into built-in types (basic types) and custom types . Built-in types are the data types provided by the language, such as: int/char..., pointers, and custom types are the types we define ourselves using class/struct/union. The following rules apply to compiler-generated default constructors:

For default generated constructors:

  1. Members of built-in types are not processed
  2. For a member of a custom type, its default constructor will be called.
    (The default constructor includes: all default constructors, constructors that do not pass parameters, and default constructors generated by the system)

For the above class, since the members in the class are all built-in types , the default constructor generated by the compiler according to the above rules does not process the built-in types, so what we see is still a random value.

Let's look at the following code to help you understand this rule

#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

After reading this example, I believe you have a good understanding of this rule, but we still have a question? What if we just want the built-in type to be initialized with the custom type?

6. In C++11, a patch has been applied to address the defect of non-initialization of built-in type members, that is, built-in type member variables can be given default values ​​when declared in a class. (similar to default parameters)

#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 = 2023;   //给默认值
	int _month = 2;    //给默认值
	int _day = 1;     //给默认值
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d;
	return 0;
}

insert image description here
7. Both the parameterless constructor and the 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.

class Date
{
    
    
public:
Date()
{
    
    
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)
{
    
    
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
// 以下测试函数能通过编译吗?
int main()
{
    
    
	Date d1;
	return 0;
}

The answer is no, no parameters are passed, the function does not know whether to call a function with no parameters or a function with all defaults

3. Destructor

1. Concept

Through the study of the previous constructor, we know how an object came about, and how did that object disappear?
Destructor: Contrary to the function of the constructor, the destructor does not complete the destruction of the object itself, and the local object destruction is done by the compiler. When the object is destroyed, it will automatically call the destructor to complete the cleanup of resources in the object.

2. Characteristics

A destructor is a special member function whose characteristics are as follows:

  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.

Example code:

#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
    
    
public:
	Stack(int capacity = 3)
	{
    
    
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
    
    
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	// 析构函数
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
		cout << "~Stack()" << endl;
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
    
    
	Stack s;
	s.Push(1);
	s.Push(2);
}

insert image description here
5. Regarding the destructor automatically generated by the compiler, will something be done? In the following program, we will see that the default destructor generated by the compiler calls its destructor for the custom type members.

#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;
};
int main()
{
    
    
	Date d;
	return 0;
}

insert image description here

After the program runs, the output: ~Time()
does not directly create an object of the Time class in the main method at all, why does it call the destructor of the Time class at the end?
Because: the Date object d is created in the main method, and d contains 4 member variables, among which _year, month, and day are built-in type members, and resource cleaning is not required when destroying. Finally, the system can directly reclaim its memory ;

and _t is an object of the Time class, so when d is destroyed, the _t object of the Time class contained in it must be destroyed, so the destructor of the Time class must be called.

However: the destructor of the Time class cannot be called directly in the main function. What actually needs to be released is the Date class object, so the compiler will call the destructor of the Date class. If Date is not explicitly provided, the compiler will give the Date class Generate a default destructor, the purpose is to call the destructor of the Time class inside it, that is, when the Date object is destroyed, it is necessary to ensure that each custom object inside it can be destroyed correctly.

The main function does not directly call the Time class destructor, but explicitly calls the default destructor generated by the compiler for the Date class.

Note: Create an object of which class to call the destructor of that class, and destroy an object of that class to call the destructor of that class

Fourth, the copy constructor

1. Concept

When we use classes to create objects, copy behavior will inevitably occur. For example, when creating objects, can we create a new object that is the same as an existing object?

Copy constructor: There is only a single formal parameter, which is a reference to the object of this class type (usually const decoration), which is automatically called by the compiler when creating a new object with an existing class type object.

a. Why do we need copy construction?

When the C/C++ compiler copies variables, it is not a simple matter. For built-in types, the C/C++ compiler can copy them by itself (copies byte by byte). For custom types, the C/C++ compiler It cannot be copied, only through the copy function.

insert image description here

(The copy of the stack needs to use the copy function for deep copy!!!)

2. Features

The copy constructor is also a special member function with the following characteristics:

  1. The copy constructor is an overloaded form of the constructor.
//拷贝构造函数
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//Date(const Date d) // 错误写法:编译报错,会引发无穷递归
	Date(const Date& d) // 正确写法
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1;
	Date d2(d1);//利用拷贝构造创建一个与d1相同的d2
	//Date d2 = d1;//与上一行的意思一致,要调用拷贝构造
	return 0;
}
  1. The parameter of the copy constructor is only one and must be a reference to a class type object , and the compiler will directly report an error if the value-passing method is used, because it will cause infinite recursive calls.

When we want to create a d2 with the same data as d1, we need to call the copy function.
① To call the copy function, we need to pass parameters because the parameters are formal parameters, and the formal parameters are a temporary copy of the actual parameters.
②So we have to call the copy function again. When calling the copy function, we need to pass parameters because the parameters are formal parameters, and the formal parameters are a temporary copy of the actual parameters.
③So we have to call the copy function again. When calling the copy function, we need to pass parameters because the parameters are formal parameters, and the formal parameters are a temporary copy of the actual parameters.
④So we have to call the copy function again. When calling the copy function, we need to pass parameters because the parameters are formal parameters, and the formal parameters are a temporary copy of the actual parameters.
... ...
...

Logic diagram:
insert image description here

Another question is why do we add the parameters of the copy construction const?
The answer is: I am afraid that we will copy it backwards!

insert image description here

insert image description here

insert image description here

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 .

//默认生成的拷贝构造函数
#include<iostream>
using namespace std;
class Time
{
    
    
public:
	Time()
	{
    
    
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
    
    
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
    
    
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

insert image description here

Note: In the default copy constructor generated by the compiler, the built-in type is directly copied in byte mode, while the custom type is copied by calling its copy constructor.

4. If the resource application is not involved in the class, the copy constructor can be written or not; once the resource application is involved, the copy constructor must be written, otherwise it is a shallow copy.

Think about a question: the default copy constructor generated by the compiler can already copy the value of the byte order , do we still need to write the copy constructor for the built-in type? The answer is whether to write the copy structure, we still have to refer to the feature 4 of the copy structure, is there any support application involved! ! !

Let's look at the following piece of code:

#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
    
    
public:
	Stack(size_t capacity = 10)
	{
    
    
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
    
    
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
    
    
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
    
    
		if (_array)
		{
    
    
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
    
    
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	return 0;
}

insert image description here
The above code needs copy construction, otherwise the two stacks will affect each other! You can see that feature 4 of the copy structure is the basis for us to write or not to write the copy structure!

3. Typical calling scenarios of copy constructor

  1. Create a new object using an existing object
  2. The function parameter type is a class type object
  3. The return value type of the function is a class type object

Reminder: In order to improve the efficiency of the program, try to use the reference type when passing parameters to general objects, and use references as much as possible according to the actual scene when returning.

V. Conclusion

The content of this chapter is quite difficult for beginners, but these member functions are very important, so be sure to understand them well! After learning them, I believe your level will be further improved!

Guess you like

Origin blog.csdn.net/qq_65207641/article/details/128984828