类和对象(上续)

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

目录

拷贝构造

拷贝构造的特征:

自定义类型的传值传参

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

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

在什么情况下使用呢?

拷贝构造中的浅拷贝问题

 ​编辑

为什么会释放两次呢?

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

运算符重载 

运算符重载的基本语法:

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

运算符重载与函数重载

运算符重载的特征:

运算符重载的价值:

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

解决方法:

赋值运算符

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


拷贝构造

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

拷贝构造的特征:

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;//赋值重载

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

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

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

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

相关文章

Vue3【十五】标签的Ref属性

Vue3【十五】标签的Ref属性 标签的ref属性 用于注册模板引用 用在dom标签上&#xff0c;获取的是dom节点 用在组件上&#xff0c;获取的是组件实例对象 案例截图 目录结构 代码 app.vue <template><div class"app"><h1 ref"title2">你…

MATLAB实现磷虾算法(Krill herd algorithm)

1.算法介绍 磷虾算法&#xff08;Krill Herd Algorithm, KH&#xff09;是一种基于生物启发的优化算法&#xff0c;其原理模拟了南极磷虾&#xff08;Euphausia superba&#xff09;群体的聚集行为。该算法旨在通过模拟磷虾个体间的相互作用、觅食行为和随机扩散&#xff0c;来…

基于机器学习的锂电池RUL SOH预测

数据集为NASA锂电池数据集。 import datetimeimport numpy as npimport pandas as pdfrom scipy.io import loadmatfrom sklearn.preprocessing import MinMaxScalerfrom sklearn.metrics import mean_squared_errorfrom sklearn import metricsimport matplotlib.pyplot as p…

C# MES通信从入门到精通(11)——C#如何使用Json字符串

前言 我们在开发上位机软件的过程中&#xff0c;经常需要和Mes系统进行数据交互&#xff0c;并且最常用的数据格式是Json&#xff0c;本文就是详细介绍Json格式的类型&#xff0c;以及我们在与mes系统进行交互时如何组织Json数据。 1、在C#中如何调用Json 在C#中调用Json相关…

【Golang】Go语言中defer与return的精妙交织:探索延迟执行与返回顺序的微妙关系

【Golang】Go语言中defer与return的精妙交织&#xff1a;探索延迟执行与返回顺序的微妙关系 大家好 我是寸铁&#x1f44a; 总结了一篇defer 和 return 返回值 的执行顺序探讨的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 在Go语言中&#xff0c;defer 和return是两…

二进制文件的膨胀策略和使用 debloat 消除膨胀测试

在恶意软件的分析中有的 Windows 可执行文件&#xff08;PE 文件&#xff09;会通过膨胀策略来绕过防病毒一些防病毒的检查&#xff0c;比如上传云进行分析&#xff0c;因为文件太大了所以无法进行一些防病毒分析。一般的可执行文件有很多的膨胀策略&#xff0c;一般简单的膨胀…

NettyのBufferChannelSelector用法

这一篇介绍Buffer&Channel&Selector的常见API使用案例 1、Buffer 1.1、从Buffe中读取/写入 以ByteBuffer为例。Buffer需要和Channel结合使用&#xff08;在上一篇中提到&#xff0c;通道是数据传输的载体&#xff0c;缓冲区是数据的临时存储区&#xff09;。 那么如何…

OSFP 1类LSA详解

概述 上图为1类LSA的实际报文结构 , 在开始之前一定需要说明 , 1类LSA是OSPF中最复杂的LSA类型 , 在LSA头部的文章中详细介绍了 LS Type / Link State ID / Adv Router 3种头部字段 , 在1类LSA的主体内容中还存在类似的字段十分的相似 , 很多网络从业者难以理解的点就在于此 , …

orbslam2代码解读(2):tracking跟踪线程

书接上回&#xff0c;mpTracker->GrabImageMonocular(im,timestamp)函数处理过程&#xff1a; 如果图像是彩色图&#xff0c;就转成灰度图如果当前帧是初始化的帧&#xff0c;那么在构建Frame的时候&#xff0c;提取orb特征点数量为正常的两倍&#xff08;目的就是能够在初…

14. RTCP 协议

RTCP 协议概述 RTCP&#xff08;Real-time Transport Control Protocol 或 RTP Control Protocol 或简写 RTCP&#xff09;&#xff0c;实时传输控制协议&#xff0c;是实时传输协议&#xff08;RTP&#xff09;的一个姐妹协议。 注&#xff1a;RTP 协议和 RTP 控制协议&#…

