【C++修行之道】类和对象(四)运算符重载

目录

一、 运算符重载

函数重载和运算符重载有什么关系?

二、.*运算符的作用

三、运算符重载的正常使用

四、重载成成员函数

五、赋值运算符重载

1.赋值运算符重载格式

传值返回和引用返回

有没有办法不生成拷贝?

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

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

六、前置++和后置++重载 


一、 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)。

注意:

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

函数重载和运算符重载有什么关系?

他们之间各论各的,没有关系

运算符重载:让自定义类型可以使用运算符,并且控制运算符的行为,增强可读性

函数重载:可以让函数名相同,参数不同的函数存在。

多个同一运算符的重载可以构成函数重载

 

二、.*运算符的作用

class OB
{
public:
	void func()
	{
		cout << "void func()" << endl;
	}
};

typedef void(OB::* Ptrfunc)();// 成员函数指针类型

int main()
{
	// 函数指针
	void (*ptr)();

	Ptrfunc fp = &OB::func;// 定义成员函数指针p指向函数func
	// 成员函数规定要加&才能取到函数指针
	OB temp;// 定义ob类对象temp


	(temp.*fp)();// 调用成员函数

	return 0;
}

typedef void(OB::* Ptrfunc)(); // 成员函数指针类型
  • 使用 typedef 定义了一个名为 Ptrfunc 的类型,这个类型是指向OB类中无参数、无返回值的成员函数的指针类型。
  • OB::*的含义,它表示这是一个指向OB类成员函数的指针 Ptrfunc
Ptrfunc fp = &OB::func; // 定义成员函数指针fp指向函数func
  • 在C++中,成员函数与普通函数在内存中的表示和存储方式有所不同。成员函数不仅包含函数的代码,还隐含地包含了一个指向类对象的this指针,这使得成员函数能够访问和修改对象的状态。
  • 在语法上,&类名::成员函数名是用来获取成员函数地址的标准方式
  • 如果不使用&运算符,编译器可能会将OB::func解析为对成员函数的调用
  • 运算符在这里的作用是明确告诉编译器:“我要的是这个成员函数的地址,而不是执行这个函数”。这样,编译器就能正确地生成获取成员函数地址的代码,而不是尝试调用该函数。
(temp.*fp)(); // 调用成员函数
  • .*:这是一个特殊的成员访问运算符,用于通过对象实例和成员函数指针来调用成员函数。当你有一个指向成员函数的指针,并且想要在某个特定的对象上调用这个函数时,就需要使用这个运算符。 
  • 在temp对象上,通过成员函数指针fp来调用它所指向的成员函数

三、运算符重载的正常使用

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;

};

// 重载成全局, 无法访问私有成员, 怎么解决?
// 1.提供这些成员get和set
// 2.友元
// 3.重载成成员函数(一般重载成这种)
//

// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数 
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

// d1 - d2
// d1 + d2 无意义
// d1 * d2 无意义
// 一个类要重载哪些运算符是看需求, 看重载有没有价值和意义 

int main()
{
	Date d3(2024, 4, 14);
	Date d4(2024, 4, 15);

	// 显示调用(可以正常使用)
	operator==(d3, d4);

	// 直接写,转换调用,编译器会转换成operator==(d3, d4)
	d3 == d4;

	return 0;
}

四、重载成成员函数

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

	 d3.Func(d4);
	//bool Func(const Date& d)
	//{
	//	return this->_year == d._year
	//		&& this->_month == d._month
	//		&& this->_day == d._day;
	//}

	// d3.operator==(d4);
	bool operator==(const Date& d)
	{
		cout << "类中";
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
		// 隐藏了this指针
		/*return this->_year == d._year
			&& this->_month == d._month
			&& this->_day == d._day;*/
	}

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

// 如果全局和类中都有运算符重载函数,编译器会选择调用类里的
bool operator==(const Date& d1, const Date& d2)
{
	cout << "全局";
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d3(2024, 4, 14);
	Date d4(2024, 4, 15);

	// 显式调用
	d3.operator==(d4);

	// 转换调用 等价于d3.operator==(d4);
	d3 == d4;

	return 0;
}
  • 通过d3.operator==(d4)显式调用了类内的operator==函数。因为这里是直接通过对象d3来调用的,所以肯定是类内的版本被调用。
  • d3 == d4这种简洁的写法在C++中会被自动转换为对operator==的调用。当有多个版本的operator==可用时(如本例中的类内和全局版本),C++会根据一定的规则(如作用域和参数匹配)来选择调用哪一个。在这个例子中,由于类内的版本是成员函数,且其参数与全局版本相同,所以编译器会优先选择类内的版本。 

五、赋值运算符重载

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

