目录
类的6个默认成员函数
概念:若一个类中没有写任何成员,我们简称为空类。然而空类并不是什么都没有。任何类在什么都不写时,编译器会自动生成6个默认成员函数
1.构造函数
在C语言阶段学习数据结构的过程中,我们会使用到结构体。而我们通常在完成增、删、查、改四个主要函数时,还需完成初始化函数以及销毁函数,初始化函数帮助我们将创建好的结构体数据初始化,以防止数据为随机数(内存残余数据)影响结果,销毁函数则负责将结构体动态开辟的内存归还给系统以防止长时间向内存申请却不归还导致内存耗尽。
而C++的类也不例外,而且这次C++的类中将初始化与销毁做成了默认成员函数“构造函数”与“析构函数”,他们即使是在空类中也存在(编译器自动生成)也可由用户自己实现,若用户自己实现,则根据情况调用用户实现的构造函数之一(函数重载),而且与C语言不同,他们作为默认成员函数也不需要由我们调用,构造函数会在类的创建时自动调用(编译器自动调用)。
概念
构造函数是一个特殊的成员函数,名字与类名相同,在创建类的对象的时,由编译器自动调用。保证每个数据成员都有一个合适的初始值,且构造函数在对象整个生命周期内只调用一次
注意
1、构造函数本质就是初始化函数
2、若用户显式的写了构造函数,编译器会调用用户所写的构造函数。若用户没有写构造函数,编译器会调用编译器默认生成的构造函数
3、编译器默认生成的构造函数对内置类型(int ,double ,char ……等)是不做处理的。而对于自定义类型会去调用它的构造函数
4、构造函数是支持函数重载
5、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
注:C++11中针对内置类型成员不参与初始化的缺陷,新增了补丁——内置类型成员变量在类中声明时可以给缺省值
特点
1.构造函数的函数名与类名相同
2.构造函数没有返回值(不需要写void)
3.对象实例化时编译器会自动调用对应的构造函数
4.构造函数支持重载
例:
class Date { public: //全缺省构造函数 Date(int year = 1990, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //无参构造函数 Date() { _year = 1999; _month = 1; _day = 1; } private: int _year; int _month; int _day; };
注:自己写的 无参的构造函数、全缺省的构造函数 、以及没写编译器自动生成的构造函数,都称为默认构造函数,并且默认构造函数只能有一个,若像上述代码那样写了多个,在类的创建时编译器会因为无法判定调用哪个构造函数而报错
2.析构函数
概念
析构函数是一个特殊的成员函数,函数名是在类名的前面加" ~ "符号,创建的类对象在生命周期结束的时会自动调用析构函数。析构函数是完成对象中资源的清理工作
工作原理:
1、析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
2、比如在对象中存在动态开辟的空间析构函数的作用就是释放这些空间
注意:
1、析构函数名是在类名前加上" ~ "
2、析构函数在对象生命周期结束的时候会自动调用
3、析构函数不支持重载
4、一个类只能有一个析构函数,若用户显式的写了编译器是不会自动生成的。若用户没有写,编译器会自动生成一个析构函数,并在对象销毁时调用
5、析构函数对内置类型不处理,因为内置类型出了作用域编译器就将它销毁,而对于自定义类型会去调用它的析构函数
6、如果类中没有申请空间的资源时,析构函数是可以不显式写的,直接使用编译器生成的默认析构函数
class Stack { public: Stack(size_t capacity = 3) { _array = (DataType*)malloc(sizeof(DataType) * capacity); if (NULL == _array) { perror("malloc申请空间失败"); return; } _capacity = capacity; _size = 0; } //若我们自己写了析构函数编译器就不会默认生成 //析构函数 ~Stack() { if (_array) { free(_array); _array = NULL; _capacity = 0; _size = 0; } } private: DataType* _array; int _capacity; int _size; };
3.拷贝构造函数
概念:
在创建对象时,创造一个与已经存在的对象一模一样的新对象,此时就可以用到拷贝构造函数
拷贝构造函数,是用已经存在的对象去初始化新对象。函数只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已经存在的类类型对象去初始化新对象时,由编译器自动调用
例:
class Date { public: Date(int year = 1990, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //拷贝构造 Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; };
注意:
1、拷贝构造是一个特殊的成员函数,是用已经存在的对象去初始化新对象。
2、编译器会自动调用
3、若用户没有显式定义,编译器会自动生成一个默认拷贝构造
4、若用户现式定义了拷贝构造,编译器不会自己生成,直接调用我们写的拷贝构造
5、只有一个形参,且参数只能是该类 类型对象的引用
特性:
1、拷贝构造是构造函数的重载形式
2、拷贝构造函数的参数只有一个且必须是类类型对象的引用,若使用传值方式编译器会直接报错,因为传值调用会需要生成一个临时的新类,而这个生成时会调用拷贝构造,然后在重复,陷入无限循环使程序崩溃
3、编译器默认生成的拷贝构造函数按字节序进行拷贝(浅拷贝)
4、编译器默认生成的拷贝构造函数,对自定义类型是调用其拷贝构造函数完成拷贝的
5、类中如果没有涉及资源申请时,拷贝构造函数我们自己写或者用编译器生成的都可以。若涉及到资源申请时,则拷贝构造是一定要自己写的,因为涉及资源申请的拷贝为深拷贝,编译器生成的只能完成浅拷贝
4.赋值运算符重载
我们在编写程序时,对于内置类型(如:int 、char 等),都可以直接使用“+”、“-”、“++”、“--”等常用操作符,而对于我们的自定义类型,如C语言中的结构体、C++中的类,我们正常情况下都不能直接使用上述操作符进行自定义类型之间的操作。
我们在C语言实现很多自定义类型的时候,偶尔会想到要是两个结构体可以直接相加相除该多好啊
而在C++中,这一想法成为了现实,我们可以将运算符重载,针对特性的类,运算符的操作将由我们重新定义。
C++中为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回类型和参数列表与普通函数类似
使用方法:
函数名称为:关键字:operator后面跟要重载的运算符号。
函数原型:返回值类型 operator运算符(参数列表)
注意:
1、不能通过连接其它符号来创建新的操作符:例如operator@
2、重载操作必须有一个类类型参数
3、用于内置类型的运算符,其含义不能改变,例如:内置类型+ 不能改变其含义
4、做为类成员函数重载时,其形参看起来比操作数少1,因为成员函数的第一个参数为隐式的this
5、" .* " 、" :: " 、" sizeof " 、" ?: " 、" . " 注意这五个运算符不能进行重载
4.1运算符重载
以上述的日期类为例,对该类运算符进行重载
class Date { public: int GetMonthDay(int year, int month) { int monthday[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 monthday[month]; } Date(int year = 1990, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(Date& d) { _year = d._year; _month = d._month; _day = d._day; } Date operator+(int day) { _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _year++; _month = 1; } } return *this; } int operator-(const Date& d) { Date big; Date small; if (*this > d) { big = *this; small = d; } else { big = d, small = *this; } int n = 0; while (small != big) { small = small + 1; n++; } return n; } private: int _year; int _month; int _day; };
注意:
1、将运算符重载到全局位置,此时为了很好的使用该运算符,就需要将类的成员公有化
4.2前置++/--与后置++/--重载
针对加减的运算符重载已经实现了,那么“++”、“--”相对来说就容易了很多。
但是这里要注意,“++”、“--”分前置和后置两种,要如何区分这两种也是重点
前置++/--:返回的是+1/-1之后的值
后置++/--:返回的是+1/-1之前的值
针对前置与后置的问题,C++有如下规定:
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器 自动传递
// 前置-- Date& operator--() // 后置-- Date operator--(int) // 后置++ Date operator++(int) // 前置++ Date& operator++()
总结:
在重载两者运算符时,前置++的参数只有一个隐含的this指针,而后置++的参数除了隐含的this指针之外,还有一个int的参数,而该参数只是为了跟前置++做区分而写的,不需要我们自己传参,编译器会默认传
4.3=赋值运算符重载
赋值运算符重载格式
参数类型:
const 类& (传递对象的引用可以提高传参效率)
赋值运算符重载,是类的默认成员函数,若我们显示书写,必须写在类里面,而传递的参数有两个,一个是隐含的this指针,另一个是const修饰的对象的引用,将该对象的值赋值给this所指向的对象
返回值类型:
对象& (返回引用可以提高返回效率,有返回值目的是为了支持连续赋值)
返回的是对象的引用,实际上返回的是this指针指向对象的引用,效率高,若直接返回对象,会调用拷贝构造,效率下降
检测是否自己给自己赋值:
判断一下是否是自己给自己赋值,若是自己给自己赋值则不进行赋值
返回*this:
为了保证可以连续赋值(A = B = C),需要返回*this
实现连续赋值,要返回this指针指向的对象的引用,*this就是对该对象解引用(对指针解引用取到的是它所指向的内容)
class Date { public: void Print() { cout << _year <<"." << _month <<"." << _day << endl; } // 获取某年某月的天数 int GetMonthDay(int year, int month) { assert(year > 0); assert(month > 0); assert(month <= 12); int monthday[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; } int ret = monthday[month]; return ret; } //全缺省构造函数 Date(int year = 1980, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //拷贝构造函数 Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } //析构函数 日期类没有自定义结构,编译器生成的析构函数就能满足需求 //~Date(); // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; } // 日期+=天数 Date& operator+=(int day) { _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _month = 1; _year++; } } return *this; } // 日期+天数 Date operator+(int day) { Date ret(*this); ret._day += day; while (ret._day > GetMonthDay(ret._year, ret._month)) { ret._day -= GetMonthDay(ret._year, ret._month); ret._month++; if (ret._month > 12) { ret._month = 1; ret._year++; } } return ret; } // 日期-天数 Date operator-(int day) { assert(day < 0); Date ret(*this); while (day >= ret._day && day > 0) { day -= ret._day; ret._month--; if (ret._month <= 0) { ret._month = 12; ret._year--; } ret._day = GetMonthDay(ret._year, ret._month); } if (day > 0) { ret._day -= day; } //_day -= day; //if (_day < 1) //{ //} return ret; } // 日期-=天数 Date& operator-=(int day) { assert(day > 0); while (day >= _day && day > 0) { day -= _day; _month--; if (_month <= 0) { _month = 12; _year--; } _day = GetMonthDay(_year, _month); } if (day > 0) { _day -= day; } return *this; } // 前置++ Date& operator++() { /*_day += 1; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _month = 1; _year++; } }*/ * this += 1; return *this; } // 后置++ Date operator++(int) { //_day += 1; //while (_day > GetMonthDay(_year, _month)) //{ // _day -= GetMonthDay(_year, _month); // _month++; // if (_month > 12) // { // _month = 1; // _year++; // } //} Date ret(*this); *this += 1; return ret; } // 后置-- Date operator--(int) { //_day--; //if (_day <= 0) //{ // _month--; // if (_month <= 0) // { // _month = 12; // _year--; // } // _day = GetMonthDay(_year, _month); //} Date ret(*this); *this -= 1; return ret; } // 前置-- Date& operator--() { //_day--; //if (_day <= 0) //{ // _month--; // if (_month <= 0) // { // _month = 12; // _year--; // } // _day = GetMonthDay(_year, _month); //} * this -= 1; return *this; } // 日期-日期 返回天数 int operator-(const Date& d) { Date big, small; if (*this > d) { big = (*this); small = d; int ret = 0; while (big != small) { big--; ret++; } return ret; } else { big = d; small = (*this); int ret = 0; while (big != small) { big--; ret++; } return -ret; } } private: int _year; int _month; int _day; };
注意:
赋值运算符只能重载到类里面不能重载到全局函数
原因:
赋值运算符如果不显式实现,编译器会生成一个默认的。用户在类外自己实现 一个全局的赋值运算符重载,编译器会在类中默认实现一个赋值运算符重载。用户实现的和编译器在类中生成的默认赋值运算符重载冲突了,因此赋值运算符重载只能是类的成员函数
日期类的实现
既然赋值运算操作符重载都已经学完了,也可以试着将">"、"<"等比较运算操作符实现,以此完成日期类的实现
class Date { public: void Print() { cout << _year <<"." << _month <<"." << _day << endl; } // 获取某年某月的天数 int GetMonthDay(int year, int month) { assert(year > 0); assert(month > 0); assert(month <= 12); int monthday[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; } int ret = monthday[month]; return ret; } //全缺省构造函数 Date(int year = 1980, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //拷贝构造函数 Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } //析构函数 日期类没有自定义结构,编译器生成的析构函数就能满足需求 //~Date(); // 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3) Date& operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; } // 日期+=天数 Date& operator+=(int day) { _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _month = 1; _year++; } } return *this; } // 日期+天数 Date operator+(int day) { Date ret(*this); ret._day += day; while (ret._day > GetMonthDay(ret._year, ret._month)) { ret._day -= GetMonthDay(ret._year, ret._month); ret._month++; if (ret._month > 12) { ret._month = 1; ret._year++; } } return ret; } // 日期-天数 Date operator-(int day) { assert(day < 0); Date ret(*this); while (day >= ret._day && day > 0) { day -= ret._day; ret._month--; if (ret._month <= 0) { ret._month = 12; ret._year--; } ret._day = GetMonthDay(ret._year, ret._month); } if (day > 0) { ret._day -= day; } //_day -= day; //if (_day < 1) //{ //} return ret; } // 日期-=天数 Date& operator-=(int day) { assert(day > 0); while (day >= _day && day > 0) { day -= _day; _month--; if (_month <= 0) { _month = 12; _year--; } _day = GetMonthDay(_year, _month); } if (day > 0) { _day -= day; } return *this; } // 前置++ Date& operator++() { /*_day += 1; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month > 12) { _month = 1; _year++; } }*/ * this += 1; return *this; } // 后置++ Date operator++(int) { //_day += 1; //while (_day > GetMonthDay(_year, _month)) //{ // _day -= GetMonthDay(_year, _month); // _month++; // if (_month > 12) // { // _month = 1; // _year++; // } //} Date ret(*this); *this += 1; return ret; } // 后置-- Date operator--(int) { //_day--; //if (_day <= 0) //{ // _month--; // if (_month <= 0) // { // _month = 12; // _year--; // } // _day = GetMonthDay(_year, _month); //} Date ret(*this); *this -= 1; return ret; } // 前置-- Date& operator--() { //_day--; //if (_day <= 0) //{ // _month--; // if (_month <= 0) // { // _month = 12; // _year--; // } // _day = GetMonthDay(_year, _month); //} * this -= 1; return *this; } // >运算符重载 bool operator>(const Date& d) { if (_year == d._year) { if (_month == d._month) { if (_day == d._day) { return false; } return _day > d._day ? true : false; } else { return _month > d._month ? true : false; } } else { return _year > d._year ? true : false; } } // ==运算符重载 bool operator==(const Date& d) { if (_year == d._year && _month == d._month && _day == d._day) { return true; } return false; } // >=运算符重载 bool operator >= (const Date& d) { return !((*this) < d); } // <运算符重载 bool operator < (const Date& d) { if (*this == d) { return true; } return !((*this) > d); } // <=运算符重载 bool operator <= (const Date& d) { return !(*this > d); } // !=运算符重载 bool operator != (const Date& d) { return !(*this == d); } // 日期-日期 返回天数 int operator-(const Date& d) { Date big, small; if (*this > d) { big = (*this); small = d; int ret = 0; while (big != small) { big--; ret++; } return ret; } else { big = d; small = (*this); int ret = 0; while (big != small) { big--; ret++; } return -ret; } } private: int _year; int _month; int _day; }; int main() { Date A(2023, 6, 21); A.Print(); Date B(2022, 5, 30); cout << A - B << endl; Date C(2023, 5, 5); Date D(1930, 10, 1); cout << D - C << endl; Date d2(2023, 5, 5); Date d3(2023, 5, 5); Date ret2 = --d2; Date ret3 = d3--; d2.Print(); ret2.Print(); ret3.Print(); //int a = 2023; //int b = a--; //int c = 2023; //int d = --c; //cout << b<<" " << d << endl; return 0; }
5.&取地址操作符重载
在C++中取地址运算符重载也是类的默认成员函数之一。
用户不显示写,编译器会默认生成。
若用户显式写,编译器不会生成。
若用户显式写了类的默认成员函数则必须写在类中,不可写在类外面(写在类外面,编译器会默认生成一个默认成员函数,在调用该函数时会出现调用不明确的错误)
class Date { public: //我们显式写,编译器不会生成 //取地址运算符重载 Date* operator&() { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1; cout << &d1 << endl; return 0; }
默认生成的取地址运算符:
一般情况下,取地址默认成员函数,不需要我们自己写,默认使用编译器生成就足够使用
class Date { private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { //直接使用编译器默认生成的取地址,同样可以完成目的 Date d1; cout << &d1 << endl; return 0; }
除非我们希望保证类对象地址的安全,不然都可以使用编译器自动生成的取地址操作符重载
但是当我们需要保证地址不能被取到时,就需要自己完成取地址操作符重载
class Date { public: //不希望对象变量的地址被取到时,可以这样写 Date* operator&() { return nullptr; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1; //编译器调用我们自己写的默认取地址重载函数 //取不到对象的地址 cout << &d1 << endl; return 0; }
6.const & 取地址操作符重载
6.1 const 成员
概念:
用const 修饰的变量及参数,说明其是不可被改变的
const 修饰*this:
而在类中用 const 修饰的成员函数 称之为 const成员函数,在类中const修饰成员函数,实际上修饰的是该成员函数隐含的this指针(const修饰的是*this,表示this指针指向的内容不可被修改)表示在该成员函数中不能对类的任何成员进行修改
隐含的this指针,默认是使用const修饰的,这里const修饰的是this本身,表示this的指向不可更改,而指向的内容可以更改
但若是使用const修饰*this,说明const修饰的是this所指向的内容,表示this所指向的内容不可更改,而this的指向对象可以更改
书写位置:
修饰*this的const是要写在成员函数括号后面的,表示修饰*this ,tihs指针指向的内容不可被修改
#include <iostream> using namespace std; class Date { public: //构造 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //以下两个Print函数 // 构成函数重载 // 因为参数类型不同 // 一个是const修饰,一个不是 void Print() { cout << "Print()" << endl; cout << "year:" << _year << endl; cout << "month:" << _month << endl; cout << "day:" << _day << endl << endl; } //const修饰的是隐含的this指针 //修饰的是*this 指针指向的内容不可被修改 //也就是不能通过该成员函数去修改类的成员 void Print() const { cout << "Print()const" << endl; cout << "year:" << _year << endl; cout << "month:" << _month << endl; cout << "day:" << _day << endl << endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { //const修饰的对象 //会去调用const成员函数 Date d1(2022, 1, 13); d1.Print(); const Date d2(2022, 1, 13); d2.Print(); return 0; }
6.2const & 运算符重载
概念:
它也是默认成员函数之一,用来取到const修饰的对象的地址,若不写编译器会自动生成
class Date { public: Date(int year = 0, int month = 0, int day = 0) { _year = year; _month = month; _day = day; } //此处括号后面const修饰的是隐含的this指针 //修饰*this this指向的内容不可被修改 //即该成员函数不可修改类的成员 const Date* operator&()const { cout << "const Date* operator&()" << endl; return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1(2023,4,4); const Date d2(2023,5,15); //const修饰的对象,要在定义的时候初始化 //因为const修饰的后期是不可改变的,所以必须要有初始值! cout << &d1 << endl; cout << &d2 << endl; return 0; }