𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary_walk
⸝⋆ ━━━┓
- 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━ ➴ ⷯ本人座右铭 : 欲达高峰,必忍其痛;欲戴王冠,必承其重。
👑💎💎👑💎💎👑
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑 希望在看完我的此篇博客后可以对你有帮助哟👑👑💎💎💎👑👑 此外,希望各位大佬们在看完后,可以互相支持,蟹蟹!
👑👑👑💎👑👑👑
目录:
一:类的6个默认成员函数
二:构造函数
三:析构函数
四:拷贝构造函数
五:赋值运算符重载
六:const成员函数
七:取地址以及const 取地址操作符重载
本篇博客主要是围绕类和对象的默认成员函数讲解为主,关于类和对象的一些预备知识,俺在这就不一一赘述了,有需求的友友们可以自行点击链接
https://blog.csdn.net/X_do_myself/article/details/136847828?spm=1001.2014.3001.5502
思维导图:
1:类的6个默认成员函数
1)空类:一个类里面什么成员也没有,称之为空类,注意即使一个这个空类里面什么内容也没
有(用户表面看到的),这个类的大小也是存在的,占1个字节
2)一个空类里面并不是什么也没有,编译器会默认生成6个默认成员函数(不需要用户自己定义,
编译器隐式调用的)
3)6个默认成员函数以及对应的功能
构造函数:主要就是完成函数的初始化
析构函数:对资源的清理
拷贝构造函数:使用同类对象初始化创建另一个同类对象
赋值运算符重载:把一个对象赋值给另一个对象
const成员函数
取地址以及const 取地址操作符重载:普通对象和const 对应的对象进行取地址
2:构造函数
2.1构造函数的引入
前期学过数据结构的友友们应该都对以下问题颇有体会,想必。
比如说,定义一个栈 这样类型的变量,使用此变量进行一系列函数调用,我们往往就会在使用之
前忘记了对变量进行初始化, 进而引发一系列问题。
针对此问题,C++ 引出了构造函数。此函数的会在我们定义变量的同时编译器自动调用,不
需要我们人为的调用此函数。
对于构造函数的初步使用
1)构造函数 的名字和类的名字相同
2)构造函数没有返回类型(注意没有返回类型不代表就写成void )
3)对象创建的同时编译器自动调用
4)构造函数支持函数重载(可理解为有多个初始化函数)
5)有参的构造函数以及无参的构造函数书写的区别
无参构造函数 在调用的时候后面是不能添加 ()
有参构造函数在创建对象的同时就给出对应的参数即可
6)当用户自己没有定义构造函数的时候,编译器会默认生成一个构造函数只不过此时是隐式调用
构造函数
编译器生成的默认构造函数:对对象初始化的时候为什么是随机值??
相信这是不少老铁们的疑惑
这是因为编译器底层对类型做了一些特殊的处理:
对于内置的类型(基本类型)(比如int ,char,float,double,指针等等):编译器在调用构
造函数的时候不会对类的成员进行处理
但是对于那些自定义类型:struct , class ,enum, unio等等,编译器在调用构造函数的时候对
这些成员会进行处理:会去调用成员的构造函数
通过调试方向,对MyQueue 类型的变量mq 自定义的成员_PopSt,_PushSt 已经进行了初始化
7)全缺省的构造函数
Date(int year = 2024, int month = 8, int day=1) //全缺省构造函数
{
cout << "Date(int year = 2024,int month = 8, int day=1)" << endl;
_year = year;
_month = month;
_day = day;
}
对于默认构造函数的理解:
相信有不少老铁们会认为:只有编译器生成的构造函数才是 默认构造函数,那你就大错特错了
全缺省的构造函数,无参的构造函数,编译器生成的构造函数都是默认构造函数
并且这三个之中只能有一个存在,否则发生二议性问题
针对编译器不能对内置类型做处理这一问题,引出了全缺省的构造函数
那么问题又来了:当无参的构造函数和全缺省的构造函数同时存在,并且对象也没有给出对
应 的参数,编译器是否可以编译通过呢???
自然是不言而喻的,不能通过:因为此时发生歧义了,编译器也不知道自己到底是调用无参
的构造函数还是调用全缺省的构造函数
所以:对于无参的构造函数以及全缺省的构造函数只能存在一个(建议:选择全缺省的
构造函数)
构造函数是默认的成员函数之一,一般情况下,需要我们手写构造函数,来决定初始化的方式;
当成员变量都是自定义类型时,可以不写构造函数,此时编译器调用默认的构造函数
3:析构函数
在学习数据结构链表等知识章节的时候,我们总是频繁的调用初始化函数,销毁函数,甚至
一个不注意,可能忘记资源的销毁……
在C++里面,我们把资源的释放交给析构函数来进行处理,析构函数是和构造函数功能相反
的一个函数,但是他们之间又有许多的共同点
3.1 析构函数基本特性
1)析构函数名字和类名一样,但是需要在类名前面加上一个 ~(方便编译器与构造函数进行区分)
2)没有返回类型也没有参数(不需要写 void )
3) 编译器会自动调用析构函数(当前对象销毁时候调用)
4)析构函数不支持函数重载
5)默认的析构函数:
当用户没有显示调用析构函数的时候,编译器会在对象生命周期结束的时候自动调用(隐式调用)
6)和构造函数一样:编译器对内置类型(基本类型)的类的成员不做处理,对自定义类型的成员做处理
7) 如果类中没有资源的清理时,析构函数可以不写,直接使用编译器生成的默认析构函数,一旦
涉及到有资源申请时,一定要写,否则会造成内存泄漏
8)默认的析构函数特点:
对自定义类型成员会去调用析构函数;
内置类型成员不做任何处理;
自动调用
以一个OJ 题为例:有效的括号
写过的老铁,相信有不少人都踩过坑:函数调用结束之前,忘记对栈的销毁。
针对这一问题:C++ 引入了析构函数,当对象销毁的时候,自动调用析构函数,来完成一些资源
释放
3.2 析构函数的使用
关于析构函数 的基本使用如下:
编译器隐式调用Date的析构函数,显示调用Time的析构函数(用户没有定义析构函数)的栗子:
析构函数是否需要写:看情况分析:
4:拷贝构造函数
还是以栈 这个类型来做引例吧
此时重载崩溃。
分析:
针对这一问题, C++ 进而引入了 拷贝构造函数
4.1概念:
拷贝构造函数:只有一个形参,该形参是对 本类 类型 对象 的引用(一般常用const修饰),在
用已存在的类类型对象创建新对象时由编译器自动调用。
4.2 特性:
1)拷贝构造函数是构造函数的一个函数重载
2 拷贝构造函数的参数只有一个且必须是类 类型 对象 的引用(若是采用传值传参的方式,会出现无
限递归的情况)
3)当用户没有定义拷贝构造函数的时候编译器会调用默认 的拷贝构造函数(隐式),但是这个默
认的拷贝构造函数只能对内置类型进行浅层拷贝(值拷贝),当涉及到自定义类型的成员的时
候,浅层拷贝就会有问题
4)类中如果没有涉及资源申请时,拷贝构造函数可写可不写;涉及到资源申请时,则拷贝构造函
数必须写,否则编译器就是执行浅层拷贝。
拷贝构造函数的基本使用栗子:
注意以下是错误使用范例:
浅层拷贝:
注意深层拷贝不再是简简单单的拷贝那么简单了,这时候涉及到析构函数先后调用的问题
分析:
问题不仅仅是体现这方面:当Pop的时候也是有问题
深拷贝:自定义类型
关于拷贝构造函数的综合应用场景:
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);//用已经存在的对象d初始化temp,就需要调用拷贝构造函数
cout << "&temp:" << &temp << endl;
return temp;//返回类型:是一个类,就需要调用拷贝构造函数,注意此时虽然是返回一个局部变量,但是编译器是借助创建一个临时变量来返回的
}
int main()
{
//拷贝构造函数适应场景:1)已经存在的类对象创建一个新的类对象 2)函数返回类型是类的 3)函数参数类型:类的类型
Date d1(2022, 1, 13);//调用构造函数创建d1这个对象,注意拷贝函数是一个特殊的构造函数(构造函数的一个重载)
cout << "&d1:" << &d1 << endl;
Test(d1);//传值传参,就需要调用拷贝 构造函数
return 0;
}
对以上结果分析见下:
5:运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的一种函数,
也有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符 (参数列表)
5.1 赋值运算符重载
1)赋值运算符重载函数针对自定义类型的成员会去调用该成员对应的拷贝构造;对于内置类型成员进行浅拷贝
自定义类型成员的运算符重载
解决:对于Stack 这个类写一个运算符重载函数
内置类型成员的运算符重载
Date 这个类的相关实现:
不知道你是否发现没有:对于Date 这个类,并没有实现赋值运算符重载函数,为什么可以完成
赋值操作呢?
这是因为:当我们没有写赋值运算符重载函数的时候,编译器会默认生成一个赋值运算符重载函数
并自动调用这个默认生成的函数
以实现 '>' '<' '<=' '>=' '==' '!= '运算符的重载为例吧:
注意:对于bool 类型,true一般以1代表真,false以0代表假
当把重载函数定义在类里面的时候,此时默认隐藏一个参数,this指针,这个指针指
向左操作数
关于this 指针相关介绍,见链接:
https://blog.csdn.net/X_do_myself/article/details/136847828?spm=1001.2014.3001.5502
代码:
class Date
{
public:
int _year;
int _month;
int _day;
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
Date(int year = 2024, int month = 3, int day = 26)//全缺省的构造函数(对于内置类型不做处理,对于自定义类型做处理)
{
_year = year;
_month = month;
_day = day;
}
//注意对于Date这个类不需要写拷贝构造函数,编译器可以进行处理
Date(const Date& d)//拷贝构造函数(对应内置类型是做浅层拷贝,自定义类型深层拷贝)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// d1 > d2 *this 默认指向运算符的左操数,此时d是d2的引用
bool 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;
}
// d1 == d2
bool operator==(const Date& d)
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool operator!=(const Date& d)
{
//直接复用之前代码
return !(*this == d);
}
//d1 >= d2 ===> d1 > d2 || d1 == d2
bool operator>=(const Date& d)
{
return(((*this) > d) || (*this) == d);
}
// d1 <= d2
bool operator<= (const Date& d)
{
//对 . 进行取反
return !(*this > d);
}
};
int main()
{
Date d1(2024, 11,26);//调用构造函数进行实例化
Date d2;// 只不过是用全缺省函数进行初始化
cout << (d1 <= d2) << endl;//编译器会自动转换成调用这个!= 运算符重载函数 d1.operator(d2)
return 0;
}
5.2 运算符重载
5.2.1含义
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符 (参数列表)
5.2.2 细节
注意:
1)不能通过连接其他符号来创建新的操作符:比如operator@
2)重载操作符必须有一个类类型参数
3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的
this指针
5).* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
以实现 日期 与 日期 之间大小关系比较为例
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)
{
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)
{
return !((*this)>=d);
}
bool Date::operator<=(const Date& d)
{
return !((*this) > d);
}
bool Date::operator!=(const Date& d)
{
return !((*this) == d);
}
5.2.3 前置,后置++和前置,后置--
1· 前置++,后置++本质就是对 对象 到底是先进行++ 操作,还是先使用对象的区别,最红对象都
是要进行++ 操作的
前置++ :先执行++操作;后 使用对象,返回++ 之后的对象
后置++:先使用对象,后 进行++ 操作,返回++ 之前的对象
注意:对于编译器而言,在进行函数声明,定义的时候,是无法区分到底调用前置++;还是后置
++的;所以对于后置++ 的函数声明,需要声明一个int 类型的参数;只是为了区分调用;注意只能
是int 类型参数;但是在使用的时候正常使用,对于调用后置++或则--的时候,可以传参也可以不
传参。
Date& Date::operator++()
{
// ++d 返回对象:++之后的
_day+=1;
return *this;
}
Date Date::operator++(int)
{
//d++ 返回++之前的对象
Date tmp(*this);
_day+=1;
return tmp;
}
Date& Date::operator--()
{
_day-=1;
return *this;
}
Date Date::operator--(int)
{
Date tmp(*this);
_day-=1;
return tmp;
}
5.3借助日期类实现相关函数
5.3.1 实现日期+天数的函数
日期+天数 可以得到一个精确的日期
2024,3, 27 + 50
这个涉及到进位的问题(如同加法的运算一样)
分析:
此时天数是 77 显然是大于3月份的天数
进位 并且 减去3月份的所有天数,
得到日期:2024 ,4 ,46,
同理,大于4月份的天数,依然是减去4月份所有的天数
2024,5,16 此时的满足5月份的天数要求
最终结果:2024,5,16
分析:
1)写一个 获取每一个月份的天数 的函数;注意闰年
int GetMonthday(int year, int month) { int Months[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)))//注意条件判断先后逻辑:先判断当前是否为2月份在进行闰年判断 return 29; else return Months[month]; }
2)用循环判断当前月份的天数是否满足要求
运行结果:
和网上求的一样,觉得没有啥问题
当我们连续调用 += 这个函数的时候,问题就来了
正确答案:
其实简单分析一下,就知道问题所在了:第一次调用是以 d1为参考对象进行50天后的日期推
算。第二次调用虽然是以d1为参考对象,但是此时的d1不再是2024,3,27了,因为实现的+=
这个运算符重载的函数,函数返回值改变了d1
改进之后的代码:实现 + 的运算符重载即可
代码:
Date Date::operator+=(int day) { _day += day; while (_day > GetMonthDay(_year,_month,_day)) { if (_month >= 13) { _month = 1; _year++; } _day -= GetMonthDay(_year, _month, _day);//减去当月的天数 _month++; } return *this;//返回*this,每次调用都会改变当前的对象 //注意当这样写有bug //如果连续调用进行日期相加的话,就出问题 } Date Date::operator+(int day) { //对上面的+=改进 Date tmp(*this);//对*this 进行拷贝一份 tmp += day;//直接调用 +=的函数 return tmp; //弊端:涉及到拷贝的调用 }
运行结果:
对最后一个结果进行验证:
5.3.2 日期 - 日期的函数
在实现日期- 日期函数之前,先写一个日期 - 天数的函数
这个分析过程和日期+天数一样,只不过这里是进行借位的运算:
2024,2,15 - 50
用当前月份的天数 - 50 = - 35 < 0
此时向前借位:注意这里借的是 1月份的天数,既不是借的2月份的天数,也不是借的3月份
的天数
用借来月份的天数 + 当前天数 - 35 = - 4 < 0
依然进行借位:借的的前一年,也就是 2023年的12月份的天数 31 + (-4) = 27
注意这里在代码实现上的细节:对应的年要 - -,对应的月份要跟新到12 月份
也就是最终结果:2023, 12 ,27
对于这个代码其实还是有一点 的Bug,连续调用这个 - = 运算符重载函数的时候,对应的结
果会觉得比较怪:
通过代码调试就知道了:- = 这个运算符重载函数每次调用都会对当前的参考对象进行改变
优化之后的代码:
运行结果:
日期 - 日期:
// d2 - d1 = 天数
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
Date tmp = d;
//找出较大的日期
if (max < min)
{
tmp = max;
max = min;
min = tmp;
}
//找出较小的那个日期,对这个较小的日期进行++,直到与较大的日期相等
int count = 0;
while (min < max)
{
count++;
++min;
}
return count;
}
运行结果:
6:const成员函数
定义
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员
函数隐含的this指针,表明在该成员函数中不能对this 指向的对象的任何成员进行修改。
应用
此时定义2个Date 的对象,区别就是有无const 进行修饰
通过运行结果看出:都可以进行调用Print这个函数
思考以下几个问题
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
1. const对象可以调用非const成员函数吗?
不能;此时会造成权限的放大
2. 非const对象可以调用const成员函数吗?可以;权限可以缩小
3. const成员函数内可以调用其它的非const成员函数吗?不能,造成权限放大
4. 非const成员函数内可以调用其它的const成员函数吗?可以,权限缩小
总结:对于只读的函数而言,都可以写成const 成员函数(函数内部不涉及到对成员的改变)
7:取地址以及const 取地址操作符重载
7.1 取地址运算符重载
注意这2个函数,对于第二个函数而言:他是一个const成员函数,返回值类型 const Date*
一般可以不写此函数,编译器会默认生成
运行结果:
在项目里面,可能有这样的需要:不想让用户取到当前对象的真实地址
可以对取地址运算符函数进行重载
7.2流插入 流提取
7.2.1 流插入
当我们想使用cout 直接对自定义类型变量进行打印输出的时候,发现编译过去
但是使用 cout 对内置类型变量进行打印的时候,就不会报错。
这是因为C++ 对cout 这个函数进行了运算符重载以及函数的重载。
一样的道理,要想对自定义类型变量直接进行打印,就需要写运算符重载的函数
可能有不少友友们会这样写。
这样写是不支持的:对于类的成员函数而言,默认函数形参的第一个参数是this 指针的
所以对于流插入,留提取的运算法重载函数,需要写成全局函数
问题又来了:当涉及到对类里面私有成员的访问时,要怎么操作
一种方法是:写一个函数:获取当前对象的成员变量;另一种方法是进行友元函数的声明,可以访
问类的私有成员
7.2.2 流提取
结语:
OK~,以上就是我今日要为各位share的,对于类和对象的6大默认成员函数这个模块的学习非常
重要,而且知识点也特别零散。希各位都可以有所收获从这篇文章里面,当然也是有许多不足之
处,欢迎各位大佬随时指出,谢谢支持!