Условный оператор if и операция ?:

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

  • Одним из способов управления поведением программы является использование оператора if
Вам постоянно придётся инструктировать свои программы: Если тут так, то делай это, а если так, то делай то. Для такого инструктирования и существует оператор if.

Текст первого кода не должен вызывать затруднений, так как легко читается по-русски. Если от результата выполнения условия требуется выполнение более одного действия, то эти действия нужно записать в фигурных скобках и вообще — хороший стиль оборачивать в фигурные скобки даже одно действие из if. Смотрите различие первого показанного кода и второго. Второй код раздут, оттого тяжеловат и читается на первый взгляд сложнее, но внутри фигурных скобок можно выполнять различное количество действий.

Понять нужно.
Условие может включать в себя другие условия:
Если здесь так, то выполнить 1
Иначе, если здесь так, то выполнить 2
Иначе, если здесь так, то выполнить 3

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

В листинге #1 возможно 3 развития поведения программы. Само развитие определяется результатом проверки условия некоторого выражения. В примере на одно выражение кода -(a > b)- приходится три варианта выполнения.
  • Предпочитайте if…else нескольким if, если перед вами проблема выбора
  • Вложенные операторы if…else могут работать намного быстрее, чем последовательность операторов if с одиночным выбором, так как возможен ранний выход после удовлетворения одного из условий.
  • Во вложенных операторах if…else проверяйте сначала условия, которые с большей вероятностью окажутся равными true. Это способствует более быстрому выполнению вложенных операторов if…else и более раннему выходу из них, чем при проверке в начале редких случаев.
Есть в C++ целый род проблем висячих мерзостей. Мерзости они потому, что мешают работе программы. Но все эти мерзости берут начало из недостаточности знаний пишущих код. Одна из представительниц этого мерзкого рода — проблема висячего else.
Теперь смотрите, что это такое. Изучите нижепоказанный код, посмотрите на его работу, что выводится на экран при выполнении программы?

В листинге #a1 программист мог хотеть не того, что в конечном итоге отдаётся при выполнении программы, созданной из такого кода. При заданных сейчас значениях проблемы не наблюдается. Но что должна делать программа? Исходя из кода можно предположить, что программист решил при x меньшем пяти выполнять внутреннюю проверку для y, а если x не меньше пяти, то обрабатывать только x. Вы пробовали разобрать этот код? Пришли ли к таким же выводам? Но на самом деле всё происходит по другому. Поменяйте значение y с пятнадцати на любое меньшее пяти и допустимое для типа int, после чего скомпилируйте и запустите программу. Попробуйте заметить нестыковку. Происходит сиё безобразие по той причине, что код из #a1, на который обращено ваше внимание, компилятор видит вот в таком виде:

После того, как вы проанализируете своими силами раскрытие кода, поймёте различие, вам станет понятней, отчего при y меньшем пяти программа повела себя немного не так, как вы или программист такую написавший могли того ожидать.
Этот пример с висячим else здорово демонстрирует, что оборачивание фигурными скобками всегда идёт только на пользу и даже способно предотвращать подобные проблемы в самом их зародыше.
Не одним висячим else C++ может затруднять жизнь начинающего программиста. Есть и другие проблемы. Одна из проблем является следствием неявных преобразований.

Как ни посмотри, в листинге #b1 должно срабатывать условие, описанное для равенства значений, но оно необязательно сработает. У меня не срабатывает. Если у вас вдруг сработало, попробуйте посмотреть в другом компиляторе. Здесь злую шутку играют неявные приведения типов. Число с точкой, которое представлено компилятору как литеральная константа, компилятором определяется как значение с типом double. Во float приходит double, происходит неявное приведение. В общем, во всём пакостном поведении виновато приведение. Этот код легко исправить, задав правильный тип при инициализации:

После собственноручного выравнивания типов проблема перестаёт быть проблемой.

  • Всегда старайтесь избегать смешения типов, вообще всегда. Бывает, что невозможно избежать смешения, но в большинстве случаев смешение происходит необязательное. Хоть компилятор и может вырулить типизацию в правильном направлении, иногда, как было показано в листинге #b1, компилятор не справляется своими силами в выполнении более общей задачи.
