> 技术文档 > 【C++】类和对象(中)构造函数、析构函数

【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;};

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章