循环不变量(特别是“Accelerated C++”的 Ch.3)

发布于 2024-09-03 03:04:48 字数 1342 浏览 2 评论 0原文

我目前正在学习“加速 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< 之前,不变式肯定为 false。 /code>,就像另一个例子一样吗?

这两个条件有何不同?

编辑:感谢您的所有回复。 我想我已经明白了,但在我选择“接受的答案”之前,为了确定起见,我将把它留得更久一点。到目前为止,所有回复基本上都同意,所以很难看起来很公平,但我想值得做。

原始段落,如下所示:

“理解此循环的不变量需要特别小心,因为 while 中的条件有副作用。这些副作用会影响不变量的真实性:成功执行 cin >> x 使第一个不变量的一部分——表示我们已经读取了计数等级的部分——因此,我们必须改变我们的分析,以考虑条件本身可能对不变量产生的影响,

在评估之前我们知道不变量是正确的。条件,所以我们知道,如果 cin >> 成功,那么我们现在已经读取了 count + 1 个等级,但是,这样做可以使这部分不变。伪造了不变量的第二部分 - 该部分表示 sum 是第一个计数成绩的总和 - 因为在我们递增计数之后,sum 现在是第一个计数 - 1 个成绩的总和,而不是第一个计数成绩。 ,我们可以通过执行 sum += x; 使不变量的第二部分为真;这样整个不变量在随后的 while 旅行中都是正确的。

如果条件为假,则意味着我们的输入尝试失败,因此我们没有获得更多数据,因此不变量仍然为真。因此,我们不必在 while 结束后考虑该条件的副作用。”

I'm currently working my way through "Accelerated C++" and just came across this in chapter 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;
}

The authors follow this by explaining that the invariant needs special attention paid to it because when the input is read into x, we will have read count + 1 grades and thus the invariant will be untrue. Similarly, when we have incremented the counter, sum will no longer be the sum of the last count grades (in case you hadn't guessed, it's the traditional program for calculating student marks).

What I don't understand is why this matters. Surely for just about any other loop, a similar statement would be true? For example, here is the book's first while loop (the output is filled in later):

// invariant: we have written r rows so far
while (r != rows) {
    // write a row of output 
    std::cout << std::endl;
    ++r;
}

Once we have written the appropriate row of output, surely the invariant is false until we have incremented r, just as in the other example?

What makes these two conditions different?

EDIT: Thanks for all your replies. I think I've got it but I'm going to leave it going a little longer before I choose an "Accepted answer" just to be sure. So far, all the replies basically agree so it hardly seems fair, but worth doing I guess.

The original paragraph, as requested below:

"Understanding the invariant for this loop requires special care, because the condition in the while has side effects. Those side effects affect the truth of the invariant: Successfully executing cin >> x makes the first part of the invariant-the part that says that we have read count grades-false. Accordingly, we must change our analysis to account for the effect that the condition itself might have on the invariant.

We know that the invariant was true before evaluating the condition, so we know that we have already read count grades. If cin >> x succeeds, then we have now read count + 1 grades. We can make this part of the invariant true again by incrementing count. However, doing so falsifies the second part of the invariant-the part that says that sum is the sum of the first count grades-because after we have incremented count, sum is now the sum of the first count - 1 grades, not the first count grades. Fortunately, we can make the second part of the invariant true by executing sum += x; so that the entire invariant will be true on subsequent trips through the while.

If the condition is false, it means that our attempt at input failed, so we didn't get any more data, and so the invariant is still true. As a result, we do not have to account for the condition's side effects after the while finishes."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

哽咽笑 2024-09-10 03:04:49

我相信这本书指的是 while 循环停止的方式。在第二种情况下,很容易看出,一旦“r”增加到足以等于“行”,循环就会停止。因为 C++ 中的大多数计数都是从零开始的,所以很可能会为每一行输出一行。

另一方面,第一个示例是使用“>>”的运算符重载在 cin 对象上。只要该函数不返回零,while 循环就会继续。在输入关闭之前,该运算符重载不会返回该值。

按什么键可以发出“cin>>”返回0?否则循环将永远不会结束。您需要确保不会创建这样的循环。

需要添加一行来停止条件之外的循环。查找“break”和“continue”语句。

I believe the book is referring to the way that the while loops are stopped. In the second case it is very easy to see that the loop will stop once "r" has been incremented enough to equal "rows". Because most counts in C++ are zero based this will most likely output a line for each row.

On the other hand, the first example is using the operator overload for ">>" on the cin object. The while loop will continue as long as this function does not return a zero. That operator overload will not return this value until the input has closed.

What key can you press to make "cin >>" return a 0? Otherwise the loop will never end. You need to make sure you don't create a loop like that.

