【C++类和对象】:构造函数、析构函数、拷贝构造函数、赋值运算符重载

news2024/9/22 3:31:01

【C++类和对象】:构造函数、析构函数、拷贝构造函数、赋值运算符重载

  • 一、构造函数
    • 1.1 概念
    • 1.2 性质
    • 1.3 实例
  • 二、析构函数
    • 2.1 概念
    • 2.2 性质
    • 2.3 实例
  • 三、拷贝构造函数
    • 3.1 概念
    • 3.2 性质
    • 3.3 实例
  • 四、赋值运算符重载
    • 4.1 运算符重载
    • 4.2 2 赋值运算符重载
      • 1. 赋值运算符重载格式
      • 2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
      • 3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
    • 4.3 实例
    • 4.4 前置++和后置++重载


在这里插入图片描述


一、构造函数

 在C语言结构体中,创建变量需要调用初始化函数来变量设置一些信息。但实际项目中,如果每个变量都通过这种方式来设置相关信息,未免过于繁琐。为此,C++为我们提供了构造函数。

1.1 概念

 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
 需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

1.2 性质

其特征如下:

  1. 函数名与类名相同,无返回值。
  2. 对象实例化时编译器自动调用对应的构造函数。
  3. 构造函数可以重载。
  4. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数。
  5. 默认生成的构造函数,内置类型类型不会处理(C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值),自定义类型会调用对应的默认构造函数。

Tips:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

1.3 实例

 分析:变量d实例化时,会调用对应的构造函数。但Date中没用显示的写,所以编译器会生成一个无参的默认构造函数。对于内置类型_year、_month、_day均初始化为1(由于我们在类中声明时给了初始值),而自定义类型_t调用其对应的默认构造函数。

#include <iostream>
using namespace std;

class Time
{
public:
	Time(int hour=1,int minute=1,int second=1)
	{
		cout << "Time()" << endl;
		_hour = hour;
		_minute = minute;
		_second = second;
	}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	void Print()
	{
		cout << _year << _month << _day << endl;
	}
private:
	// 基本类型(内置类型)
	int _year=1;
	int _month=1;
	int _day=1;
	// 自定义类型
	Time _t;//调用对应的默认构造函数
};

int main()
{
	Date d;//Date没有显示定义构造函数,编译器默认生成一个无参默认构造函数
	return 0;
}

二、析构函数

2.1 概念

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

2.2 性质

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(析构函数不能重载)。并且在对象生命周期结束时,C++编译器自动调用析构函数。
  4. 自动生成的默认构造函数,自定义类型不做处理,内置类型会调用对应的析构函数。
  5. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

2.3 实例

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

结果:程序运行结束后输出:~Time()
问:在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?(创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

解析:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,_day三个是自定义类型,销毁时不需要资源清理,最后系统直接将其内存回收即可。而_t是Time类对对象,在对d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁。


三、拷贝构造函数

3.1 概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

3.2 性质

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错,因为会引发无穷递归调用
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。(在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。)

Tips:性质2中,为啥使用传值方式会引发无穷递归调用。(下面以日期类为例)

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	// Date(const Date& d)   // 正确写法
	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.3 实例

问题1:编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?(我们以栈为例,看看会发生什么?)

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();//由于栈创建时开了10个空间,博主保证足够用就在此不检查是否扩容问题了
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

解析:s2拷贝s1时,调用默认构造函数,发生值拷贝。导致s1和s2中的成员函数_array指向同一处地址。当程序结束时,s1、s2都会调用对应的析构函数,会导致同一块地址析构两次的问题。
在这里插入图片描述


四、赋值运算符重载

4.1 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)

Tips:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@。
  2. 重载操作符必须有一个类类型参数。
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义。
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。
  5. .*  ::  sizeof  ?:  . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

实例:(以==为例,给出内置和全局两个版本)
【全局】:

// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

int  main()
{
	Date d1(2023, 10, 4);
	Date d2(2018, 9, 3);
	cout << (d1 == d2) << endl;
	return 0;
}

【内置】:

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

	// bool operator==(Date* this, const Date& d2),隐藏this指针
  // 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date& d2)
	{
		return _year == d2._year && _month == d2._month && _day == d2._day;
	}
	private:
	int _year;
	int _month;
	int _day;
};

