C++的六大“天选之子“拷贝构造与与运算符重载

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解C++中有关类和对象的介绍,本篇是中篇的第结尾篇文章,讲解拷贝构造,运算符重载以及取地址重载符.
金句分享:
✨别在最好的年纪,辜负了最好的自己.✨

一、“拷贝构造函数”

拷贝构造函数

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

2.1 自动生成的"拷贝构造函数"

假设哦我们需要创建两个一模一样的对象AB.
在这里插入图片描述
那我们可以先创建一个对象A,再通过将A作为参数,传给B进行初始化,
即一个自定义类型实例化出的对象(B)用另一个该类型实例化出的对象(A)进行初始化.

class Date
{
public:Date(int year = 2020, int month = 1, int day = 1)//全缺省构造函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date A(2023, 7, 20);A.Print();printf("\n");Date B(A);//会调用系统生成的拷贝构造B.Print();return 0;
}

运行结果:

2023-7-20

2023-7-20

其实拷贝构造函数就是构造函数的一种重载形式,他也是六大天选之子之一,没有显式定义时,编译器也会自动生成,但是只会完成"浅拷贝"(下面讲)…

2.2 自定义"拷贝构造函数"

#include <iostream>
using std::cin;
using std::cout;
using std::endl;class Date
{
public:Date(int year = 2020, int month = 1, int day = 1)//全缺省构造函数{_year = year;_month = month;_day = day;}Date(const Date& d)//拷贝构造函数{cout << "拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 7, 20);d1.Print();printf("\n");Date d2(d1);d2.Print();return 0;
}

我们发现Date(const Date& d)这里使用了引用传参,如果直接传参会怎样呢?
在这里插入图片描述
为什么会报错呢?

void test(int a)
{}void test(Date d1)
{}int main()
{Date d1(2023, 7, 20);test(2);test(d1);return 0;
}

这段代码会调用Date 类的拷贝构造.
在这里插入图片描述
对于自定义类型作为参数时,必须调用该类型的拷贝构造函数.
所以可以回答上面的问题了.

在这里插入图片描述
所以拷贝构造函数传参时采用引用传参,这样就避免了传参时调用拷贝构造.

2.3 深浅拷贝?

前面在介绍编译器自动生成的"拷贝构造函数"时,提到了浅拷贝,那什么是浅拷贝呢?

浅拷贝:按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
在这里插入图片描述

深拷贝:
在这里插入图片描述

示例:
栈类中没有显式定义拷贝构造函数,编译器自动生成的拷贝构造是浅拷贝带来的问题.

#include <iostream>
using std::cin;
using std::cout;
using std::endl;typedef int DataType;
class Stack
{
public:Stack(int capacity=5)//全缺省构造函数{cout << "Stack" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data)//压栈操作{CheckCapacity();_array[_size] = data;_size++;}~Stack()//析构函数{cout << "~Stack"<< endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity *sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);//这条语句会报错.return 0;
}

运行结果:
在这里插入图片描述
原因:
因为编译器默认生成的拷贝构造是浅拷贝,这里两个对象的_array也就指向了同一块内存空间,但是两个对象的声生命周期结束时,会调用各自的析构函数,这也就导致对同一块空间进行了释放操作.
解决方法:
显示定义一个拷贝构造函数.

	Stack(const Stack& S)//深拷贝{_array = (int*)malloc(sizeof(int) * S._capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}memcpy(S._array,_array,sizeof(int)*S._size);_capacity = S._capacity;_size = S._size;}

总结:

拷贝构造使用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
  3. 当一个对象作为参数传递给函数时,拷贝构造函数会被调用来创建一个新的对象,该新对象与传递的对象具有相同的属性和属性值,但是它们在内存中是独立的。
  4. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝.

二、赋值运算符重载(“=”)

2.1 运算符重载的介绍

