
- •1.Объявление и определение класса.
- •3. Дружественные функции.
- •4. Пеpегpузка опеpаций ( синтаксис operator)
- •5. Статические компоненты класса.
- •7. Иерархия классов ( последовательность работы конструкторов и деструкторов )
- •8. Доступ к наследуемым компонентам
- •9. Виртуальные функции (когда применяются, форма вызова)
- •10 . Шаблоны ( пример template)
- •15 Преобразование типов данных.
- •17 Ввод-вывод в файл. Сохранение объектов в файле.
- •18 Обработка исключений (блоки try, throw, catch)
- •Примеры обработки исключительных ситуаций
- •20. Паттерны и их классификация. Принцип классификации паттернов проектирования
- •Паттерны проектирования классов/обьектов
- •21. Абстрактная фабрика.
- •22. Классификация типов данных. Система типов
- •Oбщий взгляд
- •Класс String
- •Объявление строк. Конструкторы класса string
- •Операции над строками
- •Цикл foreach
- •Наследование
- •Добавление полей потомком
- •Конструкторы родителей и потомков
- •Интерфейсы
- •Две стратегии реализации интерфейса
- •Преобразование к классу интерфейса
Цикл foreach
Новым видом цикла, не унаследованным от С++, является цикл foreach, удобный при работе с массивами, коллекциями и другими подобными контейнерами данных. Его синтаксис:
foreach(тип идентификатор in контейнер) оператор
Цикл работает в полном соответствии со своим названием - тело цикла выполняется для каждого элемента в контейнере. Тип идентификатора должен быть согласован с типом элементов, хранящихся в контейнере данных. Предполагается также, что элементы контейнера (массива, коллекции) упорядочены. На каждом шаге цикла идентификатор, задающий текущий элемент контейнера, получает значение очередного
элемента в соответствии с порядком, установленным на элементах контейнера. С этим текущим элементом и выполняется тело цикла - выполняется столько раз, сколько элементов находится в контейнере. Цикл заканчивается, когда полностью перебраны все элементы контейнера.
Серьезным недостатком циклов foreach в языке C# является то, что цикл работает только на чтение, но не на запись элементов. Так что наполнять контейнер элементами приходится с помощью других операторов цикла.
В приведенном ниже примере показана работа с трехмерным массивом. Массив создается с использованием циклов типа for, а при нахождении суммы его элементов, минимального и максимального значения используется цикл foreach:
/// Демонстрация цикла foreach. Вычисление суммы,
/// максимального и минимального элементов
/// трехмерного массива, заполненного случайными числами.
public void SumMinMax()
{ int [,,] arr3d = new int[10,10,10];
Random rnd = new Random();
for (int i =0; i<10; i++)
for (int j =0; j<10; j++)
for (int k =0; k<10; k++)
arr3d[i,j,k]= rnd.Next(100);
long sum =0; int min=arr3d[0,0,0], max=arr3d[0,0,0];
foreach(int item in arr3d)
{ sum +=item;
if (item > max) max = item;
else if (item < min) min = item; }
Console.WriteLine("sum = {0}, min = {1}, max = {2}",
sum, min, max);
}//SumMinMax
25. Классы, создание объектов.
Формальное определение класса в С#
Класс в С#, как и в других языках программирования, — это пользовательский
тип данных (user defined type, UDT), который состоит из данных (часто называе-
мых атрибутами или свойствами) и функциями для выполнения с этими данными
различных действий (эти функции обычно называются методами). Классы позво-
ляют группировать в единое целое данные и функциональность, моделируя объек-
ты реального мира. Именно это свойство классов и обеспечивает одно из наиболее
важных преимуществ объектно-ориентированных языков программирования.
К примеру, предположим, что нам потребовалось создать модель сотрудника
нашей организации. Конечно же, для этой цели удобнее всего создать специаль-
ный класс. Этот класс, как минимум, должен хранить данные об имени работника,
его идентификационном номере и текущей заработной плате. Помимо этого, пусть
в нашем классе будут определены два метода — GiveBonusQ для увеличения зара-ботной платы сотрудника и 01 spl ayStats {) для вывода всех имеющихся данных об
этом сотруднике (рис. 3.1).
Как уже говорилось в главе 2, для классов С# можно определить любое количе-
ство конструкторов. Эти специальные методы классов позволяют создавать объек-
ты данного класса, настраивая при этом их исходное состояние нужным вам обра-
зом. Любой класс С# автоматически снабжается конструктором по умолчанию (не
принимающим никаких параметров). Этот конструктор в С# (в отличие от C++)
при создании объекта автоматически присвоит всем переменным-членам класса
безопасные значения по умолчанию. Кроме того, вы можете как переопределить
конструктор по умолчанию, так и создать такое количество пользовательских кон-
структоров (принимающих параметры), сколько вам необходимо. Давайте опре-
делим наш класс для сотрудника на С#:
// Исходное определение класса
class Employee
{
// Внутренние закрытые данные класса
private string fullName;
private int erapID;
private float currPay;
// Конструкторы
public EmployeeO {}
public EmployeeCstring fullName,
{
this. fullName = fullName;
this.empID = empID;
this. currPay = currPay:
int empID. float currPay)
// Метод для увеличения зарплаты сотрудника
public void CiveBonusffloat amount)
{ currPay += amount; }
// Метод для вывода сведений о текущем состоянии объекта
public virtual void DisplayStatsO
Формальное определение класса в С# 141
Console. WriteUnet"Name: {0}", full Name):
Console.WriteLineC'Pay: {0}". currPay):
Console.UrtteLineCID: {0}". empID);
Console.WriteLineC'SSN: {0}". ssn);
}
Обратите внимание на то, что определение для конструктора по умолчанию не
содержит каких-либо действий, которые должен выполнять конструктор:
class Employee
{
// Всей переменнын-членан значения по умолчанию будут присвоены автоматически
public EmployeeО {}
Необходимо обязательно помнить о следующем обстоятельстве: в С# (кик
и в C++), если вы определили хотя бы один пользовательский конструктор (при-
нимающий параметры), конструктор по умолчанию автоматически создаваться уже
не будет и его придется определять явно. В противном случае при попытке выпол-
нения такого, к примеру, кода:
// Вызываем конструктор по умолчанию
Employee e = new EmployeeO:
вы получите сообщение об ошибке компилятора.
Использование пользовательского конструктора очевидно:
// Выэываен пользовательский конструктор (двуня способами)
public static void MainO
i
Employee e = new EmployeeC'Joe", 80, 30000);
e.GiveBonusC200):
Employee e2:
e2 - new Employee("Beth". 81. 50000);
e2.GiveBonus(1000);
e2.DisplayStats():
}
Код приложения Employees (с которым мы будем работать в продолжение всей
этой главы) можно найти в подкаталоге Chapter 3.
26. Методы класса и их параметры.
27. Свойства класса.
Второй способ инкапсуляции: применение
свойств класса
Помимо традиционных методов доступа и изменения для обращения к закрытым чле-
нам класса можно также использовать свойства (properties). В Visual Basic и СОМ
свойства — это привычный инструмент. Свойства позволяют имитировать доступ
к внутренним данным класса: при получении информации или внесении изменений
через свойство синтаксис выглядит так же, как при обращении к обычной открытой
переменной. Но на самом деле любое свойство состоит из двух скрытых внутренних
методов. Преимущество свойств заключается в том, что вместо того, чтобы использо-
вать два отдельных метода, пользователь класса может использовать единствен» >е
свойство, работая с ним так же, как и с открытой переменной-членом данного класса:
// Обращение к имени сотрудника через свойство
public static int Main(sthng[] args)
Employee p = new EmployeeC);
// Устанавливаем значение
p.EmpIO = 81;
// Получаем значение
Console.WriteLineC'Person ID Is: {0}". p.EmpID):
return 0;
|Если заглянуть внутрь определения класса, то свойства всегда отображаются в
«реальные» методы доступа и изменения. А уже в определении этих методов вы
можете реализовать любую логику (например, для устранения лишних символов,
проверки допустимости вводимых числовых значений и прочего). Ниже представ-
лен синтаксис класса Employee с определением свойства EmpID:
// Пользовательское свойство EmpID для доступа к переменной етрШ
public class Employee{
private Int етрШ:
// Свойство для empID
public Int EmpID
{get {return empIO:}
set
{// Здесь вы можете реализовать логику для проверки вводимых
// значений и выполнения других действий
empID - value;} .}
Свойство С# состоит из двух блоков — блока доступа (get block) и блока изме-
нения (set block). Ключевое слово val ue представляет правую часть выражения при
присвоении значения посредством свойства. Как и все в С#, то, что представлено
словом val ue — это также объект. Совместимость того, что передается свойству как
value, с самим свойством, зависит от определения свойства. Например, свойство
EmpID предназначено (согласно своему определению в классе) для работы с закры-
тым целочисленным empID, поэтому число 81 вполне его устроит:
// В данной случае типом данных, используемым для value, будет Int
еЗ.EmpID - 81:
Показать дополнительные возможности применения ключевого слова value
можно на таком примере:
11 Свойство для ешрЮ
public Int EmpID {
get {return empID:}
set
Средства инкапсуляции в С# 153
// Как еще можно использовать value
Console.WnteLlneC'value is the Instance of: {0}". value.GetTypeO):
Console.WriteLineC'value as string: {0}". value.ToStrlngO);
erapID = value;
Результат работы данной программы представлен на рис. 3.7.
•- ^ D:\CSharp8ook\Labs\Chapter 3\Cmploye_. НИ*
иа.1»е is an instance of :Int32
jalue as string :81
Press any Itey to continue
Рис. 3.7. Значение «value» при EmpID = 81
Необходимо отметить, что обращаться к объекту v a l u e можно только в преде-
лах программного блока set внутри определения свойства. Попытка обратиться к
этому объекту из любого другого места приведет к ошибке компилятора.
Последнее, что мы отметим — использование свойств (по сравнению с тради-
ционными методами доступа и изменения) делает применение ваших типов более
простым. Например, предположим, что в классе Employee предусмотрена внутрен-
няя закрытая переменная для хранения информации о возрасте сотрудника. Вы
хотите, чтобы при наступлении дня рождения этого сотрудника значение этой пе-
ременной увеличивалось на единицу. При использовании традиционных методов
доступа и изменения эта операция будет выглядеть так:
Employee joe * new EmployeeO;
joe.SetAge( joe.GetAgeO + 1):
Используя свойство, вы можете сделать РТО проще:
Employee joe = new EmployeeO;
joe.Ag&t+:
Внутреннее представление свойств С#
Многие программисты стараются использовать для имен методов доступа и изме -
нения соответственно приставки get_ и set_ (например, get_Name() и set_Name()).
Само по себе это не представляет проблемы. Проблему представляет другое: С#
для внутреннего представления свойства использует методы с теми же самыми
префиксами. К примеру, если вы откроете сборку EmpLoyees.exe при помощи ути-
литы ILDastn.exe, вы увидите, что каждому свойству соответствуют два отдельных;
(и скрытых) метода (рис. 3.8).
Поэтому подобное определение класса вызовет ошибку компилятора:
Рис. З.8. Свойства отображаются в скрытые методы get_ и set_
Свойства только для чтения, только для записи
и статические
Наш рассказ о свойствах классов С# будет неполон, если мы не упомянем еще
о некоторых связанных с ними моментах. Как мы помним, наше свойство EmpID
было создано как свойство, доступное и для чтения, и для записи. Однако при со-
Средства инкапсуляции в С# 155
здании пользовательских свойств класса часто возникает необходимость создать
свойство, которое будет доступно только для чтения. Делается это очень просто:
необходимо в определении свойства пропустить блок set. В результате свойство
станет доступным только для чтения:
public class Employee
// Будем считать, что исходное значение этой переменной присваивается с помощью
// конструктора класса
private string ssn;
// А вот так выглядит свойство только для чтения
public string SSN ( get { return ssn: } }
C# также поддерживает статические свойства. Как мы помним, статические
переменные предназначены для хранения информации на уровне всего класса, а не
его отдельных объектов. Если у нас объявлены статические данные (то есть те же
переменные), то обращаться к ним и устанавливать значения должны статические
свойства. Предположим, что в нашем классе Employee мы собираемся, помимо все-
го прочего, хранить еще и информацию об имени организации, в которой работают
все сотрудники — объекты класса Employee. Для этого будет использована специ-
альная статическая переменная. Соответствующее статическое свойство для ра-
боты с этой переменной может выглядеть так:
// Со статическими данными должны работать статические свойства
27. Свойства класса.
Второй способ инкапсуляции: применение свойств класса. Помимо традиционных методов доступа и изменения для обращения к закрытым членам класса можно также использовать свойства (properties). В Visual Basic и СОМ свойства — это привычный инструмент. Свойства позволяют имитировать доступ к внутренним данным класса: при получении информации или внесении изменений через свойство синтаксис выглядит так же, как при обращении к обычной открытой переменной. Но на самом деле любое свойство состоит из двух скрытых внутренних методов. Преимущество свойств заключается в том, что вместо того, чтобы использовать два отдельных метода, пользователь класса может использовать единствен» >е свойство, работая с ним так же, как и с открытой переменной-членом данного класса:
// Обращение к имени сотрудника через свойство
public static int Main(sthng[] args)
Employee p = new EmployeeC);
// Устанавливаем значение
p.EmpIO = 81;
// Получаем значение
Console.WriteLineC'Person ID Is: {0}". p.EmpID):
return 0;
|Если заглянуть внутрь определения класса, то свойства всегда отображаются в «реальные» методы доступа и изменения. А уже в определении этих методов вы можете реализовать любую логику (например, для устранения лишних символов, проверки допустимости вводимых числовых значений и прочего). Ниже представлен синтаксис класса Employee с определением свойства EmpID:
// Пользовательское свойство EmpID для доступа к переменной етрШ
public class Employee
{
private Int етрШ:
// Свойство для empID
public Int EmpID
{get {return empIO:}
set
{// Здесь вы можете реализовать логику для проверки вводимых
// значений и выполнения других действий
empID - value;
} .}
Свойство С# состоит из двух блоков — блока доступа (get block) и блока изменения (set block). Ключевое слово val ue представляет правую часть выражения при присвоении значения посредством свойства. Как и все в С#, то, что представлено словом val ue — это также объект. Совместимость того, что передается свойству как value, с самим свойством, зависит от определения свойства. Например, свойство EmpID предназначено (согласно своему определению в классе) для работы с закрытым целочисленным empID, поэтому число 81 вполне его устроит:
// В данной случае типом данных, используемым для value, будет Int
еЗ.EmpID - 81:
Показать дополнительные возможности применения ключевого слова value можно на таком примере:
11 Свойство для ешрЮ
public Int EmpID
{
get {return empID:}
set
Средства инкапсуляции в С# 153
// Как еще можно использовать value
Console.WnteLlneC'value is the Instance of: {0}". value.GetTypeO):
Console.WriteLineC'value as string: {0}". value.ToStrlngO);
erapID = value;
Результат работы данной программы представлен на рис. 3.7.
•- ^ D:\CSharp8ook\Labs\Chapter 3\Cmploye_. НИ*
иа.1»е is an instance of :Int32
jalue as string :81
Press any Itey to continue
Рис. 3.7. Значение «value» при EmpID = 81
Необходимо отметить, что обращаться к объекту v a l u e можно только в пределах программного блока set внутри определения свойства. Попытка обратиться к этому объекту из любого другого места приведет к ошибке компилятора. Последнее, что мы отметим — использование свойств (по сравнению с традиционными методами доступа и изменения) делает применение ваших типов более простым. Например, предположим, что в классе Employee предусмотрена внутренняя закрытая переменная для хранения информации о возрасте сотрудника. Вы хотите, чтобы при наступлении дня рождения этого сотрудника значение этой переменной увеличивалось на единицу. При использовании традиционных методов доступа и изменения эта операция будет выглядеть так:
Employee joe * new EmployeeO;
joe.SetAge( joe.GetAgeO + 1):
Используя свойство, вы можете сделать РТО проще:
Employee joe = new EmployeeO;
joe.Ag&t+:
Внутреннее представление свойств С#
Многие программисты стараются использовать для имен методов доступа и изменения соответственно приставки get_ и set_ (например, get_Name() и set_Name()). Само по себе это не представляет проблемы. Проблему представляет другое: С# для внутреннего представления свойства использует методы с теми же самыми префиксами. К примеру, если вы откроете сборку EmpLoyees.exe при помощи утилиты ILDastn.exe, вы увидите, что каждому свойству соответствуют два отдельных; (и скрытых) метода (рис. 3.8).
Поэтому подобное определение класса вызовет ошибку компилятора:
Рис. З.8. Свойства отображаются в скрытые методы get_ и set_
Свойства только для чтения, только для записи и статические. Наш рассказ о свойствах классов С# будет неполон, если мы не упомянем еще о некоторых связанных с ними моментах. Как мы помним, наше свойство EmpID было создано как свойство, доступное и для чтения, и для записи.
Средства инкапсуляции в С# 155 здании пользовательских свойств класса часто возникает необходимость создать свойство, которое будет доступно только для чтения. Делается это очень просто: необходимо в определении свойства пропустить блок set. В результате свойство станет доступным только для чтения:
public class Employee
// Будем считать, что исходное значение этой переменной присваивается с помощью
// конструктора класса
private string ssn;
// А вот так выглядит свойство только для чтения
public string SSN ( get { return ssn: } }
C# также поддерживает статические свойства. Как мы помним, статические переменные предназначены для хранения информации на уровне всего класса, а не его отдельных объектов. Если у нас объявлены статические данные (то есть те же переменные), то обращаться к ним и устанавливать значения должны статические свойства. Предположим, что в нашем классе Employee мы собираемся, помимо всего прочего, хранить еще и информацию об имени организации, в которой работают все сотрудники — объекты класса Employee. Для этого будет использована специальная статическая переменная. Соответствующее статическое свойство для а боты с этой переменной может выглядеть так:
// Со статическими данными должны работать статические свойства
28. Наследование и виртуальные функции.