目录
5.赋值运算符重载
5.1 运算符重载
5.2 赋值运算符重载
5.3 前置++和后置++重载
6.日期类的实现——流插入,流提取重载
Date.h:
Date.cpp:
7.const成员
8.取地址及const取地址操作符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
// 全局的operator== 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, 9, 26); Date d2(2023, 9, 26); cout << (d1 == d2) << endl; cout << operator==(d1, d2) << endl; } int main() { Test(); return 0; }
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { year = year; _month = month; _day = day; } // bool operator==(Date* const this, const Date& d2) // 这里需要注意的是,左操作数是this,指向调用函数的对象 bool operator==(const Date& d2) { return _year == d2._year && _month == d2._month && _day == d2._day; } private: int _year; int _month; int _day; }; int main() { Date d1(2023,12); Date d2(2023, 12); cout << (d1 == d2) << endl; return 0; }
1. 赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
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; };
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
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 =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的 赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time { public: Time() { _hour = 1; _minute = 1; _second = 1; } 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 Date { private: // 基本类型(内置类型) int _year = 1970; int _month = 1; int _day = 1; // 自定义类型 Time _t; }; int main() { Date d1; Date d2; d1 = d2; return 0; }
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然 像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。 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; }
此时,不写赋值重载,s1 和 s2指向同一块空间,修改的也是同一块空间,析构会释放同一块空间两次,程序崩溃
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的结果
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
return *this += 1;
}
// 后置++:
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,
// 但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值
//故需在实现时需要先将this保存一份,然后给this + 1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++; // d: 2022,1,13 d1:2022,1,14
d = ++d1; // d: 2022,1,15 d1:2022,1,15
return 0;
}
#pragma once
#include
#include
using namespace std;
class Date
{
//友元函数——这个函数内部可以使用Date对象访问私有保护的成员
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
// 全缺省的构造函数_构造函数会被频繁调用,所以直接放在类里面定义作为内联函数
Date(int year = 1,int month = 1,int day = 1)
{
_year = year;
_month = month;
_day = day;
//判断日期合法性
if (!CheckDate())
{
Print();
cout << "Bogus date!!!" << endl;
}
assert(CheckDate());
}
//拷贝构造函数
//如果不写,编译器默认生成拷贝构造函数,拷贝构造函数内置类型按照字节方式进行拷贝,而自定义类型是调用其拷贝构造函数完成拷贝的。
//Date(const Date& d)
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
//析构函数
//如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数
//~Date()
//{
//}
//判断是否为闰年,频繁调用,定义成内联
bool IsLeapYear(int year)
{
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
}
// 获取某年某月的天数,频繁调用,定义成内联
int GetMonthDay(int year, int month)
{
//频繁查询天数,使用静态,不用频繁开辟空间
static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && IsLeapYear(year))
{
return days[month] + 1;
}
return days[month];
}
//判断日期合法性
bool CheckDate()
{
if (_year >= 1 && _month > 0 && _month < 13
&& _day>0 && _day <= GetMonthDay(_year, _month))
{
return true;
}
return false;
}
// >运算符重载
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);
// 赋值运算符重载
// 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);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
//转换为天数
int ChangeDays(int year);
//日期减日期 —— 运算符重载,和函数重载,此时是函数重载
int operator-(const Date& d);
/*void operator<<(ostream& out);*/
void Print();
private:
int _year;
int _month;
int _day;
};
//流插入重载
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
//流提取重载
inline istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._day >> d._day;
assert(d.CheckDate());
return in;
}
#include"Date.h"
//任何一个类,只需要写一个 > == 的重载,或者 < ==,剩下的运算符复用即可
// >运算符重载
bool Date::operator>(const Date& d)
{
if (_year == d._year)
{
if (_month == d._month)
{
return _day > d._day;
}
return _month > d._month;
}
return _year > d._year;
}
// ==运算符重载
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);
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
//不要调用对象判断,对象判断需要调用重载运算符,代价大
if (this != &d)
{
this->_day = d._day;
this->_month = d._month;
this->_year = d._year;
}
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)
{
_month = 1;
_year++;
}
}
return (*this);
}
// 日期+天数
Date Date::operator+(int day)
{
Date newDate(*this);
return newDate += day;
}
// 日期-天数
Date Date::operator-(int day)
{
Date newDate(*this);
return newDate -= day;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
if (_month == 1)
{
_month = 12;
_year--;
}
else
{
_month--;
}
_day += GetMonthDay(_year,_month);
}
return (*this);
}
// 前置++
Date& Date::operator++()
{
return *this += 1;
}
// 后置++
Date Date::operator++(int)
{
Date temp(*this);
*this += 1;
return temp;
}
// 后置--
Date Date::operator--(int)
{
Date temp(*this);
*this -= 1;
return temp;
}
// 前置--
Date& Date::operator--()
{
return *this -= 1;
}
//转换为天数
int Date::ChangeDays(int year)
{
int sumDay = _day;
for (int i = 1; i < _month; i++)
{
sumDay += GetMonthDay(_year,i);
}
while (_year > year)
{
_year--;
if (IsLeapYear(_year))
{
sumDay += 366;
}
else
sumDay += 365;
}
return sumDay;
}
//日期减日期 —— 运算符重载,和函数重载,此时是函数重载
int Date::operator-(const Date& d)
{
int year = 0;
Date d1(*this);
Date d2(d);
return abs(d1.ChangeDays(d1._year) - d2.ChangeDays(d1._year));
}
//日期间日期,方法二
//int Date::operator-(const Date& d)
//{
// Date max = *this;
// Date min = d;
// if (*this < d)
// {
// max = d;
// min = *this;
// }
// int n = 0;
// while (min != max)
// {
// ++min;
// ++n;
// }
// return n;
//}
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
//void Date::operator<<(ostream& out)
//{
// out << _year << "/" << _month << "/" << _day << endl;
//}
运算符重载:让自定义类型可以用运算符,转换成调用这个重载函数
函数重载:支持函数名相同的函数同时存在
虽然都用了重载这个词,但是它们之间互相独立,没有必然联系
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。
看下面的代码:
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(); }
请思考下面的几个问题:
- 1. const对象可以调用非const成员函数吗?
- 2. 非const对象可以调用const成员函数吗?
- 3. const成员函数内可以调用其它的非const成员函数吗?
- 4. 非const成员函数内可以调用其它的const成员函数吗?
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date { public: Date* operator&() { return this; } const Date* operator&()const { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 };
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!