资讯动态

10. 使用类

发布时间:2026/8/9 3:13:25 来源:尧图企业网站定制
使用类运算符重载运算符重载是 C 多态的一种形式允许将重载的概念扩展到运算符上赋予 C 运算符多种含义。重载运算符的限制只能重载现有的C运算符。重载后不能改变运算符的优先级不能改变操作数的个数不能重载.-*解引用?:sizeof至少有一个操作数是用户定义的类型openator关键字重载算数/关系运算符成员函数this充当左操作数。全局函数双参数。注建议用全局按值传参建议成员#include iostream using namespace std; ​ class A { private: int m_val; ​ public: // ---------- 构造函数 ---------- A(int val 0) : m_val(val) { cout 构造: m_val endl; } ​ // 拷贝构造为了让你看到“拷贝”发生方便理解 A(const A other) : m_val(other.m_val) { cout 拷贝构造: m_val endl; } ​ // 移动构造为了让你看到“移动”发生右值传参时走这里零拷贝 A(A other) noexcept : m_val(other.m_val) { cout 移动构造: m_val endl; other.m_val 0; // 偷走资源后把别人置空 } ​ // ---------- 核心法则 1: operator 必须写成 成员函数 ---------- // 理由修改左操作数自身返回自身引用方便链式操作 abc A operator(const A other) { this-m_val other.m_val; return *this; // 返回自己 } ​ // 打印函数方便看结果 void print() const { cout 当前值: m_val endl; } }; ​ // ---------- 核心法则 2: operator 写成 全局函数左操作数按值传递 ---------- // 理由 // 1. 左值传进来 - 拷贝构造安全不影响原数据。 // 2. 右值传进来 - 移动构造零开销且不影响原数据因为原数据本来就是临时的。 // 3. 内部复用 完全不需要友元因为我们没碰私有成员碰的是公有 。 A operator(A lhs, const A rhs) { lhs rhs; // 修改的是局部副本lhs原数据绝对安全 return lhs; // 返回局部对象编译器自动触发 RVO 返回值优化连移动都省了 } ​ int main() { cout ------ 场景 1: 左值 左值拷贝不破坏原数据------ endl; A a1(10); A a2(20); A a3 a1 a2; // a1 拷贝给 lhsa2 引用传递。a1、a2 没变。 a3.print(); // 输出 30 a1.print(); // 输出 10证明没被破坏 a2.print(); // 输出 20证明没被破坏 ​ cout \n------ 场景 2: 右值 右值移动零拷贝------ endl; A a4 A(5) A(7); // A(5) 移动给 lhsA(7) 引用传递。没有任何深拷贝。 a4.print(); // 输出 12 ​ cout \n------ 场景 3: 右值 左值移动引用混合------ endl; A a5 A(100) a1; // A(100) 移动给 lhsa1 引用传递。a1 依然没变。 a5.print(); // 输出 110 a1.print(); // 输出 10依然没变 ​ return 0; }重载类型转换运算符隐式转换class A { int a; public: // 将 A 隐式转换为 int operator int() const { return a; } }; ​ A obj(10); int n obj; // 这里调用了 operator intn 变成 10提醒隐式转换容易造成意料之外的编译错误通常建议在前面加 explicitC11禁止隐式调用函数调用运算符 operator()仿函数 / Functorclass Add { public: int operator()(int x, int y) const { return x y; } }; ​ Add add; int result add(3, 5); // 对象后面加括号像函数一样 result 8解引用/成员访问运算符 operator- 和 operator*智能指针专用 学std::shared_ptr和std::unique_ptr时的核心。重载这两个运算符可以让一个类表现得像指针一样。class MyPtr { A* ptr; public: A* operator-() { return ptr; } A operator*() { return *ptr; } }; // 这样你就可以用 MyPtr-a 直接访问内部 A 的成员了内存分配/释放运算符 operator new / operator delete极客玩法 重载全局或局部的 new 和 delete自定义对象如何从堆中申请/释放内存比如用内存池这是游戏引擎或高频交易系统常用的底层优化手段。友元C 控制对类对象私有部分的访问。通常公有类方法 提供唯一的访问途径。C 提供了 友元friend 机制来打破这种限制。类型说明友元函数非成员函数可访问类的私有成员友元类整个类成为另一个类的友元友元成员函数某个类的成员函数成为另一个类的友元友元函数不是成员函数不能使用成员运算符.或-调用。友元函数与成员函数具有相同的私有权限。友元函数的声明class Time { private: int hours; int minutes; public: Time operator*(double n) const; friend Time operator*(double m, const Time t); // 友元声明 };友元函数的定义// 友元定义不写 friend不写 Time:: Time operator*(double m, const Time t) { Time result; long totalminutes t.hours * m * 60 t.minutes * m; // 直接访问私有成员 result.hours totalminutes / 60; result.minutes totalminutes % 60; return result; }建议友元函数定义为以下模式// 不访问私有数据利用成员函数实现 Time operator*(double m, const Time t) { return t * m; // 调用成员函数 t.operator*(m) }常用友元重载运算符支持链式调用// 友元声明类中 friend std::ostream operator(std::ostream os, const Time t); ​ // 定义 std::ostream operator(std::ostream os, const Time t) { os t.hours hours, t.minutes minutes; return os; // 返回 os 自身 }重载运算符作为成员函数还是非成员函数对于大多数运算符C提供了两种实现方式成员函数一个通过 this 隐式传递一个作为参数显式传递。非成员函数通常为友元两个操作数都作为参数显式传递。必须用成员函数运算符说明赋值运算符()函数调用运算符[]下标运算符-指针访问类成员运算符这些运算符必须是成员函数因为它们要求左操作数是一个类对象且涉及 this 指针的特殊语义。建议用非成员函数友元的场景需要对操作数顺序有要求。第一个操作数不是类对象。需要对称性的运算符。如、-、*、等ab和ba相同的调用。优先级如果运算符必须修改对象本身状态如 、- → 通常用成员函数因为修改的是 *this如果运算符不修改对象本身且需要对称性如 、* → 通常用非成员函数友元如果第一个操作数不是类对象如 ostream → 必须用非成员函数友元矢量类实例矢量vector是一个有大小和方向的量。设计了两种等价表示方式直角坐标水平分量和垂直分量极坐标长度和方向角度vector.hpp#ifndef VECTOR_H_ #define VECTOR_H_ #include iostream namespace VECTOR { class Vector { public: enum Mode { RECT, POL }; // 两种模式 private: double x; // 水平分量 double y; // 垂直分量 double mag; // 长度 double ang; // 方向弧度 Mode mode; // 状态成员当前模式 // 私有方法保持两种表示一致 void set_mag(); void set_ang(); void set_x(); void set_y(); public: Vector(); Vector(double n1, double n2, Mode form RECT); void reset(double n1, double n2, Mode form RECT); ~Vector(); // 内联访问函数只读 double xval() const { return x; } double yval() const { return y; } double magval() const { return mag; } double angval() const { return ang; } void polar_mode(); // 切换到极坐标模式 void rect_mode(); // 切换到直角坐标模式 // 运算符重载 Vector operator(const Vector b) const; Vector operator-(const Vector b) const; Vector operator-() const; // 取反 Vector operator*(double n) const; // 友元函数 friend Vector operator*(double n, const Vector a); friend std::ostream operator(std::ostream os, const Vector v); }; } // end namespace VECTOR #endifvector.cpp#include cmath #include vector.h namespace VECTOR { const double Rad_to_deg 45.0 / atan(1.0); // 约 57.2958 // -------- 私有方法保持两种表示一致 -------- void Vector::set_mag() { mag sqrt(x * x y * y); } void Vector::set_ang() { ang (x 0.0 y 0.0) ? 0.0 : atan2(y, x); } void Vector::set_x() { x mag * cos(ang); } void Vector::set_y() { y mag * sin(ang); } // -------- 构造函数 -------- Vector::Vector() { x y mag ang 0.0; mode RECT; } Vector::Vector(double n1, double n2, Mode form) { mode form; if (form RECT) { x n1; y n2; set_mag(); set_ang(); } else if (form POL) { mag n1; ang n2 / Rad_to_deg; // 角度转为弧度 set_x(); set_y(); } else { // 非法模式置零 x y mag ang 0.0; mode RECT; } } // -------- 运算符重载 -------- Vector Vector::operator(const Vector b) const { return Vector(x b.x, y b.y); } Vector Vector::operator-(const Vector b) const { return Vector(x - b.x, y - b.y); } Vector Vector::operator-() const { return Vector(-x, -y); } Vector Vector::operator*(double n) const { return Vector(x * n, y * n); } // -------- 友元函数 -------- Vector operator*(double n, const Vector a) { return a * n; // 调用成员函数 operator*(double) } // 根据 mode 决定显示格式 std::ostream operator(std::ostream os, const Vector v) { if (v.mode Vector::RECT) { os (x,y) ( v.x , v.y ); } else if (v.mode Vector::POL) { os (m,a) ( v.mag , v.ang * Rad_to_deg ); // 弧度转度显示 } else { os Vector object mode is invalid; } return os; } } // end namespace VECTOR设计要点解析两种表示方式同步更新输入模式构造函数操作同步更新RECT直角坐标设置x、y调用set_mag()、set_ang()计算极坐标POL极坐标设置mag、ang调用set_x()、set_y()计算直角坐标类的自动转换和强制类型转换C 处理内置类型转换时兼容类型可以自动转换不兼容类型需要强制转换。类的设计可以让自定义类型之间或自定义类型与内置类型之间的转换变得有意义。隐式类型转换的场景#ifndef STONEWT_H_ #define STONEWT_H_ class Stonewt { private: enum { Lbs_per_stn 14 }; // 1 英石 14 磅 int stone; // 整英石数 double pds_left; // 剩余磅数小数 double pounds; // 总磅数 public: Stonewt(double lbs); // 从 double总磅数构造 Stonewt(int stn, double lbs); // 从英石磅构造 Stonewt(); // 默认构造函数 ~Stonewt(); void show_lbs() const; // 显示总磅数 void show_stn() const; // 显示英石磅 }; #endif对象初始化Stonewt wolfe 285.7;编译器隐式的调用了构造函数将double类型转换为Stonewt类型。赋值操作myCat 19.6;编译器调用构造函数将19.6转为一个Stonewt类型的临时对象再用编译器重写的赋值运算符将这个临时对象赋值给myCat。实现了double类型到Stone类型的转化。函数参数传递display(422, 2);编译器发现第一个参数是Stonewt 类型隐式的调用构造函数。实现了double类型到Stonewt类型的转化。函数返回值同上。注隐式类型转换从其它类型 → 类类型靠的是“只接受一个参数”的构造函数。如果是两个参数第二个参数是默认参数仍能隐式类型转换。Stonewt wolfe 285.75右边被视为逗号运算结果为5。转换函数和友元函数加法运算符的实现方法成员函数Stonewt Stonewt::operator(const Stonewt st) const { double pds pounds st.pounds; Stonewt sum(pds); return sum; }友元函数Stonewt operator(const Stonewt st1, const Stonewt st2) { double pds st1.pounds st2.pounds; Stonewt sum(pds); return sum; }两种方式只能选其一否则会导致二义性。当第一个操作数不是类对象时只有友元函数能处理。成员函数要求调用对象必须是类对象。二义性问题转换函数捣乱 如果类同时定义了转换函数如 operator double()和加法运算符重载编译器可能陷入二义性。// 同时存在 Stonewt::operator double() const; // Stonewt → double Stonewt operator(const Stonewt a, const Stonewt b); // 友元加法

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价