octopus_c头像
C++ 运算符重载详解 && 日期类的实现封面图

C++ 运算符重载详解 && 日期类的实现

5.1 运算符重载

基本概念

1.运算符重载允许用户为类类型的对象赋予运算符新的含义,使自定义类型也能像内置类型一样使用 +、-、== 等运算符。

2.使用运算符时,本质上是在调用对应的重载函数。若没有对应的重载函数,则编译报错。

3.由关键字 operator 和指定的运算符共同构成,与其他函数一样,包含返回值类型、参数列表和函数体。

// 以 Date 类为例,重载 == 运算符
class Date {
public:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day) {}

    bool operator==(const Date& d) const {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }

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

参数与调用规则

1.重载函数的参数个数与运算符的操作数个数一致,一元运算符一个参数,二元运算符两个参数。

2.当运算符重载为成员函数时,左侧对象通过隐式的 this 指针传递,因此参数列表比操作数少一个。例如二元运算符重载为成员函数时,只需显式写出一个参数。

特性

重载后的运算符,其优先级和结合性与对应的内置类型运算符保持一致,不会因为重载而改变。

重要限制与注意事项

1.不能通过连接语法中没有的符号来创建新的操作符,例如 operator@ 是非法的。

2..*、::、sizeof、?:、. 这五个运算符不能被重载,是选择题的常考点。

3.重载运算符必须至少有一个参数是类类型,不能改变内置类型对象的含义。例如 int operator+(int x, int y) 是非法的。

4.重载运算符应具有实际意义。例如 Date 类重载 -(计算日期差)是有意义的,而重载 + 则没有明确语义。

5.重载后置 ++ 时,需要额外增加一个无用的 int 形参,用于与前置 ++ 区分。

