
Объектно-ориентированное программирование.-7
.pdf
3.3.3.6. Операторы присваивания
Выражения, которые могут находиться в левой части оператора присваивания, называются l-value, а в правой части – r-value. В качестве r-value может быть любая константа, переменная, число или выражение, результат которого совместим с l-value. Между тем, l-value должно быть переменной, свойством или индексатором определенного типа – должна существовать возможность вычислить ее физический адрес. Например, можно написать i = 1, но нельзя написать 1 = i или i + j = 1.
Операторы присваивания делятся на простой (=) и составные (+=, –=,
*=, /=, %=, &=, |=, ^=, <<=, >>=).
Синтаксис простого оператора присваивания:
<тип> operator = (<тип> <l-value>, <тип> <r-value>)
Обратите внимание, что оператор присваивания возвращает результат (значение l-value). Его можно использовать в других выражениях, например, a = b = c, f(p = q), z = 4 + (x = y).
Оператор присваивания определен для всех объектов .NET. Как уже отмечалось, для типов по значению он копирует содержимое объектов, а для ссылочных типов – ссылки.
Синтаксис составных операторов присваивания:
<тип1> operator op= (<тип2> <rvalue>)
Аналогично, аргумент, использующийся в правой части данных операторов, должен быть l-value. Ограничения на типы данных – такие же, как для базовых операторов «op».
Хотя выражение x op= y эквивалентно выражению x = x op y, код MSIL в обоих случаях будет отличаться. Для составных операторов он будет более эффективным, поэтому используйте их везде, где это возможно.
Пример:
Console.WriteLine("v5 = 1 = " + (v5 = 1).GetType()); Console.WriteLine("v9 = v5 = " + (v9 = v5).GetType()); val1 = val2;
obj1 = obj2;
Console.WriteLine(val1 == val2 ? "val1 == val2" : "val1 != val2"); Console.WriteLine((object)val1 == (object)val2 ?
"ref val1 == ref val2" : "ref val1 != ref val2"); Console.WriteLine(obj1.Equals(obj2) ? "obj1 == obj2" : "obj1 != obj2"); Console.WriteLine(Object.ReferenceEquals(obj1, obj2) ?
"ref obj1 == ref obj2" : "ref obj1 != ref obj2");
181

Вывод на консоль:
v5 = 1 = System.Int64
v9 = v5 = System.Decimal val1 == val2
ref val1 != ref val2 obj1 == obj2
ref obj1 == ref obj2
3.3.3.7.Операторы преобразования типа
Вязыке C# предусмотрены два оператора явного преобразования типа:
<тип1> operator <тип1> (<тип2> x) <тип1> operator as (<тип2> x, <тип1>)
Если явное преобразование отсутствует, возникает ошибка компиляции. Отличия между ними следующие:
1)Оператор as осуществляет преобразование только к ссылочным типам данных;
2)Если в процессе преобразования возникают ошибки, оператор «()» вызывает исключительную ситуацию, а оператор as просто возвращает null.
Пример:
string v15;
object[] mas = new object[] { 1, "ABC", 2.5 };
Console.WriteLine(mas[0].GetType());
Console.WriteLine(mas[1].GetType());
Console.WriteLine(mas[2].GetType()); v1 = mas[0]; // Ошибка
v1 = (int)mas[0];
v1 = (int)mas[1]; // System.InvalidCastException
v1 = mas[2] as double; // Ошибка
v15 = mas[1] as string; Console.WriteLine(v15);
Console.WriteLine(Object.ReferenceEquals(v15, mas[1]));
В примере создается массив объектов, первый из которых имеет тип int, второй – string, третий – double. Неявное преобразование из типа object в тип int не определено, поэтому первая операция присваивания вызывает ошибку компиляции. Вторая операция выполняется корректно, v1 и mas[0] имеют тип int. Третья вызывает исключительную ситуацию во время выполнения программы, т.к. невозможно преобразовать "ABC" к типу int. Четвертая операция вызывает ошибку компиляции – оператор as не выполняет преобразование к типам по значению. В пятой происходит корректное копирование ссылки на строку. Вывод на консоль:
182

System.Int32
System.String
System.Double
ABC
True
3.3.3.8. Операторы выбора
В языке C# есть два оператора, позволяющие осуществить выбор:
1) Условный оператор – единственный тернарный оператор (имеющий три аргумента). Синтаксис:
<тип> operator ?: (bool z, <тип1> x, <тип2> y)
Если значение «z» истинно, то результатом будет «x», иначе – «y». Тип результата – это наименьший общий тип для <тип1> и <тип2>, т.е. данные типы должны иметь неявное преобразование к типу результата. Если такое преобразование отсутствует, получаем ошибку компиляции. Обычно <тип> – это один из типов <тип1> и <тип2> с более широким диапазоном значений.
2) Оператор слияния с null. Синтаксис:
<тип1> operator ?? (<тип1> x, <тип2> y)
Здесь <тип1> должен быть ссылочным или обнуляемым, а <тип2> должен иметь неявное преобразование к <тип1>. Результатом операции будет «x», если x != null, иначе – «y».
Пример:
Console.WriteLine((v11 ? |
v1 |
: v5).GetType()); |
Console.WriteLine((v11 ? |
v5 |
: v6).GetType()); // Ошибка |
v1 = v14 ?? -1; |
|
|
Console.WriteLine(v1); |
|
|
v15 = null; |
|
|
Console.WriteLine(v15 ?? |
"строка пуста!"); |
|
v1 = v2 ?? 1; // Ошибка |
|
|
|
|
|
Первая ошибка связана с тем, что не существует неявного преобразования между типами long и ulong. Вторая – v2 не является ссылочным или обнуляемым типом. Вывод на консоль:
System.Int64 -1
строка пуста!
3.3.3.9. Рефлективные операторы
Отражение (reflection) – это способность получать метаданные (т.е. данные о данных, или информацию о типе) в период исполнения (см. § 5.3).
183

Т.к. все объекты языка C# являются классами, то соответствующие метаданные называют метаклассами. Метаклассы представлены типом System.Type.
Синтаксис рефлективных операторов:
Type operator typeof (<тип>) int operator sizeof (<тип>)
bool operator is (<тип1> x, <тип2>)
Оператор typeof возвращает метакласс типа. Также его можно получить, вызвав метод GetType() для экземпляра класса. Оператор sizeof возвращает количество байтов, занимаемых экземпляром указанного типа по значению на стеке. Оператор is проверяет, является ли «x» экземпляром типа <тип2> или типа, наследуемого от него.
Пример:
Console.WriteLine(v1.GetType());
Console.WriteLine(typeof(string));
Console.WriteLine(typeof(float[,]));
Console.WriteLine(typeof(float[,]).BaseType);
Console.WriteLine(typeof(void));
unsafe
{
Console.WriteLine(sizeof(float)); Console.WriteLine(sizeof(decimal)); Console.WriteLine(sizeof(char)); Console.WriteLine(sizeof(DateTime)); Console.WriteLine(sizeof(string)); // Ошибка
}
Console.WriteLine((object)v1 is ValueType);
Console.WriteLine((object)v1 is String);
Console.WriteLine((object)v12 is Enum);
Console.WriteLine(mas[1] is String);
Вывод на консоль:
System.Int32
System.String
System.Single[,]
System.Array
System.Void
4
16
2
8 True False True True
Класс Type имеет множество членов для получения полной информации о типе данных (подробности см. в библиотеке MSDN).
184

Оператор sizeof можно применять только в небезопасном коде. Для этого используется блок unsafe (см. § 5.5). Также необходимо разрешить использование небезопасного кода в проекте (пункт меню «Проект» → «Свойства…», вкладка «Построение», отметить опцию «Разрешить небезопасный код»). Для структур размером будет сумма размеров полей (с учетом выравнивания). Ошибка связана с попыткой определения размера типа по ссылке. Если структура будет содержать поля, имеющие ссылочный тип, это также вызовет ошибку компиляции.
3.3.3.10. Операторы управления переполнением
Данные операторы управляют контекстом проверки переполнения для арифметических операций и преобразований с целыми типами данных. Синтаксис:
<тип> operator checked (<тип> <выражение>) <тип> operator unchecked (<тип> <выражение>)
Оператор checked включает проверку переполнения при вычислении целочисленных выражений, а оператор unchecked отключает ее. По умолчанию проверка выключена. Также данные операторы можно использовать как операторы языка, за которыми следует блок программы:
checked <блок> unchecked <блок>
В этом случае их действие распространяется на весь блок. Пример использования рассмотрен в п. 3.1.2.8.
3.3.3.11. Операторы для работы с указателями
При работе с указателями применяются многие из операций, знакомых ещё из языка C++:
•«*» – разыменование указателя;
•«->» – доступ к члену структуры, представленной указателем;
•«[]» – индексирование указателя;
•«&» – получение адреса переменной.
Как и в языке C++, к указателям можно применять некоторые арифметические операции (+, –, ++, ––) и операции сравнения. Работа с указателями разрешена только в небезопасном коде. Подробнее мы ее рассмотрим в § 5.5.
185

3.3.3.12. Лямбда-оператор
Маркер «=>» называется лямбда-оператором. Он используется в лямбда-выражениях для отделения входных переменных с левой стороны от тела лямбда-выражения с правой стороны. Лямбда-выражения – это встроенные выражения, аналогичные анонимным методам (см. § 4.8), но более гибкие. Они широко используются в запросах LINQ, выраженных с использованием синтаксиса методов. Материал по лямбда-выражениям в данное учебное пособие не входит.
3.3.4. Операции со строками
Как уже отмечалось в п. 3.1.3.1, в классе System.String определены четыре оператора для работы со строками – «==», «!=», «+» и «[]» (последний из них, строго говоря, является в терминах языка C# не оператором, а индексатором). Как и все объекты, строки также допускают присваивание («=»). Как будет показано в § 4.7, если к экземплярам какого-либо класса применим оператор «+», то к ним также применим оператор «+=». Это относится и к строкам. Строки не допускают модификации содержимого, поэтому операция «x += y» фактически не изменяет строку «х», а создает новую строку «x + y» и помещает ссылку на нее в «x» (см. пример ниже).
Как видно, из операций отношения к строкам применимы только операции «равно» и «не равно». Их действие основано на вызове метода Equals. Как реализовать другие операции отношения? Для этого используются раз-
личные варианты методов Compare/CompareOrdianl/CompareTo. Некоторые из них статические (например, String.Compare(x, y)), другие – нет (например, x.CompareTo(y)). Результат, возвращаемый этими методами, зависит от соотношений аргументов:
•отрицательное число, если x < y;
•ноль, если x = y;
•положительное число, если x > y.
Поэтому, например, для выполнения операции отношения «x ≤ y» сле-
дует писать «String.Compare(x, y) ≤ 0».
Пример:
string copy = "Это исходная строка"; string x = copy;
186

Console.WriteLine(Object.ReferenceEquals(copy, x)); x += "!!!"; Console.WriteLine(Object.ReferenceEquals(copy, x)); Console.WriteLine(copy);
Console.WriteLine(x);
Console.WriteLine(copy == "Это исходная строка"); Console.WriteLine(copy.Equals("Это исходная строка")); Console.WriteLine(Object.Equals(copy, "Это исходная строка")); Console.WriteLine(String.Equals(copy, "Это исходная строка")); Console.WriteLine(copy == x); Console.WriteLine(String.Compare(x, copy)); Console.WriteLine(copy.CompareTo(x));
Вывод на консоль:
True
False
Это исходная строка Это исходная строка!!!
True
True
True
True
False 1 -1
Пример: Samples\3.3\3_3_4_string.
3.3.5.Операции с перечислениями
Спеременными перечисляемого типа можно выполнять арифметические операции (+, -, ++, --), логические поразрядные операции (^, &, |, ~), сравнивать их с помощью операций отношения (<, <=, >, >=, ==, !=) и получать размер в байтах (sizeof). При использовании переменных перечисляемого типа в целочисленных выражениях и операциях присваивания требуется явное преобразование типа.
Пример:
[FlagsAttribute] enum BitFlags : byte
{
bit1 = 0x01, bit2 = 0x02, bit3 = 0x04, bit4 = 0x08, bit5 = 0x10, bit6 = 0x20, bit7 = 0x40, bit8 = 0x80
}
static int Main()
{
BitFlags bits = (BitFlags)29;
Array values = Enum.GetValues(bits.GetType()); DayOfWeek day = DayOfWeek.Friday;
DayOfWeek next = day + 1;
CultureInfo ru = new CultureInfo("ru-RU"); string[] days = ru.DateTimeFormat.DayNames;
187

Console.Write("0x{0:X} = ", (int)bits);
for (int i = values.Length - 1; i >= 0; i--)
{
Console.Write((bits & (BitFlags)values.GetValue(i)) != 0 ?
"1" : "0");
}
Console.WriteLine("Если сегодня {0}, то завтра будет {1}", days[(int)day], days[(int)next]);
next -= 3;
Console.WriteLine("А {0} {1}, чем {2}", days[(int)day], day < next ? "раньше" : "позже", days[(int)next]);
return 0;
}
Вывод на консоль:
0x1D = 00011101
Если сегодня пятница, то завтра будет суббота А пятница позже, чем среда
Пример: Samples\3.3\3_3_5_enum.
3.3.6. Операции с типом DateTime
Согласно табл. А.1 в приложении А, для структуры DateTime определены операции сложения, вычитания и все операции отношения. Оператор «+» применим для прибавления к дате и времени интервала (выраженного экземпляром структуры TimeSpan). Оператор «-» позволяет найти интервал между двумя датами, или отнять от даты интервал. Операторы «+=» и «-=» работают только с экземплярами даты и времени в левой части и интервалами в правой.
Пример:
DateTime date1 = new DateTime(2010, 10, 10);
DateTime date2 = new DateTime(2007, 7, 7);
TimeSpan span = date1 - date2;
DateTime date3 = date2 - span;
Console.WriteLine("Между {0:d} и {1:d} - {2} дней", date1, date2, span.TotalDays);
Console.WriteLine("За {0} дней до {1:d} было {2:d}", span.TotalDays, date2, date3);
date3 = date1; date3 += span;
Console.WriteLine("Через {0} дней после {1:d} будет {2:d}", span.TotalDays, date1, date3);
date3 = DateTime.Today;
if (date3 == date2) Console.WriteLine("Сегодня 7 июля 2007 года!"); else if (date3 < date2) Console.WriteLine("7 июля 2007 года еще не
188

наступило!");
else Console.WriteLine("7 июля 2007 года уже прошло!");
Вывод на консоль:
Между 10.10.2010 и 07.07.2007 - 1191 дней За 1191 дней до 07.07.2007 было 02.04.2004
Через 1191 дней после 10.10.2010 будет 13.01.2014 7 июля 2007 года уже прошло!
Пример: Samples\3.3\3_3_6_datetime.
3.3.7. Математические вычисления
Вычисление математических выражений является важным элементом языка. Основной набор перечисленных здесь функций имеется и в математических библиотеках языка C++, хотя от поддержки чисел типа long double (используемые в Borland C++ 10-байтовые числа с плавающей точкой и диапазоном ±3.6·10–4951… ±1.1·104932, поддерживаемые на аппаратном уровне математическими сопроцессорами) в языке C# разработчики отказались.
Пример: Samples\3.3\3_3_7_math.
3.3.7.1. Математические функции и константы
Все математические функции и константы в языке C# реализованы в виде членов класса System.Math. Список его открытых членов приведен в табл. 3.19. Все они являются статическими.
|
|
Табл. 3.19 – Члены класса System.Math |
|
|
|
Член |
|
Описание |
|
|
|
|
Методы |
|
|
|
|
static <тип> Abs(<тип> value) |
|
Возвращает абсолютное значение |
|
|
числа(1) |
static double Acos(double d) |
|
Арккосинус числа (arccos d) |
|
|
|
static double Asin(double d) |
|
Арксинус числа (arcsin d) |
|
|
|
static double Atan(double d) |
|
Арктангенс числа (arctg d) |
|
|
|
static double Atan2(double y, |
|
Арктангенс отношения y/x (коррект- |
double x) |
|
но работает для x = 0) |
|
|
|
|
|
|
static long BigMul(int a, int |
|
Умножает два 32-битовых числа |
b) |
|
|
|
|
|
static <тип> Ceiling(<тип> d) |
|
Округление числа(2, 4) |
|
|
|
|
189 |
static double Cos(double d) |
|
Косинус угла (cos d) |
|
|
|
static double Cosh(double d) |
|
Гиперболический косинус угла (cosh |
|
|
d) |
|
|
|
static <тип> DivRem(<тип> a, |
|
Возвращает частное от деления и |
<тип> b, out <тип> result) |
|
остаток в выходном параметре для |
|
|
|
|
|
типов int и long |
|
|
|
static double Exp(double d) |
|
Возводит число e в указанную сте- |
|
|
пень (ed) |
static <тип> Floor(<тип> d) |
|
Округление числа(2, 4) |
|
|
|
static double IEEERemainder |
|
Возвращает остаток от деления x на y |
(double x, double y) |
|
|
|
|
|
static double Log(double d) |
|
Натуральный логарифм (по основа- |
|
|
нию e) (ln d) |
|
|
|
static double Log(double a, |
|
Логарифм по указанному основанию |
double base) |
|
(logbase d) |
|
|
|
|
|
|
static double Log10(double d) |
|
Десятичный логарифм (lg d) |
|
|
|
static <тип> Max(<тип> val1, |
|
Возвращает наибольшее из двух чи- |
<тип> val2) |
|
(3) |
|
|
сел |
static <тип> Min(<тип> val1, |
|
Возвращает наименьшее из двух чи- |
<тип> val2) |
|
(3) |
|
|
сел |
static double Pow(double x, |
|
Возведение в степень (xy) |
double y) |
|
|
|
|
|
static <тип> Round(<тип> d, |
|
Ряд методов для округления чисел(2, 4) |
...) |
|
|
|
|
|
static int Sign(<тип> value) |
|
Знак числа(1). Возвращает 1 для по- |
|
|
ложительных чисел, 0 для нуля, –1 |
|
|
для отрицательных чисел |
|
|
|
static double Sin(double d) |
|
Синус угла (sin d) |
|
|
|
static double Sinh(double d) |
|
Гиперболический синус угла (sinh d) |
|
|
|
static double Sqrt(double d) |
|
Квадратный корень |
|
|
|
static double Tan(double d) |
|
Тангенс угла (tg d) |
|
|
|
static double Tanh(double d) |
|
Гиперболический тангенс угла (tgh d) |
|
|
|
static decimal Truncate(<тип> |
|
Округление числа(2, 4) |
d) |
|
|
|
|
|
|
Поля |
|
|
|
|
const double E |
|
Основание натурального логарифма |
|
|
|
|
190 |