Подтвердить что ты не робот

В С++, почему true && true || false && false == true?

Я хотел бы знать, знает ли кто-нибудь, как компилятор интерпретирует следующий код:

#include <iostream>
using namespace std;

int main() {
 cout << (true && true || false && false) << endl; // true
}

Это правда, потому что && имеет более высокий приоритет, чем || или потому что || является оператором короткого замыкания (другими словами, оператор короткого замыкания игнорирует все последующие выражения или просто следующее выражение)?

4b9b3361

Ответ 1

Caladain имеет правильный ответ, но я хотел ответить на один из ваших комментариев по его ответу:

Если короткое замыкание || оператор имеет место и замыкает выполнение второго && выражение, что означает || оператор был выполнен до второго && оператор. Это подразумевает выполнение слева направо для && & и || (не && приоритет).

Я думаю, что часть проблемы, с которой вы сталкиваетесь, заключается в том, что приоритет не совсем означает, что вы думаете, что это значит. Верно, что && имеет более высокий приоритет, чем ||, и это точно отражает поведение, которое вы видите. Рассмотрим случай с обычными арифметическими операторами: предположим, что a * b + c * (d + e). Какое из предшествующих событий говорит нам о том, как вставлять круглые скобки: сначала вокруг *, затем около +. Это дает нам (a * b) + (c * (d + e)); в вашем случае мы имеем (1 && 1) || (infiniteLoop() && infiniteLoop()). Затем представьте, что выражения становятся деревьями. Чтобы сделать это, преобразуйте каждый оператор в node с двумя его аргументами в виде дочерних элементов:

Expression trees.

Оценка этого дерева - это то, где происходит короткое замыкание. В арифметическом дереве вы можете представить себе стиль исполнения снизу вверх: сначала оцените DE = d + e, затем AB = a * b и CDE = c * DE, а окончательный результатом является AB + CDE. Но учтите, что вы могли бы одинаково хорошо оценить AB, затем DE, CDE и окончательный результат; вы не можете сказать разницу. Однако, поскольку || и && являются короткозамкнутыми, они должны использовать эту самую левую оценку. Таким образом, для оценки || сначала оценим 1 && 1. Так как это верно, || замыкает и игнорирует его правую ветвь - хотя, если бы он ее оценил, ему пришлось бы сначала оценить infiniteLoop() && infiniteLoop().

