[C++] Classes and Objects (3) Operator Overloading Assignment Overloading Address and Const Addressing Operator Overloading

foreword

In this chapter, we take over the previous chapter to continue to understand the default member functions of classes, assignment overloading , address overloading, and const address operator overloading.

But before talking about the remaining three default member functions, we must first understand operator overloading. , because assignment overloading, address taking overloading, and const address taking operator overloading are actually part of operator overloading.


1. Operator overloading

1. The concept of operator overloading

For the built-in types of C++, we have many operators that can be used, but these operators cannot be used for custom types. We can only write a function similar to the function of the operator and let the custom type call it.
For example:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//给一个给日期加num天,不修改原始值
	Date Add(int num)
	{
    
    
		//......
	}
	//给一个日期加num天,并修改原始值
	Date AddEqual(int num)
	{
    
    
		//.....
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	int a = 10;
	a + 10;
	a += 10;
	Date d1;
	d1.Add(10);
	//d1+10;    //想写成这样,这样更直观方便,可是编译器不允许啊啊啊
	d1.AddEqual(10);
	//d1+=10;   //想写成这样,这样更直观方便,可是编译器不允许啊啊啊
	return 0;
}

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)

It is not easy to understand operator overloading just by looking at the definition. Let’s look at the code first, and understand the definition and points of attention while analyzing the code.

// 全局的operator==
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, 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;
}

void Test()
{
    
    
	Date d1(2023, 2, 12);
	Date d2(2023, 2, 12);
	cout << operator==(d1, d2) << endl;//判断两个对象是否相同,第一种使用方法,直接调用函数
	cout << (d1 == d2) << endl;//判断两个对象是否相同,第二种使用方法,使用重载后的运算符
	//此处必须加括号,运算符优先级:<< 大于 ==
}

I believe that after reading this code carefully, you already have a certain understanding of operator overloading, that is, the operator that is roughly equivalent to a custom type is actually a function that we write manually, but after operator overloading, we can use built-in type operators like Use the function that way.

2. Precautions for operator overloading

  • New operators cannot be created by concatenating other symbols (operators that do not exist in C++): such as operator@
  • Overloaded operators must have a class type parameter (because operator overloading is mainly to allow custom types to use operators like built-in types)
  • The meaning of operators used for built-in types cannot be changed , for example: the built-in integer + cannot change its meaning (because operator overloading is mainly to allow custom types to use operators like built-in types, built-in types do not need operator overloading)
  • 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 hiddenthis
  • .* :: sizeof ?: .Note that the above 5 operators cannot be overloaded. This often appears in written multiple choice questions.

Second, the special case of operator overloading

1. Front ++ and post ++ types

After the above explanation of operator overloading, I believe you can also write its operator overloading for some other operators in the Date class, such as:

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator+(int num)
	{
    
    
		//......
	}
	Date& operator-(Date& d)
	{
    
    
		//......
	}
	Date& operator+=(int num)
	{
    
    
		//......
	}
	//......
	//其实这么多运算符重载我们可以一一实现,也可以实现一到两个,然后让其他运算符重载
private:
	int _year;
	int _month;
	int _day;
};

But when we were learning C language, we learned pre ++, post ++, pre - -, post - -, what should we do for this kind of operator overloading?
Let's first write the parameter list outside the function:

//前置++
	Date& operator++()
	{
    
    

	}
//后置++
	Date operator++()
	{
    
    

	}

We found that their function external parameter lists are exactly the same, so that function overloading cannot be constituted at all, and we can only implement one of pre-++ and post-++.
In order to solve this problem, we C++ specialize this kind of preposition ++, postposition ++, preposition - -, postposition - - when this kind of operator is overloaded. The rules are:

  1. Prepend++ is implemented normally
  2. Postfix ++ passes an extra int parameter when the operator is overloaded (this parameter is only for placeholder, to achieve function overloading, it has no other effect, and it is not necessary to really pass parameters)

(- - similar to the above rules)

So to implement it correctly we should implement it like this:

//前置++
	Date& operator++()
	{
    
    
		*this += 1;//假设 +=我们已经运算符重载过了
		return this;
	}
//后置++
	Date operator++(int a)//形参名可以不写,直接写成 int,调用时也不需要传递参数
	{
    
    
		Date tmp(this); //会去调用拷贝构造 
		*this += 1; //假设 +=我们已经运算符重载过了
		return tmp;
	}

