前言
本文主要介绍运算符重载和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 运算符重载
引入
重载的运算符是具有特殊名字的函数,名字为关键字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成员、友元、内部类