Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Полиморфизм в C#. Интерфейсы



Виртуальные методы и их переопределение

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

Метод объявляется как виртуальный в базовом классе с помощью ключевого слова virtual, указываемого перед его именем. Когда же виртуальный метод переопределяется в производном классе, то для этого используется модификатор override. А сам процесс повторного определения виртуального метода в производном классе называется переопределением метода. При переопределении имя, возвращаемый тип и сигнатура переопределяющего метода должны быть точно такими же, как и у того виртуального метода, который переопределяется. Кроме того, виртуальный метод не может быть объявлен как static или abstract.

class Rectangle

{

virtual public function Draw()

{ // виртуальный метод «Нарисовать прямоугольник»

Console.Write(“рисуем прямоугольник”);

}

}

 

// Класс квадрата

class Square: Rectangle

{

override public function Draw()

{ // метод «Нарисовать квадрат»

Console.Write(“рисуем квадрат”);

}

}

Переопределение метода служит основанием для воплощения одного из самых эффективных в С# принципов: динамической диспетчеризации методов, которая представляет собой механизм разрешения вызова во время выполнения, а не компиляции.

Значение динамической диспетчеризации методов состоит в том, что именно благодаря ей в С# реализуется динамический полиморфизм.

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

 

Применение абстрактных классов

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

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

Абстрактный метод создается с помощью указываемого модификатора типа abstract. У абстрактного метода отсутствует тело, и поэтому он не реализуется в базовом классе. Это означает, что он должен быть переопределен в производном классе, поскольку его вариант из базового класса просто непригоден для использования. Таким образом абстрактный метод определяет интерфейс, но не реализацию метода.

Абстрактный метод автоматически становится виртуальным и не требует указания модификатора virtual. Совместное использование модификаторов virtual и abstract считается ошибкой.

Для определения абстрактного метода служит приведенная ниже общая форма.

abstract тип имя{список_параметров);

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

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

 

Интерфейсы

В С# предусмотрено разделение интерфейса класса и его реализации с помощью ключевого слова interface.

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

Для реализации интерфейса в классе должны быть предоставлены тела (т.е. конкретные реализации) методов, описанных в этом интерфейсе. Каждому классу предоставляется полная свобода для определения деталей своей собственной реализации интерфейса. Следовательно, один и тот же интерфейс может быть реализован в двух классах по-разному. Тем не менее в каждом из них должен поддерживаться один и тот же набор методов данного интерфейса. А в том коде, где известен такой интерфейс, могут использоваться объекты любого из этих двух классов, поскольку интерфейс для всех этих объектов остается одинаковым. Благодаря поддержке интерфейсов в С# может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов.

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

interface имя{

возвращаемый_тип имя_метода1 (список_параметров);

возвращаемый_тип имя_метода2 (список_параметров);

//...

возвращаемый_тип имя_методаN{список_параметров);

}

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

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

public interface ISeries {

int GetNextO; // возвратить следующее по порядку число

void Reset(); // перезапустить

void SetStart(int x); // задать начальное значение

}

Этому интерфейсу присваивается имя ISeries. Префикс I в имени интерфейса указывать необязательно, но это принято делать в практике программирования, чтобы как-то отличать интерфейсы от классов. Интерфейс ISeries объявляется как public и поэтому может быть реализован в любом классе какой угодно программы.

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

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

class имя_класса: имя_интерфейса {

// тело класса

}

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

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

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

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

// Реализовать интерфейс ISeries,

class ByTwos: ISeries {

int start;

int val;

public ByTwos () {

start = 0;

val = 0;

}

public int GetNext() {

val += 2;

return val;

}

public void Reset() {

val = start;

}

public void SetStart(int x) {

start = x;

val = start;

}

}

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

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

// Интерфейсное свойство

тип имя{

get;

set;

}

Очевидно, что в определении интерфейсных свойств, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.

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

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

// Интерфейсный индексатор

тип_элемента this[int индекс]{

get;

set;

}

Как и прежде, в объявлении интерфейсных индексаторов, доступных только для чтения или только для записи, должен присутствовать единственный аксессор: get или set соответственно.

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

 


6. Система типов языка C#

Как и в любом языке программирования, в С# поставляется собственный набор основных типов данных, которые должны применяться для представления локальных переменных, переменных экземпляра, возвращаемых значений и входных параметров. Однако в отличие от других языков программирования, в С# эти ключевые слова представляют собой нечто большее, чем просто распознаваемые компилятором лексемы. Они, по сути, представляют собой сокращенные варианты обозначения полноценных типов из пространства имен System. В табл. 3 0перечислены эти системные типы данных вместе с охватываемыми ими диапазонами значений, соответствующими ключевыми словами на С# и сведениями о том, отвечают ли они требованиям общеязыковой спецификации CLS (Common Language Specification).

Таблица 3

Системные типы данных в С#

Cокращенный вариант обозначения в С# Отвечает ли требованиям CLS Системный тип Диапазон значений Описание
bool Да System.Boolean true или false Представляет признак истиности или ложности
sbyte Нет System.SByte от -127 до 128 8-битное число со знаком
byte Да System.Byte от 0 до 255 8-битное число без знака
short Да System.Int16 от -32 768 до 32767 16-битное число со знаком
ushort Нет System.UInt16 от 0 до 65 535 16-битное число без знака
int Да System.Int32 от - 2 147 483 648 до 2 147 483 647 32-битное число со знаком
uint Нет System.UInt32 от 0 до 4 294 967 295 32-битное число без знака
long Да System.Int64 от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 64-битное число со знаком
ulong Нет System.UInt64 от 0 до 18 446 744 073 709 551 615 64-битное число без знака
char Да System.Char от U+0000 до U+ffff Одиночный 16-битный символ Unicode
float Да System.Single от +1, 5× 10-45 до 3, 4× 1038 32-битное число с плавающей точкой
double Да System.Double от 5, 0× 10-324 до 1, 7× 10308 64-битное число с плавающей точкой
decimal Да System.Decimal от ±1, 0× 10e-28 до ±7, 9× 1028 96-битное число с плавающей точкой
string Да System.String Ограничивается объемом системной памяти Представляет ряд символов в формате Unicode
object Да System.Object Позволяет сохранять любой тип в объектной переменной Служит базовым классом для всех типов в мире.NET

 

Интересно отметить то, что даже элементарные типы данных в.NET имеют вид иерархии классов. Типы, которые находятся в самом верху иерархии, обеспечивают некоторое поведение по умолчанию, которое передается унаследованным от них типам. На рис. 1 схематично показаны отношения между ключевыми системными типами.

Рис. 1. Иерархия классов системных типов

Обратите внимание, что каждый из этих типов в конечном итоге наследуется от класса System.Object, в котором содержится набор методов (таких как ToString (), Equals () и GetHashCode ()), являющихся общими для всех поставляемых в библиотеках базовых классов.NET типов.

В С# имеются две общие категории встроенных типов данных: типы значений и ссылочные типы. Они отличаются по содержимому переменной. Если переменная относится к типу значения, то она содержит само значение, например 3, 1416 или 212. А если переменная относится к ссылочному типу, то она содержит ссылку на значение. Наиболее распространенным примером использования ссылочного типа является класс.

Каждый из числовых типов, такой как short или int. отображается на соответствующую структуру в пространстве имен System. Структуры представляют собой типы значений, которые размещаются в стеке. Типы string и object, с другой стороны, являются ссылочными типами, а это значит, что данные, сохраняемые в переменных такого типа, размещаются в управляемой куче.

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

Ссылочные типы (например, объекты) располагаются в куче. Куча — это оперативная память компьютера. Доступ к ней осуществляется медленнее, чем к стеку. Когда объект располагается в куче, то переменная хранит лишь адрес объекта. Этот адрес хранится в стеке. По адресу программа имеет доступ к самому объекту, все данные которого сохраняются в общем куске памяти (куче).

Важно отметить, что многие из числовых типов данных унаследованы от класса System.ValueType. Потомки ValueType автоматически размещаются в стеке и потому обладают очень предсказуемым временем жизни и являются довольно эффективными. Типы, у которых в цепочке наследования не присутствует класс System.ValueType (вроде System.Type, System.String, System.Array, System.Exception и System. Delegate), в стеке не размещаются, а попадают в кучу и подвергаются автоматической сборке мусора.

Роль класса System.ValueType заключается в гарантировании размещения производного типа (например, любой структуры) в стеке, а не в куче с автоматически производимой сборкой мусора. Данные, размещаемые в стеке, могут создаваться и уничтожаться очень быстро, поскольку срок их жизни зависит только от контекста, в котором они определены. За данными, размещаемыми в куче, наблюдает сборщик мусора.NET, и время их существования зависит от целого ряда различных факторов.

С функциональной точки зрения единственной задачей System.ValueType является переопределение виртуальных методов, объявленных в System.Object, так, чтобы в них использовалась семантика, основанная на значениях, а не на ссылках. Под переопределением понимается изменение реализации виртуального (или, что тоже возможно, абстрактного) метода, определенного внутри базового класса. Базовым классом для ValueType является System.Object. В действительности методы экземпляра, определенные в System.ValueType, идентичны тем, что определены в System.Object:


// Структуры и перечисления неявным образом
// расширяют возможности System.ValueType.
public abstract class ValueType: object
{
public virtual bool Equals(object obj );
public virtual int GetHashCode ();
public Type GetType();
public virtual string ToString();
}

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

// Локальные структуры извлекаются из стека
// после завершения метода.
static void LocalValueTypes ()
{
//В действительности int представляет
// собой структуру System.Int32.
int i = 0;
//В действительности Point представляет
// собой тип структуры.
Point p = new Point ();
} // Здесь i и р изымаются из стека.


Структуры и перечисления

Тип enum

При построении той или иной системы зачастую удобно создавать набор символических имен, которые отображаются на известные числовые значения. Например, может понадобиться ссылаться на цвета определенного типа (Colors) с помощью таких констант, как Red (красный), Blue (синий), Yellow (жёлтый) и Green (зелёный). Для этой цели в С# поддерживается понятие специальных перечислений. Например, ниже показано специальное перечисление по имени Colors:

// Специальное перечисление.

enum Сolors

{

Red, // = О

Green, // = 1

Blue, // = 2

Yellow // = 3

}

В перечислении Colors определены четыре именованных константы, которые соответствуют дискретным числовым значениям. По умолчанию первому элементу присваивается значение 0, а всем остальным элементам значения присваиваются согласно схеме n+1. При желании исходное значение можно изменять как угодно. Нумерация в перечислениях вовсе не обязательно должна быть последовательной и содержать только уникальные значения.

По умолчанию для хранения значений перечисления используется тип System.Int32 (который в С# называется просто int); однако при желании его легко заменить. Перечисления в С# можно определять аналогичным образом для любых других ключевых системных типов (byte, short, int или long). Например, чтобы сделать для перечисления EmpType базовым тип byte, а не int, напишите следующий код:

//На этот раз Colors отображается на тип byte.

enum Colors: byte

{

Red = 10,

Green = 1,

Blue = 100,

Yellow = 9

}

Изменять базовый тип перечислений удобно в случае создания таких приложений.NET, которые будут развертываться на устройствах с небольшим объемом памяти (таких как поддерживающие.NET сотовые телефоны или устройства PDA), чтобы экономить память везде, где только возможно. Естественно, если для перечисления в качестве базового типа указан byte, каждое значение в этом перечислении ни в коем случае не должно выходить за рамки диапазона его допустимых значений.

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

При присваивании значения переменной перечисления перед значением должно обязательно указываться имя перечисления. Из-за того, что перечисления представляют собой фиксированные наборы пар " имя/значение", устанавливать для переменной перечисления значение, которое не определено напрямую в перечисляемом типе, не допускается.

Интересный аспект перечислений в.NET связан с тем, что функциональность они получают от типа класса System.Enum. В этом классе предлагается набор методов, которые позволяют опрашивать и преобразовывать заданное перечисление. Одним из наиболее полезных среди них является метод Enum.GetUnderlyingType(), который возвращает тип данных, используемый для хранения значений перечислимого типа.

Помимо метода Enum.GetUnderlyingType (), все перечисления С# также поддерживают метод по имени ToStringO, который возвращает имя текущего значения перечисления в виде строки. Ниже приведен соответствующий пример кода:

static void Main(string [ ] args)

{

Console.WriteLine (" **** Fun with Enums *****" );

EmpType emp = EmpType.Contractor;

// Выводит строку " emp is a Contractor".

Console.WriteLine(" emp is a {0}.", emp.ToString());

Console.ReadLine();

}

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

static void Main(string[] args)

{

Console.WriteLine(" **** Fun with Enums *****" );

EmpType emp = EmpType.Contractor;

// Выводит строку " Contractor = 100".

Console.WriteLine(" {0} = {1}", emp.ToString(), (byte)emp);

Console.ReadLine();

}

 

Тип структур

Теперь рассмотрим использование типов структур (struct) в.NET. Типы структур прекрасно подходят для моделирования математических, геометрических и прочих " атомарных" сущностей в приложении. Они (как и перечисления) представляют собой определяемые пользователем типы, однако (в отличие от перечислений) просто коллекциями пар " имя/значение" - не являются. Вместо этого они скорее представляют собой типы, способные содержать любое количество полей данных и членов, выполняющих над ними какие-то операции.

В С# структуры создаются с помощью ключевого слова struct:

struct Point

{

// Поля структуры.

public int X;

public int Y;

// Добавление 1 к позиции (X, Y).

public void Increment ()

{

X++; Y++;

}

// Вычитание 1 из позиции (X, Y).

public void Decrement ()

{

X--; Y—;

}

// Отображение текущей позиции.

public void Display()

{

Console.WriteLine(" X = {0}, Y= {1}", X, Y);

}

}

Здесь определены два целочисленных поля (X и Y) с использованием ключевого слова public, которое представляет собой один из модификаторов управления доступом. Объявление данных с использованием ключевого слова public гарантирует наличие у вызывающего кода возможности напрямую получать к ним доступ из данной переменной Point (через операцию точки).

Для создания переменной типа структуры на выбор доступно несколько вариантов.

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

// Ошибка! Полю Y не было присвоено значение.

Point pi;

pl.X = 10;

pi.Display();

// Все в порядке1 Обоим полям были присвоены

// значения перед использованием.

Point p2;

р2.Х = 10;

p2.Y = 10;

р2.Display();

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

// Установка для всех полей значений по умолчанию

//за счет применения конструктора по умолчанию.

Point pi = new Point ();

// Выводит Х=0, Y=0

pi.Display();

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

Основные различия классов и структур:

1) Класс и структура принадлежат к двум различным категориям типов – типов-ссылок и типов-значений. То есть присвоение одной структуры другой вызывает операцию копирования содержимого одной из них в другую, а после присвоения переменной типа класса другой приводит к тому, что обе они ссылаются на один объект;

2) Структуру нельзя наследовать от других структур или классов. Все структуры наследуются непосредственно от System.ValueType, который наследуется от System.Object.;

3) Структура не может служить основой для классов;

4) Структуры могут быть созданы без использования нового оператора.


8. Операторы и управляющие конструкции языка C#

 

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

 

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

Арифметические операторы, представленные в языке С#, приведены ниже.

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

 

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

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

Оператор инкремента увеличивает свой операнд на 1, а оператор декремента уменьшает операнд на 1. Оба оператора инкремента и декремента можно указывать до операнда (в префиксной форме) или же после операнда (в постфиксной форме). Когда оператор инкремента или декремента предшествует своему операнду, то результатом операции становится значение операнда после инкремента или декремента. А когда оператор инкремента или декремента следует после своего операнда, то результатом операции становится значение операнда до инкремента или декремента. Рассмотрим следующий фрагмент кода.

х = 10;

у = ++х;

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

х = 10;

у = х++;

значение переменной у будет установлено равным 10, так как в этом случае значение переменной х сначала присваивается переменной у, а затем увеличивается на 1. В обоих случаях значение переменной х оказывается равным 11. Отличие состоит лишь том, когда именно это значение станет равным 11: до или после его присваивания переменной у.

 


8.2. Операторы отношения и логические операторы

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

Логические операторы: & (И), | (ИЛИ), ^ (исключающее ИЛИ), & & (укороченное И), || (укороченное ИЛИ), ! (НЕ).

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

В целом, объекты можно сравнивать на равенство или неравенство, используя операторы отношения == и! =. А операторы сравнения <, >, < = или > = могут применяться только к тем типам данных, которые поддерживают отношение порядка. Следовательно, операторы отношения можно применять ко всем числовым типам данных. Но значения типа bool могут сравниваться только на равенство или неравенство, поскольку истинные (true) и ложные (false) значения не упорядочиваются. Например, сравнение true > false в С# не имеет смысла.

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

Укороченная логическая операция И выполняется с помощью оператора & &, а укороченная логическая операция ИЛИ — с помощью оператора ||. Этим укороченным логическим операторам соответствуют обычные логические операторы & и |. Единственное отличие укороченного логического оператора от обычного заключается в том, что второй его операнд вычисляется только по мере необходимости. Укороченный оператор И называется также условным логическим оператором И, а укороченный оператор ИЛИ — условным логическим оператором ИЛИ.

 

 

Таблица истинности для логических И и ИЛИ:

A B A & & B A || B

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

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

имя_переменной = выражение

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

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

int х, у, z;

х = у = z = 100; // присвоить значение 100 переменным х, у и z

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

Для многих двоичных операций, т.е. операций, требующих наличия двух операндов, существуют отдельные составные операторы присваивания. Общая форма всех этих операторов имеет следующий вид:

имя_переменной ор = выражение

где ор — арифметический или логический оператор, применяемый вместе с оператором присваивания.

 

Оператор?

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

Выражение 1? Выражение2: Выражение3;

Здесь Выражение1 должно относиться к типу bool, а Выражение2 и Выражение3 — к одному и тому же типу. Обратите внимание на применение двоеточия и его местоположение в операторе?. Значение выражения? определяется следующим образом. Сначала вычисляется Выражение1. Если оно истинно, то вычисляется Выражение2, а полученный результат определяет значение всего выражения? в целом. Если же Выражение1 оказывается ложным, то вычисляется Выражение3, и его значение становится общим для всего выражения?. Рассмотрим следующий пример, в котором переменной absval присваивается значение переменной val.

absval = val < 0? -val: val; // получить абсолютное значение переменной val

В данном примере переменной absval присваивается значение переменной val, если оно больше или равно нулю. Если же значение переменной val отрицательно, то переменной absval присваивается результат отрицания этого значения, что в итоге дает положительное значение.

 

Управляющие операторы

Управляющие операторы разделяются на три категории: операторы выбора, к числу которых относятся операторы if и switch, итерационные операторы, в том числе операторы цикла for, while, do-while и foreach, а также операторы перехода: break, continue, goto, return и throw.

а) Операторы выбора:

1. Оператор if

Полная форма этого оператора:

if(условие) оператор;

else оператор;

где условие — это некоторое условное выражение, а оператор — адресат операторов if и else. Оператор else не является обязательным. Адресатом обоих операторов, if и else, могут также служить блоки операторов. Ниже приведена общая форма оператора if, в котором используются блоки операторов.

if(условие)

{

последовательность операторов

}

else

{

последовательность операторов

}

Если условное выражение оказывается истинным, то выполняется адресат оператора if. В противном случае выполняется адресат оператора else, если таковой существует. Но одновременно не может выполняться и то, и другое. Условное выражение, управляющее оператором if, должно давать результат типа bool.


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-07-14; Просмотров: 2757; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.153 с.)
Главная | Случайная страница | Обратная связь