Classes and objects - 6 default member functions of classes

If no functions are defined in a class, the compiler will automatically generate 6 default member functions.

  1. Constructor
  2. destructor
  3. copy constructor
  4. assignment bearer function
  5. Ordinary object take address
  6. Const object takes address

Constructor

In the previous study of data structures, we often need to call an initialization function to initialize the structure object. If we forget to initialize, an error will occur in the program, so in C++, the constructor is introduced

The constructor is a special member function. It should be noted that although the constructor is called "construction", its function is to initialize the object, not to open space to construct the object.

Constructor features:

  • The constructor name is the same as its class name
  • There is no return value, and there is no need to write void in the function type.
  • The compiler automatically calls the corresponding constructor when the object is instantiated.
  • Constructors can be overloaded, so there are multiple initialization methods

Let’s take a look at the parameterless constructor

class Date
{
    
    
public:
    //无参构造函数
	Date()
	{
    
    

	}

	//带参构造函数
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
    
    
	Date d1;  //调用无参构造函数
	Date d2(2000, 1, 1);//调用含参构造函数
	return 0;
}

One thing to note here is that there is no need to add parentheses when calling a parameterless constructor. If it is written Date d1();, it will be written as a function declaration.

and a constructor with default parameters in the stack class

class Stack
{
    
    

public:
	//带缺省的构造函数
	Stack(int capacity = 4)
	{
    
    
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
    
    
			perror("malloc fail");
			return;
		}

		_top = 0;
		_capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

If there is no explicitly defined constructor in the class, the compiler will automatically generate a parameterless default constructor. If the user explicitly defines a constructor, the compiler will not automatically generate a default constructor.

Regarding the constructor generated by the compiler by default, the built-in type will not be processed (some compilers will process it), and the custom type will use its own default
constructor .

Basic types/built-in types: int, char, double...and pointer types.
Custom types: class struct...

insert image description here

The figure below also shows that the constructor automatically generated by the compiler can handle custom types:
insert image description here

Therefore, under normal circumstances, if there are built-in type members, you have to write the constructor yourself, and you cannot use the one generated by the compiler. They are
all custom types, so you do not need to write a constructor and use the default constructor generated by the compiler itself.

To address the defect that the default constructor generated by the compiler cannot initialize basic types, it also needs to be compatible with previous operations.
In C++11, a patch has been made to address the defect that built-in type members are not initialized. Built-in introspection member variables are not initialized when they are declared. Can give default value

class Date
{
    
    
public:
	void Print() 
	{
    
    
		cout << _year << " - " << _month << " - " << _day << endl;
	}
	
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};

int main()
{
    
    
	Date d1;
	d1.Print();
	return 0;
}

The constructor generated by the compiler by default is called the default constructor. In fact, the parameterless constructor and the fully default constructor are also default constructors (the default constructor is called without passing parameters)

However, there is ambiguity when calling the parameterless constructor and the all-default constructor.

There can only be one default constructor, so we prefer the all-default constructor.


destructor

The destructor function is opposite to the constructor function. The destructor does not complete the destruction of the object itself . The local object destruction work is
completed by the compiler. When the object is destroyed, the destructor will be automatically called to complete the cleanup of resources in the object .

  • The destructor name is preceded by characters before the class name~
  • The destructor has no return value and is similar to
  • A class can only have one destructor, and destructors cannot be overloaded.
  • At the end of the object's life cycle, the C++ compiler automatically calls the destructor
  • Objects defined later are destructed first
class Stack
{
    
    
	
public:
	
	Stack(int capacity = 4)
	{
    
    
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
    
    
			perror("malloc fail");
			return;
		}
	
		_top = 0;
		_capacity = 0;
	}

	//析构函数
	~Stack()
	{
    
    
		if (_a)
		{
    
    
			free(_a);
			_a = nullptr;
			_top = 0;
			_capacity = 0;
		}
	}
	
private:
	int* _a;
	int _top;
	int _capacity;
};

When the user does not explicitly define a destructor, the compiler will generate a default destructor.

