C++ 类和对象(三)运算符重载、const成员函数、实现日期类

前言

        本文主要介绍运算符重载和const成员函数的知识,运算符重载是设计类的重要工具,其中赋值运算符重载和取地址符重载是类的默认成员函数,最后基于本篇及之前关于类和对象的知识实现比较完善的日期类。

本文实例主要基于日期类演示:

// 日期类
class Date
{
public:
	// 参数全缺省的构造函数
	Date(int year = 1970, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 拷贝构造函数
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

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

目录

前言

1 运算符重载

引入

重载赋值运算符

取地址重载

其它运算符重载演示

重载+-,+=,-=

前置++,--

后置++,--

比较:>、<、>=、<=、==、!=

重载输出运算符<<

重载输入运算符>>

​编辑

重载 [ ]

2 const成员函数

3 实现日期类


1 运算符重载

引入

        重载的运算符是具有特殊名字的函数,名字为关键字operator加运算符符号组成。运算符重载使运算符扩展到自定义类型的操作,极大地增加了代码的可读性。

注意:

  • 不能创建新的运算符,即operator只能对已有的运算符重载。
  • 重载操作符必须有一个类类型的参数。
  • 不能重定义内置类型的运算符。

注意下表中不能被重载的运算符:::        .*        .        ? :

运算符重载示例,重载日期类型==运算符:

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

我们可以重载全局的==用于日期类对象的比较,不过这时就需要将日期类的成员变量设置成公有,或者通过友元(后续讲解)使函数能够访问私有成员。为了实现封装,本文直接将运算符重载成类的成员函数,注意函数参数隐含的this指针。

// 在类中声明 bool operator == (const Date d);

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

Date d1, d2;
d1 == d2; // 调用运算符函数
d1.operator==(d2); // 显式调用

重载赋值运算符

        赋值运算符就是一个名为opreator=的函数。类可以通过赋值运算符控制其对象如何赋值,与拷贝构造函数一样,如果类中未定义赋值运算符,编译器会合成一个。

        赋值运算符只能重载成类的成员函数,不能重载成全局函数。

原因:如果我们没有在类中显式定义赋值运算符,那么编译器会生成一个默认的赋值运算符,就会与全局的赋值运算符冲突,故赋值运算符只能重载成类的成员函数。

        作为一个二元运算符,赋值运算符左侧的对象自然绑定到this参数,右侧的对象是类同类型的参数。为了符合连续赋值,赋值运算符的返回类型应该是*this。参数和返回类型一般都是引用,以提高效率。

重载日期类的赋值运算符:

// 赋值重载
Date& Date::operator=(const Date& d)
{
	if (this != &d) // 检查是否给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}
int main()
{
	Date d1;
	Date d2(2023, 9, 1);
	d1.Print();

	d1 = d2;   // d1.operator(d2)
	d1.Print();
	return 0;
}

运行结果:

编译器默认生成的赋值运算符重载与默认的拷贝构造函数类似,以值的方式逐字节拷贝。内置类型成员变量直接赋值,自定义类型成员则调用它的赋值运算符赋值。例如:

class Time
{
public:
	Time()
	{
		_hour = 12;
		_minute = 0;
		_second = 0;
	}

	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}

		return *this;
	}

private:
	int _hour;
	int _minute;
	int _second;
};

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

private:
	int _year;
	int _month;
	int _day;
	// 自定义类型成员
	Time _t;
};

int main()
{
	DateTime d1;
	DateTime d2;

	d1 = d2;// 调用默认的赋值运算符函数
	// 内置类型直接赋值
	// d1._year = d2._year;
	// d1._month = d2._month;
	// d1._day = d2._day;

	// d1._t = d2._t; // 调用Time类型的赋值运算符函数
	return 0;
}

对于日期类我们可以使用默认的赋值运算符函数,当类涉及资源管理则需要我们自己实现类的赋值运算符,比如栈类可以参考它的拷贝构造函数实现赋值运算符重载。

取地址重载

        取地址重载及const(const成员函数见下文)取地址重载是类的默认成员函数,一般不需要我们定义,由编译器默认生成。

// 日期类的默认取地址重载
Date* operator&()
{
	return this;
}

const Date* operator&() const
{
	return this;
}

其它运算符重载演示

首先定义一个GetMonthDay函数,方便日期进位:

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 % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))
		return 29;

	return MonthDay[month];
}

重载+-,+=,-=

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

	_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) 
{
	Date tmp(*this);
	tmp += day;  // 复用+=

	return tmp;
}

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

	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day; // 复用-=

	return tmp;
}

前置++,--

// 日期+1天
Date& Date::operator++()
{
	*this += 1;  // 复用+=
	return *this;// 返回++后的日期
}
// 日期-1天
Date& Date::operator--()
{
	*this -= 1;  // 复用-=
	return *this;// 返回--后的日期
}

后置++,--

为了和前置++、--区别,C++规定后置++,--须增加一个int形参。显式调用后置++,--时须传一个int值(一般为0)。

Date Date::operator++(int)
{
	Date tmp(*this);

	*this += 1;
	
	return tmp; // 返回++前的值
}

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

	*this -= 1;
 
	return tmp; // 返回--前的值
}

Date d1;
d1++;            // 后置++
d1.operator++(0);// 显式调用

比较:>、<、>=、<=、==、!=

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

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); // 复用==
}

重载输出运算符<<

        IO标准库使用>>和<<执行输入和输出操作,库中已经重载了内置类型版本如下图,我们可以重载类的>>和<<,以实现对类对象的输入输出。

        输出运算符的第一个形参是ostream的非常量对象的引用,第二个形参一般是一个常量的引用,因为打印对象通常不会改变它的内容。为了支持连续输出,返回类型为ostream的形参。

