Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Объектно-ориентированное программирование.-6

.pdf
Скачиваний:
6
Добавлен:
05.02.2023
Размер:
4.5 Mб
Скачать

Выбора

?: ??

 

 

Создания объектов

new stackalloc

 

 

Рефлективные

sizeof is typeof

 

 

Управления переполнением

checked unchecked

 

 

Работа с указателями

* -> & []

 

 

Лямбда-выражение

=>

 

 

Данное деление на категории несколько условно. Например, арифметический оператор «+» также применяется для конкатенации строк. Оператор «()» используется не только для преобразования типов, но и для вызова методов, а также как обычные скобки в выражении, указывающие порядок выполнения операций. А оператор «–>» можно отнести как к операторам работы с указателями, так и к операторам доступа. И т.д.

3.3.2. Приоритет и порядок выполнения

Когда в одном выражении несколько операторов, компилятор должен определить порядок их выполнения. При этом компилятор руководствуется правилами, которые называются старшинством операторов. Понимание старшинства операторов нужно для правильного написания выражений – иногда результат может не соответствовать ожидаемому.

Посмотрим, как старшинство операторов определяется в C# (табл.

3.18).

Табл. 3.18 – Операторы языка C# (по приоритету)

Прио-

Группа

 

Операции

Поря-

ритет

 

 

 

док

 

 

 

 

 

0

Первичные

 

()(1) . :: ()(2) [] ++(3)

 

 

 

––(3) ->(4) new typeof

 

 

 

 

checked unchecked

 

 

 

 

default delegate

 

 

 

 

stackalloc(4)

 

1

Унарные

 

+ – ! ~ ++(5) ––(5) ()(6)

 

 

 

&(4, 7) *(4, 8) sizeof(4)

 

 

 

 

true false

 

 

 

 

 

 

2

Мультипликативные

 

* / %

 

 

 

 

 

3

Аддитивные

 

+ –

 

 

 

 

 

4

Сдвига

 

<< >>

 

 

 

 

 

 

 

171

 

5

Отношения и проверки типов

< > <= >= is as

 

 

 

 

6

Эквивалентности

== !=

 

 

 

 

7

Побитовые

&

 

 

 

 

8

 

^

 

 

 

 

9

 

|

 

 

 

 

10

Булевы

&&

 

 

 

 

11

 

||

 

 

 

 

12

Поддержка null

??

 

 

 

 

13

Условный

?:

 

 

 

 

14

Присваивания

= *= /= %= += –= <<=

 

 

>>= &= ^= |=

 

 

 

 

 

 

Лямбда-выражение

=>

 

 

 

 

Примечания:

(1)скобки;

(2)оператор вызова функции;

(3)постфиксные;

(4)только в небезопасном коде;

(5)префиксные;

(6)преобразование типа;

(7)получение адреса;

(8)разыменование указателя.

При конструировании выражений важно учитывать приоритет операций. Например, результат выражения

false && false || true

зависит от того, у какого оператора выше приоритет – логической конъюнкции или дизъюнкции. Если у конъюнкции, то получим

(false && false) || true = false || true = true

А если у дизъюнкции, то

false && (false || true) = false && true = false

Если нет уверенности в очередности выполнения операций, то лучше задать ее явно, используя скобки. Важно, что у операторов присваивания самый низкий приоритет (14). Иначе в записи

<переменная> = <выражение>

172

выражение практически всегда приходилось бы брать в скобки.

По порядку выполнения операторы разделяются на операторы с левой и правой ассоциативностью. Ассоциативность определяет, какая часть выражения должна быть вычислена первой. Например, результатом приведенного выражения может быть 21 или 33 в зависимости от того, какая ассоциативность будет применяться для оператора «–»: левая или правая:

42 - 15 - 6

Данный оператор имеет левую ассоциативность, т.е. сначала вычисляется 42–15, а затем из результата вычитается 6, результат – 21. Если бы он имел правую ассоциативность, сначала вычислялась бы правая часть выражения (15–6 = 9), а затем результат вычитался бы из: 42 – 9 = 33.

Все бинарные операторы (операторы с двумя операндами), кроме операторов присваивания, – лево-ассоциативные, т.е. они обрабатывают выражения слева направо. Таким образом, «а + b + c» – то же, что и «(а + b) + c», где сначала вычисляется «а + b», а затем к сумме прибавляется «c». Операторы присваивания и условные операторы – право-ассоциативные, т.е. обрабатывают выражения справа налево. Иначе говоря, «a = b = c» эквивалентно «a = (b = c)». Например, рассмотрим следующий код:

int a = 1; int b = 2; int с = 3; a = b = c;

В результате все три переменные будут иметь значение 3. Если бы оператор присваивания был лево-ассоциативным, то сначала выполнился бы оператор «a = b», а затем – «b = c». В итоге переменная «a» имела бы значение 2, а переменные «b» и «c» – 3. Очевидно, что мы ожидаем не этого, когда пишем «a = b = c», и именно поэтому операторы присваивания и условные операторы право-ассоциативные.

3.3.3. Описание операторов

Рассмотрим перечисленные выше операторы по категориям. Но сначала выделим некоторые первичные операторы, которые нам уже знакомы или которые будут изучены позже.

Пример: Samples\3.3\3_3_3_oper.

173

3.3.3.1. Первичные операторы

Рассмотрим сначала некоторые простые первичные операторы, с которыми мы уже сталкивались ранее или рассмотрим в последующих разделах:

(x) – Это разновидность оператора «скобки» для управления порядком вычислений в математических, логических и других операциях;

x.y – Оператор «точка» используется для указания члена пространства имен, класса, структуры или интерфейса. Здесь «x» представляет сущность, содержащую в себе член «y»;

x::y – Квалификатор псевдонима пространства имен. Здесь «x» представляет псевдоним пространства имен, содержащего в себе член «y» (§ 4.1);

f(x) – Такая разновидность оператора «скобки» применяется для вызова методов;

a[x] – Квадратные скобки используются для индексации массива;

new – Этот оператор используется для создания экземпляров объектов (классов, структур, делегатов и т.д.);

stackalloc – Выделение блока памяти на стеке (п. 5.5.2.4);

default – Возвращает значение типа по умолчанию (§ 3.1);

delegate – Оператор объявления анонимной функции (§ 4.8). Остальные первичные операторы рассмотрены далее, по категориям.

Кроме условного тернарного оператора, остальные операторы являются унарными или бинарными (оператора). Синтаксис операторов будем записывать в следующей форме:

1) Для префиксных или постфиксных унарных операторов:

<тип результата> operator op (<тип аргумента> x)

В префиксных операторах знак операции (op) стоит перед аргументом (op x), в постфиксных операциях – после аргумента (x op).

2) Для инфиксных бинарных операторов:

<тип результата> operator op (<тип аргумента1> x, <тип аргумента2> y)

В инфиксных операторах знак операции (op) стоит между аргументами

(x op y).

Если оператор применим для какого-либо типа по значению <тип>, то он применим и для соответствующего обнуляемого типа <тип>?. При этом, если хотя бы один аргумент имеет неопределенное значение (null), то и результатом также будет null. Исключения будут оговариваться отдельно.

174

3.3.3.2. Арифметические операторы

Язык C#, как и большинство других языков, поддерживает основные математические операторы – бинарные (умножение, деление, сложение, вычитание и модуль) и унарные (плюс и минус). Синтаксис бинарных операто-

ров (*, /, +, -, %):

<тип> operator op (<тип> x, <тип> y)

Здесь <тип> – один из типов int, uint, long, ulong, float, double, decimal. Операторы «+» и «–» также определены для делегатов (см. § 4.8). Для выполнения арифметических операций с другими типами данных используется неявное приведение типов. А если оно не определено, компилятор генерирует ошибку:

byte b = 1;

b = b + b; // Ошибка

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

Унарных арифметических операторов четыре: плюс, минус, инкремент и декремент. Первые два являются префиксными, последние два имеют префиксную и постфиксную форму.

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

<тип> operator - (<тип> x)

Допустимые типы – int, long, float, double, decimal. В операторе унарного плюса результатом будет просто значение аргумента. Синтаксис:

<тип> operator + (<тип> x)

Допустимые типы – int, uint, long, ulong, float, double, decimal.

Операторы инкремента (++) и декремента (--) позволяют лаконично выразить, что мы хотим увеличить или уменьшить числовое значение на 1. В префиксной версии операторов сначала выполняется операция, а затем создается значение. В постфиксной версии сначала создается значение, а затем выполняется операция. Синтаксис операторов:

<тип> operator ++ (<тип> x) <тип> operator –- (<тип> x)

175

Допустимые типы – sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal и перечисления. При этом аргумент «x» обязательно должен попадать в категорию l-value (см. п. 3.3.3.6).

Пример:

int v1 = 1; uint v2 = 2; byte v3 = 1; sbyte v4 = 2; long v5 = 1; ulong v6 = 2; double v7 = 1; float v8 = 2; decimal v9 = 3; char v10 = 'A';

bool v11 = false;

Console.WriteLine("int + uint = " + (v1 + v2).GetType()); Console.WriteLine("byte - sbyte = " + (v3 - v4).GetType()); Console.WriteLine("uint * ulong = " + (v2 * v6).GetType()); Console.WriteLine("uint / byte = " + (v2 / v3).GetType()); Console.WriteLine("byte % byte = " + (v3 % v3).GetType()); Console.WriteLine("long + ulong = " + (v5 + v6).GetType()); // Ошибка Console.WriteLine("double * int = " + (v7 * v1).GetType()); Console.WriteLine("double - decimal = " + (v7 – v9).GetType()); //

Ошибка

Console.WriteLine("float - float = " + (v8 - v8).GetType()); Console.WriteLine("char * char = " + (v10 * v10).GetType()); Console.WriteLine("bool * bool = " + (v11 * v11).GetType()); // Ошибка

DayOfWeek v12 = DayOfWeek.Friday;

int a1

= 1;

int b1

= a1++;

int

a2

=

1;

int

b2

=

++a2;

Console.WriteLine("{0} {1}", a1, b1);

Console.WriteLine("{0} {1}", a2, b2);

int b3 = 1++; // Ошибка

Console.WriteLine(++v10);

Console.WriteLine(++v12);

Первая ошибка связана с тем, что нет типа данных с диапазоном значений, перекрывающим диапазоны значений типов long и ulong. Вторая – с тем, что не определены неявные преобразования между типами double и decimal. Третья – с тем, что тип bool нельзя неявно преобразовать ни к одному из типов, для которых определены арифметические операторы. Четвертая – аргумент инкремента не является l-value. Результаты работы программы:

int + uint = System.Int64 byte - sbyte = System.Int32 uint * ulong = System.UInt64

176

uint / byte = System.UInt32 byte + byte = System.Int32 double * int = System.Double float - float = System.Single char * char = System.Int32

2 1

2 2 B

Saturday

3.3.3.3.Логические операторы

Клогическим операторам относятся условные логические операторы конъюнкция (&&) и дизъюнкция (||), а также логические операторы отрицания (!) и пользовательские условные логические операторы true и false.

Условные логические операторы являются бинарными:

bool operator && (bool x, bool y) bool operator || (bool x, bool y)

Данные операторы применимы только к аргументам логического типа, а также к типам, определяющим преобразование к типу bool (как определить операции преобразования типа, читайте в § 4.7). Для обнуляемого типа bool? они не применимы.

Операторы «&&» и «||» являются условными версиями операторов «&»

и«|» для типа bool:

Операция «x && y» соответствует операции «x & y», за исключением того, что «y» вычисляется, только если «x» не равен false.

Операция «x || y» соответствует операции «x | y», за исключением того, что «y» вычисляется, только если «x» не равен true.

Синтаксис унарных логических операторов:

bool operator ! (bool x) bool operator true (<тип> x)

bool operator false (<тип> x)

Первый оператор вычисляет логическое отрицание операнда. Операторы true и false определяются в классе пользователя для того, чтобы экземпляры класса могли:

1)использоваться в качестве условия в операторах if, for, while, а также

вусловном операторе (?:) (см. пример);

2)являться аргументами логических операторов (см. § 4.7).

Пример:

CInt v16 = new CInt(1);

177

v1 = 1;

if (v1 && v11) Console.WriteLine(v1); // Ошибка v11 = false;

if (v11 && (v1++) != 0) Console.WriteLine(v1); v11 = true;

if (v11 || (v1++) != 0) Console.WriteLine(v1);

if (true || (v1++) != 0) Console.WriteLine(v1); // Предупреждение Console.WriteLine(v1);

if (v16) Console.WriteLine(v16.Value + " == true");

Вывод на консоль:

1

1

1

1 == true

Видно, что код «v1++» ни разу не был выполнен (данное выражение не обязательно заключать в скобки, т.к. приоритет инкремента выше, чем у операций сравнения; это сделано для повышения читабельности кода). В первом случае потому, что «false && x» всегда равно false, поэтому вычисление аргумента «x» не требуется. Во втором случае – потому что «true || x» всегда равно true, и вычисление аргумента «x» также не требуется. В третьем случае в логическом операторе использована не переменная, а константа, поэтому код «v1++» не будет выполнен ни при каких условиях. Компилятор генерирует предупреждение «Обнаружен недостижимый код в выражении».

Ошибка связана с тем, что оператор «int && bool» не определен, и нет неявного преобразования типа int в тип bool.

В последней строке переменная v16 является аргументом условия. Это возможно, т.к. класс CInt перегружает операторы true и false (см. исходный код решения к данному пункту). Видно, что данные операторы вызываются неявно. Запись true(v16) или false(v16) привела бы к ошибке компиляции.

3.3.3.4.Побитовые операторы

Кпобитовым операторам относятся побитовые логические операторы конъюнкция (&), дизъюнкция (|), исключающая дизъюнкция (^) и дополнение (~), а также операторы побитового сдвига (<< и >>).

Синтаксис бинарных побитовых логических операторов:

<тип> operator op (<тип> x, <тип> y)

Данные операторы применимы к аргументам следующих типов: int, uint, long, ulong, bool и перечислений.

178

Синтаксис побитового дополнения:

<тип> operator ~ (<тип> x)

Допустимые типы – те же самые за исключением bool.

Также побитовая конъюнкция и дизъюнкция применимы к обнуляемому типу bool?. При этом, если один из операндов имеет неопределенное значение (null), правила вычисления результата таковы:

false && null = null && false = false true || null = null || true = true

Во всех остальных случаях результатом будет null. Синтаксис операторов сдвига:

<тип> operator op (<тип> x, int y)

Допустимые типы аргумента – int, uint, long и ulong, а величина сдвига всегда имеет тип int. Сдвиг влево на n позиций эквивалентен умножению целого числа на 2n, сдвиг вправо – целочисленному делению на 2n. При сдвиге вправо чисел со знаком бит знака сохраняется. Во всех остальных случаях освободившиеся позиции заполняются нулями.

Пример:

bool? v13 = null;

Console.WriteLine("null & false = " + (v13 & false));

Console.WriteLine("true | null = " + (true | v13));

Console.WriteLine("null & false = " + (v13 & null));

//0111 0111 0011 0101 1001 0100 0000 0000

v1 = 2000000000;

//0011 1011 1001 1010 1100 1010 0000 0000 Console.WriteLine(v1 >> 1);

//1110 1110 0110 1011 0010 1000 0000 0000 Console.WriteLine(v1 << 1);

//1000 1000 1100 1010 0110 1100 0000 0000

v1 = -2000000000;

//1100 0100 0110 0101 0011 0110 0000 0000 Console.WriteLine(v1 >> 1);

//0001 0001 1001 0100 1101 1000 0000 0000 Console.WriteLine(v1 << 1);

Вывод на консоль:

null & false = False true | null = True null & false = 1000000000 -294967296 -1000000000 294967296

В третьей строке результат null выводится на консоль в виде пустой

179

строки.

3.3.3.5. Операторы сравнения

Большинство операторов возвращает числовые значения. Что касается операторов сравнения, они генерируют булевский результат. Операторы сравнения анализируют соотношение между операндами и возвращают значение true, если соотношение истинно, false – если ложно.

К операторам сравнения относятся операторы: меньше (<), меньше или равно (<=), больше (>), больше или равно (>=), равно (==) и не равно (!=). Синтаксис:

bool operator op (<тип> x, <тип> y)

Допустимые типы – uint, int, long, ulong, float, double, decimal и пере-

числения. Также операторы «==» и «!=» определены для типов bool, object, string и делегатов, а для обнуляемых типов они допускают сравнение с null.

На операциях со строками мы еще остановимся в п. 3.3.4, с делегатами

– в § 4.8. Здесь лишь еще раз отметим, что при сравнении типов по значению сравнивается содержимое объектов, а при сравнении ссылочных типов – ссылки, хотя некоторые классы (типа System.String) эти операторы перегружают. Операторы «==» и «!=» используют метод Object.Equals, который для ссылочных типов эквивалентен методу Object.ReferenceEquals.

Пример:

int? v14 = null; int val1 = 1; int val2 = 1;

object obj1 = val1; object obj2 = val2;

Console.WriteLine("v14 == null? " + (v14 == null));

Console.WriteLine("v13 == v11? " + (v13 == v11));

Console.WriteLine("v13 < v11? " + (v13 < v11)); // Ошибка

Console.WriteLine("val1 == val2? " + (val1 == val2));

Console.WriteLine("obj1 == obj2? " + (obj1 == obj2));

Вывод на консоль:

v14 == null? True

v13 == v11? False val1 == val2? True obj1 == obj2? False

180