【C++】类和对象(中)拷贝构造、赋值重载
三. 拷贝构造函数
1. 功能
在 用同类型对象创建新对象 时由编译器自动调用 用一个已经存在的对象初始化另一个对象
2. 特征
1. 拷贝构造函数是构造函数的一个重载形式(就是构造函数,用同类型的对象来进行构造)
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用(常用const修饰,防止被改变导致 Bug);使用传值方式编译器直接报错, 因为会引发无穷递归调用
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1) // 构造函数{_year = year;_month = month;_day = day;}// d2(d1) 拷贝构造函数// Date(Date& d)Date(const Date& d) // 就是一个构造,函数名/类名相同{_year = d._year; // this就是d2,d就是d1_month = d._month;_day = d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 4, 25);Date d2(d1); // 用同类型的对象来进行构造,d2是d1的拷贝return 0;
}
问:为什么不能传值?
void func(Date d) { }
void func(int i) { }int main()
{Date d1(2023, 4, 25);func(d1);func(10);return 0;
}
要调用 func:
内置类型:没有限制,以字节为单位直接拷贝(形参是实参一份拷贝)
自定义类型(传值):先传参(祖师爷规定:必须要调用拷贝构造函数去完成(正常的赋值也一样)) <==> 用实参的对象初始化形参
图解:
验证:
拷贝构造使用传值:
本来就是拷贝构造,要调用函数。先传参,传参又是拷贝构造;拷贝构造先传参,传参又是拷贝构造……
所以编译器不允许在这里传值
拷贝构造使用传引用:
不会形成拷贝构造,因为 d 是 d1 的别名,d2 传给了 this d 就是 d1 this 就是 d2
Date(const Date& d)
{cout << "Date(Date d)" << endl;this->_year = d._year; _month = d._month;_day = d._day;
}
特征 3
特征 3:若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储 按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
- 内置类型成员完成值拷贝 / 浅拷贝(memcpy)
- 自定义类型成员会调用它的拷贝构造
内置类型成员 · 无动态申请资源:日期类
不写拷贝构造函数
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1) // 构造函数{_year = year;_month = month;_day = day;}private: // 内置类型成员int _year;int _month;int _day;
};int main()
{Date d1(2023, 4, 25);Date d2(d1);return 0;
}
日期类可以不写拷贝构造,因为默认生成的拷贝构造就可以用
内置类型成员 · 动态申请资源:栈
不写拷贝构造函数
int main()
{Stack st1;Stack st2(st1);return 0;
}
有拷贝(值拷贝 / 浅拷贝),但不是我想要的结果:
指向同一块空间问题的:
1. st1.Push(10); 也影响了 st2
2. 出了作用域,同一块空间析构了2次(同一块空间不能析构2次),程序崩溃
在栈里面,后进先出(后定义的先析构),st2先析构,st1再析构
所以必须自己实现拷贝构造函数,实现深拷贝
class Stack
{
public:Stack(int capacity = 4) // 构造函数{cout << "Stack(int capacity = 4)" << endl;_a = (DateType*)malloc(sizeof(DateType) * capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}// st2(st1)Stack(const Stack& st) // 拷贝构造函数{_a = (DateType*)malloc(sizeof(DateType) * st._capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}memcpy(_a, st._a, sizeof(DateType) * st._size);_size = st._size;_capacity = st._capacity;}~Stack() // 析构函数{cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = _size = 0;}private:DateType* _a = nullptr;int _capacity;int _size = 0;
};int main()
{Stack st1;Stack st2(st1);return 0;
}
自定义类型成员:
class MyQueue
{
private: // 自定义类型成员Stack _pushst;Stack _popst;
};
3. 总结
这就是不让像C语言一样,把 st 直接拷贝给 s,非得调拷贝构造的原因
可以认为:拷贝构造是为自定义类型的深拷贝的对象而产生的,有资源的基本都需要深拷贝
1. Stack 的拷贝构造函数需要自己实现
2. Date,MyQueue 都不需要自己写拷贝构造函数
Date:内置类型可以完成值拷贝
MyQueue:自定义类型会调用里面 Stack 的拷贝构造(前提:Stack 的拷贝构造实现好了)
4. 拷贝构造函数的调用场景
日期类:占12字节
// void func(Date d) 拷贝12字节(传值)
void func(Date& d) // 语法上不开空间,底层开空间,但忽略不计(传引用)
{ }int main()
{Date d1;func(d1);return 0;
}
这里还能忍,下面就忍不了了
// void func(Stack st)
void func(Stack& st)
{ }int main()
{Stack st1;func(st1);return 0;
}
这里不想用传值传参了:
自定义类型的传值传参要调拷贝构造,是深拷贝,要开空间,代价大。传引用就解决了
// Stack func()
Stack& func()
{static Stack st;return st;
}int main()
{Stack ret = func();return 0;
}
如果出了作用域对象还在,愿不愿意传值返回?
传值返回,不会返回 st,会生成1个拷贝,这个拷贝又是深拷贝
出了作用域,对象还在,一定要引用返回,减少拷贝
Stack func()
{Stack st;return st;
}
如果出了作用域对象不在(是局部对象),不能用引用返回。要硬着头皮传值
因为它调用析构函数,指向的空间都销毁了
四. 赋值运算符重载
1. 运算符重载
比较日期大小
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};// 正常情况会写个函数
bool Less(const Date& x1, const Date& x2)
{if (x1._year < x2._year){return true;}else if (x1._year == x2._year && x1._month < x2._month){return true;}else if (x1._year == x2._year && x1._month < x2._month && x1._day < x2._day){return true;}return false;
}int main()
{Date d1(1949, 10, 1);Date d2(1949, 11, 1); // 比较日期大小cout << Less(d1, d2) << endl;return 0;
}
要是有人把函数名写成 Func1,还不写注释,那就不好读
int main()
{Date d1(1949, 10, 1);Date d2(1949, 11, 1); // 比较日期大小//cout << Less(d1, d2) << endl;cout << (d1 < d2) << endl; // 报错 如果能这样用就挺美// 带 () :运算符优先级,<< 优先级很高return 0;
}
为什么内置类型可以直接比较,自定义类型不可以直接比较?
内置类型是祖师爷定的,祖师爷肯定知道 int double …… 这些怎么比
自定义类型,只有你知道怎么比,怎么才能让你用 < 这个运算符呢?
bool operator<(const Date& x1, const Date& x2)
编译器做了特殊处理。看是内置类型,它知道怎么做(比较大小),就转化成对应指令
自定义类型会转换成调用这个函数,它会去看有没有重载这个日期的operator<
cout << (d1 < d2) << endl;
// 编译器自动转化成:
cout << (operator<(d1, d2)) << endl; //等价于编译器自动传 this
// 就相当于调 bool operator<(const Date& x1, const Date& x2) 这个函数d1 < d2; // 转换成 operator<(d1, d2);
operator<(d1, d2); // 显示调用也可以
1.1 概念
C++为了增强代码可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也有返回值类型、函数名字、参数列表;其返回值类型、参数列表与普通的函数类似
函数名:关键字operator 后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
自定义类型的运算符重载的本质:调用函数
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@ (没有@这个操作符)
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变。例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的 this
- 没有用过的运算符不能重载,用过的运算符里有5个不能重载 .* :: sizeof ?: . (这个经常在笔试选择题中出现)
操作符是几个操作数,重载函数就有几个参数
规定:第一个参数是做操作数,第二个参数是右操作数
并不是所有的运算符对它(日期类)都有意义
eg:比较大小、日期-日期 = 天数(有意义) 日期+日期(无意义)
是否要重载运算符,取决于这个运算符对这个类是否有意义
重载操作符必须有一个类类型参数,不能重载这个:
bool operator<(const int& x1, const int& x2)
上面是私有的,编不过。可以把它放到类里,搞成成员函数。但还是编不过
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator<(const Date& x1, const Date& x2){ }private:int _year;int _month;int _day;
};int main()
{Date d1(1949, 10, 1);Date d2(1949, 11, 1); // 比较日期大小d1 < d2; // 报错operator<(d1, d2); // 报错return 0;
}
报错:"operator<" 的参数太多了。 隐藏的this
operator< 应该有2个参数,上面实际是3个
改正:
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){ }bool operator<(const Date& x){if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;}private:int _year;int _month;int _day;
};int main()
{Date d1(1949, 10, 1);Date d2(1949, 11, 1);d1 < d2; // 自动转换为:d1.operator<(d2); d1传给了隐含的thisd1.operator<(d2);return 0;
}
2. 赋值运算符重载
已经存在的两个对象之间的复制拷贝
2.1 格式
eg场景:d2赋值给d1,用“=”赋值运算符,规定必须调用 operator=(const Date& d);函数
最简单版本的赋值:
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){ }void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(1949, 10, 1);Date d2(1949, 11, 1);// 已经存在的两个对象之间复制拷贝 -- 运算符重载函数d1 = d2;// 用一个已经存在的对象初始化另一个对象 -- 构造函数Date d3(d1);return 0;
}
这个赋值存在问题
C语言存在连续赋值的场景:
int i, j, k;
i = j = k = 0;
从右往左赋值。k = 0 这个赋值以 k 为返回值再赋给 j j = k 这个赋值以 j 为返回值再赋给 i
Date d5, d4;
d5 = d4 = d1;
// 报错:二元“=”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)
我们应该让这里返回 d4 作为右操作数再赋给 d5
// d4 = d1
void operator=(const Date& d)Date operator=(const Date& d)
{this->_year = d._year;_month = d._month;_day = d._day;return *this;
}
this 是 d4 的地址 d 就是 d2
不足:传值返回,返回的是 d4 的拷贝,代价大
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){ }Date(const Date& d) // 拷贝构造{cout << "Date(const Date& d)" << endl;_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;}private:int _year;int _month;int _day;
};int main()
{Date d1(1949, 10, 1);Date d2(1949, 11, 1);d1 = d2;Date d5, d4;d5 = d4 = d1; // d4.operator=(d1)return 0;
}
3个赋值 ==> 有3个拷贝构造
出了作用域,对象还在 ==> 引用返回
this 是形参,出了opoerator( )函数销毁
*this(就是 d4)的生命周期不在opoerator( )函数里,在外面。所以出了作用域,*this 还在
Date& operator=(const Date& d) // 引用
{if (this != &d) // 取地址 (d1 = d1; 相同不用赋值){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
此时打印结果没有拷贝构造,提高了效率
总结要点:
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this:要复合连续赋值的含义
2.2 自动生成
赋值运算符重载是默认成员函数。用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
默认生成的赋值运算符重载 和 拷贝构造行为一样:
- 内置类型成员完成值拷贝 / 浅拷贝(memcpy)
- 自定义类型成员会调用它的赋值运算符重载
Date 和 MyQueue 不需要自己实现赋值运算符重载
Stack 要自己实现,因为默认生成的是浅拷贝
2.3 不能写成全局的,只能写成成员函数
赋值运算符只能重载成类的成员函数不能重载成全局函数
赋值运算符重载是特殊的成员函数:默认成员函数
默认成员函数是亲女儿,不能在外面过夜,必须在家里过夜
默认成员函数不能写在 类外面(全局)。写在全局,类里面会默认生成一个,2个无法区分
可以声明在类里,定义在类外 ==> 此时还是成员函数
进阶玩法
重载 < == 其他复用
针对所有类
Date.h
#pragma once
#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1); // 构造 void Print(){cout << _year << "-" << _month << "-" << _day << endl;}bool operator<(const Date& x);bool operator==(const Date& x);bool operator<=(const Date& x);bool operator>(const Date& x);bool operator>=(const Date& x);bool operator!=(const Date& x);private:int _year;int _month;int _day;
};
Date.cpp
#include "Date.h"Date::Date(int year, int month, int day) // 构造函数
{_year = year;_month = month;_day = day;
}bool Date::operator<(const Date& x)
{if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;
}bool Date::operator==(const Date& x)
{return _year == x._year&& _month == x._month&& _day == x._day;
}// d1(*this) <= d2(x) 复用
bool Date::operator<=(const Date& x)
{return *this < x || *this == x;
}bool Date::operator>(const Date& x)
{return !(*this <= x);
}bool Date::operator>=(const Date& x)
{return !(*this < x);
}bool Date::operator!=(const Date& x)
{return !(*this == x);
}
日期+天数
Date.h
int GetMonthDay(int year, int month); // 天数复杂,最好实现子函数// d1 + 100
Date operator+(int day);
Date.cpp
int Date::GetMonthDay(int year, int month)
{// 不需要:每次调用都创建static int daysArr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//if (((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) && month == 2)if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))return 29;elsereturn daysArr[month];
}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;
}
Test.cpp
void TestDate1()
{Date d1(1949, 10, 1);d1 + 100;d1.Print();
}
有问题,i + 100,i 不变,是表达式有个返回值。所以不嫩能对 _year _month 改变
严格来说,我们实现的不是 + ,是 +=
Date.h
Date& operator+=(int day); // d1 += 100
Date operator+(int day); // d2 + 100
Date.cpp
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;
}// d2 + 100 不能改变d2
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; // 出了作用域,tmp销毁,不能用引用返回
}
Test.cpp
void TestDate1()
{Date d1(1949, 10, 1);d1 += 100;d1.Print();Date d2(1945, 9, 3);Date d3(d2 + 100); // "d2+100"有返回值,拿d3接收// 也可以写成 Date d3 = d2 + 100;d2.Print();d3.Print();
}// 用一个已经存在的对象初始化另一个对象 -- 拷贝构造函数
Date d4 = d2; // 等价于 Date d4(d2);//已经存在的两个对象之间复制拷贝 -- 运算符重载函数
d4 = d1;
+ 复用 +=(更好)
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -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; // 出了作用域,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; // 出了作用域,tmp销毁,不能用引用返回
}Date& Date::operator+=(int day)
{*this = *this + day;return *this;
}
对于 +、+ 复用 += 没有区别,都要创建一个对象、传值返回(拷贝构造2次)
+= 没有创建对象(拷贝构造0次)
+= 复用 +:调用 + 时,再次创建对象,传值返回(拷贝构造各2次,共4次)
日期-天数
Date.h
Date& operator-=(int day);
Date operator-(int day);
Date.cpp
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){--_month;if (_month == 0){_month = 12;--_year;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day)
{Date tmp(*this);tmp -= day;return tmp;
}
++、--
++是单运算符,因为只有一个操作数
前置++返回++以后的对象,后置++返回++之前的对象。
所以这俩函数内部是一样的,但返回值不一样,所以不能用一个运算符重载替代
Date.h
Date& operator++(); // 前置++
Date operator++(int); // 后置++
Date.cpp
Date& Date::operator++() // 前置++
{*this += 1;return *this;
}Date Date::operator++(int) // 后置++
{Date tmp = *this;*this += 1;return tmp;
}Date& Date::operator--() // 前置--
{*this -= 1;return *this;
}Date Date::operator--(int) // 后置--
{Date tmp = *this;*this -= 1;return tmp;
}
如果接收了也没意义,可以不写形参。增加这个 int 参数不是为了接收具体的值,仅仅是占位,跟前置++构成重载
Test.cpp
void TestDate2()
{Date d1(1949, 10, 1);++d1; // d1.operator++(&d1)d1++; // d1.operator++(&d1, 0)d1.Print();
}void TestDate5()
{Date d2(1949, 10, 1);Date ret2 = --d2;d2.Print();ret2.Print();Date d1(1949, 10, 1);Date ret1 = d1--;d1.Print();ret1.Print();
}
内置类型前置、后置++效率没有区别
自定义类型 前置++效率好。后置++ 会多创建2个对象。这也是牺牲后置++ 的原因
日期 - 日期
Date.h
int operator-(const Date& d);
Date.cpp
// d1 - d2
int Date::operator-(const Date& d)
{// 默认认为d1大,d2小Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}
流插入打印:全局+友元
d1.Print(); 这种打印方式麻烦。
printf 不能解决问题,只能打印内置类型%d %f ...... 不能解决自定义类型。所以 C++ 要搞自己的运算符
cout << d1; // 流插入
这时要实现运算符重载
流插入运算符 << 是双操作数:日期类对象 和 ostream 类对象 cout
ostream 是 iostream 库定义的
所以包含 <iostream> 这个头文件,再展开命名空间就可以了
double d = 1.1;
int i = 2;
cout << d;
cout << i;
C语言要指定类型打印
C++可以直接支持内置类型是因为库里面实现了 可以直接支持自动识别类型是因为函数重载
// Date.h
void operator<<(ostream& out);// Date.cpp
void Date::operator<<(ostream& out)
{out << _year << "年" << _month << "月" << _day << "日" << endl;
}// Test.cpp
void TestDate7()
{Date d1(1949, 10, 1);cout << d1; // 报错:二元“<<”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
}
cout << d1; 正常情况下要转换成调函数 d1.operator<<(cout) 但不能
d1 << cout; 才能转换成 d1.operator<<(cout)
规定:第一个参数是做操作数,第二个参数是右操作数
// Test.cpp
void TestDate7()
{Date d1(1949, 10, 1);d1 << cout;d1.operator<<(cout);
}
流插入不能写成成员函数,因为Date对象默认占用第一个参数,就是做了左操作数
写出来就一定是这样子:d1 << cout; // d1.operator<<(cout); 不符合使用习惯
写成成员函数第一个位置被 Date(this)自动占领
必须让 cout 去第一个参数:写成全局
// Date.h
class Date { };
void operator<<(ostream& out, const Date& d);// Date.cpp
void operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
此时无法访问私有,会报错
方法1:写几个公有成员函数
Date.h
class Date
{
public:int GetYear() const // 为什么加 const 下面讲{return _year;}private:
};
Test.cpp
void operator<<(ostream& out, const Date& d)
{out << d.GetYear() << "年" << d.GetMonth() << "月" << d.GetDay() << "日" << endl;
}
方法2:友元函数,突破私有的限定
Date.h
class Date
{// 友元函数声明friend void operator<<(ostream& out, const Date& d);
};
void operator<<(ostream& out, const Date& d);
友元声明在任意位置都可以,不考虑访问限定符
我是你的朋友,在我里面就可以用对象访问你的私有
Date.cpp
void operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
Test.cpp
void TestDate7()
{Date d1(1949, 10, 1);cout << d1; // 改为调全局函数 operator<<(cout, d1);operator<<(cout, d1);
}
不能写成 const ostream& out :流插入就是往 out(cout 的别名)写东西,会改变 cout
连续流插入
// Test.cpp
void TestDate8()
{Date d1(1945, 9, 3);Date d2(1949, 10, 1);Date d3(2025, 9, 3);// d1 = d2 = d3; 连续赋值,从右往左cout << d1 << d2 << d3;
}
运算符特性:连续流插入,从左往右
先 d2 流向 cout(调上面的函数);为了支持连续,得有返回值,返回值应该是 cout;cout 再做左操作数支持 d3 流插入
out 就是 cout(全局对象),出了作用域还在。所以应该返回 ostream&
Date.h
class Date
{// 友元函数声明friend ostream& operator<<(ostream& out, const Date& d);
};
ostream& operator<<(ostream& out, const Date& d);
Date.cpp
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}
流提取
Date.h
class Date
{friend istream& operator>>(istream& in, Date& d);
};
istream& operator>>(istream& in, Date& d);
不能写成 const istream& in:因为要改 istream 这个对象里的一些状态值
Date.cpp
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
Test.cpp
void TestDate9()
{Date d1;Date d2;cin >> d1 >> d2; // 输入数据到终端,再提取到内存cout << d1 << d2; // 内存流向终端
}
一切皆可输入输出
检查
因为我们封装了,所有的对象都是构造出来的。在构造时加些检查
// Date.cpp
Date::Date(int year, int month, int day) // 构造
{if (month > 0 && month < 13&& day > 0 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}
}// Test.cpp
void TestDate10()
{Date d1(2019, 13, 1);cout << d1;
}
void TestDate10()
{Date d1;cin >> d1;cout << d1;
}
// Date.cpp
istream& operator>>(istream& in, Date& d)
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13&& day > 0 && day <= d.GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}
const
void TestDate11()
{Date d1(2015, 9, 3);d1.Print();const Date d2(2019, 10, 1);d2.Print(); // 报错// “void Date::Print(void)”: 不能将“this”指针从“const Date”转换为“Date &”
}
d2 不能被改变,取地址 --> 指针指向的内容不能被改变
怎么把 this 指针改为 const Date* ? 因为 this 是隐含的,所以不能显示写
Date.h
class Date
{
public:void Print() const{cout << _year << "-" << _month << "-" << _day << endl;}
}
成员函数后面加 const 以后,普通和 const 对象都可以调用。const 修饰 *this,不修饰 this
结论:不是所有的成员函数后面都加这个 const 。要修改对象成员变量的函数不能加
只要成员函数内部不修改成员变量,都在后面加 const
这样 const对象 和 普通对象 都可以调用上面为了看起来方便,实际 const 修饰后会变成 const Date* const this
+= 不能加 const +、比较大小 可以加 const
五. 取地址运算符重载
自定义类型只要用运算符就要重载
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可。只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
class Date
{
public:Date* operator&(){cout << "Date* operator&()" << endl;return this;// return nullptr; 不想让别人取到这个普通对象的地址}const Date* operator&() const{cout << "const Date* operator&() const" << endl;return this;}private:int _year = 1;int _month = 1;int _day = 1;
};int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}
这俩函数构成重载
六. 日期类
Date.h
class Date
{// 友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1); // 构造 void Print() const{cout << _year << "-" << _month << "-" << _day << endl;}bool operator<(const Date& x) const;bool operator==(const Date& x) const;bool operator<=(const Date& x) const;bool operator>(const Date& x) const;bool operator>=(const Date& x) const;bool operator!=(const Date& x) const;int GetMonthDay(int year, int month); // 天数复杂,最好实现子函数Date& operator+=(int day); // d1 + 100Date operator+(int day) const; // d2 + 100Date& operator-=(int day); // d1 + 100Date operator-(int day) const; // d2 + 100Date& operator++(); // 前置++Date operator++(int); // 后置++Date& operator--(); // 前置--Date operator--(int); // 后置--int operator-(const Date& d) const;private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
Date.cpp
Date::Date(int year, int month, int day) // 构造
{if (month > 0 && month < 13&& day > 0 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}
}bool Date::operator<(const Date& x) const
{if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;
}bool Date::operator==(const Date& x) const
{return _year == x._year&& _month == x._month&& _day == x._day;
}// d1(*this) <= d2(x) 复用
bool Date::operator<=(const Date& x) const
{return *this < x || *this == x;
}bool Date::operator>(const Date& x) const
{return !(*this <= x);
}bool Date::operator>=(const Date& x) const
{return !(*this < x);
}bool Date::operator!=(const Date& x) const
{return !(*this == x);
}int Date::GetMonthDay(int year, int month)
{// 不需要:每次调用都创建static int daysArr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//if (((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) && month == 2)if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))return 29;elsereturn daysArr[month];
}Date& Date::operator+=(int day)
{if (day < 0){return *this -= -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 // + 复用 +=
{Date tmp(*this); // 拷贝构造一个tmp += day;return tmp; // 出了作用域,tmp销毁,不能用引用返回
}Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){--_month;if (_month == 0){_month = 12;--_year;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day) const
{Date tmp(*this);tmp -= day;return tmp;
}Date& Date::operator++() // 前置++
{*this += 1;return *this;
}Date Date::operator++(int) // 后置++
{Date tmp = *this;*this += 1;return tmp;
}Date& Date::operator--() // 前置--
{*this -= 1;return *this;
}Date Date::operator--(int) // 后置--
{Date tmp = *this;*this -= 1;return tmp;
}// d1 - d2
int Date::operator-(const Date& d) const
{// 默认认为d1大,d2小Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}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)
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13&& day > 0 && day <= d.GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}
本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注。
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章