C++:类和对象:对象的初始化和清理

news2025/1/11 22:53:20

1 前言:

构造和析构的背景

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

2:对象的初始化和清理是两个非常重要的安全问题,一个对象或者变量没有初始状态,对其使用后果是未知的,同样一个对象或者变量没及时清理,也会造成一定的安全问题。

3:C++利用了构造函数和析构函数解决上诉问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作,对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们没有提供构造和析构函数,编译器也会自动提供,只不过编译器自动提供的都是空事项。

构造函数 

主要作用在于创建对象时为对象的成员属性赋值。

1 语法:类名() {}

2: 构造函数,没有返回值不能写 void

3: 函数名称与类名相同

4:构造函数可以有参数,因此可以发生重载

5:程序在调用对象时,会自动调用构造函数,无需手动调用,而且只会调用一次

6:权限应该为 public

析构函数 

主要用于对象销毁前,系统自动调用,执行一些清理工作

1: 语法 :  ~类名(){}

2: 析构函数,没有返回值,不需要写 void

3: 函数名称和类名相同,在名称前加上符号  ~

4:析构函数不可以有采纳数,因此不可以发送重载

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

6: 权限是 public

示例代码

#include<iostream>
#include<string>
using namespace std;


class Person {
public:
	Person(){
		cout << "Person的构造函数调用了" << endl;
	}

	~Person() {
		cout << "Person的析构函数调用了" << endl;
	}
};

int main() {
	Person p;
	return 0;
}

// 打印
Person的构造函数调用了
Person的析构函数调用了

 这里我们可以看到,我们只是创建一个对象就自动执行了 构造函数和析构函数,若我们的对象是分配在 栈上,释放这个对象的时候就会自动调用析构函数。

2:构造函数的分类及其调用 

2.1 构造函数的分类

1:按照参数分类可分为: 有参构造和 无参构造,无参构造又称为 默认构造函数。

2:按照类型分类可分为:普通构造和拷贝构造。

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

 从上面代码可以也可以看到

1:只要构造函数没有参数,那么就是无参构造函数,而无参构造函数就是默认构造函数

2:只要构造函数带有参数,那么就是有参构造函数

3:拷贝构造函数也是一种有参构造函数,只是说参数是同种类比的一个对象

4: 拷贝构造函数的参数是一个引用,同时为了防止改变参数的内容。需要在前面加上 const,使之称为常量引用。

5: 只要不是拷贝构造函数,其余情况都是普通构造函数。

2.2 构造函数的调用 

构造函数的调用有三种方法 

1: 括号法

2:显示法

3:隐式转换法

括号法

#include <iostream>
using namespace std;

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

void Test() {
	Person p1(10);
	Person p2(p1);

    Person p3();
}

int main() {
	Test();

	return 0;
}

// 打印结果
有参构造函数
拷贝构造函数
析构函数!
析构函数!

注意:Person p3() ;----并没有打印无参构造函数

1:因为调用无参构造函数不能加括号

2:如果加括号,编译器慧认为这是一个函数声明,在C++的函数里面允许在声明其他函数,所以这里应该是 声明了一个函数  p3() , 返回值类型是  Person,  不接受形参。 因此就不会调用构造函数和析构函数了。

 显示法

#include <iostream>
using namespace std;

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

void Test() {
	// 显示法
	Person p1 = Person(10);
	Person p2 = Person(p1);
}

int main() {
	Test();

	return 0;
}


// 打印结果

有参构造函数!
拷贝构造函数!
析构函数!
析构函数!

在来看一个例子

void Test() {
	// 显示法
	Person(10);
	cout << "aaaaaa" << endl;
}

// 打印结果

有参构造函数!
析构函数!
aaaaaa

Person(10)   ------->这样写 编译器会认为这是创建一个 匿名对象,当前行结束之后,析构函数会立马执行,回收这个匿名对象。

隐式转换法 