Если это помогает, вы можете думать о каждом node в дереве как о вызове функции, который в первом случае производит следующее представление plus(times(a,b), times(c,plus(d,e))), а or(and(1,1), and(infiniteLoop(),infiniteLoop()) - во втором случае. Короткое замыкание означает, что вы должны полностью оценить каждый аргумент левой руки для or или and; если он true (для or) или false (для and), то игнорируйте правый аргумент.

Ваш комментарий предполагает, что сначала мы оцениваем все с наивысшим приоритетом, затем все с самым высоким приоритетом и т.д. и т.д., что соответствует широкому выполнению дерева по снизу вверх. Вместо этого происходит то, что приоритет подсказывает нам, как построить дерево. Правила для выполнения дерева не имеют значения в простом арифметическом случае; короткое замыкание, однако, является точно точной спецификацией того, как оценивать дерево.


Изменить 1: В одном из ваших других комментариев вы сказали

Ваш арифметический пример требует, чтобы два умножения были оценены до окончательного добавления, не то, что определяет приоритет?

Да, это то, что определяет приоритет, за исключением того, что это не совсем так. Это точно верно в C, но рассмотрите, как вы оцениваете выражение (non-C!) 0 * 5 ^ 7 в вашей голове, где 5 ^ 7 = 57 и ^ имеют более высокий приоритет, чем *. Согласно вашему правилу снизу вверх, мы должны оценить 0 и 5 ^ 7, прежде чем мы сможем найти результат. Но вы не потрудились бы оценить 5 ^ 7; вы просто скажете "хорошо, так как 0 * x = 0 для всех x, это должно быть 0" и пропустить всю ветвь правой руки. Другими словами, я не оценил обе стороны полностью, прежде чем оценивать окончательное умножение; Я коротко замыкался. Аналогично, поскольку false && _ == false и true || _ == true для любого _, нам может не понадобиться касаться правой части; это то, что означает, что оператор коротко замыкается. C не выполняет короткое замыкание (хотя язык может это сделать), но он выполняет короткое замыкание && и ||.

Так же, как короткое замыкание 0 * 5 ^ 7 не изменяет обычные правила приоритета PEMDAS, короткое замыкание логических операторов не изменяет того факта, что && имеет более высокий приоритет, чем ||. Это просто ярлык. Поскольку мы должны сначала выбрать некоторую сторону оператора для оценки, C promises сначала оценить левую часть логических операторов; как только это будет сделано, существует очевидный (и полезный) способ избежать оценки правой части для определенных значений и C promises для этого.

Ваше правило - оценивать выражение шириной в первом снизу вверх - также хорошо определено, и язык может решить это сделать. Однако у него есть недостаток: не допускать короткого замыкания, что является полезным поведением. Но если каждое выражение в вашем дереве четко определено (нет циклов) и чисто (без изменения переменных, печати и т.д.), То вы не можете отличить эту разницу. Это только в этих странных случаях, которые математические определения "и" и "или" не охватывают, что короткое замыкание даже заметно.

Кроме того, обратите внимание, что нет ничего фундаментального в том, что короткое замыкание работает, определяя приоритет самого левого выражения. Можно определить язык Ɔ, где ⅋⅋ представляет and и \\ представляет ||, но где 0 ⅋⅋ infiniteLoop() и 1 \\ infiniteLoop() будут циклически, а infiniteLoop() ⅋⅋ 0 и infiniteLoop() \\ 1 будут ложными и истинными, соответственно. Это справедливо только для того, чтобы сначала выбрать правую часть вместо левой части, а затем упростить таким же образом.

В двух словах:, о чем нам говорит, как построить дерево разбора. Единственные разумные приказы для оценки дерева синтаксического анализа - это те, которые ведут себя так, как если бы мы оценивали его с высоты снизу вверх (как вы хотите сделать) на четко определенных чистых значениях. Для undefined или нечистых значений должен быть выбран некоторый линейный порядок. 1 Как только выбран линейный порядок, некоторые значения для одной стороны оператора могут однозначно определять результат всего выражения (например, 0 * _ == _ * 0 == 0, false && _ == _ && false == false или true || _ == _ || true == true). Из-за этого вы можете уйти, не завершая оценку того, что приходит потом в линейном порядке; C promises, чтобы сделать это для логических операторов && и ||, оценив их в порядке слева направо, а не для чего-либо другого. Однако, благодаря приоритету, мы знаем, что true || true && false - true, а не false: потому что

  true || true && false
→ true || (true && false)
→ true || false
→ true

вместо

  true || true && false
↛ (true || true) && false
→ true && false
→ false

1: Фактически, мы могли бы также теоретически оценивать обе стороны оператора параллельно, но это не важно прямо сейчас и, конечно, не имеет смысла для C. Это приводит к более гибкому семантика, но проблема с побочными эффектами (когда они происходят?).

Ответ 2

&& имеет более высокий приоритет, чем ||.

Ответ 3

(true & true; false false && false) оценивается с помощью && & с более высоким приоритетом.

TRUE && TRUE = True

FALSE && FALSE = False

True || False = True

Обновление:

1&&1||infiniteLoop()&&infiniteLoop()

Почему это делает true в С++?

Как и раньше, давайте разбейте его. && имеет более высокий приоритет, чем || и boolean statement short circuit в С++.

1 && 1 = True.

Когда значение bool преобразуется в целочисленное значение, тогда

false -> 0
true -> 1

Выражение оценивает этот (истинный) && (true), который замыкает цепи ||, что предотвращает запуск бесконечных циклов. Там происходит гораздо больше компилятора Juju, поэтому это упрощенное представление о ситуации, которая подходит для этого примера.

В среде с ограниченным замыканием это выражение будет зависать вечно, потому что обе стороны OR будут "оценены", а правая сторона будет висеть.

Если вы запутались в приоритете, это будет выглядеть так, как бы вы оценили в своем исходном сообщении, если || имел более высокий приоритет, чем & &:

1st.) True || False = True
2nd.) True && 1st = True
3rd.) 2nd && false = false
Expression = False;

Я не могу вспомнить, идет ли он справа налево или слева направо, но в любом случае результат будет таким же. Во втором посте, если || имели более высокую точность:

1st.) 1||InfLoop();  Hang forever, but assuming it didn't
2nd.) 1 && 1st;
3rd.) 2nd && InfLoop(); Hang Forever

tl; dr: По-прежнему приоритет заключается в том, что создание && сначала оценивается, но компилятор также замыкает OR. В сущности, компилятор группирует порядок операций, подобных этому (SIMPLISTIC VIEW, сбрасывает вилы: -P)

1st.) Is 1&&1 True?
2nd.) Evaluate if the Left side of the operation is true, 
      if so, skip the second test and return True,
      Otherwise return the value of the second test(this is the OR)
3rd.) Is Inf() && Inf() True? (this would hang forever since 
      you have an infinite loop)

Обновление # 2: "Однако этот пример доказывает && НЕ имеет приоритета, поскольку || оценивается перед вторым & &. Это показывает, что && и || имеют равный приоритет и оцениваются в левом-правом значении, правильный порядок".

"Если бы у него был приоритет, он бы оценил первый & (1), затем второй && (бесконечные петли) и повесил программу. Поскольку этого не происходит, && & && не оценивается до ||."

Давайте подробно рассмотрим их.

Мы говорим о двух разных вещах. Приоритет, определяющий порядок операций и короткое замыкание, который является компилятором/языком для сохранения циклов процессора.

Пусть сначала включит Приоритет. Приоритет - короткая рука для "Ордена операций". По сути, учитывая это утверждение:   1 + 2 * 3 в каком порядке следует группировать операции для оценки?

Математика четко определяет порядок операций как предоставление умножения более высокого приоритета, чем добавление.

1 + (2 * 3) = 1 + 2 * 3
2 * 3 is evaluated first, and then 1 is added to the result.
* has higher precedence than +, thus that operation is evaluated first.

Теперь перейдем к булевым выражениям: (& & = AND, || = OR)

true AND false OR true

С++ дает И более высокий приоритет, чем OR, таким образом

(true AND false) OR true
true AND false is evaluated first, and then 
      used as the left hand for the OR statement

Итак, только в порядке приоритета (true && true || false && false) будет работать в следующем порядке:

((true && true) || (false && false)) = (true && true || false && false)
1st Comparison.) true && true
2nd Comparison.) false && false
3rd Comparison.) Result of 1st comparison || Result of Second

Со мной до сих пор? Теперь давайте перейдем к короткому замыканию: В С++ булевы операторы называются "short circuited". Это означает, что компилятор будет рассматривать данный оператор и выбрать "лучший путь" для оценки. Возьмите этот пример:

(true && true) || (false && false)
There is no need to evaluate the (false && false) if (true && true) 
equals true, since only one side of the OR statement needs to be true.
Thus, the compiler will Short Circuit the expression.  Here the compiler's
Simplified logic:
1st.) Is (true && true) True?
2nd.) Evaluate if the Left side of the operation is true, 
      if so, skip the second test and return True,
      Otherwise return the value of the second test(this is the OR)
3rd.) Is (false && false) True? Return this value

Как вы можете видеть, если (true && true) оценивается TRUE, тогда нет необходимости тратить тактовые циклы на оценку, если (false && false) истинно.

С++ Всегда короткие циклы, но другие языки предоставляют механизмы для так называемых "нетерпеливых" операторов.

Возьмем, к примеру, язык программирования Ada. В Ada "AND" и "OR" являются "нетерпеливыми" операторами... они заставляют все оценивать.

В Ada (true AND true) OR (false AND false) будет оценивать оба (true AND true) и (false AND false) перед оценкой OR. Ada Также дает вам возможность короткого замыкания с AND THEN и OR ELSE, что даст вам то же поведение, что и С++.

Я надеюсь, что полностью ответит на ваш вопрос. Если нет, сообщите мне: -)

