【问题标题】:C++ Dynamically Define FunctionC++ 动态定义函数
【发布时间】:2012-09-13 10:33:41
【问题描述】:

我正在使用 Visual c++ 开发控制台计算器,我正在创建一种让用户定义自定义线性函数的方法。这就是我被难住的地方:一旦我得到了用户想要的函数名称、斜率和 y 截距,我需要使用这些数据来创建一个可调用的函数,我可以将它传递给 muParser。

在 muParser 中,您可以像这样定义自定义函数:

double func(double x)
{
    return 5*x + 7; // return m*x + b;
}

MyParser.DefineFun("f", func);
MyParser.SetExpr("f(9.5) - pi");
double dResult = MyParser.Eval();

如何根据用户输入的值“m”和“b”动态创建这样的函数,并将其传递给“DefineFun()”方法? 这是我到目前为止所拥有的:

void cb_SetFunc(void)
{
    string FuncName, sM, sB;
    double dM, dB;
    bool GettingName = true;
    bool GettingM = true;
    bool GettingB = true;
    regex NumPattern("[+-]?(?:0|[1-9]\\d*)(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?");

    EchoLn(">>> First, enter the functions name. (Enter 'cancel' to abort)");
    EchoLn(">>> Only letters, numbers, and underscores can be used.");

    try
    {
        do // Get the function name
        {
            Echo(">>> Enter name: ");
            FuncName = GetLn();
            if (UserCanceled(FuncName)) return;

            if (!ValidVarName(FuncName))
            {
                EchoLn(">>> Please only use letters, numbers, and underscores.");
                continue;
            }
            GettingName = false;

        } while (GettingName);

        do // Get the function slope
        {
            Echo(">>> Enter slope (m): ");
            sM = GetLn();
            if (UserCanceled(sM)) return;

            if (!regex_match(sM, NumPattern))
            {
                EchoLn(">>> Please enter any constant number.");
                continue;
            }
            dM = atof(sM.c_str());
            GettingM = false;

        } while (GettingM);

        do // Get the function y-intercept
        {
            Echo(">>> Enter y-intercept (b): ");
            sB = GetLn();
            if (UserCanceled(sB)) return;

            if (!regex_match(sB, NumPattern))
            {
                EchoLn(">>> Please enter any constant number.");
                continue;
            }
            dB = atof(sB.c_str());
            GettingB = false;

        } while (GettingB);

            // ------------
            // TODO: Create function from dM (slope) and
            // dB (y-intercept) and pass to 'DefineFun()'
            // ------------
    }
    catch (...)
    {
        ErrMsg("An unexpected error occured while trying to set the function.");
    }
}

我在想没有办法为每个用户定义的函数定义单独的方法。我是否需要创建一个vector<pair<double, double>> FuncArgs; 来跟踪适当的斜率和 y 截距,然后从函数中动态调用它们?当我将它传递给DefineFun(FuncStrName, FuncMethod) 时,我将如何指定要使用哪对?

