为 Vector类重载算术操作符

在使用x、y坐标时,将两个矢量相加将非常简单,只要将两个x分量相加,得到最终的x分量,将两
个y分量相加,得到最终的y分量即可。根据这种描述,可能使用下面的代码:

不过,使用构造函数来完成这种工作,将更简单、更可靠:
Vector Vector: : operator+ (const Vector & b) const

return Vector (x + b.x, y + b.y);

上述代码将新的x分量和y分量传递给Vector构造函数,而后者将使用这些值来创建无名的新对象,

Vector sum;

sum.x = x + b.x;
sum.y = y + b.y:
return sum:

如果对象只存储x和y分量,则这很好。遗憾的是,上述代码无法设置极坐标值。可以通过添加另外
些代码来解决这种问题:
Vector Vector: :operator+ (const Vector & b) const

Vector sum;
sum.x = x + b. x:
sum.y = y + b.y;
sum.set_ang (sum.x, sum.y);
sum.set_mag (sum.x, sum.y);
return sum;

{

}

// return the constructed Vector

不过,使用构造函数来完成这种工作,将更简单、更可靠:
Vector Vector: : operator+ (const Vector & b) const

return Vector (x + b.x, y + b.y);

上述代码将新的x分量和y分量传递给Vector构造函数,而后者将使用这些值来创建无名的新对象,

并返回该对象的拷贝。这确保了新的Vector对象是根据构造函数制定的标准规则创建的。

提示:如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作。
这样做不仅可以避免麻烦,而且可以确保新的对象是按照正确的方式创建的。

1. 乘法
将矢量与…个数相乘,将使该矢量加长或缩短(取决于这个数)。因此,将矢量乘以3得到的矢量的长
度为原来的3倍,而方向不变。要在Vector类中实现矢量的这种行为很容易。对于极坐标,只要将长度进
行伸缩,并保持角度不变即可;对于直角坐标,只需将x和y分量进行伸缩即可。也就是说,如果矢量的
分量为5和12,则将其乘以3后,分量将分别是15和36。这正是重载的乘法操作符要完成的工作:

-

-

return a * n:

Vector Vector: : operator* (double n) const

return Vector (n * x, n * y);

和重载加法一样,上述代码允许构造函数使用新的x和y分量来创建正确的Vector对象。上述函数用
于处理 Vector值和double值相乘。可以像Time范例那样,使用一个内联友元函数来处理double与Vector
相乘:
Vector operator* (double n. const Vector & a) // friend function

// convert double times Vector to Vector times double

2. 对已经被重载的操作符进行重载
在C++中,(-)操作符已经有两种含义。首先,使用两个操作数,它是减法操作符。减法操作符是一个
…元操作符,因为它有两个操作数。其次,使用一个操作数时(如-x),它是负号操作符。这种形式被称为
·元操作符,即只有一个操作数。对于矢量来说,这两种操作(减法和符号反转)都是有意义的,因此Vector
类有这两种操作。
要从矢量A中减去矢量B,只要将分量相减即可,因此重载减法与重载加法相似:
Vector operator- (const Vector & b) const;
Vector Vector: :operator- (const Vector & b) const

return Vector (x - b.x, y - b.y) : // return the constructed Vector

// prototype
// definition

其中操作数的顺序非常重要。下面的语句:
diff = v1 - v2:
将被转换为下面的成员函数调用:
diff = vl.operator- (v2) :
这意味着将从隐式矢量参数减去以显式参数传递的矢量,所以应使用x-b.x,而不是b.x-x。

接下来,来看一元负号操作符,它只使用一个操作数。将这个操作符用于数字(如-x)时,将改变它
的符号。因此,将这个操作符用于矢量时,将反转矢量的每个分量的符号。更准确地说,函数应返回一个
与原来的矢量相反的矢量(对于极坐标,长度不变,但方向相反。许多没受过数学训练的政治家在这方面
是天才)。下面是重载负号的原型和定义:
Vector operator-() const;
Vector Vector: : operator-() const

return Vector (-x, -y):

