我目前正在通过“Accelerated C ++”工作,并在第3章中遇到过这个问题:
// invariant:
// we have read count grades so far, and
// sum is the sum of the first count grades
while (cin >> x) {
++count;
sum += x;
}
作者通过解释不变量需要特别注意它来遵循这一点,因为当输入被读入x
时,我们将读取count + 1
等级,因此不变量将是不真实的。同样,当我们增加计数器时,sum
将不再是最后计数等级的总和(如果您没有猜到,这是计算学生商标的传统程序)。
我不明白为什么这很重要。当然对于任何其他循环,类似的陈述是真的吗?例如,这是本书的第一个while
循环(输出稍后填写):
// invariant: we have written r rows so far
while (r != rows) {
// write a row of output
std::cout << std::endl;
++r;
}
一旦我们编写了适当的输出行,那么在我们增加r
之前,不变量肯定是假的,就像在另一个例子中一样吗?
是什么让这两个条件不同?
编辑:感谢您的所有回复。 我想我已经得到了它但是我会在我选择一个“接受的答案”之前离开它只是为了确定。到目前为止,所有回复基本上都同意所以它几乎没有看起来很公平,但我觉得值得做。
原始段落,如下所述:
“理解这个循环的不变量需要特别小心,因为while中的条件有副作用。这些副作用会影响不变量的真实性:成功执行cin&gt;&gt; x使得不变量的第一部分 - 那些说我们读过的部分是计数成绩 - 假的。因此,我们必须改变我们的分析,以考虑条件本身可能对不变量产生的影响。
我们知道在评估条件之前不变量是真的,所以我们知道我们已经读过计数等级。如果cin&gt;&gt; x成功,然后我们现在读取计数+ 1个等级。我们可以通过递增计数再次使这一部分变为真。然而,这样做会伪造不变量的第二部分 - 即总和是第一个计数等级之和的部分 - 因为在我们增加计数之后,求和现在是第一个计数的总和--1个等级,而不是第一个等级计数等级。幸运的是,我们可以通过执行sum + = x来使不变量的第二部分成立;这样整个不变量在随后的旅行中都是真实的。
如果条件为false,则表示我们对输入的尝试失败,因此我们没有获得更多数据,因此不变量仍为真。因此,我们不必在完成时间后考虑病情的副作用。“
答案 0 :(得分:4)
通常,不变量被理解为仅在循环的迭代之间应用。 (至少那是我读它们的方式!)
一般情况如下:
[invariant true];
while (keep going) {
[state transformation];
[invariant true];
}
但在状态转换期间,你的不变量不一定适用。
作为一个单独的样式注释:如果你想成为一个超级编码器,而不是将你的不变量留在评论中,那就让它们断言!
// Loop invariant: x+y = -4
for (int x = 0; x < 10; x++) {
[do something];
assert(x+y == -4); // Loop invariant here!
}
这样你就有了自我检查代码。
答案 1 :(得分:2)
从你的描述来看,听起来像作者在胡说八道。是的,不变量在指令之间暂时变得不真实,但只要你有这样的非原子操作就会发生这种情况。只要没有任何明确的断点可能导致不变量不正确且程序处于不一致状态,你就可以了。
在这种情况下,唯一可能发生的方法是,如果std :: cout在不变量是不真实的情况下抛出异常,那么你会在某处捕获该异常但继续执行处于错误状态。在我看来,作者过于迂腐。所以,只要你在错误的地方没有任何break / continue语句或抛出异常,你就可以了。我怀疑很多人会非常关注你的示例代码,因为它非常简单。
答案 2 :(得分:2)
我相信这本书指的是while循环停止的方式。在第二种情况下,很容易看到一旦“r”增加到足以等于“行”,循环就会停止。因为C ++中的大多数计数都是零,所以很可能会为每一行输出一行。
另一方面,第一个例子是使用“&gt;&gt;”的运算符重载在cin对象上。只要此函数不返回零,while循环将继续。在输入关闭之前,该运算符重载不会返回此值。
你可以按什么键来制作“cin&gt;&gt;”返回0?否则循环永远不会结束。你需要确保不要创建这样的循环。
需要添加一行来阻止循环超出条件。查看“break”和“continue”语句。
答案 3 :(得分:2)
在异常安全的背景下,这非常有趣/重要。
考虑以下情况:
operator++
operator++
会引发异常。在这种情况下,循环不变量不再成立,并且循环中发生的所有事情的状态都存在问题。这行是写的吗?计数更新了吗?总和是否仍然正确?
需要使用一些额外的保护(以临时变量的形式保存中间值和一些try / catches),以确保即使抛出异常,一切都保持一致。
答案 4 :(得分:1)
这本书似乎使事情变得复杂得多。我真的不认为用不变量解释循环是一件好事。这有点像用量子物理学解释加法。
作者通过解释不变量需要特别注意它来遵循这一点,因为当输入读入变量x时,我们将读取计数+ 1等级,因此不变量将是不真实的。类似地,当我们递增计数器时,变量sum将不再是最后计数等级的总和(如果您没有猜到,那么它是用于计算学生商标的传统程序)。
首先,不变量不明确。如果不变量是“在while
循环的迭代结束时,我们已经读取了count
的总和为sum
”,那么这对我来说是正确的。不变量没有明确规定,所以即使谈论它何时是不受尊重也没有意义。
如果不变量是“在while
循环的迭代的任何一点,我们已经读过......”,严格来说,这个不变量是不正确的。就循环而言,我认为一个不变量应该指的是存在于循环的开头,结尾或固定点的状态。
我没有那本书,我不知道是否澄清了事情,但它似乎使用了不变量。如果他们的不变量不是真的,为什么甚至在第一时间使用一个呢?
我认为你不应该太担心这件事。只要你了解这些循环如何工作,你就没事了。如果你想通过不变量来理解它们,你可以,但你必须注意你选择的不变量。不要挑坏一个,否则就会失败。你应该选择一个容易编写尊重它的代码的不变量,而不是选择一个随机的代码然后努力编写尊重它的代码,并且绝对不会选择一个模糊的代码并编写与它无关的代码然后说“必须注意,因为这实际上并不尊重我选择的模糊不变量。”
我不明白为什么这很重要。当然对于任何其他循环,类似的陈述是真的吗?
这取决于所使用的不变量(这本书与你所说的相比非常模糊),但是,在这种情况下,你似乎是正确的。
对于此代码:
// invariant: we have written r rows so far
int r = 0; // this is also important!
while (r != rows) {
// write a row of output
std::cout << std::endl;
++r;
}
不变量“在while
循环的迭代结束时,我们写了r
行”绝对是正确的。
我没有这本书,所以我不知道这些事情是否都会在以后解决。根据你的说法,这似乎是解释循环的一种非常糟糕的方式。
答案 5 :(得分:1)
我同意你必须知道不变量是什么。说我写的是旧的BankAccount。我可以说,在任何时候,交易历史记录中所有交易的总和将累计到账户余额中。这听起来很明智。这是真的很好。但是在我处理交易时的少数几行中,情况并非如此。我首先更新余额,或者将交易添加到历史记录中并更新余额。暂时不变量是不正确的。
我可以想象一本书要求你明白,不变量是你在任何时候都声称是真实的,但有时却不是,并且知道什么时候不是真的很好,因为如果你返回,或者抛出异常,或者产生并发性,或者其他什么,当它不成立时,那么你的系统就会搞砸了。想象你的释义是作者想要说的话有点难。但是我想如果我把头转向侧面并且眯着眼睛,我可以将它改为“总和是第一次计数等级的总和”,并且在任何时候都是正确的,除非我们正忙着阅读并添加它们 - 它可能不是在此过程中是真的。有意义吗?
答案 6 :(得分:1)
在您更新之后,作者正确地描述了如何在循环的每次迭代中“恢复”循环不变量。
我不明白为什么这很重要。当然对于任何其他循环,类似的陈述是真的吗?
是的,这是真的 - 这个循环没什么特别的(好的,循环条件有副作用,但这很容易被重写)。
但我认为作者想指出的重要事实是:在循环内执行动作之后,循环不变量通常不再正确。这当然是不变的问题,除非后续语句通过采取适当的行动来纠正这一点。
答案 7 :(得分:0)
在第一个例子中,count变量不用于除了每个输入循环递增之外的任何其他内容。循环将继续直到&gt;&gt;返回NULL。
在第二个示例中,必须使用要写入的行数初始化行。循环将继续,直到写入指定的行数。
while (cin >> x) {
++count;
}
rows = NROWS;
r = 0;
while (r != rows) {
// write a row of output
std::cout << std::endl;
++r;
}