运算符重载(Operator Overloading)无疑是C++最具特色的功能之一。它允许我们为自定义的类(class)赋予标准运算符(如 +, -, *, /, <<)新的含义,使代码更直观、更易读。
使用运算符重载能像操作普通整数一样,直接对两个“分数”或“矩阵”对象进行加法运算。
Fraction a(1, 2); // 分数 1/2
Fraction b(1, 4); // 分数 1/4
Fraction c = a + b; // 直接相加,而不是 a.add(b)
c.display(); // 输出 3/4
什么是运算符?
简单来说,运算符重载就是对已有的运算符进行重新定义,赋予其处理自定义数据类型的能力。
例如,表达式 a + b 在背后可能被编译器解释为 a.operator+(b) (成员函数调用) 或 operator+(a, b) (全局函数调用)。
为什么要使用运算符重载?
-
提高代码的可读性:使操作自定义类型的代码看起来就像在操作基本数据类型,更加自然和简洁。
-
增强代码的直观性:符合数学和逻辑上的直觉。例如,用 + 来连接两个字符串,用 << 将对象输出到控制台。
-
保持接口的一致性:可以像 STL(标准模板库)中的容器一样,使用 [] 来访问元素,使用 ++ 来迭代。
运算符重载的实质
运算符重载是对已有的运算符赋予多重含义
必要性:C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)
实现机制:将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。 编译系统对重载运算符的选择,遵循函数重载的选择原则。
规则与限制
- 除了类属关系运算符”.”、成员指针运算符”.* ”、作用域运算符”::”、三目运算符”?:”、”sizeof" 运算符以外,C++中的所有运算符都可以重载。
- 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。
- 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
- 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。
- 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。
- 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。
重载形式
- 重载为类成员函数。
- 重载为非成员函数(通常为友元函数)。
声明形式:
函数类型 operator 运算符(形参)
{
......
}
重载为类成员函数时 : 参数个数=原操作数个数-1 (后置++、--除外)
重载为友元函数时 : 参数个数=原操作数个数,且至少应该有一个自定义类型的形参。
对于双目运算符B
如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。
经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)
例如:将“+”、“-”运算重载为复数类的成员函数。
规则:实部和虚部分别相加减。
操作数:两个操作数都是复数类的对象。
示例代码
#include<iostream>
using namespace std;
class complex //复数类声明
{
public: //外部接口
complex(double r=0.0,double i=0.0){real=r;imag=i;}//构造函数
complex operator + (complex c2); //+重载为成员函数
complex operator - (complex c2); //-重载为成员函数
void display(); //输出复数
private: //私有数据成员
double real; //复数实部
double imag; //复数虚部
};
complex complex::operator +(complex c2) //重载函数实现
{
complex c;
c.real=c2.real+real;
c.imag=c2.imag+imag;
return complex(c.real,c.imag);
}
complex complex::operator -(complex c2) //重载函数实现
{
complex c;
c.real=real-c2.real;
c.imag=imag-c2.imag;
return complex(c.real,c.imag);
}
void complex::display()
{ cout << "(" << real << "," << imag << "i)" << endl; }
int main() //主函数
{ complex c1(5,4),c2(2,10),c3; //声明复数类的对象
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
c3=c1-c2; //使用重载运算符完成复数减法
cout<<"c3=c1-c2=";
c3.display();
c3=c1+c2; //使用重载运算符完成复数加法
cout<<"c3=c1+c2=";
c3.display();
}
运行结果:

运算符成员函数的设计
前置单目运算符
如果要重载 U 为类成员函数,使之能够实现表达式 U oprd,其中 oprd 为A类对象,则 U 应被重载为 A 类的成员函数,无形参。 经重载后, 表达式 U oprd 相当于 oprd.operator U()
例子:
#include <iostream>
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// 1. 前置 ++ 的重载 (成员函数,无参数)
// 返回引用以支持链式调用
Counter& operator++() {
++value; // 先增加
return *this; // 返回修改后的自身
}
void display() const {
std::cout << "Counter value: " << value << std::endl;
}
};
int main() {
Counter c1(10);
std::cout << "--- 前置 ++ 演示 ---" << std::endl;
std::cout << "初始状态: ";
c1.display();
Counter& c2 = ++c1; // 调用 c1.operator++()
std::cout << "++c1 之后 c1 的状态: ";
c1.display(); // 输出 11
std::cout << "赋值给 c2 后 c2 的状态: ";
c2.display(); // 输出 11
// 因为 c2 是引用,所以修改 c2 就是修改 c1
++c2;
std::cout << "++c2 之后 c1 的状态: ";
c1.display(); // 输出 12
}
-
行为: 前置 ++ 的标准行为是“先增加,后使用”。也就是说,它应该先修改对象自身的值,然后返回修改后对象的引用。
-
返回值: 返回引用 (ClassName&) 是为了支持链式调用,例如 ++(++obj)。
后置单目运算符 ++和--
如果要重载 ++或--为类成员函数,使之能够实现表达式 oprd++ 或 oprd-- ,其中 oprd 为A类对象,则 ++或-- 应被重载为 A 类的成员函数,且具有一个 int 类型形参。 经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)
例子:
#include <iostream>
class Counter {
private:
int value;
public:
Counter(int v = 0) : value(v) {}
// 前置 ++
Counter& operator++() {
++value;
return *this;
}
// 2. 后置 ++ 的重载 (成员函数,带一个哑元 int 参数)
// 返回值,因为返回的是一个临时对象(修改前的值)
Counter operator++(int) {
Counter temp = *this; // 保存当前状态(旧值)
++value; // 增加当前对象的值
return temp; // 返回旧值
}
void display() const {
std::cout << "Counter value: " << value << std::endl;
}
};
int main() {
Counter c1(10);
std::cout << "--- 后置 ++ 演示 ---" << std::endl;
std::cout << "初始状态: ";
c1.display();
Counter c2 = c1++; // 调用 c1.operator++(0)
std::cout << "c1++ 之后 c1 的状态 (已增加): ";
c1.display(); // 输出 11
std::cout << "赋值给 c2 的是 c1 的旧值: ";
c2.display(); // 输出 10
}
-
行为: 后置 ++ 的标准行为是“先使用,后增加”。这意味着函数需要先保存对象当前的状态,然后修改对象的值,最后返回那个被保存的旧状态。
-
int 参数: 这个 int 参数是一个哑元(dummy)参数。它的存在纯粹是为了给编译器一个信号,告诉它这是一个后置运算符的重载,而不是前置。你不需要给它命名,也不需要在函数体中使用它。
-
返回值: 因为返回的是修改前的临时状态,所以通常不返回引用,而是返回一个对象值 (ClassName)。
非成员运算符函数的设计
- 如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以将此运算符重载为该类的非成员(通常为友元)函数。
- 函数的形参代表依自左至右次序排列的各操作数。
- 后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名。
-
使用场景:
-
当你希望运算符的左操作数可以进行类型转换时。
-
当运算符的左操作数不是该类的对象时。最经典的例子就是 << 输出运算符,cout << obj; 的左操作数是 cout(一个 ostream 对象)。
-
当你想让二元运算符(如 +, -)的操作数对称时。
-
-
参数: 非成员函数没有隐式的 this 指针,所以所有操作数都必须作为显式参数传递。对于单目运算符,它需要一个参数;对于二元运算符,它需要两个参数。
例子:
#include <iostream>
#include <ostream> // 需要包含 ostream 头文件
class Point {
private:
int x, y;
public:
Point(int x_val = 0, int y_val = 0) : x(x_val), y(y_val) {}
// 声明非成员函数 operator<< 为友元
// 这样它就可以访问私有成员 x 和 y
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
// 非成员函数 operator<< 的定义
// 参数1: ostream 对象 (cout)
// 参数2: 我们要打印的 Point 对象
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os; // 返回 ostream 引用以支持链式输出
}
int main() {
Point p1(10, 20);
// 这行代码实际上是调用 operator<<(std::cout, p1)
std::cout << "Point p1 is: " << p1 << std::endl;
// 由于返回了 ostream 引用,可以链式调用
Point p2(30, 40);
std::cout << "Point p1 is " << p1 << " and p2 is " << p2 << std::endl;
}
1465

被折叠的 条评论
为什么被折叠?



