三、拷贝构造函数
1.概念
从拷贝这个词就可以想到,当我们在临近交作业的时候,尤其是电子档的作业时,发现作业太多,不想思考,于是就有了copy的想法;从而就产生了两个一模一样的作业,当然也有可能是一人干活,全班享受啦^-^(但是不建议大家copy)
那在创建对象时,可否创建一个与一个对象一某一样的新对象呢? 当然是可以的;
那就需要用到拷贝构造函数啦;
2.拷贝构造函数的特性
定义如下:
Data(const Data& d)
class Date { public: /*构造函数*/ Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } /*拷贝构造函数*/ Date(const Data& d) { //加const为了防止写成 d._year = _year; _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 1, 15); //拷贝复制 Date d2(d1);//调用拷贝构造---自定义类型用一个同类型的对象初始化我,就是拷贝构造 return 0; }
通过上图的分析,拷贝构造函数是必须引用传参的,为了防止被拷贝的对象被修改,加上const进行修饰;
拷贝构造函数与前两个函数一样,如果不写拷贝构造,系统会自动生成默认拷贝构造函数 ;
1.对于内置类型成员,会完成按字节序的拷贝;(称为浅拷贝)
2.对于自定义类型成员,会调用他的拷贝构造;
3.拷贝构造的注意点
以下是关于栈类的拷贝构造
class Stack { public: //构造函数 Stack(int capacity = 4) { _a = (int*)malloc(sizeof(int)); if (_a == nullptr) { cout << "malloc fail\n" << endl; exit(-1); } _top = 0; _capacity = capacity; } //拷贝构造函数 /* 编译器默认生成 */ //析构函数 ~Stack() { free(_a); _a = nullptr; _top = _capacity = 0; } private: int* _a; size_t _top; size_t _capacity; }; int main() { /*直接崩溃*/ //导致他们指向的空间被析构两次,导致程序崩溃 Stack st1(10); Stack st2(st1); return 0; }
对于栈类的拷贝构造,因为我们没有定义,编译器回去默认生成一个拷贝构造函数,实例化出st1对象后进行了一个初始化,然后对st2对象进行拷贝构造,此时并不存在任何问题,但是他们都需要进行一次析构函数,对资源进行清理;就在这个时候出现了问题;
四、赋值运算符重载
1.运算符重载
1.概念
对于内置类型的大、小进行比较,编译器可以直接处理 ,但是对于自定义类型编译不能直接处理;
比如我们定义了一个日期类,实例化出两个对象后,想要比较一下两个对象中的天数大小时,直接用运算符是完全可以的,但是想要比较d1和d2两个自定义类型的大小时,使用运算符是会发生错误的;
class Data { public: Data(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; int main() { Data d1(2022, 1, 16); Data d2(2022, 1, 31); int ret = d1._day > d2._day; cout << ret << endl; //d1 > d2;报错 return 0; }
对于以上的情况,C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字 operator 后面接需要重载的 运算符符号 。(如 operator> )
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如 operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的 操作符有一个默认的形参this,限定为第一个形参
5. .* 、 :: 、 sizeof 、 ? : (三目操作符)、 . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
2.如何定义及使用
1.在类外定义(需要将成员变量改为共有)
/*运算符重载*/ class Data { public: Data(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; /*****类外定义******/ //函数名 operator操作符 //返回类型:看操作符运算后返回值是什么 //参数:操作符有几个操作数,他就有几个参数 bool operator>(const Data& d1, const Data& 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; } else { return false; } } int main() { Data d1(2022, 1, 16); Data d2(2022, 1, 31); //默认情况下,C++是不支持自定义类型对象使用运算符 d1 > d2;//编译器看到这个就会将其转换为operator>(d1,d2),就像函数调用一样 cout << (d1 > d2) << endl; cout << operator>(d1, d2) << endl; return 0; }
对于这个函数的参数个数来说,操作符有几个操作数,参数就有几个,既然能在类内定义会发生怎样的变化呢?
2.在类内定义
class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //bool operator>(const Date& d1, const Date& d2) //按照这样去写,是错误的,操作数过多,因为隐含了一个this指针 //d1.operator > (d2); bool operator>(const Date* this, const Date& d2) //d1就传给了this,d2就传给了d2 bool operator>(const Date& d2) { if (_year > d2._year) { return true; } else if (_year == d2._year && _month > d2._month) { return true; } else if (_year == d2._year && _month == d2._month && _day > d2._day) { return true; } else { return false; } } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 1, 16); Date d2(2022, 1, 31); d1 > d2;//先去类看有没有这个运算符,没有去全局找 运算符重载一般重载为成员函数,为了解决访问私有变量的问题 //d1.operator > (d2);类里面转换为这样 return 0; }
如果直接将刚才类外定义函数直接放到类内去, 会导致操作数过多,我们直到类里面隐含了一个this指针;
在类外定义的时候,是不存在this指针的,函数有两个参数:
bool operator>(const Data& d1, const Data& d2); d1 > d2;//编译器会转换为operatot>(d1,d2); //d1传给const Data& d1;d2传给const Data& d2
在类内定义的时候由于this指针的存在,强行将上面的代码放到类内,此时形参就变成三个:
bool operator>(const Date& d1, const Date& d2);//在类外时 d1 > d2;//编译器会转换为operatot>(d1,d2); //d1传给const Date& d1;d2传给const Date& d2 bool operator>(const Date* this,const Data& d1, const Data& d2);//在类内时 d1 > d2;//编译器会转换为d1.operatot>(d2); //这里就会导致操作数过多
虽然在类外定义可以实现,但是破破坏了类的封装性,所以在类内定义时,我们只要给一个参数就可以了;
2.赋值运算符重载
1.概念
赋值运算符重载实现的是两个自定义类型的对象之间的赋值,它和拷贝构造函数不同的是:
1.拷贝构造是用一个已存在的对象去初始化一个对象;
2.赋值运算符重载是两个已存在的对象进行赋值操作;
class Date { public: //构造函数 Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //拷贝构造 Date(const Date& d) { cout << " 11111 " << endl; } //赋值运算符重载 //d1 = d3; Date operator=(const Date& d) { //防止有人d1=d1 //极端情况下自己给自己赋值,判断一下地址就直接跳过了 if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 1, 16); Date d2(2022, 1, 31); Date d3(2022, 2, 26); //一个已经存在的对象拷贝初始化一个马上创建实例化的对象 Date d4(d1);//拷贝构造 Date d5 = d1; //两个已经存在的对象,之间进行赋值拷贝 d2 = d1 = d3; //d1.operator=(d3); //d2.operator=(d1.operator=(d3)); return 0; }
因为赋值运算符重载也是在类内定义的,这里只需要给一个参数就可以;
在类中如果如果没有显示定义赋值重载函数,编译器也会在类中默认 生成一个赋值重载函数,对于内置类型的成员采用浅拷贝,对于自定义类型的成员会去调用它的赋值重载函数;