C++ 运算符重载:自定义类型的运算扩展
C++ 运算符重载:自定义类型的运算扩展
💡 学习目标:掌握运算符重载的核心语法与规则,能够为自定义类型重载常用运算符,实现类对象的灵活运算。
💡 学习重点:运算符重载的基本形式、成员函数与全局函数重载的区别、常见运算符的重载实现、禁止重载的运算符。
一、运算符重载的概念与核心价值
✅ 结论:运算符重载是 C++ 静态多态的重要体现,允许为自定义类型(如类、结构体)重新定义运算符的行为,让自定义对象可以像内置类型一样使用运算符。
运算符重载的核心价值:
- 简化代码书写:用直观的运算符替代繁琐的成员函数调用,提升代码可读性
- 统一操作风格:让自定义类型的运算逻辑与内置类型保持一致,降低学习和使用成本
- 扩展类型功能:根据业务需求定制运算符的行为,满足自定义类型的运算需求
⚠️ 注意事项:运算符重载不会改变运算符的优先级和结合性,也不会改变运算符的操作数个数。
二、运算符重载的基本语法
运算符重载的本质是函数重载,分为成员函数重载和全局函数重载两种形式。
2.1 成员函数重载语法
将运算符重载函数定义为类的成员函数,语法格式如下:
class 类名 {public: 返回值类型 operator运算符(参数列表){// 自定义运算逻辑}};- 一元运算符(如
++、--)作为成员函数时,没有参数 - 二元运算符(如
+、-)作为成员函数时,只有一个参数,表示运算符右侧的操作数
2.2 全局函数重载语法
将运算符重载函数定义为全局函数,语法格式如下:
返回值类型 operator运算符(参数列表){// 自定义运算逻辑}- 一元运算符作为全局函数时,有一个参数
- 二元运算符作为全局函数时,有两个参数,分别表示左右两侧的操作数
- 如果需要访问类的私有成员,可将全局重载函数声明为类的友元函数
三、常见运算符的重载实现
3.1 二元运算符重载:以 + 为例
以自定义 Point 类为例,重载 + 运算符实现两个点的坐标相加。
3.1.1 成员函数重载 + 运算符
#include<iostream>usingnamespace std;classPoint{public:int x, y;// 构造函数Point(int x =0,int y =0):x(x),y(y){}// 成员函数重载 + 运算符 Point operator+(const Point& p){returnPoint(this->x + p.x,this->y + p.y);}// 打印点坐标voidprint(){ cout <<"("<< x <<", "<< y <<")"<< endl;}};intmain(){ Point p1(1,2),p2(3,4); Point p3 = p1 + p2;// 等价于 p1.operator+(p2) p3.print();// 输出 (4, 6)return0;}3.1.2 全局函数重载 + 运算符
#include<iostream>usingnamespace std;classPoint{public:int x, y;// 构造函数Point(int x =0,int y =0):x(x),y(y){}// 打印点坐标voidprint(){ cout <<"("<< x <<", "<< y <<")"<< endl;}// 声明友元函数friend Point operator+(const Point& p1,const Point& p2);};// 全局函数重载 + 运算符 Point operator+(const Point& p1,const Point& p2){returnPoint(p1.x + p2.x, p1.y + p2.y);}intmain(){ Point p1(1,2),p2(3,4); Point p3 = p1 + p2;// 等价于 operator+(p1, p2) p3.print();// 输出 (4, 6)return0;}3.2 一元运算符重载:以 ++ 为例
一元运算符分为前置++和后置++,二者的重载方式有区别。
3.2.1 前置 ++ 运算符重载
前置 ++ 表示先自增,再使用,重载时没有参数。
#include<iostream>usingnamespace std;classCounter{private:int count;public:Counter(int c =0):count(c){}// 成员函数重载前置 ++ Counter&operator++(){this->count++;return*this;// 返回自增后的对象,支持链式操作}voidshow(){ cout <<"计数:"<< count << endl;}};intmain(){ Counter c(5);++c;// 等价于 c.operator++() c.show();// 输出 计数:6 Counter c2 =++c; c2.show();// 输出 计数:7return0;}3.2.2 后置 ++ 运算符重载
后置 ++ 表示先使用,再自增,重载时需要添加一个占位参数 int 区分前置版本。
#include<iostream>usingnamespace std;classCounter{private:int count;public:Counter(int c =0):count(c){}// 成员函数重载后置 ++ Counter operator++(int){ Counter temp =*this;// 保存当前对象状态this->count++;return temp;// 返回自增前的对象}voidshow(){ cout <<"计数:"<< count << endl;}};intmain(){ Counter c(5); Counter c2 = c++;// 等价于 c.operator++(0) c.show();// 输出 计数:6 c2.show();// 输出 计数:5return0;}3.3 关系运算符重载:以 == 为例
重载关系运算符(==、!=、<、> 等),实现自定义对象的比较逻辑。
#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int id;public:Student(string name,int id):name(name),id(id){}// 成员函数重载 == 运算符booloperator==(const Student& s){returnthis->id == s.id;// 按学号判断是否为同一学生} string getName(){return name;}};intmain(){ Student s1("张三",2024001); Student s2("李四",2024002); Student s3("张三",2024001);if(s1 == s3){ cout << s1.getName()<<" 和 "<< s3.getName()<<" 是同一学生"<< endl;}else{ cout <<"不是同一学生"<< endl;}if(s1 == s2){ cout <<"是同一学生"<< endl;}else{ cout << s1.getName()<<" 和 "<< s2.getName()<<" 不是同一学生"<< endl;}return0;}3.4 输入输出运算符重载:<< 和 >>
<< 和 >> 运算符通常需要全局函数 + 友元的方式重载,因为左侧操作数是 ostream 或 istream 对象。
#include<iostream>#include<string>usingnamespace std;classPerson{private: string name;int age;public:Person(string name ="",int age =0):name(name),age(age){}// 声明友元函数,重载 << 运算符friend ostream&operator<<(ostream& os,const Person& p);// 声明友元函数,重载 >> 运算符friend istream&operator>>(istream& is, Person& p);};// 重载 << 运算符,用于输出对象 ostream&operator<<(ostream& os,const Person& p){ os <<"姓名:"<< p.name <<",年龄:"<< p.age;return os;// 返回 os,支持链式输出}// 重载 >> 运算符,用于输入对象 istream&operator>>(istream& is, Person& p){ is >> p.name >> p.age;return is;// 返回 is,支持链式输入}intmain(){ Person p; cout <<"请输入姓名和年龄:"<< endl; cin >> p;// 等价于 operator>>(cin, p) cout <<"你输入的信息:"<< p << endl;// 等价于 operator<<(cout, p)return0;}四、运算符重载的限制条件
4.1 禁止重载的运算符
以下运算符不允许重载,使用时需注意:
- 成员访问运算符:
. - 成员指针访问运算符:
.* - 作用域解析运算符:
:: - 条件运算符:
?: - 预处理运算符:
#
4.2 必须以成员函数重载的运算符
以下运算符只能通过类的成员函数重载,不能使用全局函数:
- 赋值运算符:
= - 函数调用运算符:
() - 下标运算符:
[] - 箭头运算符:
->
4.3 重载赋值运算符 = 的注意事项
赋值运算符 = 是类的默认成员函数,编译器会自动生成一个浅拷贝版本。当类中包含指针成员时,必须手动重载 = 实现深拷贝,避免浅拷贝导致的内存泄漏。
#include<iostream>#include<cstring>usingnamespace std;classString{private:char*str;public:// 构造函数String(constchar*s =""){ str =newchar[strlen(s)+1];strcpy(str, s);}// 析构函数~String(){delete[] str;}// 重载赋值运算符 =,实现深拷贝 String&operator=(const String& s){if(this==&s){// 防止自赋值return*this;}// 释放当前对象的内存delete[] str;// 分配新内存并拷贝数据 str =newchar[strlen(s.str)+1];strcpy(str, s.str);return*this;// 支持链式赋值}voidshow(){ cout << str << endl;}};intmain(){ String s1("Hello C++"); String s2; s2 = s1;// 调用重载的 = 运算符 s2.show();// 输出 Hello C++return0;}⚠️ 核心注意点:重载赋值运算符时,必须返回 *this 的引用,否则无法支持链式赋值(如 a = b = c)。
五、实战案例:重载运算符实现复数运算
💡 需求:设计一个 Complex 类表示复数,重载 +、-、*、<< 运算符,实现复数的加减乘运算和输出功能。
5.1 需求分析
- 复数的形式为 a+bia + bia+bi,包含实部
real和虚部imag两个成员变量 - 重载
+:(a+bi)+(c+di)=(a+c)+(b+d)i(a+bi)+(c+di) = (a+c)+(b+d)i(a+bi)+(c+di)=(a+c)+(b+d)i - 重载
-:(a+bi)−(c+di)=(a−c)+(b−d)i(a+bi)-(c+di) = (a-c)+(b-d)i(a+bi)−(c+di)=(a−c)+(b−d)i - 重载
*:(a+bi)∗(c+di)=(ac−bd)+(ad+bc)i(a+bi)*(c+di) = (ac-bd)+(ad+bc)i(a+bi)∗(c+di)=(ac−bd)+(ad+bc)i - 重载
<<:输出复数的标准格式
5.2 完整代码实现
#include<iostream>usingnamespace std;classComplex{private:double real;// 实部double imag;// 虚部public:// 构造函数Complex(double real =0,double imag =0):real(real),imag(imag){}// 成员函数重载 + 运算符 Complex operator+(const Complex& c){returnComplex(this->real + c.real,this->imag + c.imag);}// 成员函数重载 - 运算符 Complex operator-(const Complex& c){returnComplex(this->real - c.real,this->imag - c.imag);}// 成员函数重载 * 运算符 Complex operator*(const Complex& c){double r =this->real * c.real -this->imag * c.imag;double i =this->real * c.imag +this->imag * c.real;returnComplex(r, i);}// 友元函数重载 << 运算符friend ostream&operator<<(ostream& os,const Complex& c);};// 实现 << 运算符重载 ostream&operator<<(ostream& os,const Complex& c){if(c.imag >=0){ os << c.real <<" + "<< c.imag <<"i";}else{ os << c.real <<" - "<<-c.imag <<"i";}return os;}intmain(){ Complex c1(3,4),c2(1,-2); Complex c3 = c1 + c2; Complex c4 = c1 - c2; Complex c5 = c1 * c2; cout <<"c1 = "<< c1 << endl; cout <<"c2 = "<< c2 << endl; cout <<"c1 + c2 = "<< c3 << endl; cout <<"c1 - c2 = "<< c4 << endl; cout <<"c1 * c2 = "<< c5 << endl;return0;}5.3 运行结果
c1 = 3 + 4i c2 = 1 - 2i c1 + c2 = 4 + 2i c1 - c2 = 2 + 6i c1 * c2 = 11 - 2i 六、运算符重载的开发规范
- 保持语义一致:重载后的运算符行为应与内置类型的语义相近,避免让使用者产生困惑
- 优先使用成员函数:对于一元运算符和复合赋值运算符(如
+=、-=),优先使用成员函数重载 - 输入输出用全局友元:
<<和>>运算符必须使用全局函数 + 友元的方式重载 - 深拷贝处理:当类包含指针成员时,必须手动重载赋值运算符
=实现深拷贝 - 避免过度重载:只重载实际需要的运算符,不要为了重载而重载,以免增加代码复杂度
七、本章总结
✅ 运算符重载的本质是函数重载,分为成员函数和全局函数两种实现方式。
✅ 运算符重载不会改变运算符的优先级、结合性和操作数个数。
✅ 部分运算符有固定的重载方式,如 = 必须用成员函数,<< 必须用全局友元函数。
✅ 合理使用运算符重载可以简化代码、统一风格,是 C++ 面向对象编程的重要技能。