第十一章 运算符重载
11.3.3 初始化
对于上面的complex类, 想要用标量来对Complex变量做初始化和赋值, 我们就需要从标量到Complex的转换。
例如,
Complex b = 3; //应该表示b.re = 3, b.im = 0;
具有一个参数的构造函数就刻画了由其参数类型到它构造起的类型的转换。
构造函数是有关如何建立起给定类型的一个值的处方。当程序里需要某个类型的值,而某个构造函数又能通过把所提供的值作为初始值或被赋的值,去创建起这样一个值的时候, 这个构造函数就会被调用。 因此具有一个参数也可能不需要显示调用。 例如,
只有在某个用户定义转换就有唯一性时,它才会被调用。
按照默认约定 , 类对象可以复制。 特别是可以用同一个类的对象的复制对该类的其他对象进行初始化。 即使声明了构造函数的地方,也可以这样做。(10.2.5 P204)
Complex a = b;//通过复制初始化
按照默认方式,类对象的复制就是其中各个成员的复制。 如果某个类所需要的不是这种默认方式, 那么久可以定以一个复制构造函数X:X(const X&), 由它提供需要的行为。
是一个错误,因为这样将会使任何调用都陷入无穷的递归。对于无穷递归的理解就是在传递形参的时候会调用构造函数创建一个副本在传递进去。例如
P(P c){}这样一个构造函数,调用如下 语句 p = c;而将这个调用的过程展开就可以得到p = c;àp = p(c); àp = p(p(c));而这样的调用会是无穷的昂。
从原则上说, 复制构造函数也用在简单的初始化中:
但是, 对复制构造函数的调用很容易通过优化去掉。我们可以等价的写
11.3.5 构造函数和转换
如果函数的每个参数有三种不同的情况,哪又怎样?对于一个参数的函数,我们就需要三个不同的版本,两个参数的函数需要9个版本。这可能成为令人厌倦的事情,而厌倦很容易变成错误的根源。标题中所谓的转换如:
本来是不同的类型而进行了貌似是同种类型的赋值操作。
11.3.7 另一些成员函数
至此, 我们只为complex类提供了构造函数和算术运算符,对于实际应用而言,这些还不够。
例如检查实部和虚部的值:
11.4 转换运算符
在所有用int来初始化一个Tiny,或者用int给tiny赋值的地方都需要范围检查。为了能对tiny变量使用普通整数运算,我们在这里定义了从Tiny到int的隐式转换 请注意,作为转换目标的类型是运算符名字的一部分,不能作为转换函数的返回值类型而重复写出。
也存在一些情况, 那里所需类型的值可以通过反复应用构造函数或转换运算符出来。
- 类可以定义转换,当一个类型的对象用在需要另一不同类型对象的地方时,自动应用这些转换。接受单个形参且未指定为explicit的构造函数定义了从其他类型到类类型的转换。
- 重载操作符转换函数则定义了从类类型到其他类型的转换。转换操作符必须为所转换类的成员,没有形参并且不定义返回值,转换操作符返回操作符所具有类型的值,例如,operator int() 返回 int。
如果存在一个到 int 的转换,则以下代码:
SmallInt si(3);
si + 3.14159; // convert si to
int, then convert to double
可这样确定:
1. 将 si 转换为 int 值。
2. 将所得 int 结果转换为
double 值并与双精度字面值常量 3.14159 相加,得到 double 值。
11.5 友元
一个常规的成员函数声明描述了三件在逻辑上互不相同的事情:
[1]该函数能访问类声明的私有部分.
[2] 该函数位与类的作用域中
[3]该函数必须用一个对象去激活.(只有一个this指针)
通过将函数声明为static, 我们可以让它具有前两种性质。通过将一个函数声明为friend,可以使它具有第一种性质。友元函数不是类的成员函数,本质上仍是普通函数,却能像成员函数那样访问类的私有成员.
11.7 基本运算符
一般来说,对于类型X,复制构造函数X(const X&)负责完成从类型X的一个对象出发的初始化。强调初始化和赋值是不同的操作绝不会过分。
1.1为什么要使用友元函数
在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员,那么类A中该函数要是类B的友元函数。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。
1.2使用友元函数的优缺点
1.2.1优点:能够提高效率,表达简单、清晰。
1.2.2缺点:友元函数破环了封装机制,尽量使用成员函数,除非不得已的情况下才使用友元函数。
因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
PS. 前面内容的补充
函数返回值的语义也与初始化的语义相同 , 可以认为返回语句所作的就是就是去初始化一个返回类型的匿名变量.(P132).
friend Matrix& operator+(const Matri&, const Maxtrix&); 这个地方为何用友元而不用成员。
11.6 避免复制的另一种有效的方式是使用句柄。
11.7 显示构造函数
Complex z = 2; //complex(2)初始化z , 隐式转换 , 而有时候这种转换不是所必须的。
11.8 下标
函数 可以用于为类的对象定义下标运算的意义。operator[]的第二个参数(下标)可以具有任何类型,这就使我们可以去定义vector、关联数组等。
运行结果:
重载运算符的几个小程序:
目录
- 输入和输出操作符
- 算术操作符和关系操作符
- 下标操作符
- 自加、自减操作符
- 成员访问操作符
1.1 输出操作符
1.1.2 说明
1)IO操作必须为非成员函数
原因:I/O操作的接口返回的是ostream&对象(只有返回左值,这样才可以连续的输出,例如cout<< a << b)。自定义的输出操作符应该与其相似。如果将其定义为成员函数(有个首默认参数this,即指向自己的指针),左操作数只能是该类型的对象,则没法办到。
2)因为要访问指定类的私有成员,所以在该类中声明输出操作符为友员函数。
3)第一个形参必须为引用。因为I/O对象不可以复制。同理返回值必须为一个引用。
4)第一个形参不可以为const,因为写入到流会改变其值。
5)第二个为引用,这样可以避免复制。参数可以为const,可以接收const对象和非const对象;否则,如果为非const,则只能接收非coust对象。一般为const,毕竟只是输出而已,不改变对象。
1.2 输入操作符
1.2.2 说明
1)输入与输出操作符的重要区别:输入操作符必须处理错误和文件结束的可能性。
2)输入如果有多个值的话,如果一个数错了,整个输入对象就错了(False),从示例中可以看出,前边赋值正确的已经生效了,后边的用了默认值,可以这样设计:如果输入错误,将整个对象复位,恢复到最初的状态,例:
2.3 赋值操作符
2.3.2 说明
1) 复制操作符完成对同类对象的赋值,参数一般为对类类型的const引用。
2)如果没有,编译器会自动合成一个,类的赋值操作符一定为类的成员变量,以便编译器是否需要自己合成一个。
3)返回值必须为*this的引用,这样就不需要创建和撤销结果的临时副本。同理的有复合赋值操作符,例如