【C++】类和对象(中)构造函数、析构函数
一. 类的 6 个默认成员函数
默认成员函数:我们不写,编译器会自己生成
C语言用栈时:1. 有时会忘记初始化、销毁(内存泄漏) 2. 有些地方写起来很繁琐
C++进化到可以自动初始化、销毁
以前的 C++ 栈:
typedef int DataType;class Stack
{
public:void Init(int capacity = 4){_a = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){CheckCapacity();_a[_size] = data;_size++;}void Pop(){if (Empty())return;_size--;}DataType Top() { return _a[_size - 1]; }int Empty() { return 0 == _size; }int Size() { return _size; }void Destroy(){if (_a){free(_a);_a = nullptr;_capacity = 0;_size = 0;}}private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_a, newcapacity *sizeof(DataType));if (temp == nullptr){perror("realloc申请空间失败!!!");return;}_a = temp;_capacity = newcapacity;}}private:DataType* _a;int _capacity;int _size;
};
不 Init,不 Destroy
int main()
{Stack s;// s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);printf("%d\n", s.Top());printf("%d\n", s.Size());s.Pop();s.Pop();printf("%d\n", s.Top());printf("%d\n", s.Size());// s.Destroy();return 0;
}
报错,程序异常退出
二. 构造函数
构造函数是特殊的成员函数。不是开空间创建对象,而是初始化对象,在对象整个生命周期内只调用一次
对象不需要某个函数创建。因为对象在栈里面,栈里面的变量是自动创建的(跟着栈帧走的)。函数调用,给局部变量开空间;函数结束,变量随栈帧销毁,空间也就销毁了
特征1. 函数名与类名相同
特征2. 无返回值,也不写 void
特征3. 对象实例化时 编译器 自动调用 对应的构造函数
现在的 C++ 栈:
class Stack
{
public:Stack(int capacity = 4) // 构造函数,功能:替代Init{cout << "Stack(int capacity = 4)" << endl;_a = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}/*void Init(int capacity = 4){_a = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}*/void Push(DataType data){ }void Pop(){ }DataType Top() { return _a[_size - 1]; }int Empty() { return 0 == _size; }int Size() { return _size; }void Destroy(){if (_a){free(_a);_a = nullptr;_capacity = 0;_size = 0;}}private:void CheckCapacity(){ }private:DataType* _a;int _capacity;int _size;
};
不 Init,不 Destroy
C++祖师爷规定了,对象实例化的时候,自动调用构造函数。从此不需要 Init( )
现在程序还存在内存泄漏,因为我们没有调 Destroy。先看:三. 析构函数
特征 4:构造函数可以重载
为什么构造函数支持重载? 因为有多种初始化方式
eg:一上来有一组数据作为默认初始化
class Stack
{
public:Stack(DataType* a, int n){cout << "Stack(DataType* a, int n)" << endl;_a = (DataType*)malloc(sizeof(DataType) * n);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}memcpy(_a, a, sizeof(DataType) * n);_capacity = n;_size = n;}Stack(int capacity = 4) // 构造函数,功能:替代Init{cout << "Stack(int capacity = 4)" << endl;_a = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}......~Stack(){cout << "~Stack()" << endl;if (_a){free(_a);_a = nullptr;_capacity = 0;_size = 0;}}private:void CheckCapacity(){ }private:DataType* _a;int _capacity;int _size;
};
特征 5:自动生成
如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
class Date
{
public:/* // 如果用户显式定义了构造函数,编译器将不再生成Date(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;
};int main()
{Date d1;Date d2;d1.Print();return 0;
}
一堆随机值,自动生成的构造函数好像啥也没干(编译器会干,只是我们看不见)。而且我咋知道有没有生成构造函数?
这是祖师爷的失误,这里应该初始化为0 C++标准没有规定要初始化
特征 6:
C++ 把类型分为2类:
1. 内置 / 基本类型:语言本身定义的基础类型 int / char / double / 指针 ……
2. 自定义类型: struct / class 等类型
我们不写,编译器默认生成的构造函数:
内置类型不做处理(有些编译器会处理,但我们当做不处理)、自定义类型会去调用他们的默认构造
结论:
1. 一般情况,要自己写构造函数
2. 用编译器自动生成的就可以:
a. 内置类型成员都有缺省值,且初始化符合我们的要求
b. 全是自定义类型的构造,且这些类型都定义了默认构造(用栈实现队列,定义2个栈)
2. a. 1
C++ 11发布时,打了补丁:成员声明时,可以给缺省值(主要针对内置类型)
class Date
{
public:void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:// 内置类型// C++11支持,这里不是初始化,因为这里只是声明,没有开空间// 这里给的是默认的缺省值,给编译器生成的默认构造函数用int _year = 1;int _month = 2;int _day = 3;
};int main()
{Date d1;d1.Print();return 0;
}
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year = 1;int _month = 2;int _day = 3;
};int main()
{Date d1(2025, 6, 1); // 在这显式初始化了,不会用缺省值d1.Print();return 0;
}
此时必须自己写构造函数,否则:
补充:构造函数的调用问题
构造函数的定义很特殊:同名、无返回值、自动调用 构造函数的调用也很特殊
class Date
{
public: // 写2个构造函数,构造函数可以重载。有多种初始化方式Date() // 可以无参{_year = 2025;_month = 6;_day = 1;}Date(int year, int month, int day) // 可以带参{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:// 内置类型// C++11支持,这里不是初始化,因为这里只是声明,没有开空间// 这里给的是默认的缺省值,给编译器生成的默认构造函数用int _year = 1;int _month = 2;int _day = 3;
};
int main()
{Date d1(2025, 6, 1); // 构造函数的调用1:对象 + 参数列表Date d2; // 构造函数的调用2:对象不加列表d1.Print(); // 普通函数的调用:函数名 + 参数列表d2.Print();Date d3(); // 报警告// 原因:与函数声明冲突,编译器不好识别。// 这么写编译器可以看做构造函数的调用;也可以看做一个函数的声明// Date d1(2025, 6, 1); 这一看就不是函数声明。函数声明()里不是变量对象,是类型return 0;
}
// 有人认为祖师爷脑子不清楚,下面这样写就没上面那么多事:对象调函数,变成正常的函数调用
int main()
{Date d1; // 对象调函数。对象.函数名d1.Date(); // 实事是:报错:类型名称“Data”不能出现在类成员访问表达式的右侧Date d2;d2.Date(2025, 6, 1); // 实事是:报错return 0;
}// 能这么写,为什么不这样写?:
int main()
{Date d1;d1.Init(); // 实事是:报错 叫 Init 不是更香吗,为什么还搞 Data出来?Date d2;d2.Init(2025, 6, 1); // 实事是:报错return 0;
} // 回去了。对象实例化时,自动调用怎么办?
2. a. 2
struct TreeNode
{TreeNode* _left;TreeNode* _right;int _val;
};class Tree
{
private:TreeNode* _root; // 定义一棵树,最开始要有根节点// 能不能不写它的构造函数? 可以!// 直接不写肯定不行,默认生成的构造函数对内置类型不初始化
};int main()
{Tree t1;return 0;
}
class Tree
{
private:TreeNode* _root = nullptr;
};
灵活应变:
struct TreeNode
{TreeNode* _left;TreeNode* _right;int _val;TreeNode(int val = 0) // 这里推荐自己写默认构造{_left = nullptr;_right = nullptr;_val = val;}
};class Tree
{
private:TreeNode* _root = nullptr; // 定义一棵树,最开始要有根节点
};int main()
{Tree t1;TreeNode n0;TreeNode n1(1);TreeNode n2(8);return 0;
}
2. b.
class Stack
{
public:Stack(int capacity = 4) // 这些类型(Stack)都定义了默认构造{cout << "Stack(int capacity = 4)" << endl;_a = (DataType*)malloc(sizeof(DataType) * capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}~Stack(){ }private:DataType* _a = nullptr;int _capacity;int _size = 0;
};class MyQueue
{
private: // 自定义类型(Stack)成员Stack _pushst;Stack _popst;
};int main()
{MyQueue q;return 0;
}
特征 7:默认构造函数
无参的构造函数 和 全缺省的构造函数 都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是 默认构造函数
总结:不传参就可以调用的就是默认构造函数
构造函数虽可重载,但写全缺省最香:
class Date
{
public:Date() // 无参{_year = 2025;_month = 6;_day = 1;}Date(int year = 1, int month = 1, int day = 1) // 全缺省{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:// 内置类型// C++11支持,这里不是初始化,因为这里只是声明,没有开空间// 这里给的是默认的缺省值,给编译器生成的默认构造函数用int _year = 1;int _month = 2;int _day = 3;
};
无参、全缺省语法上可同时存在,因为构成函数重载 但无参调用存在歧义,所以现实中不会同时存在
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:// 内置类型// C++11支持,这里不是初始化,因为这里只是声明,没有开空间// 这里给的是默认的缺省值,给编译器生成的默认构造函数用int _year = 1;int _month = 2;int _day = 3;
};int main()
{Date d1(2022);d1.Print();Date d2(2025, 6);d2.Print();return 0;
}
class Date
{
public:Date(int year, int month = 1, int day = 1) // 不写成全缺省(没有默认构造){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:// 内置类型// C++11支持,这里不是初始化,因为这里只是声明,没有开空间// 这里给的是默认的缺省值,给编译器生成的默认构造函数用int _year = 1;int _month = 2;int _day = 3;
};int main()
{Date d1; // 不传参数:报错:“Data”: 没有合适的默认构造函数可用// 可以不传参的是默认构造,这里必须传参数d1.Print(); return 0;
}
三. 析构函数
析构函数是特殊的成员函数,不是完成对象本身的销毁(不是销空间)。局部对象(在栈帧里,由系统完成)销毁工作是由编译器完成的
特征 1:析构函数名:~类名
特征 2:无参数(析构函数不能重载),无返回值
特征 3:对象生命周期结束(销毁)时,自动调用,完成对象中资源清理工作
class Stack
{
public:Stack(int capacity = 4) // 构造函数{ }~Stack() // 析构函数{cout << "~Stack()" << endl;if (_a){free(_a);_a = nullptr;_capacity = 0;_size = 0;}}/*void Destroy(){if (_a){free(_a);_a = nullptr;_capacity = 0;_size = 0;}}*/private:void CheckCapacity(){ }private:DataType* _a;int _capacity;int _size;
};
Stack 有了构造、析构,就不怕忘记写 初始化、清理函数 了,也简化了
特征 4:自动生成
一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
1、内置类型成员不做处理 2、自定义类型会去调用它的析构函数
所以,上面的代码,不写自己写析构,编译器默认生成的析构,不会释放 _a指向的空间,内存泄漏
class Stack
{
public:Stack(int capacity = 4) // 构造函数,功能:替代Init{ }private:DataType _a[100];int _capacity;int _size;
};
写的是静态的会不会释放?
析构函数是释放 动态申请(堆)的资源。这种 静态的资源(栈)不用手动释放,出了作用域会自动销毁
只有堆上的要手动释放
总结
1、一般情况下,有动态申请资源,就需要显式写析构函数,来释放资源
2、没有动态申请的资源,不需要写析构
3、需要释放资源的成员都是自定义类型,不需要写析构,前提:类型都定义了析构函数
// 1、栈是经典的需要写析构
~Stack()
{cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = _size = 0;
}// 2、日期类没有析构可写,没有申请资源
class Data
{
private:int _year;int _month;int _day;
};// 3、默认生成的析构会自动调用析构
class MyQueue
{
private:Stack _pushst;Stack _popst;
};
本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注。
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章