目录
赋值运算符重载
以下是一个日期类的运算符重载的案例(重点)
关于流插入与流提取的使用
方法一:定义与声明分离
方法二:使用内联函数
const成员
概念
关于上述日期类代码为什么需要在函数后面加入const
取地址及const取地址操作符重载
结束语
以下是对上半的补录 -- 补充一点细节
赋值运算符重载
赋值构造(和拷贝构造很类似,但是有区别)
//拷贝构造函数(默认生成的是浅拷贝 - 在一些情况(有动态内存开辟的)下是不够用的)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//上下两个很像,但是有区别,意义不一样
//赋值重载
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//测试赋值重载与拷贝构造的区别
void TestDate2()
{
Date d1;
Date d2(2022, 10, 11);
Date d3(d2); //拷贝构造(初始化) 一个拷贝初始化另一个马上要创建的对象
Date d4 = d2; //这里是一个拷贝构造,要看意义
d1.print(); // 赋值重载(也是默认构造函数,没有写会自动生成一个)
d1 = d2; // 赋值重载(复制拷贝) 已经存在两个对象之间拷贝
d1.print();
}
但是以上的写法是不支持连续赋值的,但是连续赋值确实我们经常使用的,比如我们写的
int i ,j; i = j = 10; //但是上面的写法是不支持这样用的,为此我们需要再改进一步
//拷贝构造函数(默认生成的是浅拷贝 - 在一些情况(有动态内存开辟的)下是不够用的)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//上下两个很像,但是有区别,意义不一样
//赋值重载
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this; //传值返回(代价大),会调用一次拷贝构造,所以不要这样写
//但是我们需要注意这里的 *this 所指的对象还在不在,this会销毁,因为函数出了作用域自动就销毁了
//return d; //理论上是可以返回d的(值一样),但是由于权限的放大就会报错,除非在函数的最前端加上 const
//但是一旦加上了const,就会导致这样的玩法不行 (i = j = 10)++ ,我们需要让它的返回值是可以进行修改的(左值)
//正常情况下都是返回 操作符 左边那一个数
}
栈上面的赋值重载需要自己写(有动态内存的开辟)
原因
正确的栈的赋值重载
//深拷贝
typedef int DataType;
class Stack
{
public:
//构造函数
Stack(size_t capacity = 4)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
_array[_size] = data;
_size++;
}
//拷贝构造 -- 深拷贝
Stack(const Stack& st)
{
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);
}
memcpy(_array, st._array, sizeof(DataType) * st._size); //利用memcpy进行拷贝,利用 st._size(数据的个数)更好点
_size = st._size;
_capacity = st._capacity;
}
//Stack(有动态内存开辟) 需要自己写一个赋值重载,默认生成的是不行滴!
Stack& operator=(const Stack& st)
{
if ( this != &st) //要加一层判断
{
//为防止这样写 st1 = st1 (自己给自己赋值,语法上是通过的,但是会致使_array的值变为随机值了,因为一开始就free掉了)
//但是还是在用,造成了越界访问(访问已经释放掉的空间,所以会是随机值)
free(_array); //这里是不能free(this)的,因为this是* const(在星的右边)类型的,本身是不能被修改的
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(-1);
}
memcpy(_array, st._array, sizeof(DataType) * st._size); //利用memcpy进行拷贝,利用 st._size(数据的个数)更好点
_size = st._size;
_capacity = st._capacity;
}
return *this;
}
//析构函数
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size = 0;
size_t _capacity = 0;
};
以下是一个日期类的运算符重载的案例(重点)
Date.h(声明)
#pragma once
#include <iostream>
using std::endl;
using std::cout;
using std::cin;
using std::ostream;
using std::istream;
//using namespace std;
//以下为一个声明与定义分离的日期类写法
/
class Date
{
public:
// 全缺省的构造函数
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;
}
}
//赋值重载
Date& operator=(const Date& d)
{
if (this != &d) //理论上加上好一点(防止自己给自己赋值),但是日期类用默认赋值函数就好了
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; //传值返回(代价大),会调用一次拷贝构造,所以不要这样写
//但是我们需要注意这里的 *this 所指的对象还在不在,this会销毁,因为函数出了作用域自动就销毁了
}
// 获取某年某月的天数
int GetMonthDay(int year, int month)
{
static int monthDayArray[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;
}
else
{
return monthDayArray[month];
}
}
//打印
void Print() const //C++是允许在这里加const的,用以修饰 *this 指针,其他的参数是不受影响的
{
cout << _year << "/" << _month << "/" << _day << endl;
}
Date& operator+=(int day);
Date operator+(int day) const;
Date operator-(int day) const;
Date& operator-=(int day);
Date& operator++();
Date operator++(int);
Date operator--(int);
Date& operator--();
bool operator==(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator!=(const Date& d) const;
int operator-(const Date& d) const;
//友元函数可以声明在类的任意位置
//friend ostream& operator<<(ostream& out, const Date& d);
//friend istream& operator>>(istream& in, Date& d);
friend inline ostream& operator<<(ostream& out, const Date& d);
friend inline istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
Date.cpp(定义)
#include "Date.h"
//以下为一个声明与定义分离的日期类写法
/
//等于 ==
bool Date::operator==(const Date& d) const
{
return _year == d._year //_year 前面默认有一个 this指针,下面同理
&& _month == d._month
&& _day == d._day;
}
//大于 >
bool Date::operator>(const Date& d) const
{
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 Date::operator>=(const Date& d) const
{
return *this > d || *this == d; //所以类里面是允许使用 this指针 的,因为有需求
}
//小于等于 <=
bool Date::operator <= (const Date& d) const
{
return *this < d || *this == d;
}
// 小于 <
bool Date::operator < (const Date& d) const
{
return !(*this >= d);
}
// 不等于 !=
bool Date::operator != (const Date& d) const
{
return !(*this == d);
}
// 加等 +=
Date& Date::operator+=(int day) //需要传引用返回
{
//加一层判断
if (day < 0)
{
return *this -= abs(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) const //要使用单纯的传值返回了,就好比 3(a) + 2 = 5; 这里的a的值是不变的
{
Date ret(*this);
ret += day;
return ret; //传值返回
}
//减等 -=
Date& Date::operator-=(int day)
{
if (day < 0)
{
//return *this -= -day;
return *this += abs(day);
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 减 -
Date Date::operator-(int day) const //日期减 天数
{
Date ret(*this);
ret -= day;
return ret; //传值返回
}
// 前置++
Date& Date::operator++()
{
return *this += 1;
}
// 后置++
Date Date::operator++(int) //关于为什么是 int 这是在创建好时候约定的,没有为什么
{
Date tmp(*this);
*this += 1;
return tmp; //传值返回
}
// 后置--
Date Date::operator--(int) //关于后置为什么多一个返回值,也是约定的,没有为什么
{
Date ret(*this);
*this -= 1;
return ret; //传值返回
}
// 前置--
Date& Date::operator--() //通过这里我们也可以知道,关于用前置++(--),还是后置++(--)的选择,后置需要拷贝
{ //当然这里是自定义类型回如此,内置类型是不会这样的,不过即使这样还是推荐使用前置的
return *this -= 1;
}
// 日期减日期 -
int Date::operator-(const Date& d) const
{
Date max = *this;
Date min = d; //只有引用与指针才可能导致权限的放大与缩小
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++n;
++min;
}
return n * flag;
}
///
//方法一:定义与声明分离
流插入 <<
//ostream& operator<<(ostream& out, const Date& d)
//{
// out << d._year << "年" << d._month << "月" << d._day << endl;
// return out;
//}
//
流提取 >>
//istream& operator>>(istream& in, Date& d)//这里就不用加const,因为需要该d里面的值
//{
// in >> d._year >> d._month >> d._day;
// return in;
//}
以下是测试案例
#include "Date.h"
//以下为一个声明与定义分离的日期类写法的测试
/
void TestDate1()
{
Date d1(2022, 10, 8);
Date d3(d1);
Date d4(d1);
d1 -= 10000;
d1.Print();
Date d2(d1);
/*Date d3 = d2 - 10000;d3.Print();*/
(d2 - 10000).Print(); //甚至可以这样只写
d2.Print();
d3 -= -10000;
d3.Print();
d4 += -10000;
d4.Print();
}
void TestDate2()
{
Date d1(2022, 10, 8);
Date d2(d1);
Date d3(d1);
Date d4(d1);
(++d1).Print(); // d1.operator++() 这里要考虑运算符的优先级
d1.Print();
(d2++).Print(); // d1.operator++(1)
d2.Print();
}
void TestDate3()
{
Date d1(2022, 10, 10);
Date d2(2023, 7, 1);
cout << d2 - d1 << endl;
cout << d1 - d2 << endl;
}
void TestDate4()
{
Date d1, d2;
//cin >> d1; // 流提取
//cout << d1; // 流插入
//d1 << cout; // d1.operator<<(cout);
cin >> d1 >> d2;
cout << d1 << d2 << endl; // operator<<(cout, d1);
cout << d1 - d2 << endl;
}
void TestDate5()
{
Date d1(2022, 10, 10);
d1.Print();
const Date d2(2022, 10, 10);
d2.Print();
//d2 += 10;
cout << d2 + 1000 << endl;
cout << &d1 << endl;
cout << &d2 << endl;
}
int main()
{
//TestDate1();
//TestDate2();
//TestDate3();
//TestDate4();
TestDate5();
return 0;
}
关于流插入与流提取的使用
关于流插入与流提取,这两家伙是被封装成类的,在使用时候需要注意 (比较复杂就不深入了)
当然还可以在类里面定义一个(日期)提取函数,这种方法Java语言中经常被使用
以下代码是直接放在上面日期类代码一起的
//为了重载流插入与流提取,需要加入这几句
using std::ostream;
using std::istream;
using std::cin;
在实现<<与>>俩个的重载需要明白下面这个注意事项
方法一:定义与声明分离
需要注意的是,这里因为里面的成员对象是私有的,在外部是使用不了的,为此需要用到友元函数
//在日期类里面定义这两句声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
Date.h
//全局函数不要写在.h文件里面,会有重命名的报错
//写在全局的原因是因为,this指针会占用第一个参数,导致这样写的函数重载用起来会很别扭
// d << out ,显然这样写是不符合我们使用流插入的习惯的
//流插入
ostream& operator<<(ostream& out, const Date& d);
//流提取
istream& operator>>(istream& in, Date& d); //这里就不用加const,因为需要该d里面的值
Date.cpp
//流插入 <<
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << endl;
return out;
}
//流提取 >>
istream& operator>>(istream& in, Date& d)//这里就不用加const,因为需要该d里面的值
{
in >> d._year >> d._month >> d._day;
return in;
}
方法二:使用内联函数
该方法只需要在.h里面写就行了 (方法一里面的代码记得屏蔽,不然会报错)
Date.h
friend inline ostream& operator<<(ostream& out, const Date& d);
friend inline istream& operator>>(istream& in, Date& d);
//方法二:使用内联函数
//使用内联函数,也可以解决重命名的问题,因为内联函数也是不会进入符号表的
//内联与static的方法很像,不报错的原因都是不进入符号表,但是两者的意义不一样
//内联函数在使用的时候就会自动展开
//流插入
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << endl;
return out;
}
//流提取
inline istream& operator>>(istream& in, Date& d)//这里就不用加const,因为需要该d里面的值
{
in >> d._year >> d._month >> d._day;
return in;
}
const成员
概念
将const 修饰的 “ 成员函数 ” 称之为 const 成员函数 , const 修饰类成员函数,实际修饰该成员函数 隐含的 this 指针 ,表明在该成员函数中 不能对类的任何成员进行修改。
关于上述日期类代码为什么需要在函数后面加入const
总结:凡是内部不改变成员变量,即*this对象数据的,这些成员函数都应该加const
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const //这个版本是给const类型变量调用的
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
样例
// 要求这个类的对象不让取地址
Date* operator&()
{
return nullptr;
//return this;
}
const Date* operator&() const
{
return nullptr;
//return this;
}
结束语
夫天未欲平治天下也;如欲平治天下,当今之世,舍我其谁也?
——《孟子》