lesson-2C++类与对象(中)(一)+https://developer.aliyun.com/article/1393889
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
Stack(int capacity) { _capacity = capacity; int top = 0; _a = (int*)malloc(sizeof(int) * _capacity); if (_a == nullptr) { perror("malloc"); } cout << "Stack的构造" << endl; } //拷贝构造 Stack(const Stack& st) { _top = st._top; _capacity = st._capacity; _a = (int*)malloc(sizeof(int) * _capacity); if (_a == nullptr) { perror("malloc"); } }
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。
拷贝构造参数不引用为什么会引发无穷递归呢?
首先要明白在传值拷贝自定义类型定义出的对象时,编译器会去调用他的拷贝构造,我们传值传参Stack类型的对象,也就是要拷贝一份给参数,所以编译器会去调这个对象的拷贝构造,我们去掉引用后的拷贝构造是这个样子的
Stack(const Stack st) { _top = st._top; _capacity = st._capacity; _a = (int*)malloc(sizeof(int) * _capacity); if (_a == nullptr) { perror("malloc"); } }
接下来我们通过图解来看一看无穷递归的出现原因。
所以我们要加上&。
3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
我们的日期类是不用我们再去单独写拷贝构造函数的,因为系统默认的拷贝构造函数就是值拷贝。
4 拷贝构造函数典型调用场景:
使用已存在对象创建新对象
Stack st2(st1);
函数参数类型为类类型对象
当然,一般来说,没有特别要求,我们还是推荐使用引用的,效率会高些。
void func2(Stack st) { //... }
函数返回值类型为类类型对象
Date func3(Date d) { //如果出了作用域,传值给d的对象还存在, //那么引用返回更好,这里我们只是为了举例子 //而且我们是推荐传引用的 return d; }
返回的时候,d已然销毁,所以我们返回的其实是d的拷贝。
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用。
赋值运算符重载
这里我们还是会通过一些例子来引入这个概念。
我们打算写个日期类来解释,在此之前,我们需要先明白,日期和日期的相加减有没有意义,比如
2023-10-23 + 2023-10-24,显然是没有意义的,但是他们之间相减就有了意义;两个日期相差多少天,那么日期间的乘除也是没有意义的;再看2023-10-23 + 50天,这个显然也是有意义的,计算50天日期是多少;这么说的话,其实日期的比较也是有意义的,相等和大小。
那么我们如何去实现日期间的比较和日期加减天数呢?
但是这些成员变量都是私有的,在类外我们不能直接用,当然,我们可以在类里写上GetYear函数等来取得他们的值进行比较,我们这里只是测试,就先将他们公开,当然这样的代码是不健康的,但是我们也说了,只是测试。
class Date { public: Date(int year = 2023, int month = 10, int day = 22) { _year = year; _month = month; _day = day; cout << "Date的构造" << endl; } int _year; int _month; int _day; }; bool equal(Date d1, Date d2) { if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day) return true; else return false; } int main() { Date d1; Date d2(2023, 2, 13); bool ret = equal(d1, d2); cout << ret << endl; return 0; }
当然,这样会更好些
bool equal(const Date& d1, const Date& d2) { if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day) return true; else return false; }
接下来进行大小比较函数,起个什么名字好呢?compare?比大还是比小?我怎么知道,compare_large? 当下,我们先随便起名字。
class Date { public: Date(int year = 2023, int month = 10, int day = 22) { _year = year; _month = month; _day = day; cout << "Date的构造" << endl; } int _year; int _month; int _day; }; bool equal(const Date& d1, const Date& d2) { if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day) return true; else return false; } bool compare(const Date& d1, const Date& d2) { if (d1._year > d2._year) { return true; } else if (d1._year == d2._year && d1._month > d2._month) { return true; } else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day) { return true; } return false; } int main() { Date d1; Date d2(2023, 2, 13); bool ret1 = equal(d1, d2); bool ret2 = compare(d1, d2); cout << ret1 << endl; cout << ret2 << endl; return 0; }
我们可以想想内置类型定义出的变量,都是可以直接比较和相加减的,那么我自定义类型定义的对象能不能也直接比较相加减呢?答案是不能,编译器无法理解对象如何比较,比如我们的d1和d2,怎么确定大小呢?年月日都大于才算大于吗?由此,我们引入一个新的概念,运算符重载,不进解决了函数名不便于理解的问题,还可以进行对象的比较。
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
看代码前别忘了六个成员函数每个函数都有一个隐含的this指针。
class Date { public: Date(int year = 2023, int month = 10, int day = 22) { _year = year; _month = month; _day = day; cout << "Date的构造" << endl; } //比较相等 bool operator==(const Date& d2) { if (_year == d2._year &&_month == d2._month && _day == d2._day) return true; else return false; } //比较大于 bool operator>(const Date& d2) { if (_year > d2._year) { return true; } else if (_year == d2._year && _month > _month) { return true; } else if (_year == d2._year && _month == d2._month && _day > d2._day) { return true; } return false; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(2023, 2, 13); //bool ret1 = equal(d1, d2); //bool ret2 = compare(d1, d2); bool ret1 = d1 == d2; //d1.operator==(d2); bool ret2 = d1 > d2; //d1.operator>(d2); cout << ret1 << endl; cout << ret2 << endl; bool ret3 = d1.operator==(d2); bool ret4 = d1.operator>(d2); cout << ret1 << endl; cout << ret2 << endl; return 0; }
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: .注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
写在类外面,只写一个参数就是如下图结果;
因为大于的比较有两个操作数。
接下来是日期加上一个数。
·
class Date { public: Date(int year = 2023, int month = 10, int day = 22) { _year = year; _month = month; _day = day; cout << "Date的构造" << endl; } int GetMonthday(int year, int month) { int month_day[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; } return month_day[month]; } Date& operator+=(int day) { _day += day; while (_day > GetMonthday(_year,_month)) { _day -= GetMonthday(_year, _month); _month++; if (_month == 13) { _year++; _month = 1; } } return *this; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1 += 50; d1.Print(); return 0; }
但是这样我们把对象d1就做了修改,我们不想修改,只想+然后返回来一个值怎么办?
将下面这个代码加入类中
Date operator+(int day) { //实例化一个对象tmp拷贝*this Date tmp(*this); //复用operator+= tmp += 50; return tmp; }
int main() { Date d1; Date d2 = d1 + 50; d2.Print(); return 0; }
接下来就是我们的赋值运算符重载了。
Date& Date::operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; }
前置++和后置++重载
前置和后置++,最难以区分的其实是他们的名字,尽管返回值不同,但参数相同,不构成重载,于是就有了一个特殊规定,看代码
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递
//前置++复用operator+= Date& Date::operator++() { *this += 1; return *this; } //后置++复用operator+= Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; }
于是我们用参数类型来区分,一个不传,一个传int类型,编译器就可以区分了。
一般情况下我们最好还是用前置++,是引用返回,而且没有拷贝,后置++就有一次拷贝,也许一次调用差距不大,但是如果调用10万次就有差别了。
日期类的实现
接下来我们将使用各种运算符重载实现各种有意义的日期比较和加减,
头文件
#pragma once #include <iostream> using namespace std; class Date { public: Date(int year = 2023, int month = 10, int day = 24); int GetMonthday(int year, int month); //operator+复用opreator+=,优于operator+=复用operator+ Date operator+(int day); Date operator-(int day); //计算日期减天数 int operator-(const Date& d); //计算日期和日期间差几天 Date& operator+=(int day); Date& operator-=(int day); Date& operator=(const Date& d); bool operator==(const Date& d); bool operator!=(const Date& d); bool operator>(const Date& d); bool operator<(const Date& d); bool operator>=(const Date& d); bool operator<=(const Date& d); //前置++复用operator+= Date& operator++(); //后置++复用operator+= Date operator++(int); void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; };
日期类的实现
#include "Date.h" Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; } int Date::GetMonthday(int year, int month) { int month_day[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; } return month_day[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; } Date Date::operator+(int day) { Date tmp(*this); tmp += 50; return tmp; } Date Date::operator-(int day) { Date tmp(*this); tmp -= day; return tmp; } Date& Date::operator-=(int day) { if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { --_year; _month = 12; } _day += GetMonthday(_year, _month); } } Date& Date::operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; } //前置++复用operator+= Date& Date::operator++() { *this += 1; return *this; } //后置++复用operator+= Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; } bool Date::operator>(const Date& d) { if (_year > d._year) { return true; } else if (_year == d._year && _month > _month) { return true; } else if (_year == d._year && _month == d._month && _day > d._day) { return true; } return false; } bool Date::operator==(const Date& d) { if (_year == d._year && _month == d._month && _day == d._day) return true; else return false; } bool Date::operator!=(const Date& d) { return !(*this == d); } bool Date::operator<(const Date& d) { return !(*this >= d); } bool Date::operator>=(const Date& d) { return *this > d || *this == d; } bool Date::operator<=(const Date& d) { return !(*this <= d); } int Date::operator-(const Date& d) { int flag = 1; Date max = *this; Date min = d; if (max < min) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min; ++n; } return n * flag; }
测试
#include "Date.h" int main() { Date d1; //测试赋值运算符重载 Date d2 = d1; cout << "测试d2赋值运算符重载" << endl; d2.Print(); //测试+运算符重载 Date d3(2023, 10, 25); d3 = d3 + 50; cout << "测试d3+运算符重载" << endl; d3.Print(); //测试-运算符重载 Date d4(2023, 10, 25); d4 = d4 - 50; cout << "测试d4-运算符重载" << endl; d4.Print(); //测试==运算符重载 Date d5(2023, 10, 25); d5 = d5 - 50; cout << "测试d5==运算符重载" << endl; cout << (d4 == d5) << endl; //测试!=运算符重载 Date d6(2023, 10, 25); cout << "测试d6!=运算符重载" << endl; cout << (d4 == d6) << endl; //测试++运算符重载 Date d7(2023, 10, 25); cout << "测试d7++运算符重载" << endl; ++d7; d7.Print(); //测试>=运算符重载 Date d8(2023, 10, 25); cout << "测试d8>=运算符重载" << endl; cout << (d7 >= d8) << endl; cout << endl << "全部通过" << endl; return 0; }
测试结果