[C++ Essence Shop] 5. Six default member functions of C++ classes and object (middle) classes

Table of contents

1. Six default member functions

2. Constructor

2.1 Concept

2.2 Default construction

2.2.1 System-generated default constructs

2.2.2 Custom default constructor

 2.3 Overloading of constructors

3. Destructor

3.1 Concept

 3.2 System-generated destructor

 3.3 Custom destructor

4. Copy construction

4.1 Concept

 4.2 Default generated copy construction (shallow copy)

 4.3 Custom copy construction (deep copy)

 5. Assignment operator overloading

5.1 Operator overloading

5.2 Assignment operator overloading

6. Address and const address operator overloading

7. Attachment: complete date class (the code in the article is not taken from the code here, it is temporarily typed for the purpose of explaining the knowledge points, the code here is the complete date class (taken from Bittech), you can learn from it)


1. Six default member functions

        When we think of an empty class, we must think that a class with nothing in it is called an empty class, but this is not the case. When nothing is written in a class, the compiler will generate six default member functions by default to complete the basic functions of a class.

  1. Constructor: Object initialization works
  2. Destructor: space cleanup work
  3. Copy Construction and Assignment Operator Overloading: Copying of Objects Copying Works
  4. Overloading of address fetching and const fetching of addresses: Generally, it is rarely implemented by itself, unless it is necessary to return a specified special address to the user.

2. Constructor

2.1 Concept

        The constructor is a special member function that is automatically called by the compiler to complete the initialization of the object when the object is created . The constructor has no return value, and the function name is the same as the class name.

        The constructor features 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.
  4. Constructors can be overloaded 
class Date
{
public:
	Date()
	{
		//构造函数
	}
private:

};

2.2 Default construction

        What is default construction? Both the parameterless constructor and the default constructor are called default constructors, and there can only be one default constructor. Generally speaking, the custom types we implement must have a default constructor, the reason will be described below. 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.

2.2.1 System-generated default constructs

         When we do not manually define the constructor, the system will generate a no-argument constructor by default. This no-argument constructor will initialize the member variables in the class, including calling its default constructor for the custom type ( This is why each class must have a default structure), does not handle built-in types , yes, you heard it right, the default structure generated by the system does not handle built-in types, this is a very bug point. For example the following case:

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

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
private:
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	cout << d._year << d._month << d._day << endl;
	return 0;
}

output:

Time()
-858993460-858993460-858993460 

        It can be seen that the constructor generated by default will not process built-in types, and its default construction will be called for custom types. Because of this, the default structure generated by the system is useless, so a patch is applied to this vulnerability in C++11: In C++11, a patch is applied for the defect of non-initialization of built-in type members, namely : The built-in type member variable can be given a default value when it is declared in the class. (Give a default value to the built-in type in the same way as giving a default value) as follows:

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

		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	// 基本类型(内置类型)
	int _year = 2023;
	int _month = 8;
	int _day = 9;
private:
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	cout << d._year << ' ' << d._month << ' '  << d._day << endl;
	return 0;
}

output:

Time()
2023 8 9

2.2.2 Custom default constructor

        There are only two types of custom default constructors, no-argument and full-default constructors, and only one of these two types of constructors can exist. But someone may ask, don't these two constructors constitute overloading, why can't they exist at the same time? First of all, the grammar stipulates that only one default structure can exist. In addition, although these two functions conform to the grammar of function overloading, there will be ambiguity when calling them. You must know that there are reasons for any grammar rules. as follows:

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

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

	Date(int a = 1, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d;  //这里出现了歧义,
			   //不知道调用的是全缺省的构造还是无参的构造
			   //因为这俩种函数都可以用 Date() 的形式来调用就出现了歧义
	return 0;
}

 Error:
C2668 'Date::Date': ambiguous call to overloaded function.

