目录
operator== operator>= operator!=
成员变量的命名风格
下文这样来初始化一个日期类的成员变量
#include<iostream>
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<iostream>
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++提供了拷贝构造函数来解决这个问题
Data类拷贝构造
拷贝构造函数和函数名和类名一致,参数类型也和类名一致,就是自己拷贝自己。
如下,我们现在就可以不用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;
}
Stack的拷贝构造
栈类的拷贝构造就没有日期类的拷贝构造那么简单了,因为涉及到了扩容的问题。
栈对拷贝构造实际上就是把栈的构造函数给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;
}
ps
拷贝构造我们一般加const。
例如:
Data(const Data& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "拷贝构造" << endl;
}
这是因为万一有人写反了,把拷贝构造变成被别的成员变量赋值,编译器还看不出来错误:
而加个const就是权力的缩小,拷贝构造不能被修改,它只能修改别人:
运算符重载
比如我们想定义两个日期类的大小,我们像下面这样比较:
编译器就会报错,因为Data是自定义类型,比较复杂,编译器没与办法直接比较。
我们只能自己手动比较:
C++给给所有的运算符比较的函数起了个统一的名称:运算符重载函数,就好比给所有初始化函数起名为构造函数,所有destory函数起名为析构函数一样。运算符重载函数用operator来表示.
运算符重载是什么意思呢?
就是说我们两个自定义类型不能直接比较,只能通过函数比较,再通过函数的返回值得到比较结果。
但是我们可以把这个比较函数重载为一个操作符,我们直接拿这个操作符用就可以实现这个函数的功能。
如下,重载为“>"操作符:
operator<
bool operator>(Data d1, Data d2)
这个时候我们就可显示调用operato>函数,或者直接比较d1,d2的值,都可以:
加括号之后:
如果我们不用运算符重载,而就用函数名的话就不能直接比较,只能通过调用函数的方式来比较:
原理:
ps
如果我们把operator>写为Data内部成员函数需要注意几点,如果我们直接放进去会报错:
C++规定运算符重载函数作为类内部成员函数时,看起来的参数要比实际用到的参数少一个。
什么意思呢?但是这样写就不能这样显示调用operator>了只能这样调用:
当然,使用运算重载后的操作符照样可以:
复用 operator<实现operator== operator>= 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+
那我们如何正确实现一个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;
}
operator-=
实现两个日期类相减:
-=是一种借位思想,如果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没有--,改一下:
看一下计算器算的: