练习代码

由于无法用一个数来表示矢量,因此应该创建一个类来表示矢量。矢量的相加和相减与普通数学运算有相似之处,所以应重载运算符使之能用于矢量。
本节将实现一个二维矢量,描述二维矢量需要有两个数,但有两种方式:

  1. 用长度和角度表示(极坐标);
  2. 用水平分量 x 和垂直分量 y 表示(直角坐标)。

    1. #ifndef VECTOR_H_
    2. #define VECTOR_H_
    3. #include <iostream>
    4. namespace VECTOR {
    5. class Vector {
    6. public:
    7. enum Mode {
    8. RECT, // 直角坐标系
    9. POL // 极坐标系
    10. };
    11. private:
    12. double x; // 水平坐标
    13. double y; // 垂直坐标
    14. double ang; // 角度
    15. double mag; // 长度
    16. Mode mode; // 采用的坐标系
    17. // 设置私有成员的函数
    18. void setMag(); // 根据 x, y 设置 mag
    19. void setAng(); // 根据 x, y 设置 ang
    20. void setX(); // 根据 mag, ang 设置 x
    21. void setY(); // 根据 mag, ang 设置 y
    22. public:
    23. Vector();
    24. // 构造函数, 默认采用直角坐标系表示法
    25. Vector(double n1, double n2, Mode form = RECT);
    26. ~Vector();
    27. // 重置 Vector 值, 默认采用直角坐标系表示法
    28. void reset(double n1, double n2, Mode form = RECT);
    29. double getXVal() const { return x; }
    30. double getYVal() const { return y; }
    31. double getAngVal() const { return ang; }
    32. double getMagVal() const { return mag; }
    33. void setPolarMode();
    34. void setRectMode();
    35. // 重载运算符 —— 成员函数
    36. Vector operator+(const Vector & vec) const;
    37. Vector operator-(const Vector & vec) const;
    38. Vector operator-() const;
    39. Vector operator*(double d) const;
    40. // 重载运算符 —— 友元函数
    41. friend Vector operator*(double d, const Vector & vec);
    42. friend std::ostream & operator<<(std::ostream & out, const Vector & vec);
    43. };
    44. } // end namespace VECTOR
    45. #endif // VECTOR_H_
    1. #include "vector.h"
    2. #include <cmath>
    3. namespace VECTOR {
    4. using std::sqrt;
    5. using std::sin;
    6. using std::cos;
    7. using std::atan;
    8. using std::atan2;
    9. using std::cout;
    10. const double RadToDeg = 45.0 / atan(1.0); // 计算一弧度对应的角度
    11. /**
    12. * Private methods
    13. */
    14. void Vector::setAng() {
    15. // 这里其实不应该用 == 的, double 类型的 == 并不准确
    16. if (x == 0.0 && y == 0.0)
    17. ang = 0.0;
    18. else
    19. ang = atan2(y, x);
    20. }
    21. void Vector::setMag() {
    22. mag = sqrt(x * x + y * y);
    23. }
    24. void Vector::setX() {
    25. x = mag * cos(ang);
    26. }
    27. void Vector::setY() {
    28. y = mag * sin(ang);
    29. }
    30. /**
    31. * Public methods —— Constructor And Destructor
    32. */
    33. Vector::Vector() {
    34. cout << "Default Constructor.\n";
    35. x = y = ang = mag = 0.0;
    36. mode = RECT;
    37. }
    38. Vector::Vector(double n1, double n2, Mode form) {
    39. cout << "Constructor. ";
    40. mode = form;
    41. if (form == RECT) {
    42. x = n1;
    43. y = n2;
    44. setMag();
    45. setAng();
    46. } else if (form == POL) {
    47. mag = n1;
    48. ang = n2 / RadToDeg;
    49. setX();
    50. setY();
    51. } else {
    52. cout << "构造方法 Vector() 的第三个参数是无效的. 对象的值将被设置为 0, 并且采用直角坐标系.";
    53. x = y = ang = mag = 0.0;
    54. mode = RECT;
    55. }
    56. cout << std::endl;
    57. }
    58. Vector::~Vector() {
    59. cout << "Destructor.\n";
    60. }
    61. /**
    62. * Public Function.
    63. */
    64. // 重新设置 vector 的值
    65. void Vector::reset(double n1, double n2, Mode form) {
    66. mode = form;
    67. if (mode == RECT) {
    68. x = n1;
    69. y = n2;
    70. setAng();
    71. setMag();
    72. } else if (mode == POL) {
    73. mag = n1;
    74. ang = n2 /RadToDeg;
    75. setX();
    76. setY();
    77. } else {
    78. cout << "reset() 的第三个参数是无效的. 对象的值将被设置为 0, 并且采用直角坐标系.\n";
    79. x = y = ang = mag = 0.0;
    80. mode = RECT;
    81. }
    82. }
    83. void Vector::setPolarMode() { mode = RECT; }
    84. void Vector::setRectMode() { mode = POL; }
    85. /**
    86. * Operator overload —— Member Function
    87. */
    88. Vector Vector::operator+(const Vector &vec) const {
    89. return Vector(x + vec.x, y + vec.y, RECT);
    90. }
    91. Vector Vector::operator-(const Vector &vec) const {
    92. return Vector(x - vec.x, y - vec.y, RECT);
    93. }
    94. Vector Vector::operator-() const {
    95. return Vector(-x, -y, RECT);
    96. }
    97. Vector Vector::operator*(double d) const {
    98. return Vector(x * d, y * d, RECT);
    99. }
    100. /**
    101. * Operator overload —— Friend Function
    102. */
    103. Vector operator*(double d, const Vector & vec) {
    104. return vec * d;
    105. }
    106. // 如果 mode 是 RECT, 显示直角坐标系的坐标;
    107. // 如果 mode 是 POL , 显示极坐标系的坐标.
    108. std::ostream & operator<<(std::ostream & out, const Vector & vec) {
    109. if (vec.mode == Vector::RECT) {
    110. out << "(x, y) = (" << vec.x << ", " << vec.y << ")" << std::endl;
    111. } else if (vec.mode == Vector::POL) {
    112. out << "(mag, ang) = (" << vec.mag << ", " << vec.ang * RadToDeg << ")\n";
    113. } else {
    114. out << "Vector object mode is invalid.\n";
    115. }
    116. return out;
    117. }
    118. }

    也可以以另一种方式来设计这个类。例如,在对象中只存储直角坐标系,不保存极坐标系,在需要用到极坐标系坐标时使用 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;