Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Podbelsky_V_V_C_Bazovy_kurs.pdf
Скачиваний:
69
Добавлен:
02.06.2015
Размер:
1.73 Mб
Скачать

38

Г л а в а 3

 

 

{

static void Main()

{

Console.WriteLine("13 % 4 = " + 13 % 4);

Console.WriteLine("-13 % 4 = " + -13 % 4);

Console.WriteLine("13 % -4 = " + 13 % -4);

Console.WriteLine("-13 % -4 = " + -13 % -4); Console.WriteLine("-13/-4*-4 + -13%-4 = " +(-13 / -4 *

-4 + -13 % -4));

}

}

Результат выполнения программы:

13 % 4 = 1 -13 % 4 = -1 13 % -4 = 1 -13 % -4 = -1

-13/-4*-4 + -13%-4 = -13

3.5. Поразрядные операции

От языков Си и Си++ язык C# унаследовал операции для работы с битовыми представлениями целых чисел:

~ - поразрядное инвертирование (поразрядное НЕ); &- поразрядная конъюнкция (поразрядное И); | - поразрядная дизъюнкция (поразрядное ИЛИ); ^ - поразрядное исключающее ИЛИ; >> - поразрядный сдвиг вправо; << - поразрядный сдвиг влево.

 

 

 

 

 

Таблица 3 . 1

 

Правила выполнения поразрядных операций

 

 

 

 

 

Значения операндов

Результаты выполнения операции

первого

 

второго

&

|

^

0

 

0

0

0

 

0

0

 

1

0

1

 

1

1

 

0

0

1

 

1

1

 

1

1

1

 

0

Операции и целочисленные выражения

39

 

 

Для иллюстрации выполнения поразрядных операций удобно использовать операнды беззнакового байтового типа (byte). Рассмотрим вначале поразрядную унарную операцию инвертирования (~). Операция применяется к каждому разряду (биту) внутреннего представления целочисленного операнда. Предположим, что десятичное значение переменной bb беззнакового байтового типа равно 3. Внутреннее представление bb имеет вид 00000011. После выполнения операции ~ битовым представлением результата (т.е. выражения ~bb), станет 11111100, т. е. 252 при записи с использованием десятичного основания. Стоит отметить, что для любого bb значением выражения bb+~ bb всегда будет 255, т.е. byte.MAX_VALUE.

Операция &. Определим две байтовых переменных bb со значением 3 и dd со значением 6:

byte bb=3, dd=6;

Поразрядные (битовые) представления: 00000011 и 00000110. Значением выражения bb&dd будет десятичное 2, имеющее

внутреннее представление 00000010.

Применив к переменным bb и dd бинарную операцию поразрядной дизъюнкции |, получим десятичное значение 7 с поразрядным представлением 00000111.

Применив бинарную операцию ^ исключающего ИЛИ к переменным bb и dd, получим десятичное значение 5 с битовым представлением 00000101.

Следующая программа содержит описанные выше выражения с поразрядными операциями над переменными типа byte.

//03_03.cs поразрядные операции с беззнаковыми переменными

using System; class Program

{

static void Main()

{

byte bb = 3;

Console.WriteLine("bb = " + bb + "; ~bb = " + (byte) (~bb));

byte dd = 6;

40

Г л а в а 3

 

 

Console.WriteLine("bb & dd = " + (byte)(bb & dd)); Console.WriteLine("bb | dd = " + (byte)(bb | dd)); Console.WriteLine("bb ^ dd = " + (byte)(bb ^ dd));

}

}

Поясним еще раз особенности обращений к методу WriteLine(). Аргумент метода должен быть строкового типа (типа string). Выражение вида

строка + арифметическое выражение

обрабатывается так: вычисляется арифметическое выражение, его значение автоматически преобразуется в строку, которая «присоединяется» к строке, размещенной слева от знака +. Чтобы значение арифметического выражения при преобразованиях «не потеряло» свой беззнаковый тип, явно используется операция приведения типов (byte). Необходимость указанного приведения типов будет обоснована в § 3.7. Более подробно приведение типов рассмотрено в следующей главе.

Результаты выполнения программы:

bb = 3; ~bb = 252 bb & dd = 2

bb | dd = 7 bb ^ dd = 5

Бинарные поразрядные операции сдвига (>> и <<) поразному используют значения своих операндов. Левый операнд задает целочисленное значение, к битовому представлению которого применяется сдвиг. Правый операнд указывает количество разрядов (битов), на которое должны сдвигаться все биты внутреннего представления левого операнда. Направление сдвига зависит от операции: << обозначает сдвиг влево, >> обеспечивает сдвиг вправо.

При сдвиге влево << все разряды, выходящие за левый край внутреннего представления значения левого операнда, отбрасываются, все “освободившиеся” справа позиции заполняются нулями. Таким образом, сдвинув влево на 3 позиции число 4 с двоичным представлением 00000100, получим 00100000 (десятичное 32). Сдвиг влево числа 00010000 (десятичное 16) на 4 позиции приводит к нулевому значению. Обратим внимание,

Операции и целочисленные выражения

41

 

 

что сдвиг влево на k позиций эквивалентен умножению левого операнда на 2k (при условии, что значащие левые ненулевые разряды не выйдут за разрядную сетку.) Значением выражения 6<<3 будет десятичное число 48, т. е. 6*23, или 00110000.

При сдвиге вправо >> разряды, выходящие за правый край представления значения левого операнда, отбрасываются. Слева «освободившиеся» позиции заполняются нулями. Таким образом, число 25 с двоичным представлением 00011001 при сдвиге на 2 позиции вправо приводит к получению кода 00000110 со значением 6. Сдвиг вправо на k позиций эквивалентен умножению на 2-k с округлением результата до целого значения. Выражение 6>>2 будет равно 1, т. е. 00000001.

Следующая программа иллюстрирует сказанное относительно поразрядных сдвигов:

//03_04.cs операции сдвигов для беззнаковых целых using System;

class Program

{

static void Main()

{

byte bb = 4;

Console.WriteLine("bb = " + bb + "; bb << 3 = " + (byte)(bb << 3));

bb = 16;

Console.WriteLine("bb = " + bb + "; bb << 4 = " + (byte)(bb << 4));

bb = 6;

Console.WriteLine("bb = " + bb + "; bb << 3 = " + (byte)(bb << 3));

bb = 25;

Console.WriteLine("bb = " + bb + "; bb >> 2 = " + (byte)(bb >> 2));

bb = 6;

Console.WriteLine("bb = " + bb + "; bb >> 2 = " + (byte)(bb >> 2));

}

}

Результат выполнения программы:

bb = 4; bb << 3 = 32 bb = 16; bb << 4 = 0

42

Г л а в а 3

 

 

bb = 6; bb << 3 = 48 bb = 25; bb >> 2 = 6

bb= 6; bb >> 2 = 1

3.6.Переполнения при операциях

сцелыми

Рассматривая поразрядные операции, мы ограничились операндами беззнакового типа byte, так как использование знаковых типов требует знакомства с правилами кодирования отрицательных целых чисел. Переменные и константы типа byte могут иметь значения от 0 до 255. Соответствующие двоичные коды - 00000000 (все нули) и 11111111 (все единицы). В то же время для знакового типа sbyte установлены пределы от -128 до +127.

Это связано с принятым на аппаратном уровне правилом кодирования знаковых целых чисел. Для их внутреннего представления используется дополнительный код. Если k – количество разрядов, отведенное для представления числа x (для sbyte k равно 8), то дополнительный код определяет выражение:

В битовом представлении чисел с использованием дополнительного кода у всех положительных чисел самый левый бит равен 0, а у отрицательных – единице.

Минимальное число типа sbyte равно -128. Его двоичный код 10000000. Число -1 представлено кодом 11111111. Представление нуля 00000000, код единицы 00000001.

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

Операции и целочисленные выражения

43

 

 

//03_05.cs поразрядное инвертирование знаковых //чисел

using System; class Program

{

static void Main()

{

sbyte sb = 9; sbyte nb = 3;

Console.WriteLine("~sb = " + ~sb); Console.WriteLine("~sb+sb = " + (~sb+sb)); Console.WriteLine("~nb = " + ~nb); Console.WriteLine("~nb+nb = " + (~nb + nb));

}

}

Результат выполнения программы:

~sb = -10 ~sb+sb = -1 ~nb = -4 ~nb+nb = -1

Поразрядный сдвиг влево << целочисленного аргумента знакового типа может не только изменить его абсолютное значение, но и зачастую изменяет его знак. Приводить примеры здесь нет необходимости. Гораздо важнее рассмотреть особенности выполнения традиционных арифметических операций над беззнаковыми и знаковыми операндами с ограниченным количеством разрядов.

Начнем с беззнаковых целочисленных типов. В результате выполнения следующего фрагмента программы:

byte b=255, c=1, d; d=(byte)(b+c);

Значением переменной d будет 0. Обоснованность такого результата иллюстрирует следующее двоичное представление:

11111111 = 255

+

00000001 = 1

100000000 = 0 (нуль за счет отбрасывания левого разряда) Теперь обратимся к операндам знаковых типов, например,

типа sbyte.

44

Г л а в а 3

 

 

Если просуммировать числа -1 (с поразрядным представлением 11111111) и 1 (с кодом 00000001), то получим девятиразрядное число с битовым представлением 100000000. Для внутреннего представления чисел типа sbyte отводится 8 разрядов. Девятиразрядное число в эти рамки не помещается, и левая (старшая) единица отбрасывается. Тем самым результатом суммирования становится код нуля 00000000. Все совершенно верно – выражение (-1+1) должно быть равно нулю! Однако, так правильно завершаются вычисления не при всех значениях целочисленных операндов.

За счет ограниченной разрядности внутреннего представления значений целых типов при вычислении выражений с целочисленными операндами существует опасность аварийного выхода результата за пределы разрядной сетки. Например, после выполнения следующего фрагмента программы:

sbyte x=127, y=127, z; z=(sbyte) (x+y);

Значением переменной z будет – 2

В этом легко убедиться, представив выполнение операции суммирования в двоичном виде:

01111111 = 127

+

01111111 = 127

11111110 = - 2 (в дополнительном коде).

Примечание. В операторе z=(sbyte)(x+y); использована операция приведения типов (sbyte). При её отсутствии результат суммирования x+y автоматически приводится к типу int. Попытка присвоить значение типа int переменной z, имеющей тип sbyte, воспринимается как ошибка, и

компиляция завершается аварийно.

Приведенные иллюстрации переполнений разрядной сетки при арифметических операциях с восьмиразрядными целыми (типов byte, sbyte) могут быть распространены и на целые типы с большим количеством разрядов (эти типы с указанием разрядностей приведены в табл. 2.1).

Основным типом для представления целочисленных данных в C# является тип int. Для представления целочисленных значений типа int используются 32-разрядные участки памяти.

Операции и целочисленные выражения

45

 

 

Тем самым предельные значения для значения типа int таковы: положительные от 0 до 231-1; отрицательные от -1 до -231.

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

// 03_06.cs переполнение при целочисленных //операндах

using System; class Program

{

static void Main()

{

int m = 1001; Console.WriteLine("m = " + m);

Console.WriteLine("m = " + (m = m * m)); Console.WriteLine("m = " + (m = m * m)); Console.WriteLine("m = " + (m = m * m));

}

}

В программе значение целочисленной переменной вначале равной 1001 последовательно умножается само на себя.

Результат выполнения программы:

m = 1001

m = 1002001

m = -1016343263 m = 554036801

После первого умножения m*m значением переменной m становится 100200110, после второго результат выходит за разрядную сетку из 32-х бит. Левые лишние разряды отбрасываются, однако, оставшийся самый левый 32-й бит оказывается равным 1, и код воспринимается как представление отрицательного числа. После следующего умножения 32-й бит оказывается равным 0, и арифметически неверный результат воспринимается как код положительного числа.

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

В рассмотренных программах с переменными типов byte и sbyte мы несколько раз применили операцию преобразования

46

Г л а в а 3

 

 

(иначе приведения) типов. Например, были использованы конструкции:

(byte)(bb&dd) z=(sbyte)(x+y);

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

Поместим в программу операторы:

short dd=15, nn=24; dd=(dd+nn)/dd;

При компиляции программы будет выведено сообщение об ошибке:

Cannot implicitly convert type ‘int’ to ‘short’.

Невозможно неявное преобразование типа int в short. Несмотря на то, что в операторах использованы переменные

только одного типа short, в сообщении компилятора указано, что появилось значение типа int! Компилятор не ошибся – при вычислении выражений с целочисленными операндами, отличными от типа long, они автоматически приводятся к типу int. Поэтому результат вычисления (dd+nn)/dd имеет тип int. Для значений типа short (см. табл. 2.1) выделяются два байта (16 разрядов), значение типа int занимает 4 байта. Попытка присвоить переменной dd с типом short значения типа int воспринимается компилятором как потенциальный источник ошибки за счет потери 16 старших разрядов числа. Именно поэтому выдано сообщение об ошибке.

Программист может «успокоить» компилятор, применив следующим образом операцию приведения типов:

dd=(short)((dd+nn)/dd);

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

Обратите внимание на необходимость дополнительных скобок. Если записать (short)(dd+nn)/dd, то в соответствии с рангами операций к типу short будет приведено значение (dd+nn), а результат его деления на dd получит тип int.

Операции и целочисленные выражения

47

 

 

Контрольные вопросы

1.Перечислите первичные операции языка C#.

2.Перечислите названия групп операций в порядке возрастания их приоритетов (рангов).

3.Знаки каких бинарных операций могут использоваться в составных операциях присваивания?

4.В чём отличия префиксных форм операций декремента и инкремента от постфиксных?

5.К каким операндам применимы операции ++ и --?

6.В чём особенность операции деления целочисленных операндов?

7.Назовите правила выполнения операций %.

8.Какому действию эквивалентен сдвиг влево разрядов битового представления целого числа?

9.Получите дополнительный код отрицательного числа типа sbyte, модуль которого не превышает 127.

10.Объясните механизм возникновения переполнения при вычислениях с целочисленными операндами.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]