【问题标题】:Angle class for radians and degrees弧度和度数的角度等级
【发布时间】:2012-08-25 17:37:53
【问题描述】:

我想做一个角度类,以弧度或度数进行初始化,但我不知道这是否是个好主意。我正在考虑这样的事情:

class Radian;
class Degree;

/**
  * My angle class.
  **/
class Angle
{
    private:
        float m_radian;

    public:
        explicit Angle(const Radian& rad);

        explicit Angle(const Degree& deg);

        float GetRadian(void) const
        {
            return this->m_radian;
        }

        float GetDegree(void) const
        {
            return ToDegree(this->m_radian);
        }

        bool IsEqual(const Angle& angle, float epsilon = 0.001f) const
        {
            return Abs<float>(this->m_radian - angle.m_radian) < epsilon;
        }

        void Set(const Angle& ang);

    protected:
        Angle(void) : m_radian(0.0f)
        {}

        Angle(float rad) : m_radian(rad)
        {}
};

class Radian : public Angle
{
    public:
        Radian(void)
        {}

        Radian(float r) : Angle(r)
        {}
};

class Degree : public Angle
{
    public:
        Degree(void)
        {}

        Degree(float d) : Angle(ToRadian(d))
        {}
};

/// Trigonometric functions.
float Sin(const Angle& angle);
...

这样我就不会在我的代码中混淆弧度和度数。但是,这是个好主意还是我应该使用其他设计?

【问题讨论】:

  • 如果你有Angle类,为什么还要有DegreeRadian
  • 就我个人而言,我认为这个三角形是一种有趣的模式,它以令人愉快的方式违反了面向对象的原则。我第一次看到它是在埃菲尔,它是复数,从那以后我就爱上了它。
  • @Beta 我的 Angle 类是一个概括。所以,当我想使用角度时,我可以使用 Degree(90) 而不是 Angle(ToRadian(90))。我想要一种不混淆弧度和度数的方法。只使用角度,我可以写出像角度(90)这样的错误。
  • @Jerry Coffin 我会检查一下,谢谢。
  • 我认为这样的想法是面向对象的想法在严肃的科学计算中没有产生影响的部分原因。我不会认为单位转换具有足够显着的行为差异来保证两个新课程。

标签: c++ math trigonometry


【解决方案1】:

这里根本不需要任何继承。一旦构建了对象,您就不再关心差异 - 它们的行为完全相同。所以你必须解决的唯一问题是如何构造Angle对象以可读的方式。

通常的解决方案是使用named constructors:

class Angle
{
    public:
        static Angle fromRadians( float v );
        static Angle fromDegrees( float v );
        // ...

    private:
        Angle( float rad );
        // ...
};

您提供了具有表达性名称的工厂函数,而不是直接调用构造函数。所以你写:

void f( Angle::fromDegrees( 3.0 ), Angle::fromRadians( 17.0 ) );

【讨论】:

  • +1 - 完全正确。这里不需要两节课。没有做过科学计算的人会做这样的事情。
【解决方案2】:

我认为这里不需要继承。就使用你的类而言,重要的是你得到一个角度 - 无论是从度数还是弧度开始都无关紧要。

免责声明:我以前做过。完全相同的用例。我的解决方案是让构造函数接受两个参数:一个数字和一个单位枚举。我会像这样使用我的课程:

Angle a(1.2345, Angle::Radians);
std::cout << a.radians() << a.degrees() << sin(a);

如果您想要方便的方法从常用单位创建角度,它们可能就是:辅助方法。不需要单独的类。

Angle r = Radians(2.3);
Angle d = Degrees(180);

无论如何 - 正是我过去一直乐于使用的。希望对您有所帮助!

【讨论】:

  • 这似乎也是一个不错的解决方案。但是,在这种情况下,您必须传递两个参数来初始化 Angle 对象。如果您需要加载很多角度,那会很糟糕吗?在构造函数上,你有一个“if”来处理角度类型?
  • +1,继承是为了修改行为,这里的行为没有任何区别来证明继承是合理的。
  • @LucasNunes - 您可以通过使 Angle::UNIT 成为乘数(或乘数表的索引)来避免条件。因此,如果您在内部将角度存储为弧度,Angle:Radians 将是 1.0Angle::Degrees 将是 0.0174532925,依此类推。
  • @LucasNunes - 关于有两个参数:我假设像这样的函数将被编译器内联,因为它真的非常小,并且在输入的情况下乘以 1 将被优化以弧度为单位。但是,如果您想确保弧度初始化速度更快,您可以选择。您可以使用“命名构造函数”,就像 Frerich 建议的那样,或者有一个朋友帮助方法,它调用一个只有一个(弧度)参数的私有构造函数。在所有其他单位中,乘法是不可避免的。
【解决方案3】:

我建议这样做:

class Radians {
    explicit Radians(float a) : angle_(a) {}
    Radians(Degrees a)        : angle_(a * PI/180.f) {}
    operator float()          { return angle_; }
private:
    float angle_;
}

class Degrees {
    explicit Degrees(float a) : angle_(a) {}
    Degrees(Radians a)        : angle_(a * 180.f/PI) {}
    operator float()          { return angle_; }
private:
    float angle_;
}

这迫使函数的自然单元成为其接口的一部分,我认为这是一件好事。你永远不应该编写一个Sin 函数来检查它给出了什么样的角度,并进行不同的计算。要么编写两个版本,然后让编译器完成工作:

float Sin(Radians x);
float Sin(Degrees x);

或者你只写一个(使用实现需要的任何类型——可能是弧度):

float Sin(Radians x);

关键不是你可以有一个“抽象”的角度(我认为这不是一个有用的概念),关键是要避免将度数与弧度混淆,并使两者之间的转换隐含。

拥有一个抽象基类Angle 会增加语法噪音(您需要在任何地方使用引用)并且可能会降低性能。此解决方案还允许您将角度存储在所需的单位中,而不是希望获得“快速路径”。

【讨论】:

  • +1 "关键不是你可以有一个“抽象”的角度(我不认为这是一个有用的概念),关键是要避免将度数与弧度混淆,并使两者隐含的转换。” - 一针见血
  • 我认为隐式转换运算符在这种情况下有点危险。例如,Radians{1} + Degrees{45} 编译得很好,但没有给出直观的结果。重载必要的运算符或使转换显式(或仅提供 getter)可能会更好。你怎么看?
  • 这不会编译,因为它是一个递归定义。
【解决方案4】:

对于相同的内角值,我会使用角度作为对象,使用度数和弧度作为不同的 getter setter。

【讨论】:

    猜你喜欢
    • 2011-01-16
    • 2022-01-24
    • 1970-01-01
    • 2015-11-28
    • 1970-01-01
    • 2016-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多