Обновление 3: Последнее обновление, и я продолжу по электронной почте, если у вас все еще есть проблемы.

"Если происходит короткое замыкание оператора || и выполняется короткое замыкание выполнения второго выражения & &, это означает, что оператор || был выполнен ПЕРЕД вторым оператором & &. Это подразумевает, что слева - прямое выполнение для && и || (не && приоритет).

Посмотрим на этот пример:

(false && infLoop()) || (true && true) = true (Put a breakpoint in InfLoop and it won't get hit)
false && infLoop() || true && true = true  (Put a breakpoint in InfLoop and it won't get hit)
false || (false && true && infLoop()) || true = false (infLoop doesn't get hit)

Если бы вы сказали, что это правда, InfLoop попадет в первые два. Вы также заметите, что InfLoop() не будет вызван в третьем примере.

Теперь давайте посмотрим на это:

(false || true && infLoop() || true);

Называется Infloop! Если OR имела более высокую точность, чем & &, то компилятор будет оценивать:

(false || true) && (infLoop() || true) = true;
(false || true) =true
(infLoop() || true = true (infLoop isn't called)

Но InfLoop получает вызов! Вот почему:

(false || true && infLoop() || true);
1st Comparison.) true && InfLoop() (InfLoop gets called)
2nd Comparison.) False || 1st Comp (will never get here)
3rd Comparison.) 2nd Comp || true; (will never get here)

Precendece ONLY устанавливает группировку операций. В этом && больше ||.

true && false || true && true gets grouped as
(true && false) || (true && true);

Затем компилятор приходит и определяет, какой порядок он должен выполнить для оценки, чтобы дать ему наилучший шанс для сохранения циклов.

Consider: false && infLoop() || true && true
Precedence Grouping goes like this:
(false && infLoop()) || (true && true)
The compiler then looks at it, and decides it will order the execution in this order:
(true && true) THEN || THEN (false && InfLoop())

Это не факт, и я не знаю, как еще это продемонстрировать. Приоритет определяется правилами грамматики языка. Оптимизация компилятора определяется каждым компилятором. Некоторые из них лучше других, но все имеют право изменять порядок группированных сравнений по своему усмотрению, чтобы дать ему "лучший" шанс для самого быстрого выполнения с наименьшим количеством сравнений.

Ответ 5

Два факта объясняют поведение обоих примеров. Во-первых, приоритет && выше, чем ||. Во-вторых, оба логических оператора используют оценку короткого замыкания.

Приоритет часто смешивается с порядком оценки, но он независим. Выражение может иметь свои отдельные элементы, оцененные в любом порядке, если конечный результат правильный. В общем случае для некоторого оператора это означает, что значение слева (LHS) может быть оценено до или после значения справа (RHS), если оба они оцениваются до того, как применяется сам оператор.

Логические операторы имеют специальное свойство: в некоторых случаях, если одна сторона оценивает определенное значение, тогда значение оператора известно независимо от значения с другой стороны. Чтобы сделать это свойство полезным, язык C (и, соответственно, каждый C-подобный язык) определил логические операторы для оценки LHS до RHS и, кроме того, только для оценки RHS, если его значение требуется, чтобы знать результат оператор.

Итак, предполагая, что обычные определения TRUE и FALSE, TRUE && TRUE || FALSE && FALSE оцениваются, начиная слева. Первый TRUE не приводит к результату первого &&, поэтому оценивается второй TRUE, а затем вычисляется выражение TRUE && TRUE (до ИСТИНА). Теперь || знает свою LHS. Еще лучше, его LHS заставил результат || быть известным, поэтому он пропускает оценку всей своей RHS.

Точный порядок оценки применяется во втором случае. Поскольку RHS || не имеет значения, он не оценивается и не вызывается infiniteLoop().

Это поведение по дизайну и полезно. Например, вы можете написать p && p->next, зная, что выражение никогда не попытается разыменовать указатель NULL.

Ответ 6

"Если происходит короткое замыкание оператора || и выполняется короткое замыкание выполнения второго выражения & &, это означает, что оператор || был выполнен ПЕРЕД вторым оператором & &. Это подразумевает, что слева - прямое выполнение для && и || (не && приоритет).

Не совсем.

(xx && yy || zz && qq)

Будет оцениваться следующим образом:

  • Проверить первый оператор.
  • Оценить xx && yy
  • Проверить следующий оператор.
  • Если следующий оператор ||, а первая часть оператора верна, пропустите остальные.
  • В противном случае проверьте следующий оператор после || и оцените его: zz && qq
  • Наконец, оцените ||.

С моей точки зрения, С++ разработан так, что он читает все, прежде чем он начнет оценивать. В конце концов, в нашем примере он не знает, что у нас есть вторая проверка && после ||, пока она не прочитает ее, то есть она должна прочитать в ||, прежде чем она перейдет ко второй &&. Таким образом, если первая часть оценивается как истина, она не будет выполнять роль после ||, но если первая часть будет равна false, то она будет делать первую часть, прочитанную в ||, найти и оценить вторую часть, и использовать результат второй части для определения конечного результата.

Ответ 7

относительно вашего редактирования: infinLoop() не будет оцениваться, потому что true || (независимо) всегда верно. Использовать true | (независимо), если что-то должно быть выполнено.

Ответ 8

Пример true && true || infiniteLoop() && infiniteLoop(), ни один из вызовов бесконечного цикла не оценивается из-за сочетания двух характеристик: && имеет приоритет над ||, а || короткое замыкание, когда левая сторона верна.

если && & и || имел такой же приоритет, оценка должна была бы выглядеть так:

((( true && true ) || infiniteLoop ) && infiniteLoop )
(( true || infiniteLoop ) && infiniteLoop )
=> first call to infiniteLoop is short-circuited
(true && infiniteLoop) => second call to infiniteLoop would have to be evaluated

но из-за && приоритет, оценка действительно идет

(( true && true ) || ( infiniteLoop && infiniteLoop ))
( true || ( infiniteLoop && infiniteLoop ))
=> the entire ( infiniteLoop && infiniteLoop ) expression is short circuited
( true )

Ответ 9

Что касается последнего кода Андрея,

#include <iostream>
using namespace std;

bool infiniteLoop () {
    while (true);
    return false;
}

int main() {
    cout << (true && true || infiniteLoop() && infiniteLoop()) << endl; // true
}

Оценка короткого замыкания означает, что вызовы infiniteLoop гарантированно не выполняются.

Однако, это интересно в извращенном виде, потому что черновик С++ 0x делает поведение Undefined > . Это новое правило, как правило, считается очень нежелательным и глупым, даже совершенно опасным, но оно как бы прокралось в черновик. Частично из соображений сценариев потоковой передачи, когда автор одной статьи считал, что это упростит правила для чего-то другого, не имеет значения.

Итак, с компилятором, что на "переднем крае" С++ 0x-соответствия программа может завершиться, с некоторым результатом, даже если она выполнила вызов infiniteLoop! Конечно, с таким компилятором он мог бы также породить это страшное явление, носовые демоны...

К счастью, как упоминалось, оценка короткого замыкания означает, что вызовы гарантированно не выполняются.

Приветствия и hth.,

Ответ 10

Так как и/или/true/false очень похоже на */+/1/0 (они математически эквивалентны-ish), то также верно следующее:

1 * 1 + 0 * 0 == 1

и довольно легко запомнить...

Итак, да, это связано с приоритетом и короткой замыканием. Превосходство логических операций довольно просто, если вы сопоставляете их с соответствующими целыми операциями.

Ответ 11

Это последовательная иллюстрация:

  (true && true || false && false)
= ((true && true) || (false && false))  // because && is higher precedence than ||, 
                                        //   like 1 + 2 * 3 = 7  (* higher than +)
= ((true) || (false))
= true

но также обратите внимание, что если оно

(true || ( ... ))

тогда правая часть не оценивается, поэтому любая функция там не вызывается, и выражение просто вернет true.

Ответ 12

Простой ответ: && имеет более высокий приоритет, чем ||. Кроме того, код не выполняется, потому что ему не нужно знать результат булевого выражения. Да, это оптимизация компилятора.