用于方程式的面向对象API

Object-oriented API for equations

本文关键字:API 面向对象 方程式 用于      更新时间:2023-10-16

让我们以二次方程为例:

a x^2 + b x + c = 0

这个方程可以被视为描述值a、b、c和x之间的关系。给定其中三个,你可以计算第四个。四种可能性是:

a = - (b x + c) / x^2
b = - (a x^2 + c) / x
c = - x (a x + b)
x = [-b +- sqrt(b^2 - 4 a c)] / (2 a)

这里有一种表示这个方程的方法。给定以下类别:

class Quadratic
{
public: 
    double a; bool HasA = false; void A(double a_) { a = a_; HasA = true; }
    double b; bool HasB = false; void B(double b_) { b = b_; HasB = true; }
    double c; bool HasC = false; void C(double c_) { c = c_; HasC = true; }
    double x; bool HasX = false; void X(double x_) { x = x_; HasX = true; }
    // a = - (b x + c) / x^2
    double A()
    {
        if (HasB == false) throw domain_error("B not set");
        if (HasC == false) throw domain_error("C not set");
        if (HasX == false) throw domain_error("X not set");
        if (x == 0.0) throw domain_error("X cannot be 0.0");
        return - (b*x + c) / (x*x);
    }
    // x = [-b +- sqrt(b^2 - 4 a c)] / (2 a)
    vector<double> X()
    {
        if (HasA == false) throw domain_error("A not set");
        if (HasB == false) throw domain_error("B not set");
        if (HasC == false) throw domain_error("C not set");
        if (a == 0.0) throw domain_error("A cannot be 0.0");
        return 
        { 
            (-b + sqrt(b*b - 4 * a*c)) / (2 * a),
            (-b - sqrt(b*b - 4 * a*c)) / (2 * a)
        };
    }
    // b = - (a x^2 + c) / x
    // ...
    // c = - x (a x + b)
    // ...
};

我们可以如下找到x。设置ABC:

obj.A(2.3);
obj.B(3.4);
obj.C(1.2);

X可能有两个值,因此迭代结果:

for each (auto elt in obj.X()) cout << elt << endl;

如果未设置任何依赖值,则会引发domain_error异常。

类似地,为了找到A,我们设置BCX:

obj.B(1.2);
obj.C(2.3);
obj.X(3.4);

并显示结果:

cout << obj.A() << endl;

我的问题是,在面向对象的语言中,有其他方法来表示和处理方程吗?有没有比以上更惯用的方法?

问题的标题是:

面向对象的方程组 API

然而,您的代码示例中没有任何面向对象的东西,至少我所知道的"面向对象编程"的既定定义中没有您没有虚拟函数,所以它不是面向对象的

Bjarne Stroustrup的常见问题解答"什么是OOP,它有什么了不起的地方?"说(我加了重点):

在C++[…]的上下文中,它意味着使用类层次结构和虚拟函数允许操作各种类型的对象通过定义良好的接口并允许程序扩展通过推导递增。

标准C++常见问题解答(也引用了第一个源代码)回答"虚拟函数(动态绑定)是OO/C++的核心吗?"如下:

如果没有虚拟函数,C++就不会是面向对象的。


因此,

我的问题是,是否有其他方法来代表和工作用面向对象的语言编写方程?

答案应该是数学计算和面向对象编程通常不能很好地混合。面向对象就是在运行时选择抽象操作的具体实现。例如,您可以在运行时根据用户的选择,选择具有相同输入和输出的不同算法。这可以通过虚拟函数来实现。尽管如此,面向对象将发生在应用程序的更高级别,并且计算本身不会是面向对象的。

有没有比以上更惯用的方法?

是,通用编程,即模板。

您提供的所有代码都适用于double值。如果我想将它与floatstd::complex<double>甚至自定义BigNumber类一起使用,该怎么办?

使用模板,您可以编写具有在编译时选择的具体实现的通用代码。

首先,让我们使您的原始代码可编译:

#include <vector>
#include <stdexcept>
#include <math.h>
class Equation
{
public:
    bool HasA;
    bool HasB;
    bool HasC;
    bool HasX;
    double a;
    double b;
    double c;
    double x;

    double A()
    {
        if (!HasB) throw std::domain_error("B not set");
        if (!HasC) throw std::domain_error("C not set");
        if (!HasX) throw std::domain_error("X not set");
        if (x == 0.0) throw std::domain_error("X cannot be 0.0");
        return - (b*x + c) / (x*x);
    }
    // x = [-b +- sqrt(b^2 - 4 a c)] / (2 a)
    std::vector<double> X()
    {
        if (!HasA) throw std::domain_error("A not set");
        if (!HasB) throw std::domain_error("B not set");
        if (!HasC) throw std::domain_error("C not set");
        if (a == 0.0) throw std::domain_error("A cannot be 0.0");
        return 
        { 
            (-b + sqrt(b*b - 4 * a*c)) / (2 * a),
            (-b - sqrt(b*b - 4 * a*c)) / (2 * a)
        };
    }
    // b = - (a x^2 + c) / x
    // ...
    // c = - x (a x + b)
    // ...
};
int main()
{
    Equation e;
    std::vector<double> v = e.X();
}

(我已经修复了== false比较,它们几乎总是不好的风格,但C++编码质量的POV还有更多的工作要做,例如使成员变量私有。)

问题是,这整件事只适用于doubles。如果你试图将其与ints一起使用,会发生以下情况:

int main()
{
    Equation e;
    std::vector<int> v = e.X();
}

结果:

error C2440: 'initializing' : cannot convert from
'std::vector<double,std::allocator<_Ty>>' to 'std::vector<int,std::allocator<_Ty>>'

以下是如何将类转换为模板:在顶部添加template <class T>,并将每个double替换为T(并添加两个static_cast,以告诉编译器您同意缩小转换范围,这可能是由于sqrt的返回类型造成的):

#include <vector>
#include <stdexcept>
#include <math.h>
template <class T>
class Equation
{
public:
    bool HasA;
    bool HasB;
    bool HasC;
    bool HasX;
    T a;
    T b;
    T c;
    T x;

    T A()
    {
        if (!HasB) throw std::domain_error("B not set");
        if (!HasC) throw std::domain_error("C not set");
        if (!HasX) throw std::domain_error("X not set");
        if (x == 0.0) throw std::domain_error("X cannot be 0.0");
        return - (b*x + c) / (x*x);
    }
    // x = [-b +- sqrt(b^2 - 4 a c)] / (2 a)
    std::vector<T> X()
    {
        if (!HasA) throw std::domain_error("A not set");
        if (!HasB) throw std::domain_error("B not set");
        if (!HasC) throw std::domain_error("C not set");
        if (a == 0.0) throw std::domain_error("A cannot be 0.0");
        return 
        { 
            static_cast<T>((-b + sqrt(b*b - 4 * a*c)) / (2 * a)),
            static_cast<T>((-b - sqrt(b*b - 4 * a*c)) / (2 * a))
        };
    }
    // b = - (a x^2 + c) / x
    // ...
    // c = - x (a x + b)
    // ...
};
int main()
{
    Equation<int> e;
    std::vector<int> v = e.X();
}

当然,这只是故事的一半,因为无论如何都不想允许整型,只允许像doublefloat这样的浮点类型(或自定义浮点类型)。将CCD_ 25截断为CCD_。

为了保持代码的通用性但防止出现此类问题,请阅读编译时检查的静态断言,将模板限制为特定类型。std::is_floating_point也可能有用。另请参阅以下关于SO的最新问题:

获取std::complex<双>通过std::is_floating_point测试

记住,这些都与面向对象编程无关。