Почему в C++ true && true || ложь && ложь == правда?

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

#include <iostream>
using namespace std;

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

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


person Andrew    schedule 31.10.2010    source источник
comment
Как только вы оцениваете истинность во множестве или, это замыкается накоротко, и все последующие выражения игнорируются.   -  person Margus    schedule 31.10.2010
comment
Логика короткого замыкания не влияет на результат, она просто определяет, вычисляется ли все выражение или вычисление прекращается, как только становится известен окончательный результат.   -  person Marcelo Cantos    schedule 31.10.2010


Ответы (12)


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

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

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

Деревья выражений.

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

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

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


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

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

Да, это то, что определяет приоритет, но это не совсем так. Это, безусловно, верно для C, но подумайте, как бы вы оценили (не-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 обещает сначала оценить левую часть логических операторов; как только это будет сделано, появится очевидный (и полезный) способ избежать вычисления правой части для определенных значений, и C обещает сделать это.

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

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

В двух словах: приоритет говорит нам, как построить дерево синтаксического анализа. Единственные разумные порядки для оценки дерева синтаксического анализа — это те, которые ведут себя как если бы мы оценивали его в ширину снизу вверх (как вы хотите) на четко определенных чистых значениях. Для неопределенных или нечистых значений необходимо выбрать некоторый линейный порядок.1 После выбора линейного порядка определенные значения для одной стороны оператора могут однозначно определять результат целое выражение (например,, 0 * _ == _ * 0 == 0, false && _ == _ && false == false или true || _ == _ || true == true). Из-за этого вы можете уйти, не завершая оценку того, что следует после в линейном порядке; C обещает сделать это для логических операторов && и ||, оценивая их слева направо, и не делать этого ни для чего другого. Однако благодаря приоритету мы действительно знаем, что 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. Это приводит к более гибкой семантике, но тот, у которого есть проблемы с побочными эффектами (когда они случаются?).

person Antal Spector-Zabusky    schedule 01.11.2010
comment
Это полезно, но как тогда можно говорить, что && когда-либо имеет приоритет? Так как || короткие замыкания, выражения с участием && и || будет ли всегда оцениваться в порядке слева направо, указанном вами в вашем дереве, чтобы определить, замыкает ли выражение... верно? - person Andrew; 01.11.2010
comment
Отличается ли порядок операций (который я могу неправильно понимать как приоритет) и приоритет? Ваш арифметический пример требует, чтобы два умножения оценивались перед окончательным сложением, разве это не то, что определяет приоритет? - person Andrew; 01.11.2010
comment
Порядок операций такой же, как и приоритет, да. Подождите, у меня есть другой способ подумать об этом, который может помочь; Я отредактирую свой вопрос, чтобы включить его. - person Antal Spector-Zabusky; 01.11.2010
comment
@ Эндрю: я добавил к своему ответу. Я надеюсь, что это поможет прояснить ситуацию. - person Antal Spector-Zabusky; 01.11.2010
comment
Я также получаю оценку правой руки в своем примере кода. _ || правда = правда; Просто оптимизация компилятора? - person Caladain; 01.11.2010
comment
Однако это не может привести к короткому замыканию. Конечно, это true (для чистых значений), но будет оцениваться левая часть. Попробуйте infiniteLoop() || true. Если это работает, ваш компилятор, вероятно, оптимизирует цикл. - person Antal Spector-Zabusky; 01.11.2010
comment
бесконечный цикл() || Истина возвращает истину. Действительно, я изменил бесконечный цикл, чтобы он возвращал значение, основанное на данных cin (дурацкий тест), и набил его точками останова, и ни одна из них не сработала. Он даже не удосужился сделать левую сторону ... полностью оптимизировал ее, когда увидел истину справа. - person Caladain; 01.11.2010
comment
странный. Я вообще не понимаю такого поведения: компиляция pastebin.com/zSKB1UBS с GCC 4.2.1 на OS X 10.6.3 без оптимизации никогда не печатает вторую строку; если я отношусь к нему как к C++, он ведет себя точно так же. Верхняя строка — единственная, которая когда-либо печаталась. - person Antal Spector-Zabusky; 01.11.2010
comment
Круто, именно дерево помогло мне увидеть, как работает приоритет. Операторы && настраиваются в дереве так, чтобы они оценивались перед ||, но короткое замыкание обеспечивает || оценивается в первую очередь. Все ответы были хорошими, но я искал другую визуализацию и, что более важно, визуализацию, которой будет следовать компилятор - еще раз спасибо! - person Andrew; 01.11.2010
comment
@Antal S-Z: что вы использовали для создания этого образа? Редактор изображений или что-то более сложное? - person darioo; 04.11.2010
comment
@darioo: я использовал пакет LaTeX qtree. Я не помню точный источник LaTeX, но это было почти \ttfamily \Tree[.+ [.* a b ] [.* c [.+ d e ]]] \Tree[.|| [.\&\& 1 1 ] [.\&\& infiniteLoop() infiniteLoop() ]]. Я визуализировал его в LaTeXit, который является хорошим приложением для Mac для рендеринга фрагментов LaTeX. - person Antal Spector-Zabusky; 04.11.2010

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

person AngelLeliel    schedule 31.10.2010
comment
Есть ли ссылка на стандарт С++, где это можно найти? - person Andrew; 31.10.2010
comment
Это был ответ Андрею :-) - person Caladain; 31.10.2010
comment
@Andrew: Поскольку вы сказали стандарт, нет; стандарт платный и поэтому недоступен нигде (если только вы не найдете его обрывки в Интернете). Однако вы можете загрузить черновик C++0x, который не меняет C++ в этом отношении. . Однако с таким распространенным явлением, как приоритет, вы можете быть уверены, что почти любой сайт, даже неавторитетный, будет иметь правильную информацию. - person GManNickG; 31.10.2010
comment
@Andrew: стандарт C++ прямо не определяет приоритет операторов. (Концептуальный) приоритет неявно присутствует в грамматике, поэтому вам придется обратиться к грамматическим правилам для окончательной ссылки (в данном случае это грамматическая продукция для логического-и-выражения). Например, приоритет работает не во всех случаях использования оператора запятой. Приоритет — это упрощенное представление грамматики. Тем не менее, мы думаем с точки зрения приоритета, и вы найдете таблицы приоритетов, например. в MSDN и в Википедии. Ура и чт., - person Cheers and hth. - Alf; 31.10.2010
comment
Для педантичных см. §5.14 [expr.log.and] и §5.15 [expr.log.or] стандарта C++03. Как уже упоминалось @Alf, приоритет подразумевается грамматикой. В важных предложениях также указано, что (в случае логического или) || гарантирует вычисление слева направо; кроме того, второй операнд не оценивается, если первый операнд оценивается как true. - person Adam Rosenfield; 01.11.2010
comment
... чертовски дебютный ответ на Stack Overflow! - person Steve Tjoa; 01.11.2010

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

