使用类 — 运算符重载、友元、自动类型转换(第 11 章)
还是类,不过在类里面增加了一些函数
目录
🔴运算符重载
🔺运算符重载:让自定义类的对象也能使用 +、-、* 等运算符,本质是定义一个名为 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); // 友元声明
};🔺定义(在类外,不加 friend 和 Time::):
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++ 对内置类型会自动转换(如 int → double),对自定义类也可以通过单参数构造函数实现隐式转换
🔺示例: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 |