class Date//日期类
{
public:Date(int year = 2023, int month = 10, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
void test1()
{Date d1(2023, 7, 28);Date d2;if (d2 == d1){cout << "d1=d2";}if (d1 < d2){cout << "d1<d2";}
}

在这里插入图片描述

自定义类型是无法像内置类型一样比较大小和使用一些常规运算符的.
为什么呢?
在这里插入图片描述
因为自定义类型是用户自己定义的,编译器不知道该如何进行比较.那编译器太笨了吧,日期按 年-月-日依次比较不就行了?
个人理解:

  1. 格局打开,如果是别的类呢?比如:person是按名字还是按职位,还是按什么?你不告诉编译器如何比较,编译器也很无奈,不敢瞎搞的.
  2. 编译器咋知道你year是年,要是牛牛用nian来命名,他也能识别出来是年吗?

综上,自定义类型如何进行运算比较,只有用户自己知道,所以用户需要自己来设计规则.

在这里插入图片描述
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型.

函数名:关键字operator+需要重载的运算符符号。
operator+ 需要重载的运算符

注意事项:

  • 不能通过连接其他符号来创建新的操作符:
    示例:operator@

  • 重载操作符必须有一个类类型参数
    运算符重载是通过类的成员函数或全局函数来实现的,而这些函数必须具有特定的参数列表。
    对于成员函数的重载操作符,至少需要一个类类型参数,它表示操作符的左操作数。例如,对于二元操作符(如 +、-、* 等),成员函数的参数列表通常还包括一个非常量引用或常量引用,表示操作符的右操作数。

  • 用于内置类型的运算符,其含义不能改变:
    例如:内置的整型*不要实现为了/,害人是不对的.

  • 作为类成员函数重载时,其形参看起来比操作数数目少1一个,因为成员函数的第一个参数为隐藏的this .

  • 注意以下5个运算符不能重载。“.*” (点星) 、" :: " sizeof ? : .

在C++中,有一些操作符是不能被重载的,包括以下几种情况:

  1. ::(作用域解析操作符):作用域解析操作符用于指定命名空间、类或结构的作用域,并访问其成员。它不能被重载,因为它的含义在语言中已经固定不可更改。

  2. .*(指针到成员操作符)和 ->*(指向成员指针的操作符):这些操作符用于访问类的成员指针。它们存储了一个指向类成员的指针,并用于在运行时访问该成员。它们也不能被重载。

  3. sizeof(大小操作符):sizeof操作符用于获取一个对象或类型的大小(以字节为单位)。它是一个编译时的操作符,不能在运行时被重载。因为在编译时就已经确定了对象或类型的大小。

  4. ?:(条件操作符,即三目运算符):条件操作符是一个三元操作符,用于根据条件选择不同的表达式。它不能被重载,因为它的语法和含义已经在语言中定义好了。

  5. .C++中,点操作符(“.”)是用来访问对象的成员的,而它本身是不能被重载的。点操作符的行为在语言中是固定的,无法通过重载来改变。

2.2 赋值运算符重载:

(1)编译器自动生成的 “赋值运算符重载”

class Date//日期类
{
public:Date(int year = 2023, int month = 10, int day = 1){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};void test1()
{Date d1(2023, 7, 28);Date d2;d1.print();d2.print();cout << endl;d2 = d1;d1.print();d2.print();}
int main()
{test1();return 0;
}

在这里插入图片描述

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

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

那编译器会生成一个默认赋值运算符重载会做什么事情呢?

以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

当然对于日期类这种只需要浅拷贝的类来说,编译器默认生成就已经足够了,但是像stack类,同样引发深浅拷贝的问题.

三、最后的两个天选之子

哈哈哈,期待到最后的两个默认成员函数其实没什么要讲解的.

  1. 取地址操作符重载operator&()
  2. const取地址操作符重载operator&()const
    这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{ 
public :Date* operator&(){return this ;}const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可除非你想搞点特殊的,返回一个特定的特殊地址.

本篇内容到此讲解完了,后续介绍日期类的具体实现,方便大家更好的理解类和对象的知识,实战才能锻炼水平哦.
在这里插入图片描述

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

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

相关文章

Vue 中使用 WebWorker

目录 安装 loader 应用场景 打包时错误处理 安装 loader npm install worker-loader -D 如果直接把worker.js放到public目录下&#xff0c;则不需要安装loader vue.config.js const { defineConfig } require(vue/cli-service)module.exports defineConfig({transpileDe…

Von Maur, Inc EDI 需求分析

Von Maur, Inc 是一家历史悠久的卖场&#xff0c;成立于19世纪&#xff0c;总部位于美国。作为一家知名的零售商&#xff0c;Von Maur 主要经营高端时装、家居用品和美妆产品。其使命是为顾客提供优质的产品和无与伦比的购物体验。多年来&#xff0c;Von Maur 凭借其卓越的服务…

Selenium入门详细教程+实例演示

目录 1.Selenium概述 1.1什么是Selenium 1.2Selenium的优势 1.3Selenium WebDriver原理 2.Selenium环境搭建 3.Selenium 简单示例 4.八大元素定位 4.1定位方式 4.2定位方式的用法 5.Selenium API 5.1WebDriver 常用 API 5.2WebElement 常用 API 5.3代码示例 6.元素等待机…

算法练习--字符串相关

文章目录 计算字符串最后一个单词的长度计算某字符出现次数明明的随机数回文字符串回文数字无重复字符的最大子串长度有效的括号罗马数字转整数字符串通配符杨辉三角查找两个字符串a,b中的最长公共子串 **找出字符串中第一个只出现一次的字符 计算字符串最后一个单词的长度 pe…

arcgis--网络分析(理论篇)

1、定义概念 &#xff08;1&#xff09;网络&#xff1a;由一系列相互联通的点和线组成&#xff0c;用来描述地理要素&#xff08;资源&#xff09;的流动情况。 &#xff08;2&#xff09;网络分析&#xff1a;对地理网络&#xff08;如交通网络、水系网络&#xff09;&…

【excel技巧】excel公式如何隐藏?

Excel文件中最重要的除了数据还有就是一些公式了&#xff0c;但是只要点击单元格&#xff0c;公式就能显示出来&#xff0c;如果不想别人看到公式应该如何设置呢&#xff1f;今天分享隐藏excel单元格数据的方法。 选中单元格&#xff0c;点击右键打开【设置单元格格式】&#x…

Python-OpenCV中的图像处理-边缘检测

Python-OpenCV中的图像处理-边缘检测 边缘检测Canny算子 边缘检测Canny算子 Canny 边缘检测是一种非常流行的边缘检测算法&#xff0c;是 John F.Canny 在 1986 年提出的。它是一个有很多步构成的算法&#xff1a;噪声去除、计算图像梯度、非极大值抑制、滞后阀值等。 Canny(i…

last_hidden_state vs pooler_output的区别

一、问题来源&#xff1a; from transformers import AutoTokenizer, AutoModel import torch # Load model from HuggingFace Hub MODEL_NAME_PATH xxxx/model/bge-large-zh tokenizer AutoTokenizer.from_pretrained(MODEL_NAME_PATH) model AutoModel.from_pretrained(M…

DROP USER c##xyt CASCADE > ORA-01940: 无法删除当前连接的用户

多创建了一个用户&#xff0c;想要给它删除掉 一 上执行过程&#xff0c;确实删除成功了 Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production With the Partitioning, OLAP, Advanced Analytics and Real Application Testing optionsSQL> DR…

在Go语言单元测试中如何解决Redis存储依赖问题

登录程序示例 在 Web 开发中&#xff0c;登录需求是一个较为常见的功能。假设我们有一个 Login 函数&#xff0c;可以实现用户登录功能。它接收用户手机号 短信验证码&#xff0c;然后根据手机号从 Redis 中获取保存的验证码&#xff08;验证码通常是在发送验证码这一操作时保…

[C#] 简单的俄罗斯方块实现

一个控制台俄罗斯方块游戏的简单实现. 已在 github.com/SlimeNull/Tetris 开源. 思路 很简单, 一个二维数组存储当前游戏的方块地图, 用 bool 即可, true 表示当前块被填充, false 表示没有. 然后, 抽一个 “形状” 类, 形状表示当前玩家正在操作的一个形状, 例如方块, 直线…

Pycharm如何打断点进行调试?

断点调试&#xff0c;是编写程序中一个很重要的步骤&#xff0c;有些简单的程序使用print语句就可看出问题&#xff0c;而比较复杂的程序&#xff0c;函数和变量较多的情况下&#xff0c;这时候就需要打断点了&#xff0c;更容易定位问题。 一、添加断点 在代码的行标前面&…