类和对象(上续)

前言:本文介绍类和对象中的一些比较重要的知识点,为以后的继续学习打好基础。

目录

拷贝构造

拷贝构造的特征:

自定义类型的传值传参

自定义类型在函数中的传值返回 

如果返回值时自定义的引用呢?

在什么情况下使用呢?

拷贝构造中的浅拷贝问题

 ​编辑

为什么会释放两次呢?

那么什么情况下需要深拷贝?

运算符重载 

运算符重载的基本语法:

类中运算符重载函数的调用的两种方法:

运算符重载与函数重载

运算符重载的特征:

运算符重载的价值:

如果将运算符重载成全局函数,就无法访问类中的私有成员了。

解决方法:

赋值运算符

调用拷贝构造与调用赋值重载的区别


拷贝构造

拷贝构造是一种特殊的构造函数

拷贝构造的特征:

1.是构造函数的重载

2.参数只有一个并且只能是引用

3.拷贝构造可以不显示写,编译器会自动生成默认构造。

浅拷贝(值拷贝)就是一个字节一个字节的拷贝。

编译器自动生成的默认拷贝的特点:对内置类型的成员,浅拷贝(值拷贝);对自定义类型的成员,拷贝需要调用其拷贝构造

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year,int month,int day)
	{
		
		_year = year;
		_month = month;
		_day = day;

	}
    //拷贝构造
	Date(Date& d)
	{
		cout << "拷贝构造" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024,6,10);
	Date d2(d1);//拷贝构造
	Date d3 = d2;//拷贝构造
	return 0;
}

自定义类型的传值传参