2. Stream insertion << stream extraction >> operator

When explaining operator overloading, we said that << is actually a shift operator , but it is overloaded in C++ as a stream insertion operator, so how do you do it? Now that we have learned about operator overloading, we can discuss this issue.

coutFirst of all, the and we often use cinare actually a ostreamtype object and a istreamtype object respectively. These two types are in the standard namespace of the two C++ standard libraries <ostream>respectively , and then the C++ standard library we often use contains and header files, so when we use or , we need to include header files and expand the contents of the standard namespace to the global.<istream>std<iostream><istream><ostream>cincout<iostream>using namespeace std;

insert image description here
Let's look again at the stream extraction <<operator overloading
insert image description here

<<It can be seen that there are many operator overloading + function overloading for stream extraction operators in C++ . We can use built-in types at will <<, but we have no way to use custom types, because custom types are our own, and C++ cannot advance Predict what custom type we want to write, and then perform operator overloading for our custom type, so we want our custom type to be extracted with streams, so we must manually implement the operator overloading of <<the custom type . Let's take a look at the first way of writing<<
insert image description here


//<< 运算符重载
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	ostream& operator<< (ostream&out)
	{
    
    
		out << _year << "年" << _month << "月" << _day << "日" << endl;
		return out;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1;
	int a = 10;
	cout << a << endl;//C++对自定义类型已经实现了 << 的运算符重载
	cout << d1;  
	return 0;
}

insert image description here
Compilation failed. We checked carefully and found that we wrote the 67th line backwards! should be written as

d1 << cout;           //第一种写法
d1.operator<<(cout);  //第二种写法

Because when the operator is overloaded, the first parameter is the left operand, and the second operand is the right operand (note that the first parameter is a hidden thispointer)

After writing it correctly, let's run it:

insert image description here
There is no problem, but it is too perverted to write like this, and it violates our intuition to use it. Intuition tells us that we should use it like this:

cout << d1;

Then we should insert this stream into <<the overloaded definition outside the function, because the first parameter passed by default inside the class is thisa pointer, and we will never achieve our goal.
So we define outside:

#include<iostream>
using namespace std;
class Date
{
    
    
	friend ostream& operator<<(ostream& out, Date& d);//友元,允许我们在类外部使用成员变量。
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, Date& d)
{
    
    
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

int main()
{
    
    
	Date d1;
	int a = 10;
	cout << a << endl;//C++对自定义类型已经实现了 << 的运算符重载
	cout << d1;
	return 0;
}

Another question, <<why do we use references when we write operator overloading?
insert image description here
Note: <<it is left associative!
The return value is referenced and we can achieve continuous printing of multiple custom types!

//假如d1 d2 d3 都是Date类型
cout << d1 << d2 << d3 << endl;

4. Assignment operator overloading (default member function)

1. Introduction

Let's first look at a usage scenario. What if we want to assign the data of an initialized custom type to another initialized custom type (not the assignment when the object is initialized, and the assignment when the object is initialized uses copy construction). manage?
Take a look at the code below:

//赋值重载
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << "年" << _month << "月" << _month << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1;
	Date d2 = d1;//或者Date d2(d1)  会调用默认生成的拷贝构造,对象初始化时赋值用的是拷贝构造
	Date d3;
	d3 = d1;//我们没有实现Date类的运算符 = 的赋值重载,所以会调用默认生成的赋值重载
	        //最后d3里面的数据与d1一样
}

2. Characteristics

1. 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.
Example code:
such as the above code

2. Assignment operator overload format:

Parameter type: const T& , passing reference can improve the efficiency of parameter passing
Return value type: T& , returning reference can improve the efficiency of returning, the purpose of returning value is to support continuous assignment to detect whether to assign to itself return
*this : to comply with continuous assignment the meaning of

#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)
	{
    
    
		_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;
};

int main()
{
    
    
	Date d1(2023,2,12);
	Date d2;
	d2 = d1;
	return 0;
}

insert image description here
insert image description here

3. Assignment operators can only be overloaded as member functions of classes and cannot be overloaded as global functions

#include<iostream>
using namespace std;
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 =”必须是非静态成员

insert image description here


