Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 37
Текст из файла (страница 37)
6.2.2. Последовательность вычислений ! [орядок вычислений подвыражений внутри выражений не определен. В частности, не стот предполагать, что выражения вычисляются слева направо. 1!апример: [а[х =/(2[+у Щ; // неизвестно, что вызовется первым — / О или уО 1!рп отсутствии ограничений на порядок вычислений можно сгенерировать более качественный код. Однако отсутствие ограничений на порядок вычислений может привести к неопределенным резулътатам. Например, Гели и второй, и третий операнды оператора?; являются!ча1це и имеют одинаковый тип, результат имеет такой же тип и является 1ча!це, Такие правила работы с 1иа1пе предоставляют большую гибкость при использовании операторов.
Особешю это полезно при написании кода, который должен работать одинаково и эффективно как со встроенными, так и с определяемыми пользователем типами данных (например прн написании шаблонов или программ, которые генерируют код С ь+). Типом резулътата оператора в[хео/'является инте~ ральный тип без знака, называемый в[хе ь и определенный в <се(сЫе/>. Типом разности указателсп будет интегральный тип со знаком, называемый р(ге[((г (и определенный в <сз(с[с[в/>.
Реализации не обязаны проверять на арифметическое переполнение и вряд лп какая-нибудь из них это делает. Например: 1б4 Глава б. Выражения и инструкции т11=1, и[с) = 1+»-, // результат не определен мохсет вычисляться либо как э[1) = 1 либо как о[2) = 1, либо привести к еще более странным результатам. Компиляторы могут выдавать предупреждающие сообщения прн возникновении такой неоднозначности.
К сожалению, большинство из нпх этого не делает. Операторы, (запятая), 8 с (логическое И) и () (логическое ИЛИ) гарантируют, что операнд слева будет вычислен до операнда справа. Например, Ь=(а=Я, а+1) присвоит 3 переменной Ь, Примеры использования )(н а<« можно найти в э б.2,3. Для встроенных типов второй операнд оператора ос вычисляется, только если его первый операнд равен 1гае.
Для оператора)) второй аргумент вычисляется только в том случае, если первый аргумент равен/а1ее. Иногда такой подход называют быстрьсл~ еьтислениеи, Обратите внимание, что, (запятая) в качестве указателя последовательности логически отличается от запятой, используемой в качестве разделителя аргументов при вызове функций. Например: /1 (и[1), 1.н-), // деа аргулента 12 ((и(г],Й->) ), //наин иргуменси Вызов/1 осуществляется с двумя аргументами, э[1) и 1+»-, и порядок вычисления аргументов не определен. Расчет на определенный порядок вычисления аргументов является исключительно плохим стилем и приводит к непредсказуемому поведению программы.
Вызов~2 имеет один аргумент — последовательность выражений, разделенных с запя той, что эквивалентно гьь. Для явного задания группирования можно пользоваться скобками. Например, а*Ь/с означает (а'Ь)/с, поэтому нужно пользоваться скобками, чтобы вычислить а*(Ь/ с); а*(Ь/с) может быть вычислено как (а*Ь)/с, только в том случае, если пользователь не заметит разницы. В частности, для многих вы шслсний с плавающей точкой результаты а*(Ь/с) и (а*Ь)/с могут значительно отличаться, поэтому компилятор будет обрабатывать выражения точно так, как они записаны. 6.2.3. Приоритет операторов Уровни приоритетов и правила ассоциативности выбраны из соображений наиболее типичного использования, Например, г/ ( 1< =0 (( сп ах <1) О...
означает «если 1 меныпе или равно 0 илп если тах меньше нь То есть, это эквивалентно следующему: Ц' ( (1<=0) (( (тих<4 ) // а не 1/(1<= (О)) тах) <1) // ... что хотя и допустимо, но вряд ли имев~ смысл. Тем не менее следует применять скобки каждый раз, когда программист сомневается в правилах. Чс м более сложными становятся выражения, тем чаше используются скобки.
Следует, однако, помнить, что сложные выражения являются псточникамп ошибок. Поэтому, если вы ощущаете непреодолимую потребность в скобках, 165 5.2. Обзор операторов подумайте о том, чтобы завести дополнительную переменную для разбиения одного сложного выражения на несколько более простых. Есть случаи, когда последовательность выполнения операторов неочевидна. Например: 1/ (1ьтавЬ==О) // не то! внражение == будет операндом для З В примере пе происходит наложение маски (отава) на1 и затем проверки результата на равенство нулю.
Так как оператор == имеет более высокий приоритет. чем $, выражение интерпретируется как 1 э (ляазй==0). К счастью, в таких случаях компилятор может легко обнаружить оппябку и выда~ь предупреждение. В нашем примере скобки очень важны: я/ ((1ьтавЬ) == 0) // ... Следующее выражение означает совсем не то, что ожидал бы математик: я/ (0<=х<=99)/1 Это выражение допустимо, но интерпретируется как (О <= х) <= 99, где результат первого сравнения равен либо ггае либо /а(ве. Затем это лопяческое значение преобразуется в 1 или 0 и сравнивается с 99, что всегда выдаст 1гае. Чтобы проверить, находится ли значение х в диапазоне 0..99, мы можем написать: Ц (0<-х оах<=99) О..
Новички обычно делают следующую ошибку — используют знак = (прпсваивание) вместо == (сравнение на равенство); Ц'(а=7) // не то! в условпп прпсвапваетсн константа Эта ошибка естественна, потому что во многих языках = означает «равно». Такие вещи компилятор также может легко обнаружить, и многие из них это делают. 6.2.4. Побитовые логические операторы Побитовые логические операторы б, ), ", —, » и «применяются к интегральным пн пам и перечислениям — то есть к Ьоо1, сйаг, вйог1, )п1, 1опд (возможно, с модификатором илв)0пес(). Для определения типа результата выпо.лняются обычные арпфмепи чегкие преобразования (В.6.3).
Типичным применением побитовых логических операторов ярляется реализация небольшого множества (вектора бит). В этом случае каждый бит целого без знака представляет элемент множества, а количество бит ограничивает количество его элементов. Бинарный оператор $ интерпретируется как пересечение, ) — как объединение, — крк симметричная разность и — — как дополнение. Для обозначения членов такого множества можно воспользоваться перечислением.
Приведем небольшой пример, заимствованный из реализации ов1геагп: епит )ов Ьаве говга1е( уооИЬИ=О, ео/ЬИ=О!,/а1161~=010, ЬадЬИ=0100 ); Можно устанавливать и проверять состояние следующим образом: всаге = лоос(Ь(1, //... яУ (е1аге с" ( Ьас(ЬЯа!1611 )) // проаяемн с потоком Глава 6. Выражения и инструкции 166 Дополнительные скобки необходимы, так как Р. имеет более высокий приоритет, чем ).
Функция, которая встретила конец файла, может сообщить об этом следующим образом: в1а1в ,'= ва1Ьсб Оператор ) используется для добавления к состоянию еоуЬ!1. Простое присвапвание в1а1е=ео7Ь!!очистило бы все остальные биты. Этп флаги состояния потока доступны и вне реализации потока.
Например, мы можем посмотреть, как отличаются сослояния двух потоков следующим образом: !п1 с1)Я'.= с!и гсЫа1в ()"саиг.гсЬ1а1е )); с7 и!в!иге() возвааи!аво~ сосгпояпие Определение различия состояний потоков не является типичной задачей. Для других подобных типов определение различий более существенно. Например, сравнение битового вектора, которьш представляет набор обрабатываемых прерываний, с другим вектором, представляющим прерывания, ожидающие обработки, является вполне тнпгпчным. Обратите внимание, что вся эта суета с би~ами производится в реализации !ов1геаш, а не в пользовательском интерфейсе. Удобства манипулирования битами может быть очень важным моментом, ио нз соображешш надежности, сопровождаемости, переносимости и т.
д. работу с бптамн лучше вести на нижних уровнях системы. За более подробным описанием понятия множества обратитесь к классам стандартной био.лиотекп ве1 (6 17.ч.3), Ь11ве1 (э 17.5.3) и иес1о> -Ьоо(> (() 16.3.11). Использование полей (() В.8.1) является удобной формой осуществления сдвигов н маскирования для извлечения битовых полей из слова. Это, конечно, можно сделать и с помощью побитовых логических операций.
Например, можно извлечь средние 16 бпт из 32-разрядного !апд следующим образом: ипв! упвс! зла г1 гпЫс!!е )!алуа) ( гв1игп (а»В) 1 Охи ) Не путайте побитовые логические операции с логическими операторами 68. ,') и!. Последние возвращают 1гие или уа!вв и в основном используются для сравнения в операторах (г; шй!1в плп /ог Я 6.3.2, ~ 6.3.3). Например, )О (не ноль) имеет значение 1гие, в то время как -0 (дополнение нуля) означает цепочку битов, состоящую из единиц, что в двоичном дополнительном представлении означает — 1. 6.2.5. Инкремент и декремент Опера гор ++ используется для явно~о указания операции увеличения вместо менее явной записи, состоящей из комбинации сложения и присваивания.
По определению, ьсЬа!ив означает Ьа!ив+= 1, что в свою очередь означает Ьа!ие = !иа!ие э- 1, при условии, что Ьа!ие не имеет побочных аффектов, Выражение, означающее объект, с которым производится ннкремент, вычисляется только один раз. Подобным же образом записывается оператор декремецта —. Операторы -'-ь и — могут быть как постфпкснымп, так и префикснымп. Значением ++х будет новое (увеличенное) значение х. Например, у=-ьх эквивалентно у=(х+=1). Напротив, значением х++ является старое значение х. Например, у=ха ь эквивалентно у(1=х, х+=1, 1), где тнп переменной 1 такой же, как у х. Также как при сложении и вычитании указателей, операторы ~-ь и — над указателямн работают в единицах разлсеров элементов, на которые ссылаются указатели; р++ приказывает р указать на следусощий элеи ент массива (э 5.3,1). 167 б 2.