如何使变量始终等于某些计算的结果?

时间:2019-03-28 16:39:39

标签: c++ c++11

在数学上,如果z = x+y/2,那么只要我们替换zx的值,y总是会改变。我们可以在编程中做到这一点,而不必每次更改zx的值时都专门更新y吗?

我的意思是这样的东西行不通,对吧?

int x;
int y;
int z{x + y};
cin >> x;
cin >> y;
cout << z;

如果您对我为什么需要它感到困惑,我希望实时显示该变量,并在rhs变量进行更改时自动更新该变量。

就像杀死一个小怪并获得金币一样,显示的净值(现金+自己物品的价值)会发生变化。或者,汽车的速度计会根据您行驶的速度或速度而变化。

12 个答案:

答案 0 :(得分:55)

编辑:在我完全回答了所提出的问题后,请同时查看enter image description here'Artelius。它解决了我的答案无法解决的一些问题(封装,避免了冗余,悬挂参考的风险)。 answerJonathan Mee显示了可能的优化(如果计算成本很高)。


您的意思是这样的:

class Z
{
    int& x;
    int& y;
public:
    Z(int& x, int& y) : x(x), y(y) { }
    operator int() { return x + y; }
};

该类将结果的计算延迟到强制转换为int为止。由于强制转换运算符不是显式的,因此Z可以在需要整数时使用。由于int有operator<<的重载,因此可以将其与e一起使用。 G。直接std::cout

int x, y;
Z z(x, y);
std::cin >> x >> y;
if(std::cin) // otherwise, IO error! (e. g. bad user input)
    std::cout << z << std::endl;

请注意,即使不可见,仍然有 still 个函数调用(强制转换操作符的隐式调用)。实际上,操作员会执行一些真实的计算(而不只是访问内部成员),因此,隐藏函数调用是否真的是个好主意值得怀疑……

答案 1 :(得分:49)

您可以通过在C ++中使用lambda来解决此问题。通常,当您设置类似

的变量时
int x;
int y;
int z{x + y};

z只会是x + y的结果。每次更改z = x + y;x时,都必须执行y才能保持更新。

但是,如果您使用lambda,则可以让它捕获应引用的对象以及应该执行的计算,然后每次访问lambda时,它将在那个时间点为您提供结果。看起来像

int x;
int y;
auto z = [&](){ return x + y; };
cin >> x;
cin >> y;
cout << z();

,现在z()将具有正确的值,而不是原始代码所具有的未初始化垃圾。

如果计算非常昂贵,您甚至可以在lambda上添加一些缓存,以确保不需要时不运行计算。看起来像

auto z = [&](){ static auto cache_x = x; 
                static auto cache_y = y; 
                static auto cache_result = x + y;
                if (x != cache_x || y != cache_y)
                {
                    cache_x = x; 
                    cache_y = y; 
                    cache_result = x + y;
                }
                return cache_result;
};

答案 2 :(得分:22)

您可能获得的最接近的结果是创建一个仿函数:

#include <iostream>

int main() {
    int x;
    int y;

    auto z = [&x, &y] { return x + y; }; // a lambda capturing x and y

    while(true) {
        std::cin >> x;
        std::cin >> y;
        std::cout << z() << "\n";
    }
}

答案 3 :(得分:20)

