在前面的博客中提到了拷贝构造: 【C++】类和对象之拷贝构造函数篇,和 运算符重载【C++】类和对象之常引用与运算符重载,接下来继续来看赋值运算符重载中的赋值运算符重载。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
//private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 1, 29);
Date d2(2024, 2, 28);
bool ret1 = d1 < d2;
bool ret2 = d1.operator<(d2);
int i = 0;
int j = 1;
bool ret3 = i < j;
Date d3(d1); //拷贝构造
d1 = d2; // 赋值重载
d1.Print();
d2.Print();
return 0;
}
d3用到了拷贝构造,同类型一个存在的对象进行初始化要创建的对象。 这里用到了赋值重载,是将已经存在的对象,一个拷贝赋值给另一个。
赋值运算符还支持连续赋值。 内置类型支持连续赋值,像下面这样
现将10赋值给j,然后这个表达式有一个返回值就是j,然后j再作为下一个返回值的右操作数,它有个返回值是i,这个i并没有再接收。 得注意运算符的优先级。
自定义类型也必须和内置类型一样符合这个规则。
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
要支持连续赋值就得有一个返回值,那么这里返回值是什么呢? 像d1=d2,d2赋值给d1,那么就返回d1。d2 = d3,就返回d2,也就是返回左操作数。 怎么拿到左操作数呢? 不是返回this,this是个指针,要返回对象。所以是*this,就是左操作数。
Date operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
这里用到的是传值返回,返回的不是*this,他不会返回当前对象,返回的是他拷贝,又得调用拷贝构造,太浪费了。所以在这里就再加一个引用就行。
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
这里不需要加const,规定的就是返回左操作数本身。
有时候可能会出现自己给自己赋值的情况。 像这样:
但也没什么问题,在这里为了避免这样的情况发生,在写赋值运算符重载时会加上一个判断。
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
this是左操作数的地址,d是有操作数,这里取d的地址,来判断他们地址是否相等。 相等就不会进去了。
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;
}
这里就会显示编译失败。 原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
/*Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}*/
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
//private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 1, 29);
Date d2(2024, 2, 28);
Date d3(d1);
d1 = d2;
d1.Print();
d2.Print();
d1 = d2 = d3;
return 0;
}
它是6个默认成员函数之一,用户不写,编译器也会自动生成。
总结跟拷贝构造类似。 对内置类型值拷贝,自定义类型调用对应的拷贝构造和赋值重载。
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实 现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
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;
}
这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。 注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
前置++和后置++怎么区分呢? 特殊处理:解决语法逻辑不自洽,自相矛盾的问题。 为了跟前置++区分,强行增加一个int形参,够成重载区分。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator++()
{
_day += 1;
return *this;
}
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;
}
前置++自己要加加
Date& Date::operator++()
{
*this += 1;
return *this;
}
前置++:返回+1之后的结果 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
后置++要返回加加之前的值,所以得先拷贝。
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
后置++: 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载。 C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1,而tmp是临时对象,因此只能以值的方式返回,不能返回引用。
先获取某年某月的天数。
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return monthDays[month];
}
先实现日期的相加: 直接加上日期,但当天数大于那个月份的时候,月份得加一。当月份到13的时候,又得将月份改到1月,这时候年得加加。这里实现的是加等。
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
但要实现加,本身不能改变,this的指向不能改变。所以这里使用拷贝构造。 出了作用域就不在了,不能用引用返回。
Date Date::operator+(int day)
{
Date tmp(*this);
//Date tmp = *this;
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
前面既有+=也有+,他们的代码类似,复用一下。
Date& Date::operator+=(int 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)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
Date& Date::operator+=(int day)
{
*this = *this + day;
return *this;
}
+复用+=更好,单独调用+=没有产生对象,+的时候避免不了,要产生对象。 而+=复用+,两个都产生了,+=是间接产生的。
在网上看看在日期相对比较大的时候看看计算有没有错误
日期的相减是借位,减的时候,天数小于1,就得向相应月借位,月减到0,年就得减一,月就得变为12。
Date& Date::operator-=(int 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;
}
日期-日期,返回的是天数。 直接相减肯定是不行,月可能不同,还有可能是闰年。
把月小的一个加到和大的那个相同,直接算相差几年,相差几年就直接相成对应年的天数。里面有闰年也好计算。
假设左操作数大,右操作数小,如果假设错误,就重新赋值。 这里加了一个标志flag = 1,假设正确就相加,错误就相减。
int Date::operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
int flag = -1;
max = d;
min = *this;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
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);
Date& operator+=(int day);
Date operator+(int day);
Date operator-(int day);
Date& operator-=(int day);
Date& operator++();
Date operator++(int);
// d1 - d2
int operator-(const Date& d);
// 本质就是inline
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return monthDays[month];
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
#include"Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
bool Date::operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month < d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day < d._day)
{
return true;
}
}
}
return false;
}
// d1 <= d2
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 _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
//
d1 += 10
Date& Date::operator+=(int 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;
}
d1 + 10
Date Date::operator+(int day)
{
Date tmp = *this;
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
// d1 += 100
Date& Date::operator+=(int day)
{
*this = *this + day;
return *this;
}
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//++d ->d.operator++()
Date& Date::operator++()
{
*this += 1;
return *this;
}
// d++ ->d.operator++(0)
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
// d1 - d2
int Date::operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
int flag = -1;
max = d;
min = *this;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
#include"Date.h"
int main()
{
Date d1(2024, 1, 29);
Date d2 = d1 + 20;
d2.Print();
d1.Print();
d2 -= 20;
d2.Print();
d1 += 10000;
d1.Print();
++d1;
d1.operator++();
d1.Print();
d1++;
d1.operator++(10);
d1.Print();
Date d4(2024, 1, 29);
Date d5(2024, 8, 1);
cout << d5 - d4 << endl;
return 0;
}