[C++ in a nutshell] Classes and objects (six default member functions, operator overloading)


Table of contents

I. Introduction 

2. Default member functions

3. Constructor

3.1 Concept

3.2 Features

4. Destructor

4.1 Concept

4.2 Features

5. Copy constructor

5.1 Concept

5.2 Features

6. Operator overloading

6.1 Introduction

6.2 Concept

6.3 Precautions

6.4 Overloading example

6.5 Assignment operator overloading

6.6 Prefix ++ and postfix ++ operator overloading

7. const member functions

7.1 Problem introduction

7.2 Definition method

7.3 Terms of use

 8. Address operator overloading


I. Introduction 

        In the last issue, we introduced some basic knowledge about classes, learned how to define a class, and experienced the characteristics of encapsulation in object-oriented . In this issue, we will continue the study of classes and objects, focusing on the member functions in C++ classes , and in the next issue we will implement a class by ourselves- the date class .

        Without further ado, let’s serve the food! ! !

2. Default member functions

        If a class has no members, we call it an empty class

//空类
class Date
{

};

         But is there really nothing in the empty class? This is not the case. When nothing is written for any class, the compiler will automatically generate 6 default member functions . Default member function: a member function that is automatically generated by the compiler if the user does not implement it explicitly . As follows:

        In the following content, we will analyze these 6 default member functions one by one.

3. Constructor

3.1 Concept

        Let’s take a look at the date class below:

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 the Date class above, we found that every time we create an object, we have to set the date for the object through the Init method , which seems too troublesome. Can we set the information synchronously when the object is created?

This can be easily solved         using the constructor . The constructor is a special member function with the same function name as the class name . It is automatically called by the compiler when creating a class object to ensure that each data member has an appropriate initial value and is only called once during the entire life cycle of the object. . Its form is as follows:

class Date
{
public:
	//Date的构造函数
	Date()
	{
		//进行初始化
        //...
	}
};

