目录
operator== operator>= operator!=
下文这样来初始化一个日期类的成员变量
- #include
- using namespace std;
-
-
- class Data
- {
- public:
- void Init(int year,int month,int day)
- {
- year = year;
- month = month;
- day = day;
-
- }
-
-
- private:
- int year;
- int month;
- int day;
- };
- int main()
- {
-
- Data T;
- T.Init(2003,11,29);
- return 0;
- }
我们发现并没有初始化上:
原因:
这样写加以区分:
- #include
- using namespace std;
-
-
- class Data
- {
- public:
- void Init(int year,int month,int day)
- {
- _year = year;
- _month = month;
- _day = day;
-
- }
-
-
- private:
- int _year;
- int _month;
- int _day;
- };
- int main()
- {
-
- Data T;
- T.Init(2003,11,29);
- return 0;
- }

有这样一个日期类:
-
-
- class Data
- {
- public:
- void Init(int year,int month,int day)
- {
- _year = year;
- _month = month;
- _day = day;
-
- }
-
-
- private:
- int _year;
- int _month;
- int _day;
- };
即然有类域,那我们可以这样访问吗?
- Data::_year;
- Data._year;
不可以,因为我们私有的成员变量只是一个声明:
只有声明是不能直接拿来用的,我们需要定义:
Data T;
现在定义了一个变量T,就开辟了一块空间,_year,_month,_day的空间也被一把开出来了。
有如下这么两个类,一个日期类,一个栈类
- typedef int DataType;
- class Stack
- {
- public:
- Stack(size_t capacity = 3)
- {
- cout << "Stack()" << endl;
- }
-
- // s1(s)
- Stack(const Stack& s)
- {
- cout << "Stack(Stack& s)" << endl;
-
- }
-
- void Push(DataType data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
-
- ~Stack()
- {
- cout << "~Stack()" << endl;
- }
- private:
- // 内置类型
- DataType* _array;
- int _capacity;
- int _size;
- };
- class Data
- {
- public:
- void Init(int year,int month,int day)
- {
- _year = year;
- _month = month;
- _day = day;
-
- }
- private:
- int _year;int _month;int _day;
- };
现在还有一个fun函数,我们把栈类的值拷贝给这个fun函数:
- void fun(Data a)
- {
-
- }
-
- int main()
- {
-
- Data T;
- T.Init(2003,11,29);
- fun(T);
-
-
- return 0;
- }
如果我们把日期类拷贝给fun函数就不会蹦:
-
- void fun1(Data s)
- {
-
- }
-
-
- int main()
- {
- Data T;
- T.Init(2003,11,29);
- fun1(T);
-
- return 0;
- }

如果我们把栈来拷贝给fun函数就会蹦:
- void fun2(Stack s)
- {
-
- }
-
-
-
- int main()
- {
- Stack s1;
-
- fun2(s1);
-
- return 0;
-
- }
-

我们可以画图解析一下:
如图,stack的int*_a是在堆上面开辟的空间,内容存在堆上的,_a指向堆。
fun是stack的浅拷贝,fun的_a也指向同一块地址。

fun后调用,先结束生命周期,先调用析构。此时就把这块空间释放掉了,那么轮到stack的时候会再次调用析构,会再把这块空间释放一次。
看下面这个图,a和s1的_array都指向堆上的同一块地址:
(ps:调试技巧:先给fun2()调用打断点,跳到fun2()调用之后再f11跳fun2()定义)

fun函数先析构:

stack再去析构的时候就会报错,因为stack的_a此时是一个野指针,指向了一块已经被释放了的空间:

- void fun2(Stack& a)
- {
-
- }
-
- int main()
- {
- Stack s1;
-
- fun2(s1);
-
- return 0;
- }

引用的话我就是你,也就不存在析构两次的问题了。
但是这个方法仍然有弊端,那就是如果我想通过a的值的改变但是不影响s1呢?
下面这段代码 apush值肯定会改变s1
- void fun2(Stack& a)
- {
- a.Push(1);
- a.Push(2);
- }
-
- int main()
- {
- Stack s1;
-
- fun2(s1);
-
- return 0;
- }
c++提供了拷贝构造函数来解决这个问题
拷贝构造函数和函数名和类名一致,参数类型也和类名一致,就是自己拷贝自己。
如下,我们现在就可以不用fun了,直接d2拷贝d1,通过拷贝构造函数:
- Data(Data d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- }
调拷贝构造有两种方法
d2,d3分别用不同方法拷贝d1:
-
- Data d1(2003,11,29);
-
-
- Data d2(d1);
-
- Data d3=d1;
- //fun1(d2);
但是会有报错:
也就是说拷贝构造函数参数必须为引用,这是因为如果不引用就会无穷无尽的拷贝。
怎么理解这句话呢?
内置类型可以直接传参,但是c++有自定义类型,列入栈这种,如果直接传参会面临析构两次的问题
于是c++规定自定义类型传参需要调用拷贝构造,如果直接传参会造成无限拷贝:

所以我们要通过引用传参:
- Data(Data& d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- }
引用是一种别名,所以引用没有拷贝。
同样的,用指针也可以解决这个问题,因为指针只是传个地址嘛,并没有拷贝值
-
- void fun1(Data* s)
- {
-
- }
-
- int main()
- {
- fun1(&d1);
- return 0;
- }
栈类的拷贝构造就没有日期类的拷贝构造那么简单了,因为涉及到了扩容的问题。
栈对拷贝构造实际上就是把栈的构造函数给copy一份,包括空间和内容,这个也叫做深拷贝。
- //构造函数
-
-
- Stack(size_t capacity = 3)
- {
- cout << "Stack()" << endl;
-
- _array = (DataType*)malloc(sizeof(DataType) * capacity);
- if (NULL == _array)
- {
- perror("malloc申请空间失败!!!");
- return;
- }
-
- _capacity = capacity;
- _size = 0;
- }
- //拷贝构造
-
-
- Stack(const Stack& s)
- {
- //cout << "Stack(Stack& s)" << endl;
- // 深拷贝
- _array = (DataType*)malloc(sizeof(DataType) * s._capacity);
- if (NULL == _array)
- {
- perror("malloc申请空间失败!!!");
- return;
- }
-
- memcpy(_array, s._array, sizeof(DataType) * s._size);
- _size = s._size;
- _capacity = s._capacity;
- }
没写拷贝构造时的:

写了拷贝构造之后的:

小结:

比如MYqueue类,我们什么都不写,直接让编译器用默认生成的,全去拿栈的用。
构造函数:默认生成,析构函数:默认生成,拷贝构造函数:默认生成。
-
- class MYqueue
- {
- Stack pushst;
- Stack Popst;
- };
-
- typedef int DataType;
- class Stack
- {
- public:
-
-
- Stack(size_t capacity = 3)
- {
- cout << "Stack()" << endl;
-
- _array = (DataType*)malloc(sizeof(DataType) * capacity);
- if (NULL == _array)
- {
- perror("malloc申请空间失败!!!");
- return;
- }
-
- _capacity = capacity;
- _size = 0;
- }
-
- //s1(s)
- Stack( Stack& s)
- {
- cout << "Stack(Stack& s)" << endl;
- // 深拷贝
- _array = (DataType*)malloc(sizeof(DataType) * s._capacity);
- if (NULL == _array)
- {
- perror("malloc申请空间失败!!!");
- return;
- }
-
- memcpy(_array, s._array, sizeof(DataType) * s._size);
- _size = s._size;
- _capacity = s._capacity;
- }
-
- void Push(DataType data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
-
- ~Stack()
- {
- cout << "~Stack()" << endl;
-
- free(_array);
- _array = nullptr;
- _size = _capacity = 0;
- }
- private:
- // 内置类型
- DataType* _array;
- int _capacity;
- int _size;
- };
-
- int main()
- {
-
-
- MYqueue a;
- MYqueue d = a;
- return 0;
-
- }

拷贝构造我们一般加const。
例如:
- Data(const Data& d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- cout << "拷贝构造" << endl;
- }
这是因为万一有人写反了,把拷贝构造变成被别的成员变量赋值,编译器还看不出来错误:
而加个const就是权力的缩小,拷贝构造不能被修改,它只能修改别人:

比如我们想定义两个日期类的大小,我们像下面这样比较:

编译器就会报错,因为Data是自定义类型,比较复杂,编译器没与办法直接比较。
我们只能自己手动比较:
C++给给所有的运算符比较的函数起了个统一的名称:运算符重载函数,就好比给所有初始化函数起名为构造函数,所有destory函数起名为析构函数一样。运算符重载函数用operator来表示.
运算符重载是什么意思呢?
就是说我们两个自定义类型不能直接比较,只能通过函数比较,再通过函数的返回值得到比较结果。
但是我们可以把这个比较函数重载为一个操作符,我们直接拿这个操作符用就可以实现这个函数的功能。
如下,重载为“>"操作符:
bool operator>(Data d1, Data d2)
这个时候我们就可显示调用operato>函数,或者直接比较d1,d2的值,都可以:

加括号之后:

如果我们不用运算符重载,而就用函数名的话就不能直接比较,只能通过调用函数的方式来比较:
原理:
如果我们把operator>写为Data内部成员函数需要注意几点,如果我们直接放进去会报错:
C++规定运算符重载函数作为类内部成员函数时,看起来的参数要比实际用到的参数少一个。
什么意思呢?
但是这样写就不能这样显示调用operator>了
只能这样调用:
当然,使用运算重载后的操作符照样可以:
ok,接下来我们再写operator==
- bool operator==(Data d2)
- {
- return _year == d2._year
- && _month == d2._month
- && _day == d2._day;
- }
再来写operator>=
我们把将才写的operator<=和operator==改一下符号吗?变成下面这样:
- bool operator<=( Data d2)
- {
- if (_year <=d2._year)
- {
- return true;
- }
- if (_year<=d2._year && _month <=d2._month)
- {
- return true;
- }
- if (_year<= d2._year && _month <= d2._month && _day <= d2._day)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
可以时可以,但是太麻烦,下面还要operator!= ,operator>,每个都这样拿代码就太冗余了。
我们可以复用,
我这里要写operator>=,我直接调用operator<和operator==就可以了。
怎么调用呢?用this指针,this是省略值的地址,解引用得到省略值,我们可以看下列代码:
- bool operator>=(Data d2)
- {
- return *this > d2 || *this == d2;
- }
同样,写operator<,只需要对operator<=取反就行了:
- bool operator<(Data d2)
- {
- return !(*this >= d2);
- }
写operator!=,对operator==取反即可:
- bool operator!=(Data d2)
- {
- return !(*this == d2);
- }
如下,d1的日期为非法日期,d3为合法日期,我把d3的日期赋值给d1,让d1也成为合法日期:
- Data d1(2003,11,33);
- Data d3(2003,11,30);
- d1 = d3;
- d3.print();
- d1.print();

但是两个类之家是不能之间进行赋值的,这个可以运行是因为编译器默认生成了赋值运算符重载,将d3的成员变量的值逐个复制给d1的成员变量。需要注意的是,当类中存在指针类型的成员变量时,使用默认的赋值运算符重载函数可能会导致浅拷贝问题。在这种情况下,你需要自己编写赋值运算符重载函数,以确保进行深拷贝操作,避免出现内存错误。
我们自己可以写一下赋值运算符重载:

内置类型我们可以像这样进行赋值:
它的原理实际上是这样:
注意:优先级问题,还需要再加个括号:
但是自定义类型就不可以了:

那我们可以按照内置类型的思路:先让d4复制给d3,再返回一个值,把这个值再赋值给d1.
返回的这个值也要是Data类型,因为要返回一个日期,把这个日期再赋值给d1.
- Data& Data::operator=(const Data& d3)
- {
- this->_year = d3._year;
- this->_month = d3._month;
- this->_day = d3._day;
-
- return *this;
- }
- Data d1(2003, 11, 33);
- Data d3(2003, 11, 22);
- Data d4(2000, 12, 3);
- d1 = d3 = d4;
- d1.print();
- d3.print();
- d4.print();

解析:

思路:日期类相加,先day和day相加,满28/29/30/31就往月进,月满了往下个月进,月满13了往年进。
闰年二月是29填,需要特殊处理:
-
- bool operator+()
- {
-
- }
-
- int Getmonth(int year,int month)
- {
- int GetArry[13] = {0, 31,28,30,31,30,31,30,31,30,31,30,31 };
-
- if (month==2&&(month % 4 == 0 && month % 100 != 0 || month % 400 == 0))
- {
- return 29;
- }
- return GetArry[month];
- }
看一下operator+怎么写:
- Data d1(2003,11,29);
-
- d1+100;
- Data(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
所以此时_year=2003,_month=11,_day=29.
- bool operator+(int day)
- {
- //天数相加
- _day += day;
-
- //如果月满了
- while (_day > Getmonth(_year, _month))
- {
-
- //天数减去满月的天数
- _day -= Getmonth(_year, _month);
-
- //从当前月进入下一个月
- ++_month;
- }
-
-
- //如果12个月都满了
- if (_month == 13)
- {
-
- //当前年进入下一年
- ++_year;
-
- //把月置为下一年的一月
- _month = 1;
- }
-
-
- }
把我们的传参再拿过来看看:
- Data d1(2003,11,29);
-
- d1+100;
d1+100是一个什么类型,日期+日期还是一个日期
所以:
Data ret=d1+100;
那operator+也应该用Data类型:
- Data operator+(int day)
- {
- _day += day;
- while (_day > Getmonth(_year, _month))
- {
- _day -= Getmonth(_year, _month);
- ++_month;
- }
-
- if (_month == 13)
- {
- ++_year;
- _month = 1;
- }
-
-
- }
operator+该返回什么呢?
返回*this
- Data d1(2003,11,29);
- Data ret = d1 + 50;
- d1.Print();
- ret.Print();

我们发现结果虽然运算正确,但是d1的值也被改为了ret的值,这说明
这是一个operator+=,而不是一个operator+
我们可以用用拷贝构造拷贝一份到空类里面,改变这个空容器,最后返回这个空容器就可以了。
- Data operator+(int day)
- {
- Data temp(*this);
- temp._day += day;
- while (temp._day > Getmonth(_year, _month))
- {
- temp._day -= Getmonth(_year, _month);
- ++temp._month;
-
-
- if (temp._month == 13)
- {
- ++temp._year;
- temp._month = 1;
- }
-
-
-
- }
-
-
- return temp;
-
- }

- Data operator+=(int day)
- {
-
- _day += day;
- while (_day > Getmonth(_year, _month))
- {
- _day -= Getmonth(_year, _month);
- _month++;
-
-
- if (_month == 13)
- {
- _year++;
- _month = 1;
- }
-
-
-
- }
-
-
- return *this;
-
- }
- Data operator+(int day)
- {
- Data temp(*this);
- temp += day;
- return temp;
-
- }

实现两个日期类相减:
-=是一种借位思想,如果2020 7 27-27还好,如果day-200呢,那不成了-173,欠债了。

这个时候就往前一个月借,当前月的已经光了,还欠了债,需要上个月来补,--_month.
当day<=0时都是不合法日期,day<0不合法是欠债了,需要往上一个月借,=0不合法是因为一个月没有0号。
当month=0时说明月借光了,再问年借,--_year;
再把month重置为去年的12月份。
_day要借的是上个月的day,借到_day.value不为负值,说明还完债了。
这时候把还完债后的年月日返回。
- Date& Date:: operator-=(int day)
- {
- _day -= day;
- while (_day <=0)
- {
- --_month;
-
- if (_month == 0)
- {
- _year;
-
- _month = 12;
- }
- _day += Getmonth(_year, _month);
- }
-
- return *this;
- }
-
- Date d1(2003, 11, 29);
- d1 -= 100;
- d1.print();

把数变大一点再测:
-
- Date d1(2003, 11, 29);
- d1 -= 20000;
- d1.print();
这肯定错了,20000多天都50多年了
肯定是_year没有--,改一下:
看一下计算器算的: