Classes and Objects: Operator Overloading

 This article will introduce operator overloading in C++, as well as the three default member functions related to operator overloading: assignment operator overloading , ordinary object address taking and const object address taking operator overloading, that is, the 6 default members in the picture below The last three functions and the first three default member functions have been discussed in previous articles about classes and objects: constructors, destructors and copy constructors_A Broccoli's Blog-CSDN Blog icon-default.png?t=N7T8https://blog.csdn .net/qq_72916130/article/details/132789343?spm=1001.2014.3001.5501 . If you are not sure, you can take a look.

This article mainly explains through a date class . I'll leave the full date class later.

class Date
{
public:
    //...
private:
	int _year;
	int _month;
	int _day;
};

1.What is operator overloading?

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 is: the keyword operator is followed by the operator symbol that needs to be overloaded.

Function prototype: return value type operator operator (parameter list)

Notice:

  • New operators cannot be created by concatenating other symbols: such as operator@
  • Overloaded operators must have a class type parameter
  • Operators used for built-in types cannot change their meaning. For example: the built-in integer type + cannot change its meaning.
  • 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 pointer.
  • .* :: sizeof ? : .Note that the above five operators cannot be overloaded. This often appears in multiple-choice questions in written exams.

Just looking at the definition is not enough. We implement the following overloading of > and ==

// >运算符重载
//因为>号有两个操作数,因为有一个是隐藏的this指针,所以这里只有一个参数
bool Date::operator>(const Date& d)
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year == d._year && _month < d._month)
	{
		return false;
	}
	else if (_year == d._year && _month == d._month && _day <= d._day)
	{
		return false;
	}
	else
	{
		return true;
	}
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

There is > and == or < and ==, and other comparison operators can be derived from these two, without having to write them one by one.


2. Assignment operator overloading

1. Assignment operator overloading format

  • Parameter type: const T& . Passing references can improve the efficiency of parameter passing. Const is added to prevent parameters from being modified.
  • Return value type: T&. Returning a reference can improve the efficiency of return. The purpose of returning a value is to support continuous assignment.
  • Check whether you assign a value to yourself
  • Return *this: to comply with the meaning of continuous assignment

Code:

Date& Date::operator=(const Date& d)//可以不用引用,但没有必要
{
	if (this != &d)//自己给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
		
	return *this;
}

For returning *this, because our usual assignment operator can be implemented in a way similar to a = b = c = d, the explicit call is a.operator(b.operator(c.operator(d))), if operator=function Without a return value, an error occurs.

2. The assignment operator can only be overloaded as a member function of the class and cannot be overloaded as a global function.

// 赋值运算符重载成全局函数,注意重载成全局函数时没有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 =”必须是非静态成员

Reason: If the assignment operator is not implemented explicitly, 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. Therefore, the assignment operator overload can only be a member function of the class.

3. When the user does not implement it explicitly, the compiler will generate a default assignment operator overload and copy it byte by value 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.

Now that the default assignment operator overloaded function generated by the compiler can complete byte order value copying, do you still need to implement it yourself
? Of course classes like date classes are not necessary.

Note: 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. Because it will also perform a simple value copy (shallow copy) of the address and release it twice during destruction, which will cause the program to crash.


3. Prefix++ and post++ overloading

There is only one operand between prefix ++ and postfix ++, and only the return value is different. A prefix ++ returns itself plus one, and postfix ++ needs to construct a Date to store the result without adding one before returning it. Only the return value is different, there is no way to distinguish the two functions, so we have to add a placeholder parameter to the post++. It has no purpose. It is just to distinguish the prefix and the postfix . The compiler also identifies it in this way.

C++ regulations: Add an extra int type parameter when overloading post++, but this parameter does not need to be passed when calling the function, the compiler will automatically pass it

Code: 

// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,
//故需在实现时需要先将this保存一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
// 后置++ 传入的值不用接收
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

-- Same thing:        

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

4.const members

The const-modified "member function" is called a const member function. The const-modified class member function. The const here needs to be added after the formal parameter list to actually modify the implicit this pointer of the member function , indicating that in the member function No members of the class can be modified.

Let's take a look at the following code:

This class member function Print() is not modified with const.

    const Date d1(2023, 9, 23);
	//const修饰的对象调用不了成员函数,因为权限放大
	d1.Print();//d1.print(&d1) 隐藏的传入this的指针是const Data* 类型

Print() is not modified by const. The function parameter this pointer is of ordinary Date* type, and the object modified by const calls the Print() function. The passed this pointer is of const Data* type, and permissions will be enlarged . You can zoom out and pan, but you can't zoom in and an error will be reported.

Please consider the following questions:

  1. Can a const object call non-const member functions?
  2. Can non-const objects call const member functions?
  3. Can other non-const member functions be called within a const member function?
  4. Can other const member functions be called within a non-const member function?

Answer: 1, 3 is not possible, the permissions are enlarged. 2 and 4 are OK, with reduced permissions.