int main()
{
	Date d1(2024, 4, 14);

	// 拷贝构造
	// 一个已经存在的对象,拷贝给另一个要创建初始化的对象
	Date d2(d1);
	Date d3 = d1;

	Date d4(2024, 5, 1);

	// 赋值拷贝/赋值重载
	// 一个已经存在的对象,拷贝赋值给另一个已经存在的对象
	d1 = d4;

	d1 = d2 = d4;

	return 0;
}
  •  Date& operator=(const Date& d):这个函数重载了赋值运算符(=),允许我们使用=来将一个Date对象的值赋给另一个已经存在的Date对象。函数中首先检查自赋值的情况(即确保赋值操作的左右两边不是同一个对象),然后复制右边的对象的年、月和日到左边的对象,并返回左边对象的引用,以支持连续赋值操作。
  • *this是对象本身,对象在main的作用域里创建,因此出main作用域才析构销毁。而出函数作用域不会销毁,所以此处才能return *this 

传值返回和引用返回

传值返回,返回的是对象的拷贝

引用返回,返回的是对象的别名

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

	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

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

	~Date()
	{
		cout << "~Date()" << endl;
		_year = -1;
		_month = -1;
		_day = -1;
	}

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

  • Date(const Date& d):这是一个拷贝构造函数,它接受一个Date对象的引用,并创建一个新的Date对象,其内容与传入的对象相同

有没有办法不生成拷贝?

使用引用返回

Date func()
{
	Date d(2024, 4, 14);
	return d;
}

int main()
{
	const Date& ref = func();
	ref.Print();

	return 0;
}

 在main函数中,首先通过调用func函数获取了一个对Date对象的常量引用ref。由于func返回的是一个临时对象,这个对象在表达式结束后就会被销毁。但是,由于ref是对这个临时对象的引用,所以这个临时对象的生命周期会被延长,直到ref的生命周期结束。这是C++11引入的引用折叠和生命周期延长规则的结果。

Date& func()
{
	Date d(2024, 4, 14);
	//cout << &d << endl;
	return d;
}

int fx()
{
	int a = 1;
	int b = 2;
	int c = 3;

	return a + b + c;
}

int main()
{
	//Date& ref = func();
	const Date& ref = func();
	cout << &ref << endl;
	fx();

	return 0;
}

在main函数中,通过const Date& ref = func();获取了func函数返回的引用,并将其存储在常量引用ref中。由于func返回的是对局部变量的引用,这里的ref实际上引用了一个已经不存在的对象,因此这是不安全的 

Date& func()
{
	static Date d(2024, 4, 14);
	return d;
}
// 出了作用域,返回对象还在没有析构,那就可以用引用返回,减少拷贝
// a、返回对象生命周期到了,会析构,传值返回
// b、返回对象生命周期没到,不会析构,传引用返回

int main()
{
	const Date& ref = func();
	//ref.Print();

	return 0;
}
  • func函数返回一个对静态局部变量d的引用,该变量在函数第一次被调用时被初始化并在程序的整个生命周期内持续存在。由于d是静态的,它不会在func函数返回后被销毁,因此可以安全地返回它的引用。 

  • 在main函数中,调用了func函数并将返回的引用赋值给const Date& ref。由于返回的是引用,因此没有发生任何拷贝操作,这是效率更高的做法。

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

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

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
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;
}

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

六、前置++和后置++重载 

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


void test02()
{
	Date d1(2024, 4, 14);
	Date d2 = ++d1;
	d1.Print();
	d2.Print();

	Date d3 = d1++;
	d1.Print();
	d3.Print();

	/*d1.operator++(1);
	d1.operator++(100);
	d1.operator++(0);
	d1.Print();*/
}

int main()
{

	test02();

	return 0;
}

今天就先到这了!!!

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!

你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

思维导图-vb.net开发带进度条的复制文件夹功能c#复制文件夹

你们谁写代码会用流程图来做计划&#xff0c;或者写项目总结报告&#xff1f; .net带进度条复制文件夹 方案 列出所有子文件夹&#xff0c;再创建&#xff0c;复制文件 大文件可以单独做进度条 缺点&#xff1a;设计会更复杂 直接…

让你的博客实现负载均衡

零、缘起 有时候博客突然挂了&#xff0c;发现服务器厂商出了问题&#xff0c;很忧伤&#xff0c;我正在写着或查阅自家博客那种不可xx的内容。这时想着&#xff0c;如果这个博客有负载均衡就好了&#xff0c;空了想着实现下。 一分钟了解负载均衡的一切 选择第二种【反向代…

衡量网络性能的指标

带宽 测速&#xff0c;下载速度一般是MB&#xff0c;运营商用的是b&#xff0c;之间有差别&#xff0c;100M带宽就是100M b 100个人访问同一个服务器&#xff0c;那么这个服务器的并发连接数就是100&#xff0c;有上限&#xff0c;受到性能的限制&#xff0c;当前面连接好多了…

【数据结构与算法 经典例题】链表的回文结构(图文详解)

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法 经典例题》C语言 期待您的关注 ​ 目录 一、问题描述 二、解题思路 三、C语言代码实现 一、问题描述 二、解…

链表反转--理解链表指针的基本操作

链表反转--理解链表指针的基本操作 链表反转的方法--主要是理解链表指针链表心得类节点是对象和指针区别&#xff1a; 链表反转的方法–主要是理解链表指针 根据值创建新列表 用一个链表指针代替整个新链表 两个链表的赋值 递归求解反向链表 用一个链表代替前后链表数…