现在,operator-()有两种不同的定义。这是可行的,因为它们的特征标不同。可以定义(-)操作符的一
元和二元版本,因为C++提供了该操作符的一元和二元版本。对于只有二元形式的操作符(如除法操作符),
只能将其重载为二元操作符。

记住:因为操作符重载是通过函数来实现的,所以只要操作符函数的特征标不同,使用的操作符数量

第11章 使用类

与相应的内置C++操作符相同,就可以多次重载同一个操作符。

11.5.3 对实现的说明

前几节介绍的实现在Vector对象中存储了矢量的直角坐标和极坐标,但公有接口并不依赖于这一事实。
所有接口都只要求能够显示这两种表示,并可以返回各个值。内部实现方式可以完全不同。正如前面指出
的,对象可以只存储x和y分量,而返回矢量长度的magval()方法可以根据x和y的值来计算出长度,而
不是查找对象中存储的这个值。这种方法改变了实现,但用户接口不变。将接口与实现分离是OOP的目标
之…,这样允许对实现进行调整,而无需修改使用这个类的程序中的代码。
这两种实现各有利弊。存储数据意味着对象将占据更多的内存,每次Vector对象被修改时,都需要更
新直角坐标和极坐标表示;但查找数据的速度比较快。如果应用程序经常需要访问矢量的这两种表示,则
这个例子采用的实现比较合适;如果只是偶尔需要使用极坐标,则另一种实现更好。可以在一个程序中使
用一种实现,而在另一个程序中使用另一种实现,但它们的用户接口相同。

11.5.4 使用Vector类来模拟随机行走

程序清单 11.15 是一个小程序,它使用了修订后的Vector 类。该程序模拟了著名的醉鬼走路问题
(Drunkard Walk problem)。实际上,醉鬼被认为是一个有许多健康问题的人,而不是大家娱乐消遣的谈资,
因此这个问题通常被称为随机行走问题。其意思是,将一个人领到街灯柱下。这个人开始走动,但每一步
的方向都是随机的(与前一步不同)。这个问题的一种表述是,这个人走到离灯柱50英尺处需要多少步。
从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。
程序清单11.15允许用户选择行走距离和步长。该程序用一个变量来表示位置(一个矢量),并报告到
达指定距离处(用两种格式表示)所需的步数。可以看到,行走者前进得相当慢。虽然走了1000步,每步
的距离为2英尺,但离起点可能只有50英尺。这个程序将行走者所走的净距离(这里为50英尺)除以步
数,来指出这种行走方式的低效性。随机改变方向使得该平均值远远小于步长。为了随机选择方向,该程
序使用了标准库函数rand()、srand()和time()(参见程序说明)。请务必将程序清单11.14和程序清单 11.15
·起进行编译。

程序清单 11.15 randwalk.cpp
// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include 
#include 
#include 
#include "vect.h"
int main ()

using namespace std:
using VECTOR :: Vector:
srand (time (0)):
double direction;
Vector step:
Vector result (0.0, 0.0);
unsigned long steps = 0;
double target:
double dstep:
cout << "Enter target distance (q to quit): ":
while (cin >> target)

cout << "Enter step length: ";
if (! (cin >> dstep))
break;

// rand(), srand () prototypes
// time () prototype

// seed random-number generator
while (result.magval ()< target)

direction = rand () % 360:
step.set (dstep, direction, 'p');
result = result + step;
steps++:

cout << "After " << steps << " steps, the subject "
"has the following location: \n";
cout << result << endl;
result.polar_mode ():
cout << " or\n" << result << endl:
cout << "Average outward distance per step = "
<< result.magval ()/steps << endl;
steps = 0:
result.set (0.0, 0.0):
cout << "Enter target distance (q to quit): ";

cout << "Bye!\n":

return 0;

-

注意:可能需要使用 stdlib.h和time.h,而不是cstdlib和ctime。如果所用的系统不支持名称空间,请
省略下面一行:

using VECTOR: : Vector;
下面是程序清单11.13~11.15组成的程序的运行情况:
Enter target distance (q to quit) : 50
Enter step length: 2
After 253 steps, the subject has the following location:
(x, y) = (46.1512, 20.4902)
or
(m, a) = (50.495, 23.9402)
Average outward distance per step = 0.199587
Enter target distance (q to quit) : 50
Enter step length: 2
After 951 steps, the subject has the following location:
(x, y) = (-21.9577, 45.3019)
or
(m, a) = (50.3429, 115.8593)
Average outward distance per step = 0.0529362
Enter target distance (q to quit) : 50
Enter step length: 1
After 1716 steps, the subject has the following location:
(x, y)= (40.0164, 31.1244)
or
(m, a) = (50.6956, 37.8755)
Average outward distance per step = 0.0295429
Enter target distance (q to quit) : q
Bye!

这种处理的随机性使得每次运行结果都不同,即使初始条件相同。不过,平均而言,步长减半,步数
将为原来的4倍。概率理论表明,平均而言,步数(N)、步长(s),净距离D之间的关系如下:
N=(D/s)2
这只是平均情况,但每次试验结果可能相差很大。例如,进行1000次试验(走50英尺,步长为2英
尺)时,平均步数为636(与理论值625非常接近),但实际步数位于91~3951之间。同样,进行1000次
试验(走50英尺,步长为1英尺)时,平均步数为2557(与理论值2500非常接近),但实际步数位于345~
10882 之间。因此,如果发现自己在随机行走时,请保持自信,迈大步走。虽然在蜿蜒前进的过程中仍旧

第11章 使用类

无法控制前进的方向,但至少会走得远一点。

程序说明
首先需要指出的是,在程序清单11.15中使用VECTOR名称空间非常方便。下面的using声明:
using VECTOR :: Vector;
使Vector类的名称可用。因为所有的Vector类方法的作用域都为整个类,所以导入类名后,无需提供其他
using声明,就可以使用Vector的方法。
接下来谈谈随机数。标准ANSIC库(C++也有)中有一个rand()函数,它返回一个从0到某个值(取
决于实现)之间的随机整数。该程序使用求模操作数来获得一个0~359的角度值。rand()函数将一种算法
用于…个初始种子值来获得随机数,该随机值将用作下一次函数调用的种子,依此类推。这些数实际上是
伪随机数,因为10次连续的调用通常将生成10个同样的随机数(具体值取决于实现)。不过,srand()函
数允许覆盖默认的种子值,重新启动另一个随机数序列。该程序使用time(0)的返回值来设置种子。time
(0)函数返回当前时间,通常为从某一个日期开始的秒数(更广义地,time()接受time_t变量的地址,将

时间放到该变量中,并返回它。将0用作地址参数,可以省略time_t变量声明)。因此,下面的语句:
srand (time (0) ) :
在每次运行程序时,都将设置不同的种子,使随机输出看上去更为随机。头文件cstdlib(以前为stdlib.h)
包含了srand()和rand()的原型,而ctime(以前是time.h)包含了time()的原型。
该程序使用result矢量记录行走者的前进情况。内循环每轮将step矢量设置为新的方向,并将它与当
前的result矢量相加。当result的长度超过指定的距离后,该循环结束。
程序通过设置矢量的模式,用直角坐标和极坐标显示最终的位置。
下面这条语句:
result = result + step:
将result设置为‘r’模式,而不管result和step的初始模式是什么。这样做的原因如下。首先,加法操作
符函数创建并返回一个新矢量,该矢量存储了这两个参数的和。该函数使用默认构造函数以‘r’模式创建
矢量。因此,被赋给 result 的矢量的模式为‘r'。默认情况下,赋值时将分别给每个成员变量赋值,因此
将‘r’赋给了 result.mode。如果偏爱其他方式,例如,result保留原来的模式,可以通过为类定义赋值操
作符来覆盖默认的赋值方式。第12章将介绍这样的范例。
顺便说一句,在将一系列位置存储到文件中很容易。首先包含头文件fstream,声明一个ofstream对象,
将其同 ·· 个文件关联起来:
#include

ofstream fout;
fout.open ("thewalk.txt");
然后,在计算结果的循环中加入类似于下面的代码:
fout << result << endl;
这将调用友元函数 operator << (fout, result),导致引用参数os指向fout,从而将输出写入到文件中。您
还可以使用fout将其他信息写入到文件中,如当前由cout显示的总结信息。

你可能感兴趣的:(c++)