C++:类的构造函数与析构函数

news2024/11/15 20:04:41

目录

一.前言

二.类的构造函数

1.构造函数基本概念与语法细则

2.编译器默认生成的无参构造函数和自定义构造函数 

3.构造函数的特性(可重载)

4.关于构造函数的注意事项

5.构造函数的应用示例:

三.类的拷贝构造函数

1.拷贝构造函数基本概念

2.编译器默认生成的拷贝构造函数和自定义拷贝构造函数 

3.编译器默认生成的拷贝构造函数和自定义拷贝构造函数各自的适用情形

4.拷贝构造函数被调用的典型场景

5.构造函数使用时的一个陷阱

四.类的析构函数

1.析构函数基本概念与语法细则

2.编译器默认生成的析构函数和自定义析构函数

3.析构函数的应用场景


知识架构:

 

一.前言

类有六个默认成员函数,这六个默认成员函数即使我们不去定义它编译器也会在编译阶段将这些函数加到类中

类的六个默认成员函数:

本篇主要讨论的是类的构造函数,拷贝构造函数和析构函数。

二.类的构造函数

1.构造函数基本概念与语法细则

(1)构造函数是一个特殊的成员函数函数的标识名与类名相同.

(2)构造函数可以自定义(函数名必须为类名),也可以由编译器默认生成(用户自定义后编译器不再生成)

(3)类的构造函数没有返回值(也无须标明返回值)

(4)构造函数由编译器自行调用(用户无法自行调用),当我们用定义好的类类型去创建一个类对象实例时,编译器就会自动调用该的构造函数.

(5)在类对象实例的整个生命周期内构造函数只被调用一次

(6)类的构造函数一般用于类成员变量的初始化

2.编译器默认生成的无参构造函数和自定义构造函数 

编译器默认生成的无参构造函数:

代码段:

class indate
{
private:
    int i;
    int j;
};


class Date
{
public:

	
private:
	int _day;
	int _month;
	int _year;
    indate _subclass;       类类型成员变量
};

int main()
{
   Date a;                 创建类对象实例时编译器自动调用构造函数(无参构造函数)
   return 0;
}

注意:

(1)我们可以认为Date类中有一个叫做Date的成员函数,indate类中有一个叫做indate的成员函数.

(2)由于我们并没有在源码的Date类中显式自定义Date函数,所以编译器在编译阶段会自行在Date类中加上编译器默认生成的无参构造函数(Date())(对于indate类也一样)。

(3)上面代码段主函数中Date a;语句执行后编译器默认生成的无参构造函数就会被调用。


对于编译器默认生成的无参构造函数有以下几点需要注意:

(1).编译器默认生成的无参构造函数不会对类对象实例内置类型成员变量(比如上述的_day,_month,_year成员)做任何处理

(2).编译器默认生成的无参构造函数会调用其类类型成员变量无参构造函数(这里强调该类对象成员必须有无参构造函数)(比如上面代码段中Date a的构造函数Date()会去调用其成员类indate _subclass的无参构造函数)


自定义构造函数:

(1)如果我们对类的构造函数的功能有特殊需求(一般是初始化成员变量),我们也可以自定义构造函数(构造函数函数名必须和类名相同)

(2)一旦用户自定义构造函数编译器将不再另外生成构造函数(拷贝构造函数另作讨论)

比如:

#include <iostream>
#include <time.h>
#include <string>
#include <map>

using std::cout;
using std::endl;

class Date
{
public:
	Date(int day, int month, int year)     Date就是类的自定义构造函数
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "initclass success" << endl;
	}
	
private:
	int _day;
	int _month;
	int _year;
};


int main()
{ 
	Date a(1, 2, 3);                      编译器自动调用构造函数(带参构造函数)
	return 0;
}

注意:

(1)如果类中只有带参数的构造函数则创建类对象实例时要如上图所示在对象的创建语句中加上实参列表

(2)如果被创建的类对象中有类对象成员,编译器会在该类的自定义构造函数中加上其类对象成员构造函数的调用指令

比如:

可以通过汇编指令观察到这一点:

3.构造函数的特性(可重载)

构造函数可以重载(非常重要)

函数重载参见:http://t.csdn.cn/KcgeK

比如:

using std::cout;
using std::endl;

class Date
{
public:
    Date()
    {
        ;
    }
	Date(int flag)                      自定义构造函数重载
	{
		cout << "reload one" << endl;
	}

	Date(int day, int month, int year)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "reload two" << endl;
	}
	
private:
	int _day;
	int _month;
	int _year;
};


int main()
{
	Date a(1,2,3);
	Date b(1);
	return 0;
}

当一个类对象实例被创建时,调用的是构造函数的哪一种重载取决于我们创建类对象时的传参方式。