void Test() {
	// 隐式转换法
	Person p = 10; // Person p = Person(10);
	Person p1 = p; // Person p1 = Person(p);
}

// 打印结果

有参构造函数!
拷贝构造函数!
析构函数!
析构函数!

3:拷贝构造函数调用时机 

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

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

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

3:以值方式返回局部对象

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

#include <iostream>
using namespace std;

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

void Test() {
	Person man(100); //p对象已经创建完毕
	Person newman(man); //调用拷贝构造函数
	Person newman2 = man; //拷贝构造

	Person newman3;
	newman3 = man; //不是调用拷贝构造函数,赋值操作
}

int main() {
	Test();

	return 0;
}

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

#include <iostream>
using namespace std;

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

// 2: 通过值得方式给函数参数传值
void dowork(Person p1) {
	
}

void Test() {
	Person p;  // 调用无参构造函数
	dowork(p);
}

int main() {
	Test();

	return 0;
}

 通过代码运行我们发现:在作为值传递时会定义一个新的对象,然后调用构造函数。

2: 以值方式返回局部对象

 

4:构造函数调用规则 

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

1:默认的构造函数(无参,函数体为空)

2:默然的析构函数(无参,函数体为空)

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

4:如果用户定义了有参构造函数,C++不再提供默认的无参构造函数,但是会提供默认的拷贝函数。

5:如果用户定义了拷贝构造函数,C++不会再提供其他构造函数(默认构造函数和有参构造函数)

5:深拷贝与浅拷贝 

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

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

案例:假设我们的 person类不仅有年龄,还有身高们可以通过有参构造函数传递参数创建相应对象。同时,身高这个数据我们在堆区创建一块内存保存,在析构函数中释放掉这块内存。

#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	// 无参默认构造函数
	Person() {
		cout << "调用无参构造函数!" << endl;
	}

	// 有参构造函数
	Person(int age, int height) {
		cout << "调用有参构造函数!" << endl;
		this->m_age = age;
		this->m_height = new int(height);

	}

	// 析构函数
	~Person()
	{
		cout << "调用析构函数!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}

public:
	int m_age;
	int* m_height;

};

void test() {
	Person p1(18, 180);
	Person p2(p1);
	cout << "p1的年龄:" << p1.m_age << "  身高:" << *p1.m_height << endl;
	cout << "p2的年龄:" << p2.m_age << "  身高:" << *p2.m_height << endl;
}

int main() {
	test();
	return 0;
}

 

 可以看到运行结果会报错:

具体原因就是: 两个对象重复释放堆区同一块内存,造成非法操作。见下图所示

 所以做析构时,P2先执行析构操作,释放了 0x0011地址,而P1在执行时,又会去释放 0x0011 ,这就会造成 同一块内存地址  0x0011 重复释放,这就是非法操作。

解决方案:

如果属性有在堆区 开辟,一定要自己提供拷贝构造函数,确保执行深拷贝操作(即在堆区开辟另一块内存存放对应的数据)

#include<iostream>
#include<string>
using namespace std;

class Person {
public:
	// 无参默认构造函数
	Person() {
		cout << "调用无参构造函数!" << endl;
	}

	// 有参构造函数
	Person(int age, int height) {
		cout << "调用有参构造函数!" << endl;
		this->m_age = age;
		this->m_height = new int(height);

	}

	// 拷贝构造函数
	Person(const Person& p) {
		cout << "调用拷贝构造函数!" << endl;
		// 如果不利用深拷贝在堆区开辟新内存,就会导致浅拷贝带来的重复释放堆区问题。
		m_age = p.m_age;
		m_height = new int(*p.m_height);
	}

	// 析构函数
	~Person()
	{
		cout << "调用析构函数!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}

public:
	int m_age;
	int* m_height;

};

void test() {
	Person p1(18, 180);
	Person p2(p1);
	cout << "p1的年龄:" << p1.m_age << "  身高:" << *p1.m_height << endl;
	cout << "p2的年龄:" << p2.m_age << "  身高:" << *p2.m_height << endl;
}

int main() {
	test();
	return 0;
}

 