Ещё одной проблемой оказывается то, что в компьютере невозможно хранить точное представление дробных чисел. Эта проблема больно сказывается при сравнении. В языке C++ невозможно точно сравнивать дробные числа. При сравнении дробных чисел приходится добавлять погрешность в сравнение. Обычно для погрешности хватает одной тысячной, но в некоторых случаях она может быть нужна меньшей. Ограничения типов в выборе диапазонов не дадут использовать слишком малую погрешность. Чтобы работать со слишком малыми числами, нужно скачивать отдельную библиотеку, но это всё выходит за рамки статьи. Многие новички бьются об одни грабли, когда пытаются сравнивать дробные числа. А что это за грабли я только что написал. Пример таких граблей достаточно прост. Описан сей момент в статье, написанной уже после этой. Сравнивание происходит не на равенство, а на разность чисел: если разнасть чисел меньше погрешности, то сравниваемые числа признаются равными.
Бывает и так, что новички используют неправильный синтаксис для сравнения. В некоторых языках возможны такие конструкции, которые невозможны в C++, либо работают своеобразно. Иногда к подобным конструкциям мы прибегаем на интуитивном уровне. Пример одной из них: проверка числа на принадлежность диапазону:

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

Скорее всего каждый мой читатель из коллегии новичков мог понять, чего хотел добиться программист, написавший листинг #c1. Но не каждый догадывается, как выполняется показанный код. Компилятор раскладывает восьмую строчку кода следующим образом:

Такое написание кода вообще не сравнение чисел, это сравнение false с true: if ((true < false) < true){ что-то }. Последнее немного с головы, значения не задавались, но приблизительно таким в конечном счёте оказывается видимый компилятором код. Объяснить новичку низкоуровневую тонкость не очень просто, вам достаточно осознать, что работает другой механизм сравнения.
Писать код для подобных случаев стоит в другом виде, в том, который делает цель своего выполнения яснее:

Этот код правильнее хотя бы потому что понятно сразу, чего добивается программист и как работает компилятор.
< — логическое меньше
> — логическое больше

<= — логическое меньше или равно
>= — логическое больше или равно

== — логическое равенство
!= — логическое неравенство

&& — логическое И

|| — логическое ИЛИ

! — логическое НЕ

Тернарная операция ?:

В изучаемых вами примерах вы иногда будете встречать необычную первое время операцию ?:. Эта операция эквивалент оператору условия с ветвлением if … else, пример которого приведён в листинге #1 (в самом начале статьи). Выполняют оба оператора одно и то же, но сами по себе они отличны друг от друга и внешним видом, и синтаксисом, и тем, что в некоторых определённо схожих случаях можно словить различное поведение.
  • ?: является "арифметическим если"
  • Может у кого-то DosBox, как и у меня. Символ вопроса ‘?’ на клавиатуре есть около русских символов ‘Б’,’Ю’, с нажатым шифтом можно поставить. Переключение раскладок англ-рус правым ctrl.
Операция ?: является троичной операцией (принимает три операнда) и работает следующим образом:

  • Первый операнд неявно преобразуется в bool. Он вычисляется, и все побочные эффекты завершаются перед продолжением.
  • Если результатом первого операнда является значение true (1), оценивается второй операнд
  • Если результатом первого операнда является значение false (0), оценивается третий операнд
Одним из существенных отличий обычного оператора if … else от операции ?: является то, что результат операции ?: можно сохранять в переменную. Тип результата операции зависит от типов её второго и третьего операндов. При разнобое типов второго и третьего операнда начинает работу сложный механизм проверок и неявных преобразований типов. Суть доносимой мной мысли, что у операции есть то, что можно сохранить в переменную прямым присваиванием как будто бы сохраняется сама операция.

В листинге #2 собрано три кода: код с операцией, код с условным оператором и укороченная форма кода с условным оператором. Результатом выполнения каждого из них оказывается одно и то же. Но имеется одна особенность, в случае с операцией ?: возможна инициализация переменной результатом выполнения некоторого вычисления, такая инициализация невозможна в случае использования оператора с ветвлением if.
Понять принцип действия операции ?: поначалу кажется сложноватым, но осилить его вполне по силам любому новичку.