3.2 Features

        The constructor is a special member function . It should be noted that although the constructor is named constructor, its main task is not to create an object and open up space, but to initialize the object .

        The constructor has the following characteristics :

  1. The function name is the same as the class name
  2. There is no return value, and void cannot exist:
    class Date
    {
    public:
    	//Date的构造函数
    	Date()
    	{
    		
    	}
    	void Date(){} //错误写法,没有返回值
    };
  3. The compiler will automatically call the corresponding constructor when the object is instantiated.
  4. The constructor supports overloading and can match different initialization information. From the perspective of parameters , they are mainly divided into parameterless constructors and parameterized constructors :
    class Date
    {
    public:
    	// 1.无参构造函数
    	Date()
    	{}
    	// 2.带参构造函数
    	Date(int year, int month, int day)
    	{
    		_year = year; 
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	//1.创建对象
    	//2.调用相应的构造函数
    	Date d1; //调用无参构造函数
    	Date d2(2023, 8, 22); //调用带参构造函数
    }

    It should be noted that when calling a parameterless constructor, there is no need to include () after the object, otherwise it will become a function declaration:

    Date d1; //调用无参构造函数
    
    Date d3(); //声明一个没有形参的函数d3,它的返回值类型为Date
  5. The constructor is the default member function . If no constructor is explicitly defined in the class , the C++ compiler will automatically generate a parameterless default constructor , once the user explicitly defines the compiler will no longer generate

    class Date
    {
    public:
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date d1; //调用编译器自动生成的默认构造函数,默认构造是无参的,相匹配
    
        Date d2(2023, 8, 22); //该行代码会报错,没有匹配的带参构造函数
    }

    And if we explicitly define the constructor, the compiler will not automatically generate a parameterless default constructor, as follows:

    class Date
    {
    public:
    	//显式定义带参的构造函数
    	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(2023, 8, 22); //调用带参的构造函数
    }

  6. The default constructor automatically generated by the compiler will not initialize built-in types , such as int, char, double, etc.; for custom types , the default constructor of the custom type will be called.

    class Time
    {
    public:
    	Time() //Time类的默认构造函数
    	{
    		_hours = 0;
    		_minute = 0;
    		_second = 0;
    	}
    private:
    	int _hours;
    	int _minute;
    	int _second;
    };
    class Date
    {
    public:
    
    private:
        //内置类型
    	int _year;
    	int _month;
        //自定义类型
    	Time _day;
    };
    
    int main()
    {
    	Date d; //调用编译器自动生成的默认构造函数
    	return 0;
    }

    We found that the default constructor of Date does not initialize _year and _month, which are still random values , but calls the default constructor of the Time class for _day, and initializes its member variables to 0. We can further verify this through debugging :

    Default constructor debugging

  7. It is worth mentioning that: In C++11 , the defect that the default constructor does not initialize the built-in type has been improved, and the member variables of the built-in type are supported to give default values ​​when they are declared in the class . as follows:

    class Date
    {
    public:
    	void Print()
    	{
    		cout << _year << '-' << _month << '-' << _day << endl;
    	}
    private:
    	int _year = 0; //声明时给默认值
    	int _month = 0;
    	int _day = 0;
    };
    
    int main()
    {
    	Date d;
    	d.Print();
    	return 0;
    }

  8.  The constructor also supports default values . The parameterless constructor and the fully default constructor are both called default constructors . There can only be one default constructor, so both cannot exist at the same time. Examples are as follows

    class Date
    {
    public:
    	Date() //无参的构造函数
    	{
    		_year = 2023;
    		_month = 8;
    		_day = 22;
    	}
    	Date(int year = 2023, int month = 8, int day = 22) //全缺省的构造函数
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date d1(2024); //编译通过,调用全缺省的构造函数
    	Date d2; //这里编译会报错,d2调用默认构造函数,但存在两个默认构造函数,编译器不知道调用哪个
    	return 0;
    }

    Tips: Generally, when we explicitly define a constructor, we are accustomed to writing the constructor as completely default to improve the robustness of the code .

4. Destructor

4.1 Concept

        The constructor initializes the object when it is created. Once it is initialized, it is destroyed . The function of the destructor is to complete the cleanup and release of the resources in the object at the end of the object's life cycle . Like constructors, destructors are called automatically by the compiler . The following is the implementation of the constructor and destructor of the Stack class

class Stack
{
public:
	Stack(size_t capacity = 4) //构造函数,初始化一个栈,写成全缺省的形式
	{
		_array = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	~Stack() //析构函数,在类名前加~号
	{
		free(_array); //堆上动态申请的空间需要由用户自行释放
		//下面的代码也可以不写,栈上的空间操作系统会自动释放
		_array = nullptr;
		_capacity = _top = 0;
	}
private:
	int* _array;
	int _capacity;
	int _top;
};

4.2 Features

        The destructor is also a special member function, whose characteristics are as follows:

  1. The function name of the destructor is the character ~ before the class name .
  2. A destructor has neither a return value nor parameters .
  3. Contrary to constructors, destructors cannot support overloading, and there can only be one destructor in a class . Referring to the characteristics of the constructor, when the user does not explicitly define a destructor, the compiler will automatically generate a default destructor.
  4. When the life cycle of the object ends, the C++ compiler will automatically call the destructor.
    class Stack
    {
    public:
    	Stack(){
    		cout << "Stack()" << endl;
    	}
    	~Stack(){
    		cout << "~Stack()" << endl;
    	}
    private:
    	int* _array;
    	int _capacity;
    	int _top;
    };
    
    int main()
    {
    	Stack s;
    	return 0;
    }

    When the s object is created, the compiler automatically calls the constructor, and when the s object life cycle ends, the compiler automatically calls the destructor. The effect is as follows:

  5. Similar to the constructor, the destructor generated by the compiler by default will not clean up the built-in type members , and will eventually be automatically recycled by the operating system ; for custom type members, the default destructor will call its destructor function to ensure that each member of its internal custom type can be destroyed correctly.

     Back to our previous date class :

    class Time
    {
    public:
    	Time() //Time类的默认构造函数
    	{
    		cout << "Time()" << endl;
    	}
    	~Time() //Time类的析构函数
    	{
    		cout << "~Time()" << endl;
    	}
    private:
    	int _hours;
    	int _minute;
    	int _second;
    };
    class Date
    {
    public:
    	//没有显式写出构造函数和析构函数,使用编译器自动生成的
    
    private:
    	int _year;
    	int _month;
    	Time _day;
    };
    
    int main()
    {
    	Date d; //调用编译器自动生成的默认构造函数
    	return 0;
    }

    Although we did not directly create an object of the Time class , we still called the constructor and destructor of the Time class. This is because the _day member in the Date class is an object of the Time class . In the default constructor and default destructor of the Date class, the constructor and destructor of the custom type Time class will be called. For _day Members perform initialization and cleanup work.

  6. If there is no dynamic application for memory in the class , the destructor does not need to be written, and the default destructor generated by the compiler is directly used, such as the Date class; when there is a resource application, it must be written, otherwise it will cause memory leaks, such as the Stack class

  7. The destructor of a class is generally called in the reverse order of the constructor call , but pay attention to the existence of static objects , because static changes the survival scope of the object , and you need to wait for the end of the program before destructing and releasing the static object.

         Q: Assuming that there are already four definitions of classes A, B, C, and D, what is the order in which the destructors of A, B, C, and D are called in the program?

C c;
int main()
{
	A a;

	B b;

	static D d;

    return 0;

}

The answer is BADC . The analysis is as follows:

1. Global variables are constructed prior to local variables , so the order of construction is cabd.

2. The order of destruction is opposite to the order of construction.

3. Static and global objects need to be destructed after the program ends , so they will be destructed after local objects.

To sum up: the order of destruction is BADC.


5. Copy constructor

5.1 Concept

        In real life, there may be someone who looks like you, we call it a twin

         So when we create a class object, can we create an object that is exactly the same as an existing object? I think everyone doesn't like Ctrl+C and Ctrl+V. Hey, this is about our copy constructor.

         Copy constructor: There is only a single formal parameter, which is a reference to an object of this type (usually const modified). When creating a new object with an existing class object, the compiler will automatically call the copy constructor.

class Date
{
public:
	Date() {};
	Date(const Date& d) //Date的拷贝构造函数
	{
		_day = d._day;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

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

5.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
  2. There is only one parameter to the copy constructor and it must be a reference to an object of this class . If you use the pass-by-value method, the compiler will directly report an error because
    it will cause infinite recursive calls .
    //拷贝构造函数的写法
    Date(const Date d) // 错误写法:编译报错,会引发无穷递归
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
    
    Date(const Date& d) // 正确写法
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
  3. If not explicitly defined , the compiler will generate a default copy constructor. The default copy constructor will copy built-in type members according to the byte order they are stored in memory . This copy is called shallow copy , or value copy; for custom type members , its copy constructor will be called to complete the copy. copy.
    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 = 2023;
    	int _month = 1;
    	int _day = 1;
    	// 自定义类型
    	Time _t;
    };
    int main()
    {
    	Date d1; //d1调用默认的构造函数进行初始化
    
    	// 用已经存在的d1拷贝构造d2,此时会调用Date类的拷贝构造函数
    	// 但Date类并没有显式定义拷贝构造函数,因此编译器会给Date类生成一个默认的拷贝构造函数
    	Date d2(d1);
    	return 0;
    }
    We can check the copy situation of the d2 object through the monitoring window . It can be seen that the copy constructor generated by the compiler by default will not only copy the custom type members (by calling the corresponding copy constructor), but also copy the built-in type members. Copy (shallow copy in byte order).

    Default copy constructor debugging

  4. Huh? Since the copy constructor generated by the compiler by default can complete the copy very well, do we still need to explicitly implement the copy constructor? For the above date class, it is indeed enough. Let us go back to the Stack class implemented before.
    class Stack
    {
    public:
    	Stack(size_t capacity = 4)
    	{
    		_array = (int*)malloc(capacity * sizeof(int));
    		if (nullptr == _array)
    		{
    			perror("malloc申请空间失败");
    			return;
    		}
    		_size = 0;
    		_capacity = capacity;
    	}
    	~Stack()
    	{
    		if (_array)
    		{
    			free(_array);
    			_array = nullptr;
    			_capacity = 0;
    			_size = 0;
    		}
    	}
    private:
    	int* _array;
    	size_t _size;
    	size_t _capacity;
    };
    int main()
    {
    	Stack s1;
    	Stack s2(s1);
    	return 0;
    }

    When we ran the code excitedly, oh, the program actually crashed

     why? This involves the defect of shallow copy copying in byte order , as shown in the figure below

     So how to solve this shallow copy problem?

    There are generally two solutions: deep copy or reference counting .


    The so-called deep copy means to manually apply for a period of space, then copy the contents of the original space to the new space in sequence, and finally let the _array pointer of s2 point to this new space. This method avoids the problem of a space being pointed to by multiple objects.

    Reference counting is to add an additional variable count to the class to record the number of times the heap space is referenced . Only when the number of references becomes 1, we release this space. This method avoids the problem of a space being released multiple times.


    Now we have a preliminary impression of these two methods, and we will explain them in detail later. However, whether it is deep copy or reference counting, the copy constructor generated by the compiler by default cannot do it, and we need to explicitly implement the copy constructor.

  5. There are three typical calling scenarios for the copy constructor: using an existing object to create a new object , the function parameter is a class object , and the function return value is a class object .

    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;
    }

    Conclusion: In order to improve efficiency and reduce the number of copy constructions, when generally object parameters are passed, we try to use reference parameters as much as possible . When the function returns, it is also based on the actual scenario. If possible, use reference return as much as possible .


6. Operator overloading

6.1 Introduction

        For built-in types , we can use the == and > operators to determine their size relationship, and we can use the + and - operators to add and subtract them...as shown below

int main()
{
	int a = 10;
	int b = 20;
	a = a + 10;
	b = b - 10;
	cout << (a == b);
	cout << (a > b);
	//还可以使用许许多多的运算符进行操作,这里就不一一挪列了
	//...
	return 0;
}

        But for custom types , that is, our classes, these operators seem to be invalid .

class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2023, 8, 24);
	Date d2(2023, 8, 25);
	d1 = d1 + 10; //d1类对象使用+号运算符
	d1 == d2; //d2类对象使用==号运算符
}

        Obviously the compiler reported an error. This is because for several fixed built-in types, the compiler knows their operation rules , but for our custom types, the compiler does not know its operation rules . For example, what is d1+10? Year + 10 or month + 10? The compiler cannot determine it and reports an error.

        A very simple solution is to define member functions for the class , and call the member functions to achieve the operations we want, as shown below:

class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void AddYear(int val) 
	{
		_year += val;
	}
	bool isSame(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 8, 24);
	Date d2(2023, 8, 24);
	d1.AddYear(1); //年份+1
	cout << d1.isSame(d2); //比较d1和d2是否相等
}

        The above method can indeed solve the problem, but it is still not intuitive enough . Every time a calculation is performed, a function needs to be called. The code is a bit frustrating. Is there any way to let the class use operators to perform calculations?

        This has to talk about our protagonist---- operator overloading

6.2 Concept

C++ introduces operator overloading         in order to enhance the readability of the code. Operator overloading is a function with a special function name . It also has its return value type, function name and parameter list. Its return value type and parameter list are similar to ordinary functions .

        The function name of operator overloading is: after the keyword operator + the operator symbol that needs to be overloaded

        Its function prototype is: return value type operator operator (parameter list)

//==号运算符重载
bool operator==(const Date& d)
{
    //函数内容
	return _year == d._year && _month == d._month && _day == d._day;
}

Through operator overloading, we can redefine existing operators and give them another function to adapt to different data types without changing the original function.

6.3 Precautions

        You need to pay attention to the following points when performing operator overloading:

  1. New operators cannot be created by concatenating other symbols : for example, using operator@ creates a new operator@
  2. Overloaded operators must have a class type parameter
  3. The meaning of built-in type operators cannot be changed . For example: the built-in integer type + cannot change its meaning
  4. The number of operands of an operator cannot be changed . An operator has several operands, and the function has several parameters when it is overloaded.
  5. Operator overloading is also available as class member functions . When overloaded as a class member function, its formal parameter will appear to be one less than the number of operands , because the first parameter of the member function is a hidden this pointer .
  6. There are five operators that do not support overloading, they are: . , ::  , sizeof , ?: and .*
     

6.4 Overloading example

         Let's take a look at an overloaded example of the date class == operator

         1. Overloaded as a global function

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

//private: //为了让==运算符重载函数能够访问,将成员变量设置为共有的
	int _year;
	int _month;
	int _day;
};

//作为全局函数重载
bool operator==(const Date& d1,const Date& d2)
{
	//这里需要类外访问成员变量
	return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; 
}
int main()
{
	Date d1(2022, 8, 24);
	Date d2(2023, 8, 24);
	cout << (d1 == d2);
	return 0;
}

When an operator is overloaded as a global function , since we inevitably need to access member variables, we need the member functions of the class to be shared , but this will inevitably destroy the encapsulation of the class .


Of course, we can also use friend functions to solve this problem. Friends will be introduced in the next article. But the most recommended thing is to overload it as a member function .

         2. Overloading as a member function

class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// bool operator==(Date* const this, const Date& d2)
	// this指向调用的对象
	bool operator==(const Date& d) //重载为成员函数
	{
		//类内访问成员变量不受访问限定符限制
		return _year == d._year && _month == d._month && _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 8, 24);
	Date d2(2023, 8, 24);
	//下面两种写法是等价的
	cout << (d1 == d2);
	cout << d1.operator==(d2);
	return 0;
}

6.5 Assignment operator overloading

        In the process of using classes, when we want to assign one class to another class, we can overload the = assignment operator . Its overload format is as follows:

  • Parameter type: const T& , passing reference can improve the efficiency of parameter passing, const ensures that the original class object will not be modified
  • Return value type: T& , using reference return can improve the efficiency of return. The purpose of return value is to support chained access .
  • Generally, it will detect whether it is assigned a value by itself. If so, no operation will be performed.
  • What is finally returned is its own reference, that is, *this , which facilitates continuous assignment.
    class Date
    {
    public:
    	Date(int year = 2023, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = 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;
    };
    
    int main()
    {
    	Date d1;
    	Date d2, d3;
    	d3 = d2 = d1; //调用赋值运算符重载
    }

Here are some notes          on assignment operator overloading

  1. The assignment operator can only be overloaded as a member function and cannot be overloaded as a global function.
    // 赋值运算符重载成全局函数
    Date& operator=(Date& left, const Date& right)
    {
    	if (&left != &right)
    	{
    		left._year = right._year;
    		left._month = right._month;
    		left._day = right._day;
    	}
    	return left;
    }
    why? In fact, in the default member function we introduced earlier , the assignment operator overloading is also among them. Therefore, when we do not implement it explicitly in the class , the compiler will automatically generate a default overloaded function . And if the user writes an assignment operator overload function outside the class, it will conflict with the default assignment operator overload generated by the compiler by default , so the assignment operator can only be overloaded as a member function .
  2. The assignment operator overloaded function generated by the compiler by default has similar rules to the copy constructor. For built-in types , they are copied in byte order; for custom types , their assignment operator overloaded functions are called.
  3. So since the assignment operator overload generated by the compiler by default can complete the byte order copy, is it still necessary to implement it explicitly? The answer is obviously a need. Like the copy constructor, when implementing a memory-managed class like Stack, we need to implement assignment operator overloading by ourselves, otherwise the program will crash due to the shallow copy problem
  4. On the other hand, if the program does not involve memory management, it does not matter whether the assignment operator overloading is implemented explicitly or not.

        Here’s a little question to try to understand the copy construct:

int main()
{
	Date d1, d2;
	Date d3 = d1; //这里调用的是拷贝构造还是赋值重载呢?
	d2 = d1; //这里呢?
	return 0;
}

Answer: The first question calls the copy constructor, and the second question calls assignment overloading.

Analysis: The copy constructor uses existing objects to construct new objects , and d3 is the new object we need to construct. The first question is to use the d1 object to construct the d3 object, so the copy constructor is called. This writing method is the same as Date d3( d1) Equivalent. The assignment operator loading function is an assignment between two existing objects . d1 and d2 are both existing objects, so d2=d1 calls assignment overloading.

6.6 Prefix ++ and postfix ++ operator overloading

        Prefix++

Next, let's try to implement the overloading of the prefixed ++ operator         on the Date class , which is used to increment the number of days . The so-called prefix ++ means to increment by 1 first and then return the result , as follows:

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator++() //前置++运算符重载
	{
		_day += 1; //先自增  (注意:这里为了演示先忽略日期进位,进位处理请看Date类的模拟实现)
		return *this; //返回自身,为了提高效率,用引用返回
	}
private:
	int _year;
	int _month;
	int _day;
};

        post++

        Postfixing ++ means returning the result first and then incrementing it . But there's a problem: both prefix++ and postfix++ are unary operators , that is, they have only one operand: the object itself. When overloaded as a member function, the overloaded function has no parameters . So how does the compiler distinguish between prefixed ++ and postfixed ++?

int main()
{
	Date d;
	//编译器要如何区分哪个operator++()函数是前置++,哪个又是后置++ ???
	++d; //相当于d.operator++()
	d++; //也相当于d.operator++()
	return 0;
}

        For this reason, C++ has made special regulations: add an additional parameter of type int to occupy the space when the post-position ++ is overloaded , but this parameter does not need to be passed when calling the function, and the compiler will pass it automatically .

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date operator++(int) //后置++运算符重载,int用于占位区分
	{
		Date temp(*this); //由于要返回+1前的结果,所以先对对象进行拷贝
		_day += 1; //然后天数+1
		return temp; //然后将+1前的对象返回。由于temp出了函数就销毁了,故不能用引用返回
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	++d; //相当于d.operator++()
	d++; //相当于d.operator++(int),int类型参数由编译器自动传递
	return 0;
}

7. const member functions

7.1 Problem introduction

        Let's look at the following code:

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

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

        The above code will report an error when compiling. The reasons for the error are as follows:

Since the object d is modified by const , its type is const Date, which means that no member of the class can be modified . When d calls the Print function, the actual parameter passed in is the address of d, that is, a pointer of const Date* type, but in the Print function, the this pointer used to receive is of Date* type, which is undoubtedly a permission amplification , so the compiler will report an error.