有两种主要技术:

  1. 延迟计算-使其成为一个可以按需计算值的函数(而不是z是一个简单变量)(请参见示例的其他答案)。如果z是某种代理对象,并且可以隐式转换为所需的类型(如Aconcagua's answer),那么这可以是源代码透明的。

  2. 显式更改通知。这要求xy是可观察的类型;当任何一个更改值时,z都会更新自身(并在适用时通知其观察者)。

通常首选第一个版本,但是如果您需要z为可观察类型,则第二个版本可能更合适。

答案 4 :(得分:17)

这听起来像XY problem(双关语)。

从它的声音来看,您并不是真正按照良好的面向对象实践编写代码。我建议您不要使用其他人建议的“技巧”,而应该实际学习如何更好地使用OO结构。

在此之前,请注意分配平等关系不同。 C ++中的=是赋值,与数学中的=不同。有一些(但不是很多)编程语言支持相等关系,但是C ++不是其中一种。事实是,增加对平等关系的支持会带来大量新挑战,因此它不像“为什么还没有在C ++中那样简单”。

无论如何,在这种情况下,您可能应该将相关变量封装在一个类中。然后,您可以使用方法来获取“最新”信息。例如:

class Player {
    std::vector<int> inventory;
    int cash;
public:
    int inventory_total();
    int net_worth();
}

//adds up total value of inventory
int Player::inventory_total() {
    int total = 0;
    for(std::vector<int>::iterator it = inventory.begin(); it != inventory.end(); ++it) {
        total += *it;
    }
    return total;
}

//calculates net worth
int Player::net_worth() {
    //we are using inventory_total() as if it were a variable that automatically
    //holds the sum of the inventory values
    return inventory_total() + cash;
}


...


//we are using net_worth() as if it were a variable that automatically
//holds the sum of the cash and total holdings
std::cout << player1.net_worth();

我承认,将这种行为添加到类中比说z = x + y要复杂得多,但实际上只是几行额外的代码。

  

如果您忘记在某个地方调用该函数,那将非常烦人并且容易出错。

在这种情况下,对象没有具有net_worth成员变量,因此您不能无意中使用它来代替调用函数。

答案 5 :(得分:8)

  1. 您为此创建了一个函数。
  2. 需要该值时,可以使用适当的参数调用该函数。

int z(int x, int y)
{
   return (x + y);
}


int x;
int y;

// This does ot work
// int z{x + y};

cin >> x;
cin >> y;
cout << z(x, y);

答案 6 :(得分:5)

您可以定义以下lambda z,该lambda总是返回x+y的当前值,因为xy被引用捕获:

DEMO

int main()
{
    int x;
    int y;

    const auto z = [&x, &y](){ return x+y; };

    std::cin  >> x; // 1
    std::cin  >> y; // 2
    std::cout << z() << std::endl; // 3

    std::cin  >> x; // 3
    std::cin  >> y; // 4
    std::cout << z() << std::endl; // 7
}

答案 7 :(得分:5)

因此,我所提供的lambda解决方案存在一个大问题,就是即使zx都没有改变,y也在每次检查时进行计算 / em>。为了解决这个问题,您确实需要链接这些变量。 我建议通过class进行此操作:

class foo {
    int x;
    int y;
    int z;
    void calculate() { z = (x + y) / 2; }
    friend istream& operator >>(istream& lhs, foo& rhs);
public:
    void set_x(const int param) {
        x = param;
        calculate();
    }
    int get_x() const { return x; }
    void set_y(const int param) {
        y = param;
        calculate();
    }
    int get_y() const { return y; }
    int get_z() const { return z; }
};

istream& operator >>(istream& lhs, foo& rhs) {
    lhs >> rhs.x >> rhs.y;
    rhs.calculate();
    return lhs;
}

每次设置zx时,它将重新计算y。如果您经常访问z并且不经常设置xy,则这是一个很好的解决方案。如果xy的设置频繁,或者calculate昂贵,您可能会考虑:

class foo {
    int x;
    int y;
    int z;
    bool dirty;
    void calculate() { z = (x + y) / 2; }
    friend istream& operator >>(istream& lhs, foo& rhs);
public:
    void set_x(const int param) {
        x = param;
        dirty = true;
    }
    int get_x() const { return x; }
    void set_y(const int param) {
        y = param;
        dirty = true;
    }
    int get_y() const { return y; }
    int get_z() const { 
        if(dirty) {
            calculate();
        }
        return z;
    }
};

istream& operator >>(istream& lhs, foo& rhs) {
    lhs >> rhs.x >> rhs.y;
    rhs.dirty = true;
    return lhs;
}

请注意,我已经包含了一个提取运算符,因此无论您选择哪种代码,都可以将其转换为简单的内容:

foo xyz;

cin >> xyz;
cout << xyz.get_z();

答案 8 :(得分:3)

您可以编写一个封装其状态的类,以在发生突变时更新或在请求时返回正确的结果:

#include <iostream>

template<typename T, typename U, typename V>
class DynamicCalc
{
public:
    DynamicCalc(const T& func, const U& memberOne, const V& memberTwo) :
        _func(func)
      , _memberOne(memberOne)
      , _memberTwo(memberTwo)
    {

    }

    void SetMemberOne(const U& memberOne) { _memberOne = memberOne; }
    void SetMemberTwo(const U& memberTwo) { _memberTwo = memberTwo; }
    auto Retrieve() { return _func(_memberOne, _memberTwo); }

    U GetMemberOne() { return _memberOne; }
    V GetMemberTwo() { return _memberTwo; }

private: 
    T _func;

    U _memberOne;
    V _memberTwo;
};

int main() {

    auto func = [](int x, int y) {
        return x + y;
    };
    DynamicCalc<decltype(func), int, int> c(func, 3, 5);

    c.SetMemberOne(5);
    std::cout << c.Retrieve();
}

实际上,如果您很高兴在重新求值时进行计算,那么就不需要使用getter / setter方法。

答案 9 :(得分:3)

可以使用宏来获取所需的内容:

    <%= a_case.client %>

{ int x, y; #define z (x + y) /* use x, y, z */ #undef z } 有点理智。为了更加理智,请不要使用宏,而应使用其他答案之一来处理多余的内容。

尽管带有自定义#undef的类在很多情况下都可以工作……嗯。

答案 10 :(得分:3)

您要描述的是 late绑定,这是像C ++这样的编译语言很难实现的功能。在解释型语言中,您所需要的只是将z设置为未求值的表达式的能力,以及将z的值的绑定时间延迟到需要计算之前,通常是通过调用强制求值的函数(例如Lisp中的eval)来发出信号。用我的专家系统的规则语言,我不仅有评估,而且有noeval,它保护了其论点免受一个评估级别的影响。这样就可以对绑定进行精细控制,如果需要,可以评估(绑定)某些子表达式,而不必评估其他子表达式。这不适用于您的情况,但是会根据语言环境设置场景。

答案 11 :(得分:0)

好吧,最后让我为您提出的问题写一个正确而正确的答案:

你不能。

每次z = x + yz更改时,您都无法编写x,然后神奇地重新运行使用y的所有代码。

那该怎么办?

如其他答案中所述,有几种模式可以表示您希望x和y的更改引起某些更新,但是无论如何,您都需要这些更新或多或少地明确地发生。

根据用例,您可以:

  • 在任何时候都必须重新计算该值。如果您编写游戏并在每一帧重新绘制屏幕,​​则可能只是确保您不小心将帧之间的z值保持足够即可。注意何时您的价值可以改变,什么时候不能改变。无论是使用函数,lambda,类方法,还是仅重复表达式,通常都是出于审美考虑。如果可以的话,这是最好的方法,因为它是完全透明的。

    例如,在赛车游戏中,您可能会在新的滴答计算开始时更新当前速度,然后在计算汽车的运动,重绘速度指示器,创建运动模糊时使用更新后的值,等等。您不需要任何魔术,甚至不需要任何功能,只需使用一个变量,因为您知道在一帧内速度不会改变。

  • 明确调用此更新。使用它,例如如果只有一个小部件,则需要更新。缺点是您需要记住调用该更新,该更新有些脆弱,但从正面来看,这很简单。将更新调用与setter集成在一起是一个中间立场,使其成为穷人的Observer实现。

  • 使用观察者模式(另请参见信号和插槽,这是实现观察者的一种方法)。使用它例如当您有许多要更新的小部件时,或者您动态创建它们时。当上述方法之一更简单时,请避免使用它。

  • 使用专用反应式编程库。由于存在这些东西,我不得不提一下。但是,老实说,我看不到任何可以使用它的应用程序。这似乎是一种复杂的拍脚方法。隐式更新将适得其反,您将不得不重写所有内容。只是不要,不是在C ++中。重要的是:虽然这种方法最接近“魔术地更新所有内容”,但是它将对编写代码的方式施加约束,最终您将获得上述解决方案之一,只是更加复杂。