练习代码
由于无法用一个数来表示矢量,因此应该创建一个类来表示矢量。矢量的相加和相减与普通数学运算有相似之处,所以应重载运算符使之能用于矢量。
本节将实现一个二维矢量,描述二维矢量需要有两个数,但有两种方式:
- 用长度和角度表示(极坐标);
用水平分量 x 和垂直分量 y 表示(直角坐标)。
#ifndef VECTOR_H_#define VECTOR_H_#include <iostream>namespace VECTOR {class Vector {public:enum Mode {RECT, // 直角坐标系POL // 极坐标系};private:double x; // 水平坐标double y; // 垂直坐标double ang; // 角度double mag; // 长度Mode mode; // 采用的坐标系// 设置私有成员的函数void setMag(); // 根据 x, y 设置 magvoid setAng(); // 根据 x, y 设置 angvoid setX(); // 根据 mag, ang 设置 xvoid setY(); // 根据 mag, ang 设置 ypublic:Vector();// 构造函数, 默认采用直角坐标系表示法Vector(double n1, double n2, Mode form = RECT);~Vector();// 重置 Vector 值, 默认采用直角坐标系表示法void reset(double n1, double n2, Mode form = RECT);double getXVal() const { return x; }double getYVal() const { return y; }double getAngVal() const { return ang; }double getMagVal() const { return mag; }void setPolarMode();void setRectMode();// 重载运算符 —— 成员函数Vector operator+(const Vector & vec) const;Vector operator-(const Vector & vec) const;Vector operator-() const;Vector operator*(double d) const;// 重载运算符 —— 友元函数friend Vector operator*(double d, const Vector & vec);friend std::ostream & operator<<(std::ostream & out, const Vector & vec);};} // end namespace VECTOR#endif // VECTOR_H_
#include "vector.h"#include <cmath>namespace VECTOR {using std::sqrt;using std::sin;using std::cos;using std::atan;using std::atan2;using std::cout;const double RadToDeg = 45.0 / atan(1.0); // 计算一弧度对应的角度/*** Private methods*/void Vector::setAng() {// 这里其实不应该用 == 的, double 类型的 == 并不准确if (x == 0.0 && y == 0.0)ang = 0.0;elseang = atan2(y, x);}void Vector::setMag() {mag = sqrt(x * x + y * y);}void Vector::setX() {x = mag * cos(ang);}void Vector::setY() {y = mag * sin(ang);}/*** Public methods —— Constructor And Destructor*/Vector::Vector() {cout << "Default Constructor.\n";x = y = ang = mag = 0.0;mode = RECT;}Vector::Vector(double n1, double n2, Mode form) {cout << "Constructor. ";mode = form;if (form == RECT) {x = n1;y = n2;setMag();setAng();} else if (form == POL) {mag = n1;ang = n2 / RadToDeg;setX();setY();} else {cout << "构造方法 Vector() 的第三个参数是无效的. 对象的值将被设置为 0, 并且采用直角坐标系.";x = y = ang = mag = 0.0;mode = RECT;}cout << std::endl;}Vector::~Vector() {cout << "Destructor.\n";}/*** Public Function.*/// 重新设置 vector 的值void Vector::reset(double n1, double n2, Mode form) {mode = form;if (mode == RECT) {x = n1;y = n2;setAng();setMag();} else if (mode == POL) {mag = n1;ang = n2 /RadToDeg;setX();setY();} else {cout << "reset() 的第三个参数是无效的. 对象的值将被设置为 0, 并且采用直角坐标系.\n";x = y = ang = mag = 0.0;mode = RECT;}}void Vector::setPolarMode() { mode = RECT; }void Vector::setRectMode() { mode = POL; }/*** Operator overload —— Member Function*/Vector Vector::operator+(const Vector &vec) const {return Vector(x + vec.x, y + vec.y, RECT);}Vector Vector::operator-(const Vector &vec) const {return Vector(x - vec.x, y - vec.y, RECT);}Vector Vector::operator-() const {return Vector(-x, -y, RECT);}Vector Vector::operator*(double d) const {return Vector(x * d, y * d, RECT);}/*** Operator overload —— Friend Function*/Vector operator*(double d, const Vector & vec) {return vec * d;}// 如果 mode 是 RECT, 显示直角坐标系的坐标;// 如果 mode 是 POL , 显示极坐标系的坐标.std::ostream & operator<<(std::ostream & out, const Vector & vec) {if (vec.mode == Vector::RECT) {out << "(x, y) = (" << vec.x << ", " << vec.y << ")" << std::endl;} else if (vec.mode == Vector::POL) {out << "(mag, ang) = (" << vec.mag << ", " << vec.ang * RadToDeg << ")\n";} else {out << "Vector object mode is invalid.\n";}return out;}}
也可以以另一种方式来设计这个类。例如,在对象中只存储直角坐标系,不保存极坐标系,在需要用到极坐标系坐标时使用 getAngVal() 和 getMagVal() 来计算极坐标系。
在重载的 +、-、* 运算符的定义中是通过类构造方法来实现的。如果方法通过计算得到一个新的类对象,则应该考虑是否可以使用类构造函数来完成这种工作。这样不仅可以避免麻烦,而且可以确保新的对象是按照正确的方式创建的。
随机漫步问题
using VECTOR::Vector;
using std::cout;
using std::cin;
using std::endl;
srand(time(0)); // 设置种子
Vector result(0, 0, VECTOR::Vector::RECT); // 采用直角坐标系记录当前所在位置
Vector step; // 记录当前随机的矢量
unsigned long steps = 0; // 记录走过的总步数
double target; // 记录目标距离
double dstep; // 记录每一步的步长
cout << "请输入目标距离(按 q 表示退出):";
while (cin >> target) {
cout << "请输入步长:";
if (!(cin >> dstep)) {
cout << "输入的步长无效,程序将退出.";
break;
}
// 如果距离没有达到目标距离, 继续走下一步
while (result.getMagVal() < target) {
// 随机生成下一步的方向, 并利用步长和方向按照极坐标系创建实例
step = Vector(dstep, rand() % 360, Vector::POL);
// 往 step 指示方向位移一步
result = result + step;
// 走的步数+1
steps++;
}
cout << "经过 " << steps << " 步之后到达指定的距离, 当前位置如下所示: " << endl;
cout << "(x, y) = (" << result.getXVal() << ", " << result.getYVal()
<< ") or (mag, ang) = (" << result.getMagVal() << ", " << result.getAngVal()
<< ")\n";
cout << "移动的平均步长为: " << result.getMagVal() / steps << endl;
// 重置步数和当前所在位置
steps = 0;
result.reset(0, 0, Vector::RECT);
cout << "请输入目标距离(按 q 表示退出):";
}
cout << "Bye!\n";
cin.clear();
time(0) 返回当前系统时间,srand(time(0)) 将当前系统时间作为种子值,rand() 函数将根据 srand() 设置的种子值返回一个伪随机数。
该程序使用 result 记录行走者当前的位置情况。内循环每轮将 step 矢量设置新的方向,并将它与当前的 result 矢量相加。当 result 的长度超过指定的距离之后,该循环结束。
result = result + step;这条语句将 result 设置为 RECT 模式,而不管 result 和 step 的初始模式是什么,这是在 + 运算符重载的实现决定的,如果不希望这样实现,可以修改 operator+() 方法的实现。
如果想要将行走者一系列的位置存储到文件中也是很容易的,只需要包含 fstream 头文件,并声明一个 ofstream 对象,将其同一个文件关联起来,然后使用 << 运算符将位置记录输出到文件中:
#include <fstream>
...
ofstream fout;
fout.open("walker.txt");
fout << result << endl;