int  main()
{
	Date d1(2023, 10, 4);
	Date d2(2023, 10, 3);
	cout << (d1 == d2) << endl;
	return 0;
}

4.2 2 赋值运算符重载

1. 赋值运算符重载格式

参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
【实例】:

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;
	}

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

【实例】:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}

分析:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
在这里插入图片描述

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
【实例】:

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

4.3 实例

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像上诉日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

//这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	//赋值运算符重载是用一个已存在的对象去初始化另一个已存在的对象
	//而拷贝构造是用一个已存在的对象去初始化另一个正要创建的对
	Stack s2;
	s2 = s1;
	return 0;
}

解析:
在这里插入图片描述

4.4 前置++和后置++重载

  前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。
【实例】:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
	//        而temp是临时对象,因此只能以值的方式返回,不能返回引用
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};

好了,本篇博客到此就结束了,希望对你又帮助。
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

一文读懂UTF-8的编码规则

之前写过一篇文章“一文彻底搞懂计算机中文编码”里面只是介绍了GB2312编码知识&#xff0c;关于utf8没有涉及到&#xff0c;经过查询资料发现utf8是对unicode的一种可变长度字符编码&#xff0c;所以再记录一下。 现在国家对于信息技术中文编码字符集制定的标准是《GB 18030-…

yolov8封装进入ROS系统

一,yolov8的环境配置工作 说明:需要相关工程全部源码的可私聊博主或评论留言 配置工作可以参考我的一篇关于yolov8环境配置的博客。 需要说明的是:直接使用: pip install ultralytics 会导致后面对于网络结构的改进无法运行。所以如果需要对网络结构进行改进的同学,不要使用…

学信息系统项目管理师第4版系列17_干系人管理

1. 项目经理和团队管理干系人的能力决定着项目的成败 2. 干系人满意度应作为项目目标加以识别和管理 3. 发展趋势和新兴实践 3.1. 识别所有干系人&#xff0c;而非在限定范围内 3.2. 确保所有团队成员都涉及引导干系人参与的活 3.3. 定期审查干系人群体&#xff0c;可与单…

互联网Java工程师面试题·Dubbo篇·第一弹

目录 1、为什么要用 Dubbo&#xff1f; 2、Dubbo 的整体架构设计有哪些分层? 3、默认使用的是什么通信框架&#xff0c;还有别的选择吗? 4、服务调用是阻塞的吗&#xff1f; 5、一般使用什么注册中心&#xff1f;还有别的选择吗&#xff1f; 6、默认使用什么序列化框架&…

CUDA+cuDNN+TensorRT 配置避坑指南

深度学习模型加速部署的环境配置&#xff0c;需要在本地安装NVIDIA的一些工具链和软件包&#xff0c;这是一个些许繁琐的过程&#xff0c;而且一步错&#xff0c;步步错。笔者将会根据自己的经验来提供建议&#xff0c;减少踩坑几率。当然可以完全按照官方教程操作&#xff0c;…

新手学习笔记-----⽂件操作

目录 1. 为什么使⽤⽂件&#xff1f; 2. 什么是⽂件&#xff1f; 2.1 程序⽂件 2.2 数据⽂件 2.3 ⽂件名 3. ⼆进制⽂件和⽂本⽂件&#xff1f; 4. ⽂件的打开和关闭 4.1 流和标准流 4.1.1 流 4.1.2 标准流 4.2 ⽂件指针 4.3 ⽂件的打开和关闭 5. ⽂件的顺序读写 …

多层神经网络和激活函数

多层神经网络的结构 多层神经网络就是由单层神经网络进行叠加之后得到的&#xff0c;所以就形成了层的概念&#xff0c;常见的多层神经网络有如下结构&#xff1a; 1&#xff09;输入层&#xff08;Input layer&#xff09;&#xff0c;众多神经元&#xff08;Neuron&#xff…

点云处理开发测试题目

点云处理开发测试题目 文件夹中有一个场景的三块点云数据&#xff0c;单位mm。是一个桌子上放了一个纸箱&#xff0c;纸箱上有四个圆孔。需要做的内容是&#xff1a; 1. 绘制出最小外接立方体&#xff0c;得到纸箱的长宽高值。注意高度计算是纸箱平面到桌子平面的距离。 2. 计…

