C#: Учебный курс

Г. Шилдт Глава 2.

Типы данных и операторы

Базовые типы данных C#
Литералы и их использование
Создание инициализированных переменных
Область видимости переменных
Арифметические операторы
Операторы сравнения и логические операторы
Оператор присваивания
Операция приведения типа, явное и неявное преобразование типов данных
Выражения в С#

Типы данных и операторы составляют основу любого языка программирования. Эти элементы определяют возможности языка и категории задач, к которым может быть применен этот язык. C# поддерживает большое количество типов данных и операторов, что делает его удобным для решения многих задач программирования.

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

Строгий контроль типов данных в C#

C# - язык со строгим контролем типов данных. Это означает, что все операции в C# контролируются компилятором на предмет совместимости типов данных, а если операция является недопустимой, то она не будет компилироваться. Такая строгая проверка типов данных помогает предотвратить ошибки и повышает надежность. Для возможности осуществления этого контроля всем переменным, результатам вычислений выражений и значениям задан определенный тип (то есть не существует переменной с неопределенным типом). Более того, тип значения определяет виды операций, которые разрешено производить над ним. Операция, разрешенная для одного типа данных, может быть запрещена для другого типа.

Обычные типы данных

C# включает две основные категории встроенных типов данных: обычные типы (или простые типы) и ссылочные типы. К ссылочным типам относятся классы, о которых мы поговорим позже. Обычные типы, которых в ядре C# тринадцать, представлены в табл. 2.1.

Таблица 2.1. Обычные типы данных

Тип Описание
Bool Значения true/false (истина/ложь)
Byte 8-битовое беззнаковое целое
Char Символ
Decimal Числовой тип для финансовых вычислений
Double Число двойной точности с плавающей точкой
Float Число одинарной точности с плавающей точкой
Int Целое число
Long Длинное целое
Sbyte 8-битовое знаковое целое
Short Короткое целое
Uint Беззнаковое целое
Ulong Беззнаковое длинное целое
Ushort Беззнаковое короткое целое

В C# для каждого типа данных строго специфицированы диапазон значений и возможные операции над ними. Соответственно требованиям переносимости в C# строго контролируется выполнение этих спецификаций. Например, тип данных int одинаков для любой среды выполнения программ, поэтому нет необходимости переписывать код для обеспечения совместимости этого типа с каждой отдельной платформой. Строгая спецификация необходима для достижения переносимости, хотя на некоторых платформах может привести к небольшой потере производительности.

Целочисленные типы

В C# определены девять целочисленных типов данных: char, byte, sbyte, short, ushort, int, uint, long и ulong. Тип данных char в основном применяется для представления символов и будет описан позже в этой главе. Остальные восемь типов используются для выполнения операций с числами. В следующей таблице перечислены эти типы, а также указано количество битов, выделяемых для представления числа, и диапазон возможных значений.

Тип Количество битов Диапазон значений
byte 8 От 0 до 255
sbyte 8 От -128 до 127
short 16 От -32768 до 32767
ushort 16 От 0 до 65535
int 32 От -2147483648 до 2147483647
uint 32 От 0 до 4294967295
long 64 От -9223372036854775808 до 9223372036854775807
ulong 64 От 0 до 18446774073709551615

Как показано в таблице, в C# определены как знаковые, так и беззнаковые варианты целочисленных типов. Различие между ними состоит в способе интерпретации старшего бита целого числа. Если указывается знаковое целое, то компилятор будет генерировать код, в котором предполагается интерпретация старшего бита целого числа как флага знака. Если флаг знака 0, то это положительное число; если флаг знака 1, то число отрицательное. Отрицательные числа практически всегда представлены с использованием метода двоичного дополнения. В этом методе все биты числа (за исключением флага знака) инвертируются, затем к этому числу прибавляется единица, в завершение флагу знака задается значение 1.

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

01111111 11111111

Поскольку это знаковый тип, при задании старшему биту значения 1 число будет интерпретировано как -1 (если использовать метод двоичного дополнения). Но если вы объявляете его тип как ushort, то при задании старшему биту значения 1 число будет интерпретировано как 65535.

Пожалуй, наиболее часто используемым целочисленным типом является int. Переменные типа int часто применяют для управления циклами, для индексации массивов и в различных математических вычислениях. При работе с целочисленными значениями, большими, чем те, которые могут быть представлены типом int, в C# используют типы uint, long, ulong, а при работе с беззнаковым целым - тип uint. Для больших значений со знаком применяют тип long, для еще больших положительных чисел (беззнаковых целых) - тип ulong.

Ниже приведена программа, вычисляющая объем куба (в кубических дюймах), длина стороны которого равна 1 миле. Поскольку это значение довольно большое, для его хранения программа использует переменную типа long.

/*
Программа вычисляет объем куба (в кубических дюймах),
длина стороны которого равна 1 миле.
(Для справки: в одной миле 5280 футов, в одном футе 12 дюймов.
*/

using System;

class Inches {
public static void Main() {
long ci;
long im;

im = 5280 * 12;

ci = im * im * im;

Console.WriteLine("Объем куба с длиной стороны, равной 1 миле, "+
"\nравен "+ ci +" кубических дюймов.");
}
}

Ниже показан результат выполнения этой программы:

Объем куба с длиной стороны, равной 1 миле,
равен 254358061056000 кубических дюймов.

Очевидно, что такой результат не может быть помещен в переменную типа int или uint.

byte и sbyte - наименьшие целочисленные типы. Значение типа byte может находиться в диапазоне от 0 до 255. Переменные типа byte особенно полезны при использовании необработанных двоичных данных, таких как поток байтов данных, сгенерированный каким-либо устройством. Для небольших знаковых целых применяется тип sbyte. Представленная ниже программа использует переменную типа byte для контроля цикла for, в котором вычисляется сумма всех целых чисел, находящихся в диапазоне от 1 до 100.

// Использование типа byte.

using System;

class Use_byte {
public static void Main() {
byte x;
int sum;

sum = 0;
for(x = 1; x <= 100; x++)
sum = sum + x;
Console.WriteLine("Сумма всех целых чисел, находящихся "+
"в диапазоне от 1 до 100, \nравна " + sum);
}
}

Результат выполнения этой программы следующий:

Сумма всех целых чисел, находящихся в диапазоне от 1 до 100, равна 5050

Поскольку в данной программе для управления циклом for используются числа от 1 до 100, находящиеся в диапазоне значений, определенных для типа byte, нет необходимости назначать тип переменной, позволяющий работать с гораздо большими числами.

Для правильного назначения типа переменной и экономии системных ресурсов воспользуйтесь приведенной выше таблицей и выберите диапазон значений типа, которому удовлетворяет значение этой переменной.

Типы данных с плавающей точкой

Как уже говорилось в главе 1, типы данных с плавающей точкой могут представлять числа, имеющие дробные части. В С# существуют два типа данных с плавающей точкой, float и double, представляющие числа с одинарной и двойной точностью соответственно. Для представления данных типа float выделяются 32 бита, что позволяет присваивать переменным значения чисел, находящихся в диапазоне от 1.5E-45 до 3.4Е+38. Для представления данных типа double выделяются 64 бита, что расширяет диапазон используемых чисел до величин из диапазона от 5Е-324 до 1.7Е+308. Чаще всего применяется тип double. Одна из причин этого в том, что многие математические функции в библиотеке классов C# (которой является библиотека .NET Framework) используют значения, имеющие тип double. Например, метод Sqrt(), определенный в стандартном классе System.Math, возвращает значение типа double, являющееся квадратным корнем его аргумента, также имеющего тип double. Ниже в качестве примера приведена программа, в которой для вычисления длины гипотенузы, заданной длинами двух катетов, используется метод Sqrt().

/*
В программе используется теорема Пифагора, позволяющая найти
длину гипотенузы по известным длинам катетов.
*/

using System;

class Hypot {
public static void Main() {
double x, y, z;

x = 3;
y = 4;

z = Math.Sqrt(x*x + y*y);

Обратите внимание на способ вызова метода Sqrt(). Имя метода отделено точкой от имени класса, членом которого он являетсЯ.

Console.WriteLine("Длина гипотенузы равна " + z);
}
}

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

Длина гипотенузы равна 5

Отметим еще одну особенность данной программы. Как уже говорилось, метод Sqrt() является членом класса Math. Обратите внимание на способ вызова метода Sqrt()- имя метода отделено точкой от имени класса, членом которого он является. Похожий способ записи нам уже встречался, когда перед именем метода WriteLine() стояло имя его класса Console. Не все стандартные методы нужно вызывать, указав вначале имя класса, в котором определен данный метод, но некоторые методы требуют именно такого вызова.

Тип decimal

Возможно, наиболее интересным числовым типом данных в C# является тип decimal, предназначенный для использования в денежных вычислениях. В типе decimal для представления значений, находящихся в диапазоне от 1Е-28 до 7.9Е+28, используется 128 битов. Вы, конечно, знаете, что в обычных арифметических вычислениях, производимых над числами с плавающей точкой, неоднократные округления значений приводят к неточному результату. Тип данных decimal устраняет ошибки, возникающие при округлении, и может представлять числа с точностью до 28 десятичных разрядов (а в некоторых случаях и до 29 разрядов). Эта способность представлять десятичные значения без ошибок округления особенно полезна, когда рассчитываются финансы.

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

/*
Программа демонстрирует использование типа decimal
для финансовых вычислений.
*/

using System;

class UseDecimal {
public static void Main() {
decimal balance;
decimal rate;

// Подсчет нового баланса
balance = 1000.10m;
rate = 0.1m;

При вводе значения типа decimal должны заканчиваться символом m или M.

balance = balance * rate + balance;

Console.WriteLine("Новый баланс: $" + balance);
}
}

Эта программа выводит следующий результат:

Новый баланс: $1100.11

Вопрос. Ранее я работал с языками программирования, в которых не было типа данных decimal. Является ли он уникальным и присущим только C#?
Ответ. Да, этот тип данных уникальный; такими языками, как С, С++ или Java, он не поддерживается.

Отметим, что после десятичных констант (то есть констант, имеющих тип decimal) должен следовать суффикс m или M, иначе эти значения будут интерпретированы как константы с плавающей точкой, не совместимые с типом данных decimal. (Далее в этой главе мы подробно рассмотрим, как специфицируются числовые константы.)

Минутный практикум

  • Какие целочисленные типы данных существуют в C#?
  • Назовите два типа данных с плавающей точкой.
  • Почему тип данных decimal так важен для финансовых вычислений?

    Символы

    В отличие от других языков программирования (таких, как С++, в которых для представления символа выделяется 8 битов, что позволяет работать с 255 символами) в C# используется стандартный набор символов Unicode, в котором представлены символы всех языков мира. В C# char - беззнаковый тип, которому для представления данных выделено 16 битов, что позволяет работать со значениями, находящимися в диапазоне от 0 до 65535 (то есть с 65535-ю символами). Стандартный 8-битовый набор символов ASCII является составной частью Unicode и находится в диапазоне от 0 до 127. Таким образом, ASCII-символы остаются действительными в C#.

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

    char ch;
    ch = 'X';

    Для вывода символьного значения используется оператор WriteLine(). Следующая строка выводит значение переменной ch:

    Console.WriteLine("Значение переменной ch - " + ch);

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

    char ch;
    ch = 10; // Данный оператор недействителен.

    Причина этого в том, что значение 10 - целочисленное и не будет автоматически конвертировано в тип char. Следовательно, операнды данной операции присваивания представляют собой несовместимые типы. Если вы попытаетесь скомпилировать этот код, компилятор сообщит об ошибке.

    Далее в этой главе мы расскажем, как можно обойти это ограничение.

    Вопрос. Почему в C# используется Unicode?
    Ответ. В задачи разработчиков C# входило создание такого компьютерного языка, который позволял бы писать программы, предназначенные для использования во всем мире. Поэтому авторы воспользовались стандартным набором символов Unicode, в котором представлены все языки мира. Конечно, использование этого стандарта неэффективно для таких языков, как английский, немецкий, испанский или французский, символы которых могут быть представлены 8 битами, но это цена, которую приходится платить за переносимость программ в глобальном масштабе.

    Булев тип данных

    Для булева типа данных, bool, в С# определены два значения true и false (истина и ложь). Следовательно, переменная типа bool или логическое выражение будут иметь одно из этих двух значений. Более того, не существует способа преобразования значений типа bool в целочисленные значения. Например, значение 1 не преобразуется в значение true, а значение 0 - в значение false.

    Приведем программу, демонстрирующую использование типа данных bool.

    // Программа демонстрирует использование
    // переменной, имеющей тип bool.

    using System;

    class BoolDemo {
    public static void Main() {
    bool b;

    b = false;
    Console.WriteLine("Переменная b имеет значение " + b);
    b = true;
    Console.WriteLine("Переменная b имеет значение " + b);

    // Булева переменная может управлять условным оператором if.
    if(b) Console.WriteLine("Этот оператор будет выполнен.");
    Булева переменная может управлять условным оператором if.

    b = false;
    if(b) Console.WriteLine("Этот оператор не будет выполнен.");
    // Условный оператор возвращает булево значение.
    Console.WriteLine("(10 > 9)-это " + (10 > 9));
    }
    }

    Ниже представлен результат, сгенерированный этой программой:

    Переменная b имеет значение False
    Переменная b имеет значение True
    Этот оператор будет выполнен.
    (10 > 9) - это True

    Обратите внимание на некоторые особенности использования булевой переменной. Во-первых, при выводе значения типа bool с помощью метода WriteLine() на экран выводится слово true или false. Во-вторых, используя переменную типа bool, можно управлять оператором if. Если условием выполнения оператора if является истинность переменной (в данной программе переменной b), то нет необходимости записывать оператор if так:

    if(b == true)...

    достаточно более короткого выражения:

    if(b)

    В-третьих, результатом выполнения оператора сравнения, такого как <, является значение типа bool. Вот почему выражение 10 > 9 выводит на экран значение true. Далее, дополнительная пара скобок, в которые заключено выражение 10 > 9, является необходимой, поскольку оператор + имеет больший приоритет, чем оператор >.

    Минутный практикум

  • Что такое Unicode?
  • Какие значения может иметь переменная типа bool?
  • Каков размер данных типа char в битах?

    Форматирование вывода

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

    Console.WriteLine("Было заказано " + 2 + " книги по $" + 3 +
    "за единицу товара.");

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

    Console.WriteLine("10/3 = " + 10.0/3.0);

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

    10/3 = 3.33333333333333

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

    Для форматирования числовых данных необходимо использовать перегруженный метод WriteLine()(перегрузка методов будет рассматриваться в главе 6), в один из параметров которого можно внедрить форматирующую информацию. Синтаксис этого метода выглядит так:

    WriteLine("форматирующая строка", arg0, arg1, ... , argN);

    В этой версии метода WriteLine()аргументы разделены запятыми, а не знаками плюс. Форматирующая строка состоит из стандартных выводимых без изменения символов и спецификаторов формата. Синтаксис спецификаторов формата обычно выглядит так:

    {argnum, width: fmt}

    Здесь элемент argnum указывает номер аргумента (начиная с ноля), который должен быть выведен на экран в данном месте строки. Элемент width определяет минимальную ширину поля, предоставляемого данному аргументу, а элемент fmt специфицирует используемый формат вывода.

    Когда при выполнении в форматирующей строке встречается спецификатор формата, программа заменяет его соответствующим аргументом, который и выводится на экран. Следовательно, позиция спецификатора формата в форматирующей строке указывает место, где на экране будут выведены соответствующие данные. Элементы width и fmt являются не обязательными. Следовательно, в своей простейшей форме спецификатор формата указывает аргумент, необходимый для вывода на экран. Например, спецификатор {0} указывает на аргумент arg0, спецификатор {1} указывает на аргумент arg1 и так далее.

    Теперь рассмотрим простой пример. Оператор
    Console.WriteLine("В феврале {0} или {1} дней.", 28, 29);
    выведет на экран следующую строку:
    В феврале 28 или 29 дней.

    Как видите, спецификатор {0} заменяется значением 28, а спецификатор {1} - значением 29. Обратите внимание, что аргументы метода WriteLine() разделяются запятыми, а не знаками плюс.

    Следующий пример демонстрирует усовершенствованную версию предыдущего оператора, в которой специфицирована минимальная ширина поля:

    Console.WriteLine("В феврале {0,10} или {1,5} дней.", 28, 29);

    Результат выполнения данного оператора следующий:

    В феврале 28 или 29 дней.

    Если выводимое число меньше заданной ширины поля, то неиспользованные части поля заполняются пробелами. Помните, что ширина поля - это минимальное количество позиций, выделенных для выводимого значения. Если для вывода числа потребуется большее количество позиций, они будут выделены автоматически.

    В предыдущих примерах к самим значениям форматирование не применялось, но безусловно, используемые спецификаторы формата должны контролировать отображение данных. Чаще всего форматируются значения с плавающей точкой и значения, имеющие тип decimal. Одним из простейших способов спецификации формата является написание шаблона, который будет использован методом WriteLine() при форматировании выводимых значений. Шаблон создается следующим образом: в спецификаторе формата указывается номер аргумента, затем после двоеточия при помощи символа # указываются позиции выводимых цифр. Так, после выполнения оператора

    Console.WriteLine("10/3 = {0:#.##}", 10.0/3.0);
    программа выведет отформатированное значение, являющееся результатом деления 10.0 на 3.0,
    10/3 = 3.33

    В этом примере шаблон создается последовательностью символов #.##, с помощью которых методу WriteLine() сообщается о необходимости вывода двух десятичных разрядов. Если программой выводится число, у которого целая часть содержит более одного знака, то во избежание потери данных метод WriteLine() будет выводить более одной цифры слева от запятой.

    В случае необходимости вывода значения в долларах и центах используйте спецификатор формата C. Например,

    decimal balance

    balance = 12323.09m;
    Concole.WriteLine("Текущий баланс равен: {0:C}", balance);

    В результате будет выведено следующее сообщение:

    Текущий баланс равен: $12,323.09

    Проект 2-1. Разговор с Марсом

    @S2 = Mars.cs

    Наименьшее расстояние между Марсом и Землей составляет примерно 34 миллиона миль. Предположим, что на Марсе находится человек, с которым нам необходимо поговорить. Для этого нужно написать программу, которая могла бы вычислить, сколько времени потребуется сигналу на преодоление этого расстояния. Скорость света равна примерно 186000 миль в секунду, следовательно, для вычисления времени задержки сигнала (времени между отправкой и приемом сигнала) необходимо разделить расстояние на скорость света. Значение времени должно быть выведено в секундах и минутах.

    Пошаговая инструкция
    1. Создайте новый файл и назовите его Mars.cs.

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

    double distance;
    double lightspeed;
    double delay;
    double delay_in_min;

    3. Задайте переменным distance и lightspeed начальные значения:

    distance = 34000000; // 34000000 миль
    lightspeed = 186000; // 186000 миль в секунду

    4. Для вычисления времени задержки сигнала разделите значение переменной distance на значение переменной lightspeed. При этом будет получено значение задержки сигнала в секундах. Присвойте это значение переменной delay и выведите результаты следующим образом:

    delay = distance / lightspeed;

    Console.WriteLine("Расстояние между Землей и Марсом сигнал " +
    "пройдет за \n" + delay + " секунд.");

    5. Для получения значения результата в минутах разделите значение результата в секундах, который содержится в переменной delay, на 60; выведите результат на экран, используя следующие строки кода:

    delay_in_min = delay / 60;

    Console.WriteLine("Время задержки сигнала составляет " + delay_in_min
    + " минут.");

    Ниже приведен полный листинг программы Mars.cs:

    /*
    Проект 2-1
    "Разговор с Марсом"
    Назовите этот файл Мars.cs
    */

    using System;

    class Mars {
    public static void Main() {
    double distance;
    double lightspeed;
    double delay;
    double delay_in_min;

    distance = 34000000; // 34000000 миль
    lightspeed = 186000; // 186,000 миль в секунду

    delay = distance / lightspeed;

    Console.WriteLine("Расстояние между Землей и Марсом сигнал " +
    "пройдет за \n" + delay + " секунд.");

    delay_in_min = delay / 60;

    Console.WriteLine("Время задержки сигнала составляет " +
    delay_in_min + " минут.");
    }
    }

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

    Расстояние между Землей и Марсом сигнал пройдет за 182.795698924731 секунд.
    Время задержки сигнала составляет 3.04659498207885 минут.

    7. Конечно, большинство людей в своей работе не сталкиваются с числами, имеющими слишком много десятичных разрядов. Для улучшения читабельности выводимого результата программы замените соответствующие операторы WriteLine(); представленными ниже:

    Console.WriteLine("Расстояние между Землей и Марсом сигнал " +
    "пройдет за \n{0:###.###} секунд.", delay);

    Console.WriteLine("Время задержки сигнала составляет {0:###.###} минут.", delay_in_min);

    8. Повторно скомпилируйте и запустите программу. Теперь на экран будет выведен отформатированный результат вычислений:

    Расстояние между Землей и Марсом сигнал пройдет за
    182.796 секунд.
    Время задержки сигнала составляет 3.047 минут.
    В этом случае на экран выводятся только три десятичных разряда.

    Литералы

    В C# литералами (или константами) называют фиксированные значения, представленные в удобной для чтения форме (например, число 100 является литералом). В основном литералы и способы их использования настолько понятны, что мы без особых объяснений применяли их в той или иной форме во всех предыдущих программах. Теперь же расскажем о них более подробно.

    В C# литералы могут быть значениями любого типа, от которого зависит способ представления каждого литерала. Как уже говорилось, символьные константы заключаются в одинарные кавычки; так, 'a' и '%' являются символьными константами.

    Целочисленные литералы специфицируются как числа без дробной части. Например, 10 и -100 являются целочисленными константами. Константы с плавающей точкой требуют при написании использования десятичной точки, за которой следует дробная часть числа. Например, 11.123 является константой с плавающей точкой. В C# разрешается также использовать экспоненциальное представление чисел с плавающей точкой.

    Поскольку C# - язык со строгим контролем типов, литералы также определяются как принадлежащие к какому-нибудь типу. Чтобы не возникало затруднений при определении типа каждого числового литерала, в C# существуют специальные правила.

    Типом целочисленных литералов является наименьший целочисленный тип, начиная с int, который способен хранить данное число. Следовательно, в зависимости от величины числа типом целочисленных литералов может быть int, uint, long или ulong. Литералы с плавающей точкой имеют тип double.

    Если вас не устраивает тип, определенный в С# по умолчанию, вы можете задать нужный тип литерала явно посредством добавления суффикса. Чтобы указать тип литерала long, добавьте к числу букву l или L (например, число 20 имеет тип int, а 12L - тип long). Для спецификации беззнакового целочисленного литерала добавьте букву u или U (например, число 100 имеет тип int, а 100U - тип uint). Для спецификации длинного беззнакового целого прибавьте к константе суффикс ul или UL (например, литерал 984375UL имеет тип ulong). Чтобы специфицировать литерал типа float, добавьте к константе символ f или F (например, литерал 10.19F имеет тип float). Для спецификации литералов типа decimal добавьте к значению букву m или M (9.95М является литералом, имеющим тип decimal).

    Хотя целочисленные литералы по умолчанию создаются как значения, имеющие тип int, uint, long или ulong, они могут быть присвоены переменным типа byte, sbyte, short или ushort, но только в том случае, если присваиваемое значение вообще может представляться этим типом. Целочисленный литерал всегда может быть присвоен переменной типа long.

    Шестнадцатеричные литералы

    Иногда в программировании вместо десятичной легче применять шестнадцатеричную систему, использующую цифры от 0 до 9 и символы от A до F, которым присвоены значения от 10 до 15 соответственно. Например, шестнадцатеричное число 10 соответствует 16 в десятичной системе исчисления. Учитывая достаточно частое применение шестнадцатеричных чисел, C# позволяет использование целочисленных констант в шестнадцатеричном формате. Шестнадцатеричные литералы должны начинаться с символов 0x (ноль и x). Приведем несколько примеров:

    count = 0xFF; // 255 в десятичной системе
    incr = 0x1a; // 26 в десятичной системе

    Символьные escape-последовательности

    В C# большинство выводимых символьных констант можно заключать в одинарные кавычки, но применение некоторых символов (например, символа возврата каретки) является проблематичным. Кроме того, часть символов (например, символы одинарных или двойных кавычек) имеют в C# специальное значение, поэтому их прямое использование недопустимо. По этим причинам в C# предусмотрены специальные escape-последовательности, которые перечислены в табл. 2.2. Эти последовательности используются вместо символов, которые они представляют.

    Например, таким образом переменной ch присваивается символ табуляции:

    ch = '\t';

    А так переменной ch присваиваются одинарные кавычки:

    ch = '\'';

    Таблица 2.1. Символьные escape-последовательности

    Escape-последовательность Описание
    \a Предупреждение (звонок)
    \b Возврат на одну позицию (backspace)
    \f Переход на новую страницу (formfeed)
    \n Переход на новую строку (linefeed)
    \r Возврат каретки
    \t Горизонтальная табуляция
    \v Вертикальная табуляция
    \0 Ноль
    \' одинарные кавычки
    \" Двойные кавычки
    \\ Символ обратной косой черты

    Строковые литералы

    В C# поддерживается еще один тип литералов - строка, являющаяся набором символов, заключенных в двойные кавычки. Ниже приведен пример строки:

    "Небольшой проверочный текст."

    Вы уже встречали примеры таких строк, применявшиеся во многих операторах WriteLine(); предыдущих программ.

    Кроме обычных символов строковые литералы могут содержать одну или несколько escape-последовательностей. В качестве примера рассмотрим программу, которая использует escape-последовательности \n и \t.

    // В программе демонстрируется использование escape-последовательностей
    // в строковых литералах.

    using System;

    class StrDemo {
    public static void Main() {
    Console.WriteLine("Первая строка.\nВторая строка.");
    Использование escape-последовательности \n для перехода на новую строку.
    Console.WriteLine("A\tB\tC");
    Console.WriteLine("D\tE\tF");
    Использование табуляции для выравнивания выводимых символов.
    }
    }

    Эта программа выводит следующие строки:
    Первая строка.
    Вторая строка.
    A B C
    D E F

    Вопрос. Я знаю, что в языке С++ разрешается специфицировать литералы в восьмеричной системе. Разрешены ли восьмеричные литералы в C#?
    Ответ. Нет, в C# литералы можно специфицировать только в десятичной и шестнадцатеричной форме. В современном программировании восьмеричная форма встречается редко.

    Заметьте, что escape-последовательность \n используется для перехода на новую строку. Чтобы разбить выводимые данные на несколько строк, не обязательно использовать множество операторов WriteLine();, достаточно поместить escape-последовательность \n в пределах длинной строки там, где должна начинаться новая строка.

    В дополнение к вышеизложенному добавим, что в С# имеетcя еще возможность специфицирования копирующегося строкового литерала. Он начинается с символа @, за которым следует строка в кавычках. Содержимое строкового литерала, взятого в кавычки, принимается без модификации и может охватывать две и более строки. Следовательно, в этом случае можно использовать символы перехода на новую строку, табуляции и так далее без применения escape-последовательностей. Единственное исключение состоит в том, что при необходимости получения в выводимых данных символа двойных кавычек (") их нужно ввести дважды подряд (""). Следующая программа демонстрирует использование копирующихся строковых литералов.

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

    using System;

    class Verbatim {
    public static void Main() {
    Console.WriteLine(@"Это копирующийся строковый литерал,

    Этот копирующийся строковый литерал содержит вложенные символы перехода на новую строку.

    который охватывает
    несколько строк.
    ");
    Console.WriteLine(@"Здесь также используется табуляция:
    В этом копирующемся строковом литерале содержатся также символы табуляции.
    1 2 3 4
    5 6 7 8
    ");
    Console.WriteLine(@"Программисты говорят: ""C# - интересный язык.""");
    }
    }

    Результат выполнения этой программы выглядит следующим образом:

    Это копирующийся строковый литерал,
    который охватывает
    несколько строк.

    Здесь также используется табуляция:
    1 2 3 4
    5 6 7 8

    Программисты говорят: "C# - интересный язык."

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

    Минутный практикум

  • Каков тип литерала 10? Каков тип литерала 10.0?
  • Специфицируйте значение 100 как long. Специфицируйте значение 100 как uint.
  • Что такое @"testing"?

    Вопрос. Является ли символьным литералом строка, содержащая одиночный символ (например, "k" и 'k')?
    Ответ. Нет. Не путайте строки с символами. Символьный литерал представляет единичный символ типа char. Строка, содержащая только одну букву, все равно остается строкой. Хотя строки состоят из символов, они имеют другой тип.

    Переменные и их инициализация

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

    type var-name;
    где type - это тип переменной, а var-name - ее имя. Вы можете объявлять переменные любого действительного типа, включая рассмотренные выше простые типы. При создании переменной создается экземпляр ее типа, следовательно, возможности переменной определяются ее типом. Например, переменная типа bool не может использоваться для хранения значений с плавающей точкой. Более того, при объявлении переменной ее тип фиксируется и изменять его нельзя. Например, переменная типа int не может превратиться в переменную типа char.

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

    В C# определено несколько различных типов переменных. Переменные, которые использовались в наших программах до этого момента, называются локальными переменными, поскольку они объявлялись в области видимости метода.

    Инициализация переменной

    Прежде чем использовать переменную, вы должны присвоить ей значение. Сделать это можно двумя способами. Можно присвоить значение переменной в отдельном операторе после ее объявления. А можно сделать это в одном операторе, затем объявить тип переменной и присвоить ей значение, для чего ввести после имени переменной символ знака равенства (оператор присваивания), а за ним - присваиваемое значение. Синтаксис оператора инициализации показан ниже.

    type var = value;

    Здесь value - это значение, которое присваивается переменной во время ее создания. Значение должно быть совместимо с указанным типом.

    Приведем несколько примеров объявления переменных с одновременным присваиванием им значений:

    int count = 10; // переменной count задается начальное значение 10
    char ch = 'X' // переменной ch присваивается символ X
    float f = 1.2F; // переменной f присваивается значение 1.2

    При объявлении двух и более переменных одного типа можно разделить переменные в списке запятыми и присвоить одной или нескольким из них начальное значение. Например:

    int a, b = 8, c = 19, d; // переменным b и c присвоено начальное значение

    В этом случае инициализированы только переменные b и c.

    Динамическая инициализация В предыдущих примерах переменным присваивались только конкретные числовые значения (то есть значения, которые не были получены в результате вычисления какого-либо выражения). Но в C# можно инициализировать переменные и динамически, используя любое действительное выражение. Например, ниже приведена короткая программа, которая вычисляет объем цилиндра по заданному радиусу основания и высоте:

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

    using System;

    class DynInit {
    public static void Main() {
    double radius = 4, height = 5;

    // Динамически инициализируется переменная volume
    double volume = 3.1416 * radius * radius * height;
    Переменная volume инициализируется динамически во время выполнения программы.

    Console.WriteLine("Объем цилиндра равен: " + volume);
    }
    }

    В этой программе объявляются три локальные переменные -radius, height и volume. Первые две, radius и height, инициализированы конкретными значениями. Переменная volume инициализируется динамически - ей присваивается значение, соответствующее объему цилиндра. Ключевым моментом является то, что в выражении инициализации может быть использован любой элемент, который уже был до этого объявлен (то есть действительный на время инициализации данной переменной). Таким элементом может быть метод, другая переменная или литерал.

    Область видимости и время жизни переменных

    Все переменные, которые мы использовали до этого момента, объявлялись в начале метода Main(). Но в C# можно объявлять локальные переменные и внутри блока. Как уже говорилось в главе 1, блок начинается открывающейся фигурной скобкой и заканчивается закрывающей фигурной скобкой. Он определяет область видимости, которая зависит от того, имеет ли данный блок вложенные блоки. Каждый раз, создавая блок, вы создаете новую область видимости, определяющую время жизни объявляемых переменных.

    Наиболее важными областями видимости в C# являются те, которые определяются классом и методом. На данном этапе мы рассмотрим только область видимости, определяемую методом.

    Область видимости, определяемая методом, начинается с открывающей фигурной скобки. Однако если этот метод имеет параметры, они также включаются в область видимости метода. Общее правило состоит в том, что объявленная внутри области видимости переменная является невидимой (то есть недоступной) для кода, который определен за этой областью. Следовательно, при объявлении переменной в пределах ее области видимости вы локализуете эту переменную и защищаете ее от неразрешенного доступа и модификации. Можно сказать, что правила, касающиеся области видимости, обеспечивают фундамент для инкапсуляции.

    Область видимости может быть вложенной. Например, каждый раз, создавая блок кода, вы создаете новую вложенную область видимости, и при этом внешняя область становится видимой для вложенной области. Это означает, что объекты, объявленные во внешней области видимости, будут видимы для кода из внутренней области видимости, но объекты, объявленные во внутренней области видимости, не будут видимы для кода внешней области видимости.

    Чтобы понять это правило, рассмотрим следующую программу:

    // В программе демонстрируется зависимость возможности доступа к
    // переменной от области видимости, в которой она была объявлена.

    using System;

    class ScopeDemo {
    public static void Main() {
    int x; // Переменная x известна всему коду
    // в пределах метода Main().

    x = 10;
    if(x == 10) { //Создается новая область видимости.
    int y = 20; // Эта переменная будет видна только в рамках // этого блока.

    // В данном блоке видны обе переменные, x и y.
    Console.WriteLine("Видны x и y: " + x + " " + y);
    x = y * 2;
    }
    // y = 100; // Ошибка! Переменная y здесь не видна!
    y - это переменная, объявленная во вложенной области видимости, поэтому здесь она не видна.

    // Выполняется доступ к переменной x, которая была объявлена
    // в этой области видимости.
    Console.WriteLine("Значение переменной x равно: " + x);
    }
    }

    Как указано в комментариях, объявленная в методе Main() переменная x доступна всему коду в пределах этого метода. Переменная y объявляется в рамках блока if. Поскольку блок определяет область видимости, переменная y является видимой только в пределах самого блока. Вот почему использование переменной y в строке y = 100; вне своего блока является ошибкой, о чем и говорится в комментарии. Если вы удалите начальный символ комментария, произойдет ошибка компилирования, так как переменная y невидима за пределами своего блока. В рамках блока if может использоваться переменная x, поскольку в блоке (вложенной области видимости) код имеет доступ к переменным, объявленным во внешней области видимости.

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

    Обратите внимание на еще один важный момент: переменные создаются при их объявлении в какой-либо области видимости (при входе в данную область) и уничтожаются при выходе из этой области. Это означает, что переменная не будет сохранять свое значение при выходе из своей области видимости, то есть переменная, объявленная в пределах метода, не будет хранить свои значения между вызовами этого метода. Также переменная, объявленная в рамках блока, будет терять свое значение при выходе из блока. Следовательно, время жизни переменной ограничено ее областью видимости.

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

    // В программе демонстрируется использование правила,
    // определяющего время жизни переменной.

    using System;

    class VarInitDemo {
    public static void Main() {
    int x;

    for(x = 0; x < 3; x++) {
    int y = -1; // Переменная y инициализируется каждый раз
    // при входе в блок.
    Console.WriteLine("Значение переменной y равно: " + y);
    // Всегда выводится значение -1.
    y = 100;
    Console.WriteLine("Теперь значение переменной y равно: " + y);
    }
    }
    }

    Ниже показан результат выполнения этой программы:

    Значение переменной y равно: -1
    Теперь значение переменной y равно: 100
    Значение переменной y равно: -1
    Теперь значение переменной y равно: 100
    Значение переменной y равно: -1
    Теперь значение переменной y равно: 100

    Как видите, переменная y инициализируется значением -1 каждый раз при входе во внутренний цикл for, затем ей присваивается значение 100, которое утрачивается при завершении цикла.

    Заметим, что между языками C# и С есть отличие в работе с областями видимости.

    Несмотря на то, что блоки могут быть вложенными, переменная, объявленная во внутренней области видимости, не может иметь такое же имя, как переменная, объявленная во внешней области видимости. Следующая программа, в которой делается попытка объявить две переменные с одинаковыми именами, не будет скомпилирована:

    /*
    В этой программе делается попытка объявить во внутренней области
    видимости переменную с тем же именем, что и у переменной,
    объявленной во внешней области видимости.

    *** Эта программа не будет скомпилирована. ***
    */

    using System;

    class NestVar {
    public static void Main() {
    int count;

    Переменную count объявлять нельзя, поскольку переменнаё с таким именем уже объявлена в методе Main().

    for(count = 0; count < 10; count = count+1) {
    Console.WriteLine("Счет проходов цикла: " + count);

    int count; // Это объявление недействительно!!!
    for(count = 0; count < 2; count++)
    Console.WriteLine("В программе есть ошибка!");
    }
    }
    }

    Если у вас есть опыт программирования на С/С++, то вам известно, что в этих языках нет ограничений для имен переменных, объявляемых во внутренней области видимости. То есть в С/С++ объявление еще одной переменной count в цикле for является допустимым, но при этом данное объявление скроет внешнюю переменную. Чтобы скрытие переменной не приводило к ошибкам программирования, разработчики C# запретили такое действие.

    Минутный практикум

  • Что определяет область видимости?
  • Где в блоке могут объявляться переменные?
  • Когда в блоке создается переменная? Когда она уничтожается?

    Операторы

    В C# предусмотрен большой выбор операторов. Оператором является символ, сообщающий компилятору о необходимости выполнения специфической математической или логической операции. В C# существуют четыре основных класса операторов (арифметические, побитовые, логические и операторы сравнения), а также несколько дополнительных операторов для обработки некоторых специальных ситуаций. В этой главе будут рассмотрены арифметические, логические операторы и операторы сравнения. Кроме того, мы расскажем об операторе присваивания. Побитовые и другие специальные операторы будут рассмотрены позже.

    Арифметические операторы
    В C# определены следующие арифметические операторы.

    Оператор Значение
    + Сложение
    - Вычитание (также унарный минус)
    * Умножение
    / Деление
    % Взятие по модулю (остаток от деления)
    ++ Инкремент
    -- Декремент

    Арифметические операторы сложения (+), вычитания (-), умножения (*) и деления (/) работают в C# так же, как в любом другом языке программирования, они могут применяться к любым встроенным числовым типам данных.

    Хотя действие арифметических операторов вам хорошо известно, некоторые специальные ситуации требуют пояснения. При применении оператора деления к целочисленным данным любой остаток будет отброшен (например, в целочисленном делении 10/3 будет равно 3). Остаток этого деления можно получить при использовании оператора взятия по модулю (%). Он работает в C# так же, как в других языках, - получает остаток от целочисленного деления (так, 10 % 3 будет равно 1). В C# оператор взятия по модулю может применяться как к целочисленным данным, так и к значениям с плавающей точкой (10.0 % 3.0 будет также равно 1), в отличие от С/С++, где операцию взятия по модулю разрешено производить только с целочисленными типами данных.

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

    // В программе демонстрируется использование оператора взятия по модулю.

    using System;

    class ModDemo {
    public static void Main() {
    int iresult, irem;
    double dresult, drem;

    iresult = 10 / 3;
    irem = 10 % 3;
    dresult = 10.0 / 3.0;
    drem = 10.0 % 3.0;

    Console.WriteLine("Результат целочисленного деления 10/3 равен: "
    +iresult + "\nостаток равен: " + irem);
    Console.WriteLine("Результат деления чисел, имеющих тип double, - "+
    " 10.0/3.0 равен: \n" + dresult + "\n остаток равен: " + drem);
    }
    }

    Программа выводит следующий результат:

    Результат целочисленного деления 10/3 равен: 3
    остаток равен: 1
    Результат деления чисел, имеющих тип double, - 10.0/3.0 равен: 3.33333333333333 остаток равен: 1

    Как видите, оператор взятия по модулю возвращает значение 1 как при работе с целочисленными операндами, так и при работе с данными, имеющими тип double.

    Операторы инкремента и декремента Об операторах инкремента (++)и декремента (--) мы уже упоминали в главе 1. У этих операторов есть некоторые специальные свойства, которые делают их очень полезными при задании алгоритма изменения переменной цикла (например, в цикле for). Рассмотрим, как функционируют операторы инкремента и декремента.

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

    x = x + 1;
    выполняет ту же самую последовательность действий, что и оператор
    x++;
    а оператор
    x = x - 1;
    выполняет ту же самую последовательность действий, что и оператор
    x--;
    Оба эти оператора могут указываться либо до операнда (как префикс), либо после (как постфикс). Например, оператор
    x = x +1;
    можно записать так:
    ++x; // оператор указывается до операнда
    или так:
    x++; // оператор указывается после операнда

    В этом примере оператор инкремента указан двумя способами, между которыми нет никакой смысловой разницы. Но когда оператор инкремента или декремента используется как часть большего выражения, то от расположения оператора по отношению к операнду зависит алгоритм выполнения блока кода. Если оператор указывается перед операндом, C# увеличивает значение переменной (операнда) до того, как эта переменная будет использована оставшейся частью выражения. Если оператор указывается после операнда, то C# увеличивает значение переменной после того, как оставшаяся часть выражения использует эту переменную в своих операциях. Рассмотрим следующий пример:

    x = 10;
    y = ++x;
    В этом случае переменной y будет присвоено значение 11.
    Но если код будет написан так:
    x = 10;
    y = x++;
    то вначале переменной y будет присвоено значение 10, а затем значение переменной х будет увеличено на единицу. В обоих случаях значение переменной x будет увеличено до 11, различие состоит только во времени, когда это произойдет.

    Благодаря названным свойствам операторы инкремента и декремента широко используются при формировании алгоритмов.

    Операторы сравнения и логические операторы В терминах оператор сравнения и логический оператор слово сравнение означает оценку одного значения по сравнению с другим, а слово логический - способ, которым можно связать значения true и false (истина и ложь) в выражении. Поскольку результатом выполнения операторов сравнения являются булевы значения, эти операторы часто работают совместно с логическими операторами. Поэтому их можно рассматривать вместе.

    Ниже представлены операторы сравнения.

    Оператор Значение
    == Равно
    != Не равно
    > Больше чем
    < Меньше чем
    >= Больше или равно
    <= Меньше или равно

    Далее перечислены логические операторы.

    Оператор Значение
    & AND (И)
    | OR (ИЛИ)
    ^ XOR (исключающее ИЛИ)
    && Short-circuit AND (быстрый оператор И)
    || Short-circuit OR (быстрый оператор ИЛИ)
    ! NOT (НЕ)

    Результатом выполнения операторов сравнения и логических операторов являются значения типа bool.

    В C# операторы == и!= могут применяться ко всем объектам для их сравнения на предмет равенства или неравенства. Операторы сравнения <, >, <=, >= применимы только к перечисляемым типам данных, которые упорядочены в своей структуре (например, упорядоченная структура чисел 1, 2, 3 и так далее или упорядоченные символы букв алфавита). Следовательно, все операторы сравнения могут применяться ко всем числовым типам данных. Однако значения типа bool могут сравниваться только на предмет равенства или неравенства, поскольку значения true и false не упорядочены. Например, выражение true > false в языке C# не имеет смысла.

    Для логических операторов операнды должны иметь тип bool. Результатом логических операций также являются значения, имеющие тип bool. Логические операторы &, |, ^ и! поддерживают базовые логические операции AND, OR, XOR и NOT соответственно, результаты выполнения которых соответствуют значениям, приведенным в следующей таблице истинности.

    p q p & q p q p ^ q
    false false false false false true
    true false false true true false
    false true false true true true
    true true true true false false

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

    Рассмотрим программу, в которой демонстрируется несколько операторов сравнения и логических операторов:

    // В программе демонстрируется использование логических операторов
    // и операторов сравнения.

    using System;

    class RelLogOps {
    public static void Main() {
    int i, j;
    bool b1, b2;

    i = 10;
    j = 11;
    if(i < j) Console.WriteLine("i < j");
    if(i <= j) Console.WriteLine("i <= j");
    if(i != j) Console.WriteLine("i != j");
    if(i == j) Console.WriteLine("Эта строка не будет выведена.");
    if(i >= j) Console.WriteLine("Эта строка не будет выведена.");
    if(i > j) Console.WriteLine("Эта строка не будет выведена.");

    b1 = true;
    b2 = false;
    if(b1 & b2) Console.WriteLine("Эта строка не будет выведена.");
    if(!(b1 & b2)) Console.WriteLine("Результатом вычисления"+
    " выражения !(b1 & b2) будет значение true.");
    if(b1 | b2) Console.WriteLine("Результатом вычисления"+
    " выражения b1 | b2 будет значение true.");
    if(b1 ^ b2) Console.WriteLine("Результатом вычисления"+
    " выражения b1 ^ b2 будет значение true.");
    }
    }

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

    i < j
    i <= j
    i != j

    Результатом вычисления выражения !(b1 & b2) будет значение true.

    Результатом вычисления выражения b1 | b2 будет значение true.

    Результатом вычисления выражения b1 ^ b2 будет значение true.

    Быстрые логические операторы

    В С# предусмотрены специальные быстрые версии логических операторов AND и OR, при использовании которых программа будет выполняться быстрее. Рассмотрим, как они работают. Если при выполнении оператора AND первый операнд имеет значение false, результатом будет false, независимо от значения второго операнда. Если при выполнении оператора OR первый операнд имеет значение true, результатом будет значение true, независимо от значения второго операнда. Следовательно, в этих случаях нет необходимости оценивать второй операнд, и соответственно программа будет выполняться быстрее.

    Быстрый оператор AND обозначается символом &&, а быстрый оператор OR - символом ||. Единственная разница между обычной и быстрой версиями операторов состоит в том, что обычные операторы всегда оценивают и первый, и второй операнды, а быстрые операторы оценивают второй операнд только при необходимости.

    Ниже приведена программа, в которой демонстрируется использование быстрого оператора AND. Программа определяет, является ли значение переменной d кратным значению переменной n. Это осуществляется с помощью оператора взятия по модулю. Если остаток деления n/d равен нолю, то значение переменной d является делителем значения переменной n. Однако поскольку операция взятия по модулю предусматривает выполнение деления, в программе используется быстрый оператор AND для предотвращения деления на ноль.

    // В программе демонстрируется использование быстрого оператора AND.

    using System;

    class SCops {
    public static void Main() {
    int n, d;

    n = 10;
    d = 2;
    if(d != 0 && (n % d) == 0)
    Console.WriteLine(d + " является делителем числа " + n);

    d = 0; // Теперь присваиваем переменной d значение 0.

    // Поскольку переменная d имеет значение 0, второй операнд
    // не оценивается.
    if(d != 0 && (n % d) == 0)
    Console.WriteLine(d + " является делителем числа " + n);
    Применение быстрого оператора предотвращает деление на ноль.

    /* Теперь в выражении условия оператора if будет использован
    обычный оператор AND, что приведет к делению на ноль.
    */
    if(d != 0 & (n % d) == 0)
    Оцениваются оба операнда, что приводит к делению на ноль.
    Console.WriteLine(d + " является делителем числа " + n);
    }
    }

    Для предотвращения деления на ноль вначале оператор if проверяет, не равно ли нолю значение переменной d. Если равно, то быстрый оператор AND останавливает дальнейшее выполнение оператора и деление по модулю не выполняется. Поскольку в программе в первом случае значение переменной d равно 2, выполняется операция взятия по модулю. Во втором случае переменной d присваивается заведомо неприемлемое значение 0 и операция взятия по модулю не выполняется, в результате чего предотвращается деление на ноль. Наконец, в третьем случае используется обычный оператор AND. При этом оцениваются оба операнда, что приводит к ошибке выполнения программы при попытке деления на ноль.

    Проект 2-2. Вывод таблицы истинности логических операторов

    @S2 = LogicOpTable.cs

    В этом проекте рассматривается создание программы, которая выводит на экран таблицу истинности логических операторов. В программе были использованы элементы C# - одна из escape-последовательностей и логические операторы, описанные в этой главе. В проекте также демонстрируется различие в приоритетности между арифметическим оператором + и логическими операторами.

    Пошаговая инструкция
    1. Создайте новый файл и назовите его LogicalOpTable.cs.

    2. Для выравнивания столбцов таблицы используйте escape-последовательность \t, которая вставляет символы табуляции в каждую строку вывода. Например, представленный ниже оператор WriteLine() выводит заголовок для таблицы:
    Console.WriteLine("P\tQ\tAND\tOR\tXOR\tNOT");

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

    4. Ниже представлен полный код программы, который нужно ввести в файл LogicalOpTable.cs.

    /*
    Проект 2-2
    Выводит на печать таблицу истинности логических операторов.
    */

    using System;

    class LogicalOpTable {
    public static void Main() {

    bool p, q;

    Console.WriteLine("P\tQ\tAND\tOR\tXOR\tNOT");
    p = true; q = true;
    Console.Write(p + "\t" + q +"\t");
    Console.Write((p&q) + "\t" + (p|q) + "\t");
    Console.WriteLine((p^q) + "\t" + (!p));

    p = true; q = false;
    Console.Write(p + "\t" + q +"\t");
    Console.Write((p&q) + "\t" + (p|q) + "\t");
    Console.WriteLine((p^q) + "\t" + (!p));

    p = false; q = true;
    Console.Write(p + "\t" + q +"\t");
    Console.Write((p&q) + "\t" + (p|q) + "\t");
    Console.WriteLine((p^q) + "\t" + (!p));

    p = false; q = false;
    Console.Write(p + "\t" + q +"\t");
    Console.Write((p&q) + "\t" + (p|q) + "\t");
    Console.WriteLine((p^q) + "\t" + (!p));
    }
    }

    5. Скомпилируйте и запустите программу. В результате ее выполнения будет выведена следующая таблица:

    P Q AND OR XOR NOT
    true true true true false false
    true false false true true false
    false true false true true true
    false false false false false true

    6. Обратите внимание, что логические операторы, указываемые в методах Write() и WriteLine() в качестве параметров, заключены в круглые скобки. Это необходимо, поскольку в C# оператор + имеет более высокий приоритет, чем логические операторы.

    7. Попытайтесь самостоятельно модифицировать программу таким образом, чтобы на экран выводились символы 1 и 0 вместо слов true и false. Возможно, это задача окажется достаточно сложной!

    Вопрос. Зачем в С# нужны обычные операторы AND и OR, если в некоторых случаях быстрые операторы являются более эффективными?
    Ответ. Иногда в операторах AND и OR требуется оценить оба операнда.

    Это иллюстрируется следующей программой.

    // Еще одна программа, в которой демонстрируется разница в
    // применении быстрого и обычного операторов AND.

    using System;

    class SideEffects {
    public static void Main() {
    int i;
    i = 0;

    /* В выражении условия оператора if к переменной i будет
    применена операция инкремента, даже если условие окажется ложным.
    */
    if(false & (++i < 100))
    Console.WriteLine("Эта строка не будет выведена.");
    // Следующим оператором будет выведено
    // значение переменной i, равное 1.
    Console.WriteLine("Если оператор инкремента будет выполнен,"+
    " переменная i примет значение: " + i);

    /* В этом случае к переменной i не будет применена операция
    инкремента, поскольку используется быстрый оператор AND.
    */
    if(false && (++i < 100))
    Console.WriteLine("Эта строка не будет выведена.");
    // Следующим оператором вновь будет выведено
    // значение переменной i, равное 1.
    Console.WriteLine("Если оператор инкремента будет выполнен,"+
    " переменная i примет значение: " + i);
    }
    }

    Комментарии указывают, что в выражении условия первого оператора if к переменной i будет применена операция инкремента независимо от истинности выражения. Но при использовании быстрого оператора значение переменной i не будет увеличено на единицу, поскольку первый операнд имеет значение false. То есть этот пример показывает, что если в коде необходимо выполнить проверку операнда, находящегося справа от оператора AND, вы должны использовать обычную форму этого оператора (то же касается оператора OR).

    Минутный практикум

  • Какие действия производит оператор взятия по модулю (%)? К каким типам данных он может быть применен?
  • Какие типы данных могут использоваться в качестве операндов логических операторов?
  • Всегда ли быстрый оператор оценивает оба своих операнда?

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

    Мы уже неоднократно использовали оператор присваивания в примерах программ этой книги, а теперь рассмотрим его более подробно. Оператор присваивания указывается как одинарный знак равенства (=). В С# он работает практически так же, как в любом другом языке программирования. Оператор присваивания имеет следующий синтаксис:

    var = expression;

    Тип переменной должен быть совместим с типом выражения.

    Оператор присваивания имеет одно очень интересное свойство, с которым вы, возможно, еще не знакомы, - он позволяет создавать "цепочку присваиваний". В качестве примера рассмотрим фрагмент кода:

    int x, y, z;
    x = y = z = 100; // Переменным x, y, z присваивается значение 100.

    В приведенном выше фрагменте с помощью одного оператора переменным x, y и z присваивается значение 100. Такая последовательность переменных и операторов допускается, поскольку оператор = присваивает переменной, находящейся слева от него, значение выражения, находящегося справа. Следовательно, выражение z = 100 будет иметь значение 100, которое присваивается переменной y, а затем - переменной x. Используя "цепочку присваиваний", можно одним значением легко инициализировать группу переменных.

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

    В C#, как и в C/C++, имеются составные операторы присваивания, в которых арифметические операторы совмещены с операторами присваивания. Рассмотрим применение составного оператора присваивания на примере. Выражение

    x = x + 10;
    может быть записано с использованием составного оператора присваивания:
    x += 10;

    Пара операторов += указывает, что компилятор должен присвоить переменной x значение выражения x + 10.

    Приведем еще один пример. Выражение
    x = x - 100;
    можно записать как
    x -= 100;

    Оба оператора присваивают переменной x значение x - 100.

    Для всех логических операторов (то есть операторов, требующих два операнда) существуют составные операторы присваивания. Синтаксис этих операторов следующий:

    var op = expression;

    Таким образом, в C# имеются следующие составные операторы присваивания:

    += -= *= /=
    %= &= |= ^=

    Составные операторы присваивания в сравнении с их "обычными" аналогами имеют два преимущества. Во-первых, они более компактны, а во-вторых, их использование позволяет ускорить компилирование кода (так как операнд оценивается только один раз).

    По этим причинам составные операторы присваивания часто используются в профессионально написанных C#-программах.

    Преобразование типа в операциях присваивания

    В программировании часто применяется присваивание значения переменной одного типа переменной другого типа. Например, вы можете присвоить значение переменной типа int переменной типа float, как показано ниже:

    int i;
    float f;

    i = 10;
    f = i; // присвоение значения типа int переменной типа float

    Если в операции присваивании используются совместимые типы данных, то тип переменной, находящейся справа от оператора, автоматически преобразуется в тип переменной, находящейся слева от него. Следовательно, в предыдущем примере тип переменной i преобразуется в тип float, а затем значение переменной i присваивается переменной f. Но поскольку C# является языком со строгим контролем типов, то не все его типы данных являются совместимыми, и следовательно, не все типы преобразований разрешены. Например, типы данных bool и int несовместимы.

    При присваивании одного типа данных переменной другого типа автоматическое преобразование типа происходит, если
    два типа данных совместимы;
    конечный тип (слева) больше исходного типа (справа).

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

    Для выполнения расширяющих преобразований все числовые типы, включая целочисленные данные и данные с плавающей точкой, являются совместимыми. Например, в следующей программе выполняемое преобразование действительно, поскольку преобразование данных типа long в double является расширяющим и выполняется автоматически.

    // В программе демонстрируется автоматическое преобразование типа
    // значения из long в double.

    using System;

    class LtoD {
    public static void Main() {
    long L;
    double D;

    L = 100123285L;
    D = L;

    Console.WriteLine("L и D: " + L + " " + D);
    }
    }

    Хотя существует автоматическое преобразование типа переменной из long в double, обратное автоматическое преобразование осуществить нельзя, поскольку оно не будет расширяющим. Следовательно, такая версия предыдущей программы будет недействительной:

    // *** Эта программа не будет скомпилирована. ***

    using System;

    class LtoD {
    public static void Main() {
    long L;
    double D;

    D = 100123285.0;
    L = D; // Такое преобразование недействительно!!!
    Console.WriteLine("L и D: " + L + " " + D);
    }
    }

    В дополнение к уже указанным ограничениям необходимо добавить, что не существует автоматического преобразования между типами decimal и float или double либо преобразования переменных числового типа в переменные типа char или bool. Переменные типов char и bool также несовместимы друг с другом.

    Выполнение операции приведения типа между несовместимыми типами данных

    Автоматические преобразования очень удобны, но они не отвечают всем потребностям программирования, поскольку в них допускается расширяющее преобразование только между совместимыми типами данных. Во всех других случаях приходится применять операцию приведения типа. Приведение типа - это инструкция компилятору для преобразования одного типа данных в другой. Такое преобразование типов данных является явным. Операция приведения типа имеет следующий синтаксис:

    (target-type) expression

    Конечный тип (target-type) указывает, к какому типу должно быть приведено выражение. Так, если необходимо преобразовать выражение x/y к типу int, следует написать:

    double x, y;
    // ...
    (int) (x/y)

    В этом примере приведение типа преобразует результат вычисления выражения к типу int, хотя переменные x и y имеют тип double. Круглые скобки, в которые заключено выражение x/y, являются обязательными. В противном случае приведение к типу int будет применено только для переменной x, а не для значения результата деления. Здесь применяется операция приведения типа, поскольку автоматическое преобразование данных типа double в тип int не может быть выполнено.

    Когда в результате приведения типа происходит сужающее преобразование, информация может быть утеряна. Например, если при приведении данных типа long в int значение переменной, имеющей тип long, выйдет за диапазон значений, допустимых для типа int, то биты старшего разряда будут удалены, следовательно, информация будет утеряна. Если значение с плавающей точкой приводится к целочисленному типу, дробные компоненты утрачиваются, поскольку при таком преобразовании они отбрасываются. Например, если значение 1.23 преобразуется в целочисленный тип, результирующим будет значение 1, а дробная часть 0.23 будет утеряна.

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

    // В программе демонстрируется использование операции приведения типа.

    using System;

    class CastDemo {
    public static void Main() {
    double x, y;
    byte b;
    int i;
    char ch;

    x = 10.0;
    y = 3.0;

    i = (int) (x/y); // Выражение, имеющее тип double, приводится
    // к типу int.
    Console.WriteLine("Целая часть выражения x/y равна: " + i);

    i = 100;
    b = (byte) i;
    Console.WriteLine("Значение переменной b равно: " + b);

    i = 257;
    b = (byte) i;
    Console.WriteLine("Значение переменной b равно: " + b);

    b = 88; // Значение ASCII-кода для символа X (англ).
    ch = (char) b;
    Console.WriteLine("ch: " + ch);
    }
    }

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

    Целая часть выражения x/y равна: 3
    Значение переменной b равно: 100
    Значение переменной b равно: 1
    ch: X

    В этой программе приведение результата вычисления выражения (x / y) к типу int приводит к отбрасыванию дробной части и потере информации. Затем, когда переменной b присваивается значение 100, информация не теряется, поскольку это значение находится в диапазоне допустимых значений, определенных для типа byte. Но при попытке присвоить переменной b значение 257 происходит потеря информации, потому что число 257 превышает максимально допустимое значение, определенное для типа byte. И наконец, при присваивании значения типа byte переменной типа char информация не теряется, поскольку используется приведение типа.

    Минутный практикум

  • Что такое операция приведения типа?
  • Можно ли без выполнения операции приведения типа присвоить значение типа short переменной типа int? А значение типа byte - переменной типа char?
  • Запишите оператор x = x + 23; другим способом.

    Приоритетность операторов

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

    Таблица 2.3. Приоритетность операторов в C#

    () [] . ++(постфикс) --(постфикс) checked new sizeof typeof unchecked
    ! ~ (cast) +(унарный) -(унарный) ++(префикс) --(префикс)
    * / %
    + -
    << >>
    < > <= >= is
    == !=
    &
    ^
    |
    &&
    ||
    ?:
    = op=

    Выражения

    Операторы, переменные и литералы являются компонентами выражений. Выражением в C# является действительная комбинация этих компонентов. Если вы уже занимались программированием (или хотя бы изучали алгебру), то, вероятно, имеете представление о синтаксисе выражений. Однако при работе с ними нужно учитывать несколько важных моментов, о которых мы сейчас поговорим.

    Преобразование типов в выражениях
    В пределах выражения существует возможность совмещения двух и более различных типов данных при условии, что они являются совместимыми. Например, вы можете совмещать в выражении данные типа short и long, поскольку оба эти типа являются числовыми. Если в выражении совмещаются различные типы данных, они преобразуются в один и тот же тип на основе алгоритма пошагового преобразования (то есть в соответствии с приоритетностью выполнения операций).

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

    Если один операнд имеет тип decimal, то второй операнд автоматически преобразуется к типу decimal (за исключением случаев, когда он имеет тип float или double; в этом случае произойдет ошибка).

    Если один из операндов имеет тип double, второй операнд автоматически преобразуется к типу double.

    Если один операнд имеет тип float, второй операнд автоматически преобразуется к типу float.

    Если один операнд имеет тип ulong, второй операнд автоматически преобразуется к типу ulong (за исключением случаев, когда он имеет тип sbyte, short, int или long; в этих случаях произойдет ошибка).

    Если один операнд имеет тип long, второй операнд автоматически преобразуется к типу long.

    Если один операнд имеет тип uint, а второй операнд - тип sbyte, short или int, то оба операнда автоматически преобразуются к типу long.

    Если один операнд имеет тип uint, второй операнд автоматически преобразуется к типу uint.

    Если ни одно из вышеуказанных правил не применялось, оба операнда преобразуются к типу int.

    Обратите внимание на некоторые моменты, касающиеся правил автоматического преобразования типов. Не все типы данных могут совмещаться в выражениях (в частности, невозможно автоматическое преобразование данных типа float или double в тип decimal и невозможно совмещение данных типа ulong с любым другим типом знаковых целочисленных данных). Совмещение этих типов требует использования операции явного приведения типа.

    Внимательно прочитайте последнее правило, в котором говорится, что если ни одно из вышеперечисленных правил не применялось, то все операнды преобразуются к типу int. Следовательно, все значения, имеющие тип char, sbyte, byte, ushort и short, в выражении преобразуются к типу int для выполнения вычислений. Такая процедура называется автоматическим преобразованием к целочисленному типу. Это также означает, что результат всех математических операций будет иметь тип, которому для хранения значения выделено не меньше битов, чем типу int.

    Важно понимать, что автоматическое преобразование типов применяется к значениям (операндам) только тогда, когда выражение вычисляется. Например, если внутри выражения значение переменной типа byte преобразуется к типу int, то за пределами выражения переменная сохраняет тип byte.

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

    // В программе демонстрируется применение правила автоматического
    // преобразования к целочисленному типу.

    using System;

    class PromDemo {
    public static void Main() {
    byte b;
    int i;

    b = 10;
    i = b * b; // В данном случае не требуется явное приведение типа.

    b = 10;
    b = (byte) (b * b); // Тип результата должен быть приведен
    // к типу переменной b.

    Console.WriteLine("Значения переменных i и b: " + i + " " + b);
    }
    }

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

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

    char ch1 = 'a', ch2 = 'b';
    ch1 = (char) (ch1 + ch2);

    Без приведения типа результат сложения значений переменных ch1 и ch2 имеет тип int и не может быть присвоен переменной типа char.

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

    // В программе демонстрируется применение операции приведения типа
    // к результату вычисления выражения.

    using System;

    class UseCast {
    public static void Main() {
    int i;

    for(i = 0; i < 5; i++) {
    Console.WriteLine("Целочисленный результат вычисления выражения "
    +i+"/3: " + i / 3);
    Console.WriteLine("Результат вычисления выражения, выводимый" +
    "с дробной частью: {0:#.##}",(double) i / 3);
    Автоматическое преобразование типа значения из long в double.
    Console.WriteLine();
    }
    }
    }

    Ниже показан результат выполнения этой программы:
    Целочисленный результат вычисления выражения 0/3: 0
    Результат вычисления выражения, выводимый с дробной частью:

    Целочисленный результат вычисления выражения 1/3: 0
    Результат вычисления выражения, выводимый с дробной частью: .33

    Целочисленный результат вычисления выражения 2/3: 0
    Результат вычисления выражения, выводимый с дробной частью: .67

    Целочисленный результат вычисления выражения 3/3: 1
    Результат вычисления выражения, выводимый с дробной частью: 1

    Целочисленный результат вычисления выражения 4/3: 1
    Результат вычисления выражения, выводимый с дробной частью: 1.33

    Вопрос. Происходит ли преобразование типов в выражениях с унарными операторами, такими как унарный минус?
    Ответ. Да. Для унарных операций операнды, у которых диапазон допустимых значений меньший, чем у int (такие, как byte, sbyte, short и ushort), преобразуются к типу int. Операнд типа char также преобразуется к типу int. Кроме того, если операнду типа uint присваивается отрицательное значение, он преобразуется к типу long.

    Использование пробелов и круглых скобок

    Для улучшения читабельности программы выражения в C# могут содержать символы табуляции и пробелы. Например, следующие два выражения аналогичны, но второе гораздо легче для чтения:

    x=10/y*(127/x);

    x = 10 / y * (127/x);

    Круглые скобки повышают приоритет операторов, содержащихся внутри них (скобки используются точно так же, как в алгебре). Использование дополнительных круглых скобок не приведет к ошибкам или замедлению вычисления выражения, поэтому их можно применять для определения точного порядка действий и, следовательно, для улучшения читабельности программы. Например, очевидно, что второе выражение читается проще, чем первое:

    x = y/3-34*temp+127;
    x = (y/3) - (34*temp) + 127;

    Проект 2-3. Вычисление суммы регулярных выплат по кредиту

    @S2 = RegPay.cs

    Как уже говорилось, тип данных decimal особенно удобно использовать в денежных вычислениях. Предлагаемая программа вычисляет сумму регулярных выплат по кредиту (например, на покупку автомобиля). Принимая начальные данные: срок кредита, число платежей за год и величину процентов по кредиту, программа вычисляет размер одного платежа. Поскольку это финансовые вычисления, для представления данных имеет смысл использовать тип decimal. В этом проекте демонстрируется применение операций приведения типов, а также метода Pow() из библиотеки C#.

    Для вычисления размера платежа используется следующая формула:
    где в переменной IntRate указывается процент выплат по кредиту, в переменной Principal содержится значение стартового баланса, в переменной PayPerYear указывается число платежей в год, а в переменной NumYears задается срок погашения кредита в годах.

    Отметим, что в знаменателе формулы вы должны возвести в степень соответствующее значение. Для этого используется математический метод Math.Pow(). Вот как он вызывается:

    result = Math.Pow(base, exp);

    Метод Math() возвращает значение основания степени (base), возведенного в показатель степени (exp). Аргументы метода Pow() должны иметь тип double, и возвращаемое методом значение также будет иметь тип double. Это означает, что вам необходимо использовать приведение типов для преобразования типа double в тип decimal.

    Пошаговая инструкция
    1. Создайте новый файл и назовите его RegPay.cs.

    2. Используйте в программе следующие переменные:

    decimal Principal; // Значение стартового баланса.
    decimal IntRate; // Процент выплат по кредиту, например 0.075.
    decimal PayPerYear; // Количество платежей в год.
    decimal NumYears; // Срок погашения кредита.
    decimal Payment; // Размер платежа.
    decimal numer, denom; // Вспомогательные переменные.
    double b, e; // Основание и показатель степени
    // для вызова метода Pow().
    (Поскольку большая часть вычислений будет осуществляться с использованием данных типа decimal, большинство переменных имеет тип decimal). Обратите внимание, что за каждым объявлением переменной следует комментарий, объясняющий ее использование. Это помогает понять, какие функции выполняет каждая переменная. Хотя мы не включали такие комментарии в большинство коротких программ, приводимых в этой книге, нужно отметить, что с увеличением объема и сложности программы эти комментарии часто становятся необходимыми для понимания алгоритма.

    3. Добавьте в программу строки кода, специфицирующие информацию о кредите. В программе заданы следующие данные: значение стартового баланса равно $10000, проценты по кредиту составляют 7.5%, число выплат в год - 12, а срок погашения кредита 5 лет.

    Principal = 10000.00m;
    IntRate = 0.075m;
    PayPerYear = 12.0m;
    NumYears = 5.0m;

    4. Добавьте строки кода, в которых производятся финансовые вычисления:

    numer = IntRate * Principal / PayPerYear;

    e = (double) -(PayPerYear * NumYears);
    b = (double) (IntRate / PayPerYear) + 1;

    denom = 1 - (decimal) Math.Pow(b, e);
    Payment = numer / denom;
    (Обратите внимание, как нужно использовать приведение типов данных для передачи значений методу Pow() и преобразования возвращаемого значения. Помните, что в C# не существует автоматического преобразования между типами данных decimal и double.)

    5. Завершите программу оператором, который выводит значение ежемесячной выплаты:

    Console.WriteLine("Размер ежемесячной выплаты: {0:C}", Payment);

    6. Ниже приведен полный текст программы RegPay.cs:

    /*
    Проект 2-3
    Программа вычисляет размер ежемесячной выплаты по кредиту.
    Назовите этот файл RegPay.cs.
    */

    using System;

    class RegPay {
    public static void Main() {
    decimal Principal; // Значение стартового баланса.
    decimal IntRate; // Процент выплат по кредиту,
    // например 0.075.
    decimal PayPerYear; // Количество платежей в год.
    decimal NumYears; // Срок погашения кредита.
    decimal Payment; // Размер платежа.
    decimal numer, denom; // Вспомогательные переменные.
    double b, e; // Основание и показатель степени
    // для вызова метода Pow().

    Principal = 10000.00m;
    IntRate = 0.075m;
    PayPerYear = 12.0m;
    NumYears = 5.0m;

    numer = IntRate * Principal / PayPerYear;

    e = (double) -(PayPerYear * NumYears);
    b = (double) (IntRate / PayPerYear) + 1;

    denom = 1 - (decimal) Math.Pow(b, e);

    Payment = numer / denom;

    Console.WriteLine("Размер ежемесячной выплаты: {0:C}", Payment);
    }
    }

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

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

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

    1. Почему в C# строго специфицируются диапазоны допустимых значений и характеристики его простых типов?

    2. Что представляет собой символьный тип в C# и чем он отличается от символьного типа, используемого в других языках программирования?

    3. Справедливо ли утверждение, что переменная типа bool может хранить любое значение, поскольку любое ненулевое значение является истинным?

    4. Используя одну строку кода и escape-последовательности, напишите оператор WriteLine();, в результате выполнения которого будут выведены следующие три строки:

    Первая.
    Вторая.
    Третья.

    5. Какая ошибка содержится в данном фрагменте кода?

    fo r(i = 0; i < 10; i++) {
    int sum;
    sum = sum + i;
    }
    Console.WriteLine("Сумма равна: " + sum);

    6. Объясните различие между постфиксной (d++) и префиксной (++d) формами оператора инкремента.

    7. Напишите фрагмент кода, в котором для предотвращения ошибки деления на ноль использован быстрый оператор AND.

    8. К какому типу преобразуются типы byte и short в выражении?

    9. Какой из нижеприведенных типов не может совмещаться в выражении со значением типа decimal:

    а) float б) int
    в) uint
    г) byte

    10. Когда необходимо применять приведение типов?

    11. Напишите программу, которая находит все простые числа в диапазоне от 1 до 100.

    12. Самостоятельно перепишите программу, предназначенную для вывода таблицы истинности (проект 2-2), таким образом, чтобы в ней вместо escape-последовательностей использовались копирующиеся строковые литералы с вложенными знаками табуляции и символами новой строки.

    закрыть