11.5 再谈重载:一个矢量类

矢量

用户修改了矢量的一种表示后,对象将自动更新另一种表示。使对象有这种智能。该清单将类声明放在VECTOR名称空间中。该程序使用枚举创建两个常量(RECT和POL),用于标识两种表示法。

vector.h

#ifndef VECTOR_H
#define VECTOR_H
#include <iostream>
using namespace std;
namespace VECTOR
{
    class Vector
    {
    public:
        enum Mode {RECT, POL}; //直角坐标,极坐标
    private:
        double x; //水平矢量
        double y; //垂直矢量
        double mag; //矢量长度
        double ang; //矢量角度
        Mode mode; //RECT or POL

        //用私有方法来设置值
        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;} //返回x值
        double yval() const {return y;} //返回y值
        double magval() const {return mag;} //返回矢量长度
        double angval() const {return ang;} //返回矢量角度
        void polar_mode(); //设置模式为极坐标
        void rect_mode(); //设置模式为直角坐标

        //运算符重载
        Vector operator+(const Vector &b) const;
        Vector operator-(const Vector &b) const;
        Vector operator-() const;
        Vector operator*(double n) const;

        //友元
        friend Vector operator*(double n, const Vector &a);
        friend ostream & operator<<(ostream &os, const Vector &v);
    };
}

#endif // VECTOR_H

无法用一个数表示矢量,因此应创建一个类来表示矢量。其次,矢量与普通数学运算有相似之处。重载运算符,用于矢量。

实现一个二维矢量,用大小(长度)和方向(角度)描述矢量;用分量x和y表示矢量。

两个分量分别是水平矢量(x分量)和垂直矢量(y分量),将其相加可以得到最终的矢量。

vector.cpp

#include <cmath>
#include "vector.h"
#include <iostream>
using namespace std;

namespace VECTOR
{
    //度换算为弧度
    const double Rad_to_deg = 45.0 / atan(1.0);
    //大约57.2957795130823

    //计算x和y的幅度
    void Vector::set_mag()
    {
        mag = sqrt(x * x + y * y);
    }

    void Vector::set_ang()
    {
        if(x == 0.0 && y == 0.0)
            ang = 0.0;
        else
            ang = atan2(y, x);
    }

    //设置x的极坐标
    void Vector::set_x()
    {
        x = mag * cos(ang);
    }

    //设置y的极坐标
    void Vector::set_y()
    {
        y = mag * sin(ang);
    }
    
    //默认构造函数
    Vector::Vector()
    {
        x = y = mag = ang = 0.0;
        mode = RECT;
    }
 
    //如果form是直角坐标,则构造直角坐标向量
    //如果form是极坐标,则构造极坐标向量
    Vector::Vector(double n1, double n2, Vector::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;
        }
    }

    //重置向量 
    void Vector::reset(double n1, double n2, Vector::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()
    {

    }

    //设置为极坐标模式
    void Vector::polar_mode()
    {
        mode = POL;
    }

    //设置为直角坐标模式
    void Vector::rect_mode()
    {
        mode = RECT;
    }
 
    //向量相加
    Vector Vector::operator+(const Vector &b) const
    {
        return Vector(x + b.x, y + b.y);
    }

    //向量相减
    Vector Vector::operator-(const Vector &b) const
    {
        return Vector(x - b.x, y - b.y);
    }
 
    //反向量
    Vector Vector::operator-() const
    {
        return Vector(-x, -y);
    }

    //矢量与数乘
    Vector Vector::operator*(double n) const
    {
        return Vector(n * x, n * y);
    }

    //数与矢量乘
    Vector operator*(double n, const Vector &a)
    {
        return a * n;
    }
   
    //输出重载
    ostream & operator<<(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;
    }
}

也可以以另一种方式设置类。例如,在对象中存储直角坐标而不是极坐标,并使用方法magval()和angval()来计算极坐标。reset()是并非必不可少的。假设shove是一个Vector对象。

shove.reset(100, 300);

使用构造函数得到相同的结果

shove = Vector(100, 300);

然而,set()修改shove的内容,而使用构造函数将增加额外的步骤:创建一个临时对象,然后将其赋值给shove。

使用状态成员

Vector类储存了矢量的直角坐标和极坐标。它使用名为mode的成员来控制使用构造函数、reset()方法、重载的operator<<()函数使用哪种形式,其中枚举RECT表示直角坐标模式(默认值)、POL表示极坐标模式。这样的成员被称为状态成员,描述对象所处的状态。

Vector::Vector(double n1, double n2, Vector::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或省略了,则将输入解释为直角坐标;如果为POL,则将输入解释为极坐标:

Vector folly(3.0, 4.0); //x=3, y=4
Vector foolery(20.0, 30.0, VECTOR::Vector::POL); //mag=20, ang=30

标识符POL的作用域为类,因此类定义可使用未限定的名称。但全限定名为VECTOR::Vector::POL,因为POL是在Vector类中定义的,而Vector是在名称空间VECTOR中定义的。

注意:如果用户提供的是x值和y值,则构造函数将使用私有方法set_mag()和set_ang()来设置距离和角度值;如果提供的是距离和角度值,则构造函数将使用set_x()和set_y()方法来设置x值和y值。另外,如果用户指定的不是RECT或POL,则构造函数将显示一条警告消息,并将状态设置为RECT。

难以将RECT和POL外的其他值传递给构造函数,因为第三个参数的类型为VECTOR::Vector::Mode。

像下面这样的调用无法通过编译,因为诸如2等整数不能隐式地转换为枚举类型:

Vector rector(20.0, 30.0, 2); //类型无法匹配,2不是一个枚举类型

可以尝试如下代码:

Vector rector(20.0, 30.0, VECTOR::Vector::Mode(2)); //类型转换

编译发出警告。

operator<<()函数也使用模式来确定如何显示值:

ostream & operator<<(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永远不会执行。但进行检查,有助于捕获难以发现的编程错误。

为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; //不完整
}

无法设置极坐标值。

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; //两种模式
}

