构造和析构函数
对于类定义的每个对象,都需要初始化和清理。(清理可能不需要,一旦需要,但忘记了清理,就会内存泄露)
构造和析构函数,最大的好处是自动调用。这样我们就不用提心吊胆,检查有没有忘记什么操作。
我们可以自己定义构造和析构函数,也可交给编译器自动帮我们构造。
实际情况是,构造函数大多要自己写出来。但析构大部分可以不用自己写。(内置类型和自定义类型)
若我们不显式定义构造函数,编译器自动生成的,大都不满足我们的需求。(尤其是内置类型,随机的值)
析构函数则相反,只要对象中的成员没有额外申请资源,我们就可以不用自己析构。
顺序
析构对象时:反向,后定义的先释放。
构造对象时:正向,先定义的先构造。
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (_a == nullptr)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
cout << "Stack()" << endl;
}
~Stack()
{
cout <<" ~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
class MyQueue
{
public:
MyQueue()
{
cout << "MyQueue()" << endl;
}
~MyQueue()
{
cout << "~MyQueue()" << endl;
}
private:
Stack pushst;
Stack popst;
};
int main()
{
Stack st;
MyQueue mq;
return 0;
}

拷贝构造(复制)
故事发生在初始化对象时,我们想要复制粘贴一份对象到另一个对象中去~
若是实参传入同类型对象,就需要调用拷贝构造。
特点
解释:
若是给传值传参函数传入类对象,程序会先去调用类的拷贝构造,把实参对象的值拷贝给形参,才算成功传入了实参。
即传值传参会形成新的拷贝构造。
而如果类自身的拷贝构造函数,使用的就是传值传参,那么当参数传进来后,它又形成一个新的拷贝构造,将无穷递归,无法返回。
使用引用,给传入的参数取别名,而不是创建新的对象,避免了无限调用拷贝构造。
(传指针也可以,但就不称为拷贝构造了,因为传实参的形式不一样,只能称为普通的构造函数)
class Date
{
public:
Date(int year=1,int month=1,int day =1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数,const引用
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;
};
Date d1(2026, 6, 12);
//这里采用传指针,可以完成拷贝,但不是拷贝构造,仅仅是一个普通的构造。
Date d2(&d1);
d1.Print();
d2.Print();
//这样写才是拷贝构造,通过同类型的对象初始化构造,而不是指针。
Date d3(d1);
d3.Print();
//也可这样写,也是拷贝构造。
Date d4 = d1;
d4.Print();

3.凡是自定义类型对象,进行拷贝行为必须调用拷贝构造,故传值传参和传值返回,都会调用拷贝构造完成。
void Func1(Date d)
{
cout << &d << endl;
d.Print();
}
Date d1(2026, 6, 12);
//C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里传值传参要调用拷贝。
//故这里的d1传值传参给d要调用拷贝构造完成拷贝,传引用传参可以减少这里的拷贝行为。
Func1(d1);
cout << &d1 << endl;

可以看到,这里打印出来的&d和&d1不同,说明d1的值成功拷贝进入d地址中。
4.无显式定义的拷贝构造函数时,由编译器默认生成,对内置类型完成值拷贝/浅拷贝,
注意:如果内置类型是指针并且申请了资源,直接进行值拷贝,会出问题。
①两个类对象的成员指针变量会指向同一个位置,一个修改会影响另一个。
②若析构一个对象后,其指向空间被释放,另一个对象会析构同一空间,该空间就被释放两次。崩溃。
这种情况下,必须自己定义相应的拷贝构造函数——
深拷贝 ①开辟一个相同大小的空间,让新对象的指针指向 这一块空间。 ②给这个空间存入一样的内容。(开辟新空间、拷贝数值)
如此一来,两对象将互不影响。
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (_a == nullptr)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
//自定义实现深拷贝。
Stack(const Stack& st)
{
//先让_a指向一块同样大小的空间,再拷贝值。
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
//进行拷贝。
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_capacity = st._capacity;
_top = st._top;
}
void Push(STDataType x)
{
if (_top == _capacity)
{
int newcapacity = _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, newcapacity * sizeof(STDataType));
if (tmp == nullptr)
{
perror("realloc fail");
return;
}
_capacity = newcapacity;
_a = tmp;
}
_a[_top++] = x;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
Stack st1;
st1.Push(1);
st1.Push(2);
//如果用自动生成的拷贝构造完成浅拷贝,
//会导致st1,st2里面的_a指针指向同一块资源,析构时会析构两次,程序崩溃。
//这里使用的是自定义的深拷贝构造。
Stack st2 = st1;
而对于自定义类型,编译器默认调用其构造、拷贝构造和析构函数。
class MyQueue
{
public:
//…
private:
Stack pushst;
Stack popst;
};
//生成mq1时,编译器自动调用Stack类的构造函数来构造pushst,popst
MyQueue mq1;
//MyQueue自动生成的拷贝构造,会自动调用Stack拷贝构造来构造pushst,popst的拷贝
//只要Stack的拷贝构造没问题,MyQueue的拷贝就没问题。
MyQueue mq2 = mq1;
5.若需要显式写析构函数,就需要显式写拷贝构造。反之,也成立。
6.只有当前函数结束后还存在的 返回对象,才能用引用返回。否则,野引用。
Date& Func2()
{
Date tmp(2024, 7, 5);
tmp.Print();
return tmp;
}
Date d1(2026, 6, 12);
//Func2返回一个局部对象tmp的引用作为返回值
//Func2函数结束,tmp对象就销毁了,相当于一个野引用
Date ret = Func2();
ret.Print();

若是传值返回,会调用拷贝构造,创建一个临时对象,即使函数结束,局部对象均被销毁,临时对象也还存在,程序可以正常运行。
调用形式
- Date d2 = d1 ;
- Date d2(d1) ;
补充
数组不需要释放。
数组在对象空间内,没有额外申请资源。
指针也不一定需要释放,主要看它是否有动态开辟资源。(以上提到的指针均有额外资源)
![P11246 [GESP202409 六级] 小杨和整数拆分-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108100631-6af049a75855c-220x150.png)


![打卡信奥刷题(3678)用C++实现信奥题 P12274 [蓝桥杯 2024 国 Python B] 设计-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261108053830-6af00ad6bdf8d-220x150.png)