 打印结果是正常的,并没有出现任何问题。

6:初始化列表 

C++的构造函数主要是对属性做初始化操作,现在我们可以通过另一种方式取初始化属性的值

#include<iostream>
using namespace std;
class Person {
public:
	// 传统方式初始化
	/*Person(int a, int b, int c) {
		this->m_A = a;
		this->m_B = b;
		this->m_C = c;
	}*/

	// 初始化列表的方式初始化
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}

	// 打印初始化的值
	void PrintPerson() {
		cout << "m_A :" << m_A << endl;
		cout << "m_B :" << m_B << endl;
		cout << "m_C :" << m_C << endl;
	}

private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {
	Person p(1, 2, 3);
	p.PrintPerson();
	return 0;
}

7:类对象作为类成员 

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。例如:B类中有对象A作为成员,A为对象成员。

案例:假设有人 类和手机 类,手机 类的对象是人 类的成员

class Phone {}

class Person {

        Phone p;

}

question:当创建Person对象时,Phone与Person的构造函数和析构函数的顺序谁先谁后?

answer: 构造函数的顺序是先调用 对象成员(Phone)的构造函数,在调用本类(Person)构造函数,析构函数顺序恰恰与构造函数顺序相反。(先调用本来的析构函数,在调用对象成员的析构函数)。

#include<iostream>
#include<string>
using namespace std;
class Phone {
public:
	Phone(string pName) {
		this->m_phoneName = pName;
		cout << "调用Phone的构造函数" << endl;
	}

	~Phone()
	{
		cout << "调用phone的析构函数" << endl;
	}


public:
	string m_phoneName;
};

class Person {
public:
	//初始化列表可以告诉编译器调用哪一个构造函数
	// m_phone(pName) 等同于: phone m_phone = pName (隐式转换,即通过有参构造隐式构造一个对象)
	Person(string name, string PName) :m_Name(name), m_Phone(PName) {
		cout << "调用 Person构造函数" << endl;
	}

	~Person()
	{
		cout << "调用Person的析构函数" << endl;
	}

