4.4运算符重载与友元

117 阅读3分钟

运算符重载与友元:定制类行为

一、基础运算符重载

1.1 成员函数重载

class Vector3D {
    float x, y, z;
public:
    Vector3D operator+(const Vector3D& rhs) const {
        return {x+rhs.x, y+rhs.y, z+rhs.z};
    }
  
    Vector3D& operator+=(const Vector3D& rhs) {
        x += rhs.x;
        y += rhs.y;
        z += rhs.z;
        return *this;
    }
};

1.2 友元函数重载

class Complex {
    double real, imag;
public:
    friend Complex operator+(const Complex& a, const Complex& b);
};
​
Complex operator+(const Complex& a, const Complex& b) {
    return {a.real+b.real, a.imag+b.imag};
}

1.3 输入输出重载

class Student {
    string name;
    int id;
public:
    friend ostream& operator<<(ostream& os, const Student& s);
    friend istream& operator>>(istream& is, Student& s);
};

ostream& operator<<(ostream& os, const Student& s) {
    return os << "ID:" << s.id << " Name:" << s.name;
}

istream& operator>>(istream& is, Student& s) {
    return is >> s.id >> s.name;
}

二、友元机制详解

2.1 友元函数特性

class Matrix {
    int data[4][4];
public:
    friend Matrix multiply(const Matrix& a, const Matrix& b);
};

Matrix multiply(const Matrix& a, const Matrix& b) {
    Matrix result;
    // 访问私有数据进行矩阵乘法
    return result;
}

2.2 友元类关系

class Engine {          // 被访问类
    friend class Car;    // 声明友元类
    int rpm;
};
​
class Car {             // 友元类
public:
    void adjustEngine(Engine& e) {
        e.rpm = 2000;   // 访问Engine私有成员
    }
};

🔍 友元使用场景对比表

场景类型适用方案典型示例
运算符需要对称性友元函数operator<<
跨类数据访问友元类汽车-引擎关系
工具函数普通友元函数矩阵乘法工具

三、重载规则与限制

3.1 可重载运算符列表

+  -  *  /  %  ^  &  |  ~
!  =  <  >  +=  -=  *=  /=
%  =  ^=  &=  |=  <<  >>  >>=
<<=  ==  !=  <=  >=  &&  ||  ++
--  ->*  ,  ->  []  ()  new  delete

3.2 不可重载运算符

.     .*     ::     ?:     sizeof
typeid  alignof  noexcept

3.3 特殊运算符规则

下标运算符[]

class StringArray {
    string* arr;
public:
    string& operator[](size_t index) { 
        return arr[index]; 
    }
    const string& operator[](size_t index) const {
        return arr[index];
    }
};

函数调用运算符()

class Adder {
    int base;
public:
    Adder(int b) : base(b) {}
    int operator()(int x) const { 
        return base + x; 
    }
};

Adder add5(5);
cout << add5(3);  // 输出8

四、综合应用案例

4.1 智能指针实现

template<typename T>
class SmartPtr {
    T* ptr;
public:
    explicit SmartPtr(T* p = nullptr) : ptr(p) {}
    ~SmartPtr() { delete ptr; }
  
    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
  
    // 禁止拷贝(示例)
    SmartPtr(const SmartPtr&) = delete;
    SmartPtr& operator=(const SmartPtr&) = delete;
};

4.2 矩阵运算系统

class Matrix {
    vector<vector<double>> data;
public:
    // 矩阵加法
    Matrix operator+(const Matrix& rhs) const;
  
    // 矩阵乘法
    friend Matrix operator*(const Matrix& a, const Matrix& b);
  
    // 标量乘法
    Matrix operator*(double scalar) const;
  
    // 输出格式化
    friend ostream& operator<<(ostream& os, const Matrix& m);
};

五、最佳实践与陷阱

5.1 运算符重载原则

  1. 保持语义一致性operator+不应修改操作数
  2. 返回值优化:算术运算符返回新对象而非引用
  3. 关系运算符成对实现:实现==时同时实现!=
  4. 流操作符使用友元:保证访问权限

5.2 典型错误示例

// 错误:修改左操作数
Vector3D operator+(Vector3D& lhs, const Vector3D& rhs) {
    lhs.x += rhs.x;  // 错误!改变了左操作数
    return lhs;
}
​
// 危险:返回局部变量引用
const string& operator+(const string& a, const string& b) {
    string tmp = a + b;
    return tmp;  // 返回局部对象引用!
}

六、总结与提升

课后练习

  1. 实现分数类Fraction,支持四则运算和约分
  2. 为自定义字符串类添加[]运算符和+运算符
  3. 设计支持链式调用的日志类,重载<<运算符

进阶话题

  • 类型转换运算符重载(operator int()等)
  • new/delete运算符重载实现内存池
  • C++20的三路比较运算符(<=>)

设计准则

  • 优先使用成员函数形式重载+=-=等修改型运算符
  • 对需要对称性的运算符(如+、*)使用友元函数
  • 避免重载逻辑运算符(&&、||)以免失去短路特性
  • 保持运算符的数学含义,不要赋予反直觉的行为

通过合理运用运算符重载和友元机制,可以创建出表现力强、接口直观的自定义类型。建议结合单元测试验证运算符行为,并参考标准库(如string、complex等)的实现方式。记住:优秀的运算符重载应让使用者感觉在使用内置类型。