例如,重载日期类的<<:

// 在类中声明友元函数	
friend ostream& operator<<(ostream& out, const Date& d);

// 重载日期类的全局<<
ostream& operator<<(ostream& cout, const Date& d)
{
	cout << d._year << "年" << d._month << "月" << d._day << "日";

	return out;
}

此时,成员函数Print就不需要存在了。

输入输出运算符必须是非成员函数。如果输入输出运算符是类的成员函数,那么它们左侧的对象只能是类的对象,这种形式与标准库并不兼容。

Date d;

d << cout; // 如果operator<<是类的成员函数

重载输入运算符>>

        输入运算符的第一个形参是将要读取的流(iostream的对象等)的引入,第二个形参是将要读入到的(非常量)对象的引用。返回值通常为某个流的引用。

例如,重载日期类的>>:

// 类中声明友元函数
friend istream& operator>>(istream& in, Date& d);

// 重载日期类的全局>>
istream& operator>>(istream& cin, Date& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

输入输出测试:

int main()
{
	Date d1(2023,1,1);

	cout << d1 << endl;

	cout << "输入日期:";
	cin >> d1;

	cout << d1 << endl;

	return 0;
}

运行结果:

重载 [ ]

用C语言实现顺序表时,要打印顺序表元素,只能通过定义一个Print函数实现。现在简单设计一个顺序表,重载[ ]运算符使得访问顺序表数据像数组一样方便直观。

#include<assert.h>
// 顺序表
struct SeqList
{
public:
	void PushBack(const int& x)
	{
		// ... 扩容
		_a[_size++] = x;
	}

	size_t size() 
	{
		return _size;
	}

	// 读/写
	int& operator[](size_t i)
	{
		assert(i < _size);

		return _a[i];
	}

private:
	// C++11
	int* _a = (int*)malloc(sizeof(int) * 10);
	size_t _size = 0;
	size_t _capacity = 0;
};

int main()
{
	SeqList sl;
	sl.PushBack(1);
	sl.PushBack(2);
	sl.PushBack(3);
	sl.PushBack(4);

	for (size_t i = 0; i < sl.size(); i++)
	{
		sl[i] *= 2; // 修改
		cout << sl[i] << " "; // 读取
	}

	return 0;
}

运行结果:

2 const成员函数

        const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰的是类成员函数隐含的this指针,表明在该成员函数中不能对this指针指向的对象进行修改。

例如,对于日期类的Print函数,我们需要重载一个const修饰的版本,用于打印const修饰的对象。

// 非const修饰
void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
// const修饰版本
void Date::Print() const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

Date d1;
const Date d2(2023,9,10);
d1.Print(); // 调用Print()
d2.Print(); // 调用Print() const

实际上const形参可以接收非const实参,所以日期类的Print函数只须保留const修饰版本即可。

对于顺序表,const修饰的operator[ ]的返回值也需要用const修饰,实现对const对象的成员只读。

// 只读
const int& SeqList::operator[](size_t i) const
{
	assert(i < _size);

	return _a[i];
}

// 读/写
int& SeqList::operator[](size_t i)
{
	assert(i < _size);

	return _a[i];
}

cosnt修饰主要涉及对权限的理解,思考以下问题:

1.const对象可以调用非const成员函数吗?不可以
2.非const对象可以调用const成员函数吗?可以
3.const成员函数内可以调用其他的非const成员函数吗?不可以
4.非cosnt成员函数内可以调用其他的cosnt成员函数吗?可以

解释:

1,3 const对象/成员函数调用一个非const成员函数,即将const修饰的实参(只读)传给一个非const的形参(读写),属于权限的放大,不能调用。

2,4 const对象/成员函数调用一个const成员函数,即将非const修饰的实参(读写)传给一个const的形参(只读),属于权限的缩小,可以调用。

3 实现日期类

        在前面的讲解中已经实现了日期类的大部分功能,接下来给出日期类更加完善的代码,主要增加operator-函数(日期-日期),以及对需要的成员函数用const修饰。大家可以参考代码练习。

使用分离式编译,在Date.h头文件中声明日期类,在Date.cpp文件中定义类的成员函数。

Date.h文件

#include<iostream>
using namespace std;

class Date
{
    // 友元声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:

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

	// 拷贝构造函数
	Date(const Date& d);

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

	// 赋值运算符重载
	Date& operator=(const Date& d);

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

	// 日期+天数
	Date operator+(int day) const;

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

	// 日期-天数
	Date operator-(int day) const;


	// 前置++
	// d++ -> d.operator()
	Date& operator++();

	// 后置++
	// d++ -> d.operator(0)
	Date operator++(int);

	// 前置--
	Date& operator--();

	// 后置--
	Date operator--(int);


	// >运算符重载
	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;

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

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

Date.cpp文件

#include"Date.h"

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

	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

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 % 400 == 0) || (year % 4 == 0 && year % 100 != 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 << "非法日期" << endl;
	}
}

Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

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)
{
	if (day < 0)
		return *this -= (-day);

	_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)
		return *this += (-day);

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

bool Date::operator > (const Date& d) const
{
	if (_year > d._year)
		return true;
	else if (_year == d._year && _month > d._month)
		return true;
	else if (_year == d._year && _month == d._month && _day > d._day)
		return true;
	else
		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);
}

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

如果本文内容对你有帮助,可以点赞收藏,感谢支持,期待你的关注。

下篇预告:C++ 类和对象(四)初始化列表、static成员、友元、内部类

猜你喜欢

转载自blog.csdn.net/2301_79391308/article/details/132579955
今日推荐