Uploaded by rikaru

Основы ООП

advertisement
Л. А. ЗАЛОГОВА
ОСНОВЫ
ОБЪЕКТНО ОРИЕНТИРОВАННОГО
ПРОГРАММИРОВАНИЯ
НА БАЗЕ ЯЗЫКА С#
Учебное пособие
Издание второе, стереотипное
САНКТ ПЕТЕРБУРГ
МОСКВА•КРАСНОДАР
2020
УДК 004
ББК 32.973.26 018я73
З 24
Залогова Л. А. Основы объектно ориентированного
программирования на базе языка С# : учебное пособие /
Л. А. Залогова. — 2 е изд., стер. — Санкт Петербург :
Лань, 2020. — 192 с. — (Учебники для вузов. Специаль
ная литература). — Текст : непосредственный.
ISBN 9785811447572
Учебное пособие посвящено основным принципам объектно
ориентированного программирования с использованием языка С#.
Рассматриваются классы, объекты, наследование, полиморфизм.
Особое внимание уделяется взаимодействию объектов. Изложение
сопровождается примерами.
Предназначено для студентов, владеющих навыками процедур
ного программирования, а также для тех, кто желает освоить осно
вы языка С#.
УДК 004
ББК 32.973.26 018я73
Рецензенты:
А. И. МИКОВ — доктор физико математических наук, профессор,
зав. кафедрой вычислительных технологий Кубанского государствен
ного университета; О. Л. ВИКЕНТЬЕВА — кандидат технических
наук, доцент, и. о. зав. кафедрой информационных технологий в биз
несе Национального исследовательского университета «Высшая
школа экономики» (г. Пермь).
Обложка
Е. А. ВЛАСОВА
© Издательство «Лань», 2020
© Л. А. Залогова, 2020
© Издательство «Лань»,
художественное оформление, 2020
Оглавление
Введение .................................................................................................................5
Глава 1. Введение в объектно-ориентированное программирование ...............7
1.1. Основные понятия .......................................................................................7
1.2. Принципы объектно-ориентированного программирования ..................9
1.2.1. Инкапсуляция ........................................................................................9
1.2.2. Наследование.......................................................................................13
1.2.3. Полиморфизм ......................................................................................15
Коротко о главном............................................................................................16
Задания ..............................................................................................................17
Глава 2. Основы программирования на языке С#.............................................18
2.1. Краткая историческая справка .................................................................18
2.2. Структура простого консольного приложения .......................................19
2.3. Типы ...........................................................................................................21
2.4. Константы ..................................................................................................23
2.5. Переменные................................................................................................24
2.6. Преобразования типов ..............................................................................25
2.6.1. Преобразования значений арифметического типа ...........................25
2.6.2. Преобразования значений строкового типа ......................................26
2.7. Операции и выражения .............................................................................28
2.8. Управление.................................................................................................33
2.9. Массивы значений.....................................................................................38
Коротко о главном............................................................................................42
Задания ..............................................................................................................43
Глава 3. Классы и объекты..................................................................................45
3.1. Описание классов ......................................................................................45
3.2. Создание и удаление объектов .................................................................47
3.3. Методы класса ...........................................................................................51
3.4. Конструкторы ............................................................................................54
3.5. Массивы объектов .....................................................................................57
3.6. Коллекция List ...........................................................................................60
3.7. Свойства .....................................................................................................64
3.8. Передача параметров ................................................................................69
3.8.1. Параметры значимого типа ................................................................70
3.8.2. Параметры ссылочного типа ..............................................................71
3.9. Перегрузка методов...................................................................................76
3.10. Статические методы и поля....................................................................81
3.10.1. Статические методы..........................................................................81
3.10.2. Статические поля ..............................................................................83
Коротко о главном............................................................................................86
Задания ..............................................................................................................87
3
Глава 4. Наследование.........................................................................................92
4.1. Понятие наследования ..............................................................................92
4.2. Особенности базового и производного классов .....................................93
4.3. Доступ к элементам класса и наследование............................................98
4.4. Конструкторы и наследование ...............................................................100
4.5. Создание многоуровневой иерархии .....................................................104
4.6. Абстрактные классы................................................................................108
Коротко о главном..........................................................................................108
Задания ............................................................................................................109
Глава 5. Полиморфизм ......................................................................................112
5.1. Ссылки на объект базового класса и объекты производных классов .112
5.2. Использование ссылки на объект базового класса ...............................116
5.3. Виртуальные методы...............................................................................117
5.4. Динамическое связывание ......................................................................119
5.5. Основные этапы реализации полиморфизма ........................................120
5.6. Приведение типов объектов ...................................................................124
5.7. Абстрактные методы...............................................................................126
Коротко о главном..........................................................................................128
Задания ............................................................................................................129
Глава 6. Интерфейсы .........................................................................................131
6.1. Описание и реализация интерфейсов ....................................................131
6.2. Использование стандартных интерфейсов............................................139
Коротко о главном..........................................................................................145
Задания ............................................................................................................146
Глава 7. Работа с файлами ................................................................................148
7.1. Текстовые и бинарные файлы ................................................................148
7.2. Исключения..............................................................................................149
7.3. Текстовый ввод/вывод ............................................................................151
7.3.1. Ввод информации из текстового файла...........................................151
7.3.2. Особенности ввода данных ..............................................................153
7.3.3. Вывод информации в текстовый файл ............................................155
7.4. Сериализация объектов...........................................................................158
7.4.1. Cериализация объектов в бинарный файл ......................................159
7.4.2. Десериализация объектов из бинарного файла ..............................163
Коротко о главном..........................................................................................167
Задания ............................................................................................................168
Глава 8. Взаимодействие объектов...................................................................170
8.1. Организация взаимодействия объектов.................................................170
8.2. Реализация взаимодействия объектов ...................................................172
Коротко о главном..........................................................................................188
Задания ............................................................................................................189
Список литературы............................................................................................191
4
Введение
Определенный способ мышления (парадигма) служит основой для
создания языка программирования. Существуют разные парадигмы
программирования – процедурная, объектно-ориентированная, функциональная, логическая. Каждая из парадигм используется для решения определенного класса задач. Некоторые языки поддерживают
несколько парадигм, другие же, наоборот, ориентированы на реализацию только одной парадигмы. Для каждого языка программирования одна из парадигм является основной, а другие парадигмы – дополнительными.
Как правило, для начинающих программистов первой парадигмой
становится процедурная, так как освоение предмета сопровождается
изучением языка Паскаль или С. Использование процедурной парадигмы предполагает, что программист составляет последовательность
вызовов процедур (функций) для управления выполнением программы. В процедурном программировании данные и процедуры (функции) для их обработки не связаны по смыслу. Дело в том, что любые
процедуры (функции) могут обращаться к любым данным при условии соответствия количества и типов параметров. Поэтому не существует способа защиты данных от неправильного использования.
Объектно-ориентированное программирование (ООП) является
результатом развития процедурного программирования, однако предлагает другой подход к разработке программ. В ООП данные и методы объединяются в классы, т. е. между ними устанавливается связь.
На основе классов создаются объекты – главные элементы программы. В процессе выполнения программы объекты взаимодействуют
между собой, т. е. обмениваются информацией.
В настоящее время знание основ объектно-ориентированного
программирования определяет успех во многих областях профессиональной деятельности.
В учебном пособии в лаконичной и доступной форме представлен
базовый курс по ООП. Основное внимание уделяется базовым понятиям и принципам ООП. Реализация же этих понятий и принципов
демонстрируется с использованием языка C#, одного из современных
перспективных языков программирования.
Текст пособия включает восемь глав. Первая глава посвящена обзору основных понятий и принципов ООП. Вторая глава содержит
описание базовых конструкций C#, необходимых для написания про5
грамм. В третьей главе рассматриваются понятия классов и объектов.
В четвертой главе описывается один из основных принципов ООП –
наследование. В пятой главе изложены особенности полиморфизма и
его реализация. Следующие две главы посвящены интерфейсам и методам работы с текстовыми и бинарными файлами. Парадигма ООП
предполагает, что любая программная система проектируется как совокупность взаимосвязанных объектов. Поэтому в восьмой главе на
примере практической задачи показана организация взаимодействия
объектов.
Изложение материала сопровождается содержательными примерами. Каждая глава завершается кратким изложением основных положений и заданиями из различных предметных областей.
Учебное пособие предназначено для студентов младших курсов,
начинающих изучать ООП. Кроме того, пособие заинтересует тех,
кто желает освоить основы ООП на базе языка C#. Единственное требование к освоению курса – владение навыками процедурного программирования.
6
Глава 1. Введение в объектно-ориентированное
программирование
В объектно-ориентированном программировании (ООП) любая
программа создается как совокупность взаимодействующих объектов. Шаблоны, на основе которых строятся объекты, называются
классами. Согласно первому принципу ООП – инкапсуляции в классе
определяются данные и действия, выполняемые над этими данными.
Классы, как правило, образуют иерархию. Второй принцип ООП –
наследование позволяет создать общий класс, который может быть
унаследован другими, более специализированными классами. И, наконец, третий принцип ООП – полиморфизм означает, что методы с
одним и тем же именем могут иметь разный код.
В этой главе рассматриваются основные понятия ООП, а также
три принципа – инкапсуляция, наследование и полиморфизм.
1.1. Основные понятия
Мы видим окружающий нас мир как множество объектов.
Объект – некоторая часть окружающей нас действительности
(человек, место, вещь и т. д.).
Объекты можно рассматривать с разных точек зрения. С одной стороны, как реальные, с другой – как абстрактные.
Абстрактный объект – информационная модель реального объекта.
Другими словами, абстрактный объект – это описание только тех
свойств оригинала, которые понадобятся при использовании.
Очевидно, что объект в компьютерной программе не может быть
реальным. Поэтому в дальнейшем нас будут интересовать только абстрактные объекты.
Каждый объект имеет определенные свойства (атрибуты) и поведение.
Атрибут – характеристика объекта.
7
Пример 1.1. Объекты и их атрибуты.
Объекты и их атрибуты представлены в таблице 1.1.
Таблица 1.1. Объекты и их атрибуты
Объект
Атрибуты объекта
Жесткий емкость,
диск
количество занятой памяти
Дата
день,
месяц,
год
имя,
дата создания,
Документ
местоположение,
объем занимаемой памяти
У каждого конкретного объекта атрибуты имеют определенные значения.
Состояние объекта – перечень его атрибутов и их текущих значений.
Изменение состояния объекта – изменение значений его атрибутов.
Поведение объекта – действия, которые могут выполняться над
объектом или которые может выполнять сам объект.
Метод – отдельное действие (вариант поведения).
Поведение объекта определяется его методами.
Примеры объектов, их атрибутов, значения атрибутов и поведения представлены в таблице 1.2.
Операционная система Windows является объектно-ориентированной. Документы и программы – информационные объекты. Аппаратные средства (дисководы, принтеры и др.) также представляются информационными объектами. Каждый объект Windows имеет
атрибуты (имя, графическое обозначение и др.) и поведение. Для знакомства с атрибутами объекта и его поведением используется контекстное меню.
8
Пример 1.2. Объекты, их атрибуты, значения атрибутов, поведение
(табл. 1.2).
Таблица 1.2. Объекты, их атрибуты и поведение
Объект
Жесткий
диск
Дата
Атрибуты
объекта
Значения
атрибутов
120 Гб
емкость,
объем
занятой 20 Гб
памяти
день,
месяц,
год
имя,
дата создания,
Документ местоположение,
объем занимаемой памяти
31
июнь
2009
main.doc
21 июля
2016
С:\Темр
50 Кб
Поведение (методы)
форматирование,
копирование
установить текущую
дату,
установить дату, следующую за текущей
датой
чтение,
открытие,
переименование,
удаление
1.2. Принципы объектно-ориентированного
программирования
1.2.1. Инкапсуляция
В реальном мире атрибуты объекта и его поведение объединены.
Инкапсуляция – объединение атрибутов и методов для их обработки.
Объект представляется совокупностью атрибутов и методов для
их обработки.
Обычно программы манипулируют множеством объектов с одинаковыми атрибутами и поведением. Эти объекты различаются значениями атрибутов (состоянием).
Пример 1.3. Использование нескольких объектов для представления
различных дат (табл. 1.3).
9
Таблица 1.3. Объекты для представления различных дат
Объект
Атрибуты
объекта
Значения
атрибутов
день,
Дата1 месяц,
год
31
июль
2015
день,
Дата2 месяц,
год
11
сентябрь
2016
день,
Дата3 месяц,
год
10
январь
2017
Поведение
установить текущую дату;
установить дату, следующую за текущей датой
установить текущую дату;
установить дату, следующую за текущей датой
установить текущую дату;
установить дату, следующую за текущей датой
Класс – описание множества объектов, имеющих одинаковые атрибуты и поведение.
Объекты Дата1, Дата2 и Дата3 в примере 1.3 принадлежат одному
классу.
В программировании понятие абстрактный тип определяется
как описание данных вместе с множеством действий, которые можно
выполнять над этими данными. Поэтому можно дать еще одно определение класса.
Класс – абстрактный тип, на основе которого создаются объекты.
В описании класса указываются:
имя класса;
атрибуты (поля);
методы.
Для наглядного представления классов воспользуемся диаграммами.
Диаграмма класса – наглядное представление класса на листе
бумаги.
В верхней части диаграммы класса записывается имя класса, а в
нижней – поля и методы (рис. 1.1).
10
Рис. 1.1. Диаграмма класса Data
Объект (экземпляр класса) – конкретная сущность, определенная
во времени и пространстве.
Можно создать произвольное количество объектов одного класса.
Объекты, относящиеся к одному классу, имеют одинаковые атрибуты
(поля) и поведение (методы). При этом каждый объект содержит собственные копии полей и методов класса.
Диаграмма объекта – наглядное представление объекта на листе
бумаги.
В верхней части диаграммы объекта записывается имя объекта и
имя класса, а в нижней – поля и методы (рис. 1.2, 1.3).
Два объекта одного класса отличаются только текущим состоянием, при этом можно изменить состояние одного объекта так, чтобы он
стал равным другому объекту.
Обращение к полям и методам класса:
имя_класса.имя_поля
имя_класса.вызов_метода.
Например,
Data.day – обращение к полю day класса Data,
Data.Next()– обращение к методу Next класса Data.
Обращение к полям и методам объекта:
имя_объекта.имя_поля
имя_объекта.вызов_метода.
Например,
birthday.month –
обращение к полю month объекта birthday,
myday.Set()– обращение к методу Set объекта myday.
11
Рис. 1.2. Диаграммы объектов birthday и myday класса Data
(поля объектов не инициализированы)
Рис. 1.3. Диаграммы объектов birthday и myday класса Data
(поля объектов инициализированы)
Рассмотрим особенности процедурного и объектно-ориентированного программирования.
В процедурном программировании данные не инкапсулируются,
так как любые методы (процедуры, функции) могут обращаться к
любым данным при условии соответствия количества и типов параметров. Рассмотрим пример. В процедурной программе (на языке С)
обрабатывается информация о студентах и преподавателях университета, при этом используется функция регистрации студента в библиотеке. Заголовок функции имеет вид
Registration (char* name /*имя*/,
int year /*год рождения*/).
Функцию Registration можно вызвать для любых данных символьного и целого типа. Поэтому программист может воспользоваться этой функцией не только для студента, но и для преподавателя
(имя преподавателя имеет тип char*, а год рождения – тип int).
Дело в том, что в процедурном программировании отсутствует связь
12
между данными и методами, т. е. не существует способа защиты данных от неправильного использования. Именно этот факт является недостатком процедурных языков.
В ООП данные и методы объединены. Использование класса
Student1 (рис. 1.4) исключает возможность регистрации преподавателя в студенческой библиотеке, так как только методы этого класса Set (инициализация ролей) и Registraion (регистрация студента в библиотеке) имеют доступ к полям name и year.
Рис. 1.4. Диаграмма класса Student1
и диаграммы объектов St11 и St12
Инкапсуляция позволяет поместить данные и методы в класс и
таким образом установить связь между ними.
1.2.2. Наследование
Наследование – такое отношение между классами, когда вновь
создаваемый класс строится на основе существующего класса-предка.
Базовый класс – класс, от которого производится наследование.
Производный класс – класс, который наследуется от другого
класса.
Базовый класс является более общим. Производный класс является более специфическим и, следовательно, менее общим.
13
Предположим, что имеется два класса Student2 и
GradStudent. Класс Student2 содержит поля name и year,
а также методы Set и Display. Метод Set инициализирует поля
объекта, а метод Display отображает значения этих полей на устройстве вывода. Класс GradStudent включает поля и методы студентов, а также содержит элементы, уникальные для аспирантов –
поля major и speciality, а также метод GetMajor, который выводит информацию о руководителе (рис. 1.5).
Рис. 1.5. Базовый класс Student2
и производный класс GradStudent
Диаграммы объектов этих классов представлены на рис. 1.6.
Так как классы могут наследовать атрибуты и поведение других
классов, уменьшается объем нового кода, который нужно создать при
разработке программы.
Если не использовать наследование, то для каждого класса пришлось бы в явной форме определять все входящие в его состав элементы. В этом случае для класса GradStudent пришлось бы явно
описать четыре поля: name, year, major и speciality.
14
Благодаря наследованию класс нужно доопределить только теми элементами, которые делают его более специфическим. Наследование
дает возможность сравнительно быстро создавать новые (производные) классы, добавляя поля и методы в базовые классы.
Рис. 1.6. Диаграммы объектов класса Student2
и объектов класса GradStudent
1.2.3. Полиморфизм
Полиморфизм (многообразие) означает, что методы с одним и
тем же именем могут реализовывать различное поведение, т. е. иметь
разный код. Решение о том, какой метод вызвать, принимается на
этапе выполнения программы.
Классы Student и GradStudent содержат методы Set и
Display. Оба класса спроектированы таким образом, что методы
Set и Display выполняют похожие задачи, но делают это поразному. Если бы в каждом из классов использовались разные названия метода Display, например StudDisplay и GradDisplay,
программисту пришлось бы запоминать названия методов обоих
классов. Это может быть трудной задачей, особенно в тех случаях,
15
когда классы содержат много методов. Предпочтительнее определить
методы с одинаковым названием, но разным поведением. Например,
можно организовать список, в котором в произвольном порядке будут чередоваться объекты классов Student и GradStudent. Тогда
при последовательном просмотре элементов списка и обращении к
методу Display будет вызываться подходящая версия этого метода.
Если текущий элемент списка принадлежит классу Student, метод
Display напечатает имя и год. Если же элемент списка принадлежит классу GradStudent, метод Display напечатает имя, год, фамилию руководителя и специальность. Полиморфизм дает возможность взаимодействовать с объектом, не зная, к какому конкретному классу он принадлежит – базовому или производному.
Коротко о главном
1. Атрибут – характеристика объекта. Состояние объекта – перечень его атрибутов и их текущих значений.
2. Поведение объекта – действия, которые могут выполняться над
объектом или которые может выполнять сам объект.
3. Принципы ООП – инкапсуляция, наследование, полиморфизм.
4. Инкапсуляция – объединение атрибутов и методов для их обработки.
5. Класс – описание множества объектов, имеющих одинаковые
атрибуты и поведение.
6. Объект – экземпляр класса. Объекты, относящиеся к одному
классу, имеют одинаковые атрибуты и поведение. Каждый объект содержит собственные копии полей и методов класса.
7. Диаграмма класса (объекта) – наглядное представление класса
(объекта) на листе бумаги.
8. Наследование – такое соотношение между классами, когда
вновь создаваемый класс строится на основе существующего классапредка.
9. Базовый класс – класс, от которого производится наследование.
10. Производный класс – класс, который наследуется от другого
класса.
11. Полиморфизм означает, что методы с одним и тем же именем
могут реализовывать различное поведение, т. е. иметь разный код.
Решение о том, какой метод вызвать, принимается на этапе выполнения программы.
16
Задания
Задание 1.1
1. Определить поля и методы классов:
а) «Автомобиль»; б) «Компания»; в) «Редакция газеты».
2. Изобразить диаграммы классов из пункта 1.
3. Изобразить примеры диаграмм для объектов классов из пункта 2.
Задание 1.2
1. Транспортная компания владеет самолетами и автомобилями.
Характеристики самолета: мощность, стоимость, номер, марка,
максимальная высота полета.
Характеристики автомобиля: мощность, стоимость, номер, марка,
пробег, пройден ли техосмотр.
Используя наследование, изобразить диаграммы классов «Автомобиль» и «Самолет».
2. Изобразить примеры диаграмм для объектов классов из пункта 1.
17
Глава 2. Основы программирования на языке С#
В этой главе рассматриваются основные конструкции языка С#,
необходимые для написания программ (структура консольного приложения, типы, выражения, управление). Более полная информация
изложена в [1].
2.1. Краткая историческая справка
Процедурный язык С разработан Дэнисом Ритчи в 1970-х гг. Использование процедурного программирования для создания больших
программных систем связано с определенными трудностями (большие программы трудно создавать, отлаживать, сопровождать). Поэтому стали появляться новые подходы к программированию. Один
из них – объектно-ориентированный. В то время самый популярный
язык С не поддерживал принципы ООП. Был создан язык С++. Разработчик С++ – Бьярни Страуструп (1979). Сначала новый язык был назван «С с классами», но в 1983 г. это название изменили на С++.
С++ полностью включает элементы языка С. Таким образом, С –
фундамент, на котором построен С++. Большинство дополнений, которые Страуструп внес в С, были предназначены для поддержки
ООП.
На протяжении 1980-х гг. С++ интенсивно развивался и к началу
1990-х гг. получил широкое распространение. В наши дни С++ – универсальный язык программирования для решения задач из различных
предметных областей, в том числе задач системного программирования.
Подход при разработке С++ – взять за основу существующий
язык и поднять его на новую ступень развития. Этот подход был использован и при разработке языка C#.
Объектно-ориентированный язык C# создан в конце 1990-х гг.
Первая реализация C# появилась в середине 2000 г. Один из главных
авторов C# – Андрес Хейлсберг (он был автором turbo Pascal). Хейлсберг не ставил перед собой задачу создания абсолютно нового языка.
Он усовершенствовал и добавил новые возможности в уже существующие языки. C# связан с С++ и С (рис. 2.1).
18
Рис. 2.1. Связь языков С, С++ и C#
2.2. Структура простого консольного приложения
В консольных приложениях для взаимодействия с пользователем
используется одно окно. Информация выводится только в символьном режиме. Консольные приложения наилучшим образом подходят
для изучения языка, так как в них не используется множество стандартных объектов, необходимых для создания графического интерфейса. Сначала, как правило, учатся создавать именно консольные
приложения. Такой подход позволяет сосредоточиться на основных
конструкциях языка, которые в дальнейшем будут использоваться
для создания графических приложений.
Для описания конструкций языка воспользуемся соглашениями:
1) ключевые слова записываются полужирным шрифтом;
2) конструкции, требующие дальнейшей расшифровки, – прописными буквами.
Простое консольное приложение – класс, содержащий метод
Main:
using
System;
class ИМЯ_КЛАССА {
public static void Main() {
ОПИСАНИЯ
ОПЕРАТОРЫ
}
}
19
Powered by TCPDF (www.tcpdf.org)
Пример 2.1. Первая программа на С#.
using System;
class First {
public static void Main() {
Console.WriteLine
(“First C#-program”);
}
}
Файлы с программами на С# имеют расширение .cs. Например,
first.cs.
Пояснения к программе
1. Язык С# содержит большой набор предопределенных классов.
В целях упорядочивания классы группируются в пространства имен.
Пространство имен в С# – совокупность логически связанных
классов.
Некоторые пространства имен, например System, очень велики,
другие же, наоборот, содержат всего несколько классов. При создании приложения несколькими программистами совпадение имен
классов исключено, так как на каждый класс можно сослаться с использованием имени его пространства имен.
Предложение
using
System;
означает, что в программе будет использовано пространство имен
System.
2. Каждая программа на C# должна содержать метод Main.
Именно этот метод вызывается средой исполнения при запуске программы, т. е. с него начинается выполнение любого приложения. Если среда исполнения не находит метод Main, то выдается сообщение
об ошибке. Метод Main всегда объявляется как public и
static. Эти ключевые слова будут рассмотрены в разделах 3.1
и 3.10.
3. Console – класс пространства имен System.
4. Console.WriteLine("First C#-program") – обращение к методу WriteLine класса Console для вывода на экран
строки "First C#-program".
20
Если предложение using System; в программе отсутствует, то
вызов метода WriteLine записывается так:
System.Console.WriteLine ("First C#-program").
2.3. Типы
В С# все константы, переменные, а также значения выражений
имеют определенный тип.
Существуют разные способы классификации типов.
1. Простые и составные (скалярные и структурные) типы.
Значение простого (скалярного) типа не распадается на составляющие. Значение составного (структурного) типа состоит из нескольких компонент.
2. Встроенные типы и типы, определяемые пользователем.
Встроенные типы не требуют предварительного определения. Типы,
определяемые пользователем, создаются и описываются в программе.
3. Значимые и ссылочные типы.
Переменная типа значения содержит значение. Например, тип
int является значимым типом (рис. 2.2).
int number;
number = 4567;
4567
4567 – значение
Рис. 2.2. Описание и графическое представление
переменной типа значения
Доступ к значениям ссылочных типов выполняется через ссылочную переменную (ссылку), которая содержит адрес объекта в памяти.
Ссылка – адрес объекта в памяти.
Пример 2.2. Строка – переменная ссылочного типа (рис. 2.3).
string mytext; // объявление ссылки на строку
mytext = "My program";
Рис. 2.3. Описание и графическое представление строки –
переменной ссылочного типа
21
Строка "My program" размещается в памяти и ее адрес запоминается в переменной mytext.
Пример 2.3. Ввод/вывод строк.
string s,s1;
s = "string1";
Console.WriteLine (s); // вывод строки
s = Console.ReadLine (); // ввод строки
Console.WriteLine (s);
Метод ReadLine считывает с клавиатуры символы до тех пор,
пока не будет нажата клавиша <Enter>, и возвращает строку. Метод
WriteLine выводит строку на экран.
Встроенные типы С# приведены в таблице 2.1.
Таблица 2.1. Встроенные типы С#
Представление,
размер
Знаковое, 8 бит
Беззнаковое,
byte
System.Byte
8 бит
Знаковое,
short
System.Short
16 бит
ushort System.UShort Беззнаковое,
16 бит
Целый
Знаковое,
int
System.Int32
32 бита
uint
System.UInt32 Беззнаковое,
32 бита
Знаковое,
long
System.Int64
64 бита
Беззнаковое,
ulong
System.UInt64
64 бита
float
System.Single 32 бита
Вещественный
double System.Double 64 бита
decimal System.Decimal 128 бит
Финансовый
System.Boolean 1 бит
bool
Логический
char
System.Char
Символьный
16 бит (Unicode)
string System.String
Строковый
Тип значения
Ключевое
Системный тип
слово
sbyte
System.SByte
22
Встроенные типы соответствуют системным типам (классам), определенным в пространстве имен System.
В языке С# четко определено, какие типы относятся к ссылочным, а какие – к значимым. Например, логический и арифметические
типы – значимые; массивы, строки, классы – ссылочные типы.
2.4. Константы
Способ представления констант зависит от их типа.
Символьные константы заключаются между двумя одинарными кавычками. Например, 'k' или '3'. Строковые константы записываются между двойными кавычками ("Hello!").
Константы целого типа – числа без дробной части. Например,
200 и -79 – целочисленные константы. Если значение целой константы находится внутри диапазона допустимых значений типа int, константа рассматривается как int, иначе она относится к наименьшему
из типов uint, long или ulong. Например, константа 345 относится к типу int, а константа 2147483648 – к типу uint.
Суффиксы для явного задания типа константы перечислены в
таблице 2.2.
Таблица 2.2. Суффиксы констант
Суффикс
L, l
U, u
F, f
D, d
M, m
Значение
Длинное целое (long)
Беззнаковое целое (unsigned)
Вещественное с одинарной точностью (float)
Вещественное с двойной точностью (double)
Финансовое (decimal)
Вещественные константы должны иметь дробную часть. Пример такой константы – число 123.56. Для вещественных констант
также используется экспоненциальное представление, например,
0.12345Е+3 или 123.45Е-3. По умолчанию все вещественные
константы имеют тип double. Поэтому в результате компиляции
фрагмента программы
23
float myvariable;
myvariable = 123.56; // ! ошибка
будет выдано сообщение об ошибке. Чтобы задать константу типа
float, нужно присоединить к ней справа букву f (суффикс) или F:
float myvariable;
myvariable = 123.56f; // верно
Константа типа decimal имеет суффикс m или M:
decimal mydec;
mydec1 = 123.56m; //верно
mydec2 = 123.56; // ! ошибка
Константы логического типа – true и false.
2.5. Переменные
Переменная – именованная область памяти, в которую можно
записать значение.
Все переменные должны быть описаны до их использования. При
описании задается имя переменной и ее тип:
ТИП ИМЯ_ПЕРЕМЕННОЙ;
Имя переменной должно начинаться с буквы или символа подчеркивания и состоять из латинских букв, цифр и подчеркиваний.
Замечание. В C# принято соглашение: первый символ имени переменной – строчная буква, а первый символ каждого нового слова,
входящего в имя, – прописная буква. Например,
myFirstExample или my_First_Example.
Переменная должна получить значение до использования. Это
можно сделать с помощью оператора присваивания либо одновременно с объявлением переменной. Например,
double number1; //объявление переменной
number1 = 345.5678;// инициализация переменной
// с помощью оператора присваивания
double number2 = 6785.24156;// инициализация
// переменной при объявлении
24
Следует помнить, что имя переменной должно отражать ее содержательный смысл.
В C# можно объявить несколько переменных одного типа в виде
списка, при этом одной или нескольким переменным присвоить начальные значения:
int current_Value, myDay, last_Number = 500;
Пример 2.4. Использование вещественных переменных в программе
на C#.
using System;
//Вычисление площади круга
class Circle {
public static void Main() {
double radius, area;
radius=10.0;
area=radius*radius*3.1416;
Console.WriteLine ("Площадь = "+ area);
}
}
При инициализации переменной можно использовать не только
константу, но и выражение, значение которого вычислено к моменту
описания этой переменной:
double s1 = 6.78, s2 = 5.0;
double d3 = s1*s1+s2*s2;//инициализация переменной
//значением выражения
2.6. Преобразования типов
Все операции в выражении проверяются компилятором на соответствие типов операндов. Если при вычислении выражения операнды операции имеют разные типы, то возникает необходимость преобразования их к одному типу. Кроме этого, компилятор проверяет
соответствие типов формальных и фактических параметров при вызове функции.
2.6.1. Преобразования значений арифметического типа
Преобразования значений арифметического типа (целых, вещественных, финансовых) могут быть двух видов – неявные и явные.
25
Неявные преобразования – это преобразования одного типа в
другой, которые выполняются компилятором автоматически.
Неявное преобразование возможно в тех случаях, когда оно не
ведет к потере точности данных. Например,
float myfloat = 12.5f;
double mydouble = 34.98;
mydouble = myfloat;//верно, так как
//32- разрядное значение преобразуется в
//64-разрядное значение
myfloat = mydouble;//! неверно, так как
//64- разрядное значение преобразуется в
// 32-разрядное значение
Во всех остальных случаях необходимо выполнять приведение к
типу, т. е. явное преобразование. Явные преобразования могут приводить к потере точности, поэтому они выполняются программистом,
который полностью отвечает за получаемые результаты.
Операция приведения типа имеет вид
(ТИП) ВЫРАЖЕНИЕ
Здесь ТИП определяет тот тип, к которому должно быть преобразовано выражение. Например,
double x,y; int z;
float p;
x = 25.75; y = 4.654;
//выражение типа double преобразуется
//к int; при этом дробная часть отбрасывается
z = (int) (x/y);
//выражение типа double преобразуется к float;
p = (float) (x+y);
2.6.2. Преобразования значений строкового типа
Преобразования значений строкового типа в значения арифметического типа, и наоборот, должны выполняться явно. Однако операция приведения типа вида (ТИП) ВЫРАЖЕНИЕ может использоваться только для арифметических типов. Поэтому последовательность
действий
string mystr; int x;
x= (int)(Console.ReadLine());// ошибка!
26
приведет к возникновению ошибки. При этом компилятор выдаст сообщение
Cannot convert type 'string' to type 'int'.
В этом случае можно воспользоваться методами класса Convert
пространства имен System. Класс Convert содержит методы с
именами To<Type>, где <Type> принимает значения Int32,
UInt32, Int64, UInt64, Double, Single, Decimal,
String и др. Для всех арифметических типов метод ToString()
преобразует значение соответствующего типа в строку.
Пример 2.5. Использование класса Convert для преобразования
значений разных типов:
int x;
double d2 = 345.3456476784;
int x1 = 456;
double d3 = 456.413896;
//преобразование строки к значению типа int
x = Convert.ToInt32 (Console.ReadLine());
// преобразование значения типа double к значению
//типа float
float f2 = Convert.ToSingle(d2);
// преобразование значения типа int в строку
string mystr = Convert.ToString(x1);
// преобразование значения типа double в строку
string mystr1 = Convert.ToString(d3);
Кроме того, для преобразования строки в число можно воспользоваться другим способом. Встроенные типы языка С# реализованы
как классы, имеющие поля и методы. В классе int существует метод
Parse, у которого аргумент – символьное представление целого
числа, а результат – внутреннее представление этого числа. Аналогичный метод Parse реализован в классах float, double и др.
Пример 2.6. Использование метода Parse классов int, double и
float для преобразования строки в число
int x;
float y;
double z;
27
// преобразование строки к значению типа int
x = int.Parse (Console.ReadLine());
// преобразование строки к значению типа float
y = float.Parse (Console.ReadLine());
// преобразование строки к значению типа double
z = double.Parse (Console.ReadLine());
В отличие от метода ReadLine метод WriteLine выполняет
неявное преобразование типов, т. е. принимает внутреннее представление чисел и логических значений, переводит их в строку и выводит
в окно консоли.
Неявное преобразование типов происходит также при объединении строк операцией +. Все числа и логические значения при этом
преобразуются к строковому типу.
Пример 2.7. Напечатать значения переменных с пояснительным
текстом.
Вариант 1
double z1 = 23.45; int z2 = 15;
Console.WriteLine ("z1 = " + z1 + "
Вариант 2
double z1 = 23.45; int z2 = 15;
string str = "z1= " + z1 + " z2 =
Console.WriteLine (str);
z2 = " + z2);
" + z2;
Результат печати:
z1 = 23,45 z2 = 15
2.7. Операции и выражения
Операция – действие, вырабатывающее некоторое значение, называемое результатом операции. Исходные данные для операции – ее
операнды.
Операции делятся на группы в зависимости от типов операндов и
результата.
Арифметические операции применяются к значениям простых
числовых типов (табл. 2.3).
28
Если в арифметических операциях используются целые числа, результатом вычислений будет целое число, при этом остаток от деления отбрасывается. Операция % определена над значениями всех
арифметических типов и возвращает остаток от деления нацело; тип
результата зависит от типов операндов.
Таблица 2.3. Арифметические операции
Операция
Описание
+
Сложение
*
/
Вычитание
Умножение
Деление
%
Остаток от деления
Операции отношения сравнивают два значения и возвращают
значение логического типа (true или false).
Операции отношения приведены в таблице 2.4.
Таблица 2.4. Операции отношения
Операция
Описание
==
Равно
!=
Не равно
>
Больше
<
Меньше
>=
Больше или равно
<=
Меньше или равно
Логические операции выполняются над значениями логического
типа и возвращают значение true или false (табл. 2.5).
Операция «логическое И» возвращает true только тогда, когда
оба операнда равны true, в противном случае – false. Если первый операнд этой операции равен false, то второй операнд не вычисляется и результату операции присваивается значение false.
Значение операции «логическое ИЛИ» равно true, если хотя бы
29
один операнд имеет значение true. Операция «логическое НЕ» возвращает значение, логически обратное значению операнда.
Таблица 2.5. Логические операции
Операция
&&
Описание
Логическое И
||
Логическое ИЛИ
!
Логическое НЕ
Побитовые операции применяются к значениям целочисленного
типа для работы с битами (табл. 2.6).
Таблица 2.6. Побитовые операции
Операция
Описание
&
Побитовое И
|
Побитовое ИЛИ
^
Побитовое исключающее ИЛИ
~
Побитовое НЕ
<<
Побитовый сдвиг влево
>>
Побитовый сдвиг вправо
Операции «побитовое И», «побитовое ИЛИ» и «побитовое
исключающее ИЛИ» последовательно сравнивают два бита в соответствующих позициях операндов. Операция «побитовое И» возвращает 1, если оба бита равны 1, иначе – 0. Значение операции «побитовое ИЛИ» равно 1, если один из битов равен 1. Операция «побитовое исключающее ИЛИ» возвращает 1, если один из операндов равен
1; если же операнды имеют одинаковые значения, то – 0.
Унарная операция «побитовое НЕ» возвращает 1, если значение
бита равно 0, иначе – 1.
Операция «побитовый сдвиг влево» сдвигает биты числа влево на
указанное количество позиций. Биты, которые сдвигаются за левый
край ячейки, теряются. Новые же биты, которые добавляются к числу
справа, устанавливаются равными 0. Аналогично операция «побитовый сдвиг вправо» сдвигает биты числа вправо. Биты, которые сдви30
гаются за правый край ячейки, теряются; биты, которые добавляются
к числу слева, устанавливаются равными 0.
Операция «присваивание» устанавливает значение одной или
нескольких переменных.
Левая часть операции присваивания – список переменных, разделенных знаком =. Правая часть – выражение.
Синтаксис операции присваивания:
ПЕРЕМЕННАЯ = ВЫРАЖЕНИЕ
ПЕРЕМЕННАЯ_1 = . . . = ПЕРЕМЕННАЯ_N = ВЫРАЖЕНИЕ
Выражение правой части вычисляется и все переменные левой
части получают значение вычисленного выражения. При этом переменные левой части должны иметь одинаковый тип или неявно приводиться к одному типу.
Для присваиваний вида
ПЕРЕМЕННАЯ = ПЕРЕМЕННАЯ ОПЕРАЦИЯ ВЫРАЖЕНИЕ
допускается краткая форма записи
ПЕРЕМЕННАЯ ОПЕРАЦИЯ = ВЫРАЖЕНИЕ
В краткой форме разрешается использовать арифметические, логические и побитовые операции. Например, присваивание x=x+10
можно записать так: х+=10.
Операции ++ и -- используются для добавления и удаления единицы из операнда.
Операции ++ и -- могут быть префиксными (++х и --х) и постфиксными (х++ и х--). В префиксных операциях увеличение/уменьшение выполняется до использования переменной, в постфиксных же операциях, наоборот, – после использования.
Например,
int x, n;
n = 15; x = ++n;
Console.WriteLine("x="+x + " n="+n);
n= 15; x = n++;
Console.WriteLine("x=" + x + " n="+ n);
Результат печати:
x=16 n=16
x=15 n=16
31
Операция sizeof применяется к значимому типу и возвращает его размер в байтах. Результат выполнения операции
sizeof(double) равен 8.
Операция is определяет принадлежность выражения заданному
типу и возвращает логическое значение. Например,
int z = 0;
// значение операции is – true:
bool compare = z is int;
Эта операция обычно используется при работе с иерархиями классов.
Выражение состоит из операндов – констант, переменных, вызовов функций, соединенных знаками операций и скобками. Выражение всегда возвращает значение, тип которого определяется типами
операндов. В таблице 2.7 все операции языка C# сгруппированы по
категориям в порядке уменьшения приоритета.
Таблица 2.7. Приоритеты операций языка C#
Приоритет
0
1
2
3
4
5
6
7
8
9
10
11
12
13
Категория
Операции
(x)
x.y
f(x)
a[x]
x++
x-new sizeof
typeof checked unchecked
+ - ! ~ ++x --x (type)x
Унарные
Мультипликативные * / %
+ Аддитивные
<< >>
Побитовые сдвиги
Отношения,
< > <= >= is
as
проверка типов
== !=
Равенства
&
Побитовое И
Побитовое исклю^
чающее ИЛИ
|
Побитовое ИЛИ
&&
Логическое И
||
Логическое ИЛИ
Условное выражение ? :
= *= /= %= += -= <<= >>=
Присваивание
&= ^= |=
Первичные
32
Вычисление выражения выполняется в соответствии с приоритетом (старшинством) операций языка программирования.
2.8. Управление
Оператор присваивания
В языке C# присваивание – операция, используемая в выражениях. Однако в большинстве случаев присваивание используется как
обычный оператор. Например,
double x, y, z, u=5.8; v= 6.9;
x = y = z = (u + v)/(u - v);
Важно понимать различие между присваиванием для величин значимых и ссылочных типов. В случае присваивания значения переменной значимого типа изменяется содержимое памяти, занимаемой
этой переменной. Ссылочное присваивание – это операция над ссылками. Поэтому в результате ссылочного присваивания ссылка будет
указывать на другой объект и, таким образом, у одного объекта появится несколько ссылок. Подробнее о ссылочном присваивании изложено в разделе 3.2.
Составной оператор
Фигурные скобки объединяют несколько операторов в составной
оператор.
Синтаксис составного оператора:
{
ОПЕРАТОР_1
. . .
ОПЕРАТОР_N
}
В соответствии с синтаксисом языка C# символ « ; » является неотъемлемой частью оператора. Поэтому в приведенной выше записи
синтаксиса составного оператора точка с запятой входит в конструкцию ОПЕРАТОР. Однако символом завершения составного оператора
является фигурная скобка «}», а не точка с запятой.
Условный оператор
Синтаксис полного условного оператора:
if (ВЫРАЖЕНИЕ) ОПЕРАТОР_1 else ОПЕРАТОР_2
33
Если ВЫРАЖЕНИЕ справедливо, то выполняется ОПЕРАТОР_1, в
противном случае - ОПЕРАТОР_2. ВЫРАЖЕНИЕ должно заключаться в круглые скобки и быть логического типа. Операторы могут быть
любыми.
Синтаксис неполного условного оператора:
if (ВЫРАЖЕНИЕ) ОПЕРАТОР
Пример 2.8. Использование условного и составного операторов.
Вычислить значение квадратного корня числа, введенного с клавиатуры
using System;
class Program {
public static void Main(){
Console.WriteLine("Введите число");
double x =double.Parse(Console.ReadLine());
double result;
if (x < 0.0)
Console.WriteLine
("Число не может быть меньше 0.0");
else { result = Math.Sqrt(x);
Console.WriteLine
("Результат = " + result);
}
}
}
Оператор выбора
Оператор выбора используется для сравнения выражения с набором значений и выбора одного из нескольких фрагментов кода.
Синтаксис оператора выбора:
switch( ВЫРАЖЕНИЕ ) {
case ЗНАЧЕНИЕ_1: ОПЕРАТОРЫ_1
break;
case ЗНАЧЕНИЕ_2: ОПЕРАТОРЫ_2
break;
. . .
default: ОПЕРАТОРЫ
break;
}
34
Значение, которое возвращает выражение, сравнивается с каждым
из значений, которые следуют после ключевого слова case. Если
встречается совпадение, то выполняется код, расположенный после
соответствующего ключевого слова case. Например, если значение
выражения совпадает со значением ЗНАЧЕНИЕ_2, то выполняются
ОПЕРАТОРЫ_2. Оператор break завершает выполнение оператора
выбора.
Пример 2.9. Использование оператора выбора.
Даны день, месяц и год. Определить правильность задания даты.
Используем факт: год является високосным, если делится на 4, за исключением тех годов, которые оканчиваются на 00 и не делятся на
400. Например, 1700 год не является високосным.
using System;
class Program{
public static void Main(){
int day, month,year;
int k; bool test = false;
Console.WriteLine
("Введите дату:день,месяц,год (три числа)");
day = int.Parse(Console.ReadLine());
month= int.Parse(Console.ReadLine());
year= int.Parse(Console.ReadLine());
switch (month) {
case 4:
case 6:
case 9:
case 11: test = day < 31; break;
case 2:
if ((year % 4 == 0 && year % 100 != 0)
||year % 400 == 0)
k = 29;
else k = 28;
test = day <= k; break;
case 1:
case 3:
case 5:
case 7:
case 8:
35
case 10:
case 12: test = day <= 31; break;
default: Console.WriteLine
("Неверно задан месяц");
break;
}
if (test) Console.WriteLine
("Дата задана верно");
else Console.WriteLine
("Дата задана неверно");
}
}
Оператор цикла с предусловием
Оператор цикла с предусловием используется в тех случаях, когда необходимо повторять один или несколько операторов до тех пор,
пока заданное логическое выражение равно true.
Синтаксис оператора цикла с предусловием:
while (ВЫРАЖЕНИЕ) ОПЕРАТОР
ВЫРАЖЕНИЕ должно быть логического типа;
ОПЕРАТОР повторяется до тех пор, пока ВЫРАЖЕНИЕ не станет ложным.
Чтобы избежать зацикливания, переменные, входящие в ВЫРАЖЕНИЕ, должны изменяться в теле цикла.
Пример 2.10. Использование оператора цикла с предусловием.
Найти сумму кубов первых n натуральных чисел
using System;
class Loop {
public static void Main(){
int n, s=0;
Console.WriteLine ("Количество чисел?");
n = int.Parse(Console.ReadLine ());
while (n > 0) {
s += n*n*n;
n--;
}
36
Console.WriteLine ("результат = "+ s);
}
}
Если ввести отрицательное значение, то тело цикла не выполнится ни разу, а результат будет равен 0.
Оператор цикла с постусловием
Тело оператора цикла с предусловием может не выполниться ни
разу. В ряде случаев возникает необходимость выполнить тело цикла
хотя бы один раз независимо от результата проверки условия. В этом
случае можно воспользоваться оператором цикла с постусловием, в
котором проверка условия выполняется при выходе из цикла.
Синтаксис оператора цикла с постусловием:
do
ОПЕРАТОР
while (ВЫРАЖЕНИЕ);
Цикл с постусловием выполняется до тех пор, пока ВЫРАЖЕНИЕ
остается истинным.
Оператор цикла с параметром
Синтаксис оператора цикла с параметром:
for (ИНИЦИАЛИЗАТОРЫ; УСЛОВИЕ;
СПИСОК_ВЫРАЖЕНИЙ) ОПЕРАТОР
 ИНИЦИАЛИЗАТОРЫ задают начальное значение одной или нескольких переменных, называемых параметрами цикла; в большинстве случаев цикл for имеет один параметр цикла;
 УСЛОВИЕ задает условие продолжения цикла; если условие
становится ложным, цикл завершает работу;
 выражения, записанные в СПИСКЕ ВЫРАЖЕНИЙ через запятую,
показывают, как изменяются параметры цикла на каждом шаге выполнения тела цикла.
Пример 2.11. Использование оператора цикла с параметром.
Найти сумму кубов первых n натуральных чисел
using System;
class Program{
public static void Main(){
int n, summa, i;
37
Console.WriteLine ("Количество чисел?");
n = int.Parse(Console.ReadLine());
summa = 0;
for (i = n; i > 0; i--) summa +=i*i*i;
Console.WriteLine(" summa = " + summa);
}
}
Параметр цикла сохраняет свое значение при выходе из цикла.
Оператор return
Оператор return используется для возврата из метода, а также
для возвращения методом значения.
Синтаксис оператора return:
return
или
return ВЫРАЖЕНИЕ
ВЫРАЖЕНИЕ определяет способ вычисления значения, возвращаемого методом.
2.9. Массивы значений
Массив – ограниченная последовательность однотипных элементов.
Объявление одномерного массива:
ТИП [ ] ИМЯ_МАССИВА = new ТИП [РАЗМЕР];
ТИП – тип элемента
[ ] – обозначение одномерного массива
ИМЯ_МАССИВА – ссылочная переменная на массив
new – оператор выделения памяти под массив
[РАЗМЕР] – количество элементов массива
Создание массива значений выполняется в два этапа:
1) объявление ссылочной переменной на массив (рис. 2.4а);
2) выделение памяти для элементов массива и сохранение адреса выделенной памяти в ссылочной переменной (рис. 2.4б).
38
Powered by TCPDF (www.tcpdf.org)
Рис. 2.4. Графическое представление одномерного массива значений
Индексы (номера) элементов задаются значениями целого типа,
при этом нижняя граница равна 0.
Для обращения к элементу одномерного массива необходимо
указать имя массива, за которым в квадратных скобках следует индекс этого элемента.
Пример 2.12. Инициализация и печать одномерного массива.
Верхняя граница массива – константа
using System;
class Array1Demo {
public static void Main() {
int [ ] sample = new int [10];
int i;
for (i=0; i<10; i++) sample[i] = i;
for (i=0; i<10; i++)
Console.WriteLine ("sample[" + i + "]= "
+ sample[i]);
}
}
В С# верхняя граница массива может задаваться не только константой, но и переменной, значение которой должно быть определено
в момент объявления массива. В этом случае размер массива определяется во время выполнения программы.
Пример 2.13. Инициализация и печать одномерного массива.
Верхняя граница массива задается переменной
using System;
class Array2Demo {
public static void Main(){
Console.WriteLine
(" введите число элементов массива");
int size = int.Parse(Console.ReadLine());
39
int [] table = new int [size];
int i;
for ( i = 0; i < size; i++) table[i] = i;
Console.WriteLine(" элементы массива:");
for (i = 0; i < size; i++)
Console.Write (" " + table[i]);
}
}
В тех случаях, когда значения элементов массива известны заранее, можно воспользоваться их инициализацией при объявлении массива:
ТИП [ ] ИМЯ_МАССИВА =
{ЗНАЧЕНИЕ1, ЗНАЧЕНИЕ2,. . ., ЗНАЧЕНИЕN};
при этом new не используется, так как память выделяется автоматически.
Пример 2.14. Инициализация одномерного массива целых чисел при
объявлении
int [ ] money = { 5000, 1000, 500, 100, 50 };
В C# используются многомерные массивы.
Объявление двумерного массива:
ТИП [ , ] ИМЯ_МАССИВА =
new ТИП [РАЗМЕР,РАЗМЕР];
Пример 2.15. Инициализация двумерного массива целых чисел при
объявлении
int [ , ] my_table =
{{ 10, 20, 30}, {40, 50, 60 }};
Описание и инициализация элементов массива большей размерности выполняются аналогично. В этом случае число запятых, увеличенное на единицу, задает размерность массива.
Пример 2.16. Ввод двумерного массива с клавиатуры и вывод на
экран
using System;
class Array3Demo {
public static void Main() {
int [ , ] table = new int [3,3];
40
int i,j;
Console.WriteLine("Введите девять чисел");
for (i=0; i<3; i++)
for (j=0; j<3; j++)
table [i,j] =
int.Parse(Console.ReadLine());
for (i = 0; i < 3; i++){
Console.WriteLine();
for (j = 0; j < 3; j++)
Console.Write (table [i,j] + " ");
}
}
}
При печати матрицы все числа должны быть аккуратно выравнены. Язык C# предоставляет удобный способ управления форматом
вывода. В этом случае метод WriteLine (Write) имеет более
одного параметра – формат вывода и список вывода:
Console.WriteLine("формат вывода", список вывода).
Формат вывода состоит из одного или нескольких элементов,
синтаксис которых имеет вид текст{N, M:s}.Здесь текст необязателен. Параметр N указывает на позицию элемента списка вывода
среди выводимых значений. Параметр M задает ширину поля, в которое будет записано выводимое значение. Если M отрицательно,
значение будет выравнено влево, иначе – вправо. Параметр s является необязательным и используется для форматирования чисел. Метод
WriteLine должен содержать столько элементов формата, сколько
указано выводимых элементов. Например, форматированный вывод
двумерного массива (см. пример 2.16) можно выполнить следующим
образом:
for (i = 0; i < 3; i++){
Console.WriteLine();
for (j = 0; j < 3; j++)
Console.Write ("{0,5:d}",table [i,j]);
Результат вывода:
234 4567
5
45 9876
8
23 185
67
41
В этом случае каждый выводимый элемент записывается в поле
ширины 5 в формате целого числа с выравниванием вправо.
Другой пример: форматированный вывод таблицы значений
квадратных корней из целых чисел:
for (int n = 0; n < 10; n++)
Console.WriteLine
("{0,5}{1,9:f3}",n,Math.Sqrt(n));
Здесь нулевой выводимый элемент записывается в поле ширины 5 с выравниванием вправо, следующий элемент выводится в поле
шириной 9 с выравниванием вправо в формате с фиксированной точкой и тремя десятичными знаками.
Коротко о главном
1. Простое консольное приложение – класс, содержащий метод
Main.
2. Пространство имен в С# – совокупность логически связанных
классов. Console – класс пространства имен System. WriteLine – метод класса Console.
3. Переменная – именованная область памяти, в которую можно
записать значение. Все переменные должны быть описаны до их использования. При описании задаются имя переменной и ее тип.
4. Переменная типа значения содержит значение. Переменная
ссылочного типа содержит ссылку на объект, а не сам объект. В языке С# четко определено, какие типы относятся к ссылочным, а какие – к значимым. Логический тип и арифметические типы – значимые; массивы, строки, классы – ссылочные.
5. Операция – действие, вырабатывающее некоторое значение,
называемое результатом операции.
6. Выражение состоит из операндов – констант, переменных, вызовов функций, соединенных знаками операций и скобками. Выражение всегда возвращает значение, тип которого определяется типами
операндов.
7. Вычисление выражения выполняется в соответствии с приоритетом (старшинством) операций языка программирования.
8. Неявные преобразования типов – это преобразования одного
типа в другой, которые выполняются компилятором автоматически.
Неявное преобразование возможно в тех случаях, когда оно не ведет
42
к потере точности данных. Во всех остальных случаях необходимо
выполнять приведение к типу, т. е. явное преобразование.
9. Основные операторы языка С#: присваивание, составной, условный, оператор выбора, цикл с предусловием, цикл с постусловием, цикл с параметром, оператор возврата из метода.
10. Массив – ограниченная последовательность однотипных элементов.
11. Создание массива значений выполняется в два этапа: 1) объявление ссылочной переменной на массив; 2) выделение памяти для
элементов массива.
12. В языке С# верхняя граница массива может задаваться не
только константой, но и переменной, значение которой должно быть
определено в момент объявления массива.
Задания
Написать программы на языке C#. Разработать набор тестов. Выполнить тестирование.
Задание 2.1. Из дюймов в метры [5].
Длина отрезка задана в дюймах (1 дюйм = 2,54 см). Перевести значение длины в метрическую систему, т. е. выразить ее в метрах, сантиметрах и миллиметрах. Например, 115,375 дм = 2 м 93 см 0,525 мм.
Исходные данные определяются вводом.
Задание 2.2. Обмен валюты [4].
У вас есть доллары. Вы хотите обменять их на рубли. Есть информация о стоимости купли-продажи в банках города. В городе N банков.
Составьте программу, определяющую, какой банк выбрать, чтобы
выгодно обменять доллары на рубли.
Задание 2.3. Оплата услуг связи [4].
Услуги телефонной сети оплачиваются по следующему правилу: за
разговоры до А минут в месяц оплачиваются В руб., а разговоры
сверх установленной нормы оплачиваются из расчета С руб. в минуту. Написать программу, вычисляющую плату за пользование телефоном для введенного времени разговоров за месяц.
Задание 2.4. Вырубка леса [5].
Леспромхоз ведет заготовку деловой древесины. Первоначальный
объем ее на территории леспромхоза составлял P кубометров. Еже43
годный прирост – К%. Годовой план заготовки – T кубометров. Через
сколько лет в бывшем лесу будут расти одни опята? (Данные ввести с
клавиатуры; результат вывести на экран.)
Задание 2.5. Стипендия [4].
Ежемесячная стипендия студента составляет А руб., а расходы на
проживание превышают стипендию и составляют В руб. в месяц.
Рост цен ежемесячно увеличивает расходы на 3%. Составьте программу расчета необходимой суммы денег, которую надо единовременно попросить у родителей, чтобы можно было прожить учебный
год (10 месяцев), используя только эти деньги и стипендию.
Задание 2.6. Привал [5].
Путник двигался t1 часов со скоростью v1, затем t2 часов – со
скоростью v2 и t3 часов со скоростью v3. За какое время он преодолел
первую половину пути, после чего запланировал привал?
Задание 2.7. Лотерея [5].
Дана таблица выигрышей денежной лотереи: K – массив номеров выигравших билетов (упорядочен по возрастанию), S – массив сумм выигрышей. Определить суммарный выигрыш для пачки купленных билетов с номерами l1, l2, …, ln.
Задание 2.8. Таблица значений функции [5].
В результате эксперимента получены наборы значений аргумента Х и
соответствующих значений функции Y. Сформировать и напечатать
таблицу значений функции, упорядочив их по возрастанию Х. Если
одному значению Х соответствует несколько значений Y, взять их
среднее значение.
44
Глава 3. Классы и объекты
Классы и объекты – базовые понятия объектно-ориентированного
программирования. В первой главе эти понятия были рассмотрены
очень кратко. В этой главе структура классов и объектов излагается
более подробно.
3.1. Описание классов
Класс – описание множества объектов, имеющих одинаковые атрибуты и поведение.
Поскольку класс определяется как описание данных вместе с
множеством действий, которые выполняются над этими данными,
можно дать еще одно определение класса.
Класс – абстрактный тип, на основе которого создаются объекты.
Фактически класс – образец (шаблон) для создания объектов.
Класс должен определять одну логическую сущность, т. е. содержать логически связанные данные. Например, класс, в котором хранится информация о сотруднике предприятия, не должен содержать
сведения о погоде или урожае.
Описание класса включает:

имя класса;

описание атрибутов (полей);

описание методов (кода, обрабатывающего эти поля).
Классы могут включать только методы или только поля. Поля и
методы называют элементами класса. Кроме того, класс может определять и ряд других элементов.
Описание класса, содержащего только поля и методы:
class ИМЯ_КЛАССА {
ОПИСАНИЕ ПОЛЕЙ
ОПИСАНИЕ МЕТОДОВ
}
45
Поля класса синтаксически являются переменными, а методы –
функциями.
Уточненный вариант описания класса:
class ИМЯ_КЛАССА {
//описание полей
ДОСТУП ТИП ПЕРЕМЕННАЯ_1;
ДОСТУП ТИП ПЕРЕМЕННАЯ_2;
. . .
ДОСТУП ТИП ПЕРЕМЕННАЯ_N;
// описание методов
ДОСТУП ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД1
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ)
{ ТЕЛО МЕТОДА }
ДОСТУП ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД_2
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ)
{ ТЕЛО МЕТОДА}
. . .
ДОСТУП ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД_N
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ)
{ ТЕЛО МЕТОДА}
}
Спецификатор доступа ДОСТУП определяет способ получения
доступа к элементу класса.
Элементы с доступом private (принят по умолчанию) могут
использоваться только другими элементами этого же класса. Элементы с доступом public могут использоваться в любых частях программы.
Пример 3.1. Описание класса, содержащего только поля.
Описать класс «Компания»
Поля класса:
название, количество сотрудников, месячный фонд зарплаты.
class Company {
public string name; //название компании
public int persons; //количество сотрудников
public int money;// месячный фонд зарплаты
};
46
3.2. Создание и удаление объектов
Создание объекта выполняется в два этапа:
1) объявление ссылочной переменной на объект;
2) выделение памяти для объекта и сохранение адреса памяти в
ссылочной переменной.
Создание объекта класса Company:
Вариант 1
// объявление ссылки на объект
Company
company1;
// выделение памяти для объекта и сохранение
// адреса памяти в ссылочной переменной
company1 = new Company ();
Переменная company1 – ссылка на объект класса Company,
т. е. сompany1 – переменная, которая может содержать адрес объекта. Оператор new динамически (во время выполнения программы)
выделяет память для объекта и возвращает ссылку на него (рис. 3.1).
Рис. 3.1. Графическое представление объекта класса Company
и ссылки на него в памяти компьютера
Вариант 2 (объединение объявления ссылки на объект и выделения
памяти для объекта)
Company company1 = new Company;
Присвоим значения полям объекта (рис. 3.2).
company1.name = "Computers";
company1.persons = 10;
company1.money = 300000;
47
Рис. 3.2. Графическое представление объекта класса Company
и ссылки на него в памяти компьютера
(поля объекта инициализированы)
Можно объединить создание и инициализацию объекта следующим образом:
Company company1 =
new Company(){name = "Computers",
persons = 10,
money = 300000};
Воспользуемся еще одной ссылкой на объект company2.
Company
company2;
В результате выполнения оператора присваивания
company2 = company1; // операция над ссылками!
в переменную company2 записывается ссылка на объект из переменной company1 (не происходит дублирования объекта!). Теперь
две переменные ссылаются на один и тот же объект (рис. 3.3).
Рис. 3.3. Графическое представление объекта класса Company
и двух ссылок на него в памяти компьютера
48
В языке С# память для всех объектов выделяется динамически.
Описание класса определяет, сколько памяти должно быть выделено
для объекта в момент его создания. Поскольку объем памяти ограничен, важно удалять объекты, неиспользуемые в программе. В некоторых языках программирования освобождение выделенной памяти
выполняется программистом. Например, в языке С++ для этого используется оператор delete. Освобождение памяти в С# выполняет
специальная программа Сборщик мусора, которая запускается без
вмешательства программиста.
Основные функции Сборщика мусора:
обнаружение недоступных объектов (объект становится недоступным, если не существует ссылок на него);
освобождение занимаемой ими памяти.
Пример 3.2. Появление недоступного объекта.
using System;
class Company{
public string name; //название компании
public int persons; //количество сотрудников
public int money;// месячный фонд зарплаты
};
class MyDemo{
public static void Main(){
Company company1 = new Company();
company1.name = "Computers";
company1.persons = 100;
company1.money = 1000000; //рис. 3.4а
Company company2 = new Company();
company2.name = "Books";
company2.persons = 200;
company2.money = 2000000; //рис. 3.4б
// появление недоступного объекта
company1 = company2; // рис.3.4с
Console.WriteLine("В компании " +
company1.name +" трудятся " +
company1.persons + " сотрудников ");
Console.WriteLine
("Фонд зарплаты: " + company1.money);
}
}
49
Рис. 3.4. Графическое представление
появления недоступного объекта
Результат работы программы:
В компании Books трудятся 200 сотрудников.
Фонд зарплаты 2000000
50
Теперь объект, содержащий информацию о компании Computers, недоступен.
Освобождение памяти, занятой недоступными объектами, требует
времени процессора, которое используется для выполнения программы пользователя. Поэтому Сборщик мусора работает периодически
(нерегулярно), т. е. не гарантируется, что память, занятая объектами,
будет освобождена сразу после того, как эти объекты стали недоступными. При этом Сборщик мусора обеспечивает объем памяти,
достаточный для создания новых объектов.
3.3. Методы класса
Методы – функции, которые манипулируют полями класса.
Важно помнить, что:

программный код может располагаться только внутри методов;

методы можно определить только внутри класса.
Описание метода:
ДОСТУП ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ)
{ ТЕЛО МЕТОДА }
ДОСТУП – спецификатор доступа (см. раздел 3.1);
ТИП_ВОЗВР_ЗНАЧЕНИЯ указывает тип значения, возвращаемого
методом; если метод не возвращает значения, необходимо указать
void;
ИМЯ_МЕТОД – имя метода (идентификатор);
ФОРМАЛЬНЫЕ ПАРАМЕТРЫ – переменные, которые получают значения соответствующих фактических параметров при вызове метода;
ФОРМАЛЬНЫЕ ПАРАМЕТРЫ могут отсутствовать.
Имя Main зарезервировано для метода, с которого начинается выполнение программы.
Пример 3.3. Добавление методов в класс Company. Создание и
инициализация объектов. Вызов методов.
1. Описать класс «Компания».
Поля класса:
название, количество сотрудников, месячный фонд зарплаты.
51
Методы класса:

печать полной информации о компании;

вычисление средней зарплаты сотрудников;

определение наибольшего количества сотрудников, которые могут работать в компании при заданной средней зарплате;

определение суммы налога и отчислений в пенсионный фонд.
using System;
class Company {
public string name; //название
public int persons; //количество сотрудников
public int money;// месячный фонд зарплаты
//-----------описание методов---------------// 1. метод не возвращает значения
// и не имеет параметров;
// печать информации о компании
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
//2. метод возвращает значение
// и не имеет параметров;
// вычисление средней зарплаты сотрудников
public int averageSalary (){
return money/persons;
}
//3.метод возвращает значение
// и имеет один параметр;
// определение наибольшего количества
// сотрудников, которые могут работать
// в компании при заданной средней зарплате
public int maxPersons (int salary) {
return money/salary;
}
//4. метод возвращает значение
// и имеет два параметра;
// определение суммы налога и
// отчислений в пенсионный фонд (ПФ)
public int minus (int minus1/*налог в % */,
52
int minus2 /*отчисление в ПФ в %*/){
//!перед именем каждого параметра указывается
// тип; параметры отделяются запятыми
return money*(minus1 + minus2)/100;
}
}; // конец описания класса Company
Воспользуемся классом Company для создания объектов в основной программе.
class Program {
public static void Main() {
//создание и инициализация объекта
Company factory = new Company();
factory.name = "Премьер";
factory.persons = 100;
factory.money = 1000000;
//вызовы методов
factory.show();
Console.WriteLine("Средняя зарплата: " +
factory.averageSalary());
Console.WriteLine("Отчисления: " +
factory.minus (13,4));
Console.WriteLine ("Средняя зарплата?");
int average = int.Parse(Console.ReadLine());
Console.WriteLine
("Количество сотрудников: " +
factory.maxPersons(average));
}
}
Результат работы программы:
В компании Премьер трудятся 100 сотрудников
Фонд зарплаты: 1000000
Средняя зарплата: 10000
Отчисления: 170000
Средняя зарплата?
12000
Количество сотрудников: 83
53
3.4. Конструкторы
Значения полей объекта можно инициализировать разными способами:
 при создании объекта:
Company factory =
new Company(){name = "Премьер",
persons = 100,
money = 1000000};
 после создания объекта с помощью оператора присваивания:
factory.name = "Премьер";
factory.persons = 100;
factory.money = 1000000;
 после создания объекта в результате вызова инициализирующего
метода, описанного в классе:
using System;
class Company {
public string name; //название
public int persons; //количество сотрудников
public int money;// месячный фонд зарплаты
// метод для инициализации полей объекта
public void Initialize(string new_name,
int new_persons, int new_money){
name=new_name;
persons=new_persons;
money=new_money;
}
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
// описания других методов – см. пример 3.3
}
class Program {
public static void Main() {
// создание объекта
Company factory = new Company();
54
// инициализация объекта
factory.Initialize ("Премьер", 100, 1000000);
// вывод информации об объекте
factory.show();
}
}
Недостаток такого подхода состоит в том, что метод Initialize нужно вызывать после создания объекта. Желательно выполнять
инициализацию объекта при его создании. Именно для этой цели
предназначены конструкторы.
Конструктор – метод, который инициализирует объект при его
создании.
Конструктор может быть использован для инициализации полей
объекта, а также для выполнения других дополнительных действий.
Особенности конструктора:
имя конструктора совпадает с именем класса;
конструктор не возвращает значение;
в описании конструктора не указывается тип возвращаемого
значения;
конструктор вызывается по умолчанию в момент создания
объекта.
Объявление конструктора:
ДОСТУП ИМЯ_КЛАССА (ФОРМАЛЬНЫЕ ПАРАМЕТРЫ) {
ОПИСАНИЯ
ОПЕРАТОРЫ
}
Обычно в качестве спецификатора доступа ДОСТУП используется public, так как конструктор, как правило, вызывается вне класса;
кроме того, могут отсутствовать ФОРМАЛЬНЫЕ ПАРАМЕТРЫ.
Описание конструктора для класса Company:
public Company (string newname, int newpersons,
int newmoney){
name = newname;
persons = newpersons;
money = newmoney;
}
55
В определении конструктора обычно используется ключевое слово this для ссылки на текущий объект. Дело в том, что удобно
иметь одинаковые имена для полей и формальных параметров. Таким
образом, конструктор класса Company c использованием ссылки
this запишется так:
public Company (string name, int persons,
int money){
this.name = name;
this.persons = persons;
this.money = money;
}
Пример 3.4. Добавление конструктора в класс Company. Использование конструктора в основной программе
using System;
class Company {
public string name; //название
public int persons; //количество сотрудников
public int money;// месячный фонд зарплаты
// конструктор
public Company(string name, int persons,
int money){
this.name = name;
this.persons = persons;
this.money = money;
}
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
public int averageSalary (){
return money/persons;
}
public int maxPersons (int salary){
return money/salary;
}
56
public int minus (int minus1/*налог в %*/,
int minus2 /*отчисление в ПФ в %*/){
return money*(minus1 + minus2)/100;
}
}; // конец описания класса Company
class Program {
public static void Main() {
// создание объекта и вызов конструктора
Company factory =
new Company("Премьер", 100, 1000000);
factory.show();
Console.WriteLine("Средняя зарплата: " +
factory.averageSalary());
Console.WriteLine("Отчисления: " +
factory.minus (13,4));
Console.WriteLine ("Средняя зарплата?");
int average = int.Parse(Console.ReadLine());
Console.WriteLine
("Количество сотрудников: " +
factory.maxPersons(average));
}
}
Результат выполнения этой программы совпадает с результатом
выполнения ее предыдущей версии (см. пример 3.3).
В операторе
Company factory =
new Company("Премьер", 100, 1000000);
"Премьер", 100, 1000000 – фактические параметры конструктора. Эти параметры используются для инициализации полей объекта.
3.5. Массивы объектов
Ранее были рассмотрены массивы, элементы которых имели значимые типы. Однако в программах достаточно часто встречаются
массивы объектов. Следует обратить внимание на процесс создания
массива объектов. В остальных же случаях работа с массивами объектов аналогична работе с массивами значений.
57
Powered by TCPDF (www.tcpdf.org)
Создание массива объектов выполняется в три этапа:
1) объявление ссылочной переменной на массив (рис. 3.5а);
2) выделение памяти для массива ссылок на объекты и сохранение адреса выделенной памяти в ссылочной переменной (рис. 3.5б);
3) выделение памяти для объектов и запись в соответствующие
элементы массива ссылок на эти объекты (рис. 3.5в).
Рис. 3.5. Графическое представление
одномерного массива объектов
Пример 3.5. Создание массива объектов, содержащих информацию о
компаниях города. Просмотр элементов массива
using System
class Company {
ОПИСАНИЯ ПОЛЕЙ И МЕТОДОВ // см. пример 3.4
}
class MasCompany {
public static void Main() {
int i;
// 1) объявляем ссылку на массив объектов
// класса Company;
// 2) выделяем память для массива ссылок
58
// на объекты и сохраняем адрес выделенной
// памяти в ссылочной переменной
Company [] CityCompany = new Company [5];
// 3) создаём объекты и записываем ссылки
// на эти объекты в соответствующие
// элементы массива
for ( i = 0; i<5; i++){
Console.WriteLine
("Информация о компании: \n");
CityCompany [i] = new Company(
Console.ReadLine(),
int.Parse(Console.ReadLine()),
int.Parse(Console.ReadLine()));
}
// печать информации о компаниях, в которых
// средняя зарплата превышает 10000
for ( i = 0; i<5; i++)
if (CityCompany [i].averageSalary ()> 10000)
CityCompany [i].show();
}
} // конец описания класса MasCompany
Массивы хорошо подходят для хранения фиксированного набора
элементов. Однако при работе с ними возникает ряд проблем:
после создания массива нельзя изменить количество его элементов;
при удалении элемента необходимо сдвигать соседние элементы;
если массив заполнен не полностью, то для добавления элемента в начало или в середину необходимо сдвигать элементы, соседние с местом вставки;
если массив заполнен полностью, то добавление элемента
требует создания нового массива большего размера и копирования в него исходного массива.
В языке C# существуют коллекции, которые позволяют решить
подобные проблемы добавления и удаления элементов.
59
3.6. Коллекция List
Коллекция – упорядоченный набор произвольного количества
элементов.
Работа с коллекциями отличается от работы с массивами. Память
под коллекции не фиксируется. Размер коллекции увеличивается и
уменьшается в соответствии с количеством ее элементов. Методы добавления и удаления элементов реализованы внутри коллекции. Однако массивы занимают меньше места в памяти, и работа с ними ведется быстрее, чем с коллекцией.
В C# существуют разные виды коллекций, но наиболее часто используется коллекция класса List (список), которая содержит методы для выполнения операций над списками. Рассмотрим работу
именно с этой коллекцией.
Для работы с коллекцией List необходимо подключить пространство имен System.Collections.Generic.
Создание коллекции выполняется в два этапа:
1) объявление коллекции;
2) добавление элементов в коллекцию.
Тип элементов коллекции указывается в момент ее объявления в
угловых скобках < >. Поэтому коллекция List называется обобщенной. Тип может быть как значимым, так и ссылочным.
Индексы (номера) элементов задаются значениями целого типа;
нижняя граница равна 0. Для обращения к элементу коллекции List
необходимо указать имя коллекции и индекс этого элемента в квадратных скобках.
Пример 3.6. Создание коллекции List целых чисел.
Некоторые методы для работы с коллекцией List
using System;
using System.Collections.Generic;
namespace коллекция{
class Program {
public static void Main(){
//объявление коллекции
List <int> my_int = new List <int>();
//добавление элементов в коллекцию
my_int.Add(1); my_int.Add(10);
my_int.Add(25); my_int.Add(17);
60
my_int.Add(10);
//результат: коллекция {1,10,25,17,10}
//определение количества элементов коллекции
int size = my_int.Count; // результат: 5
// определение индекса элемента коллекции
int ind = my_int.IndexOf(25);
// результат: 2
//удаление из коллекции
//первого вхождения элемента
my_int.Remove(10);
// результат: коллекция {1,25,17,10}
//удаление из коллекции элемента
//с указанным индексом
my_int.RemoveAt(2);
// результат: коллекция {1,25,10}
//проверка коллекции на наличие элемента
bool cont = my_int.Contains(25);
// результат: True
//вставка элемента по указанному
// индексу(сдвиг вправо)
my_int.Insert(0,5000);
//результат: коллекция {5000,1,25,10}
// сортировка коллекции
my_int.Sort();
// результат: коллекция {1,10,25,5000}
}
}
}
Кроме этого, можно преобразовать коллекцию в массив. Чтобы
увидеть весь список методов для работы с коллекцией, достаточно
после имени коллекции ввести точку.
Язык C# предоставляет возможность инициализации коллекции в
момент ее объявления.
Пример 3.7. Инициализация коллекции List целых чисел в момент
ее объявления
List<int> my_int =
new List<int>() {1,10,25,17,10};
61
Для просмотра элементов коллекции удобно использовать цикл
foreach.
Синтаксис цикла foreach:
foreach ( ТИП ПЕРЕМЕННАЯ in КОЛЛЕКЦИЯ)
ОПЕРАТОР
В круглых скобках указывается тип и имя переменной, через которую становится доступным очередной элемент коллекции при каждом повторении тела цикла. Тип переменной должен совпадать с типом элементов коллекции. Например, печать элементов коллекции
my_int может быть записана следующим образом:
foreach (int value in my_int )
Console.WriteLine(" " + value);
В этом примере обращение к очередному элементу коллекции
my_int выполняется через переменную value. При использовании цикла foreach не нужно помнить о том, сколько элементов
находится в коллекции, так как всегда будут просмотрены все ее
элементы.
С помощью цикла foreach удобно обрабатывать и элементы
массива.
Пример 3.8. Использование цикла foreach для просмотра элементов массива. Нахождение максимального элемента вектора
using System;
class Program{
public static void Main(){
int[] vector=
{11, 15, 19, 31, 25, 47, 50, 75, 16, 43 };
int max=vector[0];
foreach (int element in vector)
if (element > max)
max = element;
Console.WriteLine(" max = " + max);
}
}
При использовании цикла foreach никогда не произойдет выход за границы массива.
62
Пример 3.9. Создание коллекции List объектов класса Company.
Просмотр элементов коллекции. Удаление объектов из коллекции
List. Добавление объектов в коллекцию List.
using System;
using System.Collections.Generic;
namespace List_Example{
class Company {
ОПИСАНИЯ ПОЛЕЙ И МЕТОДОВ // см. пример 3.4
}
class Program {
public static void Main(){
//объявление коллекции
List<Company> comp1 = new List<Company>();
// добавление элементов в коллекцию
comp1.Add(new Company("ABCD1", 101, 900001));
comp1.Add(new Company("ABCD2", 102, 900002));
comp1.Add(new Company("ABCD3", 103, 900003));
Console.WriteLine("Исходная коллекция");
foreach (Company z in comp1)
z.show();
// метод Remove удаляет элемент по ссылке:
comp1.Remove(comp1[1]);
// метод RemoveAt удаляет элемент по индексу:
comp1.RemoveAt(0);
// вставка объекта по указанному индексу
//(сдвиг вправо)
comp1.Insert
(0, new Company("ABCD4", 104, 900004));
Console.WriteLine
("Преобразованная коллекция");
foreach (Company z in comp1)
z.show();
}
}
}
63
Исходная коллекция содержит информацию о компаниях
ABCD1, ABCD2 и ABCD3; преобразованная коллекция – о компаниях ABCD4 и ABCD3.
Пример 3.10. Инициализация коллекции List объектов класса
Company в момент ее объявления.
Вариант 1
В классе Company объявлен конструктор с тремя параметрами
List <Company> comp1 = new List<Company>() {
new Company("ABCD1", 101, 900001),
new Company("ABCD2", 102, 900002),
new Company("ABCD3", 103, 900003)
};
Вариант 2
В классе Company конструктор отсутствует или объявлен перегруженный конструктор с нулевым количеством параметров
List<Company> comp1 = new List<Company>() {
new Company(){name="ABCD1",persons= 101,
money = 900001},
new Company(){name="ABCD2",persons= 102,
money = 900002},
new Company(){name ="ABCD3",persons= 103,
money = 900003},
};
Объявление коллекции с одновременным добавлением начального набора объектов делает код более компактным.
3.7. Свойства
В C# по умолчанию все поля являются частными (private). Если поле объявить общедоступным (public), то его значение можно
будет читать и изменять в любых частях программы. Однако возникают ситуации, когда нужно сделать поле доступным за пределами
класса и в то же время контролировать способ работы с ним. Например, разрешить только читать значение поля или записывать в него
данные из определенного диапазона. Этого можно достичь, используя
64
закрытое поле и метод класса для доступа к нему. Однако свойства
обеспечивают более удобный способ доступа к закрытым полям в
различных частях программы.
Свойства позволяют получать и устанавливать значения закрытых полей класса вне определения класса.
Таким образом, если связать поле со свойством, то свойство будет ограничивать действия, выполняемые над этим полем за пределами класса.
Свойство состоит из имени и аксессоров (средств доступа) get и
set.
Аксессор get используется для чтения содержимого закрытого
поля, а аксессор set – для записи в него нового значения.
Описание свойства:
ДОСТУП ТИП ИМЯ {
get {
КОД ЧТЕНИЯ ПОЛЯ
}
set {
КОД ЗАПИСИ ПОЛЯ
}
ДОСТУП – спецификатор доступа;
ТИП – тип свойства; ИМЯ – имя свойства.
После того, как свойство объявлено, его можно использовать
вместо поля, которое оно защищает. Имя свойства можно записывать
в выражениях и операторе присваивания подобно полям; только в
этом случае будут вызываться аксессоры get или set (в зависимости от контекста). Как правило, имена свойств совпадают с именами
полей, однако имена полей записываются со строчной буквы, а имена
свойств – с прописной буквы.
Аксессоры подобны методам, за исключением того, что у них отсутствуют параметры и, следовательно, скобки. Любое свойство может состоять из одного или двух аксессоров; важно, чтобы присутствовал один из них. Если в свойстве определяется только аксессор
get, то изменение соответствующего поля запрещено. Если в описании свойства присутствует только аксессор set, то чтение содержи65
мого поля невозможно. В аксессоре set используется ключевое слово value – параметр, через который передается значение поля.
Обращение к свойству, как и к полю, выполняется с помощью
операции «точка».
Пример 3.11. Описать класс «Компания». Обеспечить следующие
варианты доступа к полям класса за пределами класса:
название компании можно читать и задать только один раз;
количество сотрудников доступно для чтения и записи;
фонд заработной платы закрыт для доступа.
using System;
namespace Свойства{
class Company {
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд зарплаты
public string Name {// свойство
get { return name; }
set {if (name == null) name = value;}
}
public int Persons {// свойство
get { return persons; }
set { persons = value;}
}
// методы
public Company () { // конструктор
persons = 10000;
}
// описания других методов – см. пример 3.3
}
class Program{
public static void Main(){
Company comp1 = new Company();
// вызывается аксессор set
// свойства Name-из контекста:
comp1.Name = "АБВГД";
//вызывается аксессор get
//свойства Name - из контекста:
Console.Write("В компании " + comp1.Name );
66
//вызывается аксессор get свойства Persons:
Console.WriteLine(" трудятся "
+ comp1.Persons + " сотрудников");
// повторное изменение названия компании
// название компании не изменится:
comp1.Name = "КЛМН";
// вызываетcя аксессор set
//свойства Persons
comp1.Persons += 10;
Console.WriteLine("В компании "+comp1.Name +
" трудятся " + comp1.Persons +
" сотрудников");
}
}
}
Результат работы программы:
В компании АБВГД трудятся 10000 сотрудников
В компании АБВГД трудятся 10010 сотрудников
Тип DateTime (дата и время суток) пространства имен System
не предоставляет непосредственный доступ к полям, но содержит
свойства, позволяющие работать с этими полями. Примерами таких
свойств являются Day (день месяца), Month (месяц), Year (год),
Now (текущая дата и время).
Тип TimeSpan (интервал времени) также содержит ряд свойств,
например Days (количество дней периода времени).
Пример 3.12.
TimeSpan
Использование
свойств
типов
DateTime
using System;
class Program{
public static void Main(){
DateTime now = DateTime.Now;
Console.WriteLine(" сегодня " + now);
//последний день текущего года
DateTime endOfYear =
new DateTime(now.Year, 12, 31);
//количество дней между двумя датами
67
и
int x = (endOfYear - now).Days;
Console.WriteLine
("кол-во дней до конца года "+ x);
}
}
Все
классы-массивы
являются
наследниками
класса
System.Array, который содержит собственные методы и свойства.
Рассмотрим следующие объявления массивов:
int[ ]table1 = new int[30];
double[ ]table2 = new double[30];
int[ , ]my_table3 = new int[10,20];
Массив table1 принадлежит классу int[ ] – одномерному
массиву значений типа int; массив table2 принадлежит классу double[ ] – одномерному массиву значений типа double;
массив table3 – двумерному массиву значений типа double.
Все эти классы наследуют классу System.Array, свойство Length
которого позволяет определить длину массива.
Пример
3.13.
System.Array
Использование
свойства
Length
класса
Задача «Банкомат». Клиент должен получить S руб. Банкомат выдает деньги купюрами 5000, 1000, 500 и 100 руб. Сколько купюр разного достоинства должен выдать банкомат, если он начинает выплату
с самых крупных купюр?
Например, купюры, которые должен выдать банкомат для суммы
17 625 руб., представлены в таблице 3.1.
Таблица 3.1. Вариант выдачи
банкоматом 17 625 руб.
Достоинство Количество
купюры
купюр
5000
3
1000
2
500
1
100
1
68
Количество и достоинства купюр, которыми заправляется банкомат, могут меняться (банкомат может содержать только два вида купюр достоинством 500 и 100 руб.).
using System
class Program {
public static void Main(){
int[ ]money = { 5000, 1000, 500, 100, 50 };
int summa, // сумма, запрошенная клиентом
x, // количество купюр текущего достоинства
i, // индекс текущей купюры в коллекции
last; // достоинство последней купюры
// в коллекции
Console.WriteLine("Введите сумму ");
summa = int.Parse(Console.ReadLine());
i = 0; last = money[money.Length - 1];
while (summa >= last) {
x = summa / money[i];
Console.WriteLine
(money[i] + " - " + x);
summa %= money[i];
i++;
}
}
}
Если банкомат заправляется другим набором купюр, достаточно
внести единственное изменение в инициализацию массива money,
например,
int[ ]money = { 1000, 500, 100 };
3.8. Передача параметров
Память под локальные переменные и параметры функций выделяется при вызове функции и освобождается при выходе из нее.
С каждым вызовом функции связывается область данных фиксированной длины. В области данных хранятся значения параметров и локальных переменных, а также административная информация. Когда
функция вызывается, она забирает под свою область данных необхо69
димый объем памяти. Когда выполняется выход из функции, память,
занятая областью данных, освобождается.
Рассмотрим передачу параметров значимых и ссылочных типов
двумя способами – по значению и по ссылке.
3.8.1. Параметры значимого типа
При подстановке значением фактический параметр вычисляется,
и полученное значение записывается на место соответствующего
формального параметра. После выхода из функции память, занятая
областью данных этой функции, освобождается. Таким образом, при
подстановке значением нет никакого способа изменить значение фактического параметра и, следовательно, передать через него результат
работы функции.
Класс Company (см. пример 3.4) содержит методы (функции), у
которых все параметры передаются значением, так как являются аргументами этих методов.
Чтобы изменить значение переменной – параметра внутри метода, нужно воспользоваться передачей параметра по ссылке, т. е. передать адрес этой переменной. Это означает, что в области данных вызываемой функции на место формального параметра должен быть записан адрес соответствующего фактического параметра. Тогда все
изменения значения фактического параметра будут выполняться в
области данных вызывающей функции.
В C# существует два варианта передачи параметра по ссылке.
Вариант 1. Использование модификатора ref:
1) перед описанием формального параметра в заголовке функции
пишется слово ref;
2) перед именем фактического параметра также пишется слово
ref;
3) фактический параметр должен быть инициализирован перед
обращением к методу.
Вариант 2. Использование модификатора out:
1) перед описанием формального параметра в заголовке функции
пишется слово out;
2) перед именем фактического параметра также пишется слово
out;
70
3) необязательно инициализировать фактический параметр перед
обращением к методу.
Пример 3.14. Передача через параметры результатов работы метода.
Описать метод, который возвращает значение sin и cos для заданного угла
using System;
namespace my{
class Trigonometry {
public void Sin_Cos (double angle,
out double sinAngle, out double cosAngle) {
sinAngle = Math.Sin (angle);
cosAngle = Math.Cos (angle);
}
}
class myDemo{
public static void Main(){
double a = Math.PI/2;
double sin1, cos1;
Trigonometry my1 = new Trigonometry();
my1.Sin_Cos (a, out sin1, out cos1);
Console.WriteLine(
"sin={0,6:f3} cos= {1,6:f3}", sin1,cos1);
}
}
}
3.8.2. Параметры ссылочного типа
Передача параметров значимых типов отличается от передачи параметров ссылочных типов.
При передаче значением параметра значимого типа, например
double, фактический параметр вычисляется и записывается на место
соответствующего формального параметра. При передаче значением
параметра ссылочного типа в область данных вызываемой функции
записывается ссылка, например, ссылка на объект. Память под сами
объекты выделяется в момент их создания в специальной области па71
мяти, которая называется кучей. Таким образом, объект хранится в
куче, а ссылка на него – в стеке областей данных. Обращение к объекту выполняется через ссылку, поэтому изменение состояния объекта (значений полей) в вызываемой функции изменяет его состояние в
вызывающей функции. Таким образом, если ссылка передается значением, то соответствующий ей объект – по ссылке.
Пример 3.15. Передача значением параметра ссылочного типа
using System;
namespace Company{
class Company{
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд заработной платы
public int Money{
get { return money; }
set { money = value; }
}
public Company(string name, int persons,
int money){
this.name = name; this.persons = persons;
this.money = money;
}
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
public int averageSalary (){
return money/persons;
}
}
class Program {
// если средняя зарплата сотрудников
//компании < 10000, увеличить месячный фонд
// заработной платы в два раза
static void New_Salary(Company z) {
// параметр ссылочного типа
//передается значением
72
if (z.averageSalary() < 10000) z.Money *= 2;
}
public static void Main(){
Company factory =
new Company("ABCD", 10, 90000 );
// вывод исходного состояния объекта
factory.show();
New_Salary (factory);
// вывод нового состояния объекта
factory.show();
}
}
}
Результат работы программы:
В компании ABCD трудятся 10 сотрудников
Фонд заработной платы 90000
В компании ABCD трудятся 10 сотрудников
Фонд заработной платы 180000
В результате вызова функции New_Salary изменился объект, на
который указывает ссылка, сама же ссылка осталась прежней.
При передаче ссылки на объект значением в вызывающей функции изменяется объект.
Модификаторы ref и out можно применять и к параметрам
ссылочного типа. В этом случае реализуется передача ссылки по
ссылке, т. е. в вызывающей функции будет изменяться значение самой ссылочной переменной.
Пример 3.16. Передача параметра ссылочного типа по значению и по
ссылке
using System;
class Company {
ОПИСАНИЯ ПОЛЕЙ И МЕТОДОВ // см. пример 3.15
}
class Program{
// если средняя зарплата сотрудников < 10000,
//закрыть компанию
// передача ссылки на объект по значению
73
static void New_State1(Company z) {
if ( z.averageSalary()< 10000) z = null;
}
// если средняя зарплата сотрудников < 10000,
// закрыть компанию
// передача ссылки на объект по ссылке
static void New_State2(ref Company z) {
if (z.averageSalary() < 10000)
z = null;
}
public static void Main()
{ Company factory1 =
new Company("ABCD", 10, 90000);
Console.WriteLine(
"Передача ссылки на объект по значению:");
New_State1(factory1);
if (factory1 == null)
Console.WriteLine("Компания закрыта");
else factory1.show();
Console.WriteLine
("Передача ссылки на объект по ссылке");
New_State2(ref factory1);
if (factory1 == null)
Console.WriteLine("Компания закрыта");
else factory1.show();
}
}
Результат работы программы:
Передача ссылки на объект по значению:
В компании ABCD трудятся 10 сотрудников
Фонд заработной платы 90000
Передача ссылки на объект по ссылке:
Компания закрыта
Таким образом,
 после выхода из функции New_State1 (ссылка на объект передается по значению) адрес объекта не изменяется, поэтому печатается исходная информация о компании;
