目录
前言:类的6个默认成员函数
一, 构造函数
1. 概念
2. 特性
二, 析构函数
2.1 概念
2.2 特性
2.3 牛刀小试
三, 拷贝构造函数
3.1概念
3. 2 特点
四, 赋值运算符重载
4. 1 运算符重载
五, const成员函数
六,取地址及const取地址操作符重载
七,练习——实现日期计算器
结语
前言:类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
一, 构造函数
1. 概念
2. 特性
- 1. 函数名与类名相同。
- 2. 无返回值
- 3. 对象实例化时编译器自动调用对应的构造函数。
- 4. 构造函数可以重载。(后面拷贝构造函数会体现)
- 5. 如果存在未自定义默认构造函数,编译器不再生成默认构造函数。
下面是一段自定义构造函数的:
- #include <iostream>
- using namespace std;
- class Date
- {
- public:
- Date(int year = 2023, int month = 5, int day = 9) // 自定义默认构造函数,设置全缺省参数,对数据进行初始化。
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main()
- {
- Date z;
- Date z1(2012, 12, 12); // 由于我们自定义构造函数支持带参数设置数据初始化。
- // 接下来,我们注释掉自定义默认构造函数,来测试一下编译器默认自动生成的构造函数。
- }
当我们注释掉我们自定义的构造函数时,我们会发现z对象 的类成员变量,依旧是随机值。这里我们不禁会想,编译器自动生成的默认构造函数似乎什么也没做??
- 内置类型就是语言提供的数据类型,如:int/char...;
- 自定义类型就是我们使用class/struct/union等自己定义的类型。
- #include <iostream>
- using namespace std;
- class Date
- {
- public:
- Date(int year = 2023, int month = 5, int day = 9) // 自定义默认构造函数,设置全缺省参数,对数据进行初始化。
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- };
-
- class Time
- {
- public:
- Date _t; // 自定义类型
-
- private: // 内置类型
- int _hour;
- int _minute;
- int _second;
- };
- int main()
- {
- Time k;
- }
调试结果如下:
特点:
1. 内置函数不做处理。
2. 自定义类型会调用(自定义类型的)默认构造函数。
这里我们就有了两种对内置成员初始化的方法:
- 1. 通过C++补丁初始化;()
- 2. 自定义默认构造函数,同时给缺省参数。
二, 析构函数
2.1 概念
2.2 特性
- 1. 析构函数名是在类名前加上字符 ~。
- 2. 无参数无返回值类型。
- 3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
首先我们来看下面代码:
- #include <iostream>
- using namespace std;
- class Date
- {
- public:
- Date(int year = 2023, int month = 5, int day = 9) // 默认构造函数
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- ~Date() // 默认析构函数
- {
- _year = _month = _day = 0;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main()
- {
- Date z;
- return 0;
- }
我们将显式析构函数注释掉,让我们测试一下编译器自动生成的默认析构函数的结果,下面的程序我们会看到:编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
- #include <iostream>
- using namespace std;
- class Date
- {
- public:
- Date(int year = 2023, int month = 5, int day = 9)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- ~Date()
- {
- cout << "~Date()" << endl; // 对调用Date的析构函数进行标记
- _year = _month = _day = 0;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
-
- class Time
- {
- public:
- Date _t; // 自定义类型
- ~Time()
- {
- cout << "~Time()" << endl; // 对调用Time的析构函数进行标记
- }
- private: // 内置类型
- int _hour = 10;
- int _minute = 10;
- int _second = 10;
- };
-
- void func()
- {
- Time z;
- }
- int main()
- {
- func();
- return 0;
- }
结果可以看出:
2.3 牛刀小试
看下面代码,输出顺序是什么?
- class A
- {
- public:
- A(int a = 0)
- {
- _a = a;
- cout << "A(int a = 0)->" <<_a<< endl;
- }
-
- ~A()
- {
- cout << "~A()->" <<_a<<endl;
- }
- private:
- int _a;
- };
-
- A aa3(3);
-
- void f()
- {
- static int i = 0;
- static A aa0(0);
- A aa1(1);
- A aa2(2);
- static A aa4(4);
- }
-
- // 构造顺序:3 0 1 2 4 1 2
- // 析构顺序:~2 ~1 ~2 ~1 ~4 ~0 ~3
- int main()
- {
- f();
- f();
- return 0;
- }
核心思路:遵循栈的思路,先进后出。
三, 拷贝构造函数
3.1概念
在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
3. 2 特点
- 1. 拷贝构造函数是构造函数的一个重载形式。
- 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
下面是会发生无穷递归代码:
- class Date
- {
- public:
- Date(int year = 2023, int month = 5, int day = 9)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- Date(Date b1) // 正确代码:Date(Date& b1)
- {
- _year = b1._year;
- _month = b1._month;
- _day = b1._day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
无穷递归如图:
换成类类型引用即可解决无穷递归的问题。
- class Time
- {
- public:
- Time(int hour = 1 , int minute = 2, int second = 3)
- {
- _hour = hour;
- _minute = minute;
- _second = second;
- }
-
- Time(const Time& a)
- {
- _hour = a._hour;
- _minute = a._minute;
- _second = a._second;
- cout << "Time(Time& a)" << endl;
- }
-
- ~Time()
- {
- cout << "~Time()" << endl;
- }
- private:
- int _hour;
- int _minute;
- int _second;
- };
-
- class Date
- {
- public:
- Date(int year = 2023, int month = 5, int day = 9)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- Date(const Date& b1) // 正确代码:Date(Date& b1)
- {
- _year = b1._year;
- _month = b1._month;
- _day = b1._day;
- }
-
- Time _t;
- private:
- int _year;
- int _month;
- int _day;
- };
- void func()
- {
- Date z;
- Date k(z);
- }
-
- int main()
- {
- func();
- return 0;
- }
我们发现,编译器自动生成的拷贝构造函数,足够我们进行值拷贝,那么我们还需要自定义拷贝构造函数吗?
- // 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
- typedef int DataType;
- class Stack
- {
- public:
- Stack(size_t capacity = 10)
- {
- _array = (DataType*)malloc(capacity * sizeof(DataType));
- if (nullptr == _array)
- {
- perror("malloc申请空间失败");
- return;
- }
- _size = 0;
- _capacity = capacity;
- }
- void Push(const DataType& data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
- ~Stack()
- {
- if (_array)
- {
- free(_array);
- _array = nullptr;
- _capacity = 0;
- _size = 0;
- }
- }
- private:
- DataType *_array;
- size_t _size;
- size_t _capacity;
- };
- int main()
- {
- Stack s1;
- s1.Push(1);
- s1.Push(2);
- s1.Push(3);
- s1.Push(4);
- Stack s2(s1);
- return 0;
- }
所以类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,需要深度拷贝,则拷贝构造函数是一定要写的,否则就是浅拷贝。
四, 赋值运算符重载
4. 1 运算符重载
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义。
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
下面有有几个应用例子:
- class Time
- {
- public:
- Time(int hour = 1 , int minute = 2, int second = 3)
- {
- _hour = hour;
- _minute = minute;
- _second = second;
- }
-
- Time(const Time& a)
- {
- _hour = a._hour;
- _minute = a._minute;
- _second = a._second;
- cout << "Time(Time& a)" << endl;
- }
-
- bool operator==(const Time& a)
- {
- return _hour == a._hour &&
- _minute == a._minute &&
- _second == a._second;
- }
-
- bool operator>(const Time& a)
- {
- return _hour > a._hour &&
- _minute > a._minute &&
- _second > a._second;
- }
-
- Time& operator=(const Time& a) // 内成员赋值运算符重载
- {
- _hour = a._hour;
- _minute = a._minute;
- _second = a._second;
- return *this;
- }
- ~Time()
- {
- cout << "~Time()" << endl;
- }
- private: // 内置类型
- int _hour;
- int _minute;
- int _second;
- };
这里对赋值运算符进行补充;
1.赋值运算符只能重载成类的成员函数不能重载成全局函数
- // 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
- typedef int DataType;
- class Stack
- {
- public:
- Stack(size_t capacity = 10)
- {
- _array = (DataType*)malloc(capacity * sizeof(DataType));
- if (nullptr == _array)
- {
- perror("malloc申请空间失败");
- return;
- }
- _size = 0;
- _capacity = capacity;
- }
- void Push(const DataType& data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
- ~Stack()
- {
- if (_array)
- {
- free(_array);
- _array = nullptr;
- _capacity = 0;
- _size = 0;
- }
- }
- private:
- DataType* _array;
- size_t _size;
- size_t _capacity;
- };
- int main()
- {
- Stack s1;
- s1.Push(1);
- s1.Push(2);
- s1.Push(3);
- s1.Push(4);
- Stack s2;
- s2 = s1;
- return 0;
- }
结果分析:
五, const成员函数
让我们看看下面的实例代码吧:
- class moss
- {
- public:
- moss(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- void Print()
- {
- cout << "Print()" << endl;
- cout << "year:" << _year << endl;
- cout << "month:" << _month << endl;
- cout << "day:" << _day << endl << endl;
- }
- void Print() const // 这就是const成员函数,同上面的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; // 日
- };
- void Test3()
- {
- moss d1(2022, 1, 13);
- d1.Print();
- const moss d2(2022, 1, 13);
- d2.Print();
- }
首先我们已经知道const具有限制权限的功能,比如 int this,如图:
对本次事例解析:
六,取地址及const取地址操作符重载
这个比较容易理解,这两个默认成员函数一般不用重新定义 ,编译器默认会生成。 看下面代码:
- class Date
- {
- public :
- Date* operator&()
- {
- return this ;
- }
- const Date* operator&()const
- {
- return this ;
- }
- private :
- int _year ; // 年
- int _month ; // 月
- int _day ; // 日
- };
七,练习——实现日期计算器
用C++类编写一个日期计算器,利用今天的运算符重载知识,实现日期+天数,日期-天数,日期-日期的功能。
下面是代码分享:
头文件:
- #pragma once
-
- #include<iostream>
- using namespace std;
- #include <assert.h>
-
- class Date
- {
- public:
- // 获取某年某月的天数
- int GetMonthDay(int year, int month)
- {
- // 因为需要频繁调用,所以写成内联函数。
- static int M[13] = { 0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
- int day = M[month];
- if (month == 2 &&
- ((year % 4 == 0 && year % 100 != 0) ||
- (year % 400 == 0)))
- {
- day++;
- }
- return day;
- }
- // 全缺省的构造函数
- Date(int year = 1900, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
-
- if (!CheckDate())
- {
- cout << "非法日期" << endl;
- exit(-1);
- }
- }
-
- bool CheckDate()
- {
- if (_month > 12 || _day > GetMonthDay(_year, _month))
- {
- return false;
- }
- else
- {
- return true;
- }
- }
- // 拷贝构造函数
- // d2(d1)
- Date(const Date& d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- }
-
- // 赋值运算符重载
- // d2 = d3 -> d2.operator=(&d2, d3)
- Date& operator=(const Date& d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- return *this;
- }
- // 析构函数, 全是内置函数,没什么好清理的,调用编译器自动生成的就行。
- ~Date()
- {
- ;
- }
- // 日期+=天数
- Date& operator+=(int day);
- // 日期+天数
- Date operator+(int day);
- // 日期-天数
- Date operator-(int day);
- // 日期-=天数
- Date& operator-=(int day);
- // 前置++
- Date& operator++();
- // 后置++
- Date operator++(int);
- // 后置--
- Date operator--(int);
- // 前置--
- Date& operator--();
- // >运算符重载
- 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 operator-(const Date& d);
-
- int WeekDay()
- {
- Date start(1, 1, 1);
- int LeveDay = 1;
- int day = (*this - start);
- LeveDay += day;
- int WeekDay = LeveDay % 7;
- return WeekDay;
- }
- // 友元函数为外边全局函数拥有调用类成员的权限。
- friend ostream& operator<<(ostream& cou, const Date& a);
- friend istream& operator>>(istream& cin, Date& a);
-
- void Print()const // 本身是 Date* const this--是不能修改其指向的this,
- //但可以修改其内容,为了能接受被const Date* const this的对象所以需要缩小权限(方法就是函数名后加const)
- //反正print函数没有修改功能。
- {
- cout << _year << endl;
- cout << _month << endl;
- cout << _day << endl;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- };
-
- // 放在全局中可以外部调用重载后的运算符,但放在外面又不能访问类成员,这里会用到后面的友元函数。
- // 重载cout输出流
- inline ostream& operator<<(ostream& cou, const Date& a)
- {
- cou << a._year << "年" << a._month << "月" << a._day << "日";
- return cou;
- }
-
- // 重载cint提取流
- inline istream& operator>>(istream& cin, Date& a)
- {
- cin >> a._year >> a._month >> a._day;
- assert(a.CheckDate());
- return cin;
- }
函数实现源文件:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"Date.h"
-
- // 日期+=天数
- Date& Date::operator+=(int day)
- {
- _day += day;
- while ( Date::GetMonthDay(_year, _month) < _day)
- {
- _day -= Date::GetMonthDay(_year, _month);
- _month++;
-
- if (_month == 13)
- {
- _month = 1;
- _year++;
- }
- }
- return *this;
- }
-
- // 日期+天数 让我们得出结果的临时拷贝,所以可以复用+= ,
- // 但有一个缺点是,日期其实已经被污染,不能在原日期下重新调用。
- Date Date::operator+(int day)
- {
- *this += day;
- Date tmp = *this;
- return tmp;
- }
-
-
- // 日期-天数 //可以复用 -= ,但原日期被修改。
- Date Date::operator-(int day)
- {
- *this -= day;
- Date tmp = *this;
- return tmp;
- }
- // 日期-=天数
- Date& Date::operator-=(int day)
- {
- _day -= day;
- while (_day <= 0)
- {
- _month--; // 如果_month == 0了,就提取不了对应月份的天数
- if (_month == 0)
- {
- _year--;
- _month = 12;
- }
- _day += Date::GetMonthDay(_year, _month);
- }
- return *this;
- }
-
- // 前置++ 加完后返回
- // 为了区分重载函数前置与后置++,C++做了重载区分,就是后置++增加一个int参数。
- Date& Date::operator++()
- {
- // 可以进行复用+=
- * this += 1;
- return *this;
- }
-
- // 后置++
- Date Date::operator++(int)
- {
- Date tmp(*this);
- *this += 1;
- return tmp;
- }
-
- // 后置--
- Date Date::operator--(int)
- {
- Date tmp(*this);
- *this -= 1;
- return tmp;
- }
- // 前置--
- Date& Date::operator--()
- {
- *this -= 1;
- return *this;
- }
- // // >运算符重载
- 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 _year == d._year &&
- _month == d._month &&
- _day == d._day;
- }
-
- // >=运算符重载
- bool Date::operator >= (const Date& d)
- {
- return (*this > d) || (*this == d);
- }
-
- // <运算符重载
- bool Date::operator < (const Date& d)
- {
- if ((_year < d._year) ||
- ((_year <= d._year) && (_month < d._month)) ||
- ((_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)
- {
- return _year != d._year &&
- _month != d._month &&
- _day != d._day;
- }
-
- // 日期-日期 返回天数 思路:用小的加到大的,计算中间的次数。
- int Date::operator-(const Date& d)
- {
- int flog = 1;
- Date a1(*this); // 假设a1大
- Date a2(d);
- if (a1 < a2) // a1小替换为a2
- {
- a1 = d;
- a2 = *this;
- flog = -1;
- }
- int n = 0;
- while (a2 < a1)
- {
- /*if (a2._day == 9 && a2._month == 5)
- {
- int x = 0;
- }*/
- ++a2;
- n++;
- }
- return n * flog;
- }
测试源文件:
- #define _CRT_SECURE_NO_WARNINGS 1
-
- #include"Date.h"
-
- void Test()
- {
- const char* WeekRoom[7] = { "周天", "周一", "周二", "周三", "周四", "周五", "周六" };
-
- do
- {
- cout << "------------------------------------" << endl;
- cout << "--------请输入要操作的选项----------" << endl;
- cout << "----1. 日期+天数---2.日期-天数----" << endl;
- cout << "----3. 日期-日期---0.结束程序 --------" << endl;
- cout << "----4. --------" << endl;
- int opertaton = 0;
- cin >> opertaton;
- Date a1, a2;
- int day;
- switch (opertaton)
- {
- case 1:
- cout << "-----------"<< endl;
- cin >> a1 >> day;
- cout << (a1 += day) << endl;
- cout << WeekRoom[a1.WeekDay()] << endl;
- break;
- case 2:
- cout << "-----------" << endl;
- cin >> a1 >> day;
- cout << (a1 -= day) << endl;
- cout << WeekRoom[a1.WeekDay()] << endl;
- break;
- case 3:
- cout << "-----------" << endl;
- cin >> a1 >> a2;
- cout << (a1 - a2);
- break;
- case 0:
- cout << "结束程序";
- exit(-1);
- default:
- break;
- }
- } while (true);
- }
-
- int main()
- {
- Test();
- return 0;
- }
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论;如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。