1. 运算符重载的基本概念运算符重载是C一项强大的特性它允许我们为自定义类型类或结构体重新定义运算符的行为。通过运算符重载我们可以让自定义类型像内置类型一样使用标准的运算符语法使代码更加直观和自然从本质上讲运算符重载是函数重载的一种特殊形式。当我们重载一个运算符时实际上是在定义一个特殊的成员函数或全局函数函数名由operator关键字后接要重载的运算符组成12a b;// 等价于 a.operator(b) 或 operator(a, b)a b;// 等价于 a.operator(b) 或 operator(a, b)运算符重载提供了语法糖让代码更加直观和易读。比较以下两种写法12345// 使用运算符重载list1 list2;// 不使用运算符重载list1.concat(list2);第二种写法显然更加符合直觉让自定义类型与内置类型有一致的操作方式2. 算术运算符重载2.1 加法运算符重载在代码中复数类的加法运算符重载展示了运算符重载的基本用法1234567Complex operator(Complex a, Complex b){Complex ret;ret.real a.real b.real;ret.image a.image b.image;returnret;}运算符重载有两种实现方式​成员函数形式和全局函数形式​​成员函数形式隐含一个this指针参数只需要一个显式参数123456789classComplex {public:Complex operator(constComplex other) {Complex ret;ret.real this-real other.real;ret.image this-image other.image;returnret;}};​全局函数形式需要两个显式参数通常需要声明为类的友元函数以访问私有成员1234classComplex {friendComplex operator(constComplex a,constComplex b);// ...};代码中使用了全局函数形式并将它们声明为友元函数这样可以访问Complex类的私有成员real和image。2.2 减法运算符重载减法运算符的重载与加法类似只需改变运算逻辑1234567Complex operator-(Complex a, Complex b){Complex ret;ret.real a.real - b.real;ret.image a.image - b.image;returnret;}3. 流运算符重载3.1 输出运算符重载输出运算符的重载需要特别注意返回类型和参数类型。代码中实现了这一功能12345ostream operator(ostream cout, Complex other){cout other.real other.image i;returncout;}这里有几个关键点​返回类型必须是ostream引用这样才能支持链式输出如cout a b c;​第一个参数是ostream类型通常是cout或其变体​第二个参数是要输出的对象函数返回输出流对象本身使链式操作成为可能如果返回void类型将无法实现链式输出。这是因为cout c endl会被解析为(cout c) endl如果cout c返回void那么void endl就是非法的。3.2 避免不必要的拷贝使用const Complex other而不是Complex other的好处避免拷贝构造。当对象较大时传引用可以显著提高性能。正确的声明应该是1ostream operator(ostream cout,constComplex other);这里的const确保不会意外修改对象状态。4. 自增运算符重载自增运算符有前置和后置两种形式需要分别处理。4.1 前置自增运算符12345Complex operator()//前置{this-real 1;return*this;}前置返回引用这是为了保持与内置类型一致的行为。这样a本身可以作为左值使用4.2 后置自增运算符123456Complex operator(int)//后置{Complex a *this;//保存原始值this-real 1;returna;//返回原始值}后置通过int参数与前置版本区分这个参数仅用于区分并不实际使用。它返回的是值而不是引用因为返回的是局部对象不能返回引用。注释中提到的后置返回临时对象这个对象在函数结束后会被销毁所以不能返回引用。5. 赋值运算符重载赋值运算符重载需要特别注意深拷贝和自赋值问题。1234567891011Jeff operator(Jeff a){if(age){deleteage;age NULL;}age newint;//分配新内存*age *a.age;return*this;}这里有几个重要考虑​检查自赋值​虽然代码中没有显式检查但a a这样的自赋值应该安全处理​释放旧资源​在分配新资源前释放已有资源防止内存泄漏​深拷贝​创建新内存并复制内容而不是简单复制指针​返回引用​支持链式赋值a b c改进版本应该包含自赋值检查12345678910Jeff operator(constJeff a){if(this! a) {// 自赋值检查if(age) {deleteage;}age newint(*a.age);}return*this;}6. 关系运算符重载关系运算符重载通常返回bool值用于比较对象。1234567891011booloperator(constPoint a)const{returnthis-x a.x this-y a.y;}booloperator(constPoint a)const{intc x * x y * y;intd a.x * a.x a.y * a.y;returnc d;}代码中通过比较点到原点的距离来定义运算符这是一种常见的做法。注意这些函数被声明为const因为它们不应该修改对象状态。7. 函数调用运算符重载函数调用运算符()的重载创建了所谓的仿函数​functor。12345intoperator()(inta,intb){data;returna b data;}仿函数比普通函数更灵活因为它们可以保持状态。如你的示例所示每次调用都会增加data的值这是普通函数无法做到的。仿函数可以记录调用过程中的状态比普通函数更加灵活常用于STL算法中的定制行为8. 运算符重载的规则与最佳实践8.1 可重载的运算符C允许重载大部分运算符包括算术运算符,-,*,/,%关系运算符,!,,,,逻辑运算符,||,!赋值运算符,,-,*,/下标运算符[]函数调用运算符()流运算符,自增自减,--8.2 不可重载的运算符有些运算符不能重载包括成员访问运算符.成员指针访问运算符.*作用域解析运算符::条件运算符?:三目运算符sizeof运算符typeid运算符8.3 最佳实践​保持语义一致性​重载的运算符应该保持与内置类型相似的语义​考虑返回值类型​算术运算符通常返回新对象复合赋值运算符通常返回引用关系运算符返回bool​正确处理常量性​不修改对象的函数应声明为const​遵循三/五法则​如果定义了拷贝构造函数、拷贝赋值运算符、析构函数中的一个通常需要定义其他相关函数9. 总结运算符重载是C面向对象编程的重要特性它让自定义类型能够以更自然的方式集成到语言中。通过合理使用运算符重载我们可以编写出更加直观、易维护的代码。关键要点运算符重载的本质是函数重载遵循函数重载的规则选择成员函数还是全局函数形式取决于具体需求流运算符和通常重载为全局友元函数赋值运算符需要处理自我赋值和深拷贝问题前置和后置自增/自减运算符通过参数区分函数调用运算符重载创建仿函数可以保持状态合理使用运算符重载可以极大提高代码的可读性和易用性但也要避免滥用保持运算符的直观语义。当你面对自定义类型需要类似内置类型的操作时运算符重载是一个强大的工具。