TRUE && TRUE = True

FALSE && FALSE = False

True || False = True

Обновление:

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

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

Как и прежде, давайте разобьем его на части. && имеет более высокий приоритет, чем || и короткое замыкание логических операторов в C++.

1 && 1 = True.

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

false -> 0
true -> 1

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

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

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

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: Это по-прежнему приоритет, который заставляет && оцениваться первым, но компилятор также замыкает ИЛИ. По сути, компилятор группирует порядок операций следующим образом (УПРОЩЕННОЕ ПРЕДСТАВЛЕНИЕ, опустите вниз вилы :-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.

Теперь давайте перейдем к логическим выражениям: (&& = И, || = ИЛИ)

true AND false OR true

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

(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

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

(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's 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).

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

Возьмем, к примеру, язык программирования Ада. В Аде «И» и «ИЛИ» являются «нетерпеливыми» операторами.. они заставляют все оцениваться.

В языке Ада (истина И истина) ИЛИ (ложь И ложь) будет оценивать как (истина И истина), так и (ложь И ложь) перед оценкой ИЛИ. Ada также дает вам возможность замыкания с помощью AND THEN и OR ELSE, что даст вам то же поведение, что и C++.

Я надеюсь, что это полностью отвечает на ваш вопрос. Если нет, дайте знать :-)

Обновление 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)

Precence ТОЛЬКО устанавливает группировку операций. В этом && больше, чем ||.

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())

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