E0339 Class "Date" contains multiple default constructors.

        But if we pass the actual parameters to the constructor, we can call it, so the grammar stipulates that there can only be one default construction. The essence is because the above situation will cause ambiguity, and we can pass an actual parameter when we usually instantiate. To avoid this kind of ambiguity, even if two default structures are defined at this time, no error will still be reported, but we do not recommend writing this way, because the risk in the project is huge, such as the following:

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

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

	Date(int a = 1, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d(1,2);  //不建议,不能因为避免报错就去特殊处理初始化方式。
                  //不能同时定义俩个默认构造,即使编译器没有报错
	return 0;
}

 2.3 Overloading of constructors

        The constructor supports overloading, which allows us to deal with different initialization scenarios (here I emphasize again: the no-argument construction and the full default construction cannot be defined at the same time, and there will be ambiguity).

public:
	Date()
	{
		cout << "Date()" << endl;
	}

	Date(int a, int b = 2)
	{
		cout << "Date(int a = 1, int b = 2)" << endl;
	}
	Date(int year, int month, int day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};

3. Destructor

3.1 Concept

        The nature of the destructor is very similar to that of the constructor, but contrary to the function of the constructor, the destructor is used to clean up the space after the object is destroyed, but it does not complete the destruction of the object itself, and the local object destruction is done by the compiler of. When the object is destroyed, it will automatically call the destructor to complete the cleanup of resources in the object.

        The destructor has no parameters and no return value like the constructor, and the naming is different, and the destructor is automatically called by the compiler when the object is destroyed, which is similar to the constructor. The 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.
class Date
{
public:
	~Date()
	{
		//析构函数
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
};

 3.2 System-generated destructor

        If there is no explicit definition of a destructor in a class, the system will automatically generate a default destructor. This destructor will be called automatically at the end of the object life cycle. The default generated destructor does not process built-in types. For custom types, its destructor will be called. as follows:

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

output:

~Date()
~Time()

 3.3 Custom destructor

        As mentioned above, the destructor does not deal with built-in types, but in our daily use, we often involve memory applications (such as malloc) and then use pointer types to store addresses. We cannot rely on the system for such spaces The default destructor is generated to avoid memory leaks, and you need to define your own destructor to release it manually. 

class Date
{
public:
	Date()
	{
		p = (int*)malloc(10 * sizeof(int));
	}
	~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	int* p = nullptr;
};

4. Copy construction

4.1 Concept

        Copy construction is an overloaded form of constructor. Copy construction has only one formal parameter, which is generally a const reference of this type (only references can be passed, not values, and passing values ​​will cause infinite recursion). Called automatically by the compiler when a new object of type object is created.

        Pass by reference (correct):

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;
	}
private:
	int _year = 2023;
	int _month = 8;
	int _day = 9;
	int* p = nullptr;
};

        Pass by value (infinite recursion):

 4.2 Default generated copy construction (shallow copy)

        When we do not explicitly define the copy construction, a default copy construction will be automatically generated. The default copy construction will copy our member variables byte by byte, and also become a value copy or a shallow copy.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		p = (int*)malloc(10 * sizeof(int));
	}
    ~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
	int* p = nullptr;
};

int main()
{
	Date d1(12, 12, 12);
	Date d2(d1);   //引发异常
}

 4.3 Custom copy construction (deep copy)

        When it comes to memory management, the copy structure generated by the compiler by default is not enough. At this time, we need to make a deep copy of it. What is a deep copy: it is to create a new object and copy the "value" (all elements of the array) of the attributes of the original object. If it is the above situation, we need to re-apply for a space to save the data pointed to by dp.

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		p = (int*)malloc(10 * sizeof(int));
		if (p == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		for (int i = 0; i < 10; i++)
		{
			p[i] = 10;
		}
	}
	~Date()
	{
		free(p);
		cout << "~Date()" << endl;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		p = (int*)malloc(10 * sizeof(int));
		if (p == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		for (int i = 0; i < 10; i++)
		{
			p[i] = d.p[i];
		}
	}
private:
	int _year;
	int _month;
	int _day;
	int* p = nullptr;
};
int main()
{
	Date d1(12, 12, 12);
	Date d2(d1);
}

 output:

~Date()
~Date()

 5. Assignment operator overloading