We can use function overloading to make objects with different permissions call different functions. Print like the following code:

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;
    }
    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; // 日
};
void Test()
{
    Date d1(2022,1,13);
    d1.Print();
    const Date d2(2022,1,13);
    d2.Print();
}

Of course, it makes no sense to modify the Print function with const. Let's look at the overloading of the address-taking and const-address operators below.


5. Overloading of address-taking and const address-taking operators

These two default member functions generally do not need to be redefined, and the compiler will generate them by default.

class Date
{
public :
    //这种方式返回指针的内容可以修改,可读可写
    Date* operator&()
    {
        return this ;
    }

    //这种方式返回指针的内容也不能修改,只读
    const Date* operator&()const
    {
        return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

These two operators generally do not need to be overloaded. Just use the default address-taking overload generated by the compiler. Only in special cases, overloading is needed. For example, if you want others to get the specified content, do not return this and return nullptr. etc.

6. Implementation of Date class

Date.h 

class Date
{

public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	//全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);

	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-天数
	Date operator-(int day);
	// 日期-=天数
	Date& operator-=(int day);

	void Print();

	//下面两个是运算符重载,也构成函数重载
	// 前置++ 
	//++d1 -> d1.operator++()
	Date& operator++();
	// 后置++
	//d1++ -> d1.operator++(0)//传一个整形即可,只是用来区分前置++
	//加了一个int参数,进行占位,跟前置++构成函数重载进行区分
	//本质后置++调用,编译器进行了特殊处理
	Date operator++(int);//传入的值不用接收
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();


	// >运算符重载
	bool operator>(const Date& d);
	// ==运算符重载
	bool operator==(const Date& d);
	// >=运算符重载
	bool operator >= (const Date& d);
	// <运算符重载
	bool operator < (const Date& d);
	// <=运算符重载
	bool operator <= (const Date& d);
	// !=运算符重载
	bool operator != (const Date& d);

	// 日期-日期 返回天数
	int operator-(const Date& d);

	const Date* operator&()const;

	Date* operator&();

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

Date.cpp

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	static int MonthDay[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;
	}
	return MonthDay[month];
}

//全缺省的构造函数
//声明和定义缺省参数的值,不能都给,必须在声明中给,
//因为在编译时头文件中看到的是声明,而声明中没有提供缺省值,会报错。到不了后面的链接,通过函数名修饰规则来找到函数的定义
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	//检查日期日期是否合法
	if (month < 1 || month>12 || day < 1||_day > GetMonthDay(_year, _month))
	{
		cout << "非法日期\n" << endl;
		exit(-1);

	}
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)//可以不用引用,但没有必要
{
	if (this != &d)//自己给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
		
	return *this;
}

// 日期+=天数
Date& Date::operator+=(int day)//0次拷贝 0次赋值
{
	if (day < 0)
	{
		return *this -= (-day);
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		while (_month > 12)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
// 日期+天数
Date Date::operator+(int day)//两次拷贝
{
	if (day < 0)
	{
		return *this - (-day);
	}

	Date ret(*this);

	ret += day;

	return ret;
}

// 日期-天数
Date Date::operator-(int day)
{
	if (day < 0)
	{
		return *this + (-day);
	}

	Date ret = *this;
	ret -= day;
	return ret;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}

	_day -= day;
	while (_day < 1)
	{
		_month--;
		while (_month < 1)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

//只读函数可以加const,内部不涉及修改成员

//void Date::Print(const Data* this)
//void Date::Print() //可以同时存在,因为this指针类型不同,如果只有const版本也可调用,因为权限可以缩小
//{//不过在这里没有意义
//	cout << _year << "/" << _month << "/" << _day << endl;
//}
void Date::Print() const
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// 后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}



// >运算符重载
bool Date::operator>(const Date& d)
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year == d._year && _month < d._month)
	{
		return false;
	}
	else if (_year == d._year && _month == d._month && _day <= d._day)
	{
		return false;
	}
	else
	{
		return true;
	}
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

// >=运算符重载
bool Date::operator >= (const Date& d)
{
	return *this > d || *this == d;
}

// <运算符重载
bool Date::operator < (const Date& d)
{
	return !(*this >= d);
}

// <=运算符重载
bool Date::operator <= (const Date& d)
{
	return !(*this > d);
}

// !=运算符重载
bool Date::operator != (const Date& d)
{
	return !(*this == d);
}

//跟日期-天数构成函数重载
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (max < min)
	{
		min = *this;
		max = d;
		flag = -1;
	}
	int count = 0;
	while (max != min)
	{
		--max;
		count++;
	}
	return count * flag;
}


//日常自动生成的就可以
//不要被取到有效地址
//const Date* Date::operator&()const
//{
//	return nullptr;
//}
const Date* Date::operator&()const
{
	return this;
}
//这个接口是读写
Date* Date::operator&()//函数重载,一个给const对象用,一个给非const对象用
{
	return this;
}

This article is over!

 

Guess you like

Origin blog.csdn.net/qq_72916130/article/details/133238692