6.必须重载为全局函数,且第一个形参为 ostream/istream 对象,第二个形参为自定义类型对象。(要是重载为成员函数,第一个参数默认传给 this 指针,所以就会变成 对象在前,cin/cout 在后,出现倒反天罡的形式,所以说只能重载为全局函数,不需要 this 指针,直接传参,将第一个形参来接收 cin/cout。

// 前置 ++ 与后置 ++ 的重载
class Date {
public:
    // 前置 ++:返回引用,减少拷贝
    Date& operator++() {
        _day += 1;
        return *this;
    }

    // 后置 ++:增加一个无用的 int 形参用于区分
    // 这里的int只是用于区分,并无其他用处,传不传参都可
    Date operator++(int) {
        Date tmp(*this); //使用拷贝构造,等价于 Date tmp = *this;
        _day += 1;
        return tmp;
    }
};

// << 与 >> 必须重载为全局函数
// 这里要求将 istream/ostream 作为第一个参数是因为 输入输出是 cin/cout在前,直接传参
std::ostream& operator<<(std::ostream& out, const Date& d) {
    out << d._year << "-" << d._month << "-" << d._day;
    return out;
}

5.2 赋值运算符重载

基本概念

1.赋值运算符重载是一个默认成员函数,用于完成两个已存在对象之间的拷贝赋值。

2.需要严格区分拷贝构造与赋值运算符重载。拷贝构造用于对象初始化(创建新对象),而赋值运算符重载用于已存在对象之间的赋值。

Date d1(2024, 1, 1);
Date d2(d1);   // 拷贝构造:d2 是正在初始化的新对象
Date d3;
d3 = d1;       // 赋值运算符重载:d3 已存在,进行赋值

核心特点

1.赋值运算符重载规定必须重载为成员函数,不能写成全局函数。(适用赋值重载时,默认会将第一个参数传给 this 指针,类外不支持,所以将赋值重载写在类外面会报错)。有依赖 this 指针实现的函数就实现为成员函数,反之实现为全局函数。

2.建议写成 const 当前类类型引用(const T&),避免传值传参产生拷贝。

3.有返回值,且建议写成当前类类型引用(T&),目的是支持连续赋值,例如 a = b = c;。

4.若未显式实现,编译器会自动生成默认赋值运算符重载。默认行为是逐字节拷贝(浅拷贝),对自定义类型成员会调用其自身的赋值重载。

class Date {
public:
    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;
};

实现场景与技巧

1.若类成员全是内置类型且无资源管理(如 Date 类),编译器生成的默认赋值即可满足需求。

2.若类涉及资源管理(如 Stack 类中有指针指向堆内存),必须实现深拷贝,对资源也进行拷贝,否则会出现两个对象指向同一块内存的问题。

3.若类内部成员是自定义类型(如 MyQueue 中有 Stack 成员),编译器生成的默认赋值会调用成员变量的赋值重载,无需额外实现。

4.如果一个类显式实现了析构函数(说明释放了资源),那么它也需要显式实现赋值运算符重载,通常还需要显式实现拷贝构造。

// 涉及资源管理时,必须实现深拷贝
class Stack {
public:
    Stack(int capacity = 4)
        : _a(new int[capacity])
        , _top(0)
        , _capacity(capacity)
    {}

    ~Stack() {
        delete[] _a;
        _a = nullptr;
    }

    Stack(const Stack& st)
        : _a(new int[st._capacity])
        , _top(st._top)
        , _capacity(st._capacity) {
        memcpy(_a, st._a, sizeof(int) * _top);
    }

    Stack& operator=(const Stack& st) {
        if (this != &st) {
            int* tmp = new int[st._capacity];   // 先申请新空间,保证异常安全
            memcpy(tmp, st._a, sizeof(int) * st._top);
            delete[] _a;                        // 释放旧空间
            _a = tmp;
            _top = st._top;
            _capacity = st._capacity;
        }
        return *this;
    }

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

小结

对比项拷贝构造赋值运算符重载
作用时机对象初始化时两个已存在对象之间赋值
是否可重载为全局可以必须是成员函数
返回值建议无特殊要求返回 T&,支持连续赋值
默认行为浅拷贝浅拷贝
资源管理类需显式实现深拷贝需显式实现深拷贝

5.3日期类实现

#pragma once
#include<iostream>
#include<assert.h>
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1);
	void Print() const;
	//频繁调用,所以在类里面实现,默认inline函数
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		int Day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))))
			return 29;
		return Day[month];
	}
	bool CheckDate();
	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;
	// d1 += 天数
	Date& operator+=(int day);
	Date operator+(int day) const;
	// d1 -= 天数
	Date& operator-=(int day);
	Date operator-(int day) const;
	// d1 - d2
	int operator-(const Date& d) const;
	// ++d1 -> d1.operator++()
	Date& operator++();//前置++
	Date operator++(int);//后置++
	Date& operator--();
	Date operator--(int);
	int operator-(const Date& d);

private:
	int _year;
	int _month;
	int _day;
};
#include"Date.h"
using namespace std;
bool Date::CheckDate()
{
	if (_month < 0 || _month >12 || _day<1 || _day > GetMonthDay(_year, _month))
		return false;
	else
		return true;
}

Date::Date(int year,int month,int day)
{
	if (!CheckDate())
		cout << "日期非法" << endl;
	year = _year;
	month = _month;
	day = _day;
}

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

bool Date::operator<(const Date& d) const
{
	if (_year < d._year)
	{
		return true;
	}
	else if(_year == d._year)
	{
		if (_month < d._month)
			return true;
		else
			return _day < d._day;
	}
	return false;
}

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 *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)
	{
		return *this -= -day;
	}
	_day += day;
	while(_day > GetMonthDay(_year, _day))
	{
		_day -= GetMonthDay(_year, _day);
		++_month;
		if (_month == 13)
		{
			_month = 1;
			++_year;
		}
	}
		return *this;
}

Date Date::operator+(int day) const
{
	Date tmp = *this;//保证自己不被修改,同时返回修改之后的值
	tmp += _day;
	return tmp;
}

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

	_day -= day;
	while (_day <= 0)
	{
		--_month;//先更新_month,防止出现用 _month == 0 去取天数
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_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;
	tmp += 1;
	return tmp;
}

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

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

int Date::operator-(const Date& d)
{
	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;
}

5.4 const 成员函数

1.将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后
⾯。
2.const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const
Date* const this 表示对于指针本身以及指向的内容均不能进行修改。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/octopus_c/article/details/169463618

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--