#include <iostream>
using namespace std;
class Date
{
public:
	int GetYear()
	{
		return _year;
	}
	int GetMonth()
	{
		return _month;
	}
	int GetDay()
	{
		return _day;
	}
	Date(int year,int month,int day)
	{
		
		_year = year;
		_month = month;
		_day = day;

	}
	Date(Date& d)
	{
		cout << "拷贝构造" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void f(Date d)
{
	cout << d.GetYear() << " " << d.GetMonth() << " " << d.GetDay() << endl;
}
int main()
{
	Date d1(2024,6,10);
	Date d2(d1);
	Date d3 = d2;
	f(d1);

	return 0;
}

以上代码的运行结果 

在给f函数传值传参时,调用了一次拷贝构造。

结论:自定义类型在进行传值传参时会进行拷贝构造。

如果拷贝构造的参数是自定义类型不是自定义的引用那么就会出现无穷递归调用

自定义类型在函数中的传值返回 

下面有一段代码,以这段代码为例讲一下该问题。

#include <iostream>
using namespace std;

class Date
{

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

Date f(Date d)
{
	return d;
}

int main()
{
	Date d1(2024, 6, 10);
	Date d2 = f(d1);
	return 0;
}

 

 

结论:如果返回值,是自定义类型,那么,返回时就会进行拷贝构造,创建临时对象,再将临时对象赋值给正在创建的类

用一段错误代码解释上面的结论:

Date f(Date& d)
{
	return d;
}

int main()
{
	Date d1(2024, 6, 10);
	Date& d2 = f(d1);
	return 0;
}

 

上述错误代码的报错: 

 

原因是因为,临时对象具有常性,用d2来引用临时对象是会出现权限放大的问题,所以验证了上述的结论,如果加上const(权限平移)报错就会消失。 

而编译器为了提高效率往往会直接将其优化为一次拷贝构造

如果返回值时自定义的引用呢?

Date& f()
{
	Date d1(2023, 1, 2);
	return d1;
}

int main()
{
	Date& d1 = f();
	return 0;
}

 因为,d1 实在函数中定义的对象,出了函数的作用域就会销毁。

栈帧的角度来理解,引用的本质是指针,f函数被销毁了,main函数中的d1仍指向f中的d1的那块已被销毁的空间

自定义类型的引用返回存在风险

在什么情况下使用呢?

出了函数的作用域生命周期没到,不构析对象还在,,那么就可以用引用 返回

出了函数的作用域生命周期到了,析构,对象不存在,那么就只能用传值返回

 

拷贝构造中的浅拷贝问题

以下代码存在浅拷贝问题

#include <stdlib.h>
#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		_arr = (int*)malloc(sizeof(int) * capacity);
		_capacity = capacity;
		_top = 0;
	}

	~Stack()
	{
		cout << "~Stack()"<<endl;
		free(_arr);
		_capacity = 0;
		_top = 0;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1(4);
	Stack st2(st1);
	return 0;
}

程序崩溃:

 

 

该代码的问题就在于对一块开辟的空间释放两次

为什么会释放两次呢?

因为没有显示写拷贝构造函数,所以用的是编译器自动生成的拷贝构造函数(浅拷贝),所以在拷贝构造st2时,使st2中_arr指向的空间与st1中的一样,最后分别调用析构函数时,就造成了对用一块开辟的空间释放两次。 

解决方案就是深拷贝

	Stack(Stack& st)
	{
		_arr = (int*)malloc(sizeof(int) * st._capacity);//深拷贝
		_capacity = st._capacity;
		_top = st._top;
	}

那么什么情况下需要深拷贝?

总结:

1.如果没有管理资源,就不显示写拷贝构造,用默认拷贝构造就可以

2.都是自定义的成员,内置类型(内置类型不指向资源),也用默认拷贝;如果自定义类型的成员的内置类型指向资源,那么在该自定义类型中显示写拷贝构造

3.一般,不需要写析构函数,就需要写构造函数

4.内部有指针或一些值指向资源,显示写析构释放,通常需要写拷贝构造来完成深拷贝

运算符重载 

 

运算符重载的基本语法:

返回值类型 + operater+运算符(参数列表)

operator是关键字,operator和运算符一起构成函数名。

类中运算符重载函数的调用的两种方法:

    //在类中实现的+运算符重载(实现日期与天数的相加)
	Date operator+(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
		return *this;
	}  
     //已经在类中写了一个加号重载的函数
	Date d1(2024, 6, 10);
	d1 + 100;//第一种调用方法
	d1.operator+(100);//第二种调用方法

运算符重载与函数重载

运算符重载和函数重载没有关系,是两回事,多个相同的运算符的重载是函数重载

比如<<(流插入)可以自动识别内置类型的原因就是对<<进行重载,构成了函数重载。

运算符重载的特征:

1.不能通过其他符号重载

2.必须有一个类类型的参数

3.含义不能改变(这里是建议,比如重载的+的含义是将两个数相加,而你写的含义是相减)

4.一般,参数比运算符操纵的操作数的数目少1,因为在参数列表中有隐含的this指针

5. .*    ::    sizeof   ?:   .   这五个操作符不能被重载,.*是用于类成员函数指针的访问,

如果想了解:函数指针到底需不需要解引用?类成员函数呢?_函数指针需要解引用吗-CSDN博客

   

运算符重载的价值:

运算符重载是运算符不仅限于操纵内置类型的数据,可以实现类与类之间,或类与内置类型直间的运算,可以增强代码的可读性

一个使用运算符重载的例子:

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //得到当前月份的天数
	int	GetMonthDay(int year, int month)
	{
		int month_day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if(month==2&&((year%4==0&&year%100!=0)||year%400==0))
			return 29;
		return month_day[month];
	}
    //重载+运算符实现日期与天数的相加
	Date operator+(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
		return *this;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 6, 10);
	Date d2 = d1 + 100;
	d2.Print();
    return 0;
}

上述代码中的+的重载,是在类中实现的,或在类中声明,在类外实现。

如果将运算符重载成全局函数,就无法访问类中的私有成员了。

解决方法:

1.在类中实现成员的Get(获取成员)和Set(重新给成员赋值)的接口

2.将全局函数设为该类的友元

3.重载为成员函数(可以访问类的成员,但函数不在是全局函数)

这些方法比较建议第二种。

以下的代码是通过友元来实现全局减号的运算符重载

#include <iostream>
using namespace std;
class Date
{
    //友元就是在函数前加上一个关键字friend,并在相应的类中声明
	friend Date operator-(Date& d,int day);
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int	GetMonthDay(int year, int month)
	{
		int month_day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if(month==2&&((year%4==0&&year%100!=0)||year%400==0))
			return 29;
		return month_day[month];
	}
	Date operator+(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_month = 1;
				_year++;
			}
		}
		return *this;
	}
	void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