В листинге #3 наглядно показано, как код с тернарной операцией ?: можно разложить в код с условным оператором с ветвлением, в if.
Другой особенностью операции ?: является то, что выражение с этой операцией возможно использовать в левой части оператора присвоения =

Код #4 может вас немного запутать, поэтому привожу более очевидный пример:

В листингах #4,#4a показано как возможно использовать выражение с операцией ?: в левой части оператора присвоения. Читается девятая строчка кода #4 так: "Если result не ноль (false), то записать в a значение 100, а иначе записать в b значение 100". Необычно, но такое в C++ и других языках программирования используется. Некоторые программисты даже увлекаются использованием операции ?:, чем делают коды неудобочитаемыми, что не очень хорошо и не приветствуется большей частью сообщества программистов.
  • Если в тернарной операции второй и третий операнд разнотипны, то сработает механизм преобразований, который начнёт выполнять приведения типов. Компилятор будет стараться выровнять типы путём уширения меньших. При срабатывании такого механизма операцию ?: нельзя использовать в левой части оператора присваивания.

Идём дальше.

Вам не нужно ломать голову над кодом из листинга #5, в тем более многие из вас ещё не знают о циклах, массивах и всяких других премудростях. Только 12-я строчка приведена как пример усложнения кода. Мало того, что сам код читается тяжело, цифра 1, символы ‘i’ и ‘!’, ещё символа ‘l’ не хватает. Показан этот код, конечно, из-за сложного восприятия операции ?:, особенно тяжело, когда весь код забит такими выкрутасами. Суть работы кода такова, что просто выводится три строчки: Jason Quillstone at your service.

Переходим к листингу #6. Как вы думаете, что будет выведено на экран? Проверьте, правильно ли ваше предположение самостоятельно. Тут срабатывает неявное приведение типа первого операнда value, к типу bool. Первый операнд в такой языковой конструкции всегда приводится к булеву типу. Поскольку всё, что не ноль есть истина, то результат операции определён вторым операндом, иначе бы, если value хранило ноль, результат операции был бы определён третьим операндом.
С этого момента уровень сложности моих объяснений резко повышается, если вы совсем-совсем новичок, не знаете о функциях и указателях абсолютно ничего, то можете бегло пробежать, но не заморачиваться с моими примерами
Использование тернарной операции ?: хоть и выглядит диковато, иногда делает код гораздо удобочитаемее, чем сделало бы обычное if…else, а ещё сокращает код: то, что можно было расписать на несколько строчек, компактно умещается в одну. Привожу пример работы тернарной операции ?: с объектом cout. Пример выглядит достаточно удобоварим и приятен глазу:

В листинге #7 простая замена операцией такого блока кода:

Весь этот блок умещён в семнадцатой строчке.
В листинге #7 результат поведения программы определяется типом первых символа строки, введённой с клавиатуры. Если строка начинается с цифры, либо с цифры со знаком + или -, обозначающими положительность или отрицательность числа, то сработает одна функция, если с символа, к цифрам не принадлежащим, то другая функция. В случае срабатывания функции по цифре из строки выдернятся целое число, с которой строка начинается, хотя если число не уместится в тип int, то всё равно сработает функция для строки. Этот пример вряд ли практически можно использовать, хотя при определённой фантазии, полагаю, возможно. Он представляет собой интерес с другой точки зрения: понимание происходящего. Показан этот листинг для того, чтобы было понятно, иногда короткая запись ?: достаточно приглядно вписывается в код.
Использование тернарной операции ?: определяется набором правил в стандарте языка. Описывыается операция в разделе 5.16 стандарта. Так как мы новички, а я совсем не всё знаю, всех правил описать не смогу, но вынесу на заметку самое такое важное. Что такое тернарная операция понимаете? Это операция ?:. Так вот, тернарная операция состот из трёх операндов, т. е. вы можете разделить саму операцию на три части: проверяемое условием выражение; действие при положительном показателе; действие при отрицательном показателе. Каждая часть из трёх является операндом. Каждый операнд представляет собой некоторое выражение, из которого происходят какие-либо результаты. Эти результаты, получаемые из выражений, имеют какой-то тип. Какой имеют второй и третий операнды зависит в первую очередь от самого программиста, а в остальном от работы компилятора с приведением типов. Первый операнд всегда приводится к типу bool: выполнилось условие или нет; а оставшиеся два могут иметь как одинаковые, так и разные типы. В случае разницы типов второго и третьего операндов компилятор старается привести тип второго операнда к типу третьего, а тип третьего к типу второго. Если операнд массив или функция, то компилятор приводит тип операнда к типу указатель, а если тип lvalue, то приведение происходит к rvalue. lvalue — это то, что стоит слева от оператора присваивания, а rvalue то, что справа. Более подробное объяснение lvalue и rvalue выходит за рамки cтатьи, это отдельная тема. Суть их в том, что они являются левосторонней сущностью (left value) и правосторонней (right value).
  • Не смешивайте типы второго и третьего операндов в тернарной операции. Без хорошего понимания процессов приведения в лучшем случае при ошибке ваш код компилятор не скомпилирует, в худшем можно ожидать неопределённое поведение программы.
