C++类和对象(中)(1)

server/2024/11/15 0:09:19/

一、类的6个默认成员函数

在一个类中,如果这个类什么都没有,那么这个类我们称之为空类,那么空类中真的什么都没有吗?其实并不是,任何类在什么都不写的情况下,编译器也会自动生成六个默认成员函数。

默认成员函数:用户没有显式实现,编译器会自动生成的成员函数被称为默认成员函数

下面我们来详细介绍下这几个默认成员函数。

二、构造函数

2.1 构造函数的概念

我们来看下下面的一个Date类:

#include<iostream>
using namespace std;class Date
{
public :void Init(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;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2004, 11, 12);d2.Print();return 0;
}

对于这个Date类,我们可以通过Init公有方法给对象设置日期, 但如果每次都用Init方法初始化的话比较麻烦,因此就出现了构造函数,构造函数能够将对象在创建的时候同时给创建出的对象进行初始化。

构造函数是一个特殊的成员函数构造函数的函数名与类名相同,构造函数在创建类类型对象时,会由编译器自动调用,这样可以保障每个数据成员都有一个合适的初始值,而且构造函数在对象的整个生命周期内只能调用一次

2.2 构造函数的特性

构造函数是特殊的成员函数,但是我们要知道,虽然构造函数的的名字叫做构造,它所做的事情并不是创建对象开空间,而是初始化对象。类似于Init函数的功能。

构造函数的特征如下
1.函数名与类名相同

2.没有返回值

3.对象实例化时编译器自动调用对应的构造函数

4.构造函数可以重载

 构造函数可以写多个,即有多种初始化方式:

我们来看下面几个构造函数的实例:

class Date
{
public://1.无参构造函数Date(){}//2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;// 调用无参构造函数Date d2(2004, 11, 11);//调用带参构造函数}

注意:如果通过无参构造函数创建对象时,对象后面不用加括号,加了括号就变成函数声明了。

对于要调用带参数的构造函数时在创建对象的时候,在对象后面加上参数列表。

对于无参构造函数和带参的构造函数我们可以写成一个全缺省的带参构造,但是如果写了全缺省的代参构造后就不要再写无参的构造函数了,因为会产生调用歧义

下面就是一个全缺省的带参构造的实例:

class Date
{
public://全缺省带参构造Date(int year = 2004, int month = 11, int day = 30){_year = year;_month = month;_day = day;}void Print(){cout << _year << " " << _month << " " << _day << endl;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;//使用缺省值Date d2(2004, 11, 11);//自己传参数d1.Print();d2.Print();}int main()
{TestDate();return 0;
}

5.若是用户没有显式的实现构造函数,那么C++的编译器会自动生成一个无参的默认构造函数,一但用户显式实现了构造函数那么编译器就不会再去实现。

我们来看下将我们自己实现的构造函数给去掉,编译器会怎么将数据初始化:

class Date
{
public:全缺省带参构造//Date(int year = 2004, int month = 11, int day = 30)//{//	_year = year;//	_month = month;//	_day = day;//}void Print(){cout << _year << " " << _month << " " << _day << endl;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1;//使用缺省值//Date d2(2004, 11, 11);//自己传参数d1.Print();/*d2.Print();*/}int main()
{TestDate();return 0;
}

可以看到这里我已经将我们刚才实现的构造函数给注释掉了,那么这段代码的运行结果会是怎样的呢?

 从这个运行结果我们就可以看出,调用默认构造函数时是以随机值进行初始化的,看起来似乎这个默认构造没有太大的用途。

6.关于编译器生成的默认成员函数,很多人都会有这样的疑问,在我们不显式的实现构造函数时,编译器生成的默认构造函数看起来有没有什么用?

对于这个问题其实C++把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,比如:int、char、double...等等,自定义类型就是我们使用的class、struct、union等自己定义的类型,我们来看下下面的程序,就会发现编译器生成的默认构造函数会对自定义类型成员调用它的默认成员函数,而对内置类型不做处理(这个看编译器的底层实现,有的编译器会进行处理,C++的标准里面没有规定)。

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private:int _hour;int _minute;int _second;
};class Date
{
public:void Print(){cout << _year << " " << _month << " " << _day << endl;}
private://基本类型int _year;int _month;int _day;//自定义类型Time _t;
};int main()
{Date d1;d1.Print();return 0;
}

我们来看下这段代码的运行结果:

 可以看到这里对自定义类型进行了处理,调用了time类的构造函数,但是对内置类型并没有处理。对于这个问题到了C++11的时候给出了一个补丁,即:内置类型成员变量在类中声明时可以给默认值

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private:int _hour;int _minute;int _second;
};class Date
{
public:void Print(){cout << _year << " " << _month << " " << _day << endl;}
private://基本类型int _year = 2004;int _month = 11;int _day = 13;//自定义类型Time _t;
};int main()
{Date d1;d1.Print();return 0;
}

我们在Date类中给我们的内置成员变量都赋予了一个初始值,我们来看下这次的效果:

 可以看到这样的话就能够将内置类型默认构造的问题给解决了。

7.无参的构造函数和全缺省的构造函数都被称为默认构造函数,并且默认构造函数只能有一个。

注意:无参构造函数,全缺省构造函数和我们没显式实现的情况下编译器自动生成的默认构造函数,都是默认构造函数。

一般建议每一个类都写一个全缺省的构造函数

总结:1.不用传参数就可以调用的函数就是默认构造函数

2.编译器自动生成的默认构造函数对于内置类型没有规定要处理

3.一般情况下构造函数都需要我们自己去显式的实现

4.只有少数的情况可以让编译器自动生成构造函数,类似myqueue的成员全是自定义类型。

三、析构函数

3.1 析构函数的概念

析构函数:与构造函数的功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作,析构函数的作用相当于Destroy的功能

3.2 析构函数的特性

析构函数时特殊的成员函数,它的特征如下:

1. 析构函数名是是在类名的前面加上字符~

2. 没有参数,没有返回值类型

3.一个类里面只能有一个析构函数,若没有显式定义,编译器会自动生成默认的析构函数

4.析构函数不能重载

5.对象的声明周期结束时,C++编译系统会自动调用析构函数

对于析构函数我们先来看下下面的一个stack类:

typedef int DataType;
class Stack
{
public://全缺省的构造函数Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (_array == nullptr){perror("malloc申请空间失败!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){_array[_size] = data;_size++;}~Stack(){cout << "~Stack" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}void Print(){cout << _capacity << endl;cout << _size << endl;}private:DataType* _array;int _capacity;int _size;};void TestStack()
{Stack s1; s1.Push(1);s1.Push(2);s1.Print();
}int main()
{TestStack();return 0;
}

可以看到这里程序运行结束之后自动调用了析构函数。

6.关于编译器自动生成的析构函数,能否完成一点事情呢,其实编译器自动生成的默认析构函数,对自定义类型成员会调用它的析构函数,对于内置类型成员销毁时不需要进行资源清理,最后系统会将其内存回收即可。

我们来看下下面的一个实例:

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}~Time(){cout << "~Time()" << endl;}private:int _hour;int _minute;int _second;
};class Date
{
public:void Print(){cout << _year << " " << _month << " " << _day << endl;}
private://基本类型int _year = 2004;int _month = 11;int _day = 13;//自定义类型Time _t;
};int main()
{Date d;return 0;
}

 在这里我们看到调用Date的默认构造时调用了Time类的析构函数,因为_t是自定义类型,在d销毁时要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

7.如果类中没有申请资源的话,析构函数可以不写,直接使用编译器默认生成的析构函数就行,比如Date类,在有资源申请的情况下,一定要写析构函数,否则可能会造成内存泄露,比如:stack类。

结语:

这篇博客到这里就结束了,这里我们介绍了类的默认成员函数中的构造函数和析构函数,希望大家通过这篇文章能够对构造函数和析构函数有一定的认识。


http://www.ppmy.cn/server/5499.html

相关文章

通往大厂之路:Solr面试题及参考答案100道题

目录 什么是Solr,它主要用来做什么? 解释Solr和Lucene的关系。 Solr有哪些主要特点?

vue--样式绑定--样式切换方法

1.通过改变类名的方法改变盒子样式 可以通过 :class变量名来动态改变标签的样式名&#xff0c;变量值可以是字符串、数组、对象 1.字符串写法 适用于样式类名不确定需要动态指定 <div classbase :classa>Text</div> data:{ a:normal } classbase和 :classa可…

【WPF】取色器-Color Extractor

【WPF】取色器 序实现HookScreen Colorlayout.CS预览下载序 取色器是一个非常实用的小工具,网上也很多可供下载使用。为什么已有却还是想要自己去实现一个呢?一方面是因为工具虽小但毕竟涉及到操作系统 API 的使用。另一方面想要在技术上精进一些。 实现 实现思路测试通过 Ho…

数据的质量控制软件----fastQC

一、前言 FastQC的基本介绍: FastQC是一款基于Java的软件&#xff0c;它可以快速地对测序数据进行质量评估&#xff0c;其官网为&#xff1a;Babraham Bioinformatics - FastQC A Quality Control tool for High Throughput Sequence Data 高通量测序数据的高级质控工具输入…

详细理解React的Fiber结构

一、为什么会出现Fiber 旧版 React 通过递归的方式进行渲染&#xff0c;使用的是 JS引擎自身的函数调用栈&#xff0c;它会一直执行到栈空为止。而Fiber实现了自己的组件调用栈&#xff0c;它以链表的形式遍历组件树&#xff0c;可以灵活的暂停、继续和丢弃执行的任务。实现方式…

Set系列集合

Set系列集合特点&#xff1a; Set系列集合特点&#xff1a;无序&#xff1a;添加数据的顺序和获取出的数据顺序不一致&#xff1b;不重复&#xff1b;无索引HashSet无序&#xff1b;不重复&#xff1b;无索引LinkedHashSet有序&#xff1b;不重复&#xff1b;无索引TreeSet排序…

【架构】Elasticsearch+Logstash+Kibana架构

目录 什么是ELK ELK架构的应用场景 什么是ELK ELK是由Elasticsearch、Logstash和Kibana三个开源项目组成的技术栈&#xff0c;广泛用于搜索、日志管理和日志分析。这三个组件协同工作&#xff0c;提供了一个强大的方法来收集、存储、搜索和可视化日志数据和其他时间序列数据…

ADSP-21479的开发详解五(AD1939 C Block-Based Talkthru 48 or 96 kHz)音频直通

硬件准备 ADSP-21479EVB开发板&#xff1a; 产品链接&#xff1a;https://item.taobao.com/item.htm?id555500952801&spma1z10.5-c.w4002-5192690539.11.151441a3Z16RLU AD-HP530ICE仿真器&#xff1a; 产品链接&#xff1a;https://item.taobao.com/item.htm?id38007…