//全局函数减号的重载
Date operator-(Date& d,int day)
{
	d._day -= day;
	while (d._day <= 0)
	{
		if (d._month == 1)
		{
			d._month = 12;
			d._year--;
		}
		else
		{
			d._month--;
		}
		d._day += d.GetMonthDay(d._year, d._month);
	}
	return d;
}



赋值运算符

赋值运算符重载也是6个默认成员函数之一

调用拷贝构造与调用赋值重载的区别

	Date d1(2024, 6, 10);
	Date d2 = d1;//拷贝构造
	Date d3(d1);//拷贝构造
	Date d4(2024, 2, 11);
	d4 = d1;//赋值重载

注意:上面代码中两个等号的调用方式容易混,但最后这两个有本质的区别。

结语:希望本文能够让你有所收获 。

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

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

相关文章

前端技术入门指南

引言 在数字化时代&#xff0c;前端开发成为了连接用户与数字世界的重要桥梁。无论你是对编程充满好奇的新手&#xff0c;还是想要拓展自己技能领域的在职人员&#xff0c;前端开发都是一个值得学习和探索的领域。本文将带你走进前端技术的世界&#xff0c;为你提供一个入门指…

前端nvm的安装和使用nodejs多版本管理2024

nvm的安装和使用 1、简介 nvm是一个管理nodejs版本的工具。在实际的开发中&#xff0c;项目的开发依赖需要的nodejs版本运行环境不同&#xff0c;此时我们就需要使用nvm来进行不同nodejs版本的切换。其实就是一个方便的node版本管理工具。 注意&#xff1a;如果有安装过node&a…

机器学习笔记——支持向量机

支持向量机 参数模型对分布需要假设&#xff08;这也是与非参数模型的区别之一&#xff09;间隔最大化&#xff0c;形式转化为凸二次规划问题 最大化间隔 间隔最大化是意思&#xff1a;对训练集有着充分大的确信度来分类训练数据&#xff0c;最难以分的点也有足够大的信度将…

文心一言 VS 讯飞星火 VS chatgpt (278)-- 算法导论20.3 5题

五、假设我们创建一个包含 u 1 k u^\frac{1}{k} uk1​ 个簇(而不是全域大小为 x ↓ {\sqrt[↓]{x}} ↓x ​ 的 x ↑ {\sqrt[↑]{x}} ↑x ​ 个簇)的 vEB 树&#xff0c;其每个簇的全域大小为 u 1 − 1 k u ^ {1-\frac{1}{k}} u1−k1​ &#xff0c;其中 k>1 &#xff0c…

AI如何创造情绪价值

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面。从智能家居到自动驾驶&#xff0c;从医疗辅助到金融服务&#xff0c;AI技术的身影无处不在。而如今&#xff0c;AI更是涉足了一个全新的领域——创造情绪价值。 AI已经能够处…

Docker:利用Docker搭建一个nginx服务

文章目录 搭建一个nginx服务认识nginx服务Web服务器反向代理服务器高性能特点 安装nginx启动nginx停止nginx查找nginx镜像拉取nginx镜像&#xff0c;启动nginx站点其他方式拉取nginx镜像信息通过 DIGEST 拉取镜像 搭建一个nginx服务 首先先认识一下nginx服务&#xff1a; NGI…

Discuz! X3.4免备案无执照接入支付宝微信支付插件

下载地址&#xff1a;Discuz! X3.4免备案无执照接入支付宝微信支付插件 [充值会员]支付宝当面付版 微信支付

【2023】LitCTF

LitCTF2023&#xff08;复现&#xff09; Web&#xff1a; 1、我Flag呢&#xff1f; ​ ctrlu 读取源码&#xff0c;在最后发现了flag&#xff1a; <!--flag is here flagNSSCTF{3d5218b9-4e24-4d61-9c15-68f8789e8c48} -->2、PHP是世界上最好的语言&#xff01;&…

Linux系统编程(十二)线程同步、锁、条件变量、信号量

线程同步&#xff1a; 协同步调&#xff0c;对公共区域数据按序访问。防止数据混乱&#xff0c;产生与时间有关的错误。数据混乱的原因 一、互斥锁/互斥量mutex 1. 建议锁&#xff08;协同锁&#xff09;&#xff1a; 公共数据进行保护。所有线程【应该】在访问公共数据前先拿…

