目录
日期类:
运算符重载:
编辑
赋值重载:
拷贝构造和赋值重载的区别:
实现赋值重载:
划分成员函数:
日期类的声明和定义分离
日期类-=:
日期类-
前置后置++
日期类:
写一个简单的日期类:
#pragma once;
class Date
{
public:
Date(int year=1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
#include<iostream>
using namespace std;
#include"Date.h"
void TestDate()
{
Date d1;
Date d2(2022, 9, 18);
Date d3(2022, 2, 30);
Date d4(2022, 2, 49);
d3.Print();
d4.Print();
}
int main()
{
TestDate();
return 0;
}
我们发现,日期d3和d4都不合法,我们进行运行:
也没有提示我们输入日期错误。
我们可以对我们的构造函数进行优化:
Date(int year = 1, 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;
}
}
日期类的拷贝构造:
对于日期类,我们不需要写对应的拷贝构造,因为对于内置类型,编译器会默认执行值拷贝:
void TestDate()
{
Date d1;
Date d2(2022, 9, 18);
/*Date d3(2022, 2, 30);
Date d4(2022, 2, 49);
d3.Print();
d4.Print();*/
Date d5(d2);
d5.Print();
}
int main()
{
TestDate();
return 0;
}
运算符重载:
赋值重载:
void TestDate()
{
Date d1;
Date d2(2022, 9, 18);
/*Date d3(2022, 2, 30);
Date d4(2022, 2, 49);
d3.Print();
d4.Print();*/
/*Date d5(d2);
d5.Print();*/
Date d5;
d5 = d2;
}
拷贝构造和赋值重载的区别:
拷贝构造:一个对象拷贝初始化另一个要创建的对象。
赋值重载:已经存在的两个对象之间的拷贝。
实现赋值重载:
赋值重载可以传值传参吗?
可以,不会发生无穷递归,发生无穷递归的条件是: 拷贝构造时,使用传值传参,传值传参本身就是拷贝构造,拷贝构造又需要传值传参,无限循环。
所以这里可以传值传参。
但是传值传参还需要调用拷贝构造,有些麻烦,我们最好还是使用传地址传参。
void operator=(const Date&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void TestDate()
{
Date d1(2022, 9, 10);
Date d2;
d2 = d1;
}
int main()
{
TestDate();
return 0;
}
我们进行测试:
赋值成功。
赋值重载写的不够全面,我们知道整型可以连等:
int main()
{
/*TestDate();*/
int i, j;
i = j = 1;
return 0;
}
我们这里是不能连等的:
void TestDate()
{
Date d1(2022, 9, 10);
Date d2, d3;
d2.Print();
d3=d2 = d1;
d2.Print();
}
int main()
{
TestDate();
/*int i, j;
i = j = 1;*/
return 0;
}
原因如下:
这里我们把d1赋值给d2之后,返回值应该是d2,但是我们实现的函数是没有返回值的,所以我们需要完善返回值:
Date operator=(const Date&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
传值传参是拷贝构造,传值返回也是拷贝构造,所以我们这里可以用传引用返回:
而且this指向的对象出了作用域依旧存在,我们可以用引用返回。
能不能不返回*this,返回d呢?
答:不可以
Date& operator=(const Date&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return d;
}
1:因为d是只读的,传引用返回后的类型变成可读可写的了,属于权限的放大。
2:赋值的返回值是左操作数,不能返回右操作数。
划分成员函数:
赋值运算符的重载对于内置类型完成值拷贝,所以对于日期类,我们可以不定义赋值重载
我们没有定义赋值重载函数,依旧可以完成日期类对象的赋值。
大部分类的默认赋值重载都不需要我们自定义,但是有些需要,例如栈:
#pragma once;
class Stack
{
public:
Stack(int capacity=4 )
{
_a = (int*)malloc(sizeof(int)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
cout << "Stack构造函数()" << endl;
}
Stack(const Stack&st)
{
_a=(int*)malloc(sizeof(int)*st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int)*st._top);
_top = st._top;
_capacity = st._capacity;
}
void Push(int x)
{
_a[_top++] = x;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
cout << "Stack析构函数()" << endl;
}
private:
int*_a;
int _capacity;
int _top;
};
我们没有实现赋值重载,看看能否使用赋值重载
void StackTest()
{
Stack st1;
st1.Push(1);
st1.Push(2);
Stack st2;
st2.Push(10);
st2.Push(20);
st2.Push(30);
st2.Push(40);
st1 = st2;
}
int main()
{
StackTest();
/*int i, j;
i = j = 1;*/
return 0;
}
运行之后,直接报错。
我们进行值拷贝:
把st2._top赋值给st1._top,把st2._capacity赋值给st1._capacity。
再让st1._a指向st2._a的位置。
后创建st2的先析构,析构之后st2所指向的空间的数据释放了,这时候st1再进行析构就导致了越界访问。
并且原本属于st1的指向的数据的地址也找不到了,造成了内存泄漏。
所以对于栈类,我们要自己实现赋值重载:
我们先释放掉st1所指向的空间的数据,然后新创建一个空间,拷贝st2的数据,让st1指向该空间:
Stack& operator=(const Stack&st)
{
free(_a);
_a = (int*)malloc(sizeof(int)*st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int)*st._top);
_top = st._top;
_capacity = st._capacity;
return *this;
}
完成了深拷贝。
还有一种情况:
用自己去赋值自己。
我们进行运行,发现数据出现了错误。
因为
我们可以先提前判断:
Stack& operator=(const Stack&st)
{
if (&st != this)
{
free(_a);
_a = (int*)malloc(sizeof(int)*st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int)*st._top);
_top = st._top;
_capacity = st._capacity;
}
return *this;
}
对于一些有资源释放的类,我们也可以不自己写:
class MyQueue
{
public:
void push(int x)
{
_pushST.Push(x);
}
private:
Stack _pushST;
Stack _popST;
size_t size = 0;
};
对于内置类型完成值拷贝,对于自定义类型,调用其默认赋值重载。
void MyQueueTest()
{
MyQueue s1;
s1.push(10);
MyQueue s2;
s2 = s1;
}
int main()
{
MyQueueTest();
/*int i, j;
i = j = 1;*/
return 0;
}
完成了深拷贝。
日期类的声明和定义分离
对于比较小并且频繁调用的函数,这类函数一般是内联函数,内联函数只能在类里面定义,不能声明和定义分离。
对于行数不多并且调用不频繁的我们可以声明和定义分离。
日期类-=:
Date&Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
#include"Date.h"
void TestDate1()
{
Date d1(2022, 10, 19);
d1 -= 1000;
d1.Print();
}
int main()
{
TestDate1();
return 0;
}
日期类-
Date Date::operator-(int day)
{
Date ret(*this);
ret -=day;
return ret;
}
#include"Date.h"
void TestDate1()
{
Date d1(2022, 10, 19);
Date d2 = d1 - 10000;
d2.Print();
}
int main()
{
TestDate1();
return 0;
}
假如我们要减去一个负数呢?
#include"Date.h"
void TestDate1()
{
Date d1(2022, 10, 19);
d1 -= -10000;
d1.Print();
}
int main()
{
TestDate1();
return 0;
}
前置后置++
我们在定义的时候如何区分前置和后置++?
Date& Date::operator++()
{
*this += 1;
return *this;
}
Date Date::operator++(int)
{
Date ret = *this;
*this + 1;
return ret;
}