Текст книги "C# 4.0: полное руководство"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 29 (всего у книги 83 страниц)
Когда один интерфейс наследует другой, то в производном интерфейсе может быть объявлен член, скрывающий член с аналогичным именем в базовом интерфейсе. Такое сокрытие имен происходит в том случае, если член в производном интерфейсе объявляется таким же образом, как и в базовом интерфейсе. Но если не указать в объявлении члена производного интерфейса ключевое слово new, то компилятор выдаст соответствующее предупреждающее сообщение.
При реализации члена интерфейса имеется возможность указать его имя полностью вместе с именем самого интерфейса. В этом случае получается явная реализация члена интерфейса, или просто явная реализация. Так, если объявлен интерфейс IMyIF
interface IMyIF {
int MyMeth(int x) ;
}
то следующая его реализация считается вполне допустимой:
class MyClass : IMyIF {
int IMyIF.MyMeth(int x) {
return x / 3;
}
}
Как видите, при реализации члена MyMeth()
интерфейса IMyIF
указывается его полное имя, включающее в себя имя его интерфейса.
Для явной реализации интерфейсного метода могут быть две причины. Во-первых, когда интерфейсный метод реализуется с указанием его полного имени, то такой метод оказывается доступным не посредством объектов класса, реализующего данный интерфейс, а по интерфейсной ссылке. Следовательно, явная реализация позволяет реализовать интерфейсный метод таким образом, чтобы он не стал открытым членом класса, предоставляющего его реализацию. И во-вторых, в одном классе могут быть реализованы два интерфейса с методами, объявленными с одинаковыми именами и сигнатурами. Но неоднозначность в данном случае устраняется благодаря указанию в именах этих методов их соответствующих интерфейсов. Рассмотрим каждую из этих двух возможностей явной реализации на конкретных примерах.
В приведенном ниже примере программы демонстрируется интерфейс IEven
, в котором объявляются два метода: IsEven()
и IsOdd()
. В первом из них определяется четность числа, а во втором – его нечетность. Интерфейс IEven
затем реализуется в классе MyClass
. При этом метод IsOdd()
реализуется явно.
// Реализовать член интерфейса явно,
using System;
interface IEven {
bool IsOdd(int x);
bool IsEven(int x);
}
class MyClass : IEven {
// Явная реализация. Обратите внимание на то, что
// этот член является закрытым по умолчанию,
bool IEven.IsOdd(int x) {
if((x%2) != 0) return true;
else return false;
}
// Обычная реализация,
public bool IsEven(int x) {
IEven о = this; // Интерфейсная ссылка на вызывающий объект,
return !о.IsOdd(x);
}
}
class Demo {
static void Main() {
MyClass ob = new MyClass();
bool result;
result = ob.IsEven(4);
if(result) Console.WriteLine(«4 – четное число.»);
// result = ob.IsOdd(4); // Ошибка, член IsOdd интерфейса IEven недоступен
// Но следующий код написан верно,
//поскольку в нем сначала создается
// интерфейсная ссылка типа IEven на объект класса MyClass, а затем по
// этой ссылке вызывается метод IsOdd().
IEven iRef = (IEven) ob;
result = iRef.IsOdd(3);
if(result) Console.WriteLine(«3 – нечетное число.»);
}
}
В приведенном выше примере метод IsOdd()
реализуется явно, а значит, он недоступен как открытый член класса MyClass
. Напротив, он доступен только по интерфейсной ссылке. Именно поэтому он вызывается посредством переменной о ссылочного типа IEven
в реализации метода IsEven()
.
Ниже приведен пример программы, в которой реализуются два интерфейса, причем в обоих интерфейсах объявляется метод Meth()
. Благодаря явной реализации исключается неоднозначность, характерная для подобной ситуации.
// Воспользоваться явной реализацией
// для устранения неоднозначности
using System;
interface IMyIF_A {
int Meth(int x) ;
}
interface IMyIF_B {
int Meth(int x) ;
}
// Оба интерфейса реализуются в классе MyClass.
class MyClass : IMyIF_A, IMyIF_B {
// Реализовать оба метода Meth() явно,
int IMyIF_A.Meth(int x) {
return x + x;
}
int IMyIF_B.Meth(int x) {
return x * x;
}
// Вызывать метод Meth() по интерфейсной ссылке.
public int MethA(int x){
IMyIF_A a_ob;
a_ob = this;
return a_ob.Meth(x); // вызов интерфейсного метода IMyIF_A
}
public int MethB(int x){
IMyIF_B b_ob;
b_ob = this;
return b_ob.Meth(x); // вызов интерфейсного метода IMyIF_B
}
}
class FQIFNames {
static void Main() {
MyClass ob = new MyClass();
Console.Write("Вызов метода IMyIF_A.Meth(): ");
Console.WriteLine(ob.MethA(3));
Console.Write("Вызов метода IMyIF_B.Meth(): ");
Console.WriteLine(ob.MethB(3)) ;
}
}
Вот к какому результату приводит выполнение этой программы.
Вызов метода IMyIF_A.Meth(): 6
Вызов метода IMyIF_B.Meth(): 9
Анализируя приведенный выше пример программы, обратим прежде всего внимание на одинаковую сигнатуру метода Meth()
в обоих интерфейсах, IMyIF_A
и IMyIF_B
. Когда оба этих интерфейса реализуются в классе MyClass
, для каждого из них в отдельности это делается явно, т.е. с указанием полного имени метода Meth()
. А поскольку явно реализованный метод может вызываться только по интерфейсной
ссылке, то в классе MyClass
создаются две такие ссылки: одна – для интерфейса IMyIF_A
, а другая – для интерфейса IMyIF_B
. Именно по этим ссылкам происходит обращение к объектам данного класса с целью вызвать методы соответствующих интерфейсов, благодаря чему и устраняется неоднозначность.
Одна из самых больших трудностей программирования на C# состоит в правильном выборе между интерфейсом и абстрактным классом в тех случаях, когда требуется описать функциональные возможности, но не реализацию. В подобных случаях рекомендуется придерживаться следующего общего правила: если какое-то понятие можно описать с точки зрения функционального назначения, не уточняя конкретные детали реализации, то следует использовать интерфейс. А если требуются некоторые детали реализации, то данное понятие следует представить абстрактным классом.
Для среды .NET Framework определено немало стандартных интерфейсов, которыми можно пользоваться в программах на С#. Так, в интерфейсе System.IComparable
определен метод CompareTo()
, применяемый для сравнения объектов, когда требуется соблюдать отношение порядка. Стандартные интерфейсы являются также важной частью классов коллекций, предоставляющих различные средства, в том числе стеки и очереди, для хранения целых групп объектов. Так, в интерфейсе System.Collections.ICollection
определяются функции для всей коллекции, а в интерфейсе System.Collections.IEnumerator
– способ последовательного обращения к элементам коллекции. Эти и многие другие интерфейсы подробнее рассматриваются в части II данной книги.
Как вам должно быть уже известно, классы относятся к ссылочным типам данных. Это означает, что объекты конкретного класса доступны по ссылке, в отличие от значений простых типов, доступных непосредственно. Но иногда прямой доступ к oбъектам как к значениям простых типов оказывается полезно иметь, например, ради повышения эффективности программы. Ведь каждый доступ к объектам (даже самым мелким) по ссылке связан с дополнительными издержками на расход вычислительных ресурсов и оперативной памяти. Для разрешения подобных затруднений в C# предусмотрена структура, которая подобна классу, но относится к типу значения, а не к ссылочному типу данных.
Структуры объявляются с помощью ключевого слова struct
и с точки зрения синтаксиса подобны классам. Ниже приведена общая форма объявления структуры:
struct имя : интерфейсы {
// объявления членов
}
где имя обозначает конкретное имя структуры.
Одни структуры не могут наследовать другие структуры и классы или служить в качестве базовых для других структур и классов. (Разумеется, структуры, как и все остальные типы данных в С#, наследуют класс object
.) Тем не менее в структуре можно реализовать один или несколько интерфейсов, которые указываются после имени структуры списком через запятую. Как и у классов, у каждой структуры имеются свои члены: методы, поля, индексаторы, свойства, операторные методы и события. В структурах допускается также определять конструкторы, но не деструкторы. В то же время для структуры нельзя определить конструктор, используемый по умолчанию (т.е. конструктор без параметров). Дело в том, что конструктор, вызываемый по умолчанию, определяется для всех структур автоматически и не подлежит изменению. Такой конструктор инициализирует поля структуры значениями, задаваемыми по умолчанию. А поскольку структуры не поддерживают наследование, то их члены нельзя указывать как abstract
, virtual
или protected
.
Объект структуры может быть создан с помощью оператора new
таким же образом, как и объект класса, но в этом нет особой необходимости. Ведь когда используется оператор new
, то вызывается конструктор, используемый по умолчанию. А когда этот оператор не используется, объект по-прежнему создается, хотя и не инициализируется. В этом случае инициализацию любых членов структуры придется выполнить вручную.
В приведенном ниже примере программы демонстрируется применение структуры для хранения информации о книге.
// Продемонстрировать применение структуры.
using System;
// Определить структуру,
struct Book {
public string Author;
public string Title;
public int Copyright;
public Book(string a, string t, int c) {
Author = a;
Title = t;
Copyright = c;
}
}
// Продемонстрировать применение структуры Book,
class StructDemo {
static void Main() {
Book bookl = new Book(«Герберт Шилдт»,
«Полный справочник ho C# 4.0»,
2010); // вызов явно заданного конструктора
Book
book2 = new Book(); // вызов конструктора по умолчанию
Book bоокЗ; // конструктор не вызывается
Console.WriteLine(bookl.Author + ", " +
bookl.Title + ", (c) " + bookl.Copyright);
Console.WriteLine();
if (book2.Title == null)
Console.WriteLine(«Член book2.Title пуст.»);
// А теперь ввести информацию в структуру book2.
book2.Title = «О дивный новый мир»;
book2.Author = «Олдос Хаксли»;
book2.Copyright = 1932;
Console.Write(«Структура book2 теперь содержит:n»);
Console.WriteLine(book2.Author + ", " +
book2.Title + ", (c) " + book2.Copyright);
Console.WriteLine() ;
// Console.WriteLine(bоокЗ.Title); // неверно, этот член структуры
// нужно сначала инициализировать
bоокЗ.Title = «Красный шторм»;
Console.WriteLine(bоокЗ.Title); // теперь верно
}
}
При выполнении этой программы получается следующий результат.
Герберт Шилдт, Полный справочник по C# 4.0, (с) 2010
Член book2.Title пуст.
Структура bоок2 теперь содержит:
Олдос Хаксли, О дивный новый мир, (с) 1932
Красный шторм
Как демонстрирует приведенный выше пример программы, структура может быть инициализирована с помощью оператора new для вызова конструктора или же путем простого объявления объекта. Так, если используется оператор new, то поля структуры инициализируются конструктором, вызываемым по умолчанию (в этом случае во всех полях устанавливается задаваемое по умолчанию значение), или же конструктором, определяемым пользователем. А если оператор new не используется, как это имеет место для структуры bоокЗ
, то объект структуры не инициализируется, а его поля должны быть установлены вручную перед тем, как пользоваться данным объектом.
Когда одна структура присваивается другой, создается копия ее объекта. В этом заключается одно из главных отличий структуры от класса. Как пояснялось ранее в этой книге, когда ссылка на один класс присваивается ссылке на другой класс, в итоге ссылка в левой части оператора присваивания указывает на тот же самый объект, что и ссылка в правой его части. А когда переменная одной структуры присваивается переменной другой структуры, создается копия объекта структуры из правой части оператора присваивания. Рассмотрим в качестве примера следующую программу.
// Скопировать структуру.
using System;
// Определить структуру,
struct MyStruct {
public int x;
}
// Продемонстрировать присваивание структуры,
class StructAssignment {
static void Main() {
MyStruct a;
MyStruct b;
a.x = 10;
b.x = 20;
Console.WriteLine(«a.x {0}, b.x {1}», a.x, b.x);
a = b;
b.x = 30;
Console.WriteLine(«a.x {0}, b.x {1}», a.x, b.x);
}
}
Вот к какому результату приводит выполнение этой программы.
а.х 10, b.x 20 а.х 20, b.x 30
Как показывает приведенный выше результат, после присваивания
а = b;
переменные структуры а и b по-прежнему остаются совершенно обособленными, т.е. переменная а не указывает на переменную b и никак не связана с ней, помимо того, что она содержит копию значения переменной b. Ситуация была бы совсем иной, если бы переменные а и b были ссылочного типа, указывая на объекты определенного класса. В качестве примера ниже приведен вариант предыдущей программы, где демонстрируется присваивание переменных ссылки на объекты определенного класса.
// Использовать ссылки на объекты определенного класса,
using System;
// Создать класс,
class MyClass {
public int x;
}
// Показать присваивание разных объектов данного класса,
class ClassAssignment {
static void Main() {
MyClass a = new MyClass();
MyClass b = new MyClass();
a.x = 10;
b.x = 20;
Console.WriteLine(«a.x {0}, b.x {1}», a.x, b.x);
a = b;
b.x = 30;
Console.WriteLine(«а.х {0}, b.x {1}», a.x, b.x);
}
}
Выполнение этой программы приводит к следующему результату.
а.х 10, b.х 20 а.х 30, b.х 30
Как видите, после того как переменная b будет присвоена переменной а, обе переменные станут указывать на один и тот же объект, т.е. на тот объект, на который первоначально указывала переменная b.
О назначении структур
В связи с изложенным выше возникает резонный вопрос: зачем в C# включена структура, если она обладает более скромными возможностями, чем класс? Ответ на этот вопрос заключается в повышении эффективности и производительности программ. Структуры относятся к типам значений, и поэтому ими можно оперировать непосредственно, а не по ссылке. Следовательно, для работы со структурой вообще не требуется переменная ссылочного типа, а это означает в ряде случаев существенную экономию оперативной памяти. Более того, работа со структурой не приводит к ухудшению производительности, столь характерному для обращения к объекту класса. Ведь доступ к структуре осуществляется непосредственно, а к объектам – по ссылке, поскольку классы относятся к данным ссылочного типа. Косвенный характер доступа к объектам подразумевает дополнительные издержки вычислительных ресурсов на каждый такой доступ, тогда как обращение к структурам не влечет за собой подобные издержки. И вообще, если нужно просто сохранить группу связанных вместе данных, не требующих наследования и обращения по ссылке, то с точки зрения производительности для них лучше выбрать структуру.
Ниже приведен еще один пример, демонстрирующий применение структуры на практике. В этом примере из области электронной коммерции имитируется запись транзакции. Каждая такая транзакция включает в себя заголовок пакета, содержащий номер и длину пакета. После заголовка следует номер счета и сумма транзакции. Заголовок пакета представляет собой самостоятельную единицу информации, и поэтому он организуется в отдельную структуру, которая затем используется для создания записи транзакции или же информационного пакета любого другого типа.
// Структуры удобны для группирования
//
небольших объемов данных,
using System;
// Определить структуру пакета,
struct PacketHeader {
public uint PackNum; // номер пакета
public ushort PackLen; // длина пакета
}
// Использовать структуру PacketHeader
// для создания записи транзакции
// в сфере электронной коммерции,
class Transaction {
static uint transacNum = 0;
PacketHeader ph; // ввести структуру PacketHeader
// в класс Transaction
string accountNum;
double amount;
public Transaction(string acc, double val) {
// создать заголовок пакета
ph.PackNum = transacNum++;
ph.PackLen =512; // произвольная длина
accountNum = acc; amount = val;
}
// Сымитировать транзакцию,
public void sendTransaction() {
Console.WriteLine("Пакет #: " + ph.PackNum +
", Длина: " + ph.PackLen +
",n Счет #: " + accountNum +
«, Сумма: {0:C}n», amount);
}
}
// Продемонстрировать применение структуры
// в виде пакета транзакции,
class PacketDemo {
static void Main() {
Transaction t = new Transaction(«31243», -100.12);
Transaction t2 = new Transaction(«AB4655», 345.25);
Transaction t3 = new Transaction («8475-09», 9800.00);
t.sendTransaction();
t2.sendTransaction();
t3.sendTransaction();
}
}
Вот к какому результату может привести выполнение этого кода.
Пакет #: 0, Длина: 512,
Счет #:31243, Сумма: $100.12
Пакет #: 1, Длина: 512,
Счет #: AB4655, Сумма: $354.25
Пакет #: 2, Длина: 512,
Счет #: 8475-09, Сумма: $9,800.00
Структура PacketHeader
оказывается вполне пригодной для формирования заголовка пакета транзакции, поскольку в ней хранится очень небольшое количество данных, не используется наследование и даже не содержатся методы. Кроме того, работа со структурой PacketHeader
не влечет за собой никаких дополнительных издержек, связанных со ссылками на объекты, что весьма характерно для класса. Следовательно, структуру PacketHeader
можно использовать для записи любой транзакции, не снижая эффективность данного процесса.
Любопытно, что в C++ также имеются структуры и используется ключевое слово struct
. Но эти структуры отличаются от тех, что имеются в С#. Так, в C++ структура относится к типу класса, а значит, структура и класс в этом языке практически равноценны и отличаются друг от друга лишь доступом по умолчанию к их членам, которые оказываются закрытыми для класса и открытыми для структуры. А в C# структура относится к типу значения, тогда как класс – к ссылочному типу.
Перечисление представляет собой множество именованных целочисленных констант. Перечислимый тип данных объявляется с помощью ключевого слова enum
. Ниже приведена общая форма объявления перечисления:
enum имя { список_перечисления} ;
где имя – это имя типа перечисления, а список_перечисления – список идентификаторов, разделяемый запятыми.
В приведенном ниже примере объявляется перечисление Apple различных сортов яблок.
enum Apple { Jonathan, GoldenDel, RedDel, Winesap,
Cortland, McIntosh };
Следует особо подчеркнуть, что каждая символически обозначаемая константа в перечислении имеет целое значение. Тем не менее неявные преобразования перечислимого типа во встроенные целочисленные типы и обратно в C# не определены, а значит, в подобных случаях требуется явное приведение типов. Кроме того, приведение типов требуется при преобразовании двух перечислимых типов. Но поскольку перечисления обозначают целые значения, то их можно, например, использовать для управления оператором выбора switch
или же оператором цикла for
.
Для каждой последующей символически обозначаемой константы в перечислении задается целое значение, которое на единицу больше, чем у предыдущей константы. По умолчанию значение первой символически обозначаемой константы в перечислении равно нулю. Следовательно, в приведенном выше примере перечисления Apple
константа Jonathan
равна нулю, константа GoldenDel
– 1, константа RedDel
– 2 и т.д.
Доступ к членам перечисления осуществляется по имени их типа, после которого следует оператор-точка. Например, при выполнении фрагмента кода
Console.WriteLine(Apple.RedDel + " имеет значение " +
(int)Apple.RedDel) ;
выводится следующий результат.
RedDel имеет значение 2
Как показывает результат выполнения приведенного выше фрагмента кода, для вывода перечислимого значения используется его имя. Но для получения этого значения требуется предварительно привести его к типу int.
Ниже приведен пример программы, демонстрирующий применение перечисления Apple.
// Продемонстрировать применение перечисления.
using System;
class EnumDemo {
enum Apple { Jonathan, GoldenDel, RedDel, Winesap,
Cortland, McIntosh };
static void Main() {
string[] color = {
«красный»,
«желтый»,
«красный»,
«красный»,
«красный»,
«красновато-зеленый»
};
Apple i; // объявить переменную перечислимого типа
// Использовать переменную i для циклического
// обращения к членам перечисления.
for(i = Apple.Jonathan; i <= Apple.McIntosh; i++)
Console.WriteLine(i + " имеет значение " + (int)i);
Console.WriteLine();
// Использовать перечисление для индексирования массива.
for(i = Apple.Jonathan; i <= Apple.McIntosh; i++)
Console.WriteLine("Цвет сорта " + i + " – " + color[ (int)i]);
}
}
Ниже приведен результат выполнения этой программы.
Jonathan имеет значение 0
GoldenDel имеет значение 1
RedDel имеет значение 2
Winsap имеет– значение 3
Cortland имеет значение 4
McIntosh имеет значение 5
Цвет сорта Jonathan – красный
Цвет сорта GoldenDel – желтый
Цвет сорта RedDel – красный
Цвет сорта Winsap – красный
Цвет сорта Cortland – красный
Цвет сорта McIntosh – красновато-зеленый
Обратите внимание на то, как переменная типа Apple
управляёт циклами for
. Значения символически обозначаемых констант в перечислении Apple
начинаются с нуля, поэтому их можно использовать для индексирования массива, чтобы получить цвет каждого сорта яблок. Обратите также внимание на необходимость производить приведение типов, когда перечислимое значение используется для индексирования массива. Как упоминалось выше, в C# не предусмотрены неявные преобразования перечислимых типов в целочисленные и обратно, поэтому для этой цели требуется явное приведение типов.
И еще одно замечание: все перечисления неявно наследуют от класса System.Enum
, который наследует от класса System.ValueType
, а тот, в свою очередь, – от класса object
.
Инициализация перечисления
Значение одной или нескольких символически обозначаемых констант в перечислении можно задать с помощью инициализатора. Для этого достаточно указать после символического обозначения отдельной константы знак равенства и целое значение. Каждой последующей константе присваивается значение, которое на единицу больше значения предыдущей инициализированной константы. Например, в приведенном ниже фрагменте кода константе RedDel
присваивается значение 10.
enum Apple { Jonathan, GoldenDel, RedDel = 10, Winesap,
Cortland, McIntosh };
В итоге все константы в перечислении принимают приведенные ниже значения.
Jonathan
0
GoldenDel
1
RedDel
10
Winesap
11
Cortland
12
McIntosh
13
Указание базового типа перечисления
По умолчанию в качестве базового для перечислений выбирается тип int, тем не менее перечисление может быть создано любого целочисленного типа, за исключением char. Для того чтобы указать другой тип, кроме int, достаточно поместить этот тип после имени перечисления, отделив его двоеточием. В качестве примера ниже задается тип byte
для перечисления Apple
.
enum Apple : byte { Jonathan, GoldenDel, RedDel,
Winesap, Cortland, McIntosh };
Теперь константа Apple.Winesap
, например, имеет количественное значение типа byte
.
Применение перечислений
На первый взгляд перечисления могут показаться любопытным, но не очень нужным элементом С#, но на самом деле это не так. Перечисления очень полезны, когда в программе требуется одна или несколько специальных символически обозначаемых констант. Допустим, что требуется написать программу для управления лентой конвейера на фабрике. Для этой цели можно создать метод Conveyor()
, принимающий в качестве параметров следующие команды: «старт», «стоп», «вперед» и «назад». Вместо того чтобы передавать методу Conveyor()
целые значения, например, 1 – в качестве команды «старт», 2 – в качестве команды «стоп» и так далее, что чревато ошибками, можно создать перечисление, чтобы присвоить этим значениям содержательные символические обозначения. Ниже приведен пример применения такого подхода.
// Сымитировать управление лентой конвейера,
using System;
class ConveyorControl {
// Перечислить команды конвейера.
public enum Action { Start, Stop, Forward, Reverse };
public void Conveyor(Action com) {
switch(com) {
case Action.Start:
Console.WriteLine(«Запустить конвейер.»);
break;
case Action.Stop:
Console.WriteLine(«Остановить конвейер.»);
break;
case Action.Forward:
Console.WriteLine(«Переместить конвейер вперед.»);
break;
case Action.Reverse:
Console.WriteLine («Переместить конвейер назад.»);
break;
}
}
}
class ConveyorDemo {
static void Main() {
ConveyorControl с = new ConveyorControl();
с.Conveyor(ConveyorControl.Action.Start);
с.Conveyor(ConveyorControl.Action.Forward);
с.Conveyor(ConveyorControl.Action.Reverse);
с.Conveyor(ConveyorControl.Action.Stop);
}
}
Вот к какому результату приводит выполнение этого кода.
Запустить конвейер.
Переместить конвейер вперед.
Переместить конвейер назад.
Остановить конвейер.
Метод Conveyor()
принимает аргумент типа Action
, и поэтому ему могут быть переданы только значения, определяемые в перечислении Action
. Например, ниже приведена попытка передать методу Conveyor()
значение 22.
с.Conveyor(22); // Ошибка!
Эта строка кода не будет скомпилирована, поскольку отсутствует предварительно заданное преобразование типа int
в перечислимый тип Action
. Именно это и препятствует передаче неправильных команд методу Conveyor()
. Конечно, такое преобразование можно организовать принудительно с помощью приведения типов, но это было бы преднамеренным, а не случайным или неумышленным действием. Кроме того, вероятность неумышленной передачи пользователем неправильных команд методу Conveyor()
сводится с минимуму благодаря тому, что эти команды обозначены символическими именами в перечислении.
В приведенном выше примере обращает на себя внимание еще одно интересное обстоятельство: перечислимый тип используется для управления оператором switch
. Как упоминалось выше, перечисления относятся к целочисленным типам данных, и поэтому их вполне допустимо использовать в операторе switch
.