文章目录
- 前言
- 1.类的6个默认成员函数介绍
- 2.构造函数
- 3.析构函数
- 1.概念
- 2.析构函数特征
- 4.拷贝构造
- 1.概念
- 2.拷贝构造函数特征
- 3.注意事项
- 5.赋值运算符重载
- 1.概念
- 6.补充知识const成员函数
- 7.取地址运算符和const取地址运算符重载
- 8.总结
前言
本文主要介绍C++中的六个天选之子,也就是类中的6个默认成员函数。它们分别:构造函数 析构函数 拷贝构造函数 赋值运算符重载 取地址及const取地址操作符重载。本文主要围绕前4个进行介绍,后面两个基本上不太重要也不太需要我们自己去实现。
1.类的6个默认成员函数介绍
类的6个默认成员函数如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
2.构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数(隐式调用).
4. 构造函数可以重载。
代码示例
因为构造函数是天选之子如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
我们来看这样的代码观察一下
看到这里我们不仅有个疑问:编译器默认生成的构造函数初始化为啥打印a的成员变量是随机值呢?看起来默认构造函数好像没啥用。
其实C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char/各种类型的指针…,自定义类型就是我们使用class/struct/union等自己定义的类型,对于自定义类型编译器生成的默认构造函数会调用该类型的默认构造函数,对于内置类型不做处理。
之前我们栈相关练习的博客中结束过一道题用两个栈实现队列,假如我们自定义首先了栈类,这个时候实现队列类的时候,就只用编译器默认生成的默认构造函数即可。
注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值,
相当于给成员变量缺省值。
关于默认构造函数的概念简单说一下:
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。简单来说不用传参数的构造函数就是默认构造函数。
如果自己实现默认构造函数的话一般建议采用全缺省的形式
3.析构函数
1.概念
概念通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
伪代码示例
在C语言中我们每次使用完栈后需要手动调用销毁函数释放空间,这太麻烦了,完一哪天忘记释放了就会造成内存泄漏,C++的祖师爷创造了析构函数编译器自动调用来清理对象中的资源。
2.析构函数特征
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
当我们没有自己实现析构函数时,编译器也会生成默认的析构函数。编译器默认生成的析构的函数会对自定义类型会调用该类型的析构函数,内置类型不做处理。
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类.
4.拷贝构造
1.概念
在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?这个时候就用到了拷贝构造函数。拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2.拷贝构造函数特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
为什么会引发无穷递归呢?当我们自己实现拷贝构造的时候,需要传参。如果是传值传参这个参数是类类型的,是类类型的就需要调用拷贝构造将类对象赋值给形参初始化。但是拷贝构造还没实现,所以就会发生实现拷贝构造需要用到拷贝构造,这就是递归死循环了,所以只能引用传参。
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。(对于类中成员变量有类类型时,该类的拷贝构造函数必须存在,这样编译器才能正常调用默认生成的拷贝构造函数)。
3.注意事项
对于一些简单的类,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。对于某些类来说浅拷贝会引发一些问题。
在上图的情况下就要自己写拷贝构造函数了,给出一下简单的伪代码示例
class stack
{
public:
stack(const stack& st)
{
_a = (int*)malloc(sizeof(int) * st._top);
if (_a == nullptr)
{
exit(-1);
}
memcpy(_a, st._a, st._top);
_top = st._top;
}
private:
int* _a;
int _top;
};
拷贝构造函数典型调用场景:
1.使用已存在对象创建新对象
2.函数参数类型为类类型对象
3.函数返回值类型为类类型对象.
简单来说就是用已存在的对象初始化新对象,函数传值传参或者函数返回值类型是类类型时。为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
5.赋值运算符重载
1.概念
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
注意:不能通过连接其他符号来创建新的操作符:比如operator@ 重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,如:内置的整型+,不能改变其含义。作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this;(.* :: sizeof ?: .) 注意以上5个运算符不能重载。
代码示例
#include<iostream>
using namespace std;
class Date
{ public:
void Print();
Date& operator=(const Date& d)
{ //为了符合操作符=连续赋值 返回值是this
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
Date(int year=1,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Date::Print()
{
cout << _year <<" "<< _month<<" " << _day << endl;
}
int main()
{
Date d1(2020, 2, 2);
Date d2(2020, 1, 1);
//调用赋值运算符重载
d2 = d1;
d2.Print();
d1.Print();
}
赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
上述代码示例中为了更加符合=操作符的功能,用返回值传为引用支持连续赋值。
C++为什么要支持运算符重载呢?我们知道编译器对语法规定的内置类型能够用操作符处理的很好,但是如果对一些复杂对象也需要用到操作符该怎么办呢?为了处理这样的场景C++中产生了运算符重载。
注意事项:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
和拷贝构造函数类似:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
拷贝构造和赋值运算符的区别在于:
一个是对象初始化的时候调用,另一个是对已经存在的对象进行赋值。
伪代码示例
Date d1(2020, 2, 2);
//拷贝构造
Date d2=d1;
Date d3(2000, 1, 1);
//赋值运算符重载
d1 = d3;
6.补充知识const成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
其实准确来说const修饰的*this.
为什么会有const成员函数呢?看如下代码示例
我们看到程序是会发生报错的,为啥会发生这样的问题呢?d1是被const修饰的,但是在调用类中函数Print函数时,this指针是没有被const修饰的,相当于this指针权限被放大了,就会引发错误,C++语法规定在函数形参列表后面加上cosnt用于修饰隐藏的this指针, * this才是指向的对象准确来说const修饰的是*this。
对于一些不与需要访问类成员的成员函数我们都可以用const修饰,这样对于const修饰的对象也能正常调用成员函数。
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
1.const对象不能调用非const成员函数
2、非const对象可以调用const成员函数
3、const成员函数不能调用其它的非const成员函数
4、非const成员函数可以调用非const成员函数
这一切都源于权限不能被放大只能被缩小
7.取地址运算符和const取地址运算符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
代码示例
8.总结
以上便是对C++中6个天选之子进行简单简单介绍,关于运算符重载后续将会写一个日期类将详细介绍。以上内容如有问题,欢迎指正,谢谢!