【C++】:类和对象(中)之拷贝构造函数+赋值运算符重载

news2024/12/19 18:17:05

在这里插入图片描述
在这里插入图片描述

拷贝构造函数

概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎
在这里插入图片描述
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

特征

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

  1. 拷贝构造函数是构造函数的一个重载形式
  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;
}

在这里插入图片描述
这里大家可能会很疑惑为什么会出现无穷递归的现象 下面我画一个图大家理解一下
在这里插入图片描述

  1. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& dd)
	{
		_year = dd._year;
		_month = dd._month;
		_day = dd._day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void func1(Date& d)
{
	d.Print();
}

int main()
{
	//拷贝构造函数
	Date d1(2023, 10, 22);
	func1(d1);
	Date d2(d1);

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的
4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			/*exit(-1)*/return;
		}
		_capacity = capacity;
		_top = 0;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_capacity = _top = 0;
		_a = nullptr;
	}
	Stack(const Stack& stt)
	{
		//深拷贝
		_a = (int*)malloc(sizeof(int) * stt._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);//return;
		}
		memcpy(_a, stt._a, sizeof(int) * stt._top);
		_top = stt._top;
		_capacity = stt._capacity;
	}
private:
	int* _a;
	int _capacity;
	int _top;
};
int main()
{
	//拷贝构造函数
	Date d1(2023, 10, 22);
	func1(d1);
	Date d2(d1);


	Stack st1;
	func2(st1);
	Stack st2(st1);

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝
在这里插入图片描述
5. 拷贝构造函数典型调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象

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

在这里插入图片描述
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
.* :: 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;
}
void Test ()
{
    Date d1(2018, 9, 26);
    Date d2(2018, 9, 27);
    cout<<(d1 == d2)<<endl;
}
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,指向调用函数的对象
    bool operator==(const Date& d2)
 {
        return _year == d2._year;
            && _month == d2._month
            && _day == d2._day;
 }
private:
 int _year;
 int _month;
 int _day;
};

以下是我们关于赋值运算符重载的总代码

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

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	//int GetYear();

	bool operator==(const Date& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}

	bool operator>(const Date& y)
	{
		if (_year > y._year)
		{
			return true;
		}
		else if (_year == y._year && _month > y._month)
		{
			return true;
		}
		else if (_year == y._year && _month == y._month && _day > y._day)
		{
			return true;
		}

		return false;
	}

	// d1 - d2
	int operator-(const Date& d)
	{
		// ...
		return 0;
	}

	int GetMonthDay(int year, int month)
	{
		assert(year >= 1 && month >= 1 && month <= 12);

		int monthArray[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 monthArray[month];
	}

	// d1 += 100
	Date& operator+=(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);

			++_month;

			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}

		return *this;
	}

	// d1 + 50
	/*Date operator+(int day)
	{
		Date tmp(*this);

		tmp._day += day;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			tmp._day -= GetMonthDay(tmp._year, tmp._month);

			++tmp._month;

			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}

		return tmp;
	}*/

	Date operator+(int day)
	{
		Date tmp(*this);
		tmp += day;

		return tmp;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(2023, 10, 22);

	bool ret1 = d1 > d2;  // d1.operator>(d2) -> d1.operator>(&d1, d2)
	bool ret2 = d1 == d2; // d1.operator==(d2) -> d1.operator==(&d1, d2)

	// d1.operator>(d2);

	// 一个重载哪些运算符呢?主要这个运算符有没有意义
	// 有意义就可以实现,没有意义就不要实现
	//d1 + d2;
	d1 - d2;
	//d1 * d2;
	//d1 / d2;

	d2 += 50;
	d2.Print();

	Date ret3 = d1 + 50;
	d1.Print();
	ret3.Print();

	int i = 0, j = 0;
	//i += 50;
	int ret = j += i += 50;


	return 0;
}

赋值运算符重载

  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 ;
};
  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
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;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

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

前置++和后置++重载

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;
 }
 // 后置++:
 // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
 // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
自动传递
 // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1
 //       而temp是临时对象,因此只能以值的方式返回,不能返回引用
 Date operator++(int)
 {
 Date temp(*this);
 _day += 1;
 return temp;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date d;
 Date d1(2022, 1, 13);
 d = d1++;    // d: 2022,1,13   d1:2022,1,14
 d = ++d1;    // d: 2022,1,15   d1:2022,1,15
 return 0;
}

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

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

相关文章

Proteus仿真--量程自动切换数字电压表(仿真+程序)

本文主要介绍基于51单片机的量程自动切换数字电压表Proteus仿真设计&#xff08;完整仿真源文件及代码见文末链接&#xff09; 简介 硬件电路主要分为单片机主控模块、AD转换模块、量程选择模块以及数码管显示模块 &#xff08;1&#xff09;单片机主控模块&#xff1a;单片…

DASCTF-CBCTF-2023 Crypto部分复现

文章目录 EzRSACB backpack 这次比赛没打&#xff0c;记错时间了&#xff0c;看了一下&#xff0c;如果去做的话大概也只能做出那两道简单的题&#xff0c;还是太菜啦 EzRSA 题目描述&#xff1a; from Crypto.Util.number import * import random from gmpy2 import * from …

P6510 奶牛排队

题目 P6510 奶牛排队 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路 1.dp求最大。&#xff08;dp即前后关联&#xff09;arr[]用于存储输入的数据&#xff0c;brr[i]用于存储以第i头牛为右端点的队列最大值。 2.数组空间不够大&#xff0c;我们可以自己开辟对空间&…

【Java基础面试三十五】、谈谈你对面向接口编程的理解

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;谈谈你对面向接口编程的…

MySql数据库实现注册登录及个人信息查询的数据库设计

