任何一个类,只要被创建,都会自动生成6个默认成员函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。它用于对象的初始化。
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
以下面的程序为例:
class Data
{
public:
//Data()
//{
// _year = 2022;
// _month = 4;
// _day = 7;
//}
//Data(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
//语法上无参和全缺省的可以同时存在
//但是如果存在无参调用,就会存在二义性
Data()
{
_year = 2022;
_month = 4;
_day = 7;
}
//推荐使用全缺省或者半缺省,因为比较好用
Data(int year = 2022, int month = 4, int day = 7)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;// 调用无参构造函数
d1.print();
Data d2(2022, 4, 11);// 调用带参的构造函数
d2.print();
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();
return 0;
}
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char...,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员aa调用的它的默认成员函数
class A
{
public:
A(int a = 0)
{
cout << "A()" << endl;
}
};
class Data
{
// C++里面把类型分为两类:内置类型(基本类型),自定义类型
// 内置类型:int/char/double/指针/内置类型数组 等等
// 自定义类型:struct/class定义的类型
// 我们不写编译器默认生成构造函数,对于内置类型不做初始化处理
// 对于自定类型成员变量会去调用它的默认构造函数(不用参数就可以调的)初始化,如果没有默认构造函数就会报错
// 任何一个类的默认构造函数就是--不用参数就可以调用。
// 任何一个类的默认构造函数有三个,全缺省、无参、我们不写编译器默认生成的。
private:
int _year;
int _month;
int _day;
A aa;
};
int main()
{
Data d1;
return 0;
}
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
以下面代码为例:
class Data
{
public:
//推荐使用全缺省或者半缺省,因为比较好用
Data(int year = 2022, int month = 4, int day = 7)
{
_year = year;
_month = month;
_day = day;
}
~Data()
{
//Data类没有资源需要清理,所以Data不实现析构函数也是可以的
cout << " aaa" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
Data d2(2022, 4, 8);
return 0;
}
析构函数是特殊的成员函数。其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);//在堆上申请空间
_capacity = capacity;
_top = 0;
}
~Stack()
{
free(_a);//释放堆上的空间
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
Stack s1;
Stack s2;
return 0;
}
析构函数的调用顺序:以上面的程序为例子,由于析构函数是在栈帧上创建的,所以按照栈帧后进先出的销毁顺序,应该是先调用s2的析构函数,再调用s1的析构函数。
如果我们不写默认生成析构函数,和构造函数类似;对于内置类型的成员不做处理;对于自定义类型的成员变量会回去调用它的析构函数,以下面这个用栈实现队列为例说明一下:
// 两个栈实现一个队列
class MyQueue {
public:
// 默认生成构造函数和析构函数会对自定义类型成员调用他的构造和析构
void push(int x) {
}
private:
Stack pushST;
Stack popST;
};
int main()
{
Date d1;
Date d2(2022, 1, 15);
Stack s1;
Stack s2(20);
MyQueue mq;
return 0;
}
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
class Date
{
public:
//推荐使用全缺省或者半缺省,因为比较好用
Date(int year = 2022, int month = 4, int day = 7)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//传引用,这里最好加const
{
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
//Data类没有资源需要清理,所以Data不实现析构函数也是可以的
cout << " aaa" << endl;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 1, 15);
//拷贝复制
Date d2(d1);
d1.print();
d2.print();
return 0;
}
当使用传值方式来创建拷贝构造函数时,会引发无穷递归调用,下面图示进行说明。
3.若未显示定义,系统生成默认拷贝构造函数
默认生成拷贝构造:
1.内置类型成员,会完成按字节的拷贝,但这是浅拷贝,对于对于日期类对象,没有在堆上创建数据的对象,浅拷贝是可行的,但是对于栈对象,需要在在堆上创建数据的对象,我们需要用到深拷贝来完成对象的拷贝,为了实现深拷贝,我们需要自己实现拷贝构造函数。
2.自定义类型成员,会调用它的拷贝构造函数