【C++】对象特性:无参有参构造函数,拷贝构造函数,析构函数

目录

      • 对象的初始化和清理
        • 1.1 构造函数和析构函数
        • 1.2 构造函数的分类及调用
        • 1.3 拷贝构造函数调用时机
        • 1.4 构造函数调用规则
        • 1.5 深拷贝与浅拷贝

对象的初始化和清理

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全。

  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。

1.1 构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题。

一个对象或者变量没有初始状态,对其使用后果是未知。

同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。

c++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}

  1. 构造函数,没有返回值也不写void

  2. 函数名称与类名相同

  3. 构造函数可以有参数,因此可以发生重载

  4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法: ~类名(){}

  1. 析构函数,没有返回值也不写void

  2. 函数名称与类名相同,在名称前加上符号 ~

  3. 析构函数不可以有参数,因此不可以发生重载

  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

示例代码

#include<iostream>
using namespace std;
//对象的初始化和清理
//1、构造函数 进行初始化操作
//2、析构函数 进行清理的操作
class Person
{
public:
	//1.1、构造函数
	//没有返回值 不用void
	//函数名 与类名称相同
	//构造函数可以有参数,可以发生重载
	//创建对象的时候,构造函数会自动调用,且只调用一次
	Person()
	{
		cout << "Person 构造函数的调用" << endl;
	}

	//1.2、析构函数
	//没有返回值 不写void
	//函数名和类名相同 在名称前加~
	//析构函数不可以有参数,不可以发生重载
	//对象销毁前 会自动调用析构函数 而且只会调用一次
	~Person()
	{
		cout << "Person 析构函数的调用" << endl;
	}
};

void test01()
{
	Person P;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

运行结果

Person 构造函数的调用
Person 析构函数的调用
1.2 构造函数的分类及调用

两种分类方式:

1)按参数分为: 有参构造和无参构造

2)按类型分为: 普通构造和拷贝构造

三种调用方式:

1)括号法

2)显示法

3)隐式转换法

构造函数的分类

#include<iostream>
using namespace std;

//1构造函数的分类及调用
//分类
//按照参数分类 无参构造(默认构造) 和 有参构造
//按照类型分类 普通构造 和 拷贝构造
class Person
{
public:
	int age;
	//构造函数
	Person()
	{
		cout << "Person 无参构造函数的调用" << endl;
	}
    //有参构造函数
	Person(int a)
	{
		age = a;
		cout << "Person 有参构造函数的调用" << endl;
	}
	//拷贝构造函数
	Person(const Person &p)
	{
		//将传入的人身上的所有属性,拷贝在我的身上 
		age = p.age;
		cout << "Person 拷贝构造函数的调用" << endl;
	}
	//析构函数
	~Person()
	{
		cout << "Person 析构函数的调用" << endl;
	}
};

构造函数的调用

1)构造函数的调用:括号法

void test01()
{
	//1、括号法
	Person p1; //默认构造函数的调用
	Person p2(10); //有参构造函数的调用
	Person p3(p2); //拷贝构造函数的调用

	//注意事项1
	//调用默认构造函数时候,不用加()
	//因为下面这行代码,编译器会认为是一个函数的声明
	//Person p1();

	cout << "p2的年龄为:" << p2.age << endl;
	cout << "p3的年龄为:" << p3.age << endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

运行结果

Person 无参构造函数的调用
Person 有参构造函数的调用
Person 拷贝构造函数的调用
p2的年龄为:10
p3的年龄为:10
Person 析构函数的调用
Person 析构函数的调用
Person 析构函数的调用

2)构造函数的调用:显示法

void test01()
{
	//2、显示法
	Person p1;
	Person p2 = Person(10); //有参构造
	Person p3 = Person(p2); //拷贝构造

	//Person(10); //匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象

	//注意事项2
	//不要利用拷贝构造函数 初始化匿名对象 编译器会认为 Person(p3); 等价于 Person p3; 重定义,报错。
	//Person(p3);

	cout << "p2的年龄为:" << p2.age << endl;
	cout << "p3的年龄为:" << p3.age << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

运行结果

Person 无参构造函数的调用
Person 有参构造函数的调用
Person 拷贝构造函数的调用
p2的年龄为:10
p3的年龄为:10
Person 析构函数的调用
Person 析构函数的调用
Person 析构函数的调用

3)构造函数的调用:

void test01()
{
	//3、隐式转换法
    Person p4 = 10; //相当于 写了 Person(10); 
    Person p5 = p4; //拷贝构造

    cout << "p4的年龄:" << p4.age << endl;
    cout << "p5的年龄:" << p5.age << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

运行结果

Person 有参构造函数的调用
Person 拷贝构造函数的调用
p4的年龄:10
p5的年龄:10
Person 析构函数的调用
Person 析构函数的调用
1.3 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况。

  • 使用一个已经创建完毕的对象来初始化一个新对象。

  • 值传递的方式给函数参数传值。

  • 以值方式返回局部对象。

示例代码

#include<iostream>
using namespace std;

//拷贝构造函数调用时机

//1、使用一个已经创建完毕的对象来初始化一个新对象

//2、值传递的方式给函数参数传值

//3、值方式返回局部对象
class Person
{
public:
	int m_Age;
	Person()
	{
		cout << "Person默认构造函数的调用" << endl;
	}
	Person(int age)
	{
		m_Age = age;
		cout << "Person有参构造函数的调用" << endl;
	}
	Person(const Person& p)
	{
		m_Age = p.m_Age;
		cout << "Person拷贝构造函数的调用" << endl;
	}
	~Person()
	{
		cout << "Person默认析构函数的调用" << endl;
	}
};

1、使用一个已经创建完毕的对象来初始化一个新对象。

//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
	Person  p1(20);
	Person  p2(p1);
	cout << "p2的年龄为:" << p2.m_Age << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

运行结果

Person有参构造函数的调用
Person拷贝构造函数的调用
p2的年龄为:20
Person默认析构函数的调用
Person默认析构函数的调用

2、值传递的方式给函数参数传值

//2、值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1)
{

}
void test02()
{
	Person p;
	doWork(p);
}

运行结果

Person默认构造函数的调用
Person拷贝构造函数的调用
Person默认析构函数的调用
Person默认析构函数的调用

3、值方式返回局部对象

//3、值方式返回局部对象
Person doWork03()
{
	Person p1;
	cout << "p1的地址为:" << (int*)&p1 << endl;
	return p1;
}
void test03()
{
	Person p = doWork03();
	cout << "p的地址为:" << (int*)&p << endl;
}

运行结果

Person默认构造函数的调用
010FF894
Person拷贝构造函数的调用
Person默认析构函数的调用
010FF98C
Person默认析构函数的调用

运行结果(VS2022)编译器会优化

Person默认构造函数的调用
p1的地址为:000000B083EFF5D4
p的地址为:000000B083EFF5D4
Person默认析构函数的调用
1.4 构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数。

1.默认构造函数(无参,函数体为空)。

2.默认析构函数(无参,函数体为空)。

3.默认拷贝构造函数,对属性进行值拷贝。

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造。

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数。

示例代码

#include<iostream>
using namespace std;
//构造函数的调用规则
//1、创建一个类,C++编译器会给每个类都添加至少三个函数
//默认构造(空实现)
//析构函数(空实现)
//拷贝构造(值拷贝)

//2、
// 如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
// 如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数了

class Person
{
public:
	int m_Age;
	Person()
	{
		cout << "Person默认构造函数的调用" << endl;
	}
	Person(int age)
	{
		m_Age = age;
		cout << "Person有参构造函数的调用" << endl;
	}
	/*Person(const Person& p)
	{
		m_Age = p.m_Age;
		cout << "Person拷贝构造函数的调用" << endl;
	}*/
	~Person()
	{
		cout << "Person默认析构函数的调用" << endl;
	}
};

void test01()
{
	Person p;
	p.m_Age = 18;

	Person p2(p);
	cout << "p2的年龄为:" << p2.m_Age << endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

运行结果

Person默认构造函数的调用
p2的年龄为:18
Person默认析构函数的调用
Person默认析构函数的调用

可以看到我们定义了有参构造函数,而我们没有定义拷贝构造函数,但是编译器为我们提供了拷贝构造函数,将p.m_Age = 18值拷贝给了p2。

1.5 深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑。

浅拷贝:简单的赋值拷贝操作。

深拷贝:在堆区重新申请空间,进行拷贝操作。

示例代码

#include<iostream>
using namespace std;
//深拷贝与浅拷贝

class Person
{
public:
	int m_Age;
	int *m_Height; //身高
public:
	Person() 
	{
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age, int height)
	{
		cout << "Person的有参构造函数调用" << endl;
		m_Age = age;
		m_Height = new int(height);
	}
	//自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person& p)
	{
		cout << "Person 拷贝构造函数调用" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height; 编译器默认实现就是这行代码
		//深拷贝操作
		m_Height = new int(*p.m_Height);
	}
	~Person()
	{
		//析构代码,将堆区开辟数据做释放操作
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}
		cout << "Person的默认析构函数调用" << endl; 
	}
};

void test01()
{
	Person p1(18,160);
	Person p2(p1);

	cout << "p1的年龄为:"  << p1.m_Age << " 身高为:" << *p1.m_Height << endl;
	
	cout << "p2的年龄为:" << p2.m_Age << " 身高为:" << *p2.m_Height << endl;
}

int main()
{
	test01();

	system("pause");
	return 0;
}

运行结果

Person的有参构造函数调用
Person 拷贝构造函数调用
p1的年龄为:18 身高为:160
p2的年龄为:18 身高为:160
Person的默认析构函数调用
Person的默认析构函数调用

假如,我们没有自行设计拷贝构造函数,那么编译器会默认为浅拷贝,也就是说首先p2析构,内存释放,然后p1析构,内存释放,然而此时对应内存已经释放过了,无法释放第二次,所以会报错。

浅拷贝的意思就是 p1.m_Height在堆区开辟内存,对应的地址为0x0011,而拷贝给p2.m_Height,它对应的地址也是0x0011。

而深拷贝也就是说, p1.m_Height在堆区开辟内存,对应的地址为0x0011,而拷贝给p2.m_Height,p2.m_Height开辟了一个新地址,0x0022,但是0x0011和0x0022的内容都是160。

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智慧食堂餐卡充值文件生成器使用说明

智慧食堂餐卡充值文件生成器 下载地址&#xff1a; https://download.csdn.net/download/boysoft2002/88646277 或者百度网盘下载&#xff1a; https://pan.baidu.com/s/16cxOa5aq0CU0T0xOr2A7-A 操作使用说明 一、文件结构 下载.rar文件后&#xff0c;释放到非系统盘符的…

数据库故障Waiting for table metadata lock

场景&#xff1a;早上来发现一个程序&#xff0c;链接mysql数据库有点问题&#xff0c;随后排查&#xff0c;因为容器在k8s里面。所以尝试重启了pod没有效果 一、重启pod: 这里是几种在Kubernetes中重启Pod的方法: 删除Pod,利用Deployment重建 kubectl delete pod mypodDepl…

【小呆的力学笔记】弹塑性力学的初步认知二:应力分析(1)

文章目录 1.1 一点的应力状态1.2 一点主应力状态1.3 应力偏张量、球张量、应力不变量 1.1 一点的应力状态 物体在受到外力或者自身不均匀的温度场等作用时&#xff0c;在其内部会产生内力&#xff0c;物体的内力与方向和截面都有关系。假设有一个受到外力作用的变形体&#xf…

Java经典面试题:冒泡算法的使用

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java经典面试题&#xff1a;冒泡算法的使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题…

nodejs连接mongodb报错SyntaxError: Unexpected token .

nodejs连接mongodb报错SyntaxError: Unexpected token 如下图 经过排查&#xff0c;原因是npm默认安装的mongodb插件是最新版6.3.0 &#xff0c;而mongodb数据库版本是4.0.0 &#xff0c;两者版本不同导致nodejs报错。 解决方法是npm卸载新版本的mongodb插件&#xff0c;再安…

comfyUI + animateDiff video2video AI视频生成工作流介绍及实例

原文&#xff1a;comfyUI animateDiff video2video AI视频生成工作流介绍及实例 - 知乎 目录 收起 前言 准备工作环境 comfyUI相关及介绍 comfyUI安装 生成第一个视频 进一步生成更多视频 注意事项 保存为不同的格式 视频宽高设置 种子值设置 提示词与负向提示词…

uml用例图是什么?有哪些要素?

UML用例图是什么&#xff1f; UML用例图&#xff08;Unified Modeling Language Use Case Diagram&#xff09;是一种用于描述系统功能和用户之间交互的图形化建模工具。它是UML的一部分&#xff0c;主要用于识别和表示系统中的各个用例&#xff08;用户需求或功能点&#…

EMNLP 2023 亮点回顾:大模型时代下的 NLP 研究

作为自然语言处理&#xff08;NLP&#xff09;领域的顶级盛会&#xff0c;EMNLP 每年都成为全球研究者的关注焦点。2023 年的会议在新加坡举行&#xff0c;聚集了数千名来自世界各地的专家学者&#xff0c;也是自疫情解禁以来&#xff0c;中国学者参会最多的一次。巧的是&#…

阿赵UE学习笔记——2、新建项目和项目设置

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。继续来学习虚幻引擎的使用。这次介绍一下新建项目和项目设置。 一、新建项目 通过桌面快捷方式&#xff0c;或者EPIC Games Loader&#xff0c;启动虚幻引擎。 启动完成后&#xff0c;会打开项目管理的界面&#xff0c;可以看…

运算放大电路的输入偏置电流和输入失调电流

一般运放的datasheet中会列出众多的运放参数&#xff0c;有些易于理解&#xff0c;我们常关注&#xff0c;有些可能会被忽略了。在接下来的一些主题里&#xff0c;将对每一个参数进行详细的说明和分析。力求在原理和对应用的影响上把运放参数阐述清楚。由于本人的水平有限&…

软件测试中如何测试算法?

广义的算法是指解决问题的方案&#xff0c;小到求解数学题&#xff0c;大到制定商业策略&#xff0c;都可以叫做算法。而我们今天讨论的软件测试中的算法&#xff0c;对应的英文单词为 Algorithm&#xff0c;专指计算机处理复杂问题的程序或指令。 随着最近几年人工智能等领域…

【C语言初阶】数组

目录 一、一维数组的创建和初始化 1.1 数组的创建 1.2 数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 二、二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 三、数组越界 四、数组作…

每日一博 - Protobuf vs. Protostuff:性能、易用性和适用场景分析

文章目录 历史区别联系性能差异最佳实践场景分析小结 历史 对于Protostuff和Protobuf的关系&#xff0c;需要了解它们的起源和发展。 Protobuf&#xff08;Protocol Buffers&#xff09;是由Google开发的一种数据序列化格式&#xff0c;用于结构化数据的存储和交换。它最初是…

【✅Java中有了基本类型为什么还需要包装类?】

✅Java中有了基本类型为什么还需要包装类&#xff1f; ✅Java中有了基本类型为什么还需要包装类✅Java的8中基本数据类型 ✅知识拓展✅基本类型和包装类型的区别✅如何理解自动拆装箱&#xff1f;✅拆箱与装箱✅自动拆装箱✅自动拆装箱原理 ✅哪些地方会自动拆装箱&#xff1f;…

C++20形式的utf-8字符串转宽字符串,不依赖编译器编码形式

默认的char[]编码都是要看编译器编译选项的&#xff0c;你选了ANSI那它就是ANSI&#xff0c;你选了UTF8那它就是UTF8. 【注意&#xff1a;经典DevC只支持ANSI编码&#xff08;痛苦&#xff09;&#xff1b;上图是小熊猫DevC&#xff0c;则有这个选项】 这一点对我的代码造成了…

【教3妹学编程-算法题】循环移位后的矩阵相似检查

插&#xff1a; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家一起学习鸭~~~ 3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&…

Ubuntu 常用命令之 apt-get 命令用法介绍

apt-get是Ubuntu系统下的一个命令行工具&#xff0c;用于处理包。这个命令可以自动下载和安装软件包及其依赖项。它是Advanced Packaging Tool (APT)的一部分&#xff0c;APT是处理包的高级工具&#xff0c;可以处理复杂的包关系&#xff0c;如依赖关系等。 apt-get命令的常见…

linux: ip route 与 route 用法详解与对比

文章目录 1. 引言2. ip route2.1 描述2.2 语法2.3 参数2.4 例子 3. route3.1 描述3.2 语法3.3 参数3.4 例子 4. 对比5. 参考 1. 引言 本文主要介绍 ip route 以及 route 的用法和区别。 2. ip route 2.1 描述 用于管理静态路由表。linux 系统中&#xff0c;可以自定义从 1&…

聚类算法及可视化方法的实践与探索

簇内平方和表示数据点到其簇内质心的距离的平方和&#xff0c;公式如下&#xff1a; 其中&#xff0c; 是k簇数&#xff0c; ni是第 i 个簇的样本数&#xff0c; xij是第 i个簇中的第 j 个样本。 import matplotlib.pyplot as plt from sklearn.cluster import KMeans from sk…

Python Opencv实践 - 手势音量控制

本文基于前面的手部跟踪功能做一个手势音量控制功能&#xff0c;代码用到了前面手部跟踪封装的HandDetector.这篇文章在这里&#xff1a; Python Opencv实践 - 手部跟踪-CSDN博客文章浏览阅读626次&#xff0c;点赞11次&#xff0c;收藏7次。使用mediapipe库做手部的实时跟踪&…