C++ 运算符重载

目录

前言

算术运算符重载

加号运算符

位运算符重载

左移运算符

自增自减运算符重载

前置++自增运算符

后置++自增运算符

赋值运算符重载

等号赋值运算符重

关系运算符重载

相等

不等

函数调用运算符重载

总结


前言

在C++中,运算符重载是一种强大的特性,允许您重新定义已定义的运算符,以便它们适用于用户自定义类型。通过运算符重载,您可以编写自定义类型的运算符行为,使得用户自定义类型的对象可以像内置类型一样进行运算。

算术运算符重载

  • +:执行加法操作,通常用于数字或字符串的拼接。
  • -:执行减法操作,通常用于数字之间的减法运算。
  • *:执行乘法操作,通常用于数字之间的乘法运算。
  • /:执行除法操作,通常用于数字之间的除法运算。

通过重载这些算术运算符,您可以定义自定义类型对象之间的加减乘除操作。这使得用户自定义类型的对象可以像内置类型一样进行算术运算。

加号运算符

cpp 复制代码
#include <iostream>
using namespace std;

class Person {
public:
	Person() {
	
	};
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	// 成员函数实现 + 号运算符重载
	Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}

public:
	int m_A;
	int m_B;
};

// 全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

// 运算符重载 可以发生函数重载,就是可以根据参数的不同去分别调用不同的operator运算符重载函数
Person operator+(const Person& p2, int val)
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {

	Person p1(10, 10);
	Person p2(20, 20);

	// 成员函数方式
	Person p3 = p2 + p1;  // 相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl; // 30 30


	Person p4 = p3 + 10; // 相当于 operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl; // 40 40

}

int main() {

	test();

	return 0;
}

位运算符重载

  • &:按位与运算符,对两个操作数逐位进行与运算。
  • |:按位或运算符,对两个操作数逐位进行或运算。
  • ^:按位异或运算符,对两个操作数逐位进行异或运算。
  • ~:按位取反运算符,对操作数逐位取反。
  • <<:左移运算符,将操作数的二进制位向左移动指定的位数。
  • >>:右移运算符,将操作数的二进制位向右移动指定的位数。

通过重载位运算符,您可以定义自定义类型对象之间的按位操作。这使得自定义类型的对象可以进行位级运算。

左移运算符

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>

/* 作用:可以输出自定义数据类型 */

class Person {
	friend ostream& operator<<(ostream& out, Person& p); // 全局函数做Person类的友元,可以访问类的私有成员

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	//成员函数 实现不了  p << cout 不是我们想要的效果
	//void operator<<(Person& p){
	//}

private:
	int m_A;
	int m_B;
};

//全局函数实现左移重载
//ostream对象只能有一个,故使用 &引用接收,不会创建新对象
ostream& operator<<(ostream& out, Person& p) { 
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out; // 需返回cout引用,达到链式调用的效果
}

void test() {

	Person p1(10, 20);

	cout << p1 << "hello world" << endl; // a:10 b:20hello world  链式编程  
}

int main() {

	test(); // 重载左移运算符配合友元可以实现输出自定义数据类型

	return 0;
}

自增自减运算符重载

通过重载自增和自减运算符,您可以定义自定义类型对象的自增和自减操作。这允许您使用++--来递增或递减对象的值。

前置++自增运算符

前置++,必须返回引用,不能返回值,因为要对同一原对象操作

解释一下 test01函数中的 **cout << ++myInt << endl。**首先,递增运算符的重载函数++myInt会被执行,将myInt的值加1,并返回递增后的对象引用。然后,左移运算符的重载函数operator<<会接受递增后的对象作为参数,并将其输出到标准输出流cout中。

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>


class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);

public:
	MyInteger() {
		m_Num = 0;
	}
	//前置++,必须返回引用,不能返回值,因为要对同一原对象操作
	MyInteger& operator++() { 		//先++
		this->m_Num++;
		//再返回
		return *this;
	}

private:
	int m_Num;
};

/* 左移重载 */
ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}


// 前置++ 先++ 再返回
void test01() {
	class MyInteger myInt;
	cout << ++myInt << endl; // 1 递增重载、左移重载
	cout << myInt << endl; // 1 左移重载
}

int main() {

	test01();

	return 0;
}

后置++自增运算符

后置++, 只能返回值,因为创建的是临时对象,函数销毁,引用就不对了

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>

/*
	前置递增返回引用,后置递增返回值
*/

class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);

public:
	MyInteger() {
		m_Num = 0;
	}

	//后置++, 只能返回值,因为创建的是临时对象,函数销毁,引用就不对了
	MyInteger operator++(int) { // int 代表后置++ 
		//先返回
		MyInteger temp = *this; // 记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		this->m_Num++;
		return temp;  // 此时返回的还是++前的 0
	}

private:
	int m_Num;
};

/* 左移重载 */
ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}


//后置++ 先返回 再++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl; // 0
	cout << myInt << endl; // 1
}

int main() {

	test02();

	return 0;
}

赋值运算符重载

  • =:将右操作数的值赋给左操作数。
  • +=:将左操作数与右操作数相加,并将结果赋给左操作数。
  • -=:将左操作数与右操作数相减,并将结果赋给左操作数。
  • *=:将左操作数与右操作数相乘,并将结果赋给左操作数。
  • /=:将左操作数与右操作数相除,并将结果赋给左操作数。