解决使用gets(getchar)函数无法输入字符(字符串)和scanf_s函数显示缺少“scanf_s”整型参数的问题

一.函数介绍 gets函数&#xff1a; 该函数就是读取字符串&#xff0c;遇到空格不会停止&#xff0c;直到遇到换行字符&#xff0c;但是也会读取最后的换行字符&#xff08;这也就是我在写代码的时候遇到的一个问题&#xff09; getchar函数&#xff1a; 和gets函数类似&#x…

初识JAVA中的包装类,时间复杂度及空间复杂度

目录&#xff1a; 一.包装类 二.时间复杂度 三.空间复杂度 一.包装类&#xff1a; 在Java中&#xff0c;由于基本类型不是继承自Object&#xff0c;为了在泛型代码中可以支持基本类型&#xff0c;Java 给每个基本类型都对应了一个包装类型。 1 基本数据类型和对应的包装类 &am…

数字塔问题

#include<iostream> using namespace std; //从下向上得到最优值 void dtower(int a[][100],int s[][100],int n) {for(int in; i>1; i--){for(int j1; j<i; j){if(in)s[i][j]a[i][j];else{int ts[i1][j];if(t<s[i1][j1])ts[i1][j1];s[i][j]a[i][j]t;}}} } void…

MapReduce复习

一、MapReduce概述 1.定义 是分布式运算框架 MapReduce&#xff1a;用户处理业务相关代码自身的默认代码 2.优势劣势 优点&#xff1a; 1&#xff09;.易于编程。用户只关心业务逻辑&#xff0c;实现框架的接口。 2&#xff09;.良好的扩展性。可以动态增加服务器&#…

找不到steam_api64.dll,无法继续执行的原因及解决方法

电脑已经成为我们生活中不可或缺的一部分。然而&#xff0c;在使用电脑的过程中&#xff0c;我们经常会遇到一些常见的问题&#xff0c;其中之一就是找不到某个特定的动态链接库文件&#xff0c;比如steamapi64.dll。这个问题可能会导致某些应用程序无法正常运行&#xff0c;给…

通过DirectML和ONNXRuntime运行Phi-3模型

更多精彩内容&#xff0c;欢迎关注我的公众号“ONE生产力”&#xff01; 上篇我们讲到通过Intel Core Ultra系列处理器内置的NPU加速运行Phi-3模型&#xff0c;有朋友评论说他没有Intel处理器是否有什么办法加速Phi-3模型。通常&#xff0c;使用GPU特别是NVIDA的GPU加速AI模型…

LeetCode746使用最小花费爬楼梯

题目描述 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。 解析 动态…

【数据结构】穿梭在二叉树的时间隧道:顺序存储的实现

专栏引入 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我想让大家…

容器中运行ip addr提示bash: ip: command not found【笔记】

容器中运行ip addr提示bash: ip: command not found 原因没有安装ip命令。 rootdocker-desktop:/# ip addr bash: ip: command not found rootdocker-desktop:/# apt-get install -y iproute2

【WP】猿人学12_入门级js

https://match.yuanrenxue.cn/match/1 调试分析 打开控制台出现无限debugger&#xff0c;手动取消断点应对 手动点击各页面查看发包 m参数格式 加密数据时间戳 时间戳 时间: 2024-06-06 01:39:05时间戳: 1717609145我目前的时间是2024年6月4日21:56:22往前几分钟&#xf…

Audio PsyChat:web端语音心理咨询系统

这是一个在服务器本地运行的web语音心理咨询系统&#xff0c;咨询系统内核使用PsyChat&#xff0c;我们为其制作了Web前端&#xff0c;并拼接了ASR和TTS组件&#xff0c;使局域网内用户可以通过单纯的语音进行交互。其中ASR和TTS组件使用PaddleSpeech API。 使用 使用单卡3090…

混剪素材库有哪些?分享7个高质量混剪视频素材网站

作为自媒体创作者&#xff0c;我们经常需要高质量的混剪视频素材来吸引观众。今天&#xff0c;我将为大家介绍几个优质的视频素材网站&#xff0c;确保您的短视频制作既高效又充满创意。 蛙学府素材网 首推蛙学府素材网&#xff0c;这个平台真是创作者的福音。无论是短视频素材…

LLM的基础模型3:Transformer变种

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

Redis页面优化

文章目录 1.Redis页面缓存1.思路分析2.首先记录一下目前访问商品列表页的QPS1.线程组配置10000次请求2.请求配置3.开始压测1.压测第一次 平均QPS为6122.压测第二次 平均QPS为6153.压测第三次 平均QPS为617 3.然后记录一下访问商品详情页的QPS1.线程组配置10000次请求2.请求配置…

数据泄露怎么防?企业文件加密来帮忙

在数字化时代&#xff0c;数据泄露事件频发&#xff0c;给企业带来了前所未有的安全挑战。企业的核心数据、商业机密、客户信息等一旦泄露&#xff0c;不仅会导致经济损失&#xff0c;还会损害企业的声誉和客户信任。因此&#xff0c;如何有效防止数据泄露&#xff0c;成为了企…