目录
一、构造函数
构造函数是创建对象时对属性进行初始化的成员函数,其名字与类名保持一致。在创建类的对象时由编译器自动调用并且在对象的整个生命周期中只调用一次。
(一)函数名与返回值
构造函数的函数名必须与类名保持一致且无返回值。
(二)构造函数的调用
构造函数在对象实例化时由编译器自动调用;
(三)构造函数的重载
构造函数是可以进行函数重载的,但如存在无参和全缺省的构造函数时,在调用时会产生二义性。
class Date
{
private:
int _year;
int _month;
int _day;
public:
//无参构造函数
Date()
{
}
//全缺省构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d1; //对重载函数的调用不明确
return 0;
}
(四)默认构造函数
如果在类中没有显式定义构造函数,则编译器会自动生成一个无参的构造函数。如果用户显式定义了构造函数,编译器将不再生成。
无参构造函数,全缺省构造函数以及编译器默认生成的构造函数都可以认为时默认构造函数。
(五)默认构造函数的作用
默认构造函数不会对内置类型(例如:int、double)进行初始化,因此内置类型数据值为随机值。但默认构造函数会对自定义类型会调用其构造函数完成初始化。
(六)内置类型的成员变量给缺省值
C++11针对内置类型初始值为随机值的问题进行了处理,在内置类型的变量进行声明时可以给缺省值。
(七)初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。
注:
(1)每个成员变成在初始化列表中只能出现一次;
(2)引用成员变量,const成员变量,自定义类型变量(无默认构造函数)必须在初始化列表中进行初始化;
(3)对于自定义类型变量成员,一定会先使用初始化列表进行初始化;
(4)成员变量在类中的声明次序就是其在初始化列表中初始化的顺序,与其在初始化列表中的次序无关;
(八)explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。
用explicit修饰构造函数,将会禁止构造函数的隐式转换。
二、析构函数
析构函数是在对象销毁时由编译器自动调用,完成对象中资源的清理工作。
(一)函数名与返回值
析构函数函数名是在类名前加~,无参数无返回值。
(二)析构函数不能重载
一个类只能有一个析构函数,不能进行函数重载。若未显式定义,系统则会生成一个默认的析构函数。如果类中有资源的申请(堆上开空间等),则一定要显式定义析构函数,否则会造成内存泄漏。
(三)析构函数的调用
对象生命周期结束时,编译器自动调用析构函数。
(四) 默认析构函数
同样的,默认生成的析构函数对内置类型不处理(也不需要处理),对自定义类型会去调用其析构函数。
三、拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
(一)函数名与返回值
拷贝构造函数其实是构造函数的一个函数重载,拷贝构造函数的参数只有一个且是类类型对象的引用。若使用传值传参则会引发无穷递归报错,编译器会直接报错。
在传值传参时,编译器会将调用的参数拷贝一份再传给被调用的函数,由于该函数就是拷贝函数,因为在将参数传给拷贝构造函数的中途会再次调用拷贝构造函数,因此陷入无穷递归。
(二)默认拷贝构造函数
若未显式定义,编译器将生成默认的拷贝构造函数。默认的拷贝构造函数对象将按照内存存储的字节节序完成拷贝——浅拷贝。
(三)浅拷贝与深拷贝
浅拷贝在上文已经提及,对于内置类型,使用浅拷贝即可。但对于在类中成员变量动态开辟空间后,浅拷贝就不能满足需求了。
对于类中动态开辟的成员变量拷贝时,若使用浅拷贝生成新对象,则新对象中动态开辟空间的变量与被拷贝的对象中的变量所动态开辟的空间一致,同时还面临着同一空间被析构释放两次的风险。
因此我们需要显式定义深拷贝。
class Stack
{
public:
Stack(int capacity = 4)//构造函数
{
_capacity = capacity;
_top = 0;
_arr = (int*)malloc(sizeof(int) * capacity);
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(4);
Stack s2(s1);
return 0;
}
四、赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
只有赋值运算符是默认成员函数。默认生成的赋值运算符重载会完成对象的值拷贝。
在函数的参数设置为const T& 可减少在传参过程中的拷贝构造,同时要注意自己对自己的赋值。
class Stack
{
public:
Stack(int capacity = 4)//构造函数
{
_capacity = capacity;
_top = 0;
_arr = (int*)malloc(sizeof(int) * capacity);
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& operator==(const Stack& st)//赋值重载
{
if (this != &st)
{
free(this->_arr);
_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;
};
五、取地址以及const取地址运算符重载
在一些特殊的场景下需要显式定义,如想让别人通过取地址操作符获取到特定值(自己在重载函数内部写)或屏蔽类地址。
Date* operator&()
{
return this;
//return nullptr;
}
const Date* operator&()const
{
return this;
//return nullptr;
}