Поиск

Математические операторы

С#, как и большинство других языков, поддерживает основные математические операторы: умножение (*), деление (/), сложение (+), вычитание (—) и модуль (%). Назначение первых четырех операторов понятно из их названий; оператор модуля формирует остаток от целочисленного деления. Вот код, иллюстрирующий применение математических операторов:

using System;
class MathOpsApp
{
public static void MainQ
{

// Класс System.Random является частью библиотеки классов // .NET Framework. В его конструкторе по умолчанию // метод Next использует текущую дату/время в качестве // начального значения. Random rand = new RandomO; int a, b, c;
a = rand.Next() % 100; // Предельное значение 99. b = rand.NextO % 100; // Предельное значение 99.

Console.WriteLine("a={0} b={1}", a, b);
с = a * b;
Console.WriteLineC'a * b = {0}", c);

// Заметьте, что здесь используются целые числа. // Следовательно, если а меньше Ь, результат всегда // будет 0. Для получения более точного результата // нужно применять переменные типа double или float, с = а / b; Console.WriteLineC'a / b = {0}", с);

с = a + b;
Console.WriteLineC'a + b = {0}", c);
с = a - b;
Console.WriteLineC'a - b = {0}", c);
с = a X b;
Console.WriteLineC'a X b = {0}", c); > >
Унарные операторы

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

using System; using System;
class UnarylApp {
public static void Main()
{
int a = 0;
a = -42;
Console.WriteLine("{0}", a); } }
Однако в этом коде появляется неопределенность: using System;
class Unary2App <
public static void Main() {
int a; int b = 2; int с = 42;
a = b * -с;
Console.WriteLine("{0}", a); > >

Выражение a = b * -с не совсем понятно. Снова повторю, что использование скобок прояснит это выражение:
// При использовании скобок очевидно, что мы // умножаем b на отрицательное число с. а = b * (-с);
Если унарный минус возвращает отрицательное значение операнда, можно подумать, что унарный плюс возвращает положительное. Однако унарный плюс лишь возвращает операнд в его первоначальной форме и больше ничего не делает, т. е. не влияет на операнд. Например, выполнение этого кода приведет к выводу значения -84:

using System;
class UnarySApp {
public static void MainQ {
int a; int b = 2; int с = -42;
a = b * (+c);
Console.WriteLine("{0}", a); } }

Для получения положительного значения служит функция Math.Abs. Этот код выведет значение 84:

using System;
class Unary4App
{
public static void Main()
{
int a; int b = 2; int с = -42;
a = b * Math.Abs(c); Console.Writel_ine("{0}", a); } }

Последний унарный оператор, который я упоминал, — это Т(х). Это разновидность оператора «скобки», позволяющая приводить один тип к другому. Поскольку его можно перегрузить посредством создания пользовательского преобразования, мы обсудим его в главе 13.

Составные операторы присваивания

Составной оператор присваивания — это комбинация бинарного оператора и оператора присваивания (=). Синтаксис этих операторов таков:

хор=у
где ор — это оператор. Заметьте, что при этом левое значение (lvalue) не заменяется правым (rvalue), составной оператор оказывает такой же эффект, как:
х = х ор у
и lvalue используется как база для результата операции.

Заметьте, я сказал «оказывает такой же эффект». Компилятор не переводит выражение наподобие х += 5 в х = х + 5, он действует логически. С особым вниманием нужно отнестись к случаям, когда lvalue является методом. Рассмотрим код:

using System;
class CompoundAssignmentlApp {
protected lnt[] elements;
public int[] GetArrayElementO
{
return elements;
}
CompoundAssignment1App() {
elements = new int[1];
elements[0] = 42;
}
public static void Main() {
CompoundAssignmentlApp app = new CompoundAsslgnment1App();
Console.WrlteLine("{0>", app.GetArrayElement()[0]);
app.GetArrayElement()[0] = app.GetArrayElement()[0] + 5;
Console.WriteLine("{0}", app.GetArrayElement()[0]); }. }

Обратите внимание на выделенную строку — вызов метода Compound-AssignmentlApp.GetArrayElement и последующее изменение первого элемента — здесь я использовал синтаксис:

х = х ор у
Вот какой MSIL-код будет сгенерирован:
// Неэффективная методика: х = х ор у.
.method public hldebyslg static void Main() 11 managed
{
.entrypolnt
// Размер кода 79 (Ox4f) .maxstack 4
.locals (class CompoundAssignmentlApp V_0)
IL_0000: newobj instance void CompoundAssignmentlApp::.ctor()
IL_0005: stloc.O IL_0006: Idstr "{ОГ IL_OOOb: ldloc.0 ILJJOOc: call instance int32[]
CompoundAssignmentlApp:: GetArrayElementO
IL_0011: ldc.14.0
IL_0012: Idelema ['mscorlib']System.Int32
IL_0017: box [ 1 mscorlib']System.Int32
IL_001c: call void ['mscorlib 1 ]System.
Console::WriteLine (class System.String, class System.Object)
IL_0021: ldloc.0
IL_0022: call instance int32[]
CompoundAssignmentlApp::GetArrayElementO
IL_0027: Idc.i4.0
IL_0028: ldloc.0
IL_0029: call instance int32[]
CompoundAssignmentlApp: :GetArrayElementO
IL_002e: Idc.i4.0
IL_002f: ldelem.14
IL_0030: ldc.14.5
IL_0031: add
IL_0032: stelem.14
IL_0033: Idstr "{0}"
IL_0038: ldloc.0
IL_0039: call
instance int32[]
CompoundAssignmentlApp::GetArrayElement() IL_003e: ldc.14.0
IL_003f: Idelema ['mscorlib']Systera.Int32
IL_0044: box ['msoorlib']System.Int32 IL_0049: call void ['mscorlib']System.Console::WriteLine
(class System.String, class System.Object) IL_004e: ret
} // конец метода 'CompoundAssignmentlApp::Main' !
i

Посмотрите на вьщеленные строки: метод CompoundAssignmentlApp.Get-ArrayElement на самом деле вызывается дважды! Это по меньшей мере неэффективно, а возможно, и пагубно в зависимости от того, что еще делает этот метод.
Теперь рассмотрим другой код, в котором применен синтаксис составного оператора присваивания:
using System;
class CompoundAssignment2App {
protected int[] elements;
public int[] GetArrayElementO
{
return elements;
}
CompoundAssignment2App() {
elements = new int[1];
elements[0] = 42;
}
public static void Main() {
CompoundAssignment2App app = new CompoundAssignment2App();
Console.WriteLine("{0}", app.GetArrayElement()[0]);
app.GetArrayElement()[0] += 5; Console.WriteLine("{0}",
app.GetArrayElement()[0]); } }

Использование составного оператора присваивания приведет к созданию гораздо более эффективного MSIL-кода:

// Более эффективная методика: х ор= у.
.method public hidebysig static void Main() il managed
{
.entrypoint
I // Размер кода 76 (Ox4c) .maxstack 4
.locals (class CompoundAssignmentlApp V_0,
int32[] V_1)
IL_0000: newobj instance void CompoundAssignmentlApp::.ctor()
IL_0005: stloc.O 1 IL_0006: Idstr "{0}" 1 IL_OOOb: ldloc.0 IL_OOOc: call instance int32[]
CompoundAssignmentlApp:: GetArrayElementO
IL_0011: Idc.i4.0
IL_0012: Idelema [•mscorlib']System.Int32
IL_0017: box [ > mscorlib - ]System.Int32 lL_001c:
call void ['mscorlib']System.Console::WriteLine
(class System.String, class System.Object)
IL_0021: ldloc.0 IL_0022: call instance int32[]
CompoundAssignmentlApp::GetArrayElement()
IL_0027: dup
IL_0028: stloc.1
IL_0029: Idc.i4.0
IL_002a: ldloc.1
IL_002b: Idc.i4.0
IL_002c: ldelem.14
IL_002d: ldc.14.5
IL_002e: add
IL_002f: stelem.14
IL_0030: Idstr "{0}"
IL_0035: ldloc.0
IL_0036: call instance int32[]
CompoundAssignmentlApp::
GetArrayElementO IL_003b: Idc.i4.0
IL_003c: Idelema ['mscorlib']System.Int32
IL_0041: box [•mscorlib']System.Int32
IL_0046: call void ['mscorlib']System.Console::WriteLine
(class System.String, class System.Object)
IL_004b: ret } // конец метода 'CompoundAssignmentlApp::Main'

Вы видите, что использована команда MSIL dup. Она дублирует верхний элемент в стеке, создавая таким образом копию значения, возвра-щенного методом CompoundAssignmentlApp.Get Array Element. I

Из этого видно, что хотя по сути х +=у эквивалентно х = х + у, MSIL- код в обоих случаях разный. Это отличие должно заставить вас задуматься, какой синтаксис использовать в каждом отдельном случае. Эмпирическое правило и моя рекомендация: всегда и везде, где можно, применять составные операторы присваивания.

Операторы инкремента и декремента

Появившиеся в языке С и перенесенные в C++ и Java операторы инк 1 -ремента и декремента позволяют лаконично выразить, что вы хотите увеличить или уменьшить числовое значение на 1. То есть /++ равносильно добавлению 1 к текущему значению /'.
Наличие двух форм операторов инкремента и декремента иногда приводит к путанице. Префиксный и постфиксный типы этих операторов отличаются тем, в какой момент производится изменение значения. В префиксной версии операторов инкремента и декремента (++аи — а соответственно) сначала выполняется операция, а затем создается значение. В постфиксной версии (а++ и а—) сначала создается значение, а затем выполняется операция. Рассмотрим пример:

using System;
class IncDecApp {
public static void Foo(int j)
{
Console.WriteLine("IncDecApp.Foo j = {0}", j);
>
public static void Main() {
int i = 1;
Console.WriteLineC'flo обращения к Foo(i++) = {0}", i);
Foo(i++);
Console.WriteLine("После обращения к Foo(i++) = {0}", i);
Console.WriteLine("n");
Console.WriteLineC'flo обращения к Foo(++i) = {0}", i);
Foo(++l);
Console.WrlteLine("После обращения к Foo(++i) = {0}", i);
l Результат выполнения будет таким:
До обращения к Foo(i++) = 1
IncDecApp.Foo j = 1
После обращения к Foo(i++) = 2
До обращения к Foo(-n-i) = 2
IncDecApp.Foo j = 3
После обращения к Foo(++i) = 3

Разница в том, когда создается значение и модифицируется операнд. При вызове Foo(i++) значение /' передается (без изменений) в метод Foo и после возврата из метода / увеличивается на 1. Посмотрите на приведенный MSIL-код: команда add выполняется после помещения значения в стек.

IL.0013: ldloc.0
IL.0014: dup
IL_0015: Idc.i4.1
IL_0016: add
IL_0017: stloc.O
IL_0018: call void IncDecApp::Foo(int32)

Теперь посмотрим на префиксную форму оператора, используемую в вызове Foo(++a). В этом случае MSIL-код будет другой. При этом команда add выполняется до того, как значение помещается в стек для последующего вызова метода Foo.

IL.0049: ldloc.0
IL_004a: Idc.i4.1
IL_004b: add
IL_004c: dup
IL_004d: stloc.O
IL_004e: call void IncDecApp::Foo(int32