Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 37
Текст из файла (страница 37)
Нельзя полагаться на определенныи порядок вычисления аргуменпюв вызова функций — это плохой стиль программирования, приводящий к непредсказуемому поведению программы. Вызов функции32() оперирует единственным аргументом — выражением с операцией следования (операцией запятая), чье значение равно 1«+. Принудительную группировку выражений можно осуществить с помощью круглых скобок. Например, а*ЬУе означает (а*Ь) Ус, но применение круглых скобок позволяет навязать иной порядок вычислений исходного выражения — а* (ЬУс) . Для вычислений с плавающей запятой выражения (а*Ь) Ус и а* (ЬУс) могут давать существенно разные результаты, так что компилятор обязан соблюдать указанный порядок вычисления выражений. 6.2.3.
Приоритет операций Уровни приоритетов операций и правила ассоциативности отражают естественные способы их использования. Например: 11'(1<=0 ] [ так<1) означает «если (меньше или равно 0 или если тах меньше 1». То есть это эквива- лентно следующему выражению (('( (1<=0) ( ) (так<1) ) Л ... а не бессмысленному (хотя и формально корректному) выражению 11'(1<=(0[ [тах) < 1) Круглые скобки можно использовать всегда, когда у программиста есть сомнение. Чем сложнее выражения, тем чаще применяют круглые скобки.
В то же время, слишком сложные выражения могут приводить к ошибкам. Поэтому, если вы чув- может вычисляться как »[1] =1, или как у[2] =1, или даже еше более странным образом. Компиляторы могут предупреждать о подобных неоднозначностях (атЬ(яи(- йез), а могут, к сожалению, и не предупреждать. Для операции, (операцил запятая — готта орега(ог), а также для операций аь (логическое «И») и ] ( (логическое «ИЛИ») гарантируется, что их левый операнд будет вычислен раньше, чем правый операнд.
Например, с помощью выражения Ь= (а=2, а«1) переменной Ь будет присвоено значение 3. Примеры использования операций [ ) и ь а даны в 56.2.3. Для встроенных типов правый операнд операции аь вычисляется лишь в случае, когда левый операнд равен Вне. Для операции [ ] правый операнд вычисляется только в случае, когда левый операнд равен майе. Эти правила часто называют редуцированной схемой вычислений (айаг(-с!гси11 еча!иа((оп). Обратите внимание на то, что запятая как операция следования (ледиепс(пя орега(ог) логически отличается от запятой, используемой в качестве разделителя аргументов функций.
К примеру, в следующем коде П4 Глава б. Выражения и операторы ствуете настоятельную необходимость в круглых скобках, то возможно стоит применить вспомогательные (промехсуточные) переменные и избавиться от сложных выражений. Бывают случаи, когда приоритет операций не обеспечивает очевидности порядка вычислений. Например: (г" (латала==о) 77...
Наложение маски (переменная щи) на! и сравнение результата с нулем является хотя и очевидной, но неправильной интерпретацией. Из-за того, что у операции == приоритет выше, чем у операции а, выражение на самом деле эквивалентно (а(таз)с==0). К счастью, в большинстве подобных случаев компилятор выдает предупреждение о возможной ошибке.
В общем, нужно применить группируюшие круглые скобки: (Г"( (Гьтавв) ==О) У ... Следующее выражение работает не так, как ожидал бы математик: (г(0<=х<=99) 77... Это формально корректное выражение интерпретируется как (0<=х) <=99, где первое сравнение вырабатывает ггие или уайе. Эти логические значения неявно преобразуются в 1 или О, а затем сравниваются с 99, что всегда дает «ае. Для проверки попадания значения х в диапазон О.. 99 можно написать (У(0<=к ьь х<=99) 77... Для новичков типично ошибочное применение операции = (операция присваивания) вместо операции == (операция сравнения на равенство); ф'(аГ 7) Это довольно естественно, поскольку во многих языках программирования знак = означает «равно».
Но к счастью, большинство компиляторов распознают подобного рода проблемы и предупреждают программиста о возможных ошибках. 6.2.4. Побитовые логические операции Побитовые логические операции (ЫПя(зе (орса( орега(огв) ь, ), ", -, » и «применяются к интегральным типам и перечислениям, то есть к типам Ьоо!, сйаг, вйогг, (иб 1оая (возможно, с модификатором иаялпеИ). Обычные арифметические преобразования определяют тип результата (5С.б.З).
Типичным примером использования побитовых логических операций служит задача построения небольшого множества (битового вектора). Каждому элементу множества соответствует один бит беззнакового целого, так что количество элементов множества ограничивается количеством бит. Бинарная операция а трактуется как операция пересечения множеств, операция ) трактуется как объединение множеств, операция " — как симметричная разность, и операция -— как дополнение. Для именования элементов такого множества можно использовать перечисление.
Вот маленький пример, взятый из реализации потоков типа овггеат: 6.2. Обзор операций языка Сч-ь сии<и <оз Ьазе:: <о<па<в 1 еоо«Ь«=0, ео<Ь<г1, 1аОЫ<=2, ЬайЬ«=4 Можно устанавливать и проверять состояние потока следующим образом: в<а<в=яоо«Ы< < У.. << (в<а<ел (Ьа<<Ы<)<аОЬ«) ) Л' с потоком проблемы Здесь дополнительные круглые скобки необходимы, так как приоритет операции ь выше, чем приоритет операции ). Достигнув конца ввода, функция может просигнализировать об этом следующим образом: зиив ) =во)ЬЬ< где с помощью операции ) = к текущему состоянию потока добавляется еоуЬ1<, в то время как простое присваивание жа<е=ео1Ы< обнулило бы все остальные биты.
Флаги состояния потока доступны и клиентам (то есть вне реализации потоков). Например, клиент может посмотреть, как различаются состояния двух потоков: т«в(<=с<и.пЬ<а<е() "сои<.пЬ<а<е() < Б'г<Ь<о<е() возвращает состояние Вычисление различий в состояниях потоков встречается не столь уж и часто. Но для других типов, базирующихся на битовых векторах, эта задача весьма актуальна. Например, важно сравнивать битовые вектора, один из которых представляет собой набор обрабатываемых прерываний, а другой — набор прерываний, ожидающих обработки.
Обратите внимание на то, что вся эта возня с битами выполняется внутри реализации потоков, а не в клиентском коде. Удобная манипуляция битами может быть и важна, но из соображений надежности, сопровождаемости, переносимости и т.д. ее лучше запрятать поглубже, на нижние уровни реализации системы. За более подробными сведениями о множествах обратитесь к описаниям таких типов стандартной библиотеки, как ве< (З17.4.3), ЫЬе< (517.5.3) и гесгог«Ьоо1> (З16.3.11). Удобно использовать битовые поля (ЗС.8.1), чтобы выполнять битовые сдвиги и маскирование с целью извлечения отдельных бит из целочисленного слова. Это, конечно, можно сделать и с помощью побитовых логических операций. Например, можно следующим образом извлечь средние 16 бит из 32-разрядного 1опя, иив<впед влог< тиЫ<е <<опв а) )гв<иги <а»8) ваха< ) Ие путайте побитовые логические операции с логическими операциями ьь, ) ) и ) .
Последние возвращают в ие или уайе и в основном используются в условных выражениях операторов <1,' иЫ1е или удг (З6.3.2, З6.3.3). Например, . О (не нуль) есть «ие, в то время как -О (битовое «НЕ») дает набор всех битов, равных единице, что в двоичном дополнительном коде означает -1.
6.2.5. Инкремент и декремент Операция в+ выражает инкремент (увеличение но единицу) самым непосредственным образом, в то время как применение комбинации из сложения и присваи- 176 Глава б. Выражения и операторы вания делает то же самое лишь косвенно. По определению, +ь1га1ие означает 1га1ие+=1, что в свою очередь означает 1га1ие=1га1ив+1, если у 1ва!ие нет побочных эффектов. Выражение, возвращающее подлежащий инкременту объект, вычисляется один (и только один) раз. Операция декремента записывается как —. Операции ьв и -- используются как в префиксной, так и в постфиксной формах. Значением выражения ммх является новое (инкрементированное) значение х. Например, у=++х эквивалентно у= (х+=1) .
Значением же выражения х+ь является старое значение х. Например, у=х++ эквивалентно у= (ьых,к+=1, г), где переменная г имеет тот же тип, что и х. Как и в случае сложения и вычитания указателей, операции ь+ и -- работают над указателями в единицах размера указуемых объектов, что удобно для массивов; р+ь заставляет указатель р настроиться на следующий элемент массива (В5.3.1). Операции ++ и -- особенно полезны при инкрементировании и декрементировании переменных в циклах. Например, копирование строк с терминальным нулем можно выполнить следующим образом: воЫ сру (сйаг* р, сопят сдпг* а) ( ыь(1в(*рьл="Оь.ь) ) Язык С++ (как и язык С) либо сильно любят, либо сильно ненавидят за возможность написания кода, ориентированного на выражения (ехргетоп-ог/епгед сод(пя). Так как вй/1е (*р-~ь;=Я((++) выглядит более чем странно для программистов на большинстве языков (кроме С/С++), а на С и С++ подобного рода фрагменты встречаются отнюдь не редко, то стоит присмотреться к ним подробнее.