使用类 — 运算符重载、友元、自动类型转换(第 11 章)

←面向对象OOP


还是类,不过在类里面增加了一些函数

目录


🔴运算符重载

🔺运算符重载:让自定义类的对象也能使用 +-* 等运算符,本质是定义一个名为 operator@ 的函数(@ 是运算符符号)

🔺语法:

返回类型 operator+(const 类名& 参数) const;

🔺示例:为 Time 类重载 +,实现两个时间相加:

class Time {
private:
    int hours;
    int minutes;
public:
    Time operator+(const Time& t) const;
    Time operator*(double n) const;
};
 
Time Time::operator+(const Time& t) const {
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

🔺调用方式(两种等价):

total = t1 + t2;           // 编译器转换为:
total = t1.operator+(t2);  // 成员函数调用

🔺重载限制

限制说明
不能新建运算符只能重载已有的 C++ 运算符
至少一个操作数是用户自定义类型不能重载 int + int
不能改变运算符的操作数个数二元运算符重载后仍是二元
不能重载的运算符sizeof..*::?:
=()[]->只能用成员函数重载,不能用友元

🔴友元函数

🔺问题:成员函数 operator* 只支持 t * 2.5,不支持 2.5 * t(左操作数必须是类对象)

🔺解决:用友元函数——不是类的成员,但可以访问类的 private 成员

🔺声明(在类内,加 friend 关键字):

class Time {
    friend Time operator*(double n, const Time& t);  // 友元声明
};

🔺定义(在类外,不加 friendTime::):

Time operator*(double n, const Time& t) {
    // 可以直接访问 t.hours、t.minutes(因为是友元)
    Time result;
    long totalminutes = t.hours * 60 * n + t.minutes * n;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

🔺这样 2.5 * t 就等价于 operator*(2.5, t),两种顺序都支持了

🔺友元不是成员函数,但它是类的授权访问者,不违反封装——封装的核心是”通过接口访问”,友元是类主动授权的接口之一


🔴友元与运算符重载的配合

🔺最典型用途:重载 <<cout 能直接输出对象

🔺cout << t 中,左操作数是 ostream 对象,不是 Time,所以不能用成员函数,必须用友元:

// 声明(类内)
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 引用,支持链式调用:cout << t1 << t2
}

🔺返回 ostream& 引用是为了支持 cout << t1 << t2 这样的链式调用


🔴自动类型转换

🔺C++ 对内置类型会自动转换(如 intdouble),对自定义类也可以通过单参数构造函数实现隐式转换

🔺示例:Stonewt 类(英石重量),构造函数接受 double

class Stonewt {
    double pounds;
public:
    Stonewt(double lbs);  // 单参数构造函数 = 隐式转换函数
};

🔺这样以下代码合法:

Stonewt myCat;
myCat = 19.6;  // 编译器自动调用 Stonewt(19.6) 创建临时对象再赋值

🔺**explicit 关键字**:禁止隐式转换,只允许显式转换:

explicit Stonewt(double lbs);  // 加了 explicit
 
myCat = 19.6;              // ❌ 编译错误,不允许隐式转换
myCat = Stonewt(19.6);     // ✅ 显式转换,允许
myCat = (Stonewt)19.6;     // ✅ 强制类型转换,允许

🔺何时用 explicit:当隐式转换可能导致意外行为时加上,是推荐的安全做法


🔴转换函数

🔺反方向:将类对象转换为内置类型,用转换函数(conversion function)

🔺语法:operator 目标类型() const;

class Stonewt {
public:
    operator double() const;   // 转换为 double
    operator int() const;      // 转换为 int
};
 
Stonewt::operator double() const {
    return pounds;
}
 
Stonewt::operator int() const {
    return int(pounds + 0.5);  // 四舍五入
}

🔺使用:

Stonewt poppins(9, 2.8);
double p_wt = poppins;   // 隐式调用 operator double()
cout << int(poppins);    // 显式调用 operator int()

🔺转换函数也可以加 explicit(C++11),禁止隐式转换:

explicit operator double() const;

🔺转换函数 vs 单参数构造函数

方向定义位置
单参数构造函数其他类型 → 本类本类内
转换函数本类 → 其他类型本类内

🔺二义性问题:若同时定义了多个转换函数,隐式转换时编译器可能报”ambiguous”错误,此时需要显式指定转换类型


本章小结

特性关键点
运算符重载operator@ 成员函数,左操作数必须是类对象
友元函数friend 声明,可访问私有成员,解决左操作数不是类对象的问题
<< 重载必须用友元,返回 ostream& 支持链式调用
隐式转换(构造)单参数构造函数自动触发,explicit 可禁止
转换函数operator 类型() 将对象转为其他类型,也可加 explicit