Adding a line to stop the loop outside of the condition is needed. Look up the "break" and "continue" statements.

帅气尐潴 2024-09-10 03:04:49

在异常安全的背景下,这确实很有趣/很重要。

考虑以下场景:

  • “count”是一个用户定义的类,具有重载的 operator++
  • 重载的 operator++ 会引发异常。
  • 稍后在循环外部捕获异常(即循环看起来像现在一样,没有 try/catch)

在这种情况下,循环不变式不再成立,并且循环中发生的所有事情的状态都存在问题。行写了吗?计数更新了吗?总数仍然正确吗?

需要使用一些额外的保护(以临时变量的形式来保存中间值和一些 try/catch),以确保即使抛出异常,一切也保持一致。

This is really interesting/important in the context of exception safety.

Consider the following scenario:

  • "count" is a user-defined class with an overloaded operator++
  • That overloaded operator++ throws an exception.
  • The exception is caught later outside the loop (i.e. the loop looks like it does now, there's no try/catch)

In that case, the loop invariant no longer holds, and the state of everything going on in the loop is in question. Was the row written? Was the count updated? Is the sum still correct?

Some extra protection (in the form of temporary variables to hold intermediate values and some try/catches) needs to be used to ensure that everything remains consistent even when an exception is thrown.

萌无敌 2024-09-10 03:04:49

这本书似乎使事情变得比应有的复杂得多。我真的不认为用不变量解释循环是一件好事。这有点像用量子物理学解释加法。

作者接着解释说,不变量需要特别注意,因为当输入读入变量 x 时,我们将读取 count+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 行”这个不变式肯定是正确的。

我没有这本书,所以我不知道这些事情以后是否都会解决。从你所说的来看,这似乎是解释循环的一种非常糟糕的方式。

The book seems to complicate things a lot more than it should. I really don't think explaining loops with invariants is a good thing. It's kind of like explaining addition with quantum physics.

The authors follow this by explaining that the invariant needs special attention paid to it because when the input is read into the variable x, we will have read count+1 grades and thus the invariant will be untrue. Similarly, when we have incremented the counter, the variable sum will no longer be the sum of the last count grades (in case you hadn't guessed, it's the traditional program for calculating student marks).

First of all the invariant isn't clear. If the invariant is "at the end of an iteration of the while loop, we have read count grades with the sum of sum", then that looks correct to me. The invariant isn't clearly specified, so it makes no sense to even talk about when it is and isn't respected.

If the invariant is "at any point of an iteration of the while loop, we have read ...`, then, strictly speaking, that invariant will not be true. As far as loops are concerned, I think an invariant should refer to the state that exists at the beginning, end, or a fixed point in the loop.

I don't have that book and I don't know if things get clarified or not, but it seems to be using invariants wrong. If their invariant isn't true, why even bother using one in the first place?

I don't think you should worry about this too much. As long as you understand how these loops work, you're fine. If you want to understand them through invariants, you can, but you must pay attention to the invariant you choose. Don't pick a bad one, or it defeats the purpose. You should pick an invariant for which it is easy to write code that respects it, not pick a random one and then struggle to write code that respects it, and definitely do not pick a vague one and write code that has nothing to do with it and then say "one must pay attention as this doesn't actually respect the vague invariant I chose".

What I don't understand is why this matters. Surely for just about any other loop, a similar statement would be true?

It depends on the invariant used (the book is pretty vague about that from what you said), but yes, you seem to be correct in this case.

For this code:

// 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;
}

The invariant "At the end of an iteration of the while loop, we have written r rows" is definitely true.

I don't have the book, so I don't know if these things are all addressed later on. From what you said though, this seems like a really lousy way to explain loops.

零崎曲识 2024-09-10 03:04:49

我同意你必须知道不变量是什么。假设我正在编写良好的旧 BankAccount。我可以说,任何时候,交易历史中所有交易的总和都会加起来就是账户的余额。这听起来很明智。如果这是真的那就太好了。但在我处理交易的几行代码中,事实并非如此。要么我先更新余额,要么我将交易添加到历史记录中并更新余额。暂时这个不变量并不成立。

我可以想象一本书希望你明白,不变量是你声称始终且始终为真的东西,但有时并非如此,并且最好知道它何时不为真,因为如果你返回,或者抛出异常,或者屈服于并发,或者其他什么,当它不正确时,那么你的系统就会一团糟。有点难以想象你的释义就是作者想要表达的意思。但我想如果我把头转向一边并眯着眼睛,我可以将其重新表述为“总和是第一个计数成绩的总和”始终是正确的,除了当我们忙于阅读和添加它们时 - 它可能不是在此过程中是正确的。有道理吗?