5.1 Operator overloading

        C++ introduces operator overloading to enhance the readability of the code. Operator overloading is a function with a special function name, and also has its return value type, function name, and parameter list. The return value type and parameter list are similar to ordinary functions. The function name is: the keyword operator followed by the operator symbol that needs to be overloaded. Function prototype: return value type operator operator (parameter list).

Notice:

  1. New operators cannot be created by concatenating other symbols: eg operator@ overloaded operator must have a class type parameter
  2. Operators used for built-in types, whose meaning cannot be changed, for example: built-in integer +, whose meaning cannot be changed
  3. When overloaded as a class member function, its formal parameters appear to be 1 less than the number of operands, because the first parameter of the member function is the hidden this
  4. .* :: sizeof ?: . Note that the above 5 operators cannot be overloaded. This often appears in written multiple choice questions.

        We have two ways to define operator overloading. It can be defined as global or directly as a function member, but when it is defined as a function member, the formal parameters we see will be one less (because the this pointer implies parameters), as follows == as an example:

//全局
bool operator==(const Date& d1, const Date& d2)
{
    return d1._year == d2._year
   && d1._month == d2._month
        && d1._day == d2._day;
}

//函数成员

class Date
{
public:
	bool operator==(const Date& d)
	{
		return (_year == d._year)
			&& (_month == d._month)
			&& (_day == d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};

        What needs to be paid attention to in operator overloading is "++" and "--", because these two operators have the difference between preposition and postposition, so the distinction between preposition and postposition is also made as follows (with + + for example):

	Date& operator++();//前置
	Date operator++(int);//后置 多了一个int形参,仅作标记,没有实际含义

5.2 Assignment operator overloading

        If the assignment operator overload function is not explicitly defined, the compiler will automatically generate a default assignment operator overload, and the copy method is shallow copy. The difference from other operator overloading functions is that assignment operator overloading must be defined as a member function and cannot be defined as a global one. If it is defined as a global one, it will be automatically generated if there is no assignment operator overloading in the class body, which will conflict with our global definition. .

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	  
	Date& operator=(const Date& d) //引用传参避免拷贝提高效率								
	{                              //引用返回因为赋值运算符支持连续赋值 d1 = d2 = d3;
		if (this != &d)
		{
			_year= d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

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

        If the type involves memory management, deep copy is required. Here it is exactly the same as the copy structure. If you don't understand it, you can look ahead.

6. Address and const address operator overloading

        These two operator overloading compilers will also be generated by default. In most cases, we don't need to define it ourselves, unless we want others to get the specified content!

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

7. Attachment: complete date class (the code in the article is not taken from the code here, it is temporarily typed for the purpose of explaining the knowledge points, the code here is the complete date class (taken from Bittech), you can learn from it)

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 2023, int month = 8, int day = 10);
	void Print() const;
	int GetMonthDay(int year, int month) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	int operator-(const Date& d) const;
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
private:
	int _year;
	int _month;
	int _day;
};
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
#include"Date.h"
int Date::GetMonthDay(int year, int month) const
{
	assert(month > 0 && month < 13);

	int monthArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400) == 0))
	{
		return 29;
	}
	else
	{
		return monthArray[month];
	}
}
Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13
		&& (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法,初始化失败" << endl;
	}
}
void Date::Print() const
{
	cout << _year << " " << _month << " " << _day << endl;
}
bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
bool Date::operator<(const Date& d) const
{
	return _year < d._year
		|| (_year == d._year && _month < d._month)
		|| (_year == d._year && _month == d._month && _day < d._day);
}
bool Date::operator<=(const Date& d) const
{
	return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
	return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
	return !(*this < d);
}
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day) const
{
	Date tmp(*this);

	tmp += day;

	return tmp;
}
Date& Date::operator-=(int day) 
{
	if (day < 0)
	{
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}
Date Date::operator-(int day) const
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date tmp(*this);

	*this += 1;

	return tmp;
}
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n*flag;
}

Guess you like

Origin blog.csdn.net/qq_64293926/article/details/132189479