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

类和对象(C++)——默认成员函数,构造函数,析构函数

1. 类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,不写的情况下编译器会默认生成以下6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载,后面小编会大概讲解一下,了解⼀下。其次就是C++11以后还会增加两个默认成员函数, 移动构造和移动赋值,这个小编后面再讲解。默认成员函数比较复杂,从两个⽅⾯去考虑:

a)我们不写时,编译器默认⽣成的函数行为是什么,是否满⾜我们的需求。

b)编译器默认⽣成的函数不满⾜我们的需求,需要自己实现,如何自己实现?

2. 构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),⽽是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数⾃动调⽤的特点就完美的替代的了Init。

构造函数的特点:

a)函数名与类名相同。

b)⽆返回值。(返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)

c)对象实例化时系统会⾃动调⽤对应的构造函数。

例如:

class Date
{
public:// 1.⽆参构造函数Date(){_year = 1;_month = 1;_day = 1;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;// 调⽤默认构造函数d1.Print();return 0;
}

当对d1进行实例化时,会调用构造函数Date进行初始化。

d)构造函数可以重载。

例如:

#include<iostream>
using namespace std;class Date
{
public:// 1.⽆参构造函数Date(){_year = 1;_month = 1;_day = 1;}//2.带参构造函数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(2024,12,13);d1.Print();d2.Print();return 0;
}

结果:

Date构造函数,参数的个数,类型不同,构成函数重载。

e)如果类中没有显式定义构造函数,则C++编译器会自动⽣成⼀个⽆参的默认构造函数,⼀旦⽤户显式定义编译器将不再生成。

#include<iostream>
using namespace std;class Date
{
public:void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2;// 调⽤默认构造函数d1.Print();d2.Print();return 0;
}

当自己没有定义构造函数时,编译器就会自动⽣成⼀个无参的默认构造函数,所以给的值就是随机值了。

f)无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。注意很多小伙伴会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,简而言之就是不传实参就可以调用的构造就叫默认构造。

例如:

无参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义

#include<iostream>
using namespace std;class Date
{
public:1.⽆参构造函数Date(){_year = 1;_month = 1;_day = 1;}//3.全缺省构造函数Date(int year = 1, int month = 1, int day = 1){_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();d2.Print();return 0;
}

g)我们不写,编译器默认生成的构造,对内置类型成员变量(如:int,float,double,int*)的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员(如:struct)变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决,初始化列表,小编后面章节再讲解。

例如:

这里用之前“用栈实现队列“举例子

typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc fail");return;}_capacity = n;_top = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
// 两个Stack实现队列class MyQueue
{
public://编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
private:Stack pushst;Stack popst;
};int main()
{MyQueue mq;return 0;
}

这里在实例化mq对象时,就会去调用stack的构造函数,进行初始化。

3. 析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。析构函数的功能类⽐我们之前Stack实现的Destroy功能,⽽像Date没有Destroy,其实就是没有资源需要释放(例如malloc堆上开辟的空间就需要释放),所以严格说Date是不需要析构函数的。

析构函数的特点:

a)析构函数名是在类名前加上字符~。

b)⽆参数⽆返回值。(这⾥跟构造类似,也不需要加void)

c)⼀个类只能有⼀个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数。

d)对象⽣命周期结束时,系统会⾃动调⽤析构函数。

f)跟构造函数类似,我们不写,编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调⽤他的析构函数。

typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc fail");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;
};
// 两个Stack实现队列class MyQueue
{
public://编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
private:Stack pushst;Stack popst;
};int main()
{Stack st;MyQueue mq;return 0;
}

结果:

这里生命周期结束时,就会调用析构函数,这里实例化对象st,调用了一次析构函数,实例化对象mq(自定义类型)时,中的两个pushst,popst,会去调用Stack中中的析构函数。

g)如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date;如果默认生成的析构就可以⽤,也就不需要显示写析构,如MyQueue;但是有资源申请时,⼀定要自己写析构,否则会造成资源泄漏,如Stack。

h)⼀个局部域的多个对象,C++规定后定义的先析构。

也就是上面这段代码中的mq中的pushst,和popst会先析构,再对st进行析构。

示例:对比⼀下⽤C++和C实现的Stack解决之前括号匹配问题isValid(小编前面文章有详细讲解,这里就不详细讲逻辑),我们发现有了构造函数和析构函数确实⽅便了很多,不会再忘记调⽤Init和Destory函数了,也⽅便了不少。

//C++版
bool isValid(const char* s) {Stack st;while (*s){if (*s == '[' || *s == '(' || *s == '{'){st.Push(*s);}else{// 右括号⽐左括号多,数量匹配问题if (st.Empty()){return false;}// 栈⾥⾯取左括号char top = st.Top();st.Pop();// 顺序不匹配if ((*s == ']' && top != '[')|| (*s == '}' && top != '{')|| (*s == ')' && top != '(')){return false;}}++s;}// 栈为空,返回真,说明数量都匹配左括号多,右括号少匹配问题return st.Empty();
}// ⽤之前C版本Stack实现
bool isValid(const char* s) {ST st;STInit(&st);while (*s){// 左括号⼊栈if (*s == '(' || *s == '[' || *s == '{'){STPush(&st, *s);}else // 右括号取栈顶左括号尝试匹配{if (STEmpty(&st)){STDestroy(&st);return false;}char top = STTop(&st);STPop(&st);// 不匹配if ((top == '(' && *s != ')')|| (top == '{' && *s != '}')|| (top == '[' && *s != ']')){STDestroy(&st);return false;}}++s;}//栈不为空,说明左括号⽐右括号多,数量不匹配bool ret = STEmpty(&st);STDestroy(&st);return ret;
}

http://www.lryc.cn/news/483417.html

相关文章:

  • 深入理解 Vue v-model 原理与应用
  • 内网域环境、工作组、局域网等探针方案
  • uniapp—android原生插件开发(3Android真机调试)
  • goframe开发一个企业网站 统一返回响应码 18
  • 基于STM32的智能门禁系统设计
  • Python学习从0到1 day28 Python 高阶技巧 ⑧ 递归
  • 知识见闻 - 苹果手机拨号键长按
  • 在 KubeVirt 中使用 GPU Operator
  • 安慰剂检验Stata代码(全套代码、示例数据及参考文献)
  • DAY6 线程
  • 基于STM32的智能门锁系统设计思路:蓝牙、RFID等技术
  • AndroidStudio-广播
  • 基于表格滚动截屏(表格全部展开,没有滚动条)
  • 洛谷P1255
  • vue3设置第三方组件 样式::v-deep
  • JAVA学习日记(十四)集合进阶
  • mysql全量与增量备份
  • “非法”操控lambda(python)
  • UDP协议和TCP协议之间有什么具体区别?
  • 论文5—《基于改进YOLOv5s的轻量化金银花识别方法》文献阅读分析报告
  • 快手直播间采集教程,快手引流,快手截流,截流工具,直播间截流,快手直播间采集,获客系统,获客软件
  • 探索MoviePy:Python视频编辑的瑞士军刀
  • mysql 实现分库分表之 --- 基于 MyCAT 的分片策略详解
  • Opencascade基础教程(14): 一个模型显示问题
  • ISP——你可以从这里起步(二)
  • Qt / Qt Quick程序打包的一些坑 (四)
  • 《传统视觉算法在视觉算法中的地位及应用场景
  • 老老实实干一辈子程序员是没出息的!这本证书你早该学!
  • 鸿蒙next版开发:相机开发-录像(ArkTS)
  • 闯关leetcode——3206. Alternating Groups I