在数学上,如果z = x+y/2
,那么只要我们替换z
和x
的值,y
总是会改变。我们可以在编程中做到这一点,而不必每次更改z
和x
的值时都专门更新y
吗?
我的意思是这样的东西行不通,对吧?
int x;
int y;
int z{x + y};
cin >> x;
cin >> y;
cout << z;
如果您对我为什么需要它感到困惑,我希望实时显示该变量,并在rhs变量进行更改时自动更新该变量。
就像杀死一个小怪并获得金币一样,显示的净值(现金+自己物品的价值)会发生变化。或者,汽车的速度计会根据您行驶的速度或速度而变化。
答案 0 :(得分:55)
编辑:在我完全回答了所提出的问题后,请同时查看'Artelius。它解决了我的答案无法解决的一些问题(封装,避免了冗余,悬挂参考的风险)。 answer的Jonathan 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)
有两种主要技术:
延迟计算-使其成为一个可以按需计算值的函数(而不是z
是一个简单变量)(请参见示例的其他答案)。如果z
是某种代理对象,并且可以隐式转换为所需的类型(如Aconcagua's answer),那么这可以是源代码透明的。
显式更改通知。这要求x
和y
是可观察的类型;当任何一个更改值时,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)
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
的当前值,因为x
和y
被引用捕获:
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解决方案存在一个大问题,就是即使z
和x
都没有改变,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;
}
每次设置z
或x
时,它将重新计算y
。如果您经常访问z
并且不经常设置x
和y
,则这是一个很好的解决方案。如果x
和y
的设置频繁,或者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 + y
或z
更改时,您都无法编写x
,然后神奇地重新运行使用y
的所有代码。
如其他答案中所述,有几种模式可以表示您希望x和y的更改引起某些更新,但是无论如何,您都需要这些更新或多或少地明确地发生。
根据用例,您可以:
在任何时候都必须重新计算该值。如果您编写游戏并在每一帧重新绘制屏幕,则可能只是确保您不小心将帧之间的z值保持足够即可。注意何时您的价值可以改变,什么时候不能改变。无论是使用函数,lambda,类方法,还是仅重复表达式,通常都是出于审美考虑。如果可以的话,这是最好的方法,因为它是完全透明的。
例如,在赛车游戏中,您可能会在新的滴答计算开始时更新当前速度,然后在计算汽车的运动,重绘速度指示器,创建运动模糊时使用更新后的值,等等。您不需要任何魔术,甚至不需要任何功能,只需使用一个变量,因为您知道在一帧内速度不会改变。
明确调用此更新。使用它,例如如果只有一个小部件,则需要更新。缺点是您需要记住调用该更新,该更新有些脆弱,但从正面来看,这很简单。将更新调用与setter集成在一起是一个中间立场,使其成为穷人的Observer实现。
使用观察者模式(另请参见信号和插槽,这是实现观察者的一种方法)。使用它例如当您有许多要更新的小部件时,或者您动态创建它们时。当上述方法之一更简单时,请避免使用它。
使用专用反应式编程库。由于存在这些东西,我不得不提一下。但是,老实说,我看不到任何可以使用它的应用程序。这似乎是一种复杂的拍脚方法。隐式更新将适得其反,您将不得不重写所有内容。只是不要,不是在C ++中。重要的是:虽然这种方法最接近“魔术地更新所有内容”,但是它将对编写代码的方式施加约束,最终您将获得上述解决方案之一,只是更加复杂。