原因:赋值运算符如果不显式实现,编译器会生成一个默认的。
此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数

insert image description here

4. If resource management is not involved in the class, the assignment operator can be implemented or not; once resource management is involved, it must be implemented.

Like the copy constructor, let's continue to think: Since the default assignment operator overload function generated by the compiler can already complete the value copy of byte order, do we need to implement it ourselves for built-in types?
Like the copy construction, feature 4 is also the judgment condition for us to write or not to write the copy overload function!
For example:

// 这里会发现下面的程序会崩溃掉,编译器生成的是浅拷贝,导致我们析构了两次空间,
//这里就需要我们以后讲的深拷贝去解决。
#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);
	Stack s2;
	s2 = s1;
	return 0;
}

insert image description here
Here we have finished the fourth of the six default member functions: copy overloading .
Copy overloading is actually part of operator overloading!

5. Take address and const take address operator overloading

1. Address operator overloading (default member function)

Let's look at the code first and think again:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	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;
	cout << &d1 << endl;
}

insert image description here
The results are in line with our expectations, and you may feel that there is nothing worth thinking about.
But we said: For custom types, we can't use operators like built-in types, but we use the address operator for the object d1 of the Date class, and we don't implement &operator &overloading, but we can used &, and it worked out fine. why?

This is because the fifth default member function: overloading of the address operator , that is, if we don’t write it, the compiler will automatically generate it for us. Its function is to help us realize the address of the custom type object.

Manual implementation of address overloading

Usually, we don't write this function ourselves, let the compiler generate it automatically. So what if we implement this function ourselves?

The implementation code is as follows:

//取地址重载函数
#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* operator&()
	{
    
    
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    
    
	Date d1;
	cout << &d1 << endl;
}

insert image description here

2, const address operator overloading (default member function)

We generally don't add it when we define an object const, so constwhat happens if we add it to an object?
Then let's look at another piece of code:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
    
    
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
	return 0;
}

We found that the compilation failed. Why did we fail to call the function after
insert image description here
adding the object ? constAccording to constthe common reasons for adding errors, it is not difficult to think that the permissions should be enlarged.

Remember thiswhat the type of pointer is? The answer is: * consttype. Here should be What type should Date * const
we consttake the address of the modified object? The answer is: const *. Here it should be that const Date*
the two types do not match, and constthe content cannot be changed after the object is modified, so we thisneed to change the type of the pointer and add one *in front const.
insert image description here

But thisthe pointer is passed by the compiler, we can't add constit, what should we do?
Here, the C++ compiler has done special processing. We need to add it constafter the function brackets to thismodify the pointer.

Correct code:

#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
    
    
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
    
    
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
	return 0;
}

insert image description here

insert image description here
The const-modified "member function" is called a const member function . The const-modified class member function actually modifies the implicit this pointer of the member function , indicating that any member of the class cannot be modified in the member function.

Please consider the following questions:

  1. Can a const object call a non-const member function?
    Answer: No, thispermissions will be enlarged when passing pointers
  2. Can a non-const object call a const member function?
    Answer: Yes, thispermissions narrow when passing pointers
  3. Can const member functions call other non-const member functions?
    Answer: No, thispermissions will be enlarged when passing pointers
  4. Can other constmember functions be called in a non-const member function?
    Answer: Yes, thispermissions narrow when passing pointers

Const take address overloading manual implementation

Similarly, in the previous code, constwhen we took the address of the type &, we did not overload it, but we can use it, also because the compiler automatically helped us realize the const address overload .

Note that the two are not the same, the two functions constitute function overloading!

address-of operator overloading

Date* operator&()             //对非 const 对象取地址

const address overloading

const Date* operator&()const  //对 const 对象取地址

Implemented manually:

//const取地址重载函数
#include<iostream>
using namespace std;
class Date
{
    
    
public:
	Date(int year=0, int month=0, int day=0)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() const
	{
    
    
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	const Date* operator&()const //返回值const Date * 是为了与this 指针保持一致
	{
    
    
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
    
    
	const Date d1;
	cout << &d1 << endl;
	return 0;
}

insert image description here

insert image description here
These two operators generally do not need to be overloaded, just use the default address overload generated by the compiler. Only in special cases, overloading is required, such as wanting obtain the specified content !

Guess you like

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