Java里面的10个Lambda表达式必须掌握,提高生产力

目录 Java里面的10个Lambda表达式必须掌握&#xff0c;提高生产力 前言 1. 使用Lambda表达式进行集合遍历 2. 使用Lambda表达式进行集合过滤 3. 使用Lambda表达式进行集合映射 4. 使用Lambda表达式进行集合排序 5. 使用Lambda表达式进行集合归约 6. 使用Lambda表达式进…

idea最新专业版安装+maven配置教程!

本教程适用于 J B 全系列产品&#xff0c;包括 Pycharm、IDEA、WebStorm、Phpstorm、Datagrip、RubyMine、CLion、AppCode 等。 &#xff08;直接复制&#xff0c;拿走不谢&#xff09; 9H1390TRAK-eyJsaWNlbnNlSWQiOiI5SDEzOTBUUkFLIiwibGljZW5zZWVOYW1lIjoi5rC45LmF5rA5rS7I…

在VMware虚拟机上安装win10 跳过 通过microsoft登录

在VMware虚拟机上安装win10 跳过 “通过microsoft登录” 配置虚拟机&#xff0c;将网卡断开&#xff0c; 具体操作&#xff1a; 虚拟机/设置/硬件/网络适配器/设备状态&#xff0c;取消已连接和启动时连接的两个对号&#xff0c; 再把虚拟机重启&#xff0c;然后就可以跳过这个…

苹果WWDC大会AI亮点:大揭晓

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

IT学习笔记--Flink

概况&#xff1a; Flink 是 Apache 基金会旗下的一个开源大数据处理框架。目前&#xff0c;Flink 已经成为各大公司大数据实时处理的发力重点&#xff0c;特别是国内以阿里为代表的一众互联网大厂都在全力投入&#xff0c;为 Flink 社区贡献了大量源码。 Apache Flink 是一个…

day27回溯算法part03| 39. 组合总和 40.组合总和II 131.分割回文串

39. 组合总和 题目链接/文章讲解 | 视频讲解 本题是 集合里元素可以用无数次&#xff0c;那么和组合问题的差别 其实仅在于 startIndex上的控制 class Solution { public:int sum;vector<int> path;vector<vector<int>> result;void backtracking(vector<…

MySQL-数据处理(1)

029-数据处理函数之获取字符串长度 select length(我是Cupid); select char_length(我是Cupid);concat (concatenate) select concat(cu, pid, so, handsome);030-去除字符串前后空白-trim select trim( a b c );select trim(leading 0 from 000110); select t…

1688商品库存查询

目录 下载安装与运行 功能简介 快速入门&#xff08;视频&#xff09; 当前支持的导出项 常用功能 历史商品是什么意思 粘贴商品有什么要求 导入商品需要什么样的模板 单个商品的查看 查看单个商品详情 下载安装与运行 下载、安装与运行 语雀 功能简介 最近一次测…

Python图像处理入门学习——基于霍夫变换的车道线和路沿检测

文章目录 前言一、实验内容与方法二、视频的导入、拆分、合成2.1 视频时长读取2.2 视频的拆分2.3 视频的合成 三、路沿检测3.1 路沿检测算法整体框架3.2 尝试3.3 图像处理->边缘检测(原理)3.4 Canny算子边缘检测(原理)3.5 Canny算子边缘检测(实现)3.5.1 高斯滤波3.5.2 图像转…

RK3568平台(显示篇)FrameBuffer 应用编程

一.FrameBuffer介绍 FrameBuffer&#xff08;帧缓冲器&#xff09;是一种计算机图形学概念&#xff0c;用于在显示器上显示图形和文本。在 计算机显示系统中&#xff0c;FrameBuffer 可以看作是显存的一个抽象概念&#xff0c;用于存储显示屏幕上显示 的像素点的颜色和位置信息…

【Java面试】十六、并发篇:线程基础

文章目录 1、进程和线程的区别2、并行和并发的区别3、创建线程的四种方式3.1 Runnable和Callable创建线程的区别3.2 线程的run和start 4、线程的所有状态与生命周期5、新建T1、T2、T3&#xff0c;如何保证线程的执行顺序6、notify和notifyAll方法有什么区别7、wait方法和sleep方…