Postgresql源码(135)生成执行计划——Var的调整set_plan_references

1 总结 set_plan_references主要有两个功能&#xff1a; 拉平&#xff1a;生成拉平后的RTE列表&#xff08;add_rtes_to_flat_rtable&#xff09;。调整&#xff1a;调整前每一层计划中varno的引用都是相对于本层RTE的偏移量。放在一个整体计划后&#xff0c;需要指向一个统一…

架构设计-全局异常处理器404、405的问题

java web 项目中经常会遇到异常处理的问题&#xff0c;普遍的做法是使用全局异常处理&#xff0c;这样做有以下几种原因&#xff1a; 集中化处理&#xff1a;全局异常处理允许你在一个集中的地方处理整个应用程序中的异常。这有助于减少代码重复&#xff0c;因为你不必在每个可…

项目方案:社会视频资源整合接入汇聚系统解决方案(五)

目录 一、概述 1.1 应用背景 1.2 总体目标 1.3 设计原则 1.4 设计依据 1.5 术语解释 二、需求分析 2.1 政策分析 2.2 业务分析 2.3 系统需求 三、系统总体设计 3.1设计思路 3.2总体架构 3.3联网技术要求 四、视频整合及汇聚接入 4.1设计概述 4.2社会视频资源分…

QT项目实战: 五子棋小游戏

目录 内容介绍 一.添加头文件 二.画棋盘 1.宏定义 2.棋盘 三.画棋子 四.获取棋子摆放位置 五.判断棋子存在 六.判断胜利 1.变量定义和初始化 2.检查获胜条件 3.游戏结束处理 七.重绘 八.效果展示 九.代码 1.mainwindow.h 2.mainwindow.cpp 3.chessitem.h 4…

【python】在【机器学习】与【数据挖掘】中的应用:从基础到【AI大模型】

目录 &#x1f497;一、Python在数据挖掘中的应用&#x1f495; &#x1f496;1.1 数据预处理&#x1f49e; &#x1f496;1.2 特征工程&#x1f495; &#x1f497;二、Python在机器学习中的应用&#x1f495; &#x1f496;2.1 监督学习&#x1f49e; &#x1f496;2.2…

【MySQL】(基础篇七) —— 通配符和正则表达式

通配符和正则表达式 本章介绍什么是通配符、如何使用通配符以及怎样使用LIKE操作符进行通配搜索&#xff0c;以便对数据进行复杂过滤&#xff1b;如何使用正则表达式来更好地控制数据过滤。 目录 通配符和正则表达式LIKE操作符百分号(%)通配符下划线(_)通配符 通配符使用技巧正…

深入理解 C++ 智能指针

文章目录 一、引言二、 原始指针的问题1、原始指针的问题2、智能指针如何解决这些问题 三、智能指针的类型四、std::shared_ptr1、shared_ptr使用2、shared_ptr的使用注意事项3、定制删除器4、shared_ptr的优缺点5、shared_ptr的模拟实现 五、std::unique_ptr1、unique_ptr的使…

SpringSecurity入门(三)

12、密码加密 12.1、不指定具体加密方式&#xff0c;通过DelegatingPasswordEncoder&#xff0c;根据前缀自动选择 PasswordEncoder passwordEncoder PasswordEncoderFactories.createDelegatingPasswordEncoder();12.2、指定具体加密方式 // Create an encoder with streng…

【iOS】UI学习——登陆界面案例、照片墙案例

文章目录 登陆界面案例照片墙案例 登陆界面案例 这里通过一个登陆界面来复习一下前面学习的内容。 先在接口部分定义两个UILabel、两个UITextField、两个UIButton按键&#xff1a; #import <UIKit/UIKit.h>interface ViewController : UIViewController {UILabel* _lb…

2024050501-重学 Java 设计模式《实战命令模式》

重学 Java 设计模式&#xff1a;实战命令模式「模拟高档餐厅八大菜系&#xff0c;小二点单厨师烹饪场景」 一、前言 持之以恒的重要性 初学编程往往都很懵&#xff0c;几乎在学习的过程中会遇到各种各样的问题&#xff0c;哪怕别人那运行好好的代码&#xff0c;但你照着写完…