Оказывается, поразрядный оператор исключающего ИЛИ (^) обладает волшебными свойствами. Листинг 2.6 демонстрирует работу этого оператора. Как и раньше, скомпилируйте и запустите эту программу, По приглашению введите два числа, разделенных одним пробелом,
Листинг 2.6. TXOR.C (поразрядный оператор исключающего ИЛИ)
___________________________________________________________
1: #include <stdio.h>
2: #include "pbin.c"
3:
4: main()
5: {
6: unsigned v1, v2, v3;
8: printf(“Enter values to OR exclusively: (ex: 1234 15) ");
9: scanf("%u %u", &v1, &v2);
10: v3 = v1 ^ v2;
11: printf(" %5u %#06x ", v1, v1); pbin(v1);
12: printf(“XOR %5u %# 6x, v2, v2); pbin(v2);
13: prlntf("====================================\n");
14: printf(" %5u %#06x ", v3, v3); pbin(v3);
15: return 0;
16 }
_______________________________________________________
В табл. 2.6 отображены результаты применения оператора поразрядного исключающего ИЛИ к каждой возможной комбинации двух однобитовых операндов. Если два операнда равны, результат будет равен нулю; если отличаются друг от друга - единице. Если посмотреть на исключающее ИЛИ под другим углом зрения, то можно сказать, что наличие единицы в одном операнде переключает соответствующий разряд другого операнда с 1 на 0 или с 0 на 1.
Таблица 2.6. Поразрядная операция исключающего ИЛИ
А | ^ | В | == | С |
^ | == | |||
^ | == | |||
^ | == | |||
^ | == |
С помощью простого эксперимента можно продемонстрировать важное свойство оператора поразрядного исключающего ИЛИ. Запустите программу TXOR и введите числа 45000 и -1 (разделенные одним пробелом). Программа отобразит следующее:
45000 0xafc8 1010111111001000
XOR 65535 0xffff 1111111111111111
=================================
20535 0х5037 0101000000110111
Число -1 в шестнадцатеричном представлении будет выглядеть как 0xffff, т.е. как 16 -битовое беззнаковое значение, у которого все разряды равны 1. Если при выполнении исключающего ИЛИ это число взять в качестве маски, то с операндом, равным 45000, в результате получим 20535. В двоичном представлении все нули операнда превратятся в единицы. Поскольку все биты второго операнда, или маски, равны 1, операция поразрядного исключающего ИЛИ эффективно переключает первоначальные значения всех разрядов первого операнда на противоположные. Теперь будет интересно повторить эксперимент с тойже самой маской, новзяв в качестве первого операнда результат предыдущего теста. Запустим снова TXOR и введем числа 2053 и -1 (разделенные одним пробелом). На этот раз программа отобразит следующее:
20535 0х5037 0101000000110111
XOR 65535 0xffff 1111111111111111
====================================
45000 0xafc8 1010111111001000
Применение маски, равной -1, к результату предыдущего эксперимента восстановило первоначальный операнд 45000. Этот факт справедлив для единичных разрядов любой маски, а не только равной -1. Выполнение операторов
С = А ^ В;
D = С ^ В;
установит переменную D равной первоначальному значению А.
Это свойство часто приносит пользу в мультипликации, гдe образы хранятся в памяти в виде битовых шаблонов (называемых битовыми образами). Отображение образа с помощью операции исключающего ИЛИ над его разрядами с битовым образом фона и использование аналогичной операции с теми же разрядами снова восстановит первоначальный образ. Это дает визуальный эффект независимого движения одного образа поверх другого.
Рис. 2.3. Результат операции исключающего ИЛИ над операндом и маской
Рис. 2.3 иллюстрирует результат применения маски к значению операнда при использовании оператора исключающего ИЛИ. Разряды маски, равные 1, переключают значения битов операнда на противоположные. Нулевые биты маски позволяют соответствующим битам операнда перейти в результат без изменений.
Сдвиг битов влево
Используйте оператор сдвига влево (<<) для выполнения сдвига битов влево на нуль или другое число позиций. Оператор
С = А << 3;
присваивает переменной С значение переменной А, сдвинутое на три бита влево.
Листинг 2.7 отображает результат поразрядного сдвига влево, примененного к двум 16 -битовым беззнаковым значениям. Скомпилируйте и запустите эту программу, затем введите два числа - 89 и 3 (разделенные пробелом), и вы увидите результат сдвига числа 89 на 3 бита влево.
Листинг 2.7. TLEFT.C (поразрядный сдвиг влево)
1: #include <stdio.h>
2: #include "pbin.c"
3:
4: main()
5: {
6: unsigned v1, v2, v3;
7:
8: printf("Enter values to SHIFT LEFT: (ex: 1234 3) ");
9: scanf("%u %u", &v1, &v2);
10: v3 = v1 << v2;
11: printf(" %5u %#06x ", v1, v1); pbin(v1);
12: printf("<< %5u %#06x ", v2, v2); pbin(v2);
13: printf("===========================^========\n");
14: printf(" %5u %#06x ", v3, v3); pbin(v3);
15: return 0;
16: }
______________________________________________
В двоичном представлении сдвиг битов на одну позицию влево эквивалентен умножению исходного значения на 2, т.е. на основание двоичной системы счисления. То же самое справедливо и для значений, записанных в других системах счисления. Например, сдвиг цифр в десятичном числе 1234 влево на одну позицию и внесение нуля справа даст в результате число 12340, которое в 10 раз больше исходного числа по основанию 10. Но в языке С все сдвиги выполняются в двоичном формате.
Поскольку двоичные компьютеры выполняют сдвиг влево очень быстро, то операции поразрядного сдвига влево являются быстрым способом умножения чисел на степень числа два. Убедитесь в этом, используя программу TLEFT. Запустите программу, введите через пробел числа 4 и 1, и вы должны увидеть следующее:
4 0х0004 0000000000000100
<< 1 0х0001 0000000000000001
8 0х0008 0000000000001000
Сдвиг числа 4 влево на один разряд дает в результате 8, что, конечно же, равно произведению 4 на 2.
Рис.2.4 иллюстрирует действие оператора С = А << В;, где А равно двоичному значению 01011001, а В равно 3. Как видно по результату (С), освобождающиеся справа позиции заполняются нулями, в то время как двигаемые влево старшие биты теряются (что иллюстрируется на рисунке символом электрического заземления), а сами разряды "исчезают в земле" подобно электронам.
Рис. 2.4. Действие оператора поразрядного сдвига влево
Сдвиг битов вправо
Как вы могли догадаться, оператор поразрядного сдвига вправо работает аналогично оператору сдвига влево, но сдвигает биты в другом направлении. Оператор
С = А >> 3;
присваивает переменной С значение переменной А, сдвинутое на три бита вправо.
Листинг 2.8 отображает результат оператора поразрядного сдвига вправо, примененного к двум 16 -битовым беззнаковым значениям. Скомпилируйте и запустите эту программу, затем введите два числа 89 и 3 (разделенные пробелом), и вы увидите результат сдвига числа - 89 на 3 бита вправо.
Листинг 2.8. TRIGHT.C (поразрядный сдвиг вправо)
______________________________________________
1: #include <stdio.h>
2: #include "pbin.c"
3:
4: main()
5: {
6: unsigned v1, v2, v3;
7:
8: printf("Enter values to SHIFT RIGHT: (ex: 1234 3) ");
9: scanf("%u %u", &v1, &v2);
10: v3 = v1 >> v2;
11: printf(" %5u %#06x ", v1, v1); pbin(v1);
12: printf(">> %5u %#06x ", v2, v2); pbin(v2);
13: printf("====================================\n");
14: printf(" %5u %#06x ",v3, v3); pbin(v3);
15: return 0;
16: }
_______________________________________________________
Если операции сдвига влево умножают числа на 2, то операции сдвига вправо выполняют деление чисел. Аналогичное утверждение справедливо для основания любой системы счисления. Сдвиг числа 12340 вправо на одну цифру и отбрасывание нуля даст число 1234, которое мы получили бы при делении исходного числа на основание 10. Но в языке С все сдвиги выполняются в двоичном формате.
Чтобы увидеть, как сдвиг двоичных чисел вправо выполняет деление на 2, запустите программу TRIGHT, введите 89 и 3 и рассмотрите результат работы программы:
89 0х0059 0000000001011001
>> 3 0х0003 0000000000000011
==========================
X000b 0000000000001011
По законам целочисленной арифметики деление числа 89 на 2 в степени 3 (т.е. 8) даст в результате 11.
Рис. 2.5 иллюстрирует действие оператора
С = А >> В;
для тех же значений, которые использовались в примере на рис. 2.4.
Рис. 2.5. Действие оператора поразрядного сдвига вправо
Дополнение до единицы
Оператор поразрядного дополнения до единицы, представленный символом ~ (в математике тильда - символ разности), переключает все биты значения с 1 на 0 и с 0 на 1. Дополнение до единицы является унарным оператором, который предшествует своему операнду подобно унарному плюсу или минусу (как в выражениях -34 или +value).
Используйте унарную операцию дополнения до единицы для выполнения поразрядного отрицания двоичных значений. Например, выражение
А = ~ В
присваивает переменной А значение дополнения или отрицания переменной В.
Листинг 2.9 демонстрирует оператор поразрядного дополнения до единицы. Введите и запустите программу, затем введите одно число, чтобы получить значение его дополнения.
Листинг 2.9. ТСОМР.С (поразрядное дополнение до единицы)
_____________________________________________________
1: #include <stdio.h>
2: #include "pbin.c"
3:
4: main()
5: {
6: unsigned int v1, v2;
7:
8: printf("Enter value to complement: ");
9: scanf("%u", &v1);
10: v2 = ~ v1;
11: printf(" %5d %#06x ", v1, v1); pbin(v1);
12: printf(“====================================\n");
13: printf("COMP %5d %#06x ", v2, v2); pbin(v2);
14: return 0;
15: }
_______________________________________________________
Дополнение до единицы - это обычная операция низкого уровня, которую большинство компьютерных процессоров выполняет в мгновение ока. Являясь двоичной логической операцией, дополнение до единицы имеет два назначения: инвертирование значений "истина" и "ложь", а также преобразование отрицательных значений в положительные и наоборот.
Если 0 представляет "ложь", то -0 является "истиной", так как отрицание нуля дает -1 (беззнаковое число 65535), а любое ненулевое число означает "истину". (Чтобы убедиться в этом, запустите ТСОМР и введите 0.) Обратное утверждение также справедливо: отрицание -1 равно 0. Однако отрицание не каждого ненулевого числа дает 0.
Кроме этого, операция дополнения до единицы играет роль в преобразовании отрицательных и положительных целых. Прибавление единицы к результату выражения дополнения до единицы формирует другое значение, называемое дополнением до двух. Посмотрите на следующий результат работы программы:
-49 0xffd3 1111111111010011
=========================
СОМР 44 0х002с 0000000000101100
Очевидно, что 44+1 равно 45 - абсолютному значению числа -45 (беззнаковый эквивалент). Затем введите число 45 и получите другой результат:
X002d 0000000000101101
=================================
СОМР -46 0xffd2 1111111111010010
Прибавление 1 к числу -46 дает первоначальное значение –45.
Один плюс дополнение до единицы любого знакового целого v равно тому же самому значению с противоположным знаком.
Рис. 6.6 иллюстрирует действие оператора дополнения до единицы, примененного к двоичному значению 00101100. Сравните биты 2-5 на этом рисунке с примером исключающего ИЛИ на рис. 2.3. Выполнение исключающего ИЛИ над некоторым битом и единичным битом переключает значение первого бита (то же самое делает операция дополнения до единицы); эту важную связь следует запомнить. Например, операция исключающего ИЛИ с операндами 00101100 и 11111111 даст тот же результат, что и выражение - 00101100.
Операторы присваивания
Вы уже познакомились с оператором присваивания, а теперь, так как вы такие хорошие друзья, пора взглянуть поближе на важный символ, представленный одним знаком равенства (=). Рассмотрим следующий простой оператор, который присваивает значение В переменной А:
А = В; /* Присвоить значение В переменной А */
В результате выполнения этого оператора значение В не изменяется, а прежнее значение А исчезает, как туман в лучах солнца.
Строго говоря, А называется l - значением (lvalue), а В - г -значением (rvalue). В общем случае l - значение - это любое выражение, которое может стоять слева от знака равенства. В свою очередь г - значение — это любое выражение, которое может стоять справа от знака равенства. l - значение (в нашем случае А) должно ссылаться на объект, который может принимать значения, - обычно это ссылка на некоторую область памяти; г - значение представляет собой любое выражение, имеющее значение, но оно не обязано хранить его — это может быть и ссылка и литеральная константа. Выражение
А = 1234
вполне законно при условии, что А может хранить числовые значения. Выражение
1234 = А; /*??? */
не имеет смысла (и не будет компилироваться), потому что литеральная константа (1234) не является l - значением.
Выражения значений
Выражения бывают разные - от очень простых до невероятно сложных. Простейшее выражение представляет собой значение. Например, переменная А типа int является выражением, которое представляет значение переменной А. В действительности, все выражения имеют значения. Выражение (А + В) равно значению суммы слагаемых А и В.
Несмотря на то, что следующее утверждение может показаться странным, но, тем не менее, присваивания также являются выражениями и, подобно всем выражениям, имеют значения. Как вы уже знаете, выражение (А = В) присваивает значение В переменной А. Однако как выражение (А = В) также имеет значение, равное присваиваемому результату. Следовательно, выражение
С = (А = В)
полностью допустимо. Сначала заключенное в круглые скобки подвыражение (А = В) присваивает значение В переменной А. Затем значение этого подвыражения (равное А) присваивается переменной С. Таким образом, как С, так и А сейчас равны В. Можно даже обойтись без круглых скобок. Выражение
С = А = В
присваивает значение В переменным А и С.
Принимая это во внимание, возьмите на вооружение один из способов инициализации многих переменных. Выражение
С = А = В = 451
присваивает число 451 переменным А, В и С.