person Caladain    schedule 31.10.2010
comment
Нет ничего, что можно было бы назвать ИСТИННЫМ и ЛОЖНЫМ. Это правда и ложь. Во-вторых, false && false в этой ситуации не оценивается как || является оператором короткого замыкания (и true && true оценивается как true) - person Chubsdad; 31.10.2010
comment
Сама причина, по которой оператор вообще может закоротить, заключается в том, что (true || X) == true. Часть о false && false здесь не имеет значения, но она все равно верна. - person cHao; 31.10.2010
comment
Извините, я провел слишком много времени в среде, которая специально не вызывает короткого замыкания, таким образом оценивая обе стороны утверждения во всей их полноте. ИСТИНА и ЛОЖЬ были просто моей короткой рукой для оценки оператора, превращающейся в логический результат (истина или ложь) для выражения. TRUE может равняться (X›2) или ключевому слову true или чему угодно. То же самое с ЛОЖЬЮ. - person Caladain; 31.10.2010
comment
Однако этот пример доказывает, что && НЕ имеет приоритета, так как || оценивается перед вторым &&. Это показывает, что && и || имеют одинаковый приоритет и оцениваются в порядке слева направо. - person Andrew; 31.10.2010
comment
Если бы && имел приоритет, он бы оценил первый && (1), затем второй && (бесконечные циклы) и повесил бы программу. Поскольку этого не происходит, && не оценивается до ||. - person Andrew; 31.10.2010
comment
При коротком замыкании || возникает оператор и прерывает выполнение второго выражения &&, что означает || оператор был выполнен ДО второго оператора &&. Это подразумевает выполнение слева направо для && и || (не && приоритет). - person Andrew; 01.11.2010
comment
Этот ответ слишком длинный. Это технически точно и тщательно, но я не голосую за него, потому что он слишком многословен. - person Adam Rosenfield; 01.11.2010
comment
(это многословно, потому что я ответил на этот вопрос примерно 3 раза ... пытаясь объяснить это, чтобы понять концепцию :-( - person Caladain; 01.11.2010
comment
@Adam Rosenfield: Вы говорите многословно, как будто это плохо :-) Я думаю, что это превосходно полный и исчерпывающий ответ. Решительный +1 от меня. - person Antal Spector-Zabusky; 01.11.2010
comment
@Adam Rosenfield: Это технически неверно. Он имеет ошибки и внутренне противоречив. Чтобы выбрать несколько моментов: но все вольны переупорядочивать сгруппированные сравнения по своему усмотрению, это просто неверно. И &&, и || подразумевают точку последовательности, и их второй операнд не должен оцениваться, если он не нужен. Кроме того, приравнивание приоритета к порядку операций вводит в заблуждение. - person CB Bailey; 01.11.2010
comment
Напишите мне по электронной почте Чарльзу (или кому-либо еще) с несоответствующими пунктами. Я писал импровизированно, и я не сомневаюсь, что это можно было бы почистить или даже расширить. - person Caladain; 01.11.2010
comment
@Charles — en.wikipedia.org/wiki/Order_of_operations. Порядок операций (формально правило приоритета) — это правило, используемое для однозначного определения того, какие процедуры следует выполнять первыми в заданном математическом выражении. Это подкреплено «Программированием и решением проблем с помощью C++ 4th Edition» Нелла Дейла | Чип Уимс. Последовательность моего поста должна быть очищена для дальнейшего использования и объединена с постом Antal S-Z (я не думал делать дерево синтаксического анализа. Знал, что что-то забыл :-D), чтобы составить довольно окончательный ответ по этому вопросу. . - person Caladain; 01.11.2010
comment
Кроме того, я, по общему признанию, ограниченно понимаю, что короткое замыкание не гарантируется языком и является чертой компилятора, которую они могли или не могли реализовать. Могут ли более знающие головы ответить, ДОЛЖЕН ли компилятор реализовать логическую логику в коротком замыкании, или если это действительно зависит от компилятора, и все просто делают это? По сути, это ДОЛЖНО быть в спецификации языка, или это просто общепринятая практика, которую делает? - person Caladain; 01.11.2010
comment
@Caladain: извините, но предоставленная вами ссылка на Википедию просто неприменима к C++. В C++ правила грамматики (из которых люди делают вывод о приоритете) определяют, как сложные выражения интерпретируются с точки зрения группировки. Они сами по себе не определяют порядок, в котором подвыражения могут оцениваться во время выполнения. Когда вы применяете порядок операций к компьютерному языку, а не просто к абстрактным математическим выражениям, вы подразумеваете что-то, что просто неверно. Короткое замыкание гарантировано языком. Это не просто оптимизация. - person CB Bailey; 01.11.2010
comment
@Чарльз - Достаточно честно. Правило приоритета — это то, как большинство людей думают об этих вещах (эмпирическое правило), и этот термин внутренне согласовывался с университетом и моими справочниками. Нет никаких сомнений в том, что грамматические правила являются окончательным источником такого поведения, но в книгах/сети/и т. д. он упоминается как порядок операций (правило приоритета), потому что он имитирует поведение математической области и его легче понять людям. . Это придирчивая вещь, которая, я думаю, отвлекает от общего ответа на вопрос, но если вы хотите отправить эту беседу по электронной почте, я был бы счастлив .... (pt1) - person Caladain; 01.11.2010
comment
(часть 2)... чтобы сформулировать более подробное заявление, которое охватывает как спецификацию языка, справочники, так и объясняет, откуда берутся термины, что они означают и как мы используем их в повседневных операциях, чтобы устранить любые будущая путаница (этот ответ в сочетании с (Antal SZ), охватывающим практически все применимые векторы для изучения этой проблемы для программиста-непрофессионала), я был бы более чем счастлив сделать это. - person Caladain; 01.11.2010

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

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

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

Таким образом, если принять обычные определения TRUE и FALSE, TRUE && TRUE || FALSE && FALSE вычисляется, начиная слева. Первое TRUE не влияет на результат первого &&, поэтому вычисляется второе TRUE, а затем вычисляется выражение TRUE && TRUE (присваивается значение TRUE). Теперь || знает свой LHS. Более того, его левая сторона заставила узнать результат ||, поэтому она пропускает оценку всей своей правой стороны.

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

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

person RBerteig    schedule 01.11.2010

&& действительно имеет более высокий приоритет.

person Ignacio Vazquez-Abrams    schedule 31.10.2010

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

Не совсем.

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

Будет оцениваться так:

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

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

person Anonymoose    schedule 06.12.2012

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

person Philipp    schedule 31.10.2010
comment
Я так понял, но в данном случае это показывает, что && не имеет приоритета над || (вместо этого происходит оценка слева направо). - person Andrew; 31.10.2010
comment
@ Андрей: Неправда. || прекращает оценку, когда его левая сторона верна; он никогда не утруждает себя вычислением правой части, потому что значение всего выражения известно. - person Billy ONeal; 01.11.2010

Что касается примера 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 )
person filipe    schedule 31.10.2010
comment
(истина || (бесконечный цикл() && бесконечный цикл()) - person Andrew; 01.11.2010
comment
Если бы && имел приоритет, он попытался бы оценить два бесконечных цикла. Вместо этого он закорачивает || (который является следующим оператором слева направо) и сокращает остальную часть выражения. - person Andrew; 01.11.2010
comment
приоритет не обязательно означает порядок, в котором оцениваются операторы, а означает, как выражения группируются вместе. см. ответ RBerteig ниже. - person filipe; 01.11.2010
comment
или обратитесь к Antal S-Z, он написал целую лекцию на эту тему в своем ответе. - person filipe; 01.11.2010

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

#include <iostream>
using namespace std;

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

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

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

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

Таким образом, с компилятором, который находится на переднем крае соответствия C++0x, программа может завершиться с некоторым результатом, даже если она выполнит вызов infiniteLoop ! Конечно, с таким компилятором он мог бы также производить такое ужасное явление, как носовые демоны...

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

Ура и чт.,

person Cheers and hth. - Alf    schedule 01.11.2010

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

1 * 1 + 0 * 0 == 1

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

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

person Macke    schedule 04.11.2010

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

person Carl    schedule 01.11.2010
comment
Нет, это не оптимизация компилятора, это правило языка. - person user207421; 01.11.2010

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

  (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.

person nonopolarity    schedule 01.11.2010