目录
1. 前言
2. 构造函数
概念
特性
实例讲解
1.系统生成构造函数
2.自己如何写构造函数
3.什么时候要写构造函数
3. 析构函数
概念
特性
注意事项
4. 拷贝构造函数
概念
特性
实例讲解
1. 系统默认拷贝构造函数
2.自己实现拷贝构造函数(深拷贝)
使用场景
5. 赋值运算符重载
概念
特性
6. const成员函数&&取地址及const取地址操作符重载
1. 前言
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
这是我们定义的一个类
class A
{
};
在我们看来是空的,但经过编译器处理后,会自动生成六个默认的成员函数(这些函数即使我们没有定义,也会显示调用),生成如下
class A
{
public:
A();//构造函数
~A();//析构函数
A(const A& a);//拷贝构造函数
A& operator=(const A& a);//赋值运算符重载
A* operator &();//取地址运算符重载
const A* operator &() const;//const修饰的取地址运算符重载
};
2. 构造函数
概念
构造函数主要完成初始化对象,赋予成员变量一个合适的初始值,相当于C语言阶段写的Init函数。
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象
特性
1.无返回值
2.构造函数的函数名和类名相同
3.对象实例化时,编译器自动调用对应的构造函数且只调用一次
4.可以重载
实例讲解
1.系统生成构造函数
若没有写构造函数,系统为则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
但是我们会发现,这里初始化的数据是随机值
这是因为C++编译器在系统默认构造函数中初始化类型时,对内置类型不做处理,对自定义类型会调用它的默认构造函数
解答:C++把类型分成内置类型和自定义类型。内置类型就是语言提供的数据类
型,如:int/float/char...,自定义类型就是我们使用class/struct/union等自己定义的类型
下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
函数。
但难道我们就不能让系统默认定义内置类型吗,并不是
C++11中针对内置类型不处理初始化为随机值的问题,打了补丁:内置类型成员变量在类中声明可以给默认值,甚至可以给动态开辟的缺省值,缺点是不能判断空间是否开辟成功。
注意这里的默认值是缺省值,不是初始化。初始化是要等对象调用时才叫初始化。
2.自己如何写构造函数
注意:必须要有一个且仅有一个默认构造函数(否则存在二义性)
默认构造函数:无参的构造函数或全缺省的构造函数
当我们没有写的构造函数中没有默认构造函数时,编译器会报错,如下面程序
这时我们就要创建一个默认构造函数了,如下面程序
class Date
{
public:
Date(int year=1,int month=1,int day=1)//方法一:提供全缺省参数的构造函数
{
_year=year;
_month = month;
_day = day;
}
//Date()//方法二:提供无参数的构造函数(两种方法二选一,否则存在二义性)
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
return 0;
}
如果我们写了两个默认构造函数,编译器也会报错,如下图
3.什么时候要写构造函数
判断当前这个类是否要写构造函数,主要根据当前类的成员变量类型决定的
若有内置类型,需要写,若无内置类型,则可以不写
3. 析构函数
概念
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。资源包括动态开辟的空间,文件的关闭等。相当于C语言阶段写的destroy函数。
特性
1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
注意事项
1.析构函数不能重载
2.对于编译器生成的默认析构函数,对自定义类型调用他的析构函数。对于内置类型,销毁时不需要资源清理,最后系统直接将其内存回收即可。
3.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如栈(Stack)类
4. 拷贝构造函数
概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数的函数名和构造函数相同,无返回值,在参数上和构造函数构成重载。
特性
1.拷贝构造函数是构造函数的一个重载形式2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用3.若未显式定义,编译器会生成默认的拷贝构造函数
实例讲解
1. 系统默认拷贝构造函数
默认的构造函数对于内置类型按照字节拷贝。对于自定义类型则调用它的拷贝构造函数
类中如果没有涉及资源申请时(如栈),默认拷贝拷贝构造函数是可以直接使用的;一旦涉及到资源申请时,则拷贝构造函数是一定要自己写的,否则就是浅拷贝
浅拷贝:通过默认的拷贝构造函数构造的对象,按字节完成拷贝。这种拷贝被称为浅拷贝(值拷贝)
系统默认生成的拷贝构造函数就是浅拷贝
浅拷贝其实就是简单的赋值,如下图(没有自己写拷贝构造函数)
栈的浅拷贝
指针虽然复制了,但是所指向的内容却没有复制,s1和s1指向同一个地址,当main函数生命周期结束时,这两个对象均会发生一次析构,d2先析构,d1后析构,析构时存在同一块空间被释放两次的问题。需要进行深拷贝。深拷贝的拷贝构造函数必须自己手动实现。
2.自己实现拷贝构造函数(深拷贝)
栈这个类因为成员变量中有动态开辟的空间,所以要用深拷贝。如下列代码
class Stack
{
public:
Stack(int capacity = 4)//构造函数
{
_capacity = capacity;
_top = 0;
_arr = (int*)malloc(sizeof(int) * 4);
if (_arr == nullptr)
{
perror("malloc fail");
exit(-1);
}
}
Stack(const Stack& st)//深拷贝(拷贝构造函数)
{
_capacity = st._capacity;
_top = st._top;
_arr = (int*)malloc(sizeof(int) * st._top);
if (_arr == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_arr, st._arr, sizeof(int) * st._top);
}
~Stack()//析构函数
{
_capacity = 0;
_top = 0;
free(_arr);
_arr = nullptr;
}
private:
int* _arr;
int _top;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(s1);
}
使用场景
1、使用已存在的对象创建新对象
2、函数参数类型为类的类型对象(传值调用,实参拷贝给形参)
3、函数返回值类型为类的良心对象(传值返回)
5. 赋值运算符重载
概念
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号(如operater==) 函数原型:返回值类型 operator操作符(参数列表) (如bool operater==(const Date& d) )
特性
1.
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
2.重载操作符必须有一个类类型参数(赋值重载函数必须在类里)
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.不能通过连接其他符号来创建新的操作符:比如operator@
5. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
6. const成员函数&&取地址及const取地址操作符重载
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}