Правила языка C++ допускают использование в одной тернарной операции вторым и третьим операндом сущностей разных типов, компилятор будет пытаться привести каждый из операндов к типу другого. Чтобы кашеварить подобным образом и творить работающий хаос, нужно всё знать о неявных приведениях, а лучше, конечно, не выпендриваться и не мешать типы.
Весьма популярным оказывется использование тернарной операции ?: при возврате значений из функций. Это ещё одно отличие ?: от составного оператора if…else.

  • Тернарная операция пользуется популярностью по определённым причинам: код выходит короче, но важнее, что это, так сказать, встраиваемая операция. Её удобно внедрять в нужное место кода.


9 комментариев на «“Условный оператор if и операция ?:”»

  1. сер:

    а эту строчку случайо мы не забыли?
    using namespace std;

    Автор сайта отвечает:
    Не. в С++ 3.1 Пространства имен нет. Для других компиляторов действительно нужно

  2. Владимир:

    А как переключить язык:
    else cout<<"РАВНЫЕ ЗНАЧЕНИЯ"; //Если оба условия не выполнены, то пишем что равны

    "Равные значение" — язык не переключается?

    Автор сайта отвечает:
    в Borland C++ 3.1 переключить язык — это правый ctrl,а знак вопроса shift + 9 при русской раскладке (у меня так)

  3. Владимир:

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

  4. Андрей:

    В С++ па и в С тоже среда программирования ненужна!
    Можно просто программу в Блокноте(Если имеется в виду не ооп(Объектность-Орьентированое Программирование) писать А потом менять расширение(Для компиляторов) 😉 😎

    Автор сайта отвечает:
    Среда программирования нужна в особенности начинающим программистам. В блокноте труднее искать ошибки, чем с помощью отладчика среды.
    В блокноте и фотографию Моно Лизы написать можно, а потом открыть ее с помощью программы просмотра изображений.

    Написать ООП программу в блокноте точно так же как и написать не ООП программу

  5. Аноним:

    Paydyot vse otlichno

  6. Светослав:

    что бы переключить раскладку используй setlocale (0,»»);

  7. Катя:

    подскажите, пожалуйста, а что делать если мне нужно работать не с цифрами, а с "да", "нет", введенными с клавиатуры? Как сделать так чтобы с++ распознавал русские да, нет?

    Автор сайта отвечает:

    Катя говорит:
    Спасибо, я попробовала, ошибок не выдает, но, когда в консоли пишешь "да", выдает ответ для "нет". Возможно я все таки не правильно ваш код вписала? (Visual Studio)

    Автор сайта отвечает:
    Все правильно, я поcмотрел в VS2013

    Вывело как должно.

    еще в 2010 студии посмотрел, тоже работает корректно.

    Катя говорит:
    спасибо БОЛЬШОЕ!

  8. Локи:

    Здравствуйте, помогите пожалуйста
    error: expected ‘)’ before ‘;’ token
    if((b>=0); ((z+x)!=0); (g*log(fabs(x+z)!=0))

    • А что Вы хотите сделать? Логические условия объединяются логическими операциями ||, &&, а не разделяются точкой с запятой.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/mayk-makgrat/java-programmirovanie-dlya-nachinauschih-17254044/?lfrom=15589587
Яндекс.Метрика