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

Mic*_*ake 58 c++ c++11

在数学,如果z = x+y/2,然后z都是会改变的,每当我们更换的价值xy。我们可以在编程中做到这一点,而不必z每次更改xand 的值时都进行专门更新y吗?

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

int x;
int y;
int z{x + y};
cin >> x;
cin >> y;
cout << z;
Run Code Online (Sandbox Code Playgroud)

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

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

Aco*_*gua 57

编辑:在我完全按照要求回答问题时,也请看看Artelius回答。它解决了我的答案无法解决的一些问题(封装,避免了冗余,悬挂参考的风险)。乔纳森·梅Jonathan Mee)的答案显示了一种可能的优化方法,如果计算成本很高。


您的意思是这样的:

class Z
{
    int& x;
    int& y;
public:
    Z(int& x, int& y) : x(x), y(y) { }
    operator int() { return x + y; }
};
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

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

  • @NayWunnaZaw:当然,如果是一次性使用,请使用“ lambda”。但是,如果您的代码在多个地方需要此类动态类型,则类会稍微复杂一些(作为模板,接收一个函数,例如[操作符包装器之一](https://en.cppreference.com/w/cpp/在定义时,诸如`std :: plus`这样的Utility / functional#Operator_function_objects)只会更长一点,同时支持重用,而无需在每个使用点重新定义相当微妙的东西。我认为这是一个更好的答案,这是一种“模式”,而不仅仅是一次性的技巧。 (5认同)
  • @NayWunnaZaw:顺便说一句,请注意:您意识到lambda是*(未命名的)类,对吗?它们是函子周围的语法糖(捕获是实例属性,而参数是`operator()`的参数),因此在两种情况下都存在一个类(或由于内联而不存在)。这显式地覆盖了`operator int`(因此隐式转换就可以解决问题),其中lambda隐式地覆盖了`operator()`(因此需要显式调用括号),否则它们是相同的。如果您更喜欢lambda来简化代码,那很好,但是就实际运行而言,它们都涉及类。 (5认同)

Nat*_*ica 50

您可以通过在C ++中使用lambda来接近这一点。通常,当您设置类似

int x;
int y;
int z{x + y};
Run Code Online (Sandbox Code Playgroud)

z只会是当时的结果x + yz = x + y;每次更改xy更新时,您都必须做。

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

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

并且现在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;
};
Run Code Online (Sandbox Code Playgroud)

  • 一个类也可以用来避免使用z()语法:http://coliru.stacked-crooked.com/a/3c3dafe9856c28b8 (7认同)
  • 嗯,您可能不希望使用“静态”进行缓存... lambda可能是有状态的。 (7认同)
  • 另外,此时您不应为每种lambda重复缓存代码。那里非常需要类封装。 (2认同)

Ted*_*gmo 25

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

#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";
    }
}
Run Code Online (Sandbox Code Playgroud)


Tob*_*ght 20

有两种主要技术:

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

  2. 明确的更改通知。这需要x并且y是可观察的类型;当其中一个更改值时,然后z更新自身(并在适用时通知其观察者)。

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

  • @Aconcagua,将其修改为:如果要执行的计算是昂贵的*并且读取比更新*更为频繁。否则,我们需要添加一种方法来避免计算未被使用的值。 (4认同)
  • 如果要进行的计算成本很高,那么观察方法也很有趣。如果实施得当,我认为它比[NathanOliver](/sf/users/303974891/)提出的[缓存](/sf/answers/3878205271/)更优雅。 。 (2认同)

Art*_*ius 17

这听起来像XY问题(双关语意)。

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

在开始讨论之前,请注意赋值相等关系是不同的。=C ++中的in是赋值,与=数学中的in不同。有一些(但不是很多)编程语言支持相等关系,但是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();
Run Code Online (Sandbox Code Playgroud)

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

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

在这种情况下,对象不具有一个net_worth成员变量,所以你可以不小心使用它,而不是调用函数。


R S*_*ahu 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);
Run Code Online (Sandbox Code Playgroud)

  • @NayWunnaZaw要解决此问题:即使有我的解决方案,也有*函数调用(对强制转换运算符!),只是它是隐式的,不需要显式的... (4认同)

Hir*_*oki 5

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

演示

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
}
Run Code Online (Sandbox Code Playgroud)

  • 不要在返回值周围使用括号-“ return”不是函数,在最坏的情况下,括号甚至可以创建悬挂的引用(返回类型为`decltype(auto)`)。 (2认同)

Jon*_*Mee 5

因此,一个很大的问题,我所提供的解决方案拉姆达看到的是,z在每次它被检查的时间计算,即使没有x,也没有y改变。为了解决这个问题,您确实需要链接这些变量。我建议通过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;
}
Run Code Online (Sandbox Code Playgroud)

z每次都会重新计算xy设置。如果您z经常访问x并且y设置得很少,这是一个很好的解决方案。如果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;
}
Run Code Online (Sandbox Code Playgroud)

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

foo xyz;

cin >> xyz;
cout << xyz.get_z();
Run Code Online (Sandbox Code Playgroud)

  • @NayWunnaZaw我的意思是一般来说,您将拥有一个主类,该主类在需要实时更新的任何类上调用tick函数,并传递自上一个tick参数以来经过的时间。您可能会拥有一个播放器类,而不是简单的`foo`来完成很多工作。就像玩家职业的滴答声一样,它需要更新毒药效果的伤害,计算冷却时间并计算金币。我认识到这很可能过于简化。但我确实认为您会想在这里上课。 (2认同)