前言: 数据库使用的是mysql 以下创建的表,实现以下功能: 用户1,账号admin,年龄20,关联3件商品 用户2,账号admin2,年龄30,关联2件商品(没有商品和用户1重复) 用户3,账号admin3,年龄50,关联2件商品(这两件商品均是用户1的其中两种) 登录查询对应数据的实现 1.创建用户表Users,并…

【SmartApi】v1.1.0版本发布

历史版本介绍 SmartApi-v1.0.0使用说明 新增功能 Email分享文档导入接口数据-目前仅支持apiPost、SmartApi数据导入 Email分享文档 效果如下&#xff1a; 使用说明&#xff1a; 进行邮箱配置&#xff1b;公网可以使用163、QQ等免费邮箱服务收件人列表用逗号隔开即可&…

PKU 概率论+数理统计 期中考复习总结

这里写目录标题 计算条件概率计算概率&#xff08;放回与不放回&#xff09;生成随机数算法Uniformity (test of frequency)1.Chi-Square test2.Kolmogorov-Sminov test Independence (test of autocorrelation)Runs test Acceptance-rejection methodmethod方法1&#xff1a;建…

【Java基础面试三十九】、 finally是无条件执行的吗?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a; finally是无条件执行的…

Git使用指南

Git指南 删除文件后无法Pull 问题描述 在删除文件后使用Pull无法拉取文件 解决方法 使用Git Bash输入以下命令 git fetch --all git reset --hard origin/master git pull切换当前分支 Pull卡死在进度条 问题描述 在使用Pull时没有报错&#xff0c;但是无法完成拉取 解决方…

2023-10-22 LeetCode每日一题(做菜顺序)

2023-10-22每日一题 一、题目编号 1402. 做菜顺序二、题目链接 点击跳转到题目位置 三、题目描述 一个厨师收集了他 n 道菜的满意程度 satisfaction &#xff0c;这个厨师做出每道菜的时间都是 1 单位时间。 一道菜的 「 like-time 系数 」定义为烹饪这道菜结束的时间&am…

【Java 进阶篇】手把手教你创建 Bootstrap 旅游网站

随着互联网的普及&#xff0c;旅游行业在全球范围内迅速发展。人们通过网络规划、预订和分享他们的旅行经历。因此&#xff0c;拥有一个令人印象深刻的旅游网站对于吸引游客和提供有用信息至关重要。在本篇博客中&#xff0c;我们将手把手教您如何创建一个令人兴奋的旅游网站&a…

用matlab求解线性规划

文章目录 1、用单纯形表求解线性规划绘制单纯形表求解&#xff1a; 2、用matlab求解线性规划——linprog()函数问题&#xff1a;补充代码&#xff1a;显示出完整的影子价格向量 1、用单纯形表求解线性规划 求解线性规划 m i n − 3 x 1 − 4 x 2 x 3 min -3x_1-4x_2x_3 min−…

通过内网穿透快速搭建公网可访问的Spring Boot接口调试环境

&#x1f525;博客主页&#xff1a; 小羊失眠啦 &#x1f516;系列专栏&#xff1a; C语言 、Cpolar、Linux ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 …

【Java基础面试三十六】、遇到过异常吗,如何处理?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;遇到过异常吗&#xff0…

敏捷是怎么提高工作效率的

敏捷管理是一门极力减少不必要工作量的艺术。 谷歌、亚马逊、苹果、微信、京东等全球 500 强企业都在用的管理方法&#xff0c;适用于各行各业&#xff0c;被盛赞为应获“管理学的诺贝尔奖”。 它专注于让员工不受种种杂事的羁绊&#xff0c;激发个体斗志&#xff0c;释放出巨大…

HashMap LinkedHashMap

1. HashMap概述 定义&#xff1a;HashMap根据键的hash值来存储数据 底层&#xff1a; JDK1.7底层&#xff1a;数组链表JDK1.8底层&#xff1a;数组链表红黑树 当链表长度达到8且数组长度长于64时&#xff0c;就会转换成红黑树&#xff08;红黑树阈值&#xff09;如果数组长度小…

如何成为开源组件库NutUI的contributor:解决issue并提交PR

前言 NutUI 是一套京东风格的轻量级移动端组件库。自诞生以来&#xff0c;一直在紧跟技术的发展不断推陈出新&#xff0c;始终保持稳健的发展态势&#xff0c;从一个单一的基础组件库发展到如今服务于数千开发者的多端 UI 组件库。NutUI 的成长离不开团队成员的努力&#xff0…

基于WebRTC的程序因虚拟内存不足导致闪退问题的排查以及解决办法的探究

目录 1、WebRTC简介 2、问题现象描述 3、将Windbg附加到目标进程上分析 3.1、Windbg没有附加到主程序进程上&#xff0c;没有感知到异常或中断 3.2、Windbg感知到了中断&#xff0c;中断在DebugBreak函数调用上 3.3、32位进程用户态虚拟地址和内核态虚拟地址的划分 …

JVM的几个面试重点

JVM的内存区域划分 JVM类加载机制 前言 Java程序最开始是一个 .java 的文件&#xff0c;JVM把它编译成 .closs 文件&#xff08;字节码文件&#xff09;&#xff0c;运行 Java 程序&#xff0c; JVM 就会读取 .class 文件&#xff0c;把文件内容读取到内存中&#xff0c;构造出…

C++:为什么析构函数一般写为虚函数

如果没有继承关系&#xff0c;析构函数写不写为虚函数都可以。 如果有继承关系、有多态性的使用需求时&#xff0c;就需要把析构函数写为虚函数&#xff0c;这样可以避免潜在的内存泄漏问题。 比如&#xff1a;当一个类被设计为作为基类&#xff0c;并且通过基类指针或引用dele…