C++ 运算符重载

 个人主页:Jason_from_China-优快云博客

所属栏目:C++系统性学习_Jason_from_China的博客-优快云博客

所属栏目:C++知识点的补充_Jason_from_China的博客-优快云博客

 运算符重载

概念概述

  • 当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
  • 运算符重载是具有特殊名字的函数,他的名字是由 operator 和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
  • 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
  • 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
  • 不能通过连接语法中没有的符号来创建新的操作符:比如 operator@。
  • .*   ::   sizeof   ?:   . 注意以上 5 个运算符不能重载。(选择题里面常考,大家要记一下)
  • 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y)。
  • 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如 Date 类重载 operator - 就有意义,但是重载 operator * 就没有意义(因为:日期之间的乘法运算在现实生活中没有一个直观且被广泛接受的含义。不像减法运算可以表示两个日期之间的时间间隔,乘法运算很难找到一个符合日期概念的自然解释)。
  • 重载 ++ 运算符时,有前置 ++ 和后置 ++,运算符重载函数名都是 operator++,无法很好的区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参,跟前置 ++ 构成函数重载,方便区分。
  • 重载 <<(流插入) 和 >>(流提取) 时,需要重载为全局函数,因为重载为成员函数,this 指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象 <<cout,不符合使用习惯和可读性。重载为全局函数把 ostream/istream 放到第一个形参位置就可以了,第二个形参位置当类类型对象。

一元二元三元运算符:

一、一元运算符

 
  1. 算术运算符:
    • 正号(+):例如+a,一般对数值类型起作用,通常意义不大,因为数值默认就是正的。
    • 负号(-):如-b,用于取负值。
  2. 逻辑运算符:
    • 逻辑非(!):例如!c,对布尔值进行取反操作,如果操作数为 true,则结果为 false;如果操作数为 false,则结果为 true
  3. 自增自减运算符:
    • 前置自增(++a):先将变量的值加 1,然后再使用变量的值。
    • 后置自增(a++):先使用变量的值,然后再将变量的值加 1。
    • 前置自减(--a):先将变量的值减 1,然后再使用变量的值。
    • 后置自减(a--):先使用变量的值,然后再将变量的值减 1。
  4. 地址运算符:
    • 取地址符(&):例如&d,用于获取变量的内存地址。
  5. 间接寻址运算符:
    • 解引用符(*):如果有一个指针变量 p*p 用于访问指针所指向的对象。
 

二、二元运算符

 
  1. 算术运算符:
    • 加法(+):如 a + b,用于数值相加或字符串连接(对于 C++ 中的 std::string 对象)。
    • 减法(-):如 a - b,用于数值相减。
    • 乘法(*):如 a * b,用于数值相乘。
    • 除法(/):如 a / b,用于数值相除。
    • 取余(%):如 a % b,用于求整数除法的余数。
  2. 位运算符:
    • 按位与(&):如 a & b,对两个操作数的每一位进行与操作。
    • 按位或(|):如 a | b,对两个操作数的每一位进行或操作。
    • 按位异或(^):如 a ^ b,对两个操作数的每一位进行异或操作。
    • 左移(<<):如 a << b,将 a 的二进制表示向左移动 b 位。
    • 右移(>>):如 a >> b,将 a 的二进制表示向右移动 b 位。
  3. 关系运算符:
    • 等于(==):如 a == b,判断两个操作数是否相等。
    • 不等于(!=):如 a!= b,判断两个操作数是否不相等。
    • 大于(>):如 a > b,判断 a 是否大于 b
    • 小于(<):如 a < b,判断 a 是否小于 b
    • 大于等于(>=):如 a >= b,判断 a 是否大于等于 b
    • 小于等于(<=):如 a <= b,判断 a 是否小于等于 b
  4. 逻辑运算符:
    • 逻辑与(&&):如 a && b,当且仅当两个操作数都为 true 时,结果为 true
    • 逻辑或(||):如 a || b,当至少一个操作数为 true 时,结果为 true
  5. 赋值运算符:
    • 简单赋值(=):如 a = b,将 b 的值赋给 a
    • 复合赋值(如 +=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=):例如 a += b 相当于 a = a + b
 

三、三元运算符(条件运算符)

 

条件运算符(?:),例如 a? b : c,如果 a 为 true,则结果为 b;如果 a 为 false,则结果为 c

内置类型和自定义类型

内置类型(简单类型):

一般情况下系统对于内置类型,直接就有对应的指令,会自动识别进行比较,核心就是比较,这里就是i和j直接进行比较 存到ret里面返回

自定义类型(按照你自己的比较方式,按照自己的需求,进行比较):

1,自定义类型不能直接转换成指令,就不能直接进行比较。尤其是像日期类里面的,年 月 日的比较,更是无法直接进行比较,你是需要返回更大的日期,还是更小的,你是需要加减,多少进位一次,这些都不是编译器决定的,是右你自己决定的。编译器不能直接进行识别。

2,内置类型比较简单,可以直接转换识别,但是自定义类型就不是

3,为了可以自己对自定义类型进行比较,语法结构就是:operator ➕运算符

自定义类型运算符重载的使用

原因:

日期类的比较日期,这里会存在一个问题,就是类外面无法访问私有变量,所以这里我们有三种解决办法

  • get函数
  • 重载为类的成员函数
  • 友元函数

这里我们以重载为成员函数为例子进行举例,至于经常用到的友元函数,类的第三章我们会讲到:

这里还有一个关键点,我们直接上代码(这里发现报错)

  

原因:可以放到类里面,重载为成员函数,但是直接拷贝放到类里面,会导致报错,因为隐藏了this指针,隐藏的this指针会指向第一个参数

  

解决:

但是此时调用,从全局调用,变成了成员函数的调用,调用方式发生了改变

此时d1传给了this,d2传给了d

试验一下 发现没有问题

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给
	Date(Date& d);
	void print();
	bool operator<(const Date& d);

	//比较日期大小(重载为成员函数)
private:
	int _year;
	int _month;
	int _day;
};

//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{
	//比较年
	if (this->_year < d._year)
	{
		return true;
	}
	else
	{
		if (this->_year == d._year && this->_month < d._month)//比较月
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日
		{
			return true;
		}
	}
	return false;
}



//.cpp测试文件
//运算符重载,比较大小
int main()
{
	//构造和拷贝构造函数的使用
	Date d1(1000, 2, 1);
	d1.print();
	Date d2 = d1;
	d2.print();

	//比较年月日
	Date d3(999, 1, 1);
	bool ret1 = d1.operator<(d3);
	cout << ret1 << endl;

	Date d4(1100, 1, 1);
	bool ret2 = d1.operator<(d4);
	cout << ret2 << endl;
	return 0;
}

重载成员函数至少有一个类类型的形参

前置++和后置++的区分

在C++里面,d++和++d,在汇编层,其实都是一样的,都是,operater++,所以是无法是分辨的

所以我们需要在书写的时候做出分辨

这里重载的函数,函数名是一样的

前置++,和后置++,运算符的复用

前置++,直接进行复用

后置++

1,保存之前的数值(拷贝下来)

2,然后进行++

3,最后返回++之前的数值

总结

注意,

在 C++ 中,后置 ++ 运算符重载函数的参数必须是 int 类型,这是 C++ 语言的规定,不能使用 double 或其他类型。

这样规定的原因主要是为了保持语言的一致性和可识别性。编译器通过参数为 int 来区分前置 ++ 和后置 ++ 的重载版本。如果允许使用其他类型,会使编译器难以确定到底是哪种 ++ 操作,增加了语言的复杂性和不确定性。

传递 int 类型参数通常只是作为一个占位符,实际在函数实现中一般不会用到这个参数的值,它的存在仅仅是为了满足语法要求以区分前置和后置自增操作。所以一般情况下,都是默认传递 int 类型参数,而不是 0 或者 1 这样的特定值,并且也不能传递 double 等其他类型。

代码的实现:

这里声明一下,上面图片的讲解的代码,是实现+=的,所以可以直接使用this+=1,这里并没有实现+=的函数重载,所以这里我们就会跑到对象里面实现day的+1

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给
	Date(Date& d);
	void print();
	bool operator<(const Date& d);
	Date& operator++();
	Date& operator++(int);
	//比较日期大小(重载为成员函数)
private:
	int _year;
	int _month;
	int _day;
};


//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{
	//比较年
	if (this->_year < d._year)
	{
		return true;
	}
	else
	{
		if (this->_year == d._year && this->_month < d._month)//比较月
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日
		{
			return true;
		}
	}
	return false;
}

//前置++
Date& Date::operator++()
{
	this->_day += 1;
	return *this;
}
//后置++
Date& Date::operator++(int)
{
	Date tmp = *this;
	this->_day += 1;
	return tmp;
}


//.cpp测试文件
//运算符重载,比较大小
int main()
{
	//构造和拷贝构造函数的使用
	Date d1(1000, 2, 1);
	d1.print();
	Date d2 = d1;
	d2.print();

	//比较年月日
	Date d3(999, 1, 1);
	bool ret1 = d1.operator<(d3);
	cout << ret1 << endl;

	Date d4(1100, 1, 1);
	bool ret2 = d1.operator<(d4);
	cout << ret2 << endl;

	//前置++和后置++
	cout << "前置++和后置++" << endl;
	d4.print();//1100 ,1,1
	
	d4.operator++();
	d4.print();

	Date ret = d4.operator++(1);
	ret.print();
	d4.print();

	return 0;
}

<think>好的,我现在要帮用户介绍C++运算符重载。首先,我需要回忆一下运算符重载的基本概念。运算符重载允许用户自定义类型使用C++内置的运算符,比如+、-、*等,使得代码更直观和易读。例如,可以让两个对象直接相加,而不必调用特定的函数。 接下来,我应该考虑用户可能的背景。用户可能是刚开始学习C++,或者已经有一定基础但想深入了解运算符重载。因此,解释需要清晰,从基础开始,逐步深入。可能需要先说明什么是运算符重载,为什么需要它,然后讲解如何实现,包括语法和注意事项。 然后,运算符重载的语法是关键部分。需要详细说明如何定义运算符重载函数,包括成员函数和非成员函数(友元函数)的区别。例如,重载+运算符时,成员函数通常只需要一个参数,因为左操作数是对象本身,而非成员函数需要两个参数。同时,要提到一些运算符必须作为成员函数重载,比如赋值运算符=。 例子是帮助理解的重要部分。我应该举一个简单的例子,比如为自定义的Vector类重载+运算符,这样用户可以看到具体的实现方式。同时,需要区分成员函数和非成员函数的例子,说明它们的不同应用场景。 注意事项和常见问题也不能遗漏。比如不能重载运算符(如.、::、sizeof等),以及重载时保持运算符的原有语义,避免滥用导致代码难以理解。此外,还需要提到运算符的返回类型和参数类型的选择,比如是否返回引用或值,参数的const修饰等。 用户可能还会有疑问,比如运算符重载的使用场景,或者何时应该使用成员函数而非友元函数。因此,在解释过程中需要涵盖这些方面,可能的话,给出一些最佳实践的建议。 最后,总结运算符重载的优点和需要注意的地方,帮助用户全面理解。确保语言通俗易懂,避免过于专业的术语,必要时进行类比,让用户更容易接受。</think>### C++运算符重载详解 #### 1. 什么是运算符重载运算符重载(Operator Overloading)是C++中的一种特性,允许开发者**为自定义类型(如类或结构体)重新定义运算符的行为**。通过重载运算符,可以让对象像内置类型(如`int`、`double`)一样使用`+`、`-`、`*`、`==`等运算符,提升代码可读性和简洁性。 --- #### 2. 如何实现运算符重载运算符重载通过**成员函数**或**全局函数**实现,语法形式为: $$ \text{返回类型 operator 运算符(参数列表)} $$ ##### 示例1:成员函数重载`+` ```cpp class Vector { public: int x, y; // 成员函数重载 + Vector operator+(const Vector& other) { return {x + other.x, y + other.y}; } }; // 使用 Vector v1{1, 2}, v2{3, 4}; Vector v3 = v1 + v2; // 等价于 v1.operator+(v2) ``` ##### 示例2:全局函数重载`<<`(输出流) ```cpp class Student { public: string name; int age; }; // 全局函数重载 << ostream& operator<<(ostream& os, const Student& s) { os << "Name: " << s.name << ", Age: " << s.age; return os; } // 使用 Student s{"Alice", 20}; cout << s; // 输出: Name: Alice, Age: 20 ``` --- #### 3. 常见运算符重载规则 | 运算符类型 | 可重载性 | 示例 | |------------------|----------------|----------------------| | 算术运算符 | `+`, `-`, `*` | `a + b` | | 关系运算符 | `==`, `>`, `<` | `a == b` | | 赋值运算符 | `=`, `+=` | `a = b` | | 下标运算符 | `[]` | `a[0]` | | 函数调用运算符 | `()` | `a(1, 2)` | | **不可重载运算符** | `.`、`::`、`?:`、`sizeof` | 无法重载 | --- #### 4. 关键注意事项 1. **参数限制**: - 至少一个操作数是用户自定义类型(不能全为内置类型)。 - 不能修改运算符的优先级或结合性。 2. **成员函数 vs 全局函数**: - 赋值运算符(`=`)、下标运算符(`[]`)、函数调用运算符(`()`)**必须**作为成员函数重载。 - 输入输出运算符(`>>`、`<<`)通常作为全局函数。 3. **保持语义一致性**: - 例如重载`+`时,不应修改操作数本身,而应返回新对象。 --- #### 5. 完整示例:复数类重载 ```cpp class Complex { private: double real, imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} // 成员函数重载 + Complex operator+(const Complex& other) { return Complex(real + other.real, imag + other.imag); } // 全局友元函数重载 << friend ostream& operator<<(ostream& os, const Complex& c); }; ostream& operator<<(ostream& os, const Complex& c) { os << c.real << " + " << c.imag << "i"; return os; } // 使用 Complex c1(2, 3), c2(4, 5); Complex c3 = c1 + c2; cout << c3; // 输出: 6 + 8i ``` --- #### 6. 应用场景 - 数学计算(向量、矩阵、复数) - 字符串操作(`str1 + str2`) - 容器类(自定义数组、链表) --- #### 7. 总结 **优点**:提升代码直观性,使自定义类型操作更自然。 **注意**:避免过度使用或违反直觉的重载(例如用`+`实现减法)。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值