使用构造函数更简单。

Vector Vector::operator+(const Vector &b) const
{
    return Vector(x + b.x, y + b.y); //返回构造的Vector
}

将新的x分量和y分量传递给Vector构造函数,将使用这些值来创建无名的新对象,并返回该对象的副本。确保了新的Vector对象是根据构造函数制定的标准规则创建的。

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

乘法
将矢量与一个数相乘,使矢量加长或缩短。对于极坐标,将长度进行伸缩,并保持角度不变;对于直角坐标,将x和y分量进行伸缩。

Vector Vector::operator*(double n) const
{
    return Vector(n * x, n * y); //返回构造的Vector
}

和加法重载一样,允许构造函数使用新的x和y分量来创建正确的Vector对象。

使用一个内联友元函数来处理double与Vector相乘:

Vector Vector::operator*(double n, const Vector &a) //友元函数
{
    return a * n; 
}

对已重载的运算符进行重载
-运算符已经有两种含义。首先,使用两个操作数,它是减法运算符。减法运算符是一个二元运算符,因为它有两个操作数。其次,使用一个操作数时(-x),它是负号运算符。这种被称为一元运算符,只有一个操作数。对于矢量来说,这两种操作都有意义。

要从矢量A中减去矢量B,只要将分量相减即可。

Vector operator-(const Vector &b) const; //原型
Vector Vector::operator-(const Vector &b) const //定义
{
    return Vector(x - b.x, y - b.y); //返回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); 
}

注意:运算符重载是通过函数来实现的,所以只要运算符函数的特征标不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符。

使用Vector类模拟随机漫步

随机漫步问题:将一个人领到灯柱下。这个人开始走动,但每一步的方向都是随机的(与前一步不同)。这个问题的一种表述是,这个人走到离灯柱50英尺处需要多少步。从矢量的角度看,相当于不断将方向随机的矢量相加,直到长度超过50英尺。

main.cpp

#include <iostream>
#include <cstdlib> //rand(),srand()原型
#include <ctime> //time()原型
#include "vector.h"
using namespace std;
using namespace VECTOR;

int main()
{
	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;
		while(result.magval() < target)
		{
			direction = rand() % 360;
			step.reset(dstep, direction, Vector::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";
	cin.clear();
	while(cin.get() != '\n')
		continue;
	return 0;
}

用一个变量来表示位置(一个矢量),并报告到达指定距离处所需的步数。可以看到,行走者前进得慢。虽然走了1000步,每步的距离为2英尺,但离起点可能只有50英尺。将行走者所走的净距离(这里是50英尺)除以步数,来指出这种行走方式的低效性。随机改变方向使得该平均值远远小于步长。
在这里插入图片描述即使初始条件相同,处理的随机性使得每次运行结果都不同。然而,平均而言,步长减半,步数将为原来的4倍。平均而言,步数(N)、步长(s)、净距离(D):
N = (D/s)2

rand()函数,它返回一个从0到某个值之间的随机整数,将一种算法用于一个初始种子值来获取随机数,该随机值将用作下一次函数调用的种子,依次类推,实际上这些数是伪随机数,因为10次连续的调用通常将生成10个同样的随机数。

srand()函数,允许覆盖默认的种子值,重新启动另一个随机数序列。使用time(0)的返回值来设置种子,time(0)返回当前时间,通常为从某一个日期开始的秒数。

srand(time(0));

头文件cstdlib包含srand()和rand()的原型,而ctime包含了time()的原型。

使用result矢量记录行走者的前进情况。内循环每轮将step矢量设置为新的方向,并将它与当前的result矢量相加。当result的长度超过指定的距离后,该循环结束。

程序通过设置矢量的模式,用直角坐标和极坐标显示最终的位置。

将result设置为RECT模式,而不管result和step的初始模式是什么:

result = result + step;

首先,加法运算符函数创建并返回一个新矢量,该矢量存储了这两个参数的和。该函数使用默认构造函数以RECT模式创建矢量。因此,被赋给result的矢量的模式为RECT。默认情况下,赋值时将分别给每个成员变量赋值,因此将RECT赋给result.mode。

将一系列位置存储到文件中很容易。首先包含头文件fstream,声明一个ofstream对象,将其同一个文件关联起来:

#include <fstream>
...
ofstream fout;
fout.open("thewalk.txt");

然后,在计算结果的循环中加入:

fout << result << endl;

将调用友元函数operator<<(fout, result),导致引用参数os指向fout,从而将输出写入到文件中。还可以使用fout将其他信息写入到文件中,由cout显示总结信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值