4.关于构造函数的注意事项

1. 无参的构造函数全缺省的构造函数这类可以无参调用的构造函数在类的定义中只能存在一个重载

关于缺省参数:http://t.csdn.cn/XubVT

比如:

2.自定义构造函数时,要定义一个可以无参调用的重载函数,不然可能会出现如下情形: 

还可能出现如下情形:

也就是说类对象作为其他类的成员被创建时必须调用其无参构造函数 

5.构造函数的应用示例:

在使用两个栈来实现一个队列的实际场景中,类的构造函数就能起到明显的作用

栈对象的类

class stack
{
    stack()                        自定义一个stack的构造函数来初始化维护栈的指针
    {
        _data=nullptr;
        _capacity=0;
        _nums=0;
    }
public:
    void stackinit(int capacity=4); 栈对象方法(函数体省略)   
    void stackpush();
    void stackcheck();
    int& stacktop();
    void stackdestroy();
private:
    int* _data;
    int _capacity;
    int _nums;

};

队列对象的类

class Queen
{
    stack _pushST;                   栈对象成员变量
    stack _popST;

};

int main ()
{
    Queen myqueen;
    return 0;
}



该例中,构造函数不仅可以使源代码变得简介,还很好地避免了忘记初始化栈对象指针的情况。

三.类的拷贝构造函数

1.拷贝构造函数基本概念

类的拷贝构造函数本质上是类的构造函数的一个特殊重载函数,其形参表中只有单个形参(形参表是区分重载的标志),该形参为本类类型对象的引用(一般常用const修饰)。

关于引用:http://t.csdn.cn/oVhTv
(1)类的拷贝构造函数和类的构造函数的关系:

(2)当我们没有显式自定义类的拷贝构造函数时(即便定义了其他重载形式的构造函数),编译器在编译阶段会自动生成编译器默认的拷贝构造函数。

(3)拷贝构造函数可以自定义(函数名必须为类名,形参表中只有一个形参且必须是该类类型对象的引用),一旦用户自定义了拷贝构造函数,编译器便不会再自动生成

(4)类的拷贝构造函数用已存在的类对象初始化新的类对象时使用。其功能是将一个对象的内容拷贝到另一个新创建对象中去

当一个类对象实例被创建时,调用的是构造函数的哪一种重载取决于我们创建类对象时的传参方式。

比如:

可以认为编译器为Date类自动添加了一个声明为Date(const Date& object)的函数

2.编译器默认生成的拷贝构造函数和自定义拷贝构造函数 

编译器默认生成的拷贝构造函数:

编译器默认生成的拷贝构造函数的功能是:对于内置类型成员变量,则逐个字节地将引用对象(实参)的成员变量拷贝到新创建的同类对象中,对于类类型成员变量,是调用该成员自身的拷贝构造函数来完成拷贝这种拷贝方式称为浅拷贝。

比如:

如果类对象中有类类型成员变量,比如:

自定义拷贝构造函数:

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date& d)                  自定义拷贝构造函数
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    Date d2(d1);
    return 0;
}

3.编译器默认生成的拷贝构造函数和自定义拷贝构造函数各自的适用情形

在一些情况下,使用编译器默认生成的拷贝构造函数是比较方便的。

但是在另外一些情况下是不能使用编译器默认生成的拷贝构造函数的,比如:用一个栈对象去初始化另外一个栈对象。

编译器默认生成的拷贝构造函数是按逐字节拷贝成员变量的浅拷贝方式进行对象拷贝的,而栈对象会在内存堆区上申请空间,上面情形中b对象和a对象都会去维护同一块堆区内存空间

两个栈对象维护同一块堆区内存空间是十分危险的,很容易出bug 

因此类对象调用了其他内存资源编译器默认生成的拷贝构造函数是不适用的,这时只能考虑使用自定义的拷贝构造函数。(用户自行设计深拷贝来完成对象的拷贝构造)

4.拷贝构造函数被调用的典型场景

(1)使用已存在对象创建新对象时
(2)调用的函数参数类型为类类型对象时
(3)函数返回值类型为类类型对象时

class Date
{
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << this << endl;
    }
    Date(const Date& d)
    {
        cout << "Date(const Date& d):" << this << endl;
    }
    ~Date()
    {
        cout << "~Date():" << this << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};



Date Test(Date d)
{
    Date temp(d);
    return temp;
}
int main()
{
    Date d1(2022,1,13);
    Test(d1);
    return 0;
}

 

5.构造函数使用时的一个陷阱

自定义构造函数时不能将类自身类型变量作为构造函数的形参

比如:

上面代码段中,创建d1对象后,用d1去初始化d2,但是Date的构造函数形参也是一个Date对象,调用函数时要在栈区上为函数形参开辟内存空间相当于又创建了一个Date对象,这时又会调用形参对象的拷贝构造函数,如此往复,形成死递归。

四.类的析构函数

1.析构函数基本概念与语法细则

(1)析构函数是一个特殊的成员函数函数的标识名是:~ + 类名.

(2)析构函数可以自定义(函数名必须为~ + 类名),也可以由编译器默认生成(用户自定义后编译器不再生成)

(3)类的析构函数没有返回值(也无须标明返回值)

(4)析构函数在类对象生命周期结束时由编译器自行调用(用户无法自行调用)

(5)在类对象实例的整个生命周期内析构函数只被调用一次

(6)类的析构函数一般用于类成员变量内存的清理。

析构函数不能重载 !!!!!!!!

2.编译器默认生成的析构函数和自定义析构函数

编译器默认生成的析构函数:

对于类对象内置类型成员变量不会做任何处理,对于类对象类类型成员变量则去调用其成员自身的析构函数

比如:

 

 

自定义析构函数以及析构函数的引用场景:

可以通过汇编观察自定义析构函数的调用过程:

 3.析构函数的应用场景

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);  为栈数据申请堆区空间
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	~Stack()                            自定义析构函数
	{
		if (_array)             
		{
			free(_array);               释放栈对象申请的堆区资源
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{
	Stack a;
	return 0;                           a的生命周期结束,调用析构函数
}

对象申请了其他内存资源时,对象销毁前必须先释放其申请的内存资源(否则会造成内存泄漏),这时候析构函数的作用就可以体现出来了,我们可以自定义析构函数去释放对象所申请的内存资源,对象销毁时编译器会自动调用析构函数,使用起来非常方便。(注意由于编译器默认生成的析构函数无法进行堆区内存资源的释放,因此本例中不能使用编译器默认生成的析构函数)

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零入门容器云实战之文章目录列表

建议: 1、网盘资源 零入门容器云网络实战 链接: https://pan.baidu.com/s/1nPLRkAwjItAHmtEU2T1F4g 提取码: rrpd 2、技术交流群 QQ群&#xff1a; 342498897 3、发布说明 绿色字体&#xff0c; 表示已经发布&#xff0c;可以观看 灰色字体&#xff0c; 表示未发布 发布频…

汽车研究(科普)

什么是汽车的排量&#xff0c;1.5L与2.0T又是指什么? 汽车的动力来源于燃油在气缸内爆燃产生的力&#xff0c;力推动活塞连着曲轴传到离合变速箱&#xff0c;通过后桥作用让车轮转&#xff0c;排量1.5、2.0指的就是气缸的容量&#xff0c;如果是带增压的用字母T表示&#xff0…

JQuery总结(二)

属性操作&#xff1a; 文本操作&#xff1a; <div><span>内容</span></div><input type"text" value"请输入内容"> </body> <script src"jQuery.min.js"></script> <script > console.lo…

目录 行盒的盒模型 显著特点 行块盒 空白折叠 可替换元素 和 非可替换元素 分页例子 display:inline-block object-fit

目录行盒的盒模型行盒显著特点行块盒空白折叠可替换元素 和 非可替换元素行盒的盒模型 常见的行盒&#xff1a;包含具体内容的元素 span、strong、em、i、img、video、audio 这些行盒模型也都有 content、padding、border、margin的 但它们与块盒还是有明显区别 行盒显著特…

【Leetcode面试常见题目题解】7. 删除链表的倒数第 N 个结点

前言 本文是LC第19题&#xff1a;删除链表的倒数第 N 个结点 题目描述 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 限制&#xff1a; 链表中结点的数目为 sz 1 < sz < 30 0 < Node.val < 100 1 < n < sz 进…

使用账号激活MATLAB软件

前言 很多学校购买了MATLAB软件的使用权&#xff0c;在校师生只需要使用自己的学校域名的邮箱&#xff0c;注册一个MATLAB账号即可免费使用MATLAB产品&#xff0c;再也不用各种去网上找破解资源了。 账号注册 访问账户注册页面&#xff1a; 创建 MathWorks 帐户然后填写账户信…

三、pyhon基础语法进阶篇(黑马程序猿-python学习记录)

黑马程序猿的python学习视频&#xff1a;https://www.bilibili.com/video/BV1qW4y1a7fU/ 目录 一、文件操作 一、 文件的读取 1. 打开文件open() 2. 读取文件10个字节read(10) 3. 读取文件全部信息read() 4. 读取文件readLines() 5. 读取文件readLine() 6. for循环读取…

【HTML】我用“一行“代码为CSDN博客主页挂上灯笼(附源码)

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

第一章 ArcMap、ArcCatalog、 ArcToolbox基础入门操作

文章目录第一节 ArcMap入门1 界面的基本介绍2 加载数据3 添加图层4 数据表5 内容列表5.1 按绘制顺序5.2 按源5.3 按可见性5.4 按可选性第二节 ArcCatalog入门1 界面和功能介绍2 文件夹连接3 新建数据4 修改字段第三节 ArcToolbox操作入门1 界面基本介绍2 查找工具3 查看帮助4 工…

汇编【王爽】实验8、9

实验8 分析一个奇怪的程序 程序从startstartstart入口处开始执行&#xff0c;一个nop指令占一个字节并表示No operation&#xff0c;此处用了两个nop指令的目的是在sss处预留两个字节的空间&#xff0c;程序执行mov cs:[di], ax之后sss处的两个字节被试图写入jmp short s1&…

【数据结构】二叉树的基本知识

目录前言一、树1、树的相关概念&#xff08;1&#xff09;结点&#xff08;2&#xff09;结点的度&#xff08;3&#xff09;叶结点&#xff08;4&#xff09;分支结点&#xff08;5&#xff09;父亲结点&#xff08;6&#xff09;子节点&#xff08;7&#xff09;树的度&#…

Spring笔记上(基于XML配置)

新年快乐。 文章目录一、Spring概述1. 为什么要用Spring框架&#xff1f;2. Spring介绍二、IOC/DI快速入门1. IOC控制反转2. DI依赖注入三、Bean的配置1. Bean的基础配置2. Bean的别名配置3. Bean的作用范围配置四、Bean的实例化1. 构造方法方式2. 静态工厂方式3. 实例工厂方式…

Java面试题,JVM相关问题

JVM相关问题一 、JDK、JRE、JVM二、内存管理三、GC如何判断对象可以被回收&#xff08;这是JVM的基础&#xff09;一 、JDK、JRE、JVM JDK&#xff1a;Java Development Kit【Java开发工具】&#xff0c;提供给Java开发人员来使用的。JRE&#xff1a;Java Runtime Environment…

Solid Edge 放样使用引导曲线

放样用引导曲线的时候被一个错误提示卡了挺长时间——“选来用作路径或横截面的所有边必须连接在一起”&#xff0c;所以记录一下遇到的问题。基础的操作可以去看帮助文件https://docs.sw.siemens.com/zh-CN/doc/246738425/PL20211001099989437.feature_modeling/feat12c&#…

Sprig框架集成(SSM框架) | Sping+SpringMVC+Mybatis

SSM框架 SSM是spingspringMVCmybatis集成的框架&#xff1a;标准的MVC模式&#xff0c;整个系统划分为表现层&#xff0c;controller层&#xff0c;service层&#xff0c;DAO层四层 Spring&#xff08;业务层&#xff09; Spring就像是整个项目中装配bean的大工厂&#xff0c;在…

MySQL server options

介绍 MySQL安装部署时&#xff0c;经常会关注一些参数是否合理。其实这些参数分为两类型。环境中调整的绝大部分是引擎层方面的。服务层参数&#xff0c;就是mysqld服务启动时的参数&#xff0c;如&#xff1a;datadir&#xff0c;port&#xff0c;socket之类的的&#xff0c;…

多重背包问题——单调队列优化

一、多重背包问题 我们在之前的文章中曾经讲解过多重背包问题&#xff0c;当时我们讲解了两种方法&#xff0c;一种方法就是三重循环&#xff0c;这种方法最为朴素好想。但是这种方法的时间复杂度非常高&#xff0c;后来我们想到了二进制优化的方式。那么今天我们将再介绍一种…

Java实习------Java基础2

基础语法基本数据类型 熟悉Java有哪些数据类型定义&#xff1a;Java语言是强类型语言&#xff0c;对于每一种数据都定义了明确的具体的数据类型&#xff0c;在内存中分配了不同大小的内存空间。Java语言提供了八种基本类型。六种数字类型&#xff08;四个整数型&#xff0c;两个…

18. 循环语句while,for语句的详解

python 中的循环语句只有 for 和 while两种&#xff0c;没有do…while循环&#xff0c;这与c/c是不同的。 1. while循环 (1) 语法格式 while <condition>&#xff1a;...# demo, 使用while循环累加1-100的和。 sum 0 counter 1 while counter < 100:sum counterco…

Java 23种设计模式(4.创建者模式-建造者模式)

代码分析 结构图 代码 public class Product {//产品类,多个部件构成List <String> parts new ArrayList<>();public void Add(String part){parts.add(part);}public void show(){System.out.println("creat parts");for(String part:parts){System…