I agree that you have to know what the invariant is. Say I am writing good old BankAccount. I may say that always and at all times, the total of all the transactions in the transaction history will add up to the balance of the account. That sounds sensible. It would be good for it to be true. But during the handful of lines when I am processing a transaction, it isn't true. Either I update the balance first, or I add the transaction to the history and update the balance. For a moment the invariant isn't true.

I can imagine a book that wants you to understand that an invariant is something you claim to be true always and at all times, but that sometimes isn't, and that it is good to know when it isn't true, because if you return, or throw an exception, or yield to concurrency, or whatever, when it isn't true, then your system will be all messed up. It's a little harder to imagine that your paraphrase is what the authors intended to say. But I guess if I turn my head sideways and squint I can rephrase it as "sum is the sum of the first count grades" is true always and at all times except right while we're busy reading and adding them - it might not be true during that process. Make sense?

窝囊感情。 2024-09-10 03:04:49

在您更新之后,作者正确地描述了如何在循环的每次迭代中“恢复”循环不变量。

我不明白为什么这很重要。当然,对于任何其他循环,类似的陈述都是正确的吗?

是的,确实如此——这个循环没有什么特别的(好吧,循环条件有一个副作用,但这可以很容易地重写)。

但我认为作者想要指出的重要事实是:在循环内执行操作后,循环不变式通常不再为真。这对于不变式来说当然是一个问题,除非后续语句通过采取适当的操作来纠正这个问题。

Following your update, the author correctly describes how a loop invariant has to be “recovered” in every iteration of a loop.

What I don't understand is why this matters. Surely for just about any other loop, a similar statement would be true?

Yes, that’s true – there’s nothing special about this loop (OK, the loop condition has a side-effect but this can easily be rewritten).

But I think the important fact that the author wanted to point out is this: after an action performed inside the loop the loop invariant isn’t generally true any more. This of course is a problem for the invariant unless subsequent statements correct this by taking appropriate actions.

冰葑 2024-09-10 03:04:49

在第一个示例中,计数变量除了为每个输入循环递增之外没有用于任何其他用途。循环将继续直到>>返回 NULL。

在第二个示例中,必须使用要写入的行数来初始化行。循环将继续,直到写入指定的行数。

while (cin >> x) {
    ++count;
}


rows = NROWS;
r = 0;

while (r != rows) {
    // write a row of output 
    std::cout << std::endl;
    ++r;
}

On the first example the count variable is not being used for anything else than just be incremented for each input loop. The loop will continue until >> returns NULL.

On the second example, rows must be initialized with the number of rows to be written. The loop will continue until the number of rows specified be written.

while (cin >> x) {
    ++count;
}


rows = NROWS;
r = 0;

while (r != rows) {
    // write a row of output 
    std::cout << std::endl;
    ++r;
}
咆哮 2024-09-10 03:04:48

一般来说,不变量被理解为仅适用于循环的迭代之间。 (至少我是这么读的!)

一般情况是这样的:

[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!
}

这样你就有了自检代码。

In general, invariants are understood to only apply between iterations of the loop. (At least that's how I read them!)

The general case looks like:

[invariant true];
while (keep going) {
    [state transformation];
    [invariant true];
}

But during the state transformation, your invariant does not necessarily hold true.

And as a separate style note: if you want to be a super coder, instead of leaving your invariants in comments, make them assertions!

// Loop invariant: x+y = -4
for (int x = 0; x < 10; x++) {
    [do something];
    assert(x+y == -4);  // Loop invariant here!
}

That way you have self-checking code.

飘过的浮云 2024-09-10 03:04:48

从你的描述看来,作者是在胡说八道。是的,不变量在指令之间暂时变得不真实,但是只要有这样的非原子操作,这种情况就会发生。只要没有任何明显的断点可能导致不变量不正确并且程序处于不一致的状态,就可以了。

在这种情况下,唯一可能发生的情况是,如果 std::cout 在不变量不正确时抛出异常,那么您会在某处捕获该异常,但会继续以错误状态执行。在我看来,作者过于迂腐了。再说一次,只要您没有在错误的位置有任何中断/继续语句或抛出异常,就可以了。我怀疑很多人会费心关注您的示例代码,因为它是如此简单。

From your description it sounds like the author is talking nonsense. Yes, the invariant becomes untrue temporarily between instructions, but that is going to happen whenever you have non-atomic operations like this. As long as there aren't any clear break points that could lead to the invariant being incorrect and the program in an inconsistent state, you are fine.

In this case, the only way that could happen is if std::cout throws an exception while an invariant is untrue, then you catch that exception somewhere but continue execution in a bad state. It seems to me the author is being overly pedantic. So again, as long as you don't have any break/continue statements in the wrong place or exceptions being thrown you are OK. I doubt many people would bother focusing on your example code because it's just so simple.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文