拷贝构造函数和运算符重载

news2024/11/23 11:30:14

文章目录

  • 拷贝构造函数
    • 特点
    • 分析拷贝构造函数情景
  • 赋值运算符重载
    • 运算符重载
      • operator<运算符重载
    • 赋值运算符
    • 前置++和后置++重载


拷贝构造函数

在创建对象的时候,是不是存在一种函数,使得能创建一个于已经存在的对象一模一样的新对象,那么接下来,让我们一起去了解一下,拷贝构造函数

特点

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

  • 拷贝构造函数是构造函数的重载
  • 拷贝构造函数的参数只有一个,且必须是类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用
  • 使用传值的方法是会报错,因为会引发无穷的递归调用
  • 若没有显示定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数对象按照内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或值拷贝。

我们以日期类为例:

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

在这里插入图片描述

所以当拷贝构造函数的的调用要用传引用,因为传引用的话,就不需要拷贝,也就是说能调用并进入拷贝构造函数,当没有&的时候,形参进入就需要拷贝,就会调用拷贝构造函数(不进去,在形参处,就需要继续拷贝,从而无穷递归,使得编译器报错)

对于默认拷贝构造函数的使用:下面代码演示:

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;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

在这里插入图片描述

  1. 内置类型成员完成浅拷贝or值拷贝
  2. 自定义类型成员会调用他的拷贝构造

举例说明

//1.全是内置成员变量
class Person{
    public:
    	Person(int data,int age){
            _data=data;
            _age=age;
        }
    void Print()
    {
        cout<<_data<<" "<<_age<<endl;
    }
    
    	int _data;
    	int _age;
};
//这个会调用默认构造函数
int main(){
    Person p1(10,20);
    Person p2(p1);    
}
//2.都是自定义函数,或者说是包括自定义函数可以使用浅拷贝,因为我们需要的就是值的传递

但是对于这样一种需要自行开辟空间如malloc的自定义类型Stack,是需要进行深拷贝的,就是需要显示拷贝构造函数

原因如下:

以Stack为例,里面malloc一个空间作为动态内存的开辟,在实例化Stack对象之后,我们会自动构造这个Stack,最后用完这个对象之后会进行销毁,~Stack()析构函数里面是由free的,如果说,我们使用默认的拷贝构造函数之后,两个对象是指向同一空间的,但是,只能free一次,所以在第二次free的时候就会报错

在这里插入图片描述

所以对于这样的拥有动态内存的自定义类型,需要我们来进行深拷贝(自定义拷贝构造函数)

总结:

类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

分析拷贝构造函数情景

由下面的代码进行演示,在由拷贝构造函数的情况下的程序运行步骤

class Person{
  public:
    Person(int data){
        cout<<"Person构造函数"<<this<<endl;
    }
    Person(const Person& p){
        cout<<"拷贝构造函数"<<this<<endl;
    }
    ~person(){
        cout<<"Person析构函数"<<this<<endl;
    }
    int data;
};

Person Test(Person p){
    Person tmp(p);
    return tmp;
}
int main()
{
    Person p(10);
    Test(p);
    return 0;
}

在这里插入图片描述

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。

赋值运算符重载

赋值运算符重载可以使得自定义类型也可以像内置类型一样进行运算符运算,以下所有的运算符重载都是以Date类为例

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,哈桑函数名字一级参数列表,其返回值类型于参数列表于普通函数类似

格式:返回值类型 operator操作符(参数列表)

函数名为:operator+需要重载的运算符符号

注意事项:

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

//具体的操作如下  以Date为例、
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;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
对于封装的问题,我们这里只能先使用public来解决,后面学到友元之后,就可以不用public
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
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;
}

我们当然可以例如上述代码一样,将operator==Date在类外面定义和声明,但是这样的话,我们如果不用友元的话,就只能将成员变量权限改为public,所以我们的方法为:将运算符重载放在Date里面

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    bool operator==(const Date& d2)
	{
	return _year == d2._year
		&& _month == d2._month
		&& _day == d2._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.operator==(d2) << endl;
	cout << (d1 == d2) << endl;
}
int main()
{
	Test();
	return 0;
}

在这里插入图片描述

    bool operator==(const Date& d2)
	{
	return _year == d2._year
		&& _month == d2._month
		&& _day == d2._day;
	}
//所以左操作数就是this指针,右操作为形参

operator<运算符重载

下面代码进行演示

//主要进行演示流程
class Person{
  public:
    Person(int age){
        _age=age;
    }
    bool operator<(const Person& p){
        if(_age<p.age) return true;
        else return false;
    }
    bool operator==(const Person& p){
        if(_age==p._age) return true;
        else return false;
    }
    bool operator<=(const Person& p){
        return (*this == p) && (*this < p);
    }
    int _age;
};
int main()
{
	Person P1(10);
    
	Person P2(20);
	cout << (P1 < P2) << endl;
    return 0;
}

为了提高服用率,我们通常将利用其他运算符重载来实现我们需要的运算符重载

    bool operator<(const Person& p){
        if(_age<p.age) return true;
        else return false;
    } 
//只需要知道一个 < 一个 ==就能得到所有的比较大小的运算符重载
    bool operator==(const Person& p){
        if(_age==p._age) return true;
        else return false;
    }
    bool operator<=(const Person& p){
        return (*this == p) && (*this < p);
    }
//我们一定注意的是,运算符重载使用的的传引用,这样可以提高效率

赋值运算符

赋值运算符为operator=,这个运算符重载有一些细节要求

赋值运算符重载的格式

  1. 参数类型:const T&,传递引用可以提高传参效率,和上述保持一致
  2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  3. 检测是否自己给自己赋值
  4. 返回*this :要复合连续赋值的含义
//首先我们来看内置类型赋值的情况
int main()
{
    int n=10;
    n=20;
    int i,j,k;
    i=j=k=0;//连续赋值,0先给k返回的结果给了j再给了i,以至于i=j=k
    //所以我们需要的返回值类型应该为类类型 例如Person
    //又因为,传引用返回可以提高效率(少一次拷贝构造返回),所以我们选择的是类类型&  如Person&
    cout<<n<<endl;//输出结果为n  这个会改变n的数值
    return 0;
}

所以我们自定义类型的赋值运算符重载也要这样,所以我们应该这样做

class Person{
  public:
    Person(int age){
        _age=age;
    }
    Person& operator=(const Person& p){
    	if(*this!=p){
            //如果两个对象不是同一个对象
            _age=p._age;//进行赋值
        }
        return *this;//这个this的生命周期不在类里面,所以是可以传引用返回
    }

    int _age;
};
int main()
{
    Person p1(10);
    Person p2(20);
    cout<<p1=p2<<endl;
	return 0;
}

在这里插入图片描述

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

在这里插入图片描述

Vs2022版本的编译器对于全局的赋值运算符重载会直接编译报错,并提示,operator=必须是成员函数

这是因为,我们规定返回类型为传引用,但是如果是全局函数,那么左值的生命周期在调用完函数之后把就结束的,所以这个地方也有问题,且“operator =”必须是非静态成员

原因解释:

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

也就是说,这个实际上就是存在默认的赋值运算符重载,所以才不能用于全局函数

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

总结:

  • 对于赋值运算符重载,在没有自定义的时候,编译器会自动提供默认赋值运算符重载,且这个默认函数是值拷贝,也就是浅拷贝的
  • 内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
  • 对于类似于栈Stack这样的类,是需要自定义的,但是对于Date或者上述Person类是不需要的,使用默认的即可
  • 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

前置++和后置++重载

我们先看内置类型前置++和后置++的区别

int main()
{
    int n=10;
    int a=n++;//后++,这一行n还是为10 这个代码结束 后 n为11
    int b=++n;//先++,这一行n就为11
    cout<<a<<" "<<b<<endl;
    return 0;
}

所以自定义类型也需要按照内置类型的方式来实现前置后置++

