文章目录
1.类的6个默认成员函数
严格意义上来说一个类什么都没有,我们把它叫做空类;
比如:
class A
{
}
这就是一个空类,站在我们人类角度看,这个类确实没有什么都没有;
那么真的是什么都没有吗?
当然不是,注意我刚才的前提:站在人类的角度;
但是我们站在编译器的角度来看呢,这个类中其实有6个默认的成员函数!
这6个成员函数是由编译器自动生成的;
默认成员函数:用户没有显示实现,编译器会自动生成的成员函数我们称为默认成员函数,当然我们也可以自己显示实现,那么便能一起就不会生成对应的默认成员函数了,实现的默认成员函数则还是会由编译器自动实现!
按照这6个默认成员函数的功能,我们可以分为两两一组:
初始化和清理:构造函数和析构函数;
拷贝赋值: 拷贝构造函数和赋值运算符重载函数;
取地址重载: 主要是普通对象和const对象取地址,这两个很少会自己实现;
2.构造函数
2.1构造函数的引入
首先我们知道在C++语法中是支持在结构体中定义成员函数的!
那么现在我们可以简单的定义一个栈类来玩一玩:
由此一个简单的类就完成了,这里我们需要使用手动调用初始化函数类初始化这个栈,只有完成到这,我们才能在后续操作中完成进栈和压栈操作!
但是呢?Init函数是需要我们手动调用的,
而c++为了解决这一点创造了构造函数(初始化由编译器自动完成)
下面是两者的对比
很明显代码量变小了;更加符合我们懒人的标准了;
2.2构造函数的特性
上面我们讲了为什么会由构造函数,下面我们来讲述一下构造函数的定义和特性
2.2.1构造函数的定义:
1…类名+(参数1,参数2,参数3,……)
1.1不需要写返回类型(不是说返回类型是void的意思,而是真的没有返回值的意思,连viod也不需要)
1.2构造函数名和类名相同(一个对向在生命周期内只调用一次)
2.2.2.构造函数可以 实现函数重载**
上面我们定义了一个无参的构造函数,那么接着我们也可以定义有参的构造函数:
2.2.5.从函数的参数类型来区分构造函数
1.无参的构造函数:
2.有参的构造函数
由赋值的参数个数区分:
满参数赋值
不满参数赋值
)
2.2.3 构造函数调用(对象实例化时,由编译器自动调用对应的构造函数!)
无参函数调用————即为定义
有参函数调用
看到这里你会不会有一个疑问;为什么无参数调用没有括号;如果按照有参的样式的化,无参数函数应该是
原因:
如果我们从函数角度来看:Stack s1();这一调用方式,si是函数名,返回值是Stack类型!这不妥妥的函数声明吗?这会使便使编译器陷入歧途;
这里的无参数调用和全缺省调用构造函数的调用相同:(都会显示使用默认构造函数)**
都是无参数调用;
所以要求不能同时出现这两个函数;
建议构造函数时,使用全缺省函数;
2.2.4。如果类中没有显示定义构造函数,C++编译器会自动生成一个无参钩造函数
一旦用户显示定义,编译器就不会自动生了!
例子:
这里我们定义了一个有参的构造函数,如果我再去调用无参构造函数编译器就会报错;
2.2.5.使用全缺省构造函数来多元创建栈帧;
构造函数的定义09 .。。。
2.2.6.编译器自动生成的默认构造函数(无参),该构造函数对于对象中的内置类型成员变量不进行处理;对于自定义类型的成员函数则调用该自定义类型的默认构造函数(无参数,全缺省,自动);
内置类型:int,char等;注意char*和结构体指针也是内置类型
对于自定义类型自动初始化的优势,对栈建立的队列有优势;
自动生成
2.2.7C++11规定可以给声明缺省值
这里的缺省值可以将自动生成的构造函数初始化;
2.2.8默认构造函数(不传参数)的种类:
全缺省的构造函数
无参的构造函数
自动生成的构造函数
有且只有一个
3.析构函数
3.1概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2 特性
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载 - 对象生命周期结束时,C++编译系统系统自动调用析构函数。
显示写析构函数的样例:
栈:
3.3析构的调用顺序:
逆序调用;构造函数顺序调用;
3.4默认析构函数的处理
1.的原因:
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对
象,所以在// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数
内置成员在栈帧中,而栈是在堆中存储;内置成员会在栈帧销毁时自动销毁;
自己定义析构函数的目的时将堆的内存清理;
4.拷贝构造函数
出现的原因:
栈的拷贝在析构时,会进行多次析构堆;
解决方法一
4.1.拷贝构造函数概念
在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
4.2拷贝构造函数特性**
拷贝构造函数也是特殊的成员函数,其特征如
4.2.1、拷贝构造函数格式:
类名(const 类名& 形参名) or 类名(类名& 形参名)
注意:该只有参数是以上形式的构造函数才是拷贝构造函数!!!!!切记切记!!!不是这样格式的函数都不能叫做拷贝构造函数!!!!!!
这里注意:参数一定有引用 ;否者拷贝构造函数将下入死循环;(原因:c++的传参数是拷贝构造函数);
举个具体例子:
死循环的示例:
4.2.2拷贝构造函数上述的调用
这里的拷贝构造函数有常参数,和变参数;
调用拷贝构造含函数的用法:
1.括号法;
2.等号法;
4.2.3
4.3、拷贝构造函数的分类
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。浅拷贝存在风险、一般情况像Stack类这种需要额外申请空间的类,就需要我们自己写拷贝构造函数,并且是用深拷贝来实现!
我们通过具体的例子来解释一下浅拷贝与深拷贝:
我们知道编译器提供的默认拷贝构造函数是由浅拷贝来实现的,我们就来用一用浅拷贝:
4.3.1浅拷贝:
普通的类型(c中的)或自定义中没有堆的使用
4.3.2深拷贝
自定义类型的呗(这一自定义类型有调用了堆的内存的)
4.3.3.拷贝构造函数的使用场景:
拷构造函数的使用场景是:
参数的传递;
返回值的传递
定义对象;另一个对象赋值;
5…赋值运算符重载(自定义类型)
5.1 运算符重载
5.1.1定义:
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
目的:是可读性变强
5.1.2运算符重载和函数实现的对比
运算符重载的函数名:operator <
简写:<
5.1.3运算符重载的注意:
- [ ]
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个自定义类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this
5. .* :: sizeof ?: .注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。
不能改变操作符造作数的个数;
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test ()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout<<(d1 == d2)<<endl;
}
终结
定义:返回值+operator+操作符+(参数)
参数要少一个;原因:this指针;
5.2 赋值运算符重载
5.2.1 定义加注意:
5.2.2赋值重载的特性:
1.会自动生成(自定义类型需要写)即:
浅拷贝不用写;直接(用类名加()或=)
深拷贝要写;函数;
例如:
时间类:浅拷贝;
栈类:深拷贝;
5.3运算符重载
5.3.1 +=
Date& Date :: operator+=(int day)
{
_day += day;
while (_day> GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
5.3.2+
// 日期+天数
Date Date :: operator+(int day)
{
//Date tmp(*this);
//tmp += day;
//return tmp;
Date d = *this += day;
return d;
}
对比+符用+=来完成定义;
/*日期+=天数*/
Date& Date :: operator+=(int day)
{
_day += day;
while (_day> GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
//* this = *this + day;
//return *this;
}
// 日期+天数
Date Date :: operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
Date d = *this += day;
return d;
//方法二就是将this指针的解引用改为其他变量;
/*Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return *this;*/
对比+=符用+来完成定义;
Date& Date :: operator+=(int day)
{
/*_day += day;
while (_day> GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;*/
* this = *this + day;
return *this;
}
// 日期+天数
Date Date :: operator+(int day)
{
//Date tmp(*this);
//tmp += day;
//return tmp;
//Date d = *this += day;
//return d;
//方法二就是将this指针的解引用改为其他变量;
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return *this;
}
对比两者(拷贝次数)
发现第一种最好;
5.3.3 -=
// 日期-=天数
Date& Date ::operator-=(int day)
{
_day -= day;
while (_day <= 0)//没有0号
{
_month--;
if (_month ==0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
5.3.4 -
Date Date ::operator-(int day)
{
Date d = *this -= day;
return d;
}
day为-数时的解决方法
修改后的+=
Date& Date :: operator+=(int day)
{
//day为负数时
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while (_day> GetMonthDay(_year,_month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
//* this = *this + day;
//return *this;
}
修改后的-=
Date& Date ::operator-=(int day)
{
//day为负数时
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day <= 0)//没有0号
{
_month--;
if (_month ==0)
{
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
5.3.5 前置++和后置++
两个++的定义:
// 前置++
Date& Date ::operator++()
{
*this += 1;
return *this;
}
// 后置++
Date Date :: operator++(int)
{
Date d = *this;
*this += 1;
return d;
}
后置++在参数中添加了一个整数(可以是任意值 )
注意这两个函数构成函数重载和运算符重载;
前置好一些
5.3.5 前置–和后置–
// 前置--
Date& Date ::operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date Date ::operator --(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
后置的显示:
5.3.6日期减日期
方法1:
方法2:用小的逐个加;
/*日期-日期 返回天数*/
int Date :: operator-(const Date& d)
{
//方法2
Date max = *this;
Date min = d;
int flag = 1;
if (*this<d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)//为什么不用<操作过多;
{
++min;
++n;
}
return n * flag;
}
使用两个栈构建的队列所有默认成员函数都不用写;
6.const成员
在类型前加const‘类型后加对象名字;
const成员只能调用const成员函数权限的频移
普通成员函数可以调用const成员函数权限的缩小
当一个对象只想实现读;就最好写成const
原因:这样只想读的对象和即想读又想写的对象都可调用;
6.1this指针是const类型的函数![在这里插入图片描述](https://img-blog.csdnimg.cn/ebe815f583654004a6ca9fde5fd3eb84.png)
这里的this指针由于不可显示;所以,C++就将函数的声明和定义后面加const
定义:
声明:
注意:区别const在函数返回类型前和人在函数后加const的区别;
示例
“【】”重定义的注意:
6.2const函数的应用
适用于对象即可读又可写 的的对象;所以
1.对象只读(对于s1)
2.即读又写
由于两者兼备所以写了重定义函数;
解决方法:
注意:
后面的const是给对象加的
-和+都可加;+=和-=不可以
反之则会有问题;
7.取地址及const取地址操作符重载
这俩个函数平常不用写;是默认成员函数,自动调用。
使用场景