cpp学习-第十四章-重载运算与类型转化

核心简记

重载运算符是有特殊名称的函数,有返回类型、参数列表、函数体:关键字operator+要定义的符号。参数与运算符作用的运算对象数量一样多,除了重载调用运算符,不能有默认实参。如果运算符函数是成员函数,则第一个运算对象绑定到隐式的this上。
运算符函数,或者是类的成员,或者至少含有一个类类型的参数。意味着不能更改内置类型的运算符。
既是一元又是二元的都能重载,根据参数数量自动判断。
运算符的优先级与结合律不变。

1
2
3
4
5
6
7
8
可重载:
+ - * / % ^ & | ~ ! , =
< > <= => ++ -- << >> ==
!= && || += -= /= %= ^= &=
|= *= <<= >>= [] () -> ->*
new new[] delete delete[]
不能被重载的
:: .* . ?:

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
外部函数
int operator+(A a,A a)
{...}
使用方式:
a+a; //普通表达式
operator+(a,a); //函数调用
成员函数
class A{
int operator+(A a){...}
};
使用:
a+a;
a.operator+(a);

重载应该与内置意义一致:

  1. io操作,定义位移使其io一直
  2. 检查相等,定义==,通常也有!=
  3. 有比较操作,定义<,同样应该定义其它。
  4. 返回类型与内置应该兼容。

成员还是非成员:

  1. = [] () -> 必须是成员
  2. 复合一般是成员,不是必须
  3. 改变对象状态或与给定类型密切相关的,如++ – *解引用,应该是成员。
  4. 具有对称性质的运算符,如算数、相等、关系、位。通常为了左右参数可通过自动转化而通用,因此非成员

如s+”” 若是成员+改成 “”+s则不正确。因为是先定+的意义,再转化类型,这里+的意义没有找到合适的函数。

当对象为const时,内部成员的成员也不可修改,直接不可,用成员函数的话会要求为const函数,而const的函数中a.f时a也是const的,因此要求f为const。可见
对象是const的,其成员均带const属性,这个属性递归传递!!!

重载运算

输入输出运算符

<<输出
第一个参数是非常量ostream对象的引用。因为向流写东西会改变其状态
第二个参数一般是常量引用
返回流对象引用
输入输出运算符必须是非成员函数

1
2
3
4
5
ostream &operator<<(ostream &os,const A &a)
{
os<<a.a<<a.b;
return os;
}

输入
参数1:读取流的引用
参数2:要读入的非常量对象引用
返回流的引用
必须是非成员函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
istream &operator>>(istream &is,A &a)
{
is>>a.a>>a.b;
if(is)//读完一次性检测
{
//读入成功的处理
}
else
{
//读入失败a的恢复
}
return is;
}

输入运算符必须处理输入可能失败的情况,如类型错误、尾等。输出不需要。
当输入流读取成功但格式不符合时,需要手动修改流的状态标出失败信息,通常置于failbit。

算数与关系运算符

通常定义为非成员函数,便于左右侧的转化,因为一般不修改状态,所以为常量引用。
算数运算通常返回临时副本作为结果。

1
2
3
4
5
6
A operator+(A a,A b)
{
A sum=a;
sum+=b;
retuen sum;
}

同时定义了复合与算数则用复合实现算数。

定义==准则:

  1. 如果一个类有判断相等的操作,则定义==。且方便使用库算法
  2. 定义== 应该能判断重复数据
  3. 有传递性
  4. 应该定义!=

关系运算符准则:

  1. 定义顺序关系
  2. 应该与==/!=的关系一致,如俩对象!=则俩对象应该有<关系。无法一致时不要定义

赋值运算符

必须是成员函数,如支持列表赋值:

1
2
3
4
5
6
7
8
9
class A{
public:
A &operator=(std::initializer_list<int> il)
{
free();//释放原本
//可以使用il的迭代器等操作
return *this;
}
}

复合运算符不要求是成员,但应该这样。

下标运算符

必须是成员函数
参数类型任意,是[]内传入的。
通常以访问元素的引用作为返回值,左值。最好定义const与非const的,作用范围广。

递增递减运算符

应同时定义前置与后置版本。通常为成员函数。

前置返回对象的引用,后置返回原值。

区分前置后置定义使用一个不使用的int参数:

1
2
3
4
5
6
7
8
9
class A{
public:
A operator++(int) //后置
{
auto ls=*this;
++*this;
return ls;
}
}

成员访问运算符

箭头必须是类的成员,解引用通常是成员。

箭头运算符只能改变从哪个对象获取成员:

1
2
3
4
5
6
7
8
9
10
11
类内:
A &operator*() const
{
return *p;//const对象中的指针指向不是const的,指针本身是const的
}
//必须返回可以使用->的对象
A *operator->() const //只有一个参数。单目
{
return p;//只能修改从哪个指针获取成员。
}

p->a对于p类型不同,等价于:

1
2
(*p).a //内置指针
p.operator->()->a; //类指针 会递归为内置指针形式

函数调用运算符

如果类重载了函数调用运算符,可以像函数一样使用类的对象。可以存储状态。

1
2
3
4
5
6
7
8
9
class A{
int operator()(int a=0)
{
return a;
}
}
使用
A a;
a(1);//运行调用重载

必须是成员函数,可重载。成为函数对象。

lambda:
lambda是函数对象,编译器生成未命名类与对象,含有重载函数调用。
默认下不可改变捕获的变量,因此默认生成的函数调用是const的。
通过引用捕获时编译器可以直接使用无需存为数据成员,相反,捕获的值要建立数据成员。

functional头文件中定义了大量的表示基本运算、关系等的函数对象,可用于算法库。指针之间<没有意义,但是用标准库less比较指针可以。

可调用对象有:
函数、函数指针、lambda、函数对象。不同的类型可能有一样的调用形式-返回值与参数相同。
functional头文件中的function类可以统一调用形式使用不同的类型。

1
2
3
4
5
6
7
8
9
10
function<T> f f用来存储可调用对象的空function对象。T的形式:返回类型(参数类型列表)
function<T> f(obj) f中存储可调用对象obj的副本
f f当条件,有可调用对象时为真。
f(args) 调用f中的对象
类型:
result_type 返回类型
argument_type 一个参时的类型
first_argument_type 俩个参时的类型
second_argument_type

function类型重载了调用运算符,接受自己的实参将其传递给存好的可调用对象。
不要直接传有重载的函数名,其无法区分,可手动传指针。
使用:

1
2
3
4
5
funtiction<int(int,int)> f1=add; //函数指针
... f2=A(); //函数对象
... [](int i,int j){return i+j;}; //lambda
使用是都是 f(1,2);

类型转化

转化构造函数与类型转化运算符共同定义了类型转化。
类型转化运算符是类的一种特殊成员函数,负责将类化为其它类型:

1
2
3
4
class A{
A(int i=0):a(i){} //转化构造函数,表示从
operator int() const{return a;} //类型转化运算符,表示向
}

operator 向的类型() const;
除了向void都可定义,没有显示的返回类型,没有形参,必须为类的成员函数,一般为const。但要返回指定的类型。

使用:

1
2
a=4;//4化为A,之后调用A的= 只有自定义的=作用与a
a+3;//a化为int,之后调用整数+ 没有自定义+

若a重载了+会有二义性。
定义向bool的转化普遍。但是很容易引起自动转化的冲突和隐式的错误。因此使用显示类型转化符:
explicit operator ….
使用时必须强制转化,不过当用作条件时,显示类型转化会被隐式执行,不必强制。

避免二义

向与从重复:

1
2
3
4
5
6
7
8
9
class A{
A(const B&);
}
class B{
operator A() const;
}
A f(const A&);
B b;
f(b); //二义性错误

从与从向与向重复

1
2
3
4
5
6
7
8
9
class A{
A(int);
A(double);
operator int();
operator double();
}
f(long)
f(a)//二义
A a((long)1)//二义

不要自己定义多种到内置类型的,由编译器自己转化类型最好

不同类型重载冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{
A(int);
}
class B{
B(int);
}
class C{
C(double);
}
void f(A);
void f(B);
f(10);//冲突
void f2(A);
void f2(C);
f2(10);//冲突!

转化的级别判定只有当同一个类时才有用,类型转化不止一个时,二义性。即使有一个精确匹配。

函数候选集扩大的冲突:
a+b 使用普通函数/成员函数均可。

1
2
3
4
class A{
int operator+(int b); //左为类
}
int operator+(A a,int b); //左可使用化为类的

俩个的作用范围不同,可以重载,但范围有交集,调用时可能冲突。
当左内置右类对象时,选择范围为非成员与内置。
当左为类对象时,范围为内置、非成员、成员。

1
2
3
4
5
class A{
operator+(int)
A(int)
}
a+1 //冲突,转化使用内置或使用成员都可

在选择范围内不要有任何的可行方案冲突。决定调用的版本时,左右参数与其转化在函数可选范围内,不要有冲突

反汇编

debug下不会使用inline。
这次观察下全局构造的位置

1
2
3
4
5
6
7
8
9
10
11
class A{
public:
A():a(10){}
int a;
};
A a;
int main(void)
{
A b;
}

ARM64

init-arry:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.init_array:0000000000010D60 ; ELF Initialization Function Table
.init_array:0000000000010D60 ; ===========================================================================
.init_array:0000000000010D60
.init_array:0000000000010D60 ; Segment type: Pure data
.init_array:0000000000010D60 AREA .init_array, DATA, ALIGN=3
.init_array:0000000000010D60 ; ORG 0x10D60
.init_array:0000000000010D60 EXPORT __INIT_ARRAY__
.init_array:0000000000010D60 __INIT_ARRAY__ DCB 0xFF ; DATA XREF: LOAD:0000000000000448↑o
.init_array:0000000000010D60 ; .got:__INIT_ARRAY___ptr↓o
.init_array:0000000000010D61 DCB 0xFF
.init_array:0000000000010D62 DCB 0xFF
.init_array:0000000000010D63 DCB 0xFF
.init_array:0000000000010D64 DCB 0xFF
.init_array:0000000000010D65 DCB 0xFF
.init_array:0000000000010D66 DCB 0xFF
.init_array:0000000000010D67 DCB 0xFF
.init_array:0000000000010D68 DCQ sub_69C
.init_array:0000000000010D70 DCB 0
.init_array:0000000000010D71 DCB 0
.init_array:0000000000010D72 DCB 0
.init_array:0000000000010D73 DCB 0
.init_array:0000000000010D74 DCB 0
.init_array:0000000000010D75 DCB 0
.init_array:0000000000010D76 DCB 0
.init_array:0000000000010D77 DCB 0
.init_array:0000000000010D77 ; .init_array ends

构造函数被调用处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.text:0000000000000680 sub_680 ; CODE XREF: sub_69C+8↓p
.text:0000000000000680 ; DATA XREF: LOAD:0000000000000370↑o
.text:0000000000000680
.text:0000000000000680 var_s0 = 0
.text:0000000000000680
.text:0000000000000680 ; __unwind {
.text:0000000000000680 STP X29, X30, [SP,#-0x10+var_s0]!
.text:0000000000000684 MOV X29, SP
//获取全局变量的地址,调用构造 .bss段,未初始化的
.text:0000000000000688 ADRP X8, #unk_11008@PAGE
.text:000000000000068C ADD X0, X8, #unk_11008@PAGEOFF
.text:0000000000000690 BL sub_728
.text:0000000000000694 LDP X29, X30, [SP+var_s0],#0x10
.text:0000000000000698 RET
.text:0000000000000698 ; } // starts at 680
.text:0000000000000698 ; End of function sub_680
.text:0000000000000698
.text:000000000000069C
.text:000000000000069C ; =============== S U B R O U T I N E =======================================
.text:000000000000069C
.text:000000000000069C ; Attributes: bp-based frame
.text:000000000000069C
.text:000000000000069C sub_69C ; DATA XREF: .init_array:0000000000010D68↓o
.text:000000000000069C
.text:000000000000069C var_s0 = 0
.text:000000000000069C
//initarry中存的函数地址
.text:000000000000069C ; __unwind {
.text:000000000000069C STP X29, X30, [SP,#-0x10+var_s0]!
.text:00000000000006A0 MOV X29, SP
//调用构造代理
.text:00000000000006A4 BL sub_680
.text:00000000000006A8 LDP X29, X30, [SP+var_s0],#0x10
.text:00000000000006AC RET
.text:00000000000006AC ; } // starts at 69C
.text:00000000000006AC ; End of function sub_69C

通过对linker的分析可以发现,在execve将linker与应用程序都加载了,之后linker解析动态段,加载so后调用所有的init_array。这时初始化全局变量。