This default destructor does not process built-in types, only custom types will process it, and its destructor will be called.

Under normal circumstances, if there is a dynamic application for resources, you need to write a destructor explicitly. If there is
no dynamic application for resources, you do not need to write a destructor.
The members that need to release resources are all custom types, and the default destructor will call their own destructor, so No need to write a separate destructor


copy constructor

When creating an object, is it possible to create a new object that is the same as an existing object?

So there is a copy constructor

  • The copy constructor is an overloaded form of the constructor, so the copy constructor name is the class name

Let’s first write a copy constructor:

//错误写法
Date(Date d)
{
    
    
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

In fact, what is written above is wrong.

C++ stipulates that built-in types are copied directly, and custom types must call the copy constructor.
For custom type parameters, we can understand it this way: passing parameters means copying a copy of the actual parameters to the formal parameters. For objects, a copy naturally needs to be called. copy constructor.

If you write it like this, when it is run Date d2(d1), the copy constructor will be called. Because the parameter passed is an object, the copy constructor needs to be called. When calling the copy constructor, you need to pass parameters and copy the constructor. It is called repeatedly like this, endlessly. .
insert image description here

So in order to solve this problem, the copy constructor needs to pass the reference of the object

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

At the same time, because it is a copy construction, the original object should not be changed, so the parameters can also be modified const.

Date(const Date& d)
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
  • There is only one parameter to the copy constructor and it must be a reference to a class type object. If you use the pass-by-value method, the compiler will directly report an error, which will lead to endless calls.

Note here that the copy constructor is generally called like this:

int main()
{
    
    
	Date d1;
	Date d2(d1);//调用拷贝构造
}

In fact, there is another way of writing, which is specially processed by the compiler:

int main()
{
    
    
	Date d1;
	Date d2 = d1;
}

Date d2 = d1This way of writing is copy construction because an existing object is used to initialize another object. Do not confuse it with the subsequent assignment overloaded function.

If the user does not explicitly define it, the compiler will generate a default copy constructor. The default copy constructor object completes the copy in byte order. This is called shallow copy/value copy

The default copy constructor completes value copy/shallow copy for built-in type members, and calls its copy constructor for custom types.

For the Date class, its member variables are all built-in types, so there is no need to write a copy constructor, just use the constructor generated by the compiler by default.
insert image description here

Let’s take a look at the classes that dynamically open space with deep copies:

class Stack
{
    
    

public:
	Stack(int capacity = 4)
	{
    
    
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
    
    
			perror("malloc fail");
			return;
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
    
    
		
		if (_a)
		{
    
    
			free(_a);
			_a = nullptr;
			_top = 0;
			_capacity = 0;
		}
	}

private:
	int* _a;
	int _top;
	int _capacity;

};


int main()
{
    
    
	Stack s1;
	Stack s2(s1);
	return 0;
}

Entering debugging, you can see that without explicitly defining the copy constructor, s1the values ​​in are also copied to. s2Continue
insert image description here
running and find that the program crashes. What's going on?

The reason is that the copy constructor generated by the compiler by default can only perform shallow copies, which causes " s1in" _aand " s2in _a" to point to the same space. When the destructor is automatically called, the same dynamic space freeis copied twice, so the error occurs.

insert image description here
Consider another point here: the dynamic resources in both new objects obtained through shallow copy are a space, which means that modification of one of the two will affect the other.

Therefore, when you encounter members that need to be dynamically opened, you need to define a copy constructor that can be used for deep copying_a . Through deep copying, you can open up a space and let the new copy object point to the new space.

insert image description here

Stack(const Stack& st)
	{
    
    
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
    
    
			perror("malloc fail");
			return;
		}
		memcpy(_a, st._a, sizeof(int) * st._top);
		_capacity = st._capacity;
		_top = st._top;
	}

From the above, we can know that if there is no resource application involved in the class, the copy constructor can be written or not. Once resource application is involved, the copy constructor must be written, otherwise it will be a shallow copy.

