Язык программирования C Пятое издание
Скачать 1.85 Mb.
|
используемый для отображения элементов вектора, задействует префиксный оператор инкремента? : (grade < 60) ? "fail" : "pass"; Первое условие проверяет, не выше ли оценка 90. Если это так, то выполняется выражение после ?, возвращающее литерал "high pass". Если условие ложно, выполняется ветвь :, которая сама является другим условным выражением. Это условное выражение проверяет, не меньше ли оценка 60. Если это так, то обрабатывается ветвь ?, возвращающая литерал "fail". В противном случае ветвь : возвращает литерал "pass". Условный оператор имеет правосторонний порядок, т.е. его операнды группируются (как обычно) справа налево. Порядок объясняет тот факт, что правое условное выражение, сравнивающее grade со значением 60, образует ветвь : левого условного выражения. Вложенные условные выражения быстро становятся нечитабельными, поэтому нежелательно создавать больше двух или трех вложений. Применение условного оператора в выражении вывода Условный оператор имеет довольно низкий приоритет. Когда условный оператор используется в большом выражении, его, как правило, следует заключать в круглые скобки. Например, условный оператор нередко используют для отображения одного из значений в зависимости от результата проверки условия. Отсутствие круглых скобок вокруг условного оператора в выражении вывода может привести к неожиданным результатам: cout << ((grade < 60) ? "fail" : "pass"); // выводит pass или fail cout << (grade < 60) ? "fail" : "pass"; // выводит 1 или 0! cout << grade < 60 ? "fail" : "pass"; // ошибка: сравнивает cout с 60 Второе выражение использует сравнение grade и 60 как операнд оператора <<. В зависимости от истинности или ложности выражения grade < 60 выводится значение 1 или 0. Оператор << возвращает объект cout, который и проверяется в условии условного оператора. Таким образом, второе выражение эквивалентно следующему: cout << (grade < 60); // выводит 1 или 0 cout ? "fail" : "pass"; // проверяет cout, а затем возвращает один из // этих двух литералов в зависимости от // истинности объекта cout Page 196/1103 Если операнд знаковый и имеет отрицательное значение, то способ обработки "знакового разряда" большинства битовых операций зависит от конкретной машины. Кроме того, результат сдвига влево, изменяющего знаковый разряд, непредсказуем. Поскольку нет никаких гарантий однозначного выполнения побитовых операторов со знаковыми переменными на разных машинах, настоятельно рекомендуется использовать в них только беззнаковые целочисленные значения. Побитовые операторы сдвига Мы уже использовали перегруженные версии операторов >> и <<, которые библиотека IO определяет для ввода и вывода. Однако первоначальное значение этих операторов — побитовый сдвиг операндов. Они возвращают значение, являющееся копией (возможно преобразованной) левого операнда, биты которого сдвинуты. Правый операнд не должен быть отрицательным, и его значение должно быть меньше количества битов результата. В противном случае операция имеет неопределенный результат. Биты сдвигаются влево (<<) или право (>>), при этом вышедшие за пределы биты отбрасываются. Оператор сдвига влево (<<) (left-shift operator) добавляет нулевые биты справа. Поведение оператора сдвига вправо (>>) (right-shift operator) зависит от типа левого операнда: если он беззнаковый, то оператор добавляет слева нулевые биты; если он знаковый, то результат зависит от конкретной реализации: слева вставляются либо копии знакового разряда, либо нули. В этих примерах подразумевается, что младший бит расположен справа, тип char содержит 8 битов, а тип int — 32 бита // 0233 - восьмеричный литерал (см. раздел 2.1.3) unsigned char bits = 0233; 1 0 0 1 1 0 1 1 bits << 8 // bits преобразуется в int и сдвигается влево на 8 битов 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0 Page 198/1103 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 bits >> 3 // сдвиг вправо на 3 бита отбрасывает 3 крайних правых бита 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 1 Побитовый оператор NOT Побитовый оператор NOT () (bitwise NOT operator) создает новое значение с инвертированными битами своего операнда. Каждый бит, содержащий 1, превращается в 0; каждый бит, содержащий 0, — в 1. unsigned char bits = 0227; 1 0 0 1 0 1 1 1 bits 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 1 0 0 0 Здесь операнд типа char сначала преобразуется в тип int. Это оставляет значение неизменным, но добавляет нулевые биты в позиции старших разрядов. Таким образом, преобразование в тип int добавляет 24 бита старших разрядов, заполненных нулями. Биты преобразованного значения инвертируются. Побитовые операторы AND, OR и XOR Побитовые операторы AND (&), OR (|) и XOR (^) создают новые значения с битовым шаблоном, состоящим из двух их операндов. unsigned char b1 = 0145; 0 1 1 0 0 1 0 1 unsigned char b2 = 0257; 1 0 1 0 1 1 1 1 b1 & b2 Все 24 старших бита 0 0 0 1 0 0 1 0 1 b1 | b2 Все 24 старших бита 0 1 1 1 0 1 1 1 1 b1 ^ b2 Все 24 старших бита 0 1 1 0 0 1 0 1 0 Каждая битовая позиция результата побитового оператора AND (&) содержит 1, если оба операнда содержат 1 в этой позиции; в противном случае результат — 0. У побитового оператора OR (|) бит содержит 1, если один или оба операнда содержат 1; в противном случае результат — 0. Для побитового оператора XOR (^) бит содержит 1, если любой, но не оба операнда содержат 1; в противном случае результат — 0. Побитовые и логические (см. раздел 4.3) операторы нередко путают. Например, путают побитовый оператор & с логическим &&, побитовый | с логическим || и побитовый с логическим !. Использование побитовых операторов Рассмотрим пример использования побитовых операторов. Предположим, что есть класс с 30 учениками. Каждую неделю класс отвечает на контрольные вопросы с оценкой "сдано/не сдано". Результаты всех контрольных записываются, по одному биту на ученика, чтобы Page 199/1103 // как коллекция битов Переменная quiz1 определена как unsigned long. Таким образом, на любой машине она будет содержать по крайней мере 32 бита. Переменная quiz1 инициализируется явно, чтобы ее значение было определено изначально. Учитель должен быть способен устанавливать и проверять отдельные биты. Например, должна быть возможность установить бит, соответствующий ученику номер 27, означающий, что этот ученик сдал контрольную. Чтобы указать, что ученик 27 прошел контрольную, создадим значение, у которого установлен только бит номер 27. Если затем применить побитовый оператор OR к этому значению и значению переменной quiz1, то все биты, кроме бита 27, останутся неизменными. В данном примере счет битов переменной quiz1 начинается с 0, соответствующего младшему биту, 1 соответствует следующему биту и т.д. Чтобы получить значение, означающее, что ученик 27 сдал контрольную, используется оператор сдвига влево и целочисленный литерал 1 типа unsigned long (см. раздел 2.1.3). 1UL << 27 // создает значение только с одним установленным битом // в позиции 27 Первоначально переменная 1UL имеет 1 в самом младшем бите и по крайней мере 31 нулевой бит. Она определена как unsigned long, поскольку тип int гарантированно имеет только 16 битов, а необходимо по крайней мере 27. Это выражение сдвигает 1 на 27 битовых позиций, вставляя в биты позади 0. К этому значению и значению переменной quiz1 применяется оператор OR. Поскольку необходимо изменить значение самой переменной quiz1, используем составной оператор присвоения (см. раздел 4.4): quiz1 |= 1UL << 27; // указать, что ученик номер 27 сдал контрольную Оператор |= выполняется аналогично оператору +=. quiz1 = quiz1 | 1UL << 27; // эквивалент quiz1 |= 1UL << 21; Предположим, что учитель пересмотрел контрольные и обнаружил, что ученик 27 фактически списал работу. Теперь учитель должен сбросить бит 27 в 0. На сей раз необходимо целое число, бит 27 которого сброшен, а все остальные установлены в 1. Применение побитового Page 200/1103 // т.е. sizeof(Sales_data) sizeof data.revenue; // размер типа члена revenue класса Sales_data sizeof Sales_data::revenue; // альтернативный способ получения // размера revenue Наиболее интересен пример sizeof *p. Во-первых, поскольку оператор sizeof имеет правосторонний порядок и тот же приоритет, что и оператор *, это выражение группируется справа налево. Таким образом, оно эквивалентно выражению sizeof(*p). Во-вторых, поскольку оператор sizeof не выполняет свой операнд, не имеет значения, допустим ли указатель p (т.е. инициализирован ли он) (см. раздел 2.3.2). Обращения к значению недопустимого указателя оператор sizeof не осуществляет, и указатель фактически не используется, поэтому он безопасен. Ему и не нужно обращаться к значению указателя, чтобы выяснить, какой тип он возвратит. По новому стандарту для доступа к члену класса при получении его размера можно использовать оператор области видимости. Обычно к членам класса можно обратиться только через объект этого класса. Больше не обязательно предоставлять объект, так как оператор sizeof не обязан выбирать член класса, чтобы узнать его размер. Результат применения оператора sizeof частично зависит от типа, к которому он применен. • Если это тип char или выражения, результат которого имеет тип char, то это гарантированно будет 1. • Если это ссылка, то возвращает размер типа объекта, на который она ссылается. • Если это указатель, то возвращается размер, необходимый для хранения указателя. • Если это обращение к значению указателя, то возвращается размер типа объекта, на который он указывает, вне зависимости от его допустимости. • Если это массив, то возвращается размер всего массива. Это эквивалентно получению размера элемента массива и его умножению на количество элементов. Обратите внимание, что оператор sizeof не преобразует массив в указатель. • Если это строка или вектор, то возвращается размер только фиксированной части этих типов; но не размер, используемый элементами объекта. Поскольку оператор sizeof возвращает размер всего массива, разделив размер массива на размер элемента, можно определить количество элементов в массиве: // sizeof(ia)/sizeof(*ia) возвращает количество элементов в ia Page 203/1103 // (p. 2.4.4) Так как оператор sizeof возвращает константное выражение, его результат можно использовать в выражении для определения размерности массива. Упражнения раздела 4.9 Упражнение 4.28. Напишите программу для вывода размера каждого из встроенных типов. Упражнение 4.29. Предскажите вывод следующего кода и объясните свое рассуждение. |