So how to solve this problem? It's very simple, just add const to modify this pointer .


7.2 Definition method

        Since the this pointer is a hidden parameter of the "non-static member function" , we cannot explicitly define the this pointer, so C++ stipulates that adding const after the member function means that it is a const member function , and the type of the this pointer is const A* this , the compiler will automatically identify and process


         Back to the above code, when we add const after the Print function, the program will run normally.

class Date
{
public:
	void Print() const //this指针的类型是const Date*
	{
		cout << "void Print() const" << endl;
	}
private:
	int _year = 2023;
	int _month = 1;
	int _day = 1;
};

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


7.3 Terms of use

  1. Const- modified member functions and non-const-decorated member functions can constitute function overloading , and the most matching function will be called when calling , which is generally used for the separation of reading and writing .
    class Date
    {
    public: 
    	void Print()  //非const成员函数
    	{
    		cout << "void Print()" << endl;
    	}
    	void Print() const  //const成员函数
    	{
    		cout << "void Print() const" << endl;
    	}
    private:
    	int _year = 2023;
    	int _month = 1;
    	int _day = 1;
    };
    
    int main()
    {
    	const Date d1;
    	Date d2;
    	d1.Print(); //const类型的对象调用cosnt成员函数
    	d2.Print(); //非const类型的对象调用非const成员函数
    	return 0;
    }

  2. It is recommended to add const modification to read-only member functions , that is, functions that do not involve modifying member variables internally 

  3. Constructors cannot be modified with const . The constructor initializes member variables , which obviously involves modification of member variables.


 8. Address operator overloading

        There are two versions of the address-of operator overload, one const and one non- const . These two member functions are also the default member functions we talked about at the beginning . When the user does not explicitly define them, the compiler will automatically generate them.

class Date
{
public:
	Date* operator&() //非const版本,this指针类型为Date*
	{
		return this;
	}
	const Date* operator&()const //const版本,this指针类型为const Date*
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

The member functions of these two versions are different from the above. Generally, you can use the address overload generated by the compiler by default . Only in special circumstances, explicit definition is needed, such as if you want others to obtain the specified content!

Guess you like

Origin blog.csdn.net/m0_69909682/article/details/132421855