By comparing the deep and shallow copies, we can understand why C++ needs to call the copy constructor when passing objects.
If all parameters are passed according to the basic type, and if the object as a parameter has dynamic resources, then the formal parameters and actual parameters share a space, it will As a result, one modification will affect the other, and when the object is destructed, the program will crash if it is destructed twice.


assignment operator overloaded function

int main()
{
    
    
	Date d1;
	Date d2;
	d2 = d1;
}

Looking at the above code, two objects are first defined, and then the value d2 = d1is assigned d1to d2
Here the assignment operator overload is called

Pay attention to distinguish assignment operator overloading and copy constructor.
The characteristics of copy constructor are: initializing another object with an existing object.
Assignment operator overloading is: assignment between two existing objects.

Let’s make a detailed distinction below:

int main()
{
    
    
	Date d1;
	Date d2;
	d2 = d1;//赋值重载
	Date d3(d1);//拷贝构造
	Date d4 = d2;//拷贝构造
}

In the above code, the existing d1and d22 objects
d2 = d1are assignments between the two existing objects, so it is assignment overloading
Date d3(d1). It is using the existing object d1to initialize an object that did not exist before d3. It is a copy construction, which is also a copy construction call. The common way of writing looks like copy overloading, but it actually initializes
Date d4 = d2an existing object , so it is still a copy constructord2d4

Assignment operator overloading:

  • Parameter type: const T&, passing by reference can improve the efficiency of parameter passing.
  • Return value type: T&, returning a reference can improve return efficiency. The purpose of having a return value is to support Nash assignment
  • Check whether it will assign a value to itself
  • return *this

Assignment overloaded function of Date class:

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

Operator overloading must be written as a member function because 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.

We can overload the assignment operator. Regardless of the type of the formal parameter, the assignment operator must be defined as a member function.

If it is not explicitly defined, the compiler will automatically generate a default assignment operator overload function, which copies the value byte by byte. The default generated
assignment overload has the same behavior as the copy:

  1. Built-in type members--value copy/shallow copy
  2. Custom type members call their own assignment overloads

Therefore, if the class does not involve dynamic resources, it does not matter whether the assignment and overloading functions are explicitly defined. Once dynamic resources are involved, they must be explicitly defined.


Address-taking and const address-taking operator overloading

Now we write a Date printing function

void Date::print()
{
    
    
	cout << _year << "-" << _month << "-" << _day << endl;
}

If we define an object: const Date d1(2022, 1, 1), by d1calling print()the function, we will find an error
insert image description here

thisThe reason for the error reported here is: the type of the pointer in print() is called with const object d1, which should beconst Date* const

const Date* const, the first constmodification is thisthe pointer itself, and the second constmodification is *thisthat it guarantees that member variables cannot be modified.

print()The type of this pointer is Date* const
from const Date* const-> Date* constwhich is the enlargement of permissions. C++ allows the reduction of permissions but does not allow the enlargement of permissions .

So in order to solve this problem, you need to modify this pointer toconst

void Date::print() const
{
    
    
	cout << _year << "-" << _month << "-" << _day << endl;
}

The reason why the modification is added after the function name constis because C++ stipulates that the actual parameters and formal parameters of the function cannot appear explicitly this. There is no way to modify this directly, so you can only add after the function name.const

The const-modified "member function" is called a const member function. The const-modified class member function actually modifies the this pointer implicit in the member function, indicating that no member of the class can be modified in the member function.

After the member function is added const, both ordinary objects and const objects can be called. Here there is only panning and shrinking of permissions, so it can be said that any object can call the const member function.

Not all member functions can add const, only those functions that do not modify member variables can add const.

There are also address operator overloaded functions and const address operator overloaded functions in C++:

Date* operator&()
{
    
    
	return this;
}

const Date* operator&() const
{
    
    
	return this;
}

If it is not explicitly defined, the compiler will automatically generate a default address fetching function.
Generally, these two operators do not need to be overloaded. Just use the ones generated by the compiler by default.
If there are special circumstances, overloading is required. It is better to let others get it. to the specified content.


Guess you like

Origin blog.csdn.net/weixin_64116522/article/details/131796216