【问题讨论】:

    标签: c++ math dynamic methods console


    【解决方案1】:

    您需要的(除了脚本语言解释器)称为"trampoline"。没有创建这些的标准解决方案,特别是因为它涉及在运行时创建代码。

    当然,如果您接受固定数量的蹦床,您可以在编译时创建它们。如果它们都是线性的,这可能会更容易:

    const int N = 20; // Arbitrary
    int m[N] = { 0 };
    int b[N] = { 0 };
    template<int I> double f(double x) { return m[I] * x + b; }
    

    这定义了一组 20 个函数 f&lt;0&gt;...f&lt;19&gt;,它们分别使用 m[0]...m[19]

    编辑:

    // Helper class template to instantiate all trampoline functions.
    double (*fptr_array[N])(double) = { 0 };
    template<int I> struct init_fptr<int I> {
      static const double (*fptr)(double) = fptr_array[I] = &f<I>;
      typedef init_fptr<I-1> recurse;
    };
    template<> struct init_fptr<-1> { };
    

    【讨论】:

    • 这样就好了,只不过muParser调用函数时,不会将I模板参数传递给函数获取mb中指定索引处的值.我不控制函数的调用方式,当 muParser 在集合表达式字符串中找到“f(x)”时,它会独立执行此操作。更不用说我会将template&lt;int&gt; double(double) 传递给double(double) 参数。
    • @BrandonMiller:您将其用作MyParser.DefineFun("foo", f&lt;0&gt;); MyParser.DefineFun("bar", f&lt;1&gt;); 。 C++ 编译器将生成多达 20 个唯一地址,muParser 将传递其中一个地址 - 而不是 I
    • 嗯!我想这正是我需要的!这看起来非常简单直接。 boost::function 的所有“绑定”、“引用”和“委托”行话让我感到困惑。马上要试试这个。这将在运行时完成,所以我认为我应该使用 vector&lt;pair&lt;double, double&gt;&gt; FuncArgs 并像 return FuncArgs.at(I).first * x + FuncArgs.at(I).second; 一样访问它们,而不是两个数组。
    • @BrandonMiller:也可以,但请记住,您不能在运行时实例化模板。如果您在编译时提及 f&lt;0&gt;, f&lt;1&gt;, f&lt;3&gt;,则只会实例化这 3 个函数。正在编辑...
    • 我碰壁了。我需要动态获取函数的索引,但我只能对索引使用常量值:pair&lt;double,double&gt; argPair; argPair.first = dM; argPair.second = dB; LinFuncArgs.push_back(argPair); int index = NCast&lt;int,size_t&gt;(LinFuncArgs.size()); Calc-&gt;DefineFun(FuncName, LinFunc&lt;index&gt;); 在最后一行我得到错误:“表达式必须有一个常量表达式”。所以我必须手动输入索引号,这完全达不到目的。
    【解决方案2】:

    我会保持简单:

    #include <functional>
    
    std::function<double(double)> f;   // this is your dynamic function
    
    int slope, yintercept;             // populate from user input
    
    f = [=](double x) -> double { return slope * x + yintercept; };
    

    现在您可以将对象f 传递给您的解析器,然后解析器可以随意调用f(x)。函数对象将捕获到的slopeyintercept的值打包。

    【讨论】:

    • 我相信 OP 想要“根据用户输入动态创建一个函数……”。但是也许我们会问动态是否意味着在运行时或编译之前......
    • 这可能会导致问题,因为外部数学库需要一个函数指针,而 std::function 不是。
    • 问题是这些函数中有许多具有不同的斜率和 y 截距。只有这两个变量跟踪它们,一旦用户定义了另一个函数,他将不再能够使用以前的函数。需要有一个具有不同 M 和 B 的函数的列表,用户可以通过键入 func(7) + anotherFunc(9.53) 来调用这些函数
    • @StefanMajewsky 是的,我相信你是对的。我需要(我猜是一个向量)这些函数指针都具有不同的斜率和截距
    • @BrandonMiller:您可以轻松地创建一个std::vector&lt;std::function&lt;double(double)&gt;&gt; 并将所有不同的功能放入其中。问题是您是否能够更改解析器的接口以接受 std::function 对象作为其核心原语。
    【解决方案3】:

    GiNaC 是可以解析和评估数学表达式的 C++ 库。

    【讨论】:

    • 谢谢,但这部分已经在控制之中。我的计算器在评估表达式方面功能齐全,我只是想让用户定义自己的线性函数以用于表达式。
    【解决方案4】:

    生成可绑定到 boost 函数的固定函数数组。

    其他人已经说过类似的方法,但是由于我花时间编写代码,所以还是在这里。

    #include <boost/function.hpp>
    
    enum {
        MAX_FUNC_SLOTS = 255
    };
    
    struct FuncSlot
    {
        double (*f_)(double);
        boost::function<double(double)> closure_;
    };
    
    FuncSlot s_func_slots_[MAX_FUNC_SLOTS];
    
    template <int Slot>
    struct FuncSlotFunc
    {
        static void init() {
            FuncSlotFunc<Slot-1>::init();
            s_func_slots_[Slot - 1].f_ = &FuncSlotFunc<Slot>::call;
        }
        static double call(double v) {
            return s_func_slots_[Slot - 1].closure_(v);
        }
    };
    template <> struct FuncSlotFunc<0> {
        static void init() {}
    };
    
    struct LinearTransform
    {
        double m_;
        double c_;
        LinearTransform(double m, double c)
            : m_(m)
            , c_(c)
        {}
        double operator()(double v) const {
            return (v * m_) + c_;
        }
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        FuncSlotFunc<MAX_FUNC_SLOTS>::init();
    
        s_func_slots_[0].closure_ = LinearTransform(1, 0);
        s_func_slots_[1].closure_ = LinearTransform(5, 1);
    
        std::cout << s_func_slots_[0].f_(1.0) << std::endl; // should print 1
        std::cout << s_func_slots_[1].f_(1.0) << std::endl; // should print 6
    
        system("pause");
        return 0;
    }
    

    因此,您可以通过以下方式获取函数指针:s_func_slots_[xxx].f_ 并使用 s_func_slots_[xxx].closure_

    设置您的操作

    【讨论】:

      【解决方案5】:

      尝试在您的应用程序中嵌入一些脚本语言。几年前,我出于类似目的使用 Tcl - 但我不知道当前时间的最佳选择是什么。

      您可以从 Tcl 开始,也可以自己寻找更好的东西:

      见:Adding Tcl/Tk to a C application

      【讨论】:

      • Lua 现在很流行,并且非常易于嵌入。它的占地面积也非常小。
      • 另一位候选人:“Chibi-Scheme 是一个非常小的库,旨在用作 C 程序中的扩展和脚本语言。” code.google.com/p/chibi-scheme
      • 另外,Squirrel 是一种非常适合嵌入 C/C++ 应用程序的语言,类似于 Lua,但更好:squirrel-lang.org
      • 这很有趣,因为最初是为了真正解析数学方程,我尝试将 TCL 嵌入我的程序中,但我遇到了缺少标题的问题,然后我试图在网上找到但无济于事。因此,如果我嵌入其中一种语言,我可以简单地创建一个定义我的函数的字符串并将其传递给嵌入式语言解析器,然后将新创建的函数传递给 DefineFun() 方法?范围问题呢?我需要该函数存在于整个程序中,以便我的 muParser 实例可以调用它。当函数返回时,我的新方法不会被破坏吗?
      • @Brandon:我相信这些脚本语言中的许多(或全部)提供了“编译”脚本函数一次然后多次使用它的功能。看看文档就行了。
      猜你喜欢
      • 2013-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-10
      • 2019-10-27
      • 2021-08-25
      • 1970-01-01
      相关资源
      最近更新 更多