🐶博主主页:@ᰔᩚ. 一怀明月ꦿ
❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C++,linux
🔥座右铭:“不要等到什么都没有了,才下定决心去做”
🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀
目录
拷贝构造函数
赋值运算符重载
运算符重载
日期类相关的所有操作符的重载
重载赋值运算符
拷贝构造和赋值运算符的显著区别
拷贝构造函数
那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象,创建新对象时由编译器自动调用。
特征
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引起无穷递归
3.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
class Date1 { public: Date1(int year=0,int month=0,int day=0) { _year=year; _month=month; _day=day; } private: int _year; int _month; int _day; }; int main() { Date1 d1(2023,7,23); //这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。 Date1 d2(d1); return 0; }
4. 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
class Stack { public: Stack(int n) { _a=(int*)malloc(sizeof(int)*n); _top=0; } void STPush(int x) { _a[_top]=x; _top++; } void STPop() { assert(!STEmpty()); _top--; } bool STEmpty() { return _top; } int STsize() { return _top; } void show() { int count=_top-1; while(count>=0) { cout<<_a[count]<<endl; count--; } } ~Stack() { free(_a); } private: int* _a; int _top; }; int main() { Stack s1(10); Stack s2(s1); return 0; } 结果: 7_21(2689,0x1000ebe00) malloc: *** error for object 0x100643370: pointer being freed was not allocated 7_21(2689,0x1000ebe00) malloc: *** set a breakpoint in malloc_error_break to debug
这里就会有问题了,因为我们没有显示定义拷贝构造函数,所以编译器使用的默认浅拷贝,对象中s1中的_a数组是把首地址拷贝给s2,析构的时候,先析构s2,在析构s1,问题出现在这,_a这一个空间,释放了两次,所以编译器就会报错。
我们这里就需要自己先定义一个,拷贝构造函数,实现深拷贝
//拷贝构造函数 //深拷贝 Stack(const Stack& st) { _a=(int*)malloc(sizeof(int)*st._capacity); memcpy(_a, st._a, sizeof(int)*st._top); _top=st._top; _capacity=st._capacity; }
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@(必须是已经存在的操作符)
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
5. .* ,:: ,sizeof ,?: ,. 注意以上5个运算符不能重载
class Date1 { public: Date1(int year=0,int month=0,int day=0) { _year=year; _month=month; _day=day; } //bool operator==(Date1* this,const Date1& di) //这里需要注意的是,左操作数是this指向的调用函数的对象 bool operator==(const Date1& di) { return _year==di._year&&_month==di._month&&_day==di._day; } private: int _year; int _month; int _day; };
日期类相关的所有操作符的重载
class Date { public: Date(int year=0,int month=0,int day=0); Date(const Date& d); // << friend ostream& operator<<(ostream& out,Date d); //赋值运算符的重载 Date& operator=(const Date& d); int GetMonthDay(int year,int month); //日期+=天数 Date& operator+=(int day); //日期+天数 Date operator+(int day); // 日期-=天数 Date& operator-=(int day); // 日期-天数 Date operator-(int day); //前置++ Date& operator++(); //后置++ Date operator++(int); //前置-- Date& operator--(); //后置-- Date operator--(int); //== 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); int GetYearDay(int year); //日期-日期 返回天数 int operator-(const Date& d); void show(); ~Date(); private: int _year; int _month; int _day; }; ostream& operator<<(ostream& out,Date d) { out<<d._year<<"/"<<d._month<<"/"<<d._day<<endl; return out; } Date::Date (int year,int month,int day) { _year=year; _month=month; _day=day; } Date:: Date(const Date& d) { _year=d._year; _month=d._month; _day=d._day; } Date& Date::operator=(const Date& d) { if(this!=&d) { _day=d._day; _month=d._month; _year=d._year; } return *this; } int Date::GetMonthDay(int year,int month) { int a[]={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 a[month]+1; return a[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>12) { _month=1; _year++; } } return *this; } //日期+天数 Date Date::operator+(int day) { Date temp(*this); temp+=day; return temp; } // 日期-=天数 Date& Date::operator-=(int day) { if(day<0) return *this+=(-day); while((_day-day)<0) { _month--; if(_month<=0) { _month=12; _year--; } day-=GetMonthDay(_year, _month); } _day-=day; return *this; } // 日期-天数 Date Date::operator-(int day) { Date temp(*this); temp-=day; return temp; } //前置++ Date& Date::operator++() { *this+=1; return *this; } //后置++ Date Date::operator++(int) { Date temp(*this); temp+=1; return temp; } //前置-- Date& Date::operator--() { *this-=1; return *this; } //后置-- Date Date::operator--(int) { Date temp(*this); temp-=1; return temp; } //== bool Date::operator==(const Date& d) { if(_year!=d._year) return false; else { if(_month!=d._month) return false; else { if(_day!=d._day) return false; return true; } } } //!= 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) { 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; else return false; } } } // <= bool Date::operator<=(const Date& d) { return (*this<d)||(*this==d); } // < bool Date::operator<(const Date& d) { 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; else return false; } } } int Date::GetYearDay(int year) { if((year%4==0&&year%100!=0)||year%400==0) return 366; return 365; } //日期-日期 返回天数 //int Date::operator-(const Date& d) //{ // int day=0,month=0,year=0; // year=d._year; // month=d._month; // day=d._day; // while(_month>0) // { // _day+=GetMonthDay(_year, _month); // _month--; // } // while(month>0) // { // day+=GetMonthDay(year, month); // month--; // } // int CountY=_year-year; // if(CountY>0) // { // while(CountY--) // { // _day+=GetYearDay(year++); // } // } // return _day-day; //} int Date::operator-(const Date& d) { Date min=d; Date max=*this; int flag=1; if(*this<d) { min=*this; max=d; flag=-1; } int n=0; while(min!=max) { ++min; n++; } return n*flag; } void Date::show() { cout<<_year<<"/"<<_month<<"/"<<_day<<endl; } Date::~Date() { cout<<"日期功能结束"<<endl; }
重载赋值运算符
在定义的同时进行赋值叫做初始化,定义完成以后再赋值(不管在定义的时候有没有赋值)就叫做赋值。初始化只能有一次,赋值可以有多次。
当以拷贝的方式初始化一个对象时,会调用拷贝构造函数;当给一个对象赋值时,会调用重载过的赋值运算符。即使没有显式的重载赋值运算符,编译器也会以默认地方式重载它。默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,这和默认拷贝构造函数的类似。
编译器默认的赋值运算符
#include<iostream> using namespace std; class Date { public: Date(int year=0,int month=0,int day=0): _year(year), _month(month), _day(day){} void show() { cout<<_year<<"/"<<_month<<"/"<<_day<<endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023,7,27); Date d2; d2=d1;//这里我们没有显示对赋值运算符重载,这里使用的编译器中默认的赋值运算符重载函数 d1.show(); d2.show(); return 0; } 结果: 2023/7/27 2023/7/27
对于简单的类,默认的赋值运算符一般就够用了,我们也没有必要再显式地重载它。但是当类持有其它资源时,例如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认的赋值运算符就不能处理了,我们必须显式地重载它,这样才能将原有对象的所有数据都赋值给新对象。
例如:
#include<iostream> using namespace std; class Date { public: Date(int year=0,int month=0,int day=0): _year(year), _month(month), _day(day) { _cal =(int*)malloc(sizeof(int)*10); } void show() { cout<<_year<<"/"<<_month<<"/"<<_day<<endl; } ~Date() { free(_cal); } private: int* _cal; int _year; int _month; int _day; }; int main() { Date d1(2023,7,27); Date d2; d2=d1; d1.show(); d2.show(); return 0; } 结果: 2023/7/27 2023/7/27 8_9(2462,0x1000efe00) malloc: *** error for object 0x101b2ad70: pointer being freed was not allocated 8_9(2462,0x1000efe00) malloc: *** set a breakpoint in malloc_error_break to debug
这里为什么会运行崩溃呢?Date类里面成员变量_cal,cal指向了一块动态开辟的空间,而动态开辟的空间需要我们手动释放,因此我们在析构函数中释放_cal动态开辟的空间。我们把d1赋值给d2时,其中是将d2._cal指向d1._cal指向的空间,所以赋值完成后,d1._cal和d2._cal指向同一块空间,程序结束时会调用析构函数,编译器就会先调用d2的析构函数,对d2._cal指向的空间进行释放,然后编译器再调用d2的析构函数,对d1._cal指向的空间进行释放,因为d1._cal和d2._cal指向同一块空间,这块空间已经被释放,free再次释放就会失败,所以就会运行崩溃。一句话来说就是对一块空间重复释放。这就是默认的赋值运算符实现的浅拷贝带来的问题。
怎么样上面的问题?我们可以显示定义赋值运算符,实现深拷贝的功能
Date& operator=(const Date& d) { _cal=(int*)malloc(sizeof(int)*10); memcpy(_cal, d._cal, 40); _year=d._year; _month=d._month; _day=d._day; return *this; }
赋值运算符主要有四点:
1. 参数类型
2. 返回值
3. 检测是否自己给自己赋值
4. 返回*this
5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。
拷贝构造和赋值运算符的显著区别
拷贝构造:一个对象已经创建,一个还没有
例如:Date d1(d2) Date d1=d2
赋值重载:两个对象都存在
例如:d1=d2
🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