大家好我是沐曦希
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
//全局operator==
class Date
{
public:
Date(int year = 1970, 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;
}
int main()
{
Date d1(2022, 10, 15);
Date d2(2022, 10, 14);
//cout << d1 == d2 << endl;
//<<--流插入,运算符优先级大于==,所以d1==d2要加括号
cout << (d1 == d2) << endl;//d1 == d2 会转换成operator==(d1,d2)
cout << operator==(d1, d2) << endl;//也可以显示调用,一般不会这样
return 0;
}
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
//检测日期的合法性
if (!(year >= 1
&& (month >= 1 && month <= 12)
&& (day >= 1 && day <= GetMonthDay(year, month))))
{
cout<<"非法日期"<<endl;
}
}
// bool operator==(Date* 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;
};
bool 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;
}
return false;
}
bool operator>=(const Date& d)
{
return *this == d || *this > d;
}
bool operator<(const Date& d)
{
return !(*this >= d);
}
bool operator<=(const Date& d)
{
return !(*this > d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
int GetMonthDay(int year, int month)
{
int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if ((((year % 4 == 0) && (year % 100 != 0))
|| (year % 400 == 0)) && month == 2)
{
return 29;
}
else
{
return MonthDayArray[month];
}
}
Date& operator+=(int day)
{
_day += day;
while (day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_year += 1;
_month = 1;
}
}
return *this;
}
Date operator+(int day)
{
Date ret(*this);
ret._day += day;
return ret;
}
赋值重载既是默认成员函数,又是运算符重载。
void TestDate()
{
Date d1;
Date d2(2022, 10, 9);
Date d3(d2);//拷贝构造(初始化) 一个初始化另一个马上要创建的对象
d1 = d2;//赋值重载(复制拷贝) 已经存在两个对象之间拷贝
}
赋值重载实现:
//d1 = d2
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
对于赋值重载(复制拷贝)和拷贝构造的区别:
赋值重载(复制拷贝) 已经存在两个对象之间拷贝
拷贝构造(初始化) 一个初始化另一个马上要创建的对象
赋值运算符重载不写,编译器会生成一个默认的,对内置类型进行值拷贝(即浅拷贝),对自定义类型调用器赋值重载。
但是如果用编译器默认的赋值运算符重载又会发生什么?
#include
using namespace std;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
Stack(const Stack& st)
{
cout << "Stack(const Stack&st" << endl;
_a = (int*)malloc(sizeof(int) * st._capacity);
if (nullptr == _a)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
void Push(int x)
{
_a[_top++] = x;
}
private:
int* _a;
int _top;
int _capacity;
};
void TestStack()
{
Stack st1;
st1.Push(1);
st1.Push(2);
Stack st2;
st2.Push(3);
st2.Push(4);
st2.Push(5);
st2.Push(6);
st1 = st2;
}
int main()
{
TestStack();
return 0;
}
发生了崩溃,实际上这里的问题还是析构了两次(直接把st2的空间拷贝给st1,st1和st2的_a指向了同一个)发生了崩溃,但是此时的st1的内存还发生了泄露。栈的赋值运算符重载实现:
//st1 = st2;
//st1 = st1;
Stack& operator = (const Stack& st)
{
if (this != &st)
{
free(_a);
_a = (int*)malloc(sizeof(int) * st._capacity);
if (nullptr == _a)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
return *this;
}
那么要写析构函数的,就必须要显示写赋值构造函数。
cin的类型是istream
cout的类型是ostream
其中cout,cerr和clog的功能相似
对于<<和>>,我们一般不写成员函数,因为this默认抢了第一个参数位置,Date对象就是左操作数,不符合使用习惯和可读性,这点值得我们去关注哈。但是如果写在全局,又引发了另一个问题:
如何去访问类的私有属性?
1.直接把私有权限改为公共权限
2.在类中设置get和set方法,然后在类外直接调用即可
3.友元声明
4.加inline改成内联函数
同时,全局变量/全局函数在所有文件中(这里我们Date.cpp,Date.h,test.cpp)都可见的,在经过编译会生成Date.o,test.o,此时里面都会有全局函数的定义,放进符号表,此时就会产生冲突。所以声明与定义应该进行分离。(也可以加上static进行修饰,static修饰全局函数会改变链接属性,只在当前文件可见)。所以尽量在。h文件中不要定义全局的函数
同时,对于频繁调用,我们可以直接用内联函数,直接展开,不进符号表
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
//检测日期的合法性
if (!(year >= 1
&& (month >= 1 && month <= 12)
&& (day >= 1 && day <= GetMonthDay(year, month))))
{
cout<<"非法日期"<<endl;
}
}
private:
int _year;
int _month;
int _day;
};
//cout << dl << d2 << endl;
//返回cout是为了链式访问
static ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
int main()
{
Date d1(2022, 10, 15);
Date d2(2022, 10, 14);
cout << d1 << d2;
return 0;
}
#include
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)
{
_year = year;
_month = month;
_day = day;
//检测日期的合法性
if (!(year >= 1
&& (month >= 1 && month <= 12)
&& (day >= 1 && day <= GetMonthDay(year, month))))
{
cout<<"非法日期"<<endl;
}
}
int GetMonthDay(int year, int month)
{
static int monthDayArray[13] = { 0,31,30,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
else
{
return monthDayArray[month];
}
}
private:
int _year;
int _month;
int _day;
};
//cout << dl << d2 << endl;
static 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._month >> d._day;
return in;
}
int main()
{
Date d1(2022, 10, 15);
Date d2(2022, 10, 14);
cout << "cout << d1 << d2结果是:" << d1 << d2 << endl;
Date d3;
cin >> d3 >> d1;
cout << d3 << d1;
return 0;
}
对于自定义类型尽量使用前置++,因为后置++要进行深度拷贝。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的结果
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// 前置+和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 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;
}
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
对于const的修饰,我们要注意权限可以进行缩小和平移,但是不能进行放大,这是在之前对于this指针( *const this)所说的。
简单来说,凡是内部不改变成员变量,其实也就是*this对象数据的,这些成员函数都应该加const
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()// Date* const this
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const//const Date* const this
{
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();
}
int main()
{
Test();
return 0;
}
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。
Date* operator&()
{
return nullptr;
}
const Date* operator&()const
{
return nullptr;
}