	void playGame() {
		cout << m_Name << " 使用" << m_Phone.m_phoneName << "手机!" << endl;
	}

private:
	string m_Name;
	Phone m_Phone;

};

int main() {
	// 当类中成员是其他类对象时,我们称该成员为: 对象成员
	// 构造的顺序: 先调用对象成员的构造,再调用本类构造
	// 析构顺序: 与构造顺序相反 (先调用本类析构,再调用对象成员析构)
	Person p("张三", "苹果13");
	p.playGame();
	return 0;
}

 

 

8:静态成员 

静态成员就是在成员变量和成员函数前加上关键字 static 。 静态成员分为:

1:静态成员变量

-------》所有对象共享同一份数据,只要一个对象修改了这个数据,则这个数据就永久性发生了变化。

------》在编译阶段分配内存(在程序还没运行前就分配了),存放在全局区

------》类内声明,类外初始化。

2:静态成员函数

-----》所有对象共享同一个函数。

----》静态成员函数只能访问静态成员变量。

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

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

相关文章

左旋咪唑大单层/青蒿素长循环/酒石酸长春瑞滨热敏/棕榈酰五肽-4柔性/Anti-HER2免疫脂质体的研究

小编今天为大家分享了左旋咪唑大单层/青蒿素长循环/酒石酸长春瑞滨热敏/棕榈酰五肽-4柔性/Anti-HER2免疫脂质体的制备研究。 青蒿素长循环脂质体的制备&#xff1a; 青蒿素(artemisinin,ART)由于溶解度差,稳定性低,限制了其应用.因此,本研究采用长循环脂质体包裹青蒿素,增强其…

RDD中groupByKey和reduceByKey区别

groupByKey和reduceByKey区别 groupByKey 每个分区不聚合&#xff0c;等最终分组完成后调用Reduce再聚合 适用于求平均数、中位数等情况 reduceByKey 每个分区并行计算先实现分区内部聚合&#xff0c;然后再将每个分区的结果做最终的聚合实现分区间聚合 等同于MR中Combin…

电商之收单系统的webhook推送重试机制

文章目录1 问题背景2 前言3 解决方案3.1 核心思路3.2 数据库设计3.3 下一次发送webhook的时间算法3.3 详细设计4 延申思考1 问题背景 作为一个收单系统&#xff0c;当获取到一笔交易的支付结果时&#xff0c;就需要发送一个webhook消息给电商系统。电商系统收到webhook消息后&a…

4. Bean的生命周期

Bean的生命周期 1.生命周期相关概念介绍 生命周期&#xff1a;从创建到消亡的完整过程bean生命周期&#xff1a;bean从创建到销毁的整体过程bean生命周期控制&#xff1a;在bean创建后到销毁前做一些事情 2. Bean销毁时机 容器关闭前触发bean的销毁 关闭容器方式&#xff…

前端基础—Ajax和XML

Ajax和XML 说到这里&#xff0c;就不得不提到另一个概念&#xff1a;Ajax&#xff08;Asynchronous JavaScript&#xff09;&#xff0c;中文可以称之为“js的异步请求”&#xff0c;国内统一称为Ajax。 Ajax的概念是每次打开新的网页时&#xff0c;不要让页面整体刷新&#…

Java学习笔记 --- MySQL-常用数据类型

一、Mysql常用数据类型 二、数值型(整数)的基本使用 使用规范&#xff1a;在能够满足需求的情况下&#xff0c; 尽量选择占用空间小的 # 演示整形的使用 # 使用tinyint来演示范围 有符号 -128 ~ 127 如果没有符号 0-255 # 1. 如果没有指定 unsigned&#xff0c;则TINYINT就是…

卡塔尔世界杯门线技术(GOAL LINE TECHNOLOGY)背后的黑科技

现代职业足球运动员踢球时足球的行进速度&#xff0c;据国际足联统计数据&#xff0c;平均速度可达 60 英里/小时。极少数爆发力超强的职业球员&#xff0c;可以将这个速度刷新到超过 100 英里/小时。比如里斯本竞技队的巴西左后卫罗尼赫伯森在 2006 年以 131.82 英里/小时的速…

HACKTHEBOX——Sunday

nmap 第一次没有进行全端口扫描&#xff0c;只发现了79和111端口&#xff0c;79端口运行着finger程序&#xff0c;111则是rpcbind。 重新扫描一次&#xff0c;这次针对全部端口进行扫描。 nmap -p- -oA nmap 10.10.10.76 然后在扫描端口详细信息 可以发现22022端口运行着ssh…

数据结构——查找最全总结(期末复习必备)

目录 查找的基本概念 线性表的查找 顺序查找 折半查找&#xff08;二分或对分查找&#xff09; 分块查找&#xff08;索引顺序查找&#xff09; 树表的查找 二叉排序树 定义&#xff1a; 二叉排序树的查找&#xff1a; 二叉排序树的插入&#xff1a; 二叉排序树的创建&…

【缺陷识别】SVM金属表面缺陷分类与测量【含GUI Matlab源码 682期】

⛄一、简介&#xff08;附lunwen、答辩PPT&#xff09; 1 题目内容 金属板广泛应用在工业生产与生产生活的各方面。由于金属板制造过程涉及到的设备、工艺等多因素的影响&#xff0c;金属板表面容易出现种类较多、形态各异的缺陷&#xff0c;这些缺陷对金属板的耐磨性、抗腐蚀…

取整的四种方式

取整的四种方式一.基本认识二.四种取整方案1.零向取整2.地板取整3.向右取整4.四舍五入一.基本认识 这里按理说5/2应该为2.5啊&#xff0c;怎么为2呢&#xff1f;按照我们曾经的理解&#xff0c;其实知道符号/其实是取整。但它究竟是如何取整呢&#xff1f; 二.四种取整方案 1.…

小啊呜产品读书笔记001:《邱岳的产品手记-16》第30讲产品案例分析:Primer的扑克牌交互 第31讲 产品分析的套路(下):如何出解决方案?

小啊呜产品读书笔记001&#xff1a;《邱岳的产品手记-16》第30讲产品案例分析&#xff1a;Primer的扑克牌交互 & 第31讲 产品分析的套路&#xff08;下&#xff09;&#xff1a;如何出解决方案&#xff1f;一、今日阅读计划二、泛读&知识摘录1、第30讲产品案例分析&…

猿如意中的【Visual Studio Code】工具详情介绍

猿如意中的【Visual Studio Code】工具一、 猿如意工具介绍二、 工具名称2.1 下载安装渠道2.2 如何在载猿如意中下载VS Code开发工具&#xff1f;2.3 安装流程2.4 安装完成的界面2.6 VS Code使用步骤常用快捷键使用感受一、 猿如意工具介绍 打开猿如意程序工具。猿如意下载地址…

东北大学2023分布式操作系统考试题目

1、简述分布式系统的设计目标中开放性的特点有哪些&#xff1f; 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 2、简述分布式体系结构中的层次结构&#xff0c;并举出一个层次结构的例子 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 …

20222-02-16 Linux 触摸屏测试工具tslib工具下载、编译、安装,buildroot ARM平台上实际运行

一、tslib全称英文是C library for filtering touchscreen events&#xff0c;是捕捉触摸屏事件的工具。 二、下载地址https://github.com/libts/tslib 三、tslib的代码如下 二、ARM交叉编译流程 1、可能需要安装下面的软件 sudo apt-get install automake autoconf libtool …

数据结构之链表 - (通过代码实现方法,熟悉方法的使用)

文章目录前言1. 链表1.1 什么是链表&#xff1f;1.2 链表的分类2. 链表方法的实现2.1 实现构建思想2.2 代码实现2.2.1 实现方法前的准备工作2.2.2 链表方法:display() - 打印链表, contains() - 查找链表中key值, size() - 求链表长度2.2.3 头插法-addFirst(), 尾插法-addLast(…

比较生成模型

说说GAN/VAE/Flow/Diffusion/AR~~~ 各类生成模型&#xff0c;比如自回归模型Autoregressive Model (AR)&#xff0c;生成对抗网络Generative Adversarial Network (GAN)&#xff0c;标准化流模型Normalizing Flow (Flow)&#xff0c;变分自编码器Variational Auto-Encoder (VA…

软件测试优秀的测试工具,会用三款工作效率能提升一半

我们将常用的测试工具分为10类。 1. 测试管理工具 2. 接口测试工具 3. 性能测试工具 4. C/S自动化工具 5.白盒测试工具 6.代码扫描工具 7.持续集成工具 8.网络测试工具 9.app自动化工具 10.web安全测试工具 注&#xff1a;工具排名没有任何意义。 大多数初学者&…

图解Python深拷贝和浅拷贝

Python中&#xff0c;对象的赋值&#xff0c;拷贝&#xff08;深/浅拷贝&#xff09;之间是有差异的&#xff0c;如果使用的时候不注意&#xff0c;就可能产生意外的结果。 下面本文就通过简单的例子介绍一下这些概念之间的差别。 对象赋值 直接看一段代码&#xff1a; wil…

蓝桥杯:数字三角形

目录 题目描述 输入描述 输出描述 输入输出样例 输入 输出 思路&#xff1a; AC代码&#xff08;Java&#xff09;&#xff1a; 题目描述 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径&#xff0c;把路径上面的数加起来可以得到一个…