【每日一题】买卖股票的最佳时机 IV

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;动态规划 写在最后 Tag 【动态规划】【数组】【2023-10-04】 题目来源 188. 买卖股票的最佳时机 IV 题目解读 本题与 121. 买卖股票的最佳时机、122. 买卖股票的最佳时机 II、123. 买卖股票的最佳时机 III 题意目的一…

一文拿捏Spring之IOC、循环依赖、Spring的设计模式

1.IOC(控制反转)与DI(依赖注入) &#x1f31f;面试题(IOC-DI): IOC控制反转是一种实现对象解耦的思想。传统情况下是采用new的方式来创建对象&#xff0c;这个对象是由我们来直接控制的&#xff0c;但是有了IOC后&#xff0c;我们可以在IOC运行期间通过DI动态的将依赖的对象进…

嵌入式系统设计与应用---ARM处理器体系结构(学习笔记)

ARM处理器概述 Cortex-A8处理器工作模式 ps&#xff1a;除用户模式以外的其他模式被称为非用户模式或特权模式&#xff1b;除用户模式及系统模式以外的其他模式可称为异常模式 Cortex-A8存储器管理​​​​​​​ ARM的基本数据类型 字节&#xff08;Byte&#xff09;&#…

激活函数介绍

介绍 神经网络当中的激活函数用来提升网络的非线性&#xff0c;以增强网络的表征能力。它有这样几个特点&#xff1a;有界&#xff0c;必须为非常数&#xff0c;单调递增且连续可求导。我们常用的有sigmoid或者tanh&#xff0c;但我们都知道这两个都存在一定的缺点&#xff0c…

第十五章 类和对象——友元

生活中你的家有客厅(Public)&#xff0c;有你的卧室(Private) 客厅所有来的客人都可以进去&#xff0c;但是你的卧室是私有的&#xff0c;也就是说只有你能进去 但是呢&#xff0c;你也可以允许你的好闺蜜好基友进去。 在程序里&#xff0c;有些私有属性 也想让类外特殊的一些…

springboot配置静态资源访问

使用springboot搭建web项目的时候&#xff0c;springboot默认从static目录访问静态资源 例如我们写一个html,然后去访问这个html 内容《你好&#xff01;》 然后运行项目 运行成功&#xff0c;此时我用的端口号是8089 然后去浏览器里访问hello.html http://localhost:8089/…

力扣 -- 879. 盈利计划(二维费用的背包问题)

解题步骤&#xff1a; 参考代码&#xff1a; 未优化的代码&#xff1a; class Solution { public:int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {//计划数int lengroup.size();//每一维都多开一行空间vector&…

查询表中指定列数据

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: select 列名1, 列名2, ..., 列名n from 表名; 想要查多少列就写多少个列名,不需要看的不要写就行了 案例&#xff1a;查询emp表中&…

Spring面试题学习: 单例Bean是单例模式吗?

单例Bean是单例模式吗 学习背景答案扩展知识单例模式Spring BeanJava Bean单例Bean 个人评价我的回答 学习背景 想换工作. 学习记录, 算是一个输出. 答案 通常来说, 单例模式是指在一个JVM中, 一个类只能构造出一个对象. 有很多方法来实现单例模式, 比如饿汉模式. 但是我们通…

使用Pytorch构建神经网络

构建神经网络的典型流程 定义一个拥有可学习参数的神经网络遍历训练数据集处理输入数据使其流经神经网络计算损失值将网络参数的梯度进行反向传播以一定的规则更新网络的权重 我们首先定义一个Pytorch实现的神经网络: # 导入若干工具包 import torch import torch.nn as nn …

【vue】element强制刷新el-carousel的dom:

文章目录 一、问题:二、分析:三、解决: 一、问题: 二、分析: el-carouse里面的数据是最新的&#xff0c;但dom却是前一个的数据 注意&#xff1a;使用了this.$forceUpdate();或this.$set(xx,xx);&#xff0c;没有效果 三、解决: :key"new Date().getTime()"

透明度和透明贴图制作玻璃水杯

1、什么是透明度 模型透明度是指一个物体或模型在呈现时的透明程度。它决定了物体在渲染时&#xff0c;是否显示其后面的物体或背景。 在图形渲染中&#xff0c;透明度通常以0到1之间的值表示。值为0表示完全透明&#xff0c;即物体不可见&#xff0c;背景或其他物体完全穿透…