class Person {
public:
    void Print() {
        cout << _age << endl;
    }
    Person(int age) {
        _age = age;
    }
    //我们规定前置++为无参
    Person& operator++() {
        (*this)._age++;
        return *this;
    }
    //后置++  括号使用这个无参类型表示
    Person operator++(int) {
        Person tmp = *this;
        (*this)._age+=1;
        return tmp;//返回临时变量,所以不能传引用返回
    }
    int _age;
};
int main()
{
    Person p(10);
    cout << p._age << endl;
    p++;
    p.Print();
    cout << p._age << endl;
    ++p;
    cout << p._age << endl;
    return 0;
}

在这里插入图片描述

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

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

相关文章

Kotlin注解

文章参考 1、概念 注解可以对 类、函数、函数参数、属性等 做标注 注解信息可用于 源码级、编译期、运行时 示例 Retention(AnnotationRetention.RUNTIME) //作用于运行时 Target(AnnotationTarget.CLASS) //限定作用于类 annotation class Api(val url: String)/*** Rete…

Nachos系统的上下文切换

Fork调用创建进程 在实验1中通过gdb调试初步熟悉了Nahcos上下文切换的基本流程&#xff0c;但这个过程还不够清晰&#xff0c;通过源码阅读进一步了解这个过程。 在实验1中通过执行Threadtest&#xff0c;Fork创建子进程&#xff0c;并传入SimpleThread执行currentThread->…

vulnhub靶场之Wayne Manor

1.信息收集 探测存活主机&#xff0c;发现192.168.239.174存活 对主机进行端口扫描&#xff0c;发现存在端口21、22、80&#xff1b;且21端口状态是filtered。 打开浏览器访问http://192.168.239.174&#xff0c;未发现可疑&#xff0c;进行目录扫描只发现robots.txt。 什…

集合详解之(七)泛型

文章目录 &#x1f412;个人主页&#x1f3c5;JavaSE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;泛型的由来--参数化类型&#x1fa80;元组的介绍 ( int...数组名x )&#x1fa84;类型通配符&#x1f387;使用泛型的注意事项&#x1f3c5;子类继承泛型类【两种情况】 …

Transformers 发展一览

动动发财的小手&#xff0c;点个赞吧&#xff01; Transformers 研究概览 1. 介绍 近年来&#xff0c;深度学习的研究步伐显着加快&#xff0c;因此越来越难以跟上所有最新发展。尽管如此&#xff0c;有一个特定的研究方向因其在自然语言处理、计算机视觉和音频处理等多个领域取…

CLion开发工具 | 04 - CLion内置工具和插件

专栏介绍 一、CLion内置工具 1. SSH终端工具 填写ssh远程连接信息&#xff1a; 连接后在Terminal栏即可使用&#xff1a; 2. HTTP请求 填写http请求内容&#xff0c;并发起请求&#xff0c;方便的一批&#xff1a; 二、插件 1. 管理已安装的插件 2. 简体中文插件 3. 主题…

React 环境搭建,并打包到服务器

一. 安装node.js brew install node 二. 创建react app npx create-react-app my-app cd my-app npm start 默认使用3000端口&#xff0c;本地3000端口被占用&#xff0c;修改/node_modules/react-scripts/scripts/start.js中的端口号 // 这是start.js部分源码 const DEFAU…

【前端客栈】使用CSS实现畅销书排行榜页面

&#x1f4ec;&#x1f4eb;hello&#xff0c;各位小伙伴们&#xff0c;我是小浪。大家都知道&#xff0c;我最近是在更新各大厂的软件测试开发的面试真题&#xff0c;也是得到了很大的反馈和好评&#xff0c;几位小伙伴也是成功找到了测开的实习&#xff0c;非常不错。如果能前…

移除链表元素

☃️个人主页&#xff1a;fighting小泽 &#x1f338;作者简介&#xff1a;目前正在学习C语言和数据结构 &#x1f33c;博客专栏&#xff1a;leetcode练习题 &#x1f3f5;️欢迎关注&#xff1a;评论&#x1f44a;&#x1f3fb;点赞&#x1f44d;&#x1f3fb;留言&#x1f4a…

ChatGPT 和 Elasticsearch:OpenAI 遇见私有数据(二)

