1、类的默认成员函数
用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。
用户不实现的情况下,一个类中编译器会默认生成以下6个默认成员函数,其中最重要的是前四个函数。

2、构造函数
首先给出Stack类、Date类和MyQueue类的代码:
cpp
typedef int STDataType;
class Stack
{
public:
void Init(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
//...其他成员函数
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
cpp
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
cpp
class MyQueue
{
private:
Stack s1;
Stack s2;
};
构造函数的作用是对象实例化时初始化对象。构造函数的本质是要替代Stack和Date类中写的Init函数的功能,构造函数自动调用的特点相比手动调用Init函数具有优越性。
(1)构造函数的特性
①函数名与类名相同。
②无返回值。
③对象实例化时系统会自动调用对应的构造函数。
④构造函数可以重载。
⑤如果类中没有显式定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,一旦用户显式定义,编译器将不再生成。
⑥无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只能有一个存在。
编译器默认生成的构造函数自不用说,一旦用户显式定义,编译器将不再生成。无参构造函数和全缺省构造函数构成函数重载,虽然构造函数允许重载,但是这两个函数调用时会产生歧义。总的来说,不传实参就可以调用的构造就叫默认构造。
⑦编译器默认生成的构造,对内置类型成员变量的初始化没有要求,(可以直接认为编译器不初始化),对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化,如果这个成员变量没有默认构造函数,那么就会报错,要初始化这个成员变量,需要用初始化列表才能解决。
cpp
// 1.⽆参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 3.全缺省构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int main()
{
Data d1;//调用默认构造函数,注意不要写成Data d1();否则编译器无法区分这里是函数声明还是实例化对象
Data d2(2025,1,1);//调用带参的构造函数
}
3、析构函数
析构函数与构造函数功能相反,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比Stack中实现的Destroy,而像Date没有 Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。
(1)析构函数的特性
①析构函数名是在类名前加上字符~。
②无参数无返回值。
③一个类只能有一个析构函数。(没有重载的概念)
④对象生命周期结束时,系统会自动调用析构函数。
⑤未显式定义时,跟构造函数类似,自动生成的默认析构函数对内置类型成员不做处理,自定类型成员会调用它的析构函数。
⑥显式定义时,对于自定义类型成员也会调用它的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数
⑦如果类中没有申请资源,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如 果默认生成的析构就可以用,也就不需要显式写析构,如MyQueue;但是有资源申请时,一定要 自己写析构,否则会造成资源泄漏(内存泄漏不报错),如Stack。
⑧一个局部域的多个对象,C++规定后定义的先析构。
Stack的构造函数和析构函数:
cpp
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
}
4、拷贝构造函数
如果一个构造函数的第⼀个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造函数是构造函数的一个重载。
(1)拷贝构造函数的特性
①以Data类为例,其拷贝构造函数开头应为Date(const Date& d),其中const的作用是避免被拷贝的内容被修改,而使用引用是因为若使用传值方式,在语法逻辑上会引发无穷递归调用,编译器会报错(每次要调用拷贝构造函数之前要先传值传参,传值传参是一种拷贝,又需要用到拷贝构造函数,形成无穷递归)
②C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
③若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成 员变量会完成值拷贝/浅拷贝(逐字节的拷贝),对自定义类型成员变量会调用它的拷贝构造。
④像Date这样的类,成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像Stack这样的类,虽然成员变量也都是内置类型,但是_a指向了资源,编译器自动生成的拷贝构造完成的浅拷贝会使两个类中的指针指向同一块空间,该空间会被析构两次,程序运行会崩溃,所以需要自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型 Stack成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,也不需要我们显式实现。如果一个类显式实现了析构并释放资源,那么它就需要显式写拷贝构造,否则就不需要。
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2026, 1, 29);
Date d2(&d1); // 这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造
Date d3(d1);// 或者可以这么写:Date d3 = d1;
d1.Print();
d2.Print();
d3.Print();
}
Stack的拷贝构造函数:
cpp
Stack(const Stack& st)
{
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_top = st._top;
_capacity = st._capacity;
}