通过重载赋值运算符,您可以定义自定义类型的对象如何进行赋值操作。这使得对象可以通过使用等号和其他赋值运算符来进行赋值。

等号赋值运算符重

c++编译器至少给一个类添加4个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 operator=, 对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

如果是浅拷贝,不做赋值重载深拷贝,那么每个类对象都会执行释放,但已释放的空间二次释放就会报错

cpp 复制代码
class Person
{
public:

	Person(int age)
	{
		// 将年龄数据开辟到堆区
		m_Age = new int(age);
	}

	// 重载赋值运算符 返回自身引用
	Person& operator=(Person &p)
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		// 编译器提供的代码是浅拷贝
		//m_Age = p.m_Age;

		// 提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age); // 各自指向各自的空间

		//返回自身
		return *this;
	}


	~Person() // 析构函数
	{
		if (m_Age != NULL)
		{
			delete m_Age; // 如果是浅拷贝,不做赋值重载深拷贝,那么每个类对象都会执行释放,同一个堆空间二次释放就会报错
			m_Age = NULL;
		}
	}

	//年龄的指针
	int *m_Age;

};


void test01()
{
	Person p1(18);
	Person p2(20);
	Person p3(30);
	p3 = p2 = p1; // 连续赋值操作,前提必须返回自身引用

	cout << "p1的年龄为:" << *p1.m_Age << endl; // 18
	cout << "p2的年龄为:" << *p2.m_Age << endl; // 18
	cout << "p3的年龄为:" << *p3.m_Age << endl; // 18
}

int main() {

	test01();

	//int a = 10;
	//int b = 20;
	//int c = 30;

	//c = b = a;
	//cout << "a = " << a << endl;
	//cout << "b = " << b << endl;
	//cout << "c = " << c << endl;

	return 0;
}

关系运算符重载

  • ==:检查两个值是否相等,返回布尔值。
  • !=:检查两个值是否不相等,返回布尔值。
  • >:检查左操作数是否大于右操作数,返回布尔值。
  • <:检查左操作数是否小于右操作数,返回布尔值。
  • >=:检查左操作数是否大于或等于右操作数,返回布尔值。
  • <=:检查左操作数是否小于或等于右操作数,返回布尔值。

通过重载这些关系运算符,您可以定义自定义类型对象之间的比较操作。这使得您可以使用关系运算符来比较自定义类型的对象,并根据需要定义其比较规则。

相等

cpp 复制代码
class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person & p) // 关系重载,相等
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

};

void test01()
{
	//int a = 0;
	//int b = 0;

	Person a("孙悟空", 18);
	Person b("孙悟空", 18);

	if (a == b)
	{
		cout << "a和b相等" << endl; // a和b相等
	}
	else
	{
		cout << "a和b不相等" << endl;
	}

}


int main() {

	test01();

	return 0;
}

不等

cpp 复制代码
#include <iostream>
using namespace std;
#include <string>

/* 重载关系运算符,可以让两个自定义类型对象进行对比操作 */

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator!=(Person & p) // 关系重载,不等
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	//int a = 0;
	//int b = 0;

	Person a("孙悟空", 18);
	Person b("孙悟空", 18);

	if (a != b)
	{
		cout << "a和b不相等" << endl;
	}
	else
	{
		cout << "a和b相等" << endl; // a和b相等
	}
}


int main() {

	test01();

	return 0;
}

函数调用运算符重载

  • 函数调用运算符 () 也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
cpp 复制代码
class MyPrint
{
public:
	void operator()(string text) // 函数调用运算符重载函数,注意是在 operator后加 (),当前是打印值
	{
		cout << text << endl;
	}

};
void test01()
{
	//重载的()操作符 也称为仿函数
	MyPrint myFunc;
	myFunc("hello world"); // hello world
}


class MyAdd
{
public:
	int operator()(int v1, int v2) // 函数调用运算符重载函数,当前是返回值
	{
		return v1 + v2;
	}
};

void test02()
{
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl; // 20

	// 匿名对象调用  MyAdd()是匿名对象,执行完当前行就会销毁,MyAdd()(100, 100)是匿名对象调用重载函数
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main() {

	test01();
	test02();
	
	return 0;
}

总结

需要注意的是,虽然运算符重载提供了强大的功能,但滥用它可能会导致代码难以理解和维护。因此,对于每个运算符,都需要仔细考虑是否有必要进行重载,以及如何遵循最佳的代码设计和风格约定。

相关推荐
DARLING Zero two♡21 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
7年老菜鸡22 分钟前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Gu Gu Study23 分钟前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
Ni-Guvara31 分钟前
函数对象笔记
c++·算法
似霰35 分钟前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
芊寻(嵌入式)1 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
獨枭1 小时前
C++ 项目中使用 .dll 和 .def 文件的操作指南
c++
霁月风1 小时前
设计模式——观察者模式
c++·观察者模式·设计模式
橘色的喵1 小时前
C++编程:避免因编译优化引发的多线程死锁问题
c++·多线程·memory·死锁·内存屏障·内存栅栏·memory barrier
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript