第18章 操作符重载
像 内置类型对象 一样便捷地操作 类类型对象
Note
(1) 何时用 非成员函数?
必须用: 第1操作数 为 `内置类型`
应该用:
不需要直接访问 类的表示部分 的函数
`对称` 的 操作符(函数)
(2) 必须为 `non-static 成员函数` 的 操作符
————————————————————
赋值 =
下标 []
函数调用 ()
箭头 ->
————————————————————
(3) `临时量` 可作 `成员操作符` 的 `左运算对象`
complex x {3, 4};
complex z { sqrt(x) += {1, 2} }; //ok: tmp = sqrt(x), tmp += {1, 2}
(4) `返回值类型 不参与 重载解析`
(5) helper 函数
类内 `private 成员函数`
类外 `非成员 函数`
(6) 用 namespace 包含 `helper 函数` & 关联类
非成员 helper 函数: `不需要 直接访问` 类的表示
1) 其声明 与 类声明 放 `同一 头文件`
2) 与 类 放 `同一 namespace`
(7) 用户无权自定义 的 操作符
:: 作用域解析
. 成员选择
.* 指向成员的指针
(8) 逻辑 与/或 (&& ||) 逗号 (,)
默认含义 包含 顺序信息, 该规则 并不适用于 用户自定义版本
(9) 隐式 / 显式调用
complex d = a + b;
complex d = a.operator+(b);
(10) 把 简单成员操作 简化为 `非成员函数` (不增加性能开销时)
inline bool operator==(complex a, complex b)
{
return a.real() == b.real() && a.imag() == b.imag();
}
18.1 操作符函数
成员函数&非成员函数 version 均定义 => 由 重载解析规则 决定选哪个
1 二元/一元 操作符
(1) 二元 操作符 @
————————————————————
a@b
————————————————————
a.operator@(b)
————————————————————
operator@(a, b)
————————————————————
(2) 一元 操作符
前置 @
————————————————————
@a
————————————————————
a.operator@()
————————————————————
operator@(a)
————————————————————
后置 @ : 相比前置版本, compiler 只加1各 dummy 参数 以区分
————————————————————
a@
————————————————————
a.operator@(int)
————————————————————
operator@(a, int)
————————————————————
2 3种具有 预置含义
的 成员操作符: compiler 为 用户自定义类型 隐含生成
, 与 内置类型 操作符
有 等价含义
= 赋值
& 取地址
, 顺序
3 3种操作符
(1) new / delete
(2) 成员函数
(3) 非成员函数: 至少含 1 个 用户自定义类型
4 传递对象: 传参 & 返回
(1) `传参`
1) 值传递: 小对象 -> 值传递 -> 性能最好
2) 引用传递
大对象
引用传递 (指 左值引用)
|
| 不想改变 对象内容
|/
const 引用(传递)
|
| 不想 copy, 只想 move
|/
右值引用(传递)
(2) `返回`
1) `internal 新建对象: 值返回
大对象 -> 应该定义 move 操作: 形似传值,实则 `move`
2) `返回 已有(参数)对象: 引用返回
A& A::operator+=(const A& rhs)
{
// ...
return *this;
}
5 非成员操作符: 必然在某 namespace(如 全局 namespace ::) 中
二元运算符 @ 解析过程: x @ y // x/y 类型为 X/Y
find scope 和 order: 见 12.3.3&4
1) X 或 X 的基类
X 中有 operator@, 就不用去 X 的基类找了(函数重载不会跨越作用域: 20.3.5);
X 中没有才会去 X 的 基类中找
2) x @ y 的 context
3) X 所在 namespace
4) Y 所在 namespace
18.2 复数类型
1 成员/非成员 操作符
(1) non-mem func 直接 操作对象
并 不是好做法
-> 更好的设计: 加中间层 memFunc, non-mem func 调 memFunc, 让 memFunc 直接操作对象
operator+(a, b) 调 a.operator+=(b)
f(a, b) 调 a.f(b)
=> 复合赋值运算符(+= 等) 比 相应的非复合赋值运算符 (+等) 简单
+ 涉及 3 个对象 (含 结果对象/临时对象 )
+= 只涉及 2 个对象, `不` 需要处理 `临时对象`
1] 运行效率提升
2] 编译器 易 inline 它
class complex
{
double re, im;
public:
complex& operator+=(complex); // 直接访问 类的 成员数据
};
inline complex& complex::operator+=(complex rhs)
{
re += rhs.re;
im += rhs.im;
return *this;
}
complex operator+(complex a, complex b);
{
return a += b; // 通过 += 访问 类的 成员数据
}
2 单参数 Ctor 类型转换: 解决 混合模式运算 组合爆炸问题
运算符 参数个数多&类型多 -> 组合过多-> 易错
各种组合共性: 几乎都有 `paraType 到 类类型` 的 `类型转换`
解决:
1] `类型转换 的 通用版本`
2] 辅以 `必要变形`
complex operator+(complex, complex);
complex operator+(complex, double);
complex operator+(double, complex);
|
| 类型转换
|/
complex operator+(complex, complex);
complex::complex(double d): re{d}, im{0} {}
3 字面值常量(2种实现)
(1) constexpr
Ctor + 编译期计算(如 能 inline)
-> 构造的对象为 字面值常量
(2) 用户自定义
字面值常量, 如 虚部 字面值常量: 把 i 定义成 后缀
// === 1
class complex
{
public:
constexpr complex(double r = 0, double i = 0)
: re{r}, im{i} { }
};
// === 2
constexpr complex operator "" i(long double d)
{
return {0, d};
}
complex f(double d)
{
complex x {0, 2.1};
return x + 12e3i;
}
// === 1
class complex
{
public:
constexpr complex(double r = 0, double i = 0)
: re{r}, im{i} { }
};
// === 2
constexpr complex operator "" i(long double d)
{
return {0, d};
}
complex f(double d)
{
complex x {0, 2.1};
return x + 12e3i;
}
18.3 类型转换
2种方式 * 显式/隐式 => 4 种组合
—————————————————
单参数 Ctor
类型转换运算符
—————————————————
——————————————————————————————————————————————————————————————————
只在何时才会执行
——————————————————————————————————————————————————————————————————
explicit `直接初始化(未使用 =)` 时
隐式 不引起 二义性时
——————————————————————————————————————————————————————————————————
单参数 Ctor 类型转换 <---目标相反---> 类型转换运算符`
X::X(T t) X::operator T()
otherType -> 类类型 类类型 -> otherType
1 类型转换运算符 & cin
成员函数 X::operator T()
定义了 从 类类型 X 到 otherType T 的 类型转换, returnType 在 运算符名字中已经出现, 不再出现在 通常的 retrunType 位置
while (cin >> x)
cout << x;
cin >> x 返回 istream& -> 隐式转化为 `表示 cin 状态 的 值`
2 explicit 类型转换运算符 & 智能指针
template <typename T, typename D = default_delete<T> >
class unique_ptr
{
public:
// ...
explicit operator bool() const noexcept;
// ...
};
void user(unique_ptr<Record> p, unique_ptr<int> q )
{
if(!p) // <=> if ( !( bool tmp{p} ) )
// ...
// explicit =>
bool b = p; // error
int x = p + q; // error
}
3 二义性 & implicitly 类型转换 不跨层
// (1) 二义性
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
| | |
| |/ |
| class X { /* ... */ X(int); X(const char*); };
| |\
| | (1) int 可隐转为 X 或 Y => 二义性
| |/
| class Y { /* ... */ Y(int); };
|
| class Z { /* ... */ Z(X); };
| |\
|_ _ _ _ _ _ _ _ _ _ _ _ _|
const char* -> X -> Z
// (2) implicitly 类型转换 不跨层
X f(X);
Y f(Y);
Z g(Z);
void user()
{
f(1); // error, 二义性: f( X(1) ) 还是 f( Y(1) )
g("hello"); // error, 需要用到 `跨层` 类型转换 => `不支持 隐式`
}
第19章 特殊运算符
Note
(1) (重载) 操作符 是 `资源管理类(容器 / 智能指针 / 迭代器)` 的 设计关键
19.1 特殊运算符
1 下标 []: paraType(即下标) 可以是 任意类型
// 关联数组: map / unordered_map 继承 关联数组 的 思想 + 高级技术
struct Assoc
{
vector< pair<string, int> > vec; // vector 元素数 {名字, 值} 对
// 通常提供 const + non-const 版本
int& operator[](const string&);
const int& operator[](const string&) const;
};
// 查找 s: 找到 -> 返回 s 的引用; 否则, 创建 新 pair{s, 0} -> 返回 新 pair 的 s.second 引用
int& operator[](const string&)
{
for(auto x: vec)
if(s == x.first)
return x.second;
vec.push_back( {s, 0} );
return vec.back().second;
}
2 函数调用 (): 为 函数对象
提供 函数调用
语法
//模板 for_each: 对 `第3参数/操作对象` 调 函数调用操作符 ()
template<typename Iter, typename F>
F for_each(Iter b, Iter e, F f)
{
while(b != e)
f(*b++); // f(...) 即调 函数调用操作符 () = operator()
return f;
}
3 解引用 ->: 箭头运算符
智能指针 对象 sp(类型) 到其 internalPtr p.operator->()
(类型) 的 类型转换
, 与 sp 所指成员 m 无关
: 箭头运算符 operator->() 是 一元后置运算符
隐式调用: 需要带 1个 成员名
; 显式调用: 箭头 -> 之后可以只带 ()
template<typename T>
class Ptr
{
T* p;
public:
T* operator->() { return p; }; // 解引用 以访问 `成员`
T& operator*() { return *p; }; // ... `整个对象` // note: * 也是 解引用运算符
T& operator[](int i){ return p[i]; }; // ... `元素`
// ...
};
void f(Ptr sp)
{
// (1) 隐式调用: 需要带 1个 `成员名`
sp->m = 7; // (sp.operator->() )->m = 7;
// (2) 显式调用: 箭头 -> 之后可以只带 ()
T* q1 = sp.operator->(); // OK
T* q1 = sp->; // error
}
4 递 增/减 ++ --: 智能指针 的移动
前置: 递增, 返回 当前对象的引用(左值); 后置: 递增, 返回旧对象(右值)
// 前置
template<typename T>
Ptr& Ptr<T>::operator++()
{
// 检查 ptr + 1 是否有效
return *++ptr;
}
// 后置
template<typename T>
Ptr Ptr<T>::operator++(int) // dummyParaType
{
// 检查 ptr + 1 是否有效
Ptr<T> old { /* ... */ }; // 临时对象 record oldObj
++ptr; // 调 前置
return old;
}
5 分配/释放 new/delete: 成员函数 operator new() / operator delete() 是 隐式的 static 成员
6 用户自定义 字面值常量: 如 虚数
19.2 字符串类 String
与 std::string 不同
1 值语义:
赋值 后, 2 个 String 对象 独立
2 move
3 短字符串优化: 短字符 String 分配在 stack(char 数组)
, 而不是 自由存储
ch 和 space 不会同时使用: 匿名 union`
高效
尤其用于 `多线程`
经验表明, 短字符串 居多
class String
{
char* ptr;
int sz; // 字符数
union {
int space; // 已分配但 unused free space 大小
char ch[short_max + 1];
}
};
1) 默认 Ctor
String::String()
: sz{0}, ptr{ch}
{
ch[0] = 0;
}
2) Ctor 参数为 C 风格字符串: `不用判断 是否为 短字符串`, 就将 space 初始化为 0 ?
长字符串 时, space 才真正有效, 且 space = 0
else, space{0} <=> ch[0] = 0
String::String(const char* p)
: sz{strlen(p) },
ptr{ (sz <= short_max) ? ch : new char[sz + 1] },
space{0}
{
strcpy(ptr, p);
}
19.3 友元
1 引入
(1) non-static / non-friend 成员函数
3 层 含义
1) 有权访问 类的 private 成员
2) 位于 类 的 scope 内
3) 必须用 `含 this 指针 的 对象` 调用
|
| 限制只有 `前 2 层含义`
|/
static 成员函数
|
| 限制只有 `第 1 层含义`
|/
友元函数: friend 非成员函数
1) 显式声明 在 `dstClass 内`
2) 可以是 `another 类的成员函数`
class List_iterator
{
// ...
int* next();
};
class List
{
friend int* List_iterator::next();
// ...
};
|
| 想 `class1 所有成员函数` 成为 class2 的 友元
|/
`友元类`
class List
{
friend class List_iterator;
// ...
};
(2) 模板参数 可为 friend
template <typename T>
class X
{
friend T;
friend class T; // 多余的 "class"
};
2 发现 友员
(1) class 内 friend 声明
的 友元性延伸
到
[1] 直接外层 非类(namespace) scope
: 该 scope 在 friend 延后定义 了友元
[2] 间接/第2外层 非类(namespace) scope
: 该 scope 在 friend 提前声明 了友元
Note: 测试表明 [2] 对 友元函数 不成立 -> 待查 ???
class C1; // 间接外层 提前声明
void f1(); //
namespace N
{
class C
{
int x;
public:
friend class C1; //
friend void f1(); //
friend class C3; //
friend void f3(); //
};
class C3 // 直接外层 延后定义
{
public:
void f()
{
C c;
c.x = 1;
}
};
void f3()
{
C c;
c.x = 1;
}
}
class C1
{
public:
void f()
{
N::C c;
c.x = 1;
}
};
void f1()
{
N::C c;
c.x = 1; // compile error: "N::C::x": 无法访问 private 成员(在“N::C”类中声明)
}
int main()
{
C1 c1;
c1.f();
f1();
N::C3 c3;
c3.f();
N::f3();
}
(2) 友元函数: paraType 常为 const dstClass&, 使自己可通过 ADL 被 found
class X
{
friend void f(); // 没用
friend void h(const X&); // 可 通过 参数找到
};
void g(const X& x)
{
f(); // scope 内 找不到 f()
h(x); // X 的 友元 h()
}
3 友员 与 成员
(1) 应该选 成员、static 成员、友元?
本质问题是: "真的应该具有 访问权限 吗 ?"
1) 需 `直接访问 类的表示部分` 的 函数,才应该作 `成员`
2) 需 `修改 对象状态`
[1] 成员函数
[2] 接受 X& / X* 参数 的 `非成员函数`
3) `二元运算符` 作用于 基本类型时,
`常需 访问 运算对象 所属类的表示部分`
=> 作 `友元函数`
网友评论