C++:运算符重载和“const”成员

news2024/12/27 10:54:42

hello,各位小伙伴,本篇文章跟大家一起学习《C++:运算符重载》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !

文章目录

    • 赋值运算符重载
      • 1. 运算符重载
      • 2.赋值运算符重载
        • 第一个点
        • 第二个点:
        • 第三个点:
    • 前置++和后置++重载
    • const成员

赋值运算符重载

1. 运算符重载

在《C++:函数重载和引用》里有讲到函数中在,那么和运算符重载有什么不一样呢?

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

函数名字为:关键字 operator 后面接需要重载的运算符符号。
函数原型:返回值类型 operator 操作符(参数列表)

但是并不是所有符号都能重载,要注意:

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

举个例子:

class Date
{
public:
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 加const修饰是为了不让传参被改变
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month
			&& _day == d._day;
	}

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

int main()
{
	Date d1(2024, 5, 1);
	Date d2(d1);//拷贝构造
	cout << (d1 == d2) << endl;

	return 0;
}

输出结果是:1,也就是说d1和d2是相等的

// 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month
			&& _day == d._day;
	}

2.赋值运算符重载

第一个点

先来看格式,赋值运算符重载格式:

  • 参数类型:const T&,传递引用可以提高传参效率 //T是类名
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
Date& operator=(const Date& d)
 {
 if(this != &d)
       {
            _year = d._year;
            _month = d._month;
            _day = d._day;
       }
        
        return *this;
 }

1.先讲讲参数形式为什么是**const T&**和返回值类型:T&,在《C++:构造函数、析构函数、拷贝构造函数》讲到过在对象的传值传参时,会发生拷贝构造,返回临时对象时,也会发生拷贝构造,会降低效率。所以选择传引用传参和用引用做返回值

用引用做返回值还有一个原因是为了支持连续赋值,举个例子:

int main()
{
	Date d1(2024,5,1);
	Date d2(1,1,1);
	Date d3(1,1,1);
	
	d3 = d2 = d1;
	return 0;
}

d2 = d1时调用了函数Date& operator=(const Date& d),会返回一个值,这个值属于什么类型会影响d3,所以返回*this是为了将d2的值赋值给d3,从而实现了连续赋值

2.检测是否自己给自己赋值,自己给自己复制就没意思了,所以直接return *this

第二个点:

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

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

第三个点:

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

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

在这里会调用Time类的赋值运算符重载完成赋值

这里有小伙伴会说:既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。但是存在即合理,看以下例子:

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
			_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

这个是用类来实现栈,为什么程序会崩溃呢?
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

逐字节拷贝:

  1. 也就是说s2的_arry的地址指向了s1的_arry的地址
  2. 那么编译器为s2的_arry开辟的空间消失了
  3. 导致内存泄漏
  4. 在最后销毁时,将同一块空间释放了两次,程序崩溃

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

前置++和后置++重载

根据前面所讲,
函数名字为:关键字 operator 后面接需要重载的运算符符号。
函数原型:返回值类型 operator 操作符(参数列表)

那么怎么区分前置++和后置++重载的函数,来看:
在C++中,前置++和后置++运算符可以被重载为成员函数或友元函数。要区分重载的前置++和后置++函数,你需要注意两点:

  1. 函数参数列表:前置++和后置++函数的参数列表应该是不同的,以便编译器可以区分它们

  2. 函数的返回类型:前置++函数应该返回引用,而后置++函数应该返回值

下面是一个示例,展示了如何重载前置++和后置++运算符:

#include <iostream>

class Counter {
public:
    Counter() : count(0) {}

    // 前置++运算符重载
    Counter& operator++() {
        ++count;
        return *this;
    }

    // 后置++运算符重载
    Counter operator++(int) {
        Counter temp = *this;
        ++(*this);
        return temp;
    }

    void display() {
        std::cout << "Count: " << count << std::endl;
    }
private:
    int count;
};

int main() {
    Counter c1, c2;

    // 前置++
    ++c1;
    c1.display();  // 输出: Count: 1

    // 后置++
    c2++;
    c2.display();  // 输出: Count: 1

    return 0;
}

在这个示例中,前置++运算符被重载为成员函数operator++(),后置++运算符被重载为成员函数operator++(int)。前置++函数返回引用,而后置++函数返回值。

注意:C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

只不过这个const放的位置有点奇怪,因为在类的成员函数参数列表中this指针已经是固定在第一位的,只是看不到,所以要这么做:

class Date
{
public:
	void Print() const
	{
		cout<<_year<<"-"
		<<_month<<"-"
		<<_day<<endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

那么const修饰后的对象调用函数,又与普通的对象调用函数有什么不一样呢?
来看下列代码:

#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}

int main()
{
	Test();
	return 0;
}

运行结果:在这里插入图片描述
很明显,d1不能调用void Print() const,d2不能调用void Print()

问题又来了:

  1. const对象内可以调用其它的非const成员函数吗?
  2. 非const对象内可以调用其它的const成员函数吗?

总结:

  1. const 对象可以调用非 const 成员函数吗?

    • 是的,const 对象可以调用非 const 成员函数。因为 const 对象的调用不会改变对象的状态,只是限制了对成员变量的修改,所以它可以安全地调用非 const 成员函数。
  2. 非 const 对象可以调用 const 成员函数吗?

    • 是的,非 const 对象可以调用 const 成员函数。因为非 const 对象的调用不会限制对成员变量的修改,所以它可以安全地调用 const 成员函数。
  3. const 成员函数内可以调用其他的非 const 成员函数吗?

    • 是的,const 成员函数内可以调用其他的非 const 成员函数。因为 const 成员函数承诺不会修改对象的状态,但它可以通过调用其他非 const 成员函数来实现某些功能。
  4. 非 const 成员函数内可以调用其他的 const 成员函数吗?

    • 是的,非 const 成员函数内可以调用其他的 const 成员函数。因为非 const 成员函数没有限制对成员变量的修改,所以它可以安全地调用 const 成员函数。

总的来说,const 成员函数提供了对象只读的访问权限,并不限制调用其他成员函数的类型;而非 const 成员函数可以调用任何类型的成员函数。

在这个示例中,callNonConstFromConst()函数是一个const成员函数,它调用了非const成员函数nonConstFunc(),这是允许的。但是,如果你尝试在非const成员函数中调用const成员函数(例如callConstFromNonConst()),编译器将会报错。

简单理解就是:权限问题,const非const,权限放大,是肯定不行的;反过来权限缩小,是可以的。

好啦,这篇文章就到此结束了
所以你学会了吗?

好啦,本章对于《C++:运算符重载和“const”成员》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!

如你喜欢,点点赞就是对我的支持,感谢感谢!!!

请添加图片描述

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

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

相关文章

C++感受5-HelloWorld 函数版

借助 Hello (std::string_view XXX) 函数&#xff0c;快速向你心目中的女神问好。 视频 ffls08-HelloWorld-函数版 函数简述 函数用于封装程序中需要反复执行的事情。事情每一次执行时&#xff0c;所需要的初始数据的变化&#xff0c;由函数参数体现。 定义函数时所写的参数&…

小孩子不懂事,写着玩的

目录 Web攻防 特有漏洞 ASP安全 ASPX&#xff08;.NET&#xff09;安全 PHP安全 JavaWeb安全 JS&#xff0c;Node.js安全 Java安全 Python安全 通用漏洞 SQL注入 MySQL-root高权限读写注入 PostgreSQL-高权限读写注入 MSSQL-sa高权限读写执行注入 SQL注入体系 o…

虹科Pico汽车示波器 | 免拆诊断案例 | 2006 款林肯领航员车发动机怠速抖动

故障现象 一辆2006款林肯领航员车&#xff0c;搭载5.4 L发动机&#xff0c;累计行驶里程约为26万km。该车因发动机怠速抖动故障进厂维修&#xff0c;维修人员更换了火花塞、点火线圈及凸轮轴位置传感器&#xff0c;清洗了积炭和喷油器&#xff0c;故障依旧&#xff0c;于是向笔…

Linux系统硬盘读写慢,如何排查

若服务器硬盘读写慢&#xff0c;导致处理性能降低&#xff0c;用户响应慢&#xff08;例如&#xff1a;ssh登录操作文件&#xff0c;处理很慢说明磁盘很慢&#xff09; 1.查看硬盘是否繁忙 top 若值比较大&#xff0c;说明当前硬盘比较繁忙&#xff0c;有大量读写操作&#x…

PO框架【自动化测试】

对象&#xff1a;Tpshop商城 需求&#xff1a;更换头像 操作步骤&#xff1a; 个人信息–头像–上传图片–图片确认–确认保存 核心代码&#xff1a; # 进入frame框架[不熟] driver.switch_to.frame(driver.find_element_by_xpath(//*[id"layui-layer-iframe1"]))…

链式存储的特点与设计由来

简介 案例引入 相关术语 两种结构的区别与实现 注&#xff1a; 特点

迪拜Token2049展会圆满落幕,MVP成唯一MEMECOIN项目,闪耀全场!

近日&#xff0c;据多家媒体报道&#xff0c;于全球财富聚集地迪拜举行的全球性大型区块链会议TOKEN2049圆满落幕。来自全球的5000多家公司和100多个国家10000名参与者共同参会&#xff0c;讨论未来30年至50年关于区块链行业的宏大未来。 新晋MEMECOIN项目MAGA VP&#xff08;…

希捷HDD最新财报:销售同比下降11%,环比增长6%,4Q24前景看好

Seagate Technology Holdings plc公布了截至2024年3月29日的第三财季财务业绩。 “随着云需求改善、我们强大的运营纪律和价格执行&#xff0c;希捷3月季度的营收增长了6%&#xff0c;非GAAP每股收益较上一季度翻了一番多。这种组合为我们市场复苏时回归目标利润率奠定了基础。…

【C++】string常用函数总结及其模拟实现

目录 一、String的构造 二、String的大小和容量 三、String的字符串比较 四、string的访问 五、String的字符插入 六、string拼接字符串 七、string的删除 八、string的查找 九、string的分割 模拟实现 一、String的构造 string()&#xff1a;生成空字符串&#xff1…

Llama3 中文通用 Agent 微调模型来啦!(附手把手微调实战教程)

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 基于大家…

文本语音互相转换系统设计

title: 文本语音互相转换系统设计 date: 2024/4/24 21:26:15 updated: 2024/4/24 21:26:15 tags: 需求分析模块化设计性能优化系统安全智能化跨平台区块链 第一部分&#xff1a;导论 第一章&#xff1a;背景与意义 文本语音互相转换系统的定义与作用 文本语音互相转换系统是…

js进行数据移除性能比较(splice,map)

当使用 splice() 方法处理大量数据时&#xff0c;确实会遇到性能问题&#xff0c;因为它涉及到移动数组中的元素&#xff0c;导致操作的时间复杂度为 O(n)。对于大量数据&#xff0c;频繁的插入和删除可能会导致性能下降。 1、设置数组数据为10000&#xff0c;使用splice移除数…

linux——yum工具详解

yum是linux中自动解决软件包依赖关系的管理器 同时&#xff0c;yum也是一个rpm软件 这里使用yum install nginx安装nginx

前缀和 求数列的子序列的K倍区间

(直接截图比复制文字要好多了) 不会做的时候我去看了之前做的关于这道题目的笔记&#xff0c; &#xff08;Ak 1&#xff09;% k 1 &#xff08;Ak 1 Ak&#xff09;% k 1 只要发现了同余数的情况就说明有一个区间满足了题目的要求。 这个方法的精妙之处就在于前缀和包括了…

窗函数的选择

不同的窗函数实质上时对矩形窗进行了不同程度的加权得到的不同类型的窗函数。 将模拟角频率转换为了数字角频率 矩形窗旁瓣过大&#xff0c;两个频率的峰值相差较大&#xff0c;因此无法识别&#xff0c;可以使用旁瓣非常小的窗函数来进行分辨&#xff0c;只是想要达到相同的分…

目标检测——小麦穗头数据集

一、重要性及意义 小麦穗头检测在农业领域具有重要意义&#xff0c;主要体现在以下几个方面&#xff1a; 首先&#xff0c;小麦穗头检测可以帮助农民和植物科学家准确评估作物的健康状况和成熟度。通过对小麦穗部的形态特征进行测量和分析&#xff0c;可以及时发现作物生长过…

简单工厂、工厂方法、抽象工厂对比

简单工厂、工厂方法和抽象工厂是三种常见的工厂设计模式&#xff0c;它们在软件设计中各有其独特的应用场景和优缺点。因为三种设计模式都属于工厂模式&#xff0c;在实际应用中可能存在误用的场景&#xff0c;这里对其做下对比&#xff0c;以便更好的理解这三种设计模式。 简…

第四百七十七回

文章目录 1. 知识回顾2. 使用方法2.1 源码分析2.2 常用属性 3. 示例代码4. 内容总结 我们在上一章回中介绍了"Get包简介"相关的内容&#xff0c;本章回中将介绍GetMaterialApp组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 知识回顾 我们在上一章回中已经…

【HTML】页面引用Vue3和Element-Plus

在现代前端开发中&#xff0c;Vue 3 和 Element Plus 是非常受欢迎的技术。Vue 3 是一个用于构建用户界面的渐进式 JavaScript 框架&#xff0c;而 Element Plus 是一个基于 Vue 3 的组件库&#xff0c;提供了丰富的 UI 组件&#xff0c;帮助开发者快速构建高质量的前端应用。 …

【Java | 多线程】LockSupport 的使用和注意事项

了解一下 LockSupport LockSupport是一个类&#xff0c;位于java.util.concurrent.locks包中&#xff0c;提供了基本的线程同步机制。 LockSupport的主要作用是挂起和唤醒线程。它提供了两个主要的静态方法&#xff1a;park()和unpark()。 park()&#xff1a;用于挂起当前线…