文章目录
1. 运算符重载
运算符重载是一种形式的C+多态。
实际上,很多C++(也包括C语言)运算符已经被重载。例如,将*
运算符用于地址,将得到存储在这个地址中的值,但将它用于两个数时,得到的将是它们的乘积。
C++根据操作数的数目和类型来决定采用哪种操作。
C++允许将运算符重载扩展到用户定义的类型,例如,允许使用+将两个对象相加。编译器将根据操作数的数目和类型决定使用哪种加法定义。
重载运算符可使代码看起来更自然。
例如,将两个数组相加是种常见的运算。通常,需要使用下面这样的for循环来实现
for(int i = 0; i < 20; i++) {
evening [i] =sam[i]+ janet [i]; //add element by element
}
但在C++中, 可以定义一个表示数组的类, 并重载+
运算符。于是便可以有这样的语句:
evening = sam + janet //add two array obi
这种简单的加法表示法隐藏了内部机理,并强调了实质,这是OOP的另一个目标。
要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:
operator op( argument-list)
例如, operator+()
重载+
运算符, operator*()
重载*
运算符。
op必须是有效的C++运算符,不能虚构个新的符号。
例如,不能有operator@()
这样的函数,因为C++中没有@
运算符。然而, operator[]()
函数
将重载[]
运算符,因为[]
是数组索引运算符。
例如,假设有一个 Salesperson类,并为它定义了一个 operato+()
成员函数,以重载+
运算符,以便能够将两个 Salesperson对象的销售额相加,则如果 district2、sid和sara都是 Salesperson类对象,便可以编写这样的等式
district2 = sid + sara;
编译器发现,操作数是 Salesperson类对象,因此使用相应的运算符函数替换上述运算符:
district =sid.operator+(sara);
然后该函数将隐式地使用sid(因为它调用了方法),而显式地使用sara对象(因为它被作为参数传递来计算总和,并返回这个值。
当然最重要的是,可以使用简便的+
运算符表示法,而不必使用笨拙的函数表示法。
2. 一个演示运算符重载的例子
operator_overload.cpp
这个例子重载 +
运算符, 实现三个类对象相加?
d4 = d1 + d2 + d3;
#include <iostream>
#include <string>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
class Demo {
private:
int iAge;
string name;
public:
Demo(int i, const string & name);
void Show();
Demo operator+(const Demo &d) const;
};
void Demo::Show()
{
cout << "iAge:" << iAge << endl;
}
Demo::Demo(int i = 0, const string & name = "null")
{
this->iAge = i;
this->name = name;
}
Demo Demo::operator+(const Demo &d) const
{
Demo result;
cout << "this->name:" << this->name << ", this->age: " << this->iAge << ", d.name:" << d.name << ", d.iAge: " << d.iAge << endl;
result.iAge = this->iAge + d.iAge;
return result;
}
int main(int argc, char *argv[])
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
Demo d4(0 , "d4");
Demo d1(11, "d1");
Demo d2(12, "d2");
Demo d3(13, "d3");
d4 = d1 + d2 + d3;
d4.Show();
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
运行结果:
meng-yue@ubuntu:~/MengYue/c++/use_class/01$ ./operator_overload
---------------开始--->公众号:梦悦foundation---------------
this->name:d1, this->age: 11, d.name:d2, d.iAge: 12
this->name:null, this->age: 23, d.name:d3, d.iAge: 13
iAge:36
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/use_class/01$
+
号运算符是从左到右计算结果的。
看结果,应该是先算 d1 + d2
,所以打印下面的这条语句,计算d1+d2
this->name:d1, this->age: 11, d.name:d2, d.iAge: 12
然后返回一个临时对象,因为是临时对象,所以name是默认的 “null”, 再加上 d3,所以打印下面的这条语句
this->name:null, this->age: 23, d.name:d3, d.iAge: 13
3. 重载运算符: 作为成员函数还是非成员函数
对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。
一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。
例如,Demo类的加法运算符在Demo类声明中的原型如下:
Demo operator+(const Demo &d) const; //member version
这个类也可以使用下面的原型:
//nonmember version
friend Demo operator+(const Demo &d1, const Demo &d2);
加法运算符需要两个操作数。
对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;对于友元版本来说,两个操作数都作为参数来传递。
注意:非成员版本的重运算符函数所需的形参数目与运算符使用的操作数数目相同;而成员版本所需的参数数目少ー个,因为其中的一个操作数是被隐式地传递的调用对象。
这两个原型都与表达式 T2 + T3
匹配,其中T2 和 T3 都是Demo类型对象。
也就是说,编译器将下面的语句
T1 = T2 + T3;
转换为下面两个的任何一个
T1 =T2operator+(T3); // member function
T1 =operator(T2, T3); //non member function
记住,在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。
因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误那么哪种格式最好呢?
对于某些运算符来说(如前所述),成员函数是唯一合法的选择。在其他情况下,这两种格式没有太大的区别。
4. 再谈重载: 一个是矢量类
下面介绍另一种使用了运算符重载和友元的类设计一个表示矢量的类。
这个类还说明了类设计的其他方面,例如,在同一个对象中包含两种描述同一样东西的不同方式等。
即使并不关心矢量,也可以在其他情况下使用这里介绍的很多新技术。
矢量( vector),是工程和物理中使用的一个术语,它是个有大小和方向的量。
vector.h
注意,程序清单vector.h中4个报告分量值的函数是在类声明中定义的,因此将自动成为内联函数。
这些函数非常短,因此适于声明为内联函数。
因为它们都不会修改对象数据,所以声明时使用了 const限定符。第10章介绍过,这种句法用于声明那些不会对其显式访问的对象进行修改的函数。
程序清单vector.cpp列出了程序清单vector.h中声明的方法和友元函数的定义,该清单利用了名称空间的开放性,将方法定义添加到 VECTOR名称空间中。
请注意,构造函数和reset()函数都设置了矢量的直角坐标和极坐标表示,因此需要这些值时,可直接使用而无需进行计算。
另外,C++的内置数学函数在使用角度时以弧度为单位,所以函数在度和弧度之间进行转换。该 Vector类实现对用户隐藏了极坐标和直角坐标之间的转换以及弧度和度之间的转换等内容。
用户只需知道:类在使用角度时以度为单位,可以使用两种等价的形式来表示矢量。
// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
class Vector
{
public:
enum Mode {
RECT, POL};
// RECT for rectangular, POL for Polar modes
private:
double x; // horizontal value
double y; // vertical value
double mag; // length of vector
double ang; // direction of vector in degrees
Mode mode; // RECT or POL
// private methods for setting values
void set_mag();
void set_ang();
void set_x();
void set_y();
public:
Vector();
Vector(double n1, double n2, Mode form = RECT);
void reset(double n1, double n2, Mode form = RECT);
~Vector();
double xval() const {
return x;} // report x value
double yval() const {
return y;} // report y value
double magval() const {
return mag;} // report magnitude
double angval() const {
return ang;} // report angle
void polar_mode(); // set mode to POL
void rect_mode(); // set mode to RECT
// operator overloading
Vector operator+(const Vector & b) const;
Vector operator-(const Vector & b) const;
Vector operator-() const;
Vector operator*(double n) const;
// friends
friend Vector operator*(double n, const Vector & a);
friend std::ostream & operator<<(std::ostream & os, const Vector & v);
};
} // end namespace VECTOR
#endif
vector.cpp
// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vector.h" // includes <iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;
namespace VECTOR
{
// compute degrees in one radian
//360 / π
const double Rad_to_deg = 45.0 / atan(1.0);
// should be about 57.2957795130823
// private methods
// calculates magnitude from x and y
void Vector::set_mag()
{
mag = sqrt(x * x + y * y);
}
//计算当前(x,y)坐标对应的弧度值
void Vector::set_ang()
{
if (x == 0.0 && y == 0.0)
ang = 0.0;
else
ang = atan2(y, x);
}
// set x from polar coordinate
void Vector::set_x()
{
x = mag * cos(ang);
}
// set y from polar coordinate
void Vector::set_y()
{
y = mag * sin(ang);
}
// public methods
Vector::Vector() // default constructor
{
x = y = mag = ang = 0.0;
mode = RECT;
}
// construct vector from rectangular coordinates if form is r
// (the default) or else from polar coordinates if form is p
Vector::Vector(double n1, double n2, Mode form)
{
mode = form;
if (form == RECT)
{
x = n1;
y = n2;
set_mag();
set_ang();
}
else if (form == POL)
{
mag = n1;
ang = n2 / Rad_to_deg;
set_x();
set_y();
}
else
{
cout << "Incorrect 3rd argument to Vector() -- ";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
// reset vector from rectangular coordinates if form is
// RECT (the default) or else from polar coordinates if
// form is POL
void Vector::reset(double n1, double n2, Mode form)
{
mode = form;
if (form == RECT)
{
x = n1;
y = n2;
set_mag();
set_ang();
}
else if (form == POL)
{
mag = n1;
ang = n2 / Rad_to_deg;
set_x();
set_y();
}
else
{
cout << "Incorrect 3rd argument to Vector() -- ";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
Vector::~Vector() // destructor
{
}
void Vector::polar_mode() // set to polar mode
{
mode = POL;
}
void Vector::rect_mode() // set to rectangular mode
{
mode = RECT;
}
// operator overloading
// add two Vectors
Vector Vector::operator+(const Vector & b) const
{
return Vector(x + b.x, y + b.y);
}
// subtract Vector b from a
Vector Vector::operator-(const Vector & b) const
{
return Vector(x - b.x, y - b.y);
}
// reverse sign of Vector
Vector Vector::operator-() const
{
return Vector(-x, -y);
}
// multiply vector by n
Vector Vector::operator*(double n) const
{
return Vector(n * x, n * y);
}
// friend methods
// multiply n by Vector a
Vector operator*(double n, const Vector & a)
{
return a * n;
}
// display rectangular coordinates if mode is RECT,
// else display polar coordinates if mode is POL
std::ostream & operator<<(std::ostream & os, const Vector & v)
{
if (v.mode == Vector::RECT)
os << "(x,y) = (" << v.x << ", " << v.y << ")";
else if (v.mode == Vector::POL)
{
os << "(m,a) = (" << v.mag << ", "
<< v.ang * Rad_to_deg << ")";
}
else
os << "Vector object mode is invalid";
return os;
}
} // end namespace VECTOR
Vector::set_mag是同来计算斜边的!
void Vector::set_mag()
{
mag = sqrt(x * x + y * y);
}
也可以以另一种方式来设计这个类。例如,在对象中存储直角坐标而不是极坐标,并使用方法 magval()和 angval()来计算极坐标。
对于很好进行坐标转换的应用来说,这将是一种效率更高的设计。
另外,方法reset()并非必不可少的。假设 shove是一个 Vector对象,而您编写了如下代码:
shove. reset(100, 300);
可以使用构造函数来得到相同的结果:
shove Vector(100, 300); // create and assign a temporary object
然而,方法reset()直接修改 shove的内容,而使用构造函数将增加额外的步骤:创建一个临时对象,然后将其赋给 shove。
这些设计决策遵守了OOP传统,即将类接口的重点放在其本质上(抽象模型),而隐藏细节。
这样,当用户使用 Vector类时,只需考虑矢量的通用特性,例如,矢量可以表示位移,可以将两个矢量相加等。
使用分量还是大小和方向来表示矢量已无关紧要,因为程序员可以设置矢量的值,并选择最方便的格式来显示它们。
下面更详细地介绍 Vector类的一些特性
1. 使用状态成员
Vector类储存了矢量的直角坐标和极坐标。
它使用名为mode的成员来控制使用构造函数、 reset()方法和重载的 operator<<()函数使用哪种形式,其中枚举RECT表示直角坐标模式(默认值)、POL表示极坐标模式。
这样的成员被称为状态成员( state member),因为这种成员描述的是对象所处的状态。
要知道具体含义,请看构造函数的代码
// construct vector from rectangular coordinates if form is r
// (the default) or else from polar coordinates if form is p
Vector::Vector(double n1, double n2, Mode form)
{
mode = form;
if (form == RECT)
{
x = n1;
y = n2;
set_mag();
set_ang();
}
else if (form == POL)
{
mag = n1;
ang = n2 / Rad_to_deg;
set_x();
set_y();
}
else
{
cout << "Incorrect 3rd argument to Vector() -- ";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
如果第三个参数是RECT或省略了(原型将默认值设置为RECT),则将输入解释为直角坐标;
如果为POL,则将输入解释为极坐标:
Vector folly(3.0, 4.0); // set x =3, y = 4
Vector foolery(200, 30.0, VECTOR::Vector::POL); //set mag =20, ang =30
标识符POL的作用域为类,因此类定义可使用未限定的名称。
但全限定名为 VECTOR:::Vector::POL,因为POL是在 Vector类中定义的,而Vector是在名称空间 VECTOR中定义的。
注意,如果用户提供的是x值和y值,则构造函数将使用私有方法 set_ mag()和 set _ang()来设置距离和角度值; 如果提供的是距离和
角度值,则构造函数将使用set_x()和set_y()方法来设置x值和y值。
另外,如果用户指定的不是RFCT或POL,则构造函数将显示一条警告息,并将状态设置为RECT。
看起来好像难以将RECT和POL外的其他值传递给枃造函数,因为第三个参数的类型为VECTOR::Vector::Mode。像下面这样的调用无法通过编译,因为诸如2等整数不能隐式地转换为枚举类型:
Vector rector(20.0, 30.0, 2); // type mismatch -2 not an enum type
然而,机智而好奇的用户可尝试下面这样的代码,看看结果如何:
Vector rector(200, 30.0, VECTOR::Vector::Mode(2)); // type cast
就这里而言,编译器将发出警告。
接下来, operator<<()函数也使用模式来确定如何显示值
// display rectangular coordinates if mode is RECT,
// else display polar coordinates if mode is POL
std::ostream & operator<<(std::ostream & os, const Vector & v)
{
if (v.mode == Vector::RECT)
os << "(x,y) = (" << v.x << ", " << v.y << ")";
else if (v.mode == Vector::POL)
{
os << "(m,a) = (" << v.mag << ", "
<< v.ang * Rad_to_deg << ")";
}
else
os << "Vector object mode is invalid";
return os;
}
由于 operator<<()是一个友元函数,而不在类作用域内,因此必须使用 Vector::RECT,而不能使用RECT。
但这个友元函数在名称空间 VECTOR中,因此无需使用全限定名 VECTOR::Vector::RECT。
设置模式的各种方法只接受RECT和POL为合法值,因此该函数中的else永远不会执行。但进行检查还是一个不错的主意,它有助于捕获难以发现的编程错误。
2. 为 Vector类重载算术运算符
在使用x、y坐标时,将两个矢量相加将非常简单,只要将两个x分量相加,得到最终的x分量,将两个y分量相加,得到最终的y分量即可。
根据这种描述,可能使用下面的代码:
Vector Vector::operator+(const Vector & b) const
{
Vector sum;
sum.x = x + b.x;
sum.y = y + b.y;
return sum; //incomplete version
}
如果对象只存储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; // version duplicates needlessly
}
然而,使用构造函数来完成这种工作,将更简单、更可靠:
Vector Vector::operator+(const Vector b) const
{
return Vector(x + b.x, y + b.y); //return the constructed Vector
}
上述代码将新的x分量和y分量传递给 Vector构造函数,而后者将使用这些值来创建无名的新对象,并返回该对象的副本。
这确保了新的 Vector对象是根据构造函数制定的标准规则创建的。
提示:如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作。
这样做不仅可以避免麻烦,而且可以确保新的对象是按照正确的方式创建的。
3. 乘法
将矢量与一个数相乘,将使该矢量加长或缩短(取决于这个数)。
因此,将矢量乘以3得到的矢量的长度为原来的三倍,而方向不变。
要在 Vector类中实现矢量的这种行为很容易。对于极坐标,只要将长度进行伸缩,并保持角度不变即可;对于直角坐标,只需将x和y分量进行伸缩即可。
也就是说,如果矢量的分量为5和12,则将其乘以3后,分量将分别是15和36。
这正是重载的乘法运算符要完成的工作
Vector Vector::operator*(double n)const
{
return Vector (n * x, n *y);
}
和重载加法一样,上述代码允许构造函数使用新的x和y分量来创建正确的 Vector对象。上述函数用于处理 Vector值和 double值相乘。
另外处理 double值和 Vector值相乘 使用一个内联友元函数来处理 double与 Vector相乘:
Vector operator*(double n, const Vector a)//friend function
{
return a * n; // convert double times Vector to Vector times double
}
4. 对已重载的运算符进行重载
在C++中, -运算符已经有两种含义。
首先,使用两个操作数,它是减法运算符。
减法运算符是一个二元运算符,因为它有两个操作数。其次,使用一个操作数时(如-x),它是负号运算符。
这种形式被称为一元运算符,即只有一个操作数。
对于矢量来说,这两种操作(减法和符号反转)都是有意义的,因此Vector类有这两种操作。
要从矢量A中减去矢量B,只要将分量相减即可,因此重载减法与重载加法相似:
Vector operator-(const Vector &b)const ;// prototype
Vector Vector::operator-(const Vector b) const // definition
{
return Vector(x-b.x, y-b.y); // return the constructed vector
}
操作数的顺序非常重要。下面的语句:
diff = v1 - v2;
将被转换为下面的成员函数调用
diff = v1. operator-(v2);
这意味着将从隐式矢量参数减去以显式参数传递的矢量,所以应使用x-b.x,而不是b.xーx。
接下来,来看一元负号运算符,它只使用一个操作数。
将这个运算符用于数字(如-x)时,将改变它的符号。
因此,将这个运算符用于矢量时,将反转矢量的每个分量的符号。更准确地说,函数应返回一个与原来的矢量相反的矢量(对于极坐标,长度不变,但方向相反)。
下面是重载负号的原型和定义:
Vector operator-() const;
Vector Vector::operator-() const
{
return Vector (-x, -y);
}
现在, operator-()有两种不同的定义。
这是可行的,因为它们的特征标不同。
可以定义-
运算符的一元和二元版本,因为C+提供了该运算符的一元和二元版本。
对于只有二元形式的运算符(如除法运算符),只能将其重载为二元运算符。
注意:因为运算符重载是通过函数来实现的,所以只要运算符函数的特征标不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符。
5. 使用 Vector类来模拟随机漫步
程序清单randwalk.cpp
是一个小程序,它使用了修订后的 Vector类。
该程序模拟了著名的醉鬼走路问题( Drunkard Walk problem)。
实际上,醉鬼被认为是一个有许多健康问题的人,而不是大家娱乐消遺的谈资,因此这个问题通常被称为随机漫步问题。
其意思是,将一个人领到街灯柱下。这个人开始走动,但每一步的方向都是随机的(与前一步不同)。
这个问题的一种表述是,这个人走到离灯柱50英尺处需要多少步。从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。
程序清单randwalk.cpp
允许用户选择行走距离和步长。该程序用一个变量来表示位置(一个矢量),并报告到达指定距离处(用两种格式表示)所需的步数。可以看到,行走者前进得相当慢。
虽然走了1000步,每步的距离为2英尺,但离起点可能只有50英尺。这个程序将行走者所走的净距离(这里为50英尺)除以步数,来指出这种行走方式的低效性。
随机改变方向使得该平均值远远小于步长。
为了随机选择方向,该程序使用了标准库函数rand()、 srand()和time()
randwalk.cpp
// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib> // rand(), srand() prototypes
#include <ctime> // time() prototype
#include "vector.h"
int main()
{
using namespace std;
using VECTOR::Vector;
srand(time(0)); // seed random-number generator
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;
while (result.magval() < target)
{
direction = rand() % 360;
step.reset(dstep, direction, POL);
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.reset(0.0, 0.0);
cout << "Enter target distance (q to quit): ";
}
cout << "Bye!\n";
/* keep window open
cin.clear();
while (cin.get() != '\n')
continue;
cin.get();
*/
return 0;
}
该程序使用 using声明导入了 Vector,因此该程序可使用 Vector::POL,而不必使用 VECTOR::Vector::POL。
首先需要指出的是,在程序清单randwalk.cpp
中使用 VECTOR名称空间非常方便。
下面的 using声明使 Vector类的名称可用:
using VECTOR::Vector
因为所有的 Vector类方法的作用域都为整个类,所以导入类名后,无需提供其他 using声明,就可以使用 Vector的方法。
接下来谈谈随机数。
标准 ANSI C库(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设置为RECT模式,而不管 result和step的初始模式是什么:
result = result + step;
这样做的原因如下。
首先,加法运算符函数创建并返回一个新矢量,该矢量存储了这两个参数的和该函数使用默认构造函数以RECT模式创建矢量。
因此,被赋给 result的矢量的模式为RECT。默认情况
下,赋值时将分别给每个成员变量赋值,因此将RECT赋给了 result. mode。
程序运行结果:
book@book-desktop:~/meng-yue/c++/use_class/01/vector$ ./randwalk
Enter target distance (q to quit): 50
Enter step length: 2
After 707 steps, the subject has the following location:
(x,y) = (-4.696, -50.2493)
or
(m,a) = (50.4682, -95.339)
Average outward distance per step = 0.0713836
Enter target distance (q to quit): q
Bye!
book@book-desktop:~/meng-yue/c++/use_class/01/vector$
顺便说一句,在将一系列位置存储到文件中很容易。首先包含头文件 fstream,声明一个 ofstream对象,将其同一个文件关联起来:
#include <fstream>
ofstream fout;
fout.open("randwalk.txt");
然后,在计算结果的循环中加入类似于下面的代码:
fout << result << endl;
这将调用友元函数operator <<(fout,result ),导致引用参数os指向fout,从而将输出写入到文件中。
您还可以使用fout将其他信息写入到文件中,如当前由cout显示的总结信息。
randwalk01.cpp
// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib> // rand(), srand() prototypes
#include <ctime> // time() prototype
#include <fstream>
#include "vector.h"
int main()
{
using namespace std;
using VECTOR::Vector;
srand(time(0)); // seed random-number generator
double direction;
Vector step;
Vector result(0.0, 0.0);
unsigned long steps = 0;
double target;
double dstep;
ofstream fout;
fout.open("randwalk.txt");
cout << "Enter target distance (q to quit): ";
while (cin >> target)
{
cout << "Enter step length: ";
if (!(cin >> dstep))
break;
while (result.magval() < target)
{
direction = rand() % 360;
step.reset(dstep, direction, Vector::POL);
result = result + step;
steps++;
fout << result << endl;
}
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.reset(0.0, 0.0);
cout << "Enter target distance (q to quit): ";
}
cout << "Bye!\n";
/* keep window open
cin.clear();
while (cin.get() != '\n')
continue;
cin.get();
*/
return 0;
}
程序运行结果:
book@book-desktop:~/meng-yue/c++/use_class/01/vector$ ./randwalk01
Enter target distance (q to quit): 50
Enter step length: 2
After 867 steps, the subject has the following location:
(x,y) = (44.4564, 23.0604)
or
(m,a) = (50.0814, 27.4166)
Average outward distance per step = 0.0577641
Enter target distance (q to quit):
打开randwalk.txt 可以看出来记录了 867 个数据,就是行走的路径都记录下来了
6. 数学知识
1. 极坐标
极坐标系(polar coordinates)是指在平面内由极点、极轴和极径组成的坐标系。在平面上取定一点O,称为极点。从O出发引一条射线Ox,称为极轴。再取定一个单位长度,通常规定角度取逆时针方向为正。这样,平面上任一点P的位置就可以用线段OP的长度ρ以及从Ox到OP的角度θ来确定,有序数对(ρ,θ)就称为P点的极坐标,记为P(ρ,θ);ρ称为P点的极径,θ称为P点的极角。
2. 三角函数
45度 和 -135度的tan(x)值都是 1,只通过 atan没有办法区分在那一个象限,除非你不关心,所以有了atan2