在之前的文章 “ChatGPT 和 Elasticsearch&#xff1a;OpenAI 遇见私有数据&#xff08;二&#xff09;” 中&#xff0c;我们详细描述了如何结合 ChatGPT 及 Elasticsearch 来进行搜索。它使用了如下的架构&#xff1a; 在今天的文章中&#xff0c;我们来详细描述实现这个的详…

BatchNormalization 介绍

1 为何要用BatchNormalization 为了让深层网络更容易训练&#xff0c;有两种方法&#xff1a; 使用更好的优化器&#xff1a;如 SDGMomentun等&#xff1b; 改变网络结构&#xff0c;比如加入BN层&#xff0c;处理网络数据&#xff0c;让网络数据服从标准的高斯分布&#xff0…

人群计数传统方法:object detection, regression-based

数据标注方式&#xff1a; &#xff08;1&#xff09;人很少、人很大的时候用bounding box&#xff0c;把人从头到脚都框进长方形方框内&#xff0c;这个方框只用记录三个点的坐标&#xff0c;左下、左上、右下&#xff1b;测试集预测的时候&#xff0c;除了点的坐标还要输出这…

1. 安装Open vSwitch环境

1. 安装Open vSwitch环境 1 配置基础环境。 在VMware Workstation软件中创建一个虚拟机VM1&#xff0c;配置2张网卡&#xff0c;虚拟机VM1配置如图4-3所示。将网卡ens33地址配置为192.168.1.131/24&#xff0c;网卡ens34地址配置为192.168.2.131/24。 图4-3 VM1虚拟机配置 2…

(有假币,因子个数)笔试强训

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 文章目录 一、选择1 二、选择2 二、[编程题]有假币 三、[编程题]因子个数 一、选择1 在使用锁保证线程安全时&#xff0c;可能会出现活跃度失败的情况&#xff0c;活跃度失败主要…

【DataGrip】手把手教你使用可视化数据库管理工具DataGrip(附数据库驱动无法下载解决办法)

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: Mysql从入门到精通近期目标&#xff1a;写好专栏的每一篇文章 目录 一、…

Android 面试笔记总结,建议吸收一下灵气~

android消息机制 消息机制指Handler、Looper、MessageQueue、Message之间如何工作的。 handler是用来处理消息和接收消息的中间者&#xff0c;handler的创建会伴随着handler中产生looper和MessageQueue&#xff0c;handler依赖于looper&#xff0c;looper依赖于MessageQueue&a…

大学生体质测试管理系统~java

摘要 大学生体质测试管理系统提供给用户一个简单方便体质测试管理信息&#xff0c;通过留言区互动更方便。本系统采用了B/S体系的结构&#xff0c;使用了java技术以及MYSQL作为后台数据库进行开发。系统主要分为系统管理员、教师和用户三个部分&#xff0c;系统管理员主要功能…

[论文笔记]C^3F,MCNN:图片人群计数模型

(万能代码)CommissarMa/Crowd_counting_from_scratch 代码&#xff1a;https://github.com/CommissarMa/Crowd_counting_from_scratch (万能代码)C^3 Framework开源人群计数框架 科普中文博文&#xff1a;https://zhuanlan.zhihu.com/p/65650998 框架网址&#xff1a;https…

[oeasy]python0141_自制模块_module_reusability_复用性

自制包内容 回忆上次内容 上次导入了外部的py文件 import my_module 导入一个自己定义的模块 可以使用my_module中的变量 不能 直接使用 my_module.py文件中的变量只要加my_module.作为前缀就可以 直接导入导入变量、函数 from my_module import pi 可以导入my_module.pi 并…

面试必问的Java 线程池原理及最佳实践

1. 概述 1.1 线程池是什么 线程池&#xff08;Thread Pool&#xff09;是一种基于池化思想管理线程的工具&#xff0c;经常出现在多线程服务器中&#xff0c;如MySQL。 创建线程本身开销大&#xff0c;反复创建并销毁&#xff0c;过多的占用内存。所以有大量线程创建考虑使用…