当前位置: 首页 > news >正文

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

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

http://www.dtcms.com/a/294246.html

相关文章:

  • 海信IP501H-IP502h_GK6323处理器-原机安卓9专用-优盘卡刷固件包
  • ZLMediaKit流媒体服务器WebRTC页面显示:使用docker部署
  • Android多开实现方案深度分析
  • Android13重置锁屏(2)
  • 论文略读:Knowledge is a Region in Weight Space for Finetuned Language Models
  • springboot集成LangChain4j
  • 世博会无法在Android上启动项目:无法连接到TCP端口5554:连接被拒绝
  • 2025暑期—05神经网络-BP网络
  • PyCharm配置python软件安装步骤(附安装包)PyCharm 2025 超详细下载安装教程
  • 【CNN】LeNet网络架构
  • 盟接之桥说制造:浅谈“客供共生关系”:构建能力闭环,实现价值共赢
  • 论文笔记:On the Biology of a Large Language Model
  • Java 高频算法
  • Python通关秘籍(七)数据结构——集合
  • mysql什么时候用char,varchar,text,longtext
  • Git 完全手册:从入门到团队协作实战(4)
  • 经典神经网络之LetNet
  • 【前沿技术动态】【AI总结】RustFS:从 0 到 1 打造下一代分布式对象存储
  • Java 时间处理 API 全解析:从 JDK7 到 JDK8 的演进
  • 有序数组中出现次数超过25%的元素
  • 数字人形象视频:开启虚拟世界的全新篇章
  • Linux 723 磁盘配额 限制用户写入 quota;snap快照原理
  • IRF 真机实验
  • [AI8051U入门第八步]硬件IIC驱动AHT10温湿度传感器
  • 密码学中的概率论与统计学:从频率分析到现代密码攻击
  • 【Kubernetes】集群启动nginx,观察端口映射,work节点使用kubectl配置
  • scikit-learn 包
  • 【后端】 FastAPI
  • AI替代人工:浪潮中的沉浮与觉醒
  • LNMP-zblog分布式部署