练习代码
由于无法用一个数来表示矢量,因此应该创建一个类来表示矢量。矢量的相加和相减与普通数学运算有相似之处,所以应重载运算符使之能用于矢量。
本节将实现一个二维矢量,描述二维矢量需要有两个数,但有两种方式:
- 用长度和角度表示(极坐标);
- 用水平分量 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 设置 mag
void setAng(); // 根据 x, y 设置 ang
void setX(); // 根据 mag, ang 设置 x
void setY(); // 根据 mag, ang 设置 y
public:
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;
else
ang = 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;