ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

c++--运算符重载

2026/8/2 19:16:50 拓冰建站 浏览量
c++--运算符重载 1.重载的运算符(1).重载运算符函数的参数数量与该运算符作用的运算对象数量一样多。(2).除了重载的函数调用运算符operator()之外其他重载运算符不能含有默认实参。(3).对一个重载的运算符其优先级和结合律与对应的内置运算符保持一致。(4).当一个重载的运算符是成员函数时this绑定到左侧运算对象。故成员运算符函数的参数数量比运算对象数量少一个。(5).对一个运算符函数或者是类的成员或者至少含一个类类型的参数。2.运算符函数的调用形式// 假设有非成员函数operator显式operator(data1, data2);隐式data1data2// 假设有成员函数operator显式data1.operator(data2)隐式data1data23.关于将运算符函数定义为成员还是非成员赋值下标[]调用成员访问箭头-必须为成员。4.运算符重载4.1.重载(1).通常输出运算符的第一个形参是一个非常量ostream对象的引用。a. 为引用是因为ostream对象不能被拷贝。b. 非常量是因为向流写入内容会改变其状态。(2).第二个形参一般是一个常量的引用该常量是我们想打印的类类型。a. 是引用是因为可以避免值拷贝。b. 常量是因为打印对象不会改变对象的内容。(3). 为与其他输出运算符一致operator一般返回它的ostream形参。(4). 一个实例#include iostream class A { public: int m_a; int m_b; }; std::ostream operator(std::ostream os, const Aitem) { os item.m_a item.m_b; return os; }4.2.重载(1). 通常输入运算符的第一个形参是运算符将要读取的流的引用(2). 第二个形参是将要读入到的非常量对象的引用。(3). 该运算符通常会返回某个给定流的引用。(4). 一个实例#include iostream class A { public: int m_a; int m_b; }; std::istream operator(std::istream is, A item) { char a; is item.m_a a item.m_b; return is; }执行输入时可能发生下列错误(1). 流含有错误类型的数据时读取操作可能失败。(2). 读取操作到达文件末尾或遇到输入流的其它错误时也会失败。4.3.算术运算符4.3.1.最佳实践(1). 类定义了一个算术运算符时一般也要定义一个复合赋值运算符。(2). 一般借用复合赋值运算符来定义算术运算符。(3). 通常算术和关系运算符定义为非成员函数以允许左侧或右侧的运算对象进行转换。(4). 这些运算符一般不需改变运算对象的状态故形参可以是常量的引用。(5). 算术运算的结果一般位于局部变量内。操作完成一般返回局部变量的副本作为结果。(6). 复合赋值运算符更适合作为类成员来定义。复合赋值应该返回引用类型以便支持链式赋值(7). 一个实例#include iostream class A { friend bool operator(const A a1, const A a2); public: A():m_a(0), m_b(0){} A(int i):m_a(i), m_b(i){} void print() { printf(a_%d,b_%d\n, m_a, m_b); } A operator(const A a2) { m_a m_a a2.m_a; m_b m_b a2.m_b; return *this; } private: int m_a; int m_b; }; A operator(const A a1, const A a2) { A sum a1; sum a2; return sum; } bool operator(const A a1, const A a2) { return a1.m_a a2.m_a a1.m_b a2.m_b; } bool operator!(const A lhs, const A rhs) { return !(lhs rhs); } int main() { A a1(10); A a2(10); A a3 a1 a2; a3.print(); return 0; }上述是一个为类型A定义运算符!的实例。我们采用类成员定义复合赋值。下述是一个复合赋值采用非成员的版本。#include iostream class A { friend bool operator(const A a1, const A a2); friend A operator(A a1, const A a2); public: A():m_a(0), m_b(0){} A(int i):m_a(i), m_b(i){} void print() { printf(a_%d,b_%d\n, m_a, m_b); } private: int m_a; int m_b; }; // 返回值是复合赋值作为表达式的结果 // 参数1是复合赋值左边操作数此操作数会被复合赋值所影响。 A operator(A a1, const A a2) { a1.m_a a2.m_a; a1.m_b a2.m_b; return a1; } A operator(const A a1, const A a2) { A sum a1; sum a2; return sum; } bool operator(const A a1, const A a2) { return a1.m_a a2.m_a a1.m_b a2.m_b; } bool operator!(const A lhs, const A rhs) { return !(lhs rhs); } int main() { A a1(10); A a2(10); A a3 a1 a2; a3.print(); return 0; }4.4.关系运算符4.4.1.最佳实践(1). 通常关系运算符定义为非成员函数以允许左侧或右侧的运算对象进行转换。(2). 这些运算符一般不需改变运算对象的状态故形参可以是常量的引用。以下是一个关系运算符重载的例子#include iostream class A { friend bool operator(const A a1, const A a2); public: A():m_a(0),m_b(0){} A(int i):m_a(i),m_b(i){} private: int m_a; int m_b; }; bool operator(const A a1, const A a2) { if(a1.m_a a2.m_a a1.m_b a2.m_b) { return true; } else { return false; } } bool operator!(const A a1, const A a2) { return !(a1 a2); } int main() { A a1(1), a2(2); printf(a1a2?%d\n, a1 a2); return 0; }4.5.赋值运算符(1). 赋值运算符限制性因素有赋值作为表达式存在一个结果数值。赋值会修改左边操作数状态。(2). 最佳实践a. 赋值一般实现为成员函数b. 为类型自定义赋值运算符时运算符内部必须处理b.1. 基类部分赋值b.2. 类型成员赋值c. 为类型自定义赋值运算符时需要考虑自赋值场景。d. 赋值与拷贝针对包含资源的类涉及浅拷贝深拷贝的概念。又涉及到左值右值移动语义完美转发的概念。这些后续统一放在专门一篇讲解。(3). 一个实例#include iostream class Norm1 { public: Norm1(char i):m_i(i){} void print() { printf(norm1:%c\n, m_i); } private: char m_i; }; class Norm2 { public: Norm2(char i):m_i(i){} void print() { printf(norm2:%c\n, m_i); } private: char m_i; }; class Base { public: Base(char c b):m_n1(c),m_n2(c){} void print() { printf(base:\n); m_n1.print(); m_n2.print(); } private: Norm1 m_n1; Norm2 m_n2; }; class A : public Base { public: A(char c a):Base(c), m_n1(c1),m_n2(c1){} A operator(const A a) { if(this a) { return *this; } Base::operator(a); m_n1 a.m_n1; m_n2 a.m_n2; return *this; } void print() { Base::print(); m_n1.print(); m_n2.print(); } private: Norm1 m_n1; Norm2 m_n2; }; int main() { A a1(x); A a2(a); a1.print(); a2.print(); a1 a2; printf(after:\n); a1.print(); a2.print(); return 0; }4.6.下标运算符对下标运算符最好定义常量和非常量版本对常量版本返回常量引用。4.6.1. 强制性约束从强制性约束角度要求形参列表只能有一个形参#include iostream class Norm { public: void print() const { printf(Norm_%d\n, m_i); } private: int m_i; }; class A { public: int operator[](Norm n) { printf(test[]\n); return 1; } private: Norm m_arrA[10]; }; int main() { A a; Norm n; int i a[n]; return 0; }上述是一个合法的重载的[]运算符。4.6.2. 最佳实践(1). 形参最好是std::size_t。(2). 重载运算符最好提供常量版本非常量版本。这样对类型的常量实例非常量实例均可执行[]运算。(3). 返回值应该设计成引用类型。标准库类型[]运算符也均返回引用类型。#include iostream class Norm { public: void print() const { printf(Norm_%d\n, m_i); } private: int m_i; }; class A { public: Norm operator[](std::size_t i) { printf([]\n); return m_arrA[i]; } const Norm operator[](std::size_t i) const { printf([]const\n); return m_arrA[i]; } private: Norm m_arrA[10]; }; int main() { A a; a[1].print(); const A a1 A(); a1[1].print(); return 0; }上述是一个[]运算符重载的最佳实例4.7.递增和递减运算符4.7.1.强制性约束(1).对后置版本的--形参列表必须提供单个类型为int的形参。(2).对前置版本的--形参列表必须为空。#include iostream class A { public: A() : m_i(0) {} int operator() { printf(()\n); m_i; return 1; } int operator(int n) { printf((int)\n); A temp *this; m_i; return 1; } private: int m_i; }; int main() { A a; a; a; return 0; }上述是一个合法的前置后置运算符的重载实现。4.7.2.最佳实践(1). 定义--运算符时候应该同时定义前置版本后置版本。(2). 前置版本应该返回类型自身的引用。后置版本应该返回类型自身。与标准库类型保持一致。(3). 不应该提供常量版本。#include iostream class Norm { private: int m_i; }; class A { public: A() : m_i(0) {} A operator() { printf(()\n); m_i; return *this; } A operator(int) { printf((int)\n); A temp *this; m_i; return temp; } private: int m_i; }; int main() { A a; a; a; return 0; }上述是一个前置后置的最佳实践。4.8.成员访问运算符*-4.8.1. 强制性约束(1).对*运算符必须提供0个形参或1个形参。(2).对-运算符必须提供0个形参。#include iostream class Norm { public: Norm(int i) : m_i(i) {} void print() { printf(i_%d\n, m_i); } private: int m_i; }; class A { public: int operator*(Norm) { printf(*\n); return 1; } int operator-() { printf(-\n); return 2; } private: int m_i; }; int main() { A a; A *p a; Norm n(1); p-operator-(); p-operator*(n); a.operator*(n); a.operator-(); // a-print(); //*a; return 0; }上述我们为类型定义了一个合法的*运算符一个合法的-运算符。4.8.2.最佳实践(1).*运算符的形参应该是0个。因为只有这样才能使用A a; *a;这样的方式触发*运算符访问。(2).-运算符的返回类型应该要么是重载了-运算符的类型要么是指针类型。因为如下述我们执行A a; a-print();这样的操作时。操作处理细节为a. 若a重载了-运算符。则执行运算符获得返回值。否则报错。b. 若返回值是指针类型进入到对指针类型执行-情形。终止。c. 若返回值不是指针类型且重载了-运算符则执行运算符获得返回值。否则报错终止。b. 进入2。我们下述的a-print()对应的输出为#include iostream class Norm { public: Norm(int i) : m_i(i) {} Norm *operator-() { printf(norm -\n); return this; } void print() { printf(i_%d\n, m_i); } private: int m_i; }; class A { public: Norm operator*() { Norm n(1); printf(*\n); return n; } Norm operator-() { Norm n(11); printf(-\n); return n; } private: int m_i; }; int main() { A a; A *p a; p-operator-(); p-operator*(); a.operator*(); a.operator-(); a-print(); *a; return 0; }4.8.3. 注意点以4.8.2中实例为说明对象。整理c类型中成员访问细节。int main() { A a; A *p a; p-operator-(); p-operator*(); a.operator*(); a.operator-(); a-print(); *a; return 0; }(1). 针对指针类型基于指针执行-可以访问到指针指向对象的成员。此行为不允许重载。(2). 针对非指针类型称其为对象类型基于对象类型执行.可以访问到对象的成员。此行为不允许重载。(3). 调用类型*-运算符的形式有显式和隐式。a. 显式调用诸如int main() { A a; A *p a; p-operator-(); p-operator*(); a.operator*(); a.operator-(); return 0; }b.隐式调用诸如int main() { A a; a-print(); *a; return 0; }4.9.函数调用运算符4.9.1.强制性约束对形参列表无强制性约束。#include iostream class A { public: A() {} int operator()(int) { printf((int)\n); return 0; } int operator()(int, int) { printf((int,int)\n); return 1; } private: int m_i; }; int main() { A a; a(1); a(1, 2); return 0; }上述我们定义了两个函数调用运算符分别接收一个参数两个参数。定义类函数调用运算符的类类的对象称作函数对象。4.10.转换构造函数4.10.1.强制性约束我们称只传递一个实参即可调用的构造函数为转换构造函数#include iostream class A { public: A(int i, int j 0) : m_i(i) { printf(A(int)_%d\n, i); } private: int m_i; }; void fun(A a) { } int main() { fun(1); return 0; }上述我们定义了一个转换构造函数。因为有了此转换构造函数上述fun(1)执行中先基于1构造得到类型A的实例再执行函数调用。若要阻止这类默认转换只需将转换构造用explicit修饰。此时只能通过显式构造基于int得到类型A实例。#include iostream class A { public: explicit A(int i, int j 0) : m_i(i) { printf(A(int)_%d\n, i); } private: int m_i; }; void fun(A a) { } int main() { fun(A(1)); return 0; }4.11.类型转换运算符4.11.1.强制性约束类型转换运算符的形参列表必须为空#include iostream class Norm { public: private: char m_c; }; class A { public: explicit A(int i, int j 0) : m_i(i) { printf(A(int)_%d\n, i); } operator int() { printf(int()\n); return m_i; } operator Norm() { printf(Norm()\n); Norm n; return n; } private: int m_i; }; void fun(A a) { } int main() { fun(A(1)); A a(1); int n 1 a; printf(n_%d\n, n); Norm nn a; return 0; }上述我们定义了两个类型转换运算符分别转换到intNorm类型。