74
 после выхода из функции New_State2 (ссылка на объект передается по ссылке) адресу объекта присваивается новое значение
null, следовательно, печатается строка «Компания закрыта».
При передаче ссылки на объект по ссылке в вызывающей функции
меняется значение самой ссылки.
Пример 3.17. Параметр метода – ссылка на массив, передаваемая по
значению. Сортировка массива методом простых вставок
using System;
namespace Сортировка_массива{
class Class1{
static public void SortInsert
(int [] numbers) {
int i, j, // параметры цикла
x; // вспомогательная переменная
for ( i = 2; i<numbers.Length; i++){
x = numbers[i]; numbers[0]=x;
j=i-1;
while (x<numbers[j]) {
numbers[j+1]=numbers [j];
j--;
}
numbers[j+1] = x;
}// for
}
public static void Main(){
Console.WriteLine
("Количество элементов массива?");
int z = int.Parse(Console.ReadLine())+1;
int [] table = new int [z];
int i;
Console.WriteLine("Ввод массива:");
for (i=1; i<table.Length; i++)
table [i] =
(int.Parse (Console.ReadLine()));
//сортировка
SortInsert (table);
//вывод результата сортировки
for (i=1; i<table.Length; i++)
75
Console.Write
("
" + table [i] + "
");
}
} // class Class1
}
Метод SortInsert получил ссылку на массив table в качестве параметра. В результате вызова этого метода изменились значения элементов массива. Теперь массив отсортирован.
Если ссылка на массив передается методу по значению, то метод может не только обращаться к элементам массива, но и изменять их.
3.9. Перегрузка методов
В языке C# в одном классе можно определить несколько методов
с одинаковыми именами, но разными наборами параметров и разной
реализацией. Такую ситуацию называют перегрузкой, а соответствующие методы – перегруженными. Перегрузка методов используется в тех случаях, когда необходимо решать подобные задачи с разным
набором параметров.
Перегруженные методы могут:
иметь разное число параметров;
отличаться типами параметров.
Для перегрузки недостаточно, чтобы методы отличались лишь
типами возвращаемых значений.
Пример 3.18. Вычислить площадь треугольника по трем точкам.
Предусмотреть три способа задания параметров – координат точек:
1) шесть параметров типа int;
2) шесть параметров типа double;
3) три параметра типа Location – класса, который используется
для представления точки и содержит два поля x и y.
Для решения задачи можно описать три метода с разными именами и типами параметров. Тогда для одних и тех же действий программисту придется помнить имена нескольких методов. Чтобы избавиться
от лишних имен, воспользуемся перегрузкой, т. е. опишем три метода
с одинаковыми именами, но разными наборами параметров.
using System;
namespace Перегрузка_методов{
76
Powered by TCPDF (www.tcpdf.org)
class Location {
int x, y;
public int X {get {return x;} }
public int Y {get {return y;} }
public Location (int x, int y) {
this.x=x;
this.y = y;
}
}
class Triangle {
public double Area ( int x1,int y1,
int x2, int y2,
int x3,int y3 /* координаты точек */){
int temp;
temp = (x1-x3)*(y2-y3) - (x2-x3)*(y1-y3);
if (temp <0)
return -0.5*temp;
else return 0.5*temp;
}
public double Area ( double x1,double y1,
double x2, double y2,
double x3, double y3 /*координаты точек*/){
double temp;
temp = (x1-x3)*(y2-y3) - (x2-x3)*(y1-y3);
if (temp <0)
return -0.5*temp;
else return 0.5*temp;
}
public double Area ( Location L1, Location L2,
Location L3 /* координаты точек */ ) {
int temp;
temp = (L1.X-L3.X)*(L2.Y-L3.Y) (L2.X-L3.X)*(L1.Y-L3.Y);
if (temp <0)
return -0.5*temp;
else return 0.5*temp;
}
}
class Area_Find{
77
public static void Main(){
Triangle my_area = new Triangle ();
Location loc1 = new Location (1,3);
Location loc2 = new Location (2,-5);
Location loc3 = new Location (-8,4);
Console.WriteLine (" Area integers = " +
my_area.Area (1,3,2,-5,-8,4));
Console.WriteLine (" Area double = " +
my_area.Area (1.1,3.2,2.0,-5.3,-8.2,4.0));
Console.WriteLine (" Area location= " +
my_area.Area (loc1,loc2,loc3));
}
}
}
При вызове метода my_area.Area компилятор находит три метода с именем Area в описании класса Triangle и выбирает тот
из них, у которого:
количество формальных и фактических параметров совпадает;
тип каждого фактического параметра совпадает с типом соответствующего формального параметра.
С перегрузкой связано важное понятие – сигнатура.
Сигнатура – имя метода со списком его параметров.
Тип возвращаемого значения не входит в понятие сигнатуры.
Следовательно, следующие три заголовка
public double Minus (double a, double b)
public int Minus (double a, double b )
public void Minus (double a, double b )
имеют одинаковую сигнатуру.
Все методы в классе должны иметь разные сигнатуры. Именно
уникальность сигнатуры позволяет компилятору выбрать подходящий перегруженный метод. Программисту достаточно помнить лишь
название одного метода, реализующего аналогичные задачи с разными наборами параметров.
Рассмотрим частный случай перегруженных методов – перегруженные конструкторы. Параметры конструктора используются для
инициализации объекта. Однако в зависимости от того, где в программе создается новый объект, может понадобиться инициализация
78
разного количества полей или даже ни одного. В таком случае нужно
создать несколько конструкторов с различным списком параметров.
Пример 3.19. Использование перегруженных конструкторов для
инициализации полей объектов.
using System;
namespace Company1{
class Company{
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд заработной платы
public string Name {
get { return name; }
set {if (name == null)
name = value;}
}
public int Persons {
get { return persons; }
set { persons = value;}
}
public int Money{
get { return money; }
set { money = value;}
}
// конструктор 1 – инициализация трех полей
public Company
(string name,int persons,int money) {
this.name = name;
this.persons = persons;
this.money = money;
}
// конструктор 2 – инициализация двух полей
public Company (string name, int persons) {
this.name= name; this.persons = persons;
}
// конструктор 3 – инициализация одного поля
public Company (string name) {
this.name= name;
}
//конструктор 4 – без параметров
79
public Company (){}
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
} // конец описания класса Company
class MyDemo {
public static void Main(){
Company factory =
new Company("Премьер", 100, 1000000);
factory.show();
Company factory1 =
new Company("Bird", 200);
//определяем третье поле – money
Console.WriteLine(" фонд зарплаты?");
factory1.Money =
Convert.ToInt32(Console.ReadLine());
factory1.show();
Company factory2 = new Company("Leon");
//определяем 2-e и 3-e поле-persons и money
Console.WriteLine(" сколько сотрудников?");
factory2.Persons =
Convert.ToInt32 (Console.ReadLine());
Console.WriteLine(" фонд зарплаты?");
factory2.Money =
Convert.ToInt32 (Console.ReadLine());
factory2.show();
Company factory3 = new Company();
//определяем поля – name, persons и money
Console.WriteLine(" название компании?");
factory3.Name = Console.ReadLine();
Console.WriteLine(" сколько сотрудников?");
factory3.Persons =
Convert.ToInt32 (Console.ReadLine());
Console.WriteLine(" фонд зарплаты?");
factory3.Money =
Convert.ToInt32 (Console.ReadLine());
factory3.show();
80
}
}
}
Конструктор Company перегружен несколько раз, т. е. программист может инициализировать объекты по-разному. Вызов подходящего конструктора зависит от набора параметров, заданных в момент
создания объекта.
3.10. Статические методы и поля
3.10.1. Статические методы
Все методы можно разделить на две основные группы: методы
объектов и методы классов.
Доступ к методам объекта осуществляется с помощью ссылки на
объект. Статические же методы принадлежат самому классу, а не какому-либо объекту. Поэтому для доступа к ним используется имя
класса.
Вызов статического метода:
ИМЯ КЛАССА.ИМЯ МЕТОДА( ФАКТИЧЕСКИЕ ПАРАМЕТРЫ )
Исполнение программы на C# начинается с вызова функции Main,
которая описывается в одном из классов программы, например,
class Example {
public static void Main(){
. . .
}
}
Среда исполнения не создает никаких объектов. Следовательно,
объявление метода Main должно указывать, что он принадлежит
классу. Для этого используется атрибут static. Метод Main
вызывается средой исполнения через имя класса Example.Main().
Методы, которые не связаны с каким-либо объектом, должны
быть объявлены статическими – с атрибутом static.
Пример 3.20. Реализовать метод average, который вычисляет
среднее своих аргументов. Этот метод может использоваться другими
классами
81
Вариант 1. Объявление метода average без атрибута static
using System;
class MathSt {
public double average (int [] table) {
int i;
double summa = 0;
for ( i=0; i< table.Length; i++)
summa += table [ i ];
return summa /= table.Length;
}
}
Метод для вычисления среднего объявлен без атрибута static.
Поэтому сначала нужно создать объект класса MathSt, и только
после этого можно вызывать метод average.
class Program{
public static void Main(){
MathSt ob = new MathSt ();
int [ ] massiv = {1,2,3,4,5,6,7};
Console.WriteLine
("среднее =" + ob.average (massiv));
}
}
Единственная причина создания объекта ob состоит в необходимости вызова метода average. После завершения работы этого
метода объект ob становится ненужным. Такой подход неэффективен. Поэтому лучше объявить метод average статическим.
Вариант 2. Объявление метода average c атрибутом static
using System;
class MathSt {
public static double average (int [] table) {
int i;
double summa = 0;
for ( i=0; i< table.Length; i++)
summa += table [ i ];
return summa /= table.Length;
}
}
82
class Program{
public static void Main(){
int[ ]massiv = {1,2,3,4,5,6,7};
Console.WriteLine
("среднее =" + MathSt.average (massiv));
}
}
Теперь вызов метода average упростился.
Возможна ситуация, когда программа содержит несколько методов, не связанных с каким-либо объектом, но реализующих логически
связанные функции. Тогда все эти функции следует описать в отдельном классе, объявив при этом статическими. Например, можно
создать класс Chemistry и включить в него методы для выполнения
химических расчетов.
Статические методы часто используются в Visual Studio.
Класс System.Math содержит статические методы для вычислений математических функций sin, cos, abs, exp и др.
Класс System.Console содержит статические методы ReadLine, WriteLine и др.
Класс System.Array содержит статические методы для обработки массивов. Например, для работы с одномерными массивами
удобно воспользоваться такими методами, как:
 Sort – сортировка массива;
 BinarySearch – определение индекса первого вхождения образца в отсортированный массив алгоритмом двоичного поиска;
 Reverse – перестановка элементов в обратном порядке.
3.10.2. Статические поля
Статические переменные принадлежат классу, а не объекту. Статическая переменная всегда одна для всех объектов одного класса.
При создании нового объекта память под статические переменные не
выделяется. Статические переменные фактически являются глобальными переменными.
Пример 3.21. Использование статической переменной.
Подсчитать количество объектов, созданных на основе одного класса
using System;
class Company {
83
static int companyCount = 0;
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд заработной платы
//-----------описание свойства-------------public static int CompanyCount {
get { return companyCount; }
}
// конструктор
public Company() {
companyCount++;
}
}
class Program {
public static void Main() {
Company x = new Company();
Company y = new Company();
Console.WriteLine("количество объектов = "
+ Company.CompanyCount);
Company z = new Company();
Company v = new Company();
Console.WriteLine("количество объектов = "
+ Company.CompanyCount);
}
}
Каждый раз, когда создается объект класса Company, значение
статической переменной companyCount увеличивается на 1. Поэтому эта переменная всегда содержит количество объектов, существующих в текущий момент. Переменная объекта не может быть использована в этом случае, так как подсчет количества объектов связан
с классом, а не с конкретным объектом.
Результат работы программы:
количество объектов = 2
количество объектов = 4
После создания второго объекта значение переменной
сompanyCount стало равным 2, после создания четвертого – 4. Если создать пятый объект, то переменная увеличится еще на 1.
84
Для инициализации статических переменных удобно использовать статический конструктор, который объявляется с атрибутом
static.
Статический конструктор обладает следующими свойствами:
выполняется только один раз, независимо от количества созданных объектов;
не имеет модификатора доступа, так как вызывается автоматически при создании первого объекта или при первом обращении к статическому элементу класса;
не может иметь параметров, следовательно, его нельзя перегружать.
Пример 3.22. Использование статической переменной и статического
конструктора. Подсчитать количество объектов, созданных на основе
одного класса
using System;
class Company{
static int companyCount;
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд заработной платы
public static int CompanyCount {
get { return companyCount; }
}
// статический конструктор
static Company() { companyCount=0; }
// конструктор
public Company (
string name,int persons, int money){
this.name = name; this.persons = persons;
this.money = money; companyCount++;
}
// описание других методов – см. пример 3.3
}
class Program{
public static void Main(){
Company factory1 =
new Company("ABCD", 10, 90000);
Console.WriteLine
85
("объектов: - " + Company.CompanyCount);
Company x =
new Company("ABCD1", 100, 1000000);
Company y =
new Company("ABCD2", 8, 200000);
Console.WriteLine
("объектов: - " + Company.CompanyCount);
Company x1 =
new Company("ABCD3", 20, 20000000);
Company y1 =
new Company("ABCD4", 10, 90000);
Console.WriteLine
("объектов: - " + Company.CompanyCount);
}
}
Результат работы программы:
объектов - 1
объектов - 3
объектов – 5
Значение статической переменной companyCount увеличивается на единицу при создании очередного объекта.
Коротко о главном
1. Описание класса включает имя класса, описание атрибутов
(полей) и описание методов. Класс может определять и ряд других
элементов.
2. Спецификатор доступа определяет способ получения доступа к
элементу класса.
3. Создание объекта выполняется в два этапа: 1) объявление ссылочной переменной на объект; 2) выделение памяти для объекта и сохранение адреса памяти в ссылочной переменной.
4. Освобождение памяти от недоступных объектов выполняет
специальная программа Сборщик мусора, которая запускается без
вмешательства программиста.
5. Программный код может располагаться только внутри методов;
методы можно определить только внутри класса.
6. Конструктор – метод, который инициализирует объект при его
создании.
86
7. Создание массива объектов выполняется в три этапа: 1) объявление ссылочной переменной на массив; 2) выделение памяти для
массива ссылок на объекты и сохранение адреса выделенной памяти в
ссылочной переменной; 3) выделение памяти для объектов и запись в
соответствующие элементы массива ссылок на эти объекты.
8. Коллекция – упорядоченный набор произвольного количества
элементов.
9. Создание коллекции выполняется в два этапа: 1) объявление
коллекции; 2) добавление элементов в коллекцию.
10. Свойства позволяют получать и устанавливать значения закрытых полей класса вне определения класса.
11. В языке C# параметры можно передавать по значению и по
ссылке. Существует два варианта передачи параметров по ссылке: с
использованием модификаторов ref и out.
12. В одном классе можно определить несколько методов с одинаковыми именами, но разными наборами параметров и разной реализацией. Такие методы называют перегруженными.
13. Сигнатура – имя метода со списком его параметров. Все методы в классе должны иметь разные сигнатуры.
14. Если программа содержит несколько методов, не связанных с
каким-либо конкретным объектом, их следует описать в отдельном
классе, объявив при этом статическими.
15. Статические поля и методы принадлежат классу, а не объекту,
поэтому обращение к ним выполняется через имя класса.
16. Нестатические поля и методы принадлежат объекту, поэтому
обращение к ним выполняется через имя объекта.
Задания
Темы. Описание класса. Создание объектов. Обращение к полям и
методам объектов.
Задание 3.1. Машина.
1. Описать класс «Машина».
Поля класса: марка, модель и номер машины.
Методы класса:

Start – печатает сообщение о том, что машина завелась, например, «Машина ВАЗ 2101 завелась»;

Stop – печатает сообщение о том, что машина остановилась, например, «Машина Mersedes 600 остановилась».
87
2. Создать несколько объектов класса «Машина».
3. Инициализировать поля объектов.
4. Для каждого объекта вызвать методы Start и Stop.
Задание 3.2. Газета.
1. Описать класс «Газета».
Поля класса: название, тираж, количество полос.
Методы класса:
Init – инициализация всех полей;
Info – получение полной информации о газете;
ChangeName – изменение названия газеты;
ChangeNumber – изменение количества полос.
2. Создать несколько объектов класса «Газета».
3. Инициализировать поля объектов.
4. Получить полную информацию обо всех объектах.
5. Изменить название и количество полос одного объекта.
6. Получить полную информацию об измененном объекте.
Задание 3.3. Здание.
1. Описать класс «Здание».
Поля класса: количество этажей, площадь, количество жильцов.
Методы класса:
Init – инициализация всех полей;
Info – получение полной информации о здании;
AreaPerson – вычисление площади на одного человека;
maxPersons – вычисление максимально возможного количества человек в здании, если на каждого приходится заданная
минимальная площадь.
2. Создать несколько объектов класса «Здание».
3. Инициализировать поля объектов.
4. Получить полную информацию обо всех объектах.
5. Вычислить максимально возможное количество человек во всех зданиях, если на каждого приходится заданная минимальная площадь.
6. Вычислить площадь на одного человека в каждом здании.
Задание 3.4. Редакция газеты.
1. Описать класс «Редакция газеты».
Поля класса: название, тираж, количество полос, имена журналистов
и их зарплата.
88
2. Обеспечить возможность следующих действий (каждое действие
оформить в виде отдельного метода):
инициализировать все поля;
получить полную информацию о газете;
подсчитать общую сумму зарплат всех журналистов газеты;
выяснить, кто из журналистов получает маленькую зарплату
(меньше средней зарплаты);
увеличить маленькую зарплату журналистов на к%;
уменьшить большую зарплату (выше средней) на р%;
выяснить, кто из журналистов получает зарплату, равную заданной величине.
3. В основной функции создать несколько объектов класса «Редакция
газеты». Проверить работоспособность всех описанных методов.
Задание 3.5. Аптека.
1. Описать класс «Аптека».
Поля класса: название аптеки, названия лекарств, стоимость каждого
лекарства.
2. Обеспечить возможность следующих действий (каждое действие
оформить в виде отдельного метода):
инициализировать все поля;
получить полную информацию об аптеке;
выяснить стоимость самого дорогого лекарства;
найти стоимость всех лекарств.
3. В основной функции создать несколько объектов класса «Аптека».
Проверить работоспособность всех описанных методов.
Задание 3.6. Туристическая компания.
1. Описать класс «Туристическая компания».
Поля класса: название компании, фамилии клиентов, размер страховки каждого клиента.
2. Обеспечить возможность следующих действий (каждое действие
оформить в виде отдельного метода):
инициализировать все поля;
получить полную информацию о компании;
найти сумму страховки всех туристов;
выяснить, у кого из туристов максимальная сумма страховки.
3. В основной функции создать объекты класса «Туристическая компания». Проверить работоспособность всех описанных методов.
89
Задание 3.7. ГИБДД.
1. Описать класс «ГИБДД», содержащий информацию о зарегистрированных машинах.
Поля класса: номера и стоимость машин, пройден ли техосмотр (для
каждой машины).
2. Обеспечить возможность следующих действий (каждое действие
оформить в виде отдельного метода):
подсчитать налог с регистрации машины (4% стоимости);
найти владельцев машин, не прошедших техосмотр;
выяснить, прошел ли техосмотр владелец самой дорогой машины;
получить полную информацию о зарегистрированных машинах.
3. В основной функции создать несколько объектов класса «ГИБДД».
Проверить работоспособность всех описанных методов.
Задание 3.8. Инициализировать объекты в заданиях 3.1–3.7 с использованием конструкторов.
Тема. Массивы объектов и коллекции List.
Задание 3.9. Клиенты страховой компании.
1. Описать класс «Клиент страховой компании».
Поля класса: имя клиента, вид страховки, размер страховки.
Методы класса: инициализация полей, получение информации о
клиенте.
2. Создать массив клиентов страховой компании.
3. Получить ответы на следующие вопросы:
кто из клиентов страховой компании застраховал автомобиль на
сумму, превышающую 2000 руб.;
каков максимальный размер страховки в страховой компании;
сколько клиентов имеют автомобильную страховку.
Задание 3.10. Туристические фирмы.
1. Описать класс «Туристическая фирма».
Поля класса: название, год создания, ИНН, прибыль за текущий год.
Методы класса: инициализация полей, получение информации о
фирме.
2. Создать коллекцию объектов класса «Туристическая фирма», содержащую информацию о туристических фирмах города.
3. Для заданной фирмы найти ИНН и прибыль за текущий год.
90
4. Найти суммарную прибыль всех фирм.
5. Выяснить, какая из фирм получила наибольшую прибыль и каков
размер этой прибыли.
6. Удалить из коллекции информацию об обанкротившейся фирме.
7. Добавить в коллекцию информацию о новой фирме.
Задание 3.11. Клиенты банка-1.
У клиента банка имеются характеристики: фамилия, номер паспорта,
количество счетов, номера счетов, размер вклада на каждом счете.
1. Описать классы «Депозит» и «Клиент».
2. Создать массив, содержащий информацию о клиентах банка.
3. Определить сумму вкладов всех вкладчиков.
4. Начислить премию в размере 2% вкладчику с максимальным вкладом.
5. Выполнить перевод заданной суммы с одного счета на другой (счета могут принадлежать разным клиентам).
Задание 3.12. Клиенты банка-2.
У клиента банка имеются характеристики: фамилия, номер паспорта,
количество счетов, номера счетов, годовой процент по каждому счету, размер вклада на каждом счете, дата открытия каждого счета, дата
закрытия счета.
1. Описать класс «Клиент банка».
2. Создать коллекцию, содержащую информацию о клиентах банка.
3. Определить, кому из вкладчиков начислена наибольшая сумма
процентов и размер этой суммы.
4. Определить номер счета с минимальным вкладом и год его открытия.
5. Выполнить перевод заданной суммы с одного счета на другой (счета могут принадлежать разным клиентам).
6. Добавить в коллекцию новых вкладчиков.
7. Удалить из коллекции вкладчиков, которые закрыли счет.
8. Выяснить, у кого из клиентов открыты вклады сроком на 2 года и
более.
91
Глава 4. Наследование
Наследование позволяет создать общий класс, который определяет элементы, характерные множеству других классов. Таким образом,
новые классы можно создавать на основе существующего классапредка. Это, в свою очередь, позволяет избежать дублирования кода
и облегчить редактирование программ. В этой главе рассматриваются
описание и использование наследования, а также его особенности и
достоинства.
4.1. Понятие наследования
Наследование – такое отношение между классами, когда один
класс повторяет структуру и поведение другого класса (наследовать – иметь признаки родителя).
Иерархические отношения между классами называют отношениями «родитель-потомок». В С# родитель называется базовым
классом, а потомок – производным (рис. 4.1).
Рис. 4.1. Графическое представление базового
и производного классов
Стрелка на рисунке 4.1 указывает от производного класса к базовому, так как элементы базового класса встраиваются в производный.
Базовый класс при этом не меняется, он даже не знает о появлении
производного класса.
Говорят, что производный класс наследует базовому классу.
92
Наследование создает иерархическую (древовидную) структуру
классов (рис. 4.2).
Рис. 4.2. Пример иерархической структуры классов
(графическое представление)
Объектно-ориентированное программирование – процесс разработки иерархии классов.
4.2. Особенности базового и производного классов
Базовый класс:
 создается в тех случаях, когда несколько классов (два и более) используют одинаковые поля и методы;
 является общим по отношению к производным классам.
Производный класс:
 наследует поля и методы базового класса;
 расширяет функциональность базового класса за счет добавления
полей и методов, уникальных для этого класса (выполняется переход от общего к частному);
 является совершенно независимым, несмотря на то, что имеет базовый класс.
Наследование позволяет избежать дублирования кода в производных классах. Следовательно, уменьшается объем программного
кода и сокращается время для его написания. Основная задача наследования состоит в том, чтобы обеспечить повторное использование
кода.
93
В C# при создании производного класса в качестве базового
можно указать только один класс. Это означает, что в C# запрещено
множественное наследование.
Пример 4.1. Построение иерархии классов «Компании города».
В городе работают страховые и транспортные компании. В таблице 4.1
описаны элементы классов «Страховая компания» и «Транспортная
компания».
Таблица 4.1. Классы и их элементы
Класс
Страховая
компания
Поля класса
название
количество
сотрудников
месячный фонд
зарплаты
количество
застрахованных
лиц
сумма страховых
взносов
сумма страховых
выплат
название
количество
сотрудников
месячный фонд
зарплаты
количество
Транспортная автомобилей
компания объем
грузоперевозок
94
Методы класса
печать информации о
компании
вычисление средней
зарплаты сотрудников
определение наибольшего
количества сотрудников,
которые могут работать в
компании при заданной средней зарплате
определение суммы налога и
отчислений в пенсионный
фонд
вычисление среднего
размера страховых взносов
печать информации о
компании
вычисление средней
зарплаты сотрудников
определение наибольшего
количества сотрудников,
которые могут работать в
компании при заданной средней зарплате
определение суммы налога и
отчислений в пенсионный фонд
определение среднего объема
грузоперевозок
Классы содержат общие поля и методы. Поэтому выделим их в
базовый класс «Компания». Этому классу будет наследовать класс
«Страховая компания» (добавятся три поля и один метод), а также
класс «Транспортная компания» (добавятся два поля и один метод).
Таким образом, получим иерархию классов (рис. 4.3).
Рис. 4.3. Иерархия классов «Компании города»
Один программист может создать и сопровождать класс «Компания»; другой – разрабатывать и сопровождать класс «Страховая компания»; третий – работать с классом «Транспортная компания».
95
Powered by TCPDF (www.tcpdf.org)
Если происходят изменения, касающиеся всех компаний, то их
вносит программист, который отвечает за базовый класс «Компания».
Производные же классы «Страховая компания» и «Транспортная
компания» наследуют все изменения базового класса. Поэтому говорят, что наследование обеспечивает распределение контроля над разработкой и сопровождением классов.
Описание производного класса имеет вид
class ИМЯ_ПРОИЗВОДНОГО_КЛАССА:ИМЯ_БАЗОВОГО_КЛАССА{
ОПИСАНИЕ ПОЛЕЙ
ОПИСАНИЕ СВОЙСТВ
ОПИСАНИЕ МЕТОДОВ
}
Пример 4.2. Иерархия классов «Компании города». Описание базового и производных классов.
Опишем иерархию классов из примера 4.1. Для того чтобы производные классы имели доступ к элементам базового класса, объявим все элементы базового класса с атрибутом public.
using System;
class Company {
public string name; //название
public int persons; //количество сотрудников
public int money;// месячный фонд зарплаты
//----------описание методов-------------// печать информации о компании
public void show(){
Console.WriteLine("В компании " + name +
"трудятся " + persons + " сотрудников " +
"фонд заработной платы " + money);
// вычисление средней зарплаты сотрудников
public int averageSalary (){
return money/persons;
}
// определение наибольшего количества
// сотрудников при заданной средней зарплате
public int maxPersons
(int salary/* средняя зарплата*/){
return money/persons;
}
96
// определение суммы налога и отчислений в ПФ
public int minus (int minus1/*налог в % */,
int minus2 /*отчисление в ПФ в %*/){
return money*(minus1 + minus2)/100;
}
}; // конец описания класса Company
class InsCompany : Company{
int counts; //количество застрахованных лиц
int summaplus; // сумма страховых взносов
int summaminus; // сумма страховых выплат
//-----------описание методов----------------//вычисление среднего размера страховых взносов
public int averageInsSumma (){
return summaplus/ counts;
}
}; // конец описания класса InsCompany
class AvtoCompany : Company{
int count; //количество автомобилей
int haul; // объём грузоперевозок
//---- ------описание методов--------------// определение среднего объема грузоперевозок
public int averageAvto (){
return haul/count ;
}
} // конец описания класса AvtoCompany
Пример 4.3. Иерархия классов «Компании города» (фрагмент). Использование методов с одинаковой сигнатурой в базовом и производном классах.
using System;
class Company{
publiс string name; //название
public int persons; //количество сотрудников
public int money; //месячный фонд зарплаты
//---------описание методов---------------public void show(){
Console.WriteLine
("Company.show: базовый класс");
}
97
// описания других методов – см. пример 4.2
}; // конец описания класса Company
class InsCompany : Company {
int counts; // количество застрахованных лиц
int summaplus; // сумма страховых взносов
int summaminus; // сумма страховых выплат
//-----------описание методов-----------------new public void show(){
Console.WriteLine
("InsCompany.show: производный класс");
}
// описания других методов – см. пример 4.2
} // конец описания класса InsCompany
class Program {
public static void Main(){
Company ob = new Company();
ob.show();
InsCompany ob1 = new InsCompany();
ob1.show();
}
}
Результат работы программы:
Company.show: базовый класс
InsCompany.show: производный класс
Первая строка печатается в результате вызова метода show базового класса, а вторая – в результате вызова метода show производного класса.
Если сигнатура метода производного класса совпадает с сигнатурой метода базового класса, то метод производного класса замещает метод базового класса. При этом заголовок метода производного класса должен начинаться с ключевого слова new.
4.3. Доступ к элементам класса и наследование
Если элементы базового класса объявлены с атрибутом
private, то в производных классах они недоступны. Для того чтобы
производный класс имел доступ к элементам базового класса, необязательно объявлять их с атрибутом public (см. примеры 4.2 и 4.3).
98
C# позволяет создавать защищённые элементы, которые доступны
для своей иерархии, но закрыты вне этой иерархии. Защищённые
элементы описываются с помощью спецификатора доступа
protected.
Таким образом, наследование обеспечивает ограничение доступа
к элементам класса.
Пример 4.4. Иерархия классов «Компании города». Описание базового класса и производных классов (все поля базового класса доступны только в рамках иерархии)
using System;
class Company {
protected string name; //название
protected int persons; //количество сотрудников
protected int money;// месячный фонд зарплаты
//-------------описание методов-----------public void show(){
Console.WriteLine("В компании " + name +
"трудятся " + persons + " сотрудников " +
"фонд заработной платы " + money);
}
public int averageSalary (){
return money/persons;
}
public int maxPersons
(int salary/* средняя зарплата*/){
return money/persons;}
public int minus (int minus1/*налог в % */,
int minus2 /*отчисление в ПФ в % */){
return money*(minus1 + minus2)/100;
}
}; // конец описания класса Company
class InsCompany : Company{
int counts; //количество застрахованных лиц
int summaplus; // сумма страховых взносов
int summaminus; // сумма страховых выплат
//-----------описание методов----------------//вычисление среднего размера страховых взносов
public int averageInsSumma (){
99
return summaplus/ counts;
}
}; // конец описания класса InsCompany
class AvtoCompany : Company{
int count ; //количество автомобилей
int haul; // объём грузоперевозок
//--------описание методов-------------------// определение среднего объема грузоперевозок
public int averageAvto (){
return haul/count ;
}
} // конец описания класса AvtoCompany
Все поля базового класса объявлены со спецификатором доступа
protected, поэтому доступны только в рамках иерархии классов.
4.4. Конструкторы и наследование
В иерархии классов как базовые, так и производные классы могут
иметь свои конструкторы.
Особенности конструкторов
1. Конструкторы не наследуются.
2. Конструктор производного класса:
вызывает конструктор базового класса, который создает часть
объекта, соответствующую базовому классу;
создает часть объекта, соответствующую производному классу.
3. Если в базовом классе описан конструктор, то все классы, которые от него наследуют, также должны содержать конструктор.
Описание конструктора производного класса:
public ИМЯ_ПРОИЗВОДНОГО_КЛАССА(СПИСОК_ПАРАМЕТРОВ_1):
base (СПИСОК_ПАРАМЕТРОВ_2){
ОПИСАНИЯ
ОПЕРАТОРЫ
}
100
СПИСОК_ПАРАМЕТРОВ_1 – параметры конструктора производного
класса
base(СПИСОК_ПАРАМЕТРОВ_2) – вызов конструктора базового
класса; ключевое слово base предоставляет доступ к элементам базового класса; сначала выполняется конструктор базового класса,
а затем – операторы конструктора производного класса
СПИСОК_ПАРАМЕТРОВ_2 содержит параметры, которые передаются конструктору базового класса
Пример 4.5. Иерархия классов «Компании города» (фрагмент). Описание базового и производного классов. Использование конструктора.
Создание и просмотр массива объектов
using System;
class Company{
protected string name; //название
protected int persons; //количество сотрудников
protected int money; // месячный фонд зарплаты
// конструктор
public Company
(string name, int persons, int money) {
this.name = name;
this.persons = persons;
this.money = money;
}
// свойство
public string Name {
get {return name;}
}
public void show(){
Console.WriteLine("В компании " + name +
" трудятся " + persons + " сотрудников "+
" фонд зарплаты " + money);
}
public int averageSalary(){
return money/persons;
}
public int maxPersons
(int salary/* средняя зарплата*/){
return money/persons;
101
}
public int minus
(int minus1 /*налог в % */,
int minus2 /*отчисление в ПФ в % */){
return money*(minus1 + minus2)/100;
}
}; // конец описания класса Company
class InsCompany : Company{
int counts; //количество застрахованных лиц
int summaplus; // сумма страховых взносов
int summaminus; // сумма страховых выплат
// конструктор
public InsCompany(string name, int persons,
int money, int counts,
int summaplus, int summaminus)
: base(name, persons, money) {
this.counts = counts;
this.summaplus = summaplus;
this.summaminus = summaminus;
}
//свойство
public int Summaplus{
get {
return summaplus;
}
}
// печать информации о страховой компании
new public void show(){
base.show();
Console.WriteLine
("застрахованных лиц "+counts +
" взносы "+ summaplus +
" выплаты
" + summaminus);
}
//вычисление среднего размера страховых взносов
public int averageIns(){
return summaplus / counts;
}
}; // конец описания класса InsCompany
102
Теперь рассмотрим работу с объектами класса InsCompany.
class Program{
public static void Main(){
int i,// параметр цикла
min,// наименьшая сумма страховых взносов
temp, j; //вспомогательные переменные
//создание массива ссылок
//на объекты класса InsCompany
InsCompany[]CityCompany = new InsCompany[4];
//создание и инициализация объектов
CityCompany[0] = new InsCompany
("ABC",100,100000,111,111111,1111);
CityCompany[1] = new InsCompany
("BCD",200,200000,222,222222,2222);
CityCompany[2] = new InsCompany
("CDE",300,300000,333,333333,3333);
CityCompany[3] = new InsCompany
("DEF",400,400000,444,444444,4444);
// печать информации о страховых компаниях
for (i = 0; i < 4; i++)
CityCompany[i].show();
//выяснить, какая страховая компания имеет
//наименьшую сумму страховых взносов
min = CityCompany[0].Summaplus; j = 0;
for (i = 1; i < 4; i++){
temp = CityCompany[i].Summaplus;
if (temp < min) {
min = temp;
j = i;
}
}
Console.WriteLine
("\n наименьшая сумма взносов в компании " +
CityCompany[j].Name);
}
}
В момент создания объекта конструктор производного класса
InsCompany сначала вызывает конструктор базового класса
Company. Поля name, persons и money инициализируются
103
конструктором базового класса. Для инициализации остальных полей
используются операторы конструктора производного класса.
4.5. Создание многоуровневой иерархии
Многоуровневое наследование имеет место, когда производный
класс наследует базовому классу, а затем сам становится базовым.
В этом случае иерархия содержит несколько уровней.
Рассмотрим построение многоуровневой иерархии «Вклады».
В банке используется несколько видов вкладов (счетов) – универсальный вклад и два вида депозитов. В таблице 4.2 приведены характеристики каждого вклада и операции с ними.
Таблица 4.2. Характеристики вкладов
Вклад (счет)
Универсальный
Депозит
«Сохраняй»
(дополнительные
взносы не предусмотрены)
Депозит
«Сохраняй
и пополняй»
(предусмотрены
дополнительные
взносы)
Характеристики
вклада
Операции с вкладами
баланс
дата открытия
открыть счет
закрыть счет
проверить баланс
снять со счета
положить на счет
баланс
дата открытия
процент по вкладу
срок вклада
открыть счет
закрыть счет
проверить баланс
начислить проценты
проверить, истек ли
срок вклада
баланс
дата открытия
процент по вкладу
срок вклада
минимальный
размер дополнительного взноса
104
открыть счет
закрыть счет
проверить баланс
начислить проценты
проверить, истек ли
срок вклада
положить на счет
Диаграммы классов при условии, что наследование не используется, представлены на рисунке 4.4.
Рис. 4.4. Диаграммы классов «Универсальный»,
«Депозит „Сохраняй“», «Депозит „Сохраняй и пополняй“»
Анализ классов показывает, что все они содержат общие поля
и методы. Поля «Баланс» и «Дата открытия», а также методы «Открыть счет», «Закрыть счет», «Проверить баланс» принадлежат всем
классам. Поэтому выделим их в базовый класс «Вклад». Этому классу будут наследовать класс «Универсальный» (добавятся два метода),
а также класс «Депозит „Сохраняй“» (добавятся два поля и два метода). Результат первого этапа построения иерархии классов на рисунке 4.5.
105
Рис. 4.5. Результат первого этапа построения
иерархии классов «Вклады»
Кроме того, классы для депозитов содержат общие поля и методы, используемые только в этих классах. К ним относятся поля «Процент по вкладу» и «Срок вклада», а также методы «Начислить проценты» и «Проверить, истек ли срок вклада». При этом класс «Депозит „Сохраняй и пополняй“» включает поле «Минимальный размер
дополнительного взноса», а также метод «Положить на счет».
Воспользуемся тем, что наследование позволяет использовать
производный класс в качестве базового класса. Тогда класс «Депозит
„Сохраняй и пополняй“» будет наследовать классу «Депозит „Сохраняй“» (рис. 4.6).
Таким образом,
класс «Вклад» содержит поля и методы, характерные для всех трех
видов вкладов;
класс «Универсальный» расширяет класс «Вклад» элементами,
специфическими для этого вида вклада;
класс «Депозит „Сохраняй“» расширяет класс «Вклад» элементами, характерными для этого вида вклада;
106
класс «Депозит „Сохраняй и пополняй“» наследует классу «Депозит „Сохраняй“», который включает поля и методы, унаследованные от класса «Вклад».
Рис. 4.6. Многоуровневая иерархия классов «Вклады»
107
4.6. Абстрактные классы
Поля и методы, общие для всех классов иерархии, описываются,
как правило, в базовом классе. В некоторых случаях нет смысла создавать объект базового класса. Например, базовый класс «Компания»
в иерархии «Компании города» (примеры 4.1, 4.2) не может быть использован для создания объекта, так как «просто компаний» не существует; все компании являются специализированными. Поэтому следует запретить создание объектов класса «Компания». Для этого в C#
нужно объявить класс абстрактным.
Описание абстрактного класса имеет вид
abstract class ИМЯ_КЛАССА {
ОПИСАНИЕ ПОЛЕЙ
ОПИСАНИЕ СВОЙСТВ
ОПИСАНИЕ МЕТОДОВ
}
Абстрактный класс подобен любому другому классу. Однако
нельзя создать объект абстрактного класса.
Пример 4.6. Описание абстрактного класса
abstract class Company {
protected string name; //название
protected int persons; //количество сотрудников
protected int money;// месячный фонд зарплаты
ОПИСАНИЕ СВОЙСТВ И МЕТОДОВ // см. пример 4.5
};
При попытке создания объекта класса Company компилятор выдаст сообщение об ошибке. Этот класс следует использовать только в
качестве базового в рамках иерархии классов.
Коротко о главном
1. Наследование – такое отношение между классами, когда один
класс повторяет структуру и поведение другого класса.
2. Наследование обеспечивает:
повторное использование кода;
распределение контроля над разработкой и сопровождением
приложения;
ограничение доступа к полям и методам.
108
3. Объектно-ориентированное программирование – процесс разработки иерархии классов.
4. Защищенные элементы, которые доступны для своей иерархии, но
закрыты вне этой иерархии, описываются со спецификатором доступа protected.
5. Конструкторы не наследуются.
6. Если конструктор описан в базовом классе, то и в производном
классе конструктор обязательно должен присутствовать.
7. В многоуровневом наследовании производный класс наследует базовому классу, а затем сам становится базовым.
8. В абстрактном классе обычно реализуется общая часть других
(производных) классов. Нельзя создать объект абстрактного класса.
Задания
Задание 4.1. «Студенты/Аспиранты».
Описать иерархию классов «Студенты/Аспиранты».
Характеристики студента: имя, факультет, год поступления, рейтинг.
Характеристики аспиранта: имя, факультет, год поступления, руководитель, код специальности.
Методы классов разработать самостоятельно.
1. Создать массив объектов класса «Студент».
2. Создать массив объектов класса «Аспирант».
3. Получить полную информацию о трех студентах первого курса
экономического факультета с самым высоким рейтингом.
4. Выяснить, кто из аспирантов специальности 05.13.11 завершает
обучение в текущем году.
5. Получить полную информацию обо всех студентах и аспирантах.
Задание 4.2. «Транспорт».
Описать иерархию классов «Транспорт», которая содержит классы
«Самолет» и «Автомобиль».
Характеристики самолета: мощность, стоимость, номер, марка,
максимальная высота полета.
Характеристики автомобиля: мощность, стоимость, номер, марка,
пробег, пройден ли техосмотр.
Методы классов разработать самостоятельно.
1. Создать коллекцию объектов класса «Автомобиль», содержащую
информацию об автомобилях, зарегистрированных в ГИБДД.
109
2. Создать коллекцию объектов класса «Самолет», содержащую информацию о самолетах частной авиакомпании.
3. Для заданного автомобиля найти стоимость и пробег.
4. Подсчитать налог с регистрации всех машин (5% стоимости машин).
5. Выяснить, прошел ли техосмотр владелец самой дорогой машины.
6. Для заданного самолета найти мощность и максимальную высоту
полета.
7. Подсчитать налог с регистрации всех самолетов (3% стоимости
самолетов).
8. Определить мощность, стоимость и марку самого дорогого самолета.
9. Получить полную информацию обо всех автомобилях и самолетах.
Задание 4.3. «Вклады».
1. Описать иерархию классов «Вклады» из раздела 4.5.
2. Проверить работоспособность полученной иерархии.
Задание 4.4. «Банковские счета» [6].
1. Описать многоуровневую иерархию классов «Банковские счета».
Банк предлагает клиентам следующие счета.
1. Сберегательный счет – Savings account.
2. Счет со временем погашения платежа – Timed Maturity Account.
3. Текущий счет – Checking Account.
4. Счет, по которому допускается овердрафт (форма предоставления краткосрочного кредита клиенту банка в случае, когда остаток на
счете клиента меньше или равен нулю) – OverdraftAccount.
Далее описаны особенности всех счетов.
Сберегательный счет – Savings account

При подсчете остатка начисляются проценты (в конце календарного года).
Например, если остаток вклада 1000 руб., а процентная ставка
2%, то после выплаты процентов остаток составит 1020 руб.

Вкладчик может осуществлять операции со счётом.

Овердрафт не допускается.
Счет со временем погашения платежа – Timed Maturity Account

При подсчете остатка начисляются проценты (в конце календарного года).

Вкладчик может осуществлять операции со счетом.
110

Если клиент банка снимет деньги со счета до окончания срока
вклада, то банк удержит проценты со снятой суммы.
Например, вкладчик снимет со счета 1000 руб. до окончания срока вклада, причем неустойка составляет 5% со снятой суммы.
Тогда остаток на счете уменьшится на 1000 руб., однако вкладчик получит только 950 руб.

Овердрафт не допускается.
Текущий счет – Checking Account

При подсчете остатка проценты не начисляются.

Вкладчик может осуществлять операции со счетом. Однако количество допустимых операций в месяц ограничено. Если вкладчик
превышает месячную квоту, банк налагает штраф на каждую
лишнюю операцию.

Овердрафт не допускается.
Счет, по которому допускается овердрафт, – OverdraftAccount
Вкладчик может снимать деньги даже в том случае, если снимаемая сумма превышает остаток. Однако банк налагает процентные выплаты на пассивный баланс.
Например, если баланс вкладчика равен 1000 руб., а процентная
ставка равна 20%, вкладчик должен уплатить банку 200 руб., и его
баланс составит 1200 руб.
Проценты начисляются только на счет с пассивным балансом.
Количество операций по вкладу в месяц не ограничивается.
2. Проверить работоспособность полученной иерархии.
111
Глава 5. Полиморфизм
В иерархии классов могут существовать несколько версий одного
метода. Это означает, что метод сначала определяется в базовом
классе, а затем переопределяется в производных классах. Интерес
представляют ситуации, когда любая из версий переопределенного
метода вызывается посредством ссылки на объект базового класса,
а решение о том, какую версию метода выполнить, принимается динамически – во время выполнения программы. В этом и заключается
принцип полиморфизма в ООП. В этой главе рассматривается реализация полиморфизма в рамках иерархии классов.
5.1. Ссылки на объект базового класса
и объекты производных классов
C# – язык программирования со строгой типизацией. Это означает, что нельзя применять операцию к операндам различных типов.
Исключения составляют неявные преобразования, которые выполняются компилятором автоматически и не ведут к потере точности данных. Неявные преобразования применяются к значениям значимых
типов. Следовательно, ссылка на объект одного класса не может
быть использована для ссылки на объект другого класса.
Пример. 5.1. Использование ссылок на объекты различных классов.
using System;
namespace MyProject{
class A {
public double x;
public A (double x) {
this.x = x;
}
}
class B{
public double y;
public int z;
public B (double y, int z) {
this.y = y;
this.z = z;
112
}
}
class Program{
public static void Main(){
A a1_object = new A(20.0);
A a2_object;
B b1_object = new B (25.5,10);
//ссылке на объект класса А присваиваем
//ссылку на объект класса А
a2_object = a1_object; // верно
//ссылке на объект класса А присваиваем
//ссылку на объект класса В
a2_object = b1_object; //ошибка!
}
}
}
В результате компиляции этой программы будет выдано сообщение об ошибке:
Cannot implicitly convert type 'MyProject.B'
to'MyProject.A'.
Ссылочная переменная некоторого класса может содержать
только ссылку на объект того же класса. Однако существует исключение из этого правила для иерархий классов.
Продолжим рассмотрение примера «Компании города» (рис. 5.1).
Рис. 5.1. Иерархия классов «Компании города»
Очевидно, что страховая компания – это компания. Обратное неверно, так как компания – это необязательно страховая компания.
Аналогичные выводы справедливы и для транспортной компании.
Таким образом,
объект класса «Страховая компания» является объектом класса
«Компания»;
113
объект класса «Транспортная компания» является объектом
класса «Компания».
Следовательно, ссылке на объект класса «Компания» можно присвоить ссылку на объект класса «Страховая компания», а также ссылку на объект класса «Транспортная компания». Обратное же неверно.
Пример. 5.2. Иерархия классов «Компании города». Использование
ссылок на объекты базового класса и производных классов
using System;
namespace myExample2{
class Company {
protected string name; //название компании
protected int persons; //количество
//сотрудников
protected int money; // месячный фонд зарплаты
ОПИСАНИЯ МЕТОДОВ
// см. пример 4.4
}
class InsCompany : Company{
int counts; //количество застрахованных лиц
int summaplus; // сумма страховых взносов
int summaminus; // сумма страховых выплат
ОПИСАНИЯ МЕТОДОВ
// см. пример 4.4
}
class AvtoCompany : Company {
int count; //количество автомобилей
int haul; // объём грузоперевозок
ОПИСАНИЯ МЕТОДОВ
// см. пример 4.4
}
class Program{
public static void Main(){
Company my_Company1,
my_Company2;
// ссылке на объект базового класса присваи// ваем ссылку на объект производного класса
my_Company1 = new InsCompany (); //верно
my_Company2 = new AvtoCompany(); //верно
114
Powered by TCPDF (www.tcpdf.org)
//ссылке на объект производного класса присваи//ваем ссылку на объект базового класса
InsCompany myIns = new Company (); //неверно!
// другие действия
}
}
}
В результате компиляции оператора этой программы
InsCompany myIns = new Company ();
будет выдано сообщение об ошибке:
Cannot implicitly convert type
'myExample2.Company' to 'myExample2.InsCompany'.
Таким образом, ссылке на объект базового класса можно присвоить ссылку на объект производного класса. Обратное же неверно.
Это правило распространяется и на многоуровневые иерархии
классов (рис. 5.2).
Рис. 5.2. Иерархия классов «Вклады»
Ссылке на объект класса «Вклад» можно присвоить не только
ссылку на объект класса «Универсальный» и «Депозит „Сохраняй“»,
но и ссылку на объект класса «Депозит „Сохраняй и пополняй“».
115
5.2. Использование ссылки на объект базового класса
Снова рассмотрим иерархию классов «Компании города» (см.
пример 5.2).
Переменная my_Company1 – ссылка на объект базового класса.
Поэтому в результате выполнения оператора
my_Company1 = new InsCompany();
ссылка my_Company1 может быть использована для доступа только
к тем элементам производного класса InsCompany, которые определены в базовом классе.
Следовательно, операторы
my_Company1.name = "ABC";
my_Company1.persons = 10000;
записаны верно. Компиляция же операторов
my_Company1.counts = 10000; // ошибка!
my_Company1.haul = 20000; // ошибка!
завершится выдачей сообщений об ошибках:
'myExample2.Company' does not contain a definition
for 'counts'
'myExample2.Company' does not contain a definition
for 'haul'.
Дело в том, что поля counts и haul объявлены в производных
классах. Базовый же класс не знает о существовании производных
классов, поэтому ему неизвестно, какие элементы были добавлены в
производные классы.
Важно понимать, что именно тип ссылочной переменной, а не
тип объекта, на который она ссылается, определяет, какие элементы являются доступными.
Это правило распространяется и на работу с методами. Так как
ссылка my_Company1 позволяет получить доступ только к тем элементам, которые определены в базовом классе, в результате компиляции оператора
int x = my_Company1.averageInsSumma(); //ошибка!
будет выдано сообщение об ошибке:
'myExample2.Company' does not contain a definition
for 'averageInsSumma'.
116
5.3. Виртуальные методы
В иерархии «Компании города» (см. пример 4.4) метод show
(«Печать информации о компании») определяется в базовом классе и
наследуется производными классами. Результат его работы одинаков
для объектов всех трех классов. Однако для объектов каждого класса
должен быть собственный вариант реализации этого метода.
Рассмотрим ситуацию, когда в рамках одной иерархии существует несколько версий одного метода. Для этого добавим метод show
в классы InsCompany и AvtoCompany. Теперь все три класса
иерархии содержат метод с одинаковым именем.
Пусть имеется фрагмент программы:
Company myCompany;
myCompany = new InsCompany
("FIRST",3,30000,50000,500000,100000);
myCompany.show();
Оба класса, Company и InsCompany, содержат метод show.
Поэтому возникает вопрос: какая версия метода show будет вызвана – из класса Company или из класса InsCompany?
Вариант 1 (использование обычных методов)
Объявление метода show в базовом классе Company:
public void show(){
Console.WriteLine("В компании " + name +
"трудятся " + persons + " сотрудников " +
" фонд заработной платы " + money);
Объявление метода show в производном классе InsCompany:
new public void show(){
base.show();
Console.WriteLine(
"застрахованных лиц "+counts +
" взносы "+ summaplus +
" выплаты " + summaminus);
}
В результате выполнения оператора
my_Company.show();
117
будет вызван метод show базового класса, несмотря на то, что этот
метод переопределяется в производном классе. Таким образом, будет
напечатана только информация о названии компании, количестве ее
сотрудников и фонде зарплаты.
В случае использования обычных методов тип ссылочной переменной определяет, какие методы будут выполнены.
Вариант 2 (использование виртуальных методов)
Метод show объявлен
 с атрибутом virtual (виртуальным методом) в базовом классе Company:
public virtual void show() {
Console.WriteLine (
"В компании " + name +
"трудятся " + persons + " сотрудников " +
"фонд заработной платы "+ money);
}
 с атрибутом override (методом-заменителем) в производном классе InsCompany:
public override void show() {
base.show();
Console.WriteLine(
"застраховано "+ counts +
" лиц "+ " сумма взносов " + summaplus +
" сумма выплат "+summaminus);
}
Тогда в результате выполнения оператора
my_Company.show();
будет вызван метод show производного класса. В результате этого
вызова будут напечатаны значения шести полей.
В случае использования виртуальных методов и методовзаменителей тип объекта, на который указывает ссылка (а не тип
ссылки), определяет, какая версия метода будет выполнена.
Метод-заменитель и виртуальный метод должны иметь одинаковые заголовки.
118
5.4. Динамическое связывание
Пусть метод show объявлен с атрибутом virtual в базовом
классе Company и с атрибутом override в производных классах
InsCompany, AvtoCompany. Во время компиляции программы с
объявлением
Company myCompany;
и операторами
if (УСЛОВИЕ)
myCompany = new InsCompany
("FIRST",3,30000,50000,500000,100000);
else {
myCompany = new AvtoCompany
("BEST",20,500000,50,800000);
}
myCompany.show();
неизвестно, ссылка на объект какого класса будет храниться в переменной myCompany: InsCompany или AvtoCompany.
Во время выполнения программы будет вызван метод show класса:
InsCompany, если переменная myCompany содержит
ссылку на объект класса InsCompany;
AvtoCompany, если myCompany содержит ссылку на
объект класса AvtoCompany.
Таким образом, решение о том, какую версию этого метода вызвать, принимается динамически – во время выполнения программы.
Связывание – установка соответствия между вызовом метода и
его описанием.
В объектно-ориентированных языках связывание происходит во
время:
компиляции;
выполнения.
Статическое (раннее) связывание – связывание во время компиляции.
В этом случае во время компиляции известно, какая версия метода будет вызвана.
119
Динамическое (позднее) связывание – связывание во время выполнения.
Такое название объясняется тем, что даже если во время компиляции известно имя вызываемого метода, например myCompany.show(), решение о том, какую версию этого метода вызвать,
принимается во время выполнения программы.
Раннее связывание используется для обычных функций. Позднее
связывание реализуется с помощью виртуальных функций. Как правило, виртуальная функция вызывается через ссылку на объект базового класса, которая может быть использована для ссылки на объект
производного класса. В этом случае версия вызываемого метода определяется типом объекта, на который указывает ссылка.
Значение слова «полиморфизм» – возможность принимать множество форм. В ООП это означает:
возможность существования нескольких (множества) версий
(форм) метода в рамках иерархии классов;
вызов подходящей версии посредством динамического связывания.
Полиморфизм и динамическое связывание – разные названия одного и того же механизма.
5.5. Основные этапы реализации полиморфизма
1. Объявить метод с атрибутом virtual (виртуальный) в базовом классе.
В примере о компаниях – это метод show().
class Company {
protected string name;//название
protected int persons;//количество сотрудников
protected int money;// месячный фонд зарплаты
public Company
(string name, int persons, int money) {
this.name = name; this.persons = persons;
this.money = money;
}
public virtual void show() {
Console.WriteLine (
"В компании " + name +
120
" трудятся " + persons + " сотрудников " +
" фонд заработной платы "+ money);
}
//описание других методов - см. пример 4.4
}
2. Объявить этот метод с атрибутом override в производных
классах.
class InsCompany : Company {
int counts; //количество застрахованных лиц
int summaplus; // сумма страховых взносов
int summaminus; // сумма страховых выплат
public InsCompany(string name, int persons,
int money, int counts,
int summaplus, int summaminus)
: base(name, persons, money) {
this.counts = counts;
this.summaplus = summaplus;
this.summaminus = summaminus;
}
public override void show() {
//вызов одноимённого метода базового класса
base.show();
Console.WriteLine
("застраховано "+ counts + " лиц "+
" сумма взносов "+ summaplus +
" сумма выплат "+summaminus);
}
//описание других методов - см. пример 4.4
}
class AvtoCompany: Company {
int count ; //количество автомобилей
int haul; // объём грузоперевозок
public AvtoCompany (string name, int persons,
int money, int count, int haul)
: base(name, persons, money) {
this.count = count;
this. haul = haul;
}
public override void show() {
121
base.show();
Console.WriteLine (
"количество автомобилей " + count +
" объём грузоперевозок " + haul);
}
//описание других методов - см. пример 4.4
}
3. В основной программе воспользоваться правилом:
ссылка на объект базового класса может быть использована как
ссылка на объект производного класса.
class MyDemo {
public static void Main() {
Company [] newcomp = new Company [4];
//ссылке на объект базового класса присвоить
//ссылку на объект производного класса
newcomp [0] = new InsCompany
("FIRST",3,30000,50000,500000,100000);
newcomp [1] = new AvtoCompany
("BEST",20,500000,50,800000);
newcomp [2] = new InsCompany
("FOOD",20,500000,5,800000,50000);
newcomp [3] = new AvtoCompany
("GOOD",30,300000,500,500000);
// вызвать метод show(),используя
// ссылку на объект базового класса:
for (int i=0; i < newcomp.Length; i++)
//динамическое связывание
newcomp [i].show();
}
}
В результате работы программы будет напечатана информация о
страховой компании FIRST, затем о транспортной компании
BEST, о страховой компании FOOD и, наконец, о транспортной
компании GOOD.
Виртуальный метод переопределять необязательно. Если в производном классе не описана собственная версия виртуального метода,
то используется версия, определенная в базовом классе.
122
Иногда перегрузку методов также называют полиморфизмом, поскольку одно и то же имя метода может обозначать различное поведение, т. е. представлять различный код. Однако в большинстве случаев термин «полиморфизм» используется применительно к динамическому, а не статическому связыванию.
Пример 5.3. Статическое связывание.
using System;
class Company {
ОПИСАНИЕ ПОЛЕЙ И МЕТОДОВ
// см. раздел 5.5.
}
class InsCompany : Company {
ОПИСАНИЕ ПОЛЕЙ И МЕТОДОВ
// см. раздел 5.5.
}
class AvtoCompany: Company {
ОПИСАНИЕ ПОЛЕЙ И МЕТОДОВ
// см. раздел 5.5.
}
class MyDemo {
public static void Main(){
InsCompany С1 = new InsCompany
("FOOD",20,500000,5,800000,50000);
AvtoCompany С2= new AvtoCompany
("GOOD",30,300000,500,500000);
// статическое связывание
С1.show( );
// статическое связывание
С2.show( );
}
Переменная С1(С2) объявлена как ссылка на объект класса
InsCompany(AvtoCompany), поэтому уже во время компиляции
известно, что метод C1.show(C2.show) принадлежит классу
InsCompany(AvtoCompany). Следовательно, в этом случае имеет
место статическое связывание.
123
5.6. Приведение типов объектов
Оператор присваивания
Company myСompany = new InsCompany
("FOOD",20,500000,5,800000,50000);
записан верно, так как ссылка на объект базового класса может быть
использована как ссылка на объект производного класса. Такое присваивание требует восходящего (снизу вверх) приведения типов, поскольку класс Company в иерархии (рис. 5.1) расположен уровнем
выше, чем класс InsCompany. При этом операция явного приведения типа не требуется. Нисходящее же приведение типа имеет место
при движении по иерархии сверху вниз.
Оператор присваивания
InsCompany my_InsComp = myСompany; // ошибка!
использовать нельзя. Дело в том, что переменная myСompany может
содержать ссылку не только на объект класса InsCompany, но и на
объект класса AvtoCompany. Однако, если известно, что
myСompany указывает на объект класса InsCompany, можно воспользоваться нисходящим приведением типа:
InsCompany my_InsComp = (InsCompany) myСompany;
Нисходящее приведение типа всегда требует явной операции
(<Тип>).
Для того чтобы узнать, указывает ли переменная на объект заданного типа, используется операция is. Например,
InsCompany my_InsComp;
Company myСompany = new InsCompany
("FOOD",20,500000,5,800000,50000);
if (myСompany is InsCompany)
my_InsComp = (InsCompany) myСompany;
Пример 5.4. Иерархия классов «Компании города». Явное приведение типа объекта. Статическое связывание.
using System;
class Company {
ОПИСАНИЕ ПОЛЕЙ И МЕТОДОВ
// см. раздел 5.5
}
124
class InsCompany : Company {
ОПИСАНИЕ ПОЛЕЙ И МЕТОДОВ
// см. раздел 5.5
}
class AvtoCompany: Company {
ОПИСАНИЕ ПОЛЕЙ И МЕТОДОВ
// см. раздел 5.5
}
class MyDemo {
public static void Main(){
Company [] newcomp = new Company [4];
newcomp [0] = new InsCompany
("FIRST",3,30000,50000,500000,100000);
newcomp [1] = new AvtoCompany
("BEST",20,500000,50,800000);
newcomp [2] = new InsCompany
("FOOD",20,500000,5,800000,50000);
newcomp [3] = new AvtoCompany
("GOOD",30,300000,500,500000);
for (int i = 0; i < newcomp.Length; i++)
//содержит ли newcomp[i] ссылку на
//объект класса InsCompany?
if (newcomp[i] is InsCompany) {
// явное приведение типа объекта
InsCompany A = (InsCompany)newcomp[i];
A.show();// статическое связывание
}
else {
AvtoCompany B =
(AvtoCompany)newcomp[i];
B.show();// статическое связывание
}
}
}
При вызове методов A.show()и B.show()имеет место статическое связывание, так как версия вызываемого метода известна во
время компиляции
125
5.7. Абстрактные методы
Нередко возникают ситуации, когда невозможно описать реализацию метода в базовом классе. Например, в базовом классе «Сотрудник» (рис. 5.3) невозможно описать метод, реализующий начисление заработной платы, так как зарплата зависит от категории сотрудника (постоянный или временный).
Однако в производных классах этот метод должен быть обязательно реализован. Поэтому необходимо средство, благодаря которому базовый класс заставит производные классы обязательно определить все необходимые методы. Таким средством в С# являются абстрактные методы.
Абстрактный метод – метод без реализации (имеется заголовок
метода, но отсутствует его тело).
Абстрактные методы могут быть только у абстрактных классов.
Рис. 5.3. Иерархия классов «Сотрудники предприятия»
Абстрактный метод описывается с помощью описателя
abstract.
Описание абстрактного метода:
аbstract ТИП ИМЯ (СПИСОК ПАРАМЕТРОВ);
Абстрактный метод автоматически является виртуальным. Однако метод, объявленный с описателем virtual, всегда имеет тело и
его необязательно переопределять. Совместное использование описателей abstract и virtual является ошибкой.
Пример 5.5. Иерархия классов «Сотрудники предприятия» (рис. 5.3).
Реализация полиморфизма с помощью абстрактного метода
using System;
abstract class worker{ // сотрудник
126
protected string name;
protected decimal salary;
//начисление зарплаты
public abstract decimal Give_Salary ();
}
//постоянный работник
class constworker :worker {
int year; double coef;
public constworker(int year, double coef) {
this.year = year; this.coef = coef;
}
//начисление зарплаты постоянному работнику
public override decimal Give_Salary() {
return (decimal)( 500 * year * coef);
}
}
// временный работник
class temporaryworker : worker{
int hours;
public temporaryworker(int hours) {
this.hours = hours;
}
// начисление зарплаты временному работнику
public override decimal Give_Salary(){
return ((decimal)(300 * hours));
}
}
class Program {
static void Main(){
worker[] factory = new worker[4];
factory[0] = new constworker(5, 1.1);
factory[1] = new temporaryworker(10);
factory[2] = new temporaryworker(6);
factory[3] = new constworker(6, 2.1);
Console.WriteLine(" Размер зарплаты ");
for (int i = 0; i < 4; i++){
if (factory[i] is constworker)
Console.Write
("постоянного работника: ");
127
else
Console.Write
("временного работника: ");
Console.WriteLine
(" " + factory[i].Give_Salary());
}
}
}
Результат работы программы:
Размер зарплаты
постоянного работника:
временного работника:
временного работника:
постоянного работника:
2750
3000
1800
6300
В классе worker объявлен абстрактный метод Give_Salary.
Реализация этого метода описана в производных классах. В основной
функции объявлен массив объектов, в который в производном порядке записаны объекты классов constworker и temporaryworker. В результате просмотра этого массива и обращения к методу Give_Salary печатается зарплата соответствующего работника.
Коротко о главном
1. Ссылка на объект одного класса не может быть использована
для ссылки на объект другого класса. Однако существует исключение
из этого правила для иерархий классов.
2. Ссылке на объект базового класса можно присвоить ссылку на
объект производного класса.
3. Если вызывается обычный метод, то тип ссылочной переменной, а не тип объекта, на который она ссылается, определяет, какие
элементы являются доступными.
4. Если вызывается виртуальный метод, то тип объекта, на который указывает ссылка (а не тип ссылки), определяет, какая версия
метода будет выполнена.
5. Установка соответствия между вызовом метода и его описанием называется связыванием.
6. В объектно-ориентированном программировании связывание
происходит во время компиляции (раннее связывание) или во время
выполнения программы (позднее или динамическое связывание).
128
7. Термин «полиморфизм» в ООП означает:
возможность существования нескольких (множества) версий
(форм) метода в рамках иерархии классов;
вызов подходящей версии посредством динамического связывания.
8. Восходящее приведение типов не требует специальной
операции (выполняется неявно).
9. Нисходящее приведение типа всегда требует явной операции
(<Тип>).
10. Абстрактный метод – метод без реализации. В производных
классах этот метод должен быть обязательно реализован.
11. Абстрактный метод автоматически является виртуальным.
Задания
Задание 5.1. Дисконтные карты.
Магазин хранит информацию о покупателях, получивших дисконтные карты. Запись о покупателе должна содержать:

фамилию, имя, отчество;

дату рождения.
Дополнительная скидка предоставляется в том случае, если покупатель приобретает товар в день своего рождения.
В дальнейшем руководство магазина приняло решение информировать покупателей о новых поступлениях товара с помощью smsсообщений. Поэтому были введены записи, которые содержат еще и

номер мобильного телефона.
Однако не все покупатели сообщают номер своего телефона. Поэтому должны поддерживаться старая и новая системы записей о покупателях.
1. Описать классы для старых и новых записей о покупателях.
2. Создать коллекцию объектов shoppers, содержащую информацию о старых и новых записях.
3. Вывести на экран информацию, содержащуюся в списке
shoppers.
4. Найти номер телефона покупателя по его фамилии, имени и
отчеству.
5. Вывести полную информацию обо всех покупателях, имеющих новые записи.
6. Определить количество старых и новых записей.
129
Задание 5.2. Пропуски для прохода в университет.
Университет имеет дорогостоящее оборудование и использует систему пропусков для прохода преподавателей и студентов на территорию. В конце учебного года пропуска выпускников аннулируются.
Имеются две системы учета пропусков – для преподавателей и для
студентов.
Информация на пропуске преподавателя:

фамилия, имя, отчество;

факультет;

кафедра.
Информация на пропуске студента:

фамилия, имя, отчество;

факультет;

год поступления.
1. Описать иерархию классов для пропусков студентов и преподавателей.
2. Создать массив объектов register, содержащий информацию о пропусках преподавателей и студентов.
3. Вывести информацию, содержащуюся в массиве register.
4. Вывести информацию о пропусках всех студентов.
5. Определить, сколько пропусков должно быть аннулировано.
130
Глава 6. Интерфейсы
Термин «интерфейс» имеет разный смысл в зависимости от контекста. Пользовательский интерфейс определяет способ взаимодействия пользователя с программами. В ООП интерфейсы понимаются в
другом смысле: интерфейс определяет набор методов, которые могут
быть реализованы классами. Интерфейс не реализует методы, он
только определяет, что должно быть сделано. Каждый класс может
определить собственную реализацию методов интерфейса. Таким образом, интерфейсы позволяют добавить поведение к классам, т. е. задать их дополнительные возможности. В этой главе рассматривается
использование интерфейсов для решения практических задач.
6.1. Описание и реализация интерфейсов
Интерфейс – набор методов, не имеющих реализации.
Интерфейс содержит только заголовки методов, которые будут
реализованы и вызваны в некоторых классах. Фактически интерфейс
определяет, что должно быть сделано, а не как необходимо это сделать. Интерфейсы похожи на абстрактные классы с абстрактными методами. Однако интерфейс не может содержать поля и методы с реализацией. Интерфейсы используются для того, чтобы добавить
методы в класс.
Как правило, разные классы реализуют одни и те же интерфейсы
по-разному.
Синтаксис описания интерфейса:
interface имя {
ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД_1
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ);
ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД_2
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ);
. . .
ТИП_ВОЗВР_ЗНАЧЕНИЯ ИМЯ_МЕТОД_N
(ФОРМАЛЬНЫЕ ПАРАМЕТРЫ);
}
131
Обычно имя интерфейса начинается с буквы I (от англ. Interface). Это условие не является обязательным, однако в этом случае
удобно отличать имена интерфейсов от имен классов.
Пример 6.1. Описание интерфейса «Счет в банке».
interface Ibank {
// Положить деньги на счет
void put(double summa);
// Снять деньги со счета
void get(double summa);
}
Чтобы реализовать интерфейс, нужно указать его имя после имени класса.
Синтаксис класса, который реализует интерфейс:
class ИМЯ_КЛАССА: ИМЯ_ИНТЕРФЕЙСА {
ОПИСАНИЕ ПОЛЕЙ
ОПИСАНИЕ СВОЙСТВ
ОПИСАНИЕ МЕТОДОВ
}
Пример 6.2. Описание класса, реализующего интерфейс «Счет в банке».
class Client : Ibank {
string name; // имя клиента
double summa_bank; // сумма вклада
//конструктор
public Client(string name, double summa_bank ){
this.name = name;
this.summa_bank = summa_bank;
}
// реализация методов интерфейса Ibank I
// Положить деньги на счет
public void put(double summa) {
summa_bank += summa;
}
// Снять деньги со счета
public void get(double summa) {
if (summa <= summa_bank)
summa_bank -= summa;
}
} // класс Client
132
Существует два способа графического представления интерфейсов. Можно изобразить диаграмму самого интерфейса и диаграмму
класса, реализующего интерфейс. Диаграмма самого интерфейса состоит из двух частей (рис. 6.1а). В верхней части записывается слово
<<interface>> и имя интерфейса, а в нижней – его методы. На рисунке 6.1б представлена диаграмма класса, реализующего интерфейс (см.
пример 6.2). В этом случае интерфейс графически изображается окружностью, которая соединяется отрезком с диаграммой класса. При
этом имя интерфейса записывается рядом с окружностью. Отрезок
означает реализацию интерфейса.
Рис. 6.1. Диаграмма интерфейса Ibаnk (а)
и диаграмма класса Client, реализующего этот интерфейс (б)
Особенности интерфейсов:
В описании интерфейса у методов нельзя явно указывать модификатор доступа. Методы интерфейса неявно имеют модификатор
доступа public.
Класс может реализовывать любое количество интерфейсов.
Класс, реализующий интерфейс, может использовать различные
модификаторы доступа для методов. Отсутствие модификатора в
этом случае соответствует модификатору private.
Один интерфейс может быть реализован любым числом классов,
при этом каждый класс может иметь собственную реализацию методов интерфейса. Таким образом, интерфейсы позволяют использовать методы с одинаковым именем, но разной реализацией.
Класс, реализующий интерфейс, должен включать реализацию всех
его методов; в противном случае компилятор выдаст сообщение об
ошибке.
133
Powered by TCPDF (www.tcpdf.org)
Класс может наследовать базовому классу и при этом реализовывать интерфейсы. В этом случае перед списком интерфейсов необходимо указать имя базового класса.
Интерфейсы могут быть организованы в иерархию. Если интерфейс наследует какой-либо другой интерфейс, то он наследует все
его элементы.
Нельзя создать экземпляр интерфейса.
Пример 6.3. Использование классом нескольких интерфейсов. Использование одного интерфейса несколькими классами.
Для клиента банка (класс Bank_Client)доступны операции
«Положить деньги на счет» (put), «Снять деньги со счета» (get) и
«Начислить проценты» (percent). Этим операциям соответствуют
методы интерфейса Iaccount. Кроме того, клиенту банка в конце
года начисляется бонус при условии, что размер вклада больше
1 000 000 руб. и вклад пролежал более 6 месяцев. Клиенту магазина
(класс Shop_Client) также полагается бонус, который вычитается
из текущей покупки при условии, что сумма всех покупок в разные
дни превышает 30 000 руб. Метод для вычисления размера бонуса
(bonus) определяется в интерфейсе Ibonus и реализуется в классах
Bank_Client и Shop_Client. Диаграммы интерфейсов представлены на рисунке 6.2.
Рис. 6.2. Диаграммы интерфейсов Iaccount
и Ibonus
Класс Bank_Client содержит:
 четыре поля: имя клиента (name), номер паспорта (passport),
сумма вклада (summa_bank) и дата открытия счета (date);
 свойство Summa_bank;
 два метода: конструктор (Bank_Client), вывод информации о
клиенте (Person_Display).
134
Кроме того, этот класс реализует интерфейсы Iaccount и
Ibonus.
Класс Shop_Client содержит:
 два поля: сумма покупок (summa_shop), сумма текущей покупки (summa_buy);
 свойство Summa_buy;
 два метода: конструктор (Shop_Client), оплата покупки
(New_Buy).
Класс Shop_Client реализует интерфейс Ibonus.
Диаграммы классов представлены на рисунке 6.3.
Рис. 6.3. Диаграммы классов Bank_Client и Shop_Client
using System;
namespace Интерфейсы{
interface Iaccount{
// Положить деньги на счет
void put(double summa);
// Снять деньги со счета
void get(double summa);
135
// Начислить проценты
void percent();
}
interface Ibonus{
// начислить бонус
double bonus();
}
class Bank_Client : Iaccount, Ibonus{
string name; // имя клиента
int passport; // номер паспорта
double summa_bank; // сумма вклада
DateTime date; // дата открытия счета
public double Summa_bank { // свойство
get { return summa_bank; }
}
// конструктор
public Bank_Client(string name,
int passport, double summa_bank,
int year, int month, int day) {
this.name = name;
this.passport = passport;
this.summa_bank = summa_bank;
date = new DateTime (year, month, day);
}
// вывод информации о клиенте
public void Person_Display(){
Console.WriteLine (" "+name+" "+passport+
" "+ summa_bank+" "+date);
}
// реализация методов интерфейса Iaccount
// Положить деньги на счет
public void put(double summa) {
summa_bank += summa;
}
// Снять деньги со счета
public void get(double summa) {
if (summa <= summa_bank)
summa_bank -= summa;
}
136
// Начислить проценты в размере 10% годовых.
// Проценты начисляются один раз, если вклад
// пролежал год
public void percent () {
DateTime today = DateTime.Today;
if ((today - date).Days == 365)
summa_bank *= 1.1;
}
// реализация метода интерфейса Ibonus
// Начислить бонус в размере 0,5% в последний
// день года, если вклад более 1000000 рублей
// пролежал более 6 месяцев
public double bonus(){
double add_bonus = 0.0;
DateTime today = DateTime.Today;
DateTime endOfYear =
new DateTime(today.Year, 12, 31);
if (today == endOfYear) {
int summa_days =
(endOfYear - date).Days;
if (summa_bank > 1000000 &&
summa_days > 180)
add_bonus = summa_bank * 0.005;
Console.WriteLine
(" Бонус начислен" + add_bonus);
}
return (add_bonus);
}
} // класс Bank_Client
class Shop_Client : Ibonus{
double summa_shop; // сумма покупок
double summa_buy; // сумма текущей покупки
public double Summa_buy{ //свойство
get { return summa_buy; }
set { summa_buy = value; }
}
public Shop_Client(){ // конструктор
summa_shop = 0; summa_buy = 0;
}
137
// реализация метода интерфейса Ibonus
// Если сумма покупок больше 30000, но меньше
//40000, то скидка на покупку составляет 2%.
// Если сумма покупок > 40000, то скидка - 5%.
public double bonus(){
double add_bonus = 0.0;
if (summa_shop > 40000)
add_bonus = summa_buy * 0.05;
else
if (summa_shop > 30000)
add_bonus = summa_buy * 0.02;
return (add_bonus);
}
public void New_Buy(){ // оплата покупки
Console.WriteLine("Введите сумму покупки");
summa_buy = int.Parse(Console.ReadLine());
summa_shop += summa_buy;
}
}
class Program{
public static void Main(){
Bank_Client client = new Bank_Client(
"Ivanov", 1234567, 2000000, 2015, 1, 9);
client.put (50000); client.get (20000);
client.Person_Display();
client.percent();
// в конце года к вкладу добавляется бонус
client.put(client.bonus());
Console.WriteLine
(" сумма вклада: " + client.Summa_bank);
//===================================
Shop_Client client1 = new Shop_Client();
client1.New_Buy(); // первая покупка
client1.Summa_buy -= client1.bonus();
Console.WriteLine
("к оплате: " + client1.Summa_buy);
client1.New_Buy(); // вторая покупка
client1.Summa_buy -= client1.bonus();
Console.WriteLine
138
("к оплате: " + client1.Summa_buy);
}
}
}
Таким образом,
класс Bank_Client реализует два интерфейса Iaccount и
Ibonus;
интерфейс Ibonus реализуется по-разному двумя классами
Bank_Client и Shop_Client.
6.2. Использование стандартных интерфейсов
Разработчики языков создают библиотеки стандартных интерфейсов. Рассмотрим стандартные интерфейсы, предназначенные для
сортировки.
Объекты коллекции можно отсортировать по значению одного
или нескольких полей. Класс List содержит метод Sort, который
используется для сортировки объектов различных классов. При этом
класс, которому принадлежат сортируемые объекты, должен реализовывать обобщенный интерфейс IComparable <T>. Термин
«обобщенный» указывает на то, что интерфейс может работать с данными различных типов. Вместо символа T необходимо указать тип
сортируемых объектов. Интерфейс IComparable <T> содержит
единственный метод CompareTo с одним параметром. Этот метод
возвращает значение типа int – результат сравнения двух объектов:
меньше 0, если текущий объект предшествует параметру метода;
больше 0, если текущий объект следует за параметром метода;
нуль, если текущий объект занимает ту же позицию в порядке
сортировки, что и параметр метода.
Метод Sort класса List использует метод CompareTo для
сравнения объектов.
Пример 6.4. Сортировка коллекции объектов класса Company.
Реализация интерфейса IComparable <Company >
Вариант 1. Сортировка по количеству сотрудников
using System;
using System.Collections.Generic;
namespace Company{
139
class Company: IComparable <Company> {
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд зарплаты
// реализация метода интерфейса
// IComparable <Company>
// сравнение объектов по
// количеству сотрудников
public int CompareTo(Company compToCompare) {
if (this.persons > compToCompare.persons)
return 1;
else
if
(this.persons < compToCompare.persons)
return -1;
else return 0;
}
public Company(
string name, int persons, int money){
this.name = name;
this.persons = persons;
this.money = money;
}
// печать информации о компании
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
// описания других методов – см. пример 3.4
} // класс Company
class Program{
public static void Main(){
// создание коллекции объектов класса Company
List<Company> comp1 = new List<Company>() {
new Company("ABCD3",103,900001),
new Company("ABCD1",101,900002),
new Company("ABCD2",102,900003)
};
// вывод исходной коллекции
140
foreach (Company z in comp1)
z.show();
// сортировка коллекции
//по количеству сотрудников
comp1.Sort();
Console.WriteLine("Результат сортировки:");
// вывод отсортированной коллекции
foreach (Company z in comp1)
z.show();
}
} // класс Program
}
Вариант 2. Сортировка по названию компании
В этом случае для реализации метода CompareTo необходимо сравнивать строки (название компании – строка). Поэтому воспользуемся
статическим методом Compare класса String. Первые два параметра этого метода – сравниваемые строки; третий параметр – логическое значение true, если при сравнении учитывается регистр, в
противном случае – false. Метод Compare возвращает целое значение, которое:
меньше 0, если первая строка предшествует второй;
больше 0, если первая строка следует за второй;
равно = 0, если строки равны.
using System;
using System.Collections.Generic;
namespace Company{
class Company: IComparable <Company> {
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд зарплаты
// реализация метода интерфейса
// IComparable <Company>
public int CompareTo(Company compToCompare) {
// сравнение объектов по названию компании
if (String.Compare(this.name,
compToCompare.name, true)>0)
return 1;
else
141
if (String.Compare(this.name,
compToCompare.name,true)<0)
return -1;
else return 0;
}
public Company(
string name, int persons, int money){
this.name = name;
this.persons = persons;
this.money = money;
}
// печать информации о компании
public void show() {
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
// описания других методов – см. пример 3.4
} // класс Company
class Program {
public static void Main(){
// создание коллекции компаний
List<Company> comp1 = new List<Company>() {
new Company("ABCD3",102,900001),
new Company("ABCD1",101,900002),
new Company("ABCD2",100,900003)
};
// вывод исходной коллекции
foreach (Company z in comp1)
z.show();
// сортировка коллекции по названию компании
comp1.Sort();
// вывод отсортированной коллекции
Console.WriteLine("Результат сортировки:");
foreach (Company z in comp1)
z.show();
}
}// класс Program
}
142
В примере 6.4 класс Company содержит реализацию метода
CompareTo для сравнения объектов по одному критерию. Однако
может возникнуть необходимость сортировки объектов по нескольким критериям. В этом случае нужно:
1) создать несколько классов, каждый из которых будет выполнять сравнение объектов по определенному критерию; при этом каждый класс должен реализовывать интерфейс IComparer<Т>; метод
Compare этого интерфейса имеет два параметра (сравниваемые объекты) и возвращает целое число, которое:
меньше 0, если первый объект предшествует второму;
больше 0, если первый объект следует за вторым;
равно = 0, если объекты равны;
2) создать экземпляры для каждого класса, реализующего сравнение объектов по заданному критерию; эти экземпляры передаются
методу Sort в качестве параметра.
Пример 6.5. Сортировка коллекции объектов класса Company по нескольким критериям.
Реализация интерфейса IComparer<Company>
using System;
using System.Collections.Generic;
namespace Сортировка {
class Company{
string name; //название
int persons; //количество сотрудников
int money; // месячный фонд заработной платы
public string Name {// свойство
get { return name; }
}
public int Persons {// свойство
get { return persons; }
}
public Company(
string name, int persons, int money){
this.name = name; this.persons = persons;
this.money = money;
}
// печать информации о компании
public void show() {
143
Console.WriteLine("В компании " + name +
" трудятся "+ persons + " сотрудников ");
Console.WriteLine("Фонд зарплаты: "+ money);
}
// описания других методов – см. пример 3.4
} // класс Company
// создание класса для сравнения объектов по
// первому критерию - количество сотрудников
class Count: IComparer<Company> {
public int Compare(Company x, Company y) {
if (x.Persons > y.Persons)
return 1;
else if (x.Persons < y.Persons)
return -1;
else return 0;
}
}
// создание класса для сравнения объектов по
//второму критерию – названию компании
class CName :IComparer<Company> {
public int Compare(Company x, Company y) {
if (String.Compare(x.Name,
y.Name, true) > 0)
return 1;
else
if (String.Compare(x.Name,
y.Name, true) < 0)
return -1;
else return 0;
}
}
class Program {
public static void Main(){
List<Company> comp1 = new List<Company>() {
new Company("ABCD3",102,900001),
new Company("ABCD1",101,900002),
new Company("ABCD2",100,900003)
144
};
foreach (Company z in comp1) z.show();
// создание экземпляра класса, реализующего
// сравнение объектов по
// количеству сотрудников
Count SortPersons = new Count ();
// сортировка по количеству сотрудников
comp1.Sort(SortPersons);
// вывод результата сортировки
Console.WriteLine("Результат сортировки:");
foreach (Company z in comp1) z.show();
// создание экземпляра класса, реализующего
// сравнение объектов по названию компании
CName SortName = new CName();
// сортировка по названию компании
comp1.Sort(SortName);
// вывод результата сортировки
Console.WriteLine("Результат сортировки:");
foreach (Company z in comp1) z.show();
}
}
}
Каждый из классов Count и CName содержит реализацию метода
Compare для сравнения объектов по определенному критерию. Для
сортировки коллекции объектов создаются экземпляры этих классов,
которые передаются методу Sort в качестве параметра.
Коротко о главном
1. Интерфейс – набор методов, не имеющих реализации.
2. Интерфейсы используются для того, чтобы добавить методы в
класс.
3. Класс может реализовывать любое количество интерфейсов.
4. Один интерфейс может быть реализован любым числом классов, при этом каждый класс может иметь собственную реализацию
методов интерфейса.
5. Класс, реализующий интерфейс, должен включать реализацию
всех его методов; в противном случае компилятор выдаст сообщение
об ошибке.
145
Задания
Задание 6.1
Отсортировать коллекцию, содержащую информацию о туристических фирмах города (см. задание 3.10), по размеру прибыли за текущий год.
Задание 6.2
Отсортировать коллекцию, содержащую информацию о клиентах
банка (см. задание 3.12). Критерии сортировки:

размер вкладов;

дата открытия счетов;

фамилии вкладчиков.
Задание 6.3. Пропуски для прохода в университет
Университет использует систему пропусков для прохода преподавателей, студентов и строителей на территорию. В конце учебного
года пропуски обновляются или аннулируются по следующему
правилу:
у всех преподавателей пропуски обновляются;
если студенты завершили учебу, то их пропуски аннулируются;
пропуски строителей аннулируются в том случае, если они
закончили выполнение ремонтных работ.
Имеются три системы учета пропусков – для преподавателей,
студентов и строителей.
Информация на пропуске преподавателя:
фамилия, имя, отчество, факультет, кафедра.
Информация на пропуске студента:
фамилия, имя, отчество, факультет, год поступления.
Информация на пропуске строителя:
фамилия, имя, отчество, факультет, дата начала работ,
дата окончания работ.
1. Описать иерархию классов для пропусков студентов, преподавателей и строителей.
2. Описать интерфейс, который содержит метод, подтверждающий
обновление пропуска или его аннулирование.
3. Реализовать этот интерфейс для различных классов.
146
4. Создать коллекцию объектов register, содержащую информацию о пропусках преподавателей, студентов и строителей. Данные
ввести из файла (информация о преподавателях, студентах и
строителях в файле расположена в произвольном порядке).
5. Вывести информацию, содержащуюся в коллекции register.
6. Вывести информацию о тех пропусках, которые должны быть аннулированы.
7. Вывести информацию о тех пропусках, которые должны быть обновлены.
147
Глава 7. Работа с файлами
Во всех ранее рассмотренных примерах использовались средства
консольного ввода/вывода – статические методы ReadLine и
WriteLine класса Console пространства имен System. Однако
данные, введенные с клавиатуры и отображенные на экране, доступны
лишь во время выполнения программы; по завершении работы программы все данные теряются. Содержимое же файлов можно использовать многократно. Кроме того, использование файлов позволяет работать с большими объемами данных. В этой главе рассматривается
работа с текстовыми файлами, а также сохранение состояния объектов
в бинарном файле.
7.1. Текстовые и бинарные файлы
Файлы можно рассматривать как текстовые или бинарные.
Текстовые файлы – последовательность символов, например, в
кодировке ASCII или Unicode.
Текстовые файлы предназначены для просмотра пользователем.
Числа и символы, записанные в памяти компьютера, сохраняются
в текстовом файле только после предварительного преобразования в
символьное представление. Содержимое текстового файла запоминается в памяти компьютера после преобразования во внутреннее представление.
Бинарные файлы – последовательность битов, которые интерпретируются только программами.
Данные в таком файле сохраняются в том виде, в каком они записаны в памяти компьютера.
В С# для организации работы с файлами используется понятие
поток.
Поток (Stream) – абстрактное понятие, относящееся к переносу
данных от источника к преемнику.
Текстовые файлы обрабатываются через символьные потоки, а
бинарные файлы – через бинарные потоки.
148
Все классы потоков для управления файлами определены в пространстве имен System.IO. Чтобы использовать эти классы, в начало программы необходимо включить инструкцию
using System.IO;
Метод Console.ReadLine используется для чтения данных
стандартного потока. Метод Console.WriteLine – для записи
данных в стандартный поток.
При попытке обращения к несуществующему файлу во время выполнения программы возникает ошибка – исключительная ситуация
(исключение). Язык С# позволяет обработать такую ситуацию, а
именно: определить блок кода, который будет автоматически выполняться при возникновении ошибки. Поэтому для организации работы
с файлами важно использовать механизм обработки исключений.
7.2. Исключения
Если во время выполнения программы возникает ошибка или непредвиденная ситуация, то генерируется исключение. Например,
ошибка – деление на ноль или обращение к несуществующему файлу,
непредвиденная ситуация – нехватка памяти. После генерации исключения его можно перехватить и выполнить некоторые действия.
Для обработки исключения код разбивается на два блока, try и
catch:
try {
КОД, КОТОРЫЙ МОЖЕТ СГЕНЕРИРОВАТЬ ИСКЛЮЧЕНИЕ
}
catch {
КОД, ОБРАБАТЫВАЮЩИЙ ИСКЛЮЧЕНИЕ
}
Когда генерируется исключение, в блок catch передается объект
класса Exception пространства имен System. В этом классе определено несколько свойств, которые можно использовать для получения полезной информации об исключении.
Воспользуемся свойством Message, которое содержит описание
исключения:
149
try {
КОД, КОТОРЫЙ МОЖЕТ СГЕНЕРИРОВАТЬ ИСКЛЮЧЕНИЕ
}
catch (Exception e) {
Console.WriteLine(e.Message);
Console.WriteLine
("Проверьте правильность имени файла");
}
Exception – имя класса пространства имен System;
е – ссылка на объект.
При генерации исключения в блоке try управление передается в
блок catch, где на экран выводится информация об исключении.
Пример 7.1. Обработка ошибки – деление на ноль.
using System;
namespace TryCatch{
class Program{
static void Main(){
int my=0;
try {
int zero=0;
my = 1 / zero;
}
catch (Exception e) {
Console.WriteLine(e.Message);
Console.WriteLine("Деление на 0");
}
Console.WriteLine("my=" + my);
}
}
}
Результат работы программы:
Attempted to devide by zero
Деление на ноль
my=0
150
7.3. Текстовый ввод/вывод
Пространство имен System.IO содержит классы
StreamReader и StreamWriter,
предназначенные для работы с символьными потоками. Эти потоки
применимы только для текстовых файлов. Назначение нескольких
методов классов StreamReader и StreamWriter представлено
в таблицах 7.1 и 7.2.
Таблица 7.1. Методы класса StreamReader
Метод
Назначение
Read
Читает символы из входного потока
ReadLine
Читает строку из входного потока;
возвращает null, если достигнут конец
файла
Close
Закрывает выходной поток
Таблица 7.2. Методы класса StreamWriter
Метод
Назначение
Write
Выводит в поток символы без символа новой строки
WriteLine
Выводит в поток символы и добавляет
символ новой строки
Close
Закрывает выходной поток
7.3.1. Ввод информации из текстового файла
Для ввода информации из текстового файла необходимо выполнить следующие действия.
1. Добавить в начало программы инструкцию
using System.IO;
2. Создать ссылку на объект класса StreamReader.
3. В блоке try cоздать объект класса StreamReader и передать
конструктору этого класса параметр – имя файла. Например:
151
StreamReader in_f;
try {
in_f = new StreamReader
("E:\\Temp\\aaa2.txt");
}
catch (Exception e) {
Console.WriteLine(e.Message);
Console.WriteLine
("Проверьте правильность имени файла");
}
или
StreamReader in_f;
try {
in_f = new StreamReader
(@"E:\Temp\aaa2.txt");
}
catch (Exception e) {
Console.WriteLine(e.Message);
Console.WriteLine
("Проверьте правильность имени файла");
}
Если при открытии файла произойдет ошибка, возникнет исключение и управление будет передано в блок catch.
4. Использовать методы Read и/или ReadLine для чтения информации из файла.
5. Закрыть выходной поток методом Close.
Пример 7.2. Чтение строк из файла и вывод на экран.
Файл, из которого читаем:
one
two
three
four
five
using System;
using System.IO;
namespace Read_strings {
152
Powered by TCPDF (www.tcpdf.org)
class Class1{
public static void Main(){
StreamReader f;
string s;
try {
f = new StreamReader
("E:\\Temp\\bank1.txt");
}
catch (Exception e) {
Console.WriteLine (e.Message);
return;
}
while ((s=f.ReadLine())!= null)
Console.WriteLine (s);
f.Close();
}
}
}
7.3.2. Особенности ввода данных
Строка может состоять из нескольких элементов (например,
чисел и/или строк), каждый из которых должен обрабатываться отдельно. Однако метод ReadLine читает строку целиком. Поэтому
необходимо уметь выделять элементы строки. Например, строка
"one 21035 7" состоит из трех элементов, каждый из которых
должен быть сохранен в отдельной переменной.
Тип string содержит метод Split, который разбивает строку
на массив строк, используя указанный разделитель. Метод Split
возвращает полученный массив строк.
Пример 7.3. Выделить из строки отдельные элементы.
//строка их трех элементов, разделенных пробелами:
string tеmp = "one 23456 9";
// tеmp.Split(' ') – массив строк из 3 элементов
string [] strf = new string [3];
strf = tеmp.Split (' ');
for (int i=0; i<3; i++)
Console.WriteLine (strf [i]);
153
Результат печати:
one
23456
9
Пример 7.4. Чтение строк из файла. Каждая строка содержит текст и
два числа. Выделить числа и преобразовать их во внутреннее представление.
Файл, из которого читаем:
one 2345 7
two 8912 89
three 6756 18
using System;
using System.IO;
namespace Read_strings1 {
class Class1{
public static void Main(){
StreamReader f; string s;
//3 элемента в строке
string[] strf=new string[3];
int x,y;
try {
f = new StreamReader
("E:\\Temp\\bbb1.txt");
}
catch (Exception e) {
Console.WriteLine (e.Message);
Console.WriteLine
("Проверьте правильность имени файла");
return;
}
while ((s=f.ReadLine())!= null) {
Console.WriteLine (" строка: " + s);
strf= s.Split(' ');
// преобразование выделенных подстрок
// во внутреннее представление чисел
x = Convert.ToInt32 (strf[1]);
y = Convert.ToInt32 (strf[2]);
154
x++; y++;
Console.WriteLine
(" x = " + x + " y = " + y);
}
f.Close();
}
}
}
Результат работы программы:
строка: one 2345 7
x = 2346 y = 8
строка: two 8912 89
x = 8913 y = 90
строка: three 6756 18
x = 6757 y = 19
7.3.3. Вывод информации в текстовый файл
Для вывода информации в текстовый файл необходимо выполнить следующие действия.
1. Добавить в начало программы инструкцию
using System.IO;
2. Создать ccылку на объект класса StreamWriter.
3. В блоке try cоздать объект класса StreamWriter . Конструктор
этого класса перегружен, т. е. может содержать один или два параметра. Первый параметр – имя файла, второй – значение логического
типа true или false. Значение true соответствует добавлению
текста в конец существующего файла, а false – удалению существующего файла и созданию нового с тем же именем. При использовании конструктора с одним параметром файл будет создаваться заново.
Например,
StreamWriter out_f;
try { // текст будет добавляться в конец файла
out_f = new StreamWriter
("E:\\Temp10\\ccc1.txt", true);
}
catch (Exception e) {
155
Console.WriteLine (e.Message); }
или
StreamWriter out_f;
try {
out_f = new StreamWriter
(@"E:\Temp\aaa2.txt", true);
}
catch (Exception e) {
Console.WriteLine (e.Message);}
Если при открытии файла произойдет ошибка, то возникнет исключение и управление будет передано в блок catch.
4. Использовать методы Write и/или WriteLine для записи данных в файл.
5. Закрыть выходной поток методом Close.
Пример 7.5. Чтение строк из файла и запись в другой файл.
using System;
using System.IO;
namespace Read_Write {
class Class1{
public static
StreamReader
StreamWriter
string s;
try {
f = new
void Main(){
f;
wr;
StreamReader
("E:\\Temp\\bbb1.txt");
// файл будет создаваться заново
wr = new StreamWriter
("E:\\Temp1\\ccc1.txt");
}
catch (Exception e) {
Console.WriteLine (e.Message);
return;
}
while ((s=f.ReadLine())!= null)
wr.WriteLine (s);
wr.Close(); f.Close();
156
}
}
}
Пример 7.6. Инициализация массива объектов из файла и запись в
другой текстовый файл.
Исходный файл:
Ivanov 12345
Petrov 98765
Sidorov 123456
using System;
using System.IO;
namespace ОбъектыЧтениеЗапись {
class Client {
string name;
int passport;
public string Name {
get {return name;}
}
public int Passport {
get {return passport;}
}
public Client ( StreamReader f ) {//конструктор
string temp = f.ReadLine( );
name = temp.Split (' ')[0];
passport = int.Parse
(temp.Split (' ') [1]);
}
public void Info () {
Console.WriteLine
(" name "+name+" passport "+passport);
}
}
class Program {
public static void Main() {
Client [ ] bank = new Client [3];
// создание объектов (инициализация из файла)
StreamReader f;
157
try {
f = new StreamReader
("E:\\Temp\\bank1.txt");
}
catch (Exception e) {
Console.WriteLine (e.Message);
return;
}
for (int i=0; i< bank.Length; i++)
bank [ i ] = new Client ( f );
for (int i=0; i< bank.Length; i++)
bank [ i ].Info () ;
f.Close ();
// запись объектов в текстовый файл
// запись каждого поля
Console.WriteLine("Запись в файл");
StreamWriter f1;
try {
f1 = new StreamWriter
("D:\\Temp1\\bank2.txt");
}
catch (Exception e) {
Console.WriteLine(e.Message);
return;
}
for (int i=0; i< bank.Length; i++)
f1.WriteLine
( bank[i].Name + " " + bank[i].Passport);
f1.Close();
}
}
}
7.4. Сериализация объектов
Сохранение объектов в текстовом файле требует достаточных
усилий, так как в этом случае необходимо отдельно запоминать каждое поле (см. пример 7.6). Чем сложнее объект, тем больший объем
кода нужно написать. Альтернативой сохранения объектов в текстовом файле является сериализация.
158
Сериализация – сохранение состояния объектов в бинарном или
XML-файле.
В первом случае объекты преобразуются в бинарный поток, во
втором – в формат xml.
Десериализация – восстановление состояния объектов, хранимых в файлах (процесс, обратный сериализации).
Сериализация и десериализация – сложные процессы, так как
объект может содержать унаследованные поля и вложенные объекты.
7.4.1. Cериализация объектов в бинарный файл
Для сериализации объекта в бинарный файл необходимо выполнить следующую последовательность действий.
1. Добавить в начало программы инструкцию
using
System.Runtime.Serialization.Formatters.Binary;
2. Объявить класс сериализуемого объекта с атрибутом
[Serializable].
3. Создать объект класса BinaryFormatter, который выполняет
сериализацию.
4. Создать поток, в который будет записываться объект.
5. Записать объект в поток (выполнить сериализацию).
6. Закрыть поток.
Пример 7.7. Сериализация объекта.
using System;
using System.IO;
// 1. Добавление инструкции
using
System.Runtime.Serialization.Formatters.Binary;
namespace Serialization{
//2. Объявление класса сериализуемого объекта
// с атрибутом [Serilizable]
[Serializable]
class Person{
string name;
159
int reiting;
public Person(string name, int reiting) {
this.name = name;
this.reiting = reiting;
}
}
class Program {
public static void Main(){
// Создание объекта для сериализации
Person student = new Person("Иванов", 5);
//3. создание объекта класса BinaryFormatter
BinaryFormatter formatter =
new BinaryFormatter();
//4. Создание потока, в который будет
// записываться объект
FileStream fs;
fs = new FileStream("D:\\people.dat",
FileMode.Open, FileAccess.Write );
// 5.Сериализация
formatter.Serialize(fs, student);
Console.WriteLine("Объект сериализован");
fs.Close();
}
}
}
Сначала создается объект student, который необходимо сериализовать. После этого создается объект formatter класса
BinaryFormatter и поток fs, в который будет записываться объект. Поток создается с параметрами FileMode.Open – открыть
файл и FileAccess.Write – записать данные в файл. Для сериализации вызывается метод formatter.Serialize, которому передаются два параметра:
поток, в котором будет происходить сохранение;
объект, который нужно сохранить в потоке.
Далее выполняется закрытие потока, так как сохранение завершено.
Аналогичным образом можно сериализовать массив объектов и
коллекцию.
160
Пример 7.8. Сериализация массива объектов.
using System;
using System.IO;
using
System.Runtime.Serialization.Formatters.Binary;
namespace Serialization{
[Serializable]
class Person{
string name; int reiting;
public Person(string name, int reiting) {
this.name = name; this.reiting = reiting;
}
}
class Program{
public static void Main(){
Person student1 = new Person("Петров", 7);
Person student2 = new Person("Иванов", 5);
Person student3 = new Person("Семенов", 2);
Person student4 = new Person("Андреев", 1);
Person[] students = new Person[]
{ student1, student2, student3, student4 };
BinaryFormatter formatter =
new BinaryFormatter();
FileStream fs;
fs = new FileStream("D:\\abc.dat",
FileMode.Open, FileAccess.Write);
// запись массива объектов в поток
formatter.Serialize(fs, students);
fs.Close();
}
}
}
Пример 7.9. Сериализация коллекции объектов.
using System;
using System.IO;
using
System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
161
namespace Serialization{
[Serializable]
class Person{
string name;
int reiting;
public Person(string name, int reiting) {
this.name = name; this.reiting = reiting;
}
}
class Program{
public static void Main(){
// объекты для сериализации
Person student1 = new Person("Петров", 7);
Person student2 = new Person("Иванов", 5);
Person student3 = new Person("Семенов", 2);
Person student4 = new Person("Андреев", 1);
// создание коллекции объектов класса Person
List <Person> students = new List <Person> ();
students.Add(student1);
students.Add(student2);
students.Add(student3);
students.Add(student4);
// создание объекта BinaryFormatter
BinaryFormatter formatter =
new BinaryFormatter();
// создание потока для сериализации коллекции
FileStream fs;
fs = new FileStream("D:\\abc.dat",
FileMode.Open, FileAccess.Write);
// сериализация коллекции
formatter.Serialize(fs, students);
Console.WriteLine(" Коллекция сериализована");
fs.Close();
}
}
}
162
7.4.2. Десериализация объектов из бинарного файла
Для десериализации объекта из бинарного файла необходимо выполнить следующую последовательность действий.
1. Добавить в начало программы инструкцию
using
System.Runtime.Serialization.Formatters.Binary;
2. Объявить класс десериализуемого объекта с атрибутом
[Serializable].
3. Создать объект класса BinaryFormatter, который выполняет
десериализацию.
4. Создать поток, из которого будет читаться объект.
5. Считать объект из потока.
6. Закрыть поток.
Пример 7.10. Десериализация объекта.
using System;
using System.IO;
// 1. Добавление инструкции
using
System.Runtime.Serialization.Formatters.Binary;
// сериализация объекта – см. пример 7.7
namespace Serialization{
// 2. Объявление класса десериализуемого
//объекта с атрибутом [Serilizable].
[Serializable]
class Person{
string name; int reiting;
public string Name{
get { return name; }
}
public int Reiting {
get { return reiting; }
}
public Person(string name, int reiting) {
this.name = name; this.reiting = reiting;
}
}
163
class Program{
public static void Main(){
//3. создание объекта класса BinaryFormatter
BinaryFormatter formatter =
new BinaryFormatter();
//4. создание потока, из которого
//будет читаться объект
FileStream fs1;
fs1 = new FileStream("D:\\people.dat",
FileMode.Open, FileAccess.Read);
// 5. Чтение объекта из потока
Person newStudent =
(Person)formatter.Deserialize(fs1);
Console.WriteLine("Объект десериализован");
Console.WriteLine("Имя - " + newStudent.Name +
" Возраст - " + newStudent.Reiting);
fs1.Close();
}
}
}
Сначала создается объект formatter класса BinaryFormatter, который будет выполнять десериализацию данных из файла. Затем поток fs1, из которого будет считываться объект. Параметры
потока: FileMode.Open – открыть файл и FileAccess.Read –
считать данные из файла.
Метод Deserialize класса BinaryFormatter имеет один
параметр – поток, из которого нужно читать данные. Этот метод возвращает объект, для которого требуется выполнить приведение к типу Person.
Замечание. Для бинарной десериализации объектов в другом проекте необходимо:
 выполнить команду Project/Properties; откроется окно;
 воспользоваться вкладкой Application;
 ввести в поле Assembly_name имя проекта, в котором файл был
сериализован.
Аналогичным образом можно десериализовать массив объектов и
коллекцию.
164
Пример 7.11. Десериализация массива объектов.
using System;
using System.IO;
using
System.Runtime.Serialization.Formatters.Binary;
// сериализация массива объектов – см. пример 7.8
namespace Serialization{
[Serializable]
class Person{
string name;
int reiting;
public string Name{
get { return name; }
}
public int Reiting{
get { return reiting; }
}
public Person(string name, int reiting) {
this.name = name;
this.reiting = reiting;
}
}
class Program {
public static void Main(){
BinaryFormatter formatter =
new BinaryFormatter();
FileStream fs1;
fs1 = new FileStream("D:\\abc.dat",
FileMode.Open, FileAccess.Read);
// десериализация массива объектов
Person[] newStudents =
(Person[])formatter.Deserialize(fs1);
foreach (Person p in newStudents)
Console.WriteLine("Имя - " + p.Name +
" Рейтинг - " + p.Reiting);
fs1.Close();
}
}
}
165
Результат работы программы:
Имя – Петров Рейтинг – 7
Имя – Иванов Рейтинг – 5
Имя – Семенов Рейтинг – 2
Имя – Андреев Рейтинг – 1
Пример 7.12. Десериализация коллекции.
using System;
using System.IO;
using
System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
//сериализация коллекции объектов – см. пример 7.9
namespace Serialization{
[Serializable]
class Person{
string name; int reiting;
public string Name{
get { return name; }
}
public int Reiting{
get { return reiting; }
}
public Person(string name, int reiting) {
this.name = name; this.reiting = reiting;
}
}
class Program
{
public static void Main(){
BinaryFormatter formatter =
new BinaryFormatter();
FileStream fs1;
fs1 = new FileStream("D:\\abc.dat",
FileMode.Open, FileAccess.Read);
List<Person> newStudents =
(List<Person>)formatter.Deserialize(fs1);
Console.WriteLine
("Коллекция десериализована");
166
foreach (Person p in newStudents)
Console.WriteLine("Имя - " + p.Name +
" Рейтинг - " + p.Reiting);
fs1.Close();
}
}
}
Результат работы программы:
Имя – Петров Рейтинг – 7
Имя – Иванов Рейтинг – 5
Имя – Семенов Рейтинг – 2
Имя – Андреев Рейтинг – 1
Важно помнить, что атрибут сериализации не наследуется. Поэтому если базовый класс объявлен с атрибутом [Serializable],
то объект производного класса будет сериализован только в том случае, если соответствующий ему класс также будет иметь атрибут
[Serializable]. Таким образом, чтобы при наследовании сохранить возможность сериализации, необходимо для базового и производного классов указать атрибут [Serializable].
Замечание. Массив (коллекция) может содержать:
объекты одного класса;
объекты базового и производных классов.
В обоих случаях сериализация/десериализация массива (коллекции) выполняется одинаково.
Коротко о главном
1. Исключение генерируется в том случае, если во время выполнения программы возникает ошибка или непредвиденная ситуация.
2. Для обработки исключения используются два блока: try и
catch. При генерации исключения в блоке try управление передается в блок catch.
3. Поток (Stream) – абстрактное понятие, относящееся к переносу данных от источника к преемнику.
4. Текстовые файлы – последовательность символов, например, в
кодировке ASCII или Unicode.
167
5. Бинарные файлы – последовательность битов, которые интерпретируются только программами.
6. Все классы потоков для управления файлами определены в
пространстве имен System.IO.
7. Классы StreamReader и StreamWriter предназначены
для работы с символьными потоками.
8. Тип String содержит метод Split, который разбивает строку на массив строк, используя указанный разделитель.
9. Сериализация – сохранение состояния объектов в бинарном
или XML-файле.
10. Десериализация – восстановление состояния объектов, хранимых в файлах (процесс, обратный сериализации).
11. Объект класса BinaryFormatter выполняет сериализацию
и десериализацию объектов.
Задания
Задание 7.1
Модифицировать задание 4.1 «Студенты/Аспиранты»:
1) ввести из файла информацию о студентах и аспирантах (для создания массива объектов);
2) вывести в файл информацию об аспирантах третьего года обучения.
Задание 7.2
Модифицировать задание 4.2 «Транспорт»:
1) ввести из файла информацию о самолетах и автомобилях (для создания коллекции объектов);
2) вывести в файл информацию обо всех автомобилях, которые не
прошли техосмотр.
Задание 7.3
Модифицировать задание 5.1 «Дисконтные карты»:
1) ввести из файла информацию о старых и новых записях (для создания коллекции объектов);
2) сериализовать коллекцию объектов, содержащую информацию о
старых и новых записях;
168
в другом приложении:
3) десериализовать коллекцию объектов о старых и новых записях;
4) определить количество покупателей, которые получат дополнительную скидку в текущем месяце;
5) определить количество sms-сообщений, которые необходимо отправить покупателям в текущем месяце с поздравлением и напоминанием о дополнительной скидке.
Задание 7.4
Модифицировать задание 5.2 «Пропуски для прохода в университет»:
1) ввести из файла информацию о пропусках студентов и преподавателей (для создания массива объектов);
2) сериализовать массив объектов, содержащий информацию о пропусках студентов и преподавателей;
в другом приложении:
3) десериализовать массив объектов о пропусках студентов и преподавателей;
4) определить, сколько пропусков выпускников экономического факультета должно быть аннулировано;
5) определить, какое количество пропусков выдано преподавателям
химического факультета.
169
Глава 8. Взаимодействие объектов
Парадигма объектно-ориентированного программирования предполагает, что любая программная система проектируется как совокупность объектов. Как правило, использование изолированных объектов не позволяет достичь поставленной цели. Интерес представляет
взаимодействие объектов, т. е. обмен информацией между объектами.
В этой главе на практической задаче показана организация взаимодействия объектов в ООП.
8.1. Организация взаимодействия объектов
Обычно говорят, что один объект взаимодействует с другим посредством сообщений, т. е. в процессе взаимодействия объекты обмениваются сообщениями. Сообщение – это вызов метода. Совместное
использование методов различных объектов – показатель взаимодействия объектов. Таким образом, методы одного объекта должны вызывать методы других объектов. В этом случае ни один метод, участвующий во взаимодействии, не может достигнуть цели самостоятельно. Именно поэтому так важно правильно организовать обмен информацией между методами различных объектов. Такой обмен предполагает, что результаты работы метода одного объекта являются исходными данными для метода другого объекта. Если объект
Object2 класса ClassB посылает сообщение объекту Object1
класса ClassА, то это означает, что объект Object2 вызывает метод объекта Object1 (рис. 8.1).
В этом случае при описании классов ClassA и ClassB возникает
проблема: как обратиться к методу класса ClassA в методе класса
ClassB?
Объект Object2 вызывает метод Find объекта object1
Рис. 8.1. Передача сообщения между объектами
170
Решение этой проблемы состоит в следующем.
Вариант 1
Cоздать объект класса ClassA в качестве поля класса ClassB; после этого из любого метода класса ClassB можно обращаться к методам класса ClassA.
Вариант 2
Cоздать объект класса ClassA в качестве локального объекта некоторого метода класса ClassB; в дальнейшем в этом методе можно
обращаться к методам класса ClassA.
Пример 8.1. Объект Object2 посылает два сообщения Find объекту Object1. Объект класса ClassA – поле класса ClassB
class ClassA {
. . .
public void Find ( ) { . . . }
}
class ClassB {
// объект класса ClassA – поле класса ClassB
ClassA object1 = new ClassA ();
. . .
public void Search1 ( ) {
. . .
object1.Find( );
}
public void Search2 ( ) {
. . .
object1.Find( );
}
}
class Program {
public static void
ClassB object2 =
. . .
object2.Search1(
object2.Search2(
}
}
Main( ){
new ClassB( );
);
);
171
Powered by TCPDF (www.tcpdf.org)
В результате вызовов методов object2.Search1 и object2.Search2 объект object2 посылает два сообщения Find
объекту object1.
Пример 8.2. Объект Object2 посылает сообщение Find объекту
Object1. Объект класса ClassA – локальный объект метода
класса ClassB
class ClassA {
. . .
public void Find ( ) {
. . .
}
}
class ClassB {
. . .
public void Search( ){
//объект класса ClassA-локальный объект метода
// класса ClassB
ClassA object1 = new ClassA ( );
. . .
object1.Find( );
}
}
class Program {
public static void Main( ){
ClassB object2 = new ClassB( );
. . .
object2.Search( );
}
}
В результате вызова метода object2.Search объект object2 посылает сообщение Find объекту object1.
8.2. Реализация взаимодействия объектов
Рассмотрим, как реализуется взаимодействие объектов на примере задачи «Поставщики/Товары».
172
Постановка задачи. Поставщики поставляют товары заказчикам.
Информация о поставщике – номер и фамилия поставщика. Информация о товаре – номер и название товара. Каждый поставщик должен зарегистрироваться как потенциальный поставщик конкретного
товара. Только после этого можно приступать к оформлению заказа
на товар. Информация о заказе – номер поставщика, номер товара и
количество единиц купленного товара.
Определим класс Supplier, который содержит информацию об
отдельном поставщике (рис. 8.2).
Замечания
1. В дальнейшем запись “:<имя класса>” в верхней части диаграммы объекта используется для обозначения объектов класса <имя
класса>.
2. Для наглядности диаграммы объектов дополнены изображениями
переменных.
Рис. 8.2. Диаграмма объекта класса Supplier
В классе Supplier описаны два поля – имя поставщика
s_name и номер поставщика s_id. Методы класса: конструктор,
печать информации о поставщике и сравнение двух объектов класса
Supplier на равенство (листинг 8.1).
Листинг 8.1. Описание класса Supplier.
class Supplier{
string s_name; // имя поставщика
int s_id; // номер поставщика
// описание свойств
public string S_name {
173
get { return s_name; }
}
public int S_id {
get { return s_id; }
}
// конструктор
public Supplier(string s_name, int s_id) {
this.s_name = s_name; this.s_id = s_id;
}
// вывод информации о поставщике
public void Display(){
Console.WriteLine("Supplier: "+ " s_name= "
+ s_name + " s_id= " + s_id);
}
// сравнение двух объектов класса Supplier
//на равенство
public override bool Equals(object obj) {
Supplier p = obj as Supplier;
return ((s_name == p.s_name)&&
(s_id == p.s_id));
}
}
Пояснение к использованию метода Equals. Библиотека классов содержит класс System.Object, по отношению к которому все
остальные классы являются производными. Любой класс наследует
элементы класса System.Object, в частности, метод
public
virtual bool Equals (object obj).
Обычно этот метод переопределяют в производном классе для
сравнения двух объектов. Операция as используется для нисходящего приведения типа. Метод Equals проверяет на равенство значения переменных s_name и s_id текущего объекта класса
Supplier со значениями переменных объекта, переданного в качестве параметра.
Кроме класса Supplier необходим класс Goods, который содержит информацию об отдельном товаре (рис. 8.3).
Поля класса Goods – название товара g_name и номер товара
g_id. Методы класса – конструктор, печать информации о товаре и
сравнение двух объектов класса Goods на равенство (листинг 8.2).
174
Рис. 8.3. Диаграмма объекта класса Goods
Листинг 8.2. Описание класса Goods.
class Goods {
string g_name; // название товара
int g_id; // номер товара
public string G_name {
get { return g_name; }
}
public int G_id {
get { return g_id; }
}
//конструктор
public Goods(string g_name, int g_id){
this.g_name = g_name; this.g_id = g_id;
}
// вывод информации о товаре
public void Display(){
Console.WriteLine("Goods: "+ "g_name=" +
g_name + " g_id=" + g_id);
}
// сравнение двух объектов класса
// Goods на равенство
public override bool Equals(object obj){
Goods p = obj as Goods;
return ((g_name == p.g_name) &&
(G_id == p.g_id));
}
}
175
Каждый поставщик должен зарегистрироваться как потенциальный поставщик товаров. Однако класс Goods не содержит информации о поставщиках, а класс Supplier – о товарах. Следовательно,
поставщик не может зарегистрироваться, а товар невозможно связать
с информацией о поставщиках, которые его поставляют. Поэтому
опишем еще два класса GSLink и GSRecords.
Класс GSLink связывает одного поставщика с одним товаром
(рис. 8.4).
Рис. 8.4. Диаграмма объекта класса GSLink
Поля класса GSLink – номер поставщика s_id, номер товара
g_id и количество товара, поставляемого этим поставщиком quantity . Методы этого класса – конструктор и проверка на совпадение
номеров поставщиков и номеров товаров у двух объектов класса
GSLink (листинг 8.3).
Листинг 8.3. Описание класса GSLink.
class GSLink{
int s_id, //номер поставщика
g_id; //номер товара
int quantity =0; // количество
// описание свойств
public int S_id {
get {return s_id; }
}
public int G_id {
get { return g_id; }
}
176
public int Quantity {
get { return quantity; }
set { quantity = value; }
}
// конструктор
public GSLink(int s_id, int g_id) {
this.s_id = s_id; this.g_id = g_id;
}
//проверка двух объектов на совпадение
// номеров поставщиков и товаров
public override bool Equals(object obj){
GSLink p = obj as GSLink;
return ((s_id == p.s_id) &&
(g_id == p.g_id));
}
} //class GSLink
Класс GSRecords используется для регистрации поставщиков
(рис. 8.5).
Поля класса GSRecords – массив gs_Links, содержащий информацию о потенциальных поставщиках (поставщик/товар);
max_counts и cur_counts – максимальное и текущее количество регистраций.
Перед регистрацией поставщика нужно проверить, существует ли
объект с заданным номером поставщика и номером товара в массиве
gs_Links. С этой целью в классе GSRecords описан метод
GS_Find , который
1) создает объект temp класса GSLink (рис. 8.6), содержащий
заданный номер поставщика num_sp и номер товара num_goods:
GSLink temp = new GSLink(num_sp, num_goods);
2) сравнивает объект temp с объектами массива gs_Links, а
именно вызывает метод Equals объекта temp; параметр этого метода – текущий объект массива gs_Links:
int i=0;
while((i<cur_counts)&&
!temp.Equals(gs_Links[i]))
i++;
177
Рис. 8.5. Диаграмма (пример) объекта класса GSRecords
Таким образом, перед добавлением нового объекта в массив
gs_Links объект класса GSRecords посылает сообщение Equals
объекту temp класса GSLink (передача сообщения выделена курсивом). Если объект с заданным номером поставщика и номером товара отсутствует в массиве gs_Links, метод AddRecord добавляет
объект temp в этот массив, т. е. регистрирует поставщика. При этом
считается, что номер поставщика и номер товара заданы верно (существуют). Регистрация поставщика завершается успешно, если она не
является повторной и не превышено максимально допустимое количество регистраций.
178
Рис. 8.6. Диаграмма (пример) объекта temp класса GSLink
Метод Display() распечатывает информацию обо всех объектах массива gs_Links. Так как доступ к полям объектов класса
GSLink – частный (private), получить их значения можно только в результате использования аксессора get соответствующих
свойств. Поэтому объект класса GSRecords посылает сообщения
каждому объекту массива gs_Links для определения номера поставщика, номера товара и его количества:
for (int i=0; i< cur_counts; i++)
Console.WriteLine(
"Supplier: " + gs_Links [i].S_id +
"Goods " + gs_Links [i].G_id +
"Quantity " + gs_Links [i].Quantity);
Описание класса GSRecords представлено в листинге 8.4.
Листинг 8.4. Описание класса GSRecords.
class GSRecords {
GSLink [] gs_Links; //ссылка на массив объектов
//класса GSLink
int max_counts; // максимальное
// количество регистраций
int cur_counts; // текущее
// количество регистраций
// описание свойства
public GSLink [] Gs_Links {
get{ return gs_Links;}
}
179
//конструктор
public GSRecords (int max_counts){
this.max_counts = max_counts;
cur_counts = 0;
//выделение памяти под массив ссылок
gs_Links = new GSLink [max_counts];
//память под объекты будет выделяться при
// регистрации поставщиков
}
// проверка, существует ли
//регистрация поставщика
public bool GS_Find (int num_sp,
int num_goods){
GSLink temp =
new GSLink (num_sp, num_goods);
int i=0;
while ((i < cur_counts) &&
!temp.Equals(gs_Links [i]))
i++;
if (i == cur_counts) return false;
else return true;
}
// регистрация поставщика
public bool AddRecord(int num_sp, int num_gsr){
// если повторная запись или много записей
if ( (GS_Find(num_sp, num_gsr))
||(cur_counts == max_counts) )
return (false);
else { // регистрируем поставщика
gs_Links[cur_counts++] =
new GSLink ( num_sp, num_gsr );
return (true);
}
}
// печать информации обо всех
// регистрациях поставщиков
public void Display(){
Console.WriteLine
("Поставщик
"+"Товар
"+ "Количество ");
for (int i = 0; i < cur_counts; i++)
180
Console.WriteLine("{0,4:d}{1,10:d}{2,9:d}",
gs_Links[i].S_id, gs_Links[i].G_id,
gs_Links[i].Quantity);
}
}
В результате создания объекта класса GSRecords и регистрации поставщиков создаются и входящие в этот объект компоненты –
объекты класса GSLink. Поэтому объект класса GSRecords является составным, а отношение между объектами классов GSRecords и GSLink – отношением включения.
Еще один класс Recorder содержит список всех поставщиков и
список всех товаров (рис. 8.7).
Эта информация хранится в массивах list_Supplier и
list_Goods соответственно. Массив list_Supplier может содержать поставщиков, которые пока не зарегистрировались на поставку какого-либо товара; а в массив list_Goods могут входить
товары, которые пока никто не планирует поставлять. Кроме того, в
классе Recorder описан объект gsr_GSRecords
класса
GSRecords, который связывает поставщиков и товары.
Методы AddSupplier и AddGoods добавляют новых поставщиков и новые товары в соответствующие массивы. Сначала метод AddSupplier проверяет, существует ли объект с заданным именем и номером поставщика в массиве list_Supplier, а именно:
1) создает объект temp класса Supplier, содержащий заданные значения имени name и номера поставщика num_sp:
Supplier temp = new Supplier(name, num_sp);
2) сравнивает объект temp с объектами массива list_Supplier,
а именно, вызывает метод Equals объекта temp; параметр этого
метода – текущий объект массива поставщиков
int i=0;
while((i<cur_sp)&&temp.Equals(list_Supplier[i]))
i++;
Чтобы избежать повторного включения информации о поставщике в массив list_Supplier, объект класса Recorder посылает
сообщение Equals объекту temp класса Supplier. Если объект
с заданным именем и номером поставщика отсутствует в массиве,
объект temp добавляется в массив.
181
Рис. 8.7. Диаграмма объекта класса Recorder
Аналогичная ситуация имеет место и при добавлении товаров:
перед включением записи о товаре в массив list_Goods объект
класса Recorder посылает сообщение Equals объекту класса
Goods.
Как только поставщики и товары добавлены, можно начинать регистрацию поставщиков. Для этого используется метод Recording,
входные данные которого – номер поставщика и номер товара. Метод
Recording
182
1) проверяет наличие заданного поставщика Supplier_id в
массиве list_Supplier:
int i=0;
while((i<cur_sp)&&(Supplier_id
!=list_Supplier[i].S_id))
i++;
в этом случае объект класса Recorder посылает сообщения каждому объекту массива list_Supplier для определения номера поставщика;
2) проверяет наличие заданного товара
list_Goods:
Goods_id в массиве
int j=0;
while ((j < cur_gs) &&
Goods_id != list_Goods[j].g_id))
j++;
здесь объект класса Recorder посылает сообщения каждому объекту массива list_Goods для определения номера товара;
3) вызывает метод AddRecord объекта gsr_GSRecords,
т. е. для регистрации поставщика объект класса Recorder посылает сообщение AddRecord объекту gsr_GSRecords:
gsr_GSRecords.AddRecord(Supplier_id , Goods_id).
Кроме того, при печати списка поставщиков и списка товаров
объект класса Recorder посылает сообщения объектам класса Supplier и Goods для получения имени и номера поставщика, а также
имени и номера товара (листинг 8.5).
Листинг 8.5. Описание класса Recorder.
class Recorder {
Goods[]list_Goods ; // ссылка на массив товаров
Supplier[]list_Supplier; // ссылка на массив
//поставщиков
GSRecords gsr_GSRecords; //ссылка на объект
//класса GSRecords
int cur_sp; //текущее количество поставщиков
183
int cur_gd; //текущее количество товаров
int max_sp; //максимал. количество поставщиков
int max_gd; //максимальное количество товаров
// Описание свойств
public Goods [] List_Goods
{ get { return list_Goods;} }
public Supplier[] List_Supplier
{ get { return list_Supplier;} }
public GSRecords Gsr_GSRecords
{ get { return gsr_GSRecords;} }
//конструктор
public Recorder
(int max_gd, int max_sp, int max_records) {
list_Goods = new Goods[max_gd];
list_Supplier = new Supplier[max_sp];
cur_sp = 0; this.max_sp = max_sp;
cur_gd = 0; this.max_gd = max_gd;
gsr_GSRecords =
new GSRecords(max_records);
}
// добавляем поставщика в массив поставщиков
public bool AddSupplier
(string name, int num_sp) {
Supplier temp = new Supplier(name, num_sp);
//проверяем, есть ли такой поставщик
int i = 0;
while ((i < cur_sp) &&
!temp.Equals(list_Supplier[i]))
i++;
if ((i!= cur_sp) /* нашли */ ||
(cur_sp==max_sp) /*регистрации завершены*/)
return false;
else { list_Supplier[cur_sp++] = temp;
return true;
}
}
//вывод списка поставщиков
public void St_Display(){
for (int i = 0; i < cur_sp; i++)
184
Console.WriteLine(
list_Supplier[i].S_name + "
list_Supplier[i].S_id);
" +
}
// добавляем товар в массив товаров
public bool AddGoods(string name, int num_gd){
Goods temp = new Goods(name, num_gd);
//проверяем, есть ли такой товар
int i = 0;
while ((i < cur_gd) &&
!temp.Equals(list_Goods[i]))
i++;
if ((i != cur_gd) /* нашли */ ||
/* слишком много товаров*/
(cur_gd == max_gd))
return false;
else {
list_Goods[cur_gd++] = temp;
return true;
}
}
// вывод списка товаров
public void Cs_Display(){
Console.WriteLine("Список товаров: ");
for (int i = 0; i < cur_gd; i++)
Console.WriteLine(
"Goods:
" + list_Goods[i].G_name +
" " + list_Goods[i].G_id);
}
public bool Recording
(int SupplierID, int GoodsID) {
//регистрация поставщика
int i=0, j=0;
//есть ли такой поставщик
while ((i < cur_sp)&&
(SupplierID!=list_Supplier[i].S_id))
i++;
if (i == cur_sp) //нет такого поставщика
return false;
else {//есть ли такой товар
185
while ((j < cur_gd) &&
(GoodsID != list_Goods[j].G_id))
j++;
if (j == cur_gd) //нет такого товара
return false;
else
// нашли поставщика и товар
// в соответствующих массивах
// регистрируем поставщика товара
return
(gsr_GSRecords.AddRecord
(SupplierID, GoodsID));
}
}
} //класс Recorder
Таким образом, в процессе использования объекта класса
Recorder реализуется взаимодействие объектов классов Supplier, Goods, GSLink и GSRecords.
Теперь в основной функции реализуем действия по регистрации
поставщиков.
class Program {
static void Main(){
Recorder my = new Recorder(3, 3, 5);
// Добавляем данные в массив поставщиков
my.AddSupplier("Иванов", 5);
my.AddSupplier("Петров", 6);
my.AddSupplier("Сидоров", 51);
my.AddSupplier("Ковалев", 61);
// Добавляем данные в массив товаров
my.AddGoods("Яблоки", 50);
my.AddGoods("Груши", 60);
my.AddGoods("Ананасы", 70);
my.AddGoods("Бананы", 80);
//Регистрируем поставщиков (поставщик/товар)
my.Recording(5, 50); my.Recording(6, 60);
my.Recording(3, 30); my.Recording(51, 60);
my.Recording(5, 50);
Console.WriteLine
("
Регистрация поставщиков: ");
186
my.Gsr_GSRecords.Display();
}
}
Результат работы программы:
Регистрация поставщиков:
Поставщик
Товар
Количество
5
50
0
6
60
0
51
60
0
Важно наглядно представить передачу сообщений между различными объектами. Для этой цели используются диаграммы взаимодействия и диаграммы последовательностей [2]. На диаграмме взаимодействия изображаются объекты и их основные связи (рис. 8.8). Диаграмма же последовательностей описывает временной порядок передачи сообщений (рис. 8.9).
Рис. 8.8. Диаграмма взаимодействия
для задачи «Поставщики/Товары»
Построение диаграммы последовательностей начинается с размещения по горизонтальной оси объектов, участвующих во взаимодействии. Обычно объекты, инициализирующие взаимодействие,
располагаются слева.
Вдоль вертикальной оси в хронологическом порядке размещаются сообщения (сверху вниз). Линия жизни объекта – вертикальная
пунктирная линия – показывает продолжительность существования
объекта. Большинство объектов существуют в течение всего периода
взаимодействия, поэтому их линии жизни выровнены по верхней
границе диаграммы и имеют одинаковую длину. Сообщения на диаграмме изображаются стрелками, направленными от одной линии
187
жизни к другой. Сплошная стрелка указывает на объект, который
принимает сообщение, а пунктирная – на объект, которому возвращается результат.
Рис. 8.9. Диаграмма последовательностей
для задачи «Поставщики/Товары»
Здесь представлено частичное решение задачи «Поставщики/
Товары», которое отвечает лишь за регистрацию поставщиков.
В дальнейшем это решение следует дополнить реализацией следующих действий:
оформить поставку заданного товара;
определить, какой товар поставляется в наибольшем количестве;
перечислить имена поставщиков, которые могут поставить товар
с указанным названием, и др.
Коротко о главном
1. Один объект взаимодействует с другим объектом посредством
сообщений. Сообщение – это вызов метода.
2. Для организации взаимодействия объектов методы одного объекта должны вызывать методы других объектов.
3. Если объект Object2 класса ClassB посылает сообщение
объекту Object1 класса ClassА, то это означает, что объект Object2 вызывает метод объекта Object1.
188
4. Два варианта ответа на вопрос: как обратиться к методу класса
ClassA в методе класса ClassB?
Вариант 1. Cоздать объект класса ClassA в качестве поля класса ClassB; после этого из любого метода класса ClassB можно
обращаться к методам класса ClassA.
Вариант 2. Cоздать объект класса ClassA в качестве локального объекта некоторого метода класса ClassB; в дальнейшем в этом
методе можно обращаться к методам класса ClassA.
5. Для наглядного представления передачи сообщений между
различными объектами используются диаграммы взаимодействия и
диаграммы последовательностей.
6. На диаграмме взаимодействия изображаются объекты и их основные связи.
7. Диаграмма последовательностей описывает последовательность (временной порядок) передачи сообщений.
Задания
Задание 8.1. Поставщики/Товары.
Дополнить решение задачи следующими действиями:

оформить поставку заданного товара;

выяснить, какой товар не поставляется ни одним поставщиком;

определить какой товар поставляется в наибольшем количестве;

определить, кто из поставщиков поставляет товар с заданным номером и в каком количестве;

каков объем поставок заданного товара у различных поставщиков;

перечислить имена поставщиков, которые могут поставить товар с
указанным названием;

сформировать список товаров, упорядоченный по объему поставок.
Задание 8.2. Библиотека.
В библиотеке хранится информация о читателях (фамилия, номер билета, адрес) и книгах (автор, название и номер). Если читатель берет
книгу, то сохраняются данные о выдаче (номер билета, номер книги,
дата выдачи и дата возврата).
Реализовать следующие действия:
1. Создать список читателей. Этот список может содержать читателей, которые пока не взяли ни одной книги.
189
2. Создать список книг. В этот список могут входить невостребованные книги.
3. Сформировать список, содержащий информацию о выдачах
книг. Перед добавлением новой записи о выдаче нужно проверить
наличие читателя и книги в соответствующих списках.
4. Добавить/удалить читателя из списка.
5. Добавить/удалить книгу из списка.
6. Добавить/удалить информацию об очередной выдаче.
7. Получить информацию обо всех книгах, выданных читателям.
8. Выяснить:

кто из читателей не вернул книги в назначенный срок;

кто взял книги в текущем месяце;

у кого из читателей книги Герберта Шилдта.
9. Изобразить диаграмму взаимодействия.
10. Изобразить диаграмму последовательностей.
Задание 8.3. Курсы по выбору.
Каждый студент должен прослушать и сдать несколько курсов, перечисленных в учебном плане.
Реализовать следующие действия:
1. Создать список студентов. Этот список может содержать
студентов, которые пока не записались ни на один курс.
2. Создать список курсов. В этот список могут входить курсы,
которые пока никто не выбрал.
3. Сформировать список, содержащий информацию о записях
студентов на курсы. Перед добавлением новой записи нужно проверить наличие студента и курса в соответствующих списках.
4. Реализовать операции для работы со списками студентов и
курсов.
5. Выставить оценку студенту по курсу.
6. Определить, сколько студентов не сдали курс.
7. Какое количество студентов успешно освоили курс.
8. На какой курс записалось наибольшее количество студентов?
9. Изобразить диаграмму взаимодействия.
10. Изобразить диаграмму последовательностей.
190
Powered by TCPDF (www.tcpdf.org)
Список литературы
1. Шилдт, Герберт. С# 4.0. Полное руководство / Герберт
Шилдт. – М. : Вильямс, 2015.
2. Буч, Гради. Объектно-ориентированный анализ и проектирование с примерами приложений / Гради Буч [и др.]. – М. : Вильямс,
2010.
3. Буч, Гради. Язык UML. Руководство пользователя / Гради Буч
[и др.]. – М. : Вильямс, 2007.
4. Сборник задач по программированию. Ч. 1 / сост. А. П. Шестаков. – Пермь, 2001.
5. Юркин, А. Г. Задачник по программированию / А. Г. Юркин. –
СПб. : Питер, 2002.
6. Синтес, Антони. Объектно-ориентированное программирование / Антони Синтес. – М. : Вильямс, 2002.
191
Любовь Алексеевна ЗАЛОГОВА
ОСНОВЫ ОБЪЕКТНООРИЕНТИРОВАННОГО
ПРОГРАММИРОВАНИЯ НА БАЗЕ ЯЗЫКА С#
Учебное пособие
Издание второе, стереотипное
Зав. редакцией литературы
по информационным технологиям
и системам связи О. Е. Гайнутдинова
ЛР № 065466 от 21.10.97
Гигиенический сертификат 78.01.10.953.П.1028
от 14.04.2016 г., выдан ЦГСЭН в СПб
Издательство «ЛАНЬ»
lan@lanbook.ru; www.lanbook.com
196105, Санкт Петербург, пр. Юрия Гагарина, д. 1, лит. А
Тел./факс: (812) 336 25 09, 412 92 72
Бесплатный звонок по России: 8 800 700 40 71
ГДЕ КУПИТЬ
ДЛЯ ОРГАНИЗАЦИЙ:
Для того, чтобы заказать необходимые Вам книги, достаточно обратиться
в любую из торговых компаний Издательского Дома «ЛАНЬ»:
по России и зарубежью
«ЛАНЬ ТРЕЙД». 196105, Санкт Петербург, пр. Ю. Гагарина, д. 1, лит. А.
тел.: (812) 412 85 78, 412 14 45, 412 85 82; тел./факс: (812) 412 54 93
e mail: trade@lanbook.ru; ICQ: 446 869 967
www.lanbook.com
пункт меню «Где купить»
раздел «Прайслисты, каталоги»
в Москве и в Московской области
«ЛАНЬ ПРЕСС». 109387, Москва, ул. Летняя, д. 6
тел.: (499) 722 72 30, (495) 647 40 77; e mail: lanpress@lanbook.ru
в Краснодаре и в Краснодарском крае
«ЛАНЬ ЮГ». 350901, Краснодар, ул. Жлобы, д. 1/1
тел.: (861) 274 10 35; e mail: lankrd98@mail.ru
ДЛЯ РОЗНИЧНЫХ ПОКУПАТЕЛЕЙ:
интернет-магазин
Издательство «Лань»: http://www.lanbook.com
магазин электронных книг
Global F5: http://globalf5.com/
Подписано в печать 01.10.19.
Бумага офсетная. Гарнитура Школьная. Формат 84×108 1/32.
Печать офсетная. Усл. п. л. 10,29. Тираж 100 экз.
Заказ № 694 19.
Отпечатано в полном соответствии
с качеством предоставленного оригинал макета
в АО «Т8 Издательские Технологии».
109316, г. Москва, Волгоградский пр., д. 42, к. 5.
Powered by TCPDF (www.tcpdf.org)
Download