Поляков Python C++ 04
Поляков Python C++ 04
Поляков
ПРОГРАММИРОВАНИЕ
К. Ю. Поляков. Программирование. Python. C++. Часть 1
К. Ю. Поляков. Программирование. Python. C++. Часть 2
К. Ю. Поляков. Программирование. Python. C++. Часть 3
К. Ю. Поляков. Программирование. Python. C++. Часть 4
12+
К. Ю. Поляков
ПРОГРАММИРОВАНИЕ
Python
C++
Часть 4
Учебное пособие
для общеобразовательных
организаций
Москва
БИНОМ. Лаборатория знаний
*4* 2019
УДК 004.9
ББК 32.97
П54
Поляков К. Ю.
П54 Программирование. Python. C++. Часть 4 : учебное
пособие / К. Ю. Поляков. — М. : БИНОМ. Лаборатория
знаний, 2019. — 192 с. : ил.
ISBN 978-5-9963-4137-5
Книга представляет собой завершающую, четвёртую часть серии
учебных пособий по программированию. В отличие от большинства
аналогичных изданий, в ней представлены два языка программирова
ния высокого уровня — Python и C++.
Главные темы пособия — объектно-ориентированное программиро
вание и создание программ с графическим интерфейсом. Изучаются ос
новные принципы объектного подхода к созданию программ: абстрак
ция, инкапсуляция, наследование, полиморфизм. Изложение ведётся
на примерах программирования игр, в которых моделируются системы
взаимодействующих объектов.
Для демонстрации возможностей сред быстрой разработки про
грамм в последней части пособия рассмотрены примеры приложений
на языке С#.
После каждого параграфа приводится большое число заданий для
самостоятельного выполнения разной сложности и вариантов проек
тных работ.
Пособие предназначено для учащихся средних школ.
УДК 004.9
ББК 32.97
Учебное издание
Поляков Константин Юрьевич
ПРОГРАММИРОВАНИЕ
Python. C++
Часть 4
Учебное пособие
Ведущий редактор О. А. Полежаева
Концепция внешнего оформления В. А. Андрианов
Художественный редактор Н, А. Новак
Технический редактор Е. В. Денюкова
Корректор Е. Н. Клишина
Компьютерная верстка: В. А. Носенко
(12+)
Подписано в печать 06.09.2018. Формат 84x108/16. Усл. печ. л. 20,16
Тираж 3 000 экз. Заказ № м7023.
ООО «БИНОМ. Лаборатория знаний»
127473, Москва, ул. Краснопролетарская, д. 16, стр. 3,
тел. (495)181-53-44, e-mail: [email protected]
http://Lbz.ru, http://metodist.Lbz.ru
Отпечатано в филиале «Смоленский полиграфический комбинат»
ОАО «Издательство «Высшая школа». 214020, Смоленск, ул. Смольянинова, 1
Тел.: +7 (4812) 31-11-96. Факс: +7 (4812) 31-31-70
E-mail: [email protected] http://www.smolpk.ru
3
Предисловие
4
Глава 1
ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ PYTHON
§1
Что такое ООП?
Ключевые слова:
• декомпозиция • процедурное программирование
• объект • объектно-ориентированное
• абстракция программирование
Процедурное программирование
Сначала возникла идея разделить программу на небольшие самостоя
тельные части (подпрограммы, процедуры), каждая из которых решает
отдельную часть общей задачи. Такой подход получил название проце
дурного программирования.
Поскольку каждая подпрограмма (процедура, функция) решает са
мостоятельную задачу, можно поручить разработку и отладку подпро
грамм разным программистам и таким образом разделить работу меж
5
Программирование на языке Python
Объектный подход
Следующая замечательная идея появилась в конце 60-х годов XX века —
применить в разработке программ тот подход, который использует
человек в повседневной жизни.
Как отмечал ещё в XVII веке французский математик и философ
Рене Декарт, люди воспринимают мир как множество объектов: пред
метов, животных, людей. Все объекты имеют внутреннее устройство
и состояние, свойства (внешние характеристики) и поведение. Чтобы
справиться со сложностью окружающего мира, люди часто игнорируют
внутреннее строение объектов, обращая внимание только на те свой
ства, которые необходимы для решения их практических задач. Такой
приём называется абстракцией.
6
Что такое ООП? §1
Взаимодействие объектов
Каждый объект «закрыт» в том смысле, что его внутреннее устройство
другим объектам неизвестно. Но объекты могут обмениваться данными
с другими объектами, используя заранее согласованные каналы связи
(рис. 1.1, а).
Рис. 1.1
Для решения задачи «на верхнем уровне» достаточно определить,
что делает тот или иной объект, не заботясь о том, как именно он это
делает. Таким образом, для преодоления сложности мы используем аб
стракцию, т. е. сознательно отбрасываем второстепенные детали.
Если всё-таки «заглянуть внутрь» такого объекта, часто можно обна
ружить, что он, в свою очередь, тоже состоит из объектов (рис. 1.1, б).
Но важно, что объект сам работает со своими внутренними данными,
не допуская к ним другие объекты. Таким образом, данные и методы
их обработки объединены — вместе они составляют объект.
7
1 Программирование на языке Python
Выводы
• Программисты столкнулись с проблемой сложности программ,
которая превысила возможности человеческого разума.
• Процедурное программирование (декомпозиция по алгоритмам) —
это пример использования принципа «разделяй и властвуй» для
борьбы со сложностью программ.
• Абстракция — это выделение характеристик объекта, существенных
в данной задаче и отличающих этот объект от других объектов.
• Объект — это данные плюс методы их обработки.
• Объектный подход — это декомпозиция задачи по объектам.
• Программирование, основанное на моделировании задачи реально
го мира как множества взаимодействующих объектов, называют
объектно-ориентированным программированием (ООП).
Вопросы и задания
1. Почему со временем неизбежно изменяются методы программирова
ния?
2. Что такое декомпозиция, зачем она применяется?
3. Какие виды декомпозиции в программировании вы знаете?
4. Что такое процедурное программирование? Какой вид декомпози
ции в нём используется?
5. Какие проблемы в программировании привели к появлению ООП?
6. Что такое абстракция? Зачем она используется в обычной жизни?
7. Объясните, почему один и тот же объект может иметь много раз
ных моделей. Какую роль здесь играет абстракция?
8. Какой вид декомпозиции используется в ООП?
9. Какие преимущества даёт объектный подход в программировании?
10. Что такое интерфейс? Приведите примеры объектов, у которых оди
наковый интерфейс и разное устройство.
§2
Модель задачи: классы и объекты
Ключевые слова:
• объектно-ориентированный анализ • свойство
• объект • метод
• класс
8
Модель задачи: классы и объекты §2
Объектно-ориентированный анализ
Для того чтобы построить объектно-ориентированную модель, нужно:
• выделить взаимодействующие объекты, с помощью которых мож
но описать поведение моделируемой системы;
• определить их свойства, существенные в данной задаче;
• описать поведение (возможные действия) объектов, т. е. команды,
которые объекты могут выполнить.
Этап разработки модели, на котором решаются перечисленные зада
чи, называется объектно-ориентированным анализом (00А). Он вы
полняется до того, как программисты напишут самую первую строку
кода, и во многом определяет качество и надёжность будущей про
граммы.
«Торпедная атака»
Выполним объектно-ориентированный анализ простой игры «Торпед
ная атака». На горизонте проходят вражеские корабли, которые нужно
атаковать с помощью торпед (и потопить) — рис. 1.2.
Рис. 1.2
Игрок, управляющий торпедным аппаратом, может:
• поворачивать торпедный аппарат влево и вправо, изменяя точку
прицеливания;
• давать команду на пуск торпеды (нажимая клавишу «пробел»).
Объекты и классы
Как построить объектную модель этой игры? Прежде всего, нужно
разобраться, что такое объект.
9
Программирование на языке Python
10
Модель задачи: классы и объекты §2
а) Корабль б) Торпеда
координаты координаты
скорость скорость
изображение курс
двигаться изображение
взорваться двигаться
Торпедный аппарат
координаты
угол поворота
повернуться
выстрелить
Рис. 1.3
Класс Торпеда — это описание общих свойств и методов торпед
(рис. 1.3, б). Основные свойства торпеды — это координаты, скорость,
курс и изображение, а метод один — двигаться в заданном направлении.
Класс Торпедный аппарат (рис. 1.3, в) содержит два свойства —
координаты и угол поворота, и два метода — повернуться и выстре
лить.
Взаимодействие объектов
Пока мы только описали модели отдельных объектов (точнее, классов).
Теперь нужно разобраться, как эти объекты взаимодействуют между
собой и с игроком.
Игрок может выполнять два действия:
• менять угол поворота торпедного аппарата (точку прицеливания);
• выполнять пуск торпеды (применять метод выстрелить торпедно
го аппарата).
По команде выстрелить из торпедного аппарата выходит торпеда,
причём направление движения торпеды совпадает с текущим направле
нием торпедного аппарата.
Когда торпеда достигнет линии движения кораблей, нужно будет
проверить, попала ли она в зону изображения какого-нибудь корабля.
Если попала, подбитый корабль взрывается и тонет, если не попала,
она просто исчезает с экрана.
Взаимодействие игрока с объектами в игре и объектов между собой
показано на рис. 1.4.
Конечно, здесь показан простейший вариант связей. Если совершен
ствовать программу, нужно учесть, например, что корабль с помощью
своих акустических средств способен заранее обнаружить торпеду и
применить манёвр по уходу от неё. Вместе с тем, торпеда может иметь
блок самонаведения и, обнаружив корабль, преследовать его.
11
1 Программирование на языке Python
поворот
выстрел
Рис. 1.4
Таким образом, мы построили объектную модель игры: выделили
классы объектов, определили их свойства и методы. Обратите внима
ние, что мы пока ничего не говорили о том, как устроены объекты и
как именно они будут действовать.
Теперь можно поручить разработку этих классов разным программи
стам. Согласно принципам ООП, ни один объект не должен зависеть
от внутреннего устройства и алгоритмов работы других объектов. По
этому каждый из программистов может решать свою задачу независи
мо от других. Важно только, чтобы все они точно соблюдали интер
фейсы — правила, описывающие взаимодействие «своих» объектов с
остальными.
Выводы
• Объектно-ориентированный анализ состоит в том, чтобы выделить
взаимодействующие объекты и описать их свойства и поведение.
• Объектом можно назвать то, что имеет чёткие границы и обладает
состоянием и поведением. Объект объединяет данные и методы их
обработки.
• Любой объект относится к какому-то классу. Класс — это описа
ние множества объектов, имеющих общий набор свойств и общее
поведение.
• Метод класса — это процедура или функция, принадлежащая
классу объектов.
• Ни один объект не должен зависеть от внутреннего устройства и
алгоритмов работы других объектов.
• Интерфейсы — это правила, описывающие взаимодействие объек
тов в программе.
Вопросы и задания
1. Какие задачи решает объектно-ориентированный анализ?
2. Чем различаются понятия «класс» и «объект»?
3. Что такое метод? Чем различаются понятия «метод» и «функция»?
4. Как изображаются классы на диаграмме?
5. Почему при объектно-ориентированном анализе не уточняют, как
именно объекты будут устроены и как они будут решать свои
задачи?
12
Классы и объекты в программе §3
§3
Классы и объекты в программе
Ключевые слова'.
• класс • экземпляр
• объект • конструктор
• свойство • параметры
• метод • переменные класса
• атрибут класса • анимация
Объявление класса
Программа, использующая объектно-ориентированный подход, начина
ется с описания классов объектов.
Класс — это новый тип данных, который вводит программист.
Как и структура (вспомните материал третьей части пособия), класс
может объединять данные различных типов в единый блок. Кроме
того, класс содержит ещё и описание методов работы со своими
данными. Свойства описывают состояние объектов данного класса,
а методы — их поведение. В программе, о которой мы говорили в
предыдущем параграфе, есть класс Корабль (англ. ship). Его можно
объявить так:
class TShip:
pass
Эти строки вводят новый тип данных (класс) TShip1’, т. е. сообща
ют компилятору, что в программе, возможно, будут использоваться
объекты этого класса. При этом в памяти пока не создаётся ни одно
го объекта класса TShip. Это описание — как чертёж, по которому в
нужный момент можно построить сколько угодно таких объектов.
13
1 Программирование на языке Python
Поля класса
Класс TShip, который мы только что объявили, пока не содержит ни
свойств, ни методов.
Свойства корабля — это его координаты, скорость и изображение.
Так как корабль движется горизонтально, его г/-координата не изме
няется. Поэтому мы будем работать только с х-координатой.
Свойства хранятся в переменных, принадлежащих объекту. Такие
переменные называются полями.
14
Классы и объекты в программе §3
Конструктор
Вернёмся к записи
ship = TShip()
Внешне это выглядит как вызов функции без параметров — после
имени TShip стоят пустые скобки. И действительно, здесь вызывается
функция, которая создаёт объект. Она называется конструктором.
15
1 Программирование на языке Python
Конструктор с параметрами
Начальные значения полей можно задавать прямо при создании объек
та. Для этого необходимо изменить конструктор, добавив дополнитель
ные параметры — начальные значения свойств:
class TShip:
def _init__ ( self, xO, vO, fileName ):
self.x = xO if xO >= 0 else 0
self.v = vO
self.image = image( self.x, 150, fileName )
В конструкторе можно проверять правильность переданных парамет
ров. Например, здесь гарантируется, что х-координата не окажется
отрицательной. Теперь создавать объект будет проще:
ship = TShip( 30, 3, "ship.gif" )
Координата этого корабля х = 30, его скорость равна трём условным
единицам, а изображение загружается из файла ship.gif в текущем
каталоге.
Мы передали конструктору только три аргумента, а не четыре, как
указано в заголовке метода _ init__ . Первый аргумент self — ссыл
ку на только что созданный объект — компилятор добавит автомати
чески.
16
Классы и объекты в программе §3
Данные класса
В конструкторе класса TShip есть одно «магическое» число — 150.
Можно выполнить рефакторинг — сделать это число константой.
Будем считать, что все корабли имеют одну и ту же 1/-координату,
поэтому эта константа должна принадлежать не конкретному экзем
пляру, а всем кораблям. Вместе с тем, делать её глобальной тоже не
хочется: любая функция сможет её изменить, и из-за этого могут воз
никнуть ошибки во время работы программы.
Выход есть — нужно указать, что эта константа (назовём её SHIP_Y)
принадлежит всему классу, а не отдельному объекту. В языке Python
нет констант, и мы вынуждены ввести её как переменную. Обычно пе
ременные, имена которых состоят только из прописных букв, считают
ся константами — все договариваются, что изменять их не будут.
Переменная, принадлежащая классу, называется переменной класса.
Её нужно определить внутри описания класса на том же уровне, что и
конструктор:
class TShip:
SHIP_Y = 150
def _ init__ ( self, x, v, fileName ):
self.x = x if x >= 0 else 0
self.v = v
self.image = image( self.x, TShip.SHIP_Y , fileName )
При обращении к переменной класса записывают название класса и
через точку — имя переменной: TShip. SHIP_Y.
Методы
Теперь научимся добавлять в класс методы. Самый простой метод
класса Корабль — метод двигаться (англ. move). Изображение корабля
нужно просто переместить по оси х вправо на величину, равную ско
рости:
class TShip:
SHIP_Y = 150
def _init__ ( self, xO, vO, fileName ):
17
1 Программирование на языке Python
Анимация
Напомним, как программируется анимация в Python. Программа
запускает таймер, который срабатывает с заданным периодом, т. е. че
рез равные интервалы времени. Срабатывание таймера — это событие,
на которое программа должна отреагировать, — изменить изображе
ние на экране. Для этого в программе нужно установить обработчик
события, т. е. определить процедуру, которая будет вызываться при
каждом срабатывании таймера.
Сначала выберем нужное количество кадров в секунду и определим
соответствующий период обновления картинки:
fps = 20
updatePeriod = round( 1000 / fps )
Количество кадров в секунду записано в глобальную переменную fps (от
англ, frames per second — кадры в секунду), а период обновления — в
переменную updatePeriod (от англ, update — обновить). Чтобы вычис
лить период обновления в миллисекундах, мы делим величину ин
тервала 1 с = 1000 мс на количество кадров в секунду и округляем
результат с помощью функции round.
Будем считать, что изображение корабля хранится в файле ship.gif,
который сохранён в том же каталоге, что и наша программа. Создадим
объект-корабль:
ship = TShip( 30, 3, "ship.gif" )
и построим новую функцию, которая должна вызываться по таймеру:
def update():
ship.move()
Остаётся установить таймер на нужное время, настроить его на
вызов нужной функции и запустить анимацию:
onTimer( update, updatePeriod )
run ()
Если вы сделали всё правильно, изображение корабля будет двигать
ся слева направо.
18
Классы и объекты в программе §3
Строим флотилию
Попробуем создать флотилию кораблей. Сначала в основной программе
определяем константу — количество кораблей:
NUMBER_OF_SHIPS = 3
Затем строим массив кораблей ships, которые начинают движение с
разных точек с разными скоростями:
ships = []
for i in range(NUMBER_OF_SHIPS) :
ships.append( TShip(100*i, 3+i, "ship.gif") )
Остаётся изменить функцию update так, чтобы она вызывала метод
move для всех кораблей из массива ships:
def update ():
for ship in ships:
ship.move()
Нам не потребовалось работать с массивами, где хранятся отдельные
свойства объектов. Каждый объект класса TShip при создании (так
сказать, «от рождения») «знает», как он устроен и что ему нужно
делать по команде move, поэтому никаких сложностей не возникает.
Можно ли было написать такую же программу, не используя объ
екты? Конечно, да. И она, возможно, получилась бы короче, чем наш
объектный вариант (с учётом описания классов). В чём же преиму
щества ООП? Дело в том, что ООП — это средство разработки боль
ших программ, моделирующих работу сложных систем. В этом случае
очень важно, что:
• задачи реального мира проще всего описываются именно с помо
щью понятий «объект», «свойства», «действия» (методы);
• основная программа, описывающая решение задачи в целом, полу
чается простой и понятной; все команды (вызовы методов) напо
минают действия в реальном мире;
• легко разделить работу между программистами в команде: каж
дый программирует свой класс и может работать независимо от
других;
• если объект Корабль понадобится в других проектах, можно будет
легко использовать уже готовый класс TShip со всеми его свойст
вами и методами.
Выводы
• В отличие от структуры класс содержит не только данные, но и
методы работы с этими данными.
• Поле — это переменная, принадлежащая объекту. Метод — это
процедура или функция, принадлежащая объекту.
19
1 Программирование на языке Python
Вопросы и задания
1. Чем различаются понятия «структура» и «класс»?
2. Как можно присвоить полям объекта начальные значения? Сравни
те разные способы.
3. Почему лучше создавать все поля объекта в конструкторе класса?
4. Может ли быть у одного класса несколько конструкторов? Обоснуй
те ответ и проверьте его экспериментально.
5. Проверьте, можно ли обращаться к приведённой в параграфе пере
менной класса SHIP Y не через класс, а через объект: ship.SHIP_Y.
6. Проект. Закончите программу, рассмотренную в тексте параграфа.
Добавьте в окно программы фоновый рисунок, сделайте так, чтобы
корабли двигались справа налево. Что, по-вашему, нужно делать,
когда корабль выходит за левый край холста?
7. Проект. Добавьте в программу новый класс Торпедный аппарат.
Изображение прицела должно перемещаться при нажатии на кла
виши-стрелки (обработчик события можно установить с помощью
функции опКеу из модуля graph).
*8. Проект. Добавьте в программу класс Торпеда. Торпеда должна вы
ходить из торпедного аппарата при нажатии на пробел. Когда тор
педа дойдёт до линии, по которой двигаются корабли, нужно про
верить, не попала ли она в какой-нибудь корабль. Если попала,
корабль должен взорваться, если не попала, торпеда пропадает с
экрана.
Указания'.
• используйте массивы (списки) для хранения объектов-кораблей
и объектов-торпед;
• в процедуре update для каждой пары «корабль — торпеда»
проверяйте, не попала ли торпеда в цель;
20
Скрытие внутреннего устройства §4
Интересный сайт
younglinux.info/oopython.php — Python. Введение в объектно-ориен
тированное программирование
§4
Скрытие внутреннего устройства
Ключевые слова:
интерфейс свойство
• инкапсуляция
Методы
Рис. 1.5
Такой подход позволяет:
• обезопасить внутренние данные (поля) объекта от изменений (воз
можно, разрушительных) со стороны других объектов;
• проверять правильность данных, поступающих от других объек
тов, тем самым повышая надёжность программы;
• переделывать внутреннюю структуру и код объекта любым спосо
бом, не меняя интерфейса; при этом никакой переделки других
объектов не потребуется.
Всё это становится действительно важно, когда разрабатывается боль
шая программа и необходимо обеспечить её надёжность.
21
1 Программирование на языке Python
Скрытие полей
Разберём простой пример. Во многих системах программирования есть
класс, описывающий свойства «пера», которое используется при рисо
вании линий в графическом режиме. Назовём этот класс ТРеп, в прос
тейшем варианте он будет содержать только одно поле color, которое
определяет цвет.
Будем хранить информацию о цвете в виде символьной строки, в
которой записан шестнадцатеричный код составляющих цвета в моде
ли RGB. Например, "00FF7F" — это светло-зелёный цвет, его красная
(R) составляющая равна 0, зелёная (G) — FF16 = 255 и синяя (В) —
7F16 = 127.
Класс Перо можно объявить так:
class ТРеп:
def _ init__ ( self ) :
self.color = "000000"
По умолчанию в Python все атрибуты класса (поля и методы)
открытые, общедоступные (англ, public). Это значит, что к полю color
может обратиться (и изменить его!) любая функция и объект любого
класса. Например, некоторая функция может случайно записать в это
поле ошибочное значение "Abra cadabra", которое потом приведёт к
ошибке в самый неожиданный момент.
Для повышения надёжности программы лучше сделать все поля объ
ектов закрытыми, чтобы их не смогли изменить другие объекты про
граммы. Имена тех атрибутов, которые нужно скрыть, в языке Python
должны начинаться с двух знаков подчёркивания, например так:
class ТРеп:
def _ init__ ( self ) :
self.__ color = "000000"
В этом примере поле _ color — закрытое, или частное (как част
ная собственность, англ, private). Теперь никакой другой объект или
подпрограмма не сможет его случайно изменить. К закрытым полям
нельзя обратиться извне (это могут делать только методы самого объ
екта), поэтому теперь (обычными средствами) невозможно не только
изменить поле объекта, но и просто узнать его значение.
Итак, все поля и методы, названия которых начинаются с двух зна
ков подчёркивания, — закрытые. Вы наверняка заметили, что конструк
тор — метод _ init__ — тоже начинается с двух знаков подчёркива
ния. Это особый метод: он никогда не вызывается по имени, транслятор
выполняет его автоматически при создании каждого нового объекта.
22
Скрытие внутреннего устройства §4
Конечно, такая простая проверка сделана только для того, чтобы не усложнять
пример. В реальной программе нужно ещё проверить, что все 6 символов —
шестнадцатеричные цифры.
23
Программирование на языке Python
Свойства (property)
Чтобы упростить запись при обращении к полям через методы, во
многие языки программирования ввели понятие свойства (англ, pro
perty). Внешне обращение к свойству выглядит так же, как обраще
ние к полю объекта. Но на самом деле при записи и чтении значения
свойства вызываются методы объекта, определённые программистом.
24
Скрытие внутреннего устройства §4
class ТРеп:
def _init__ ( self ):
self.__ color = "000000"
def _ setColor ( self, newColor ):
if len ( newColor ) == 6:
self.__ color = newColor
else: self.__ color = "000000"
color = property( lambda x: x.__ color , __ setColor )
25
1 Программирование на языке Python
class TPen:
def __init__ ( self ):
self.__ color = 0
def _ getColor ( self ) :
return "{:06X}". format( self.__ color )
def _ setColor( self, newColor ):
if len( newColor ) == 6:
self.__ color = int( newColor, 16 )
else: self.__ color = 0
color = property ( __ getColor, __ setColor )
Для перевода числового кода в символьную запись используется
функция format. Формат 06Х означает «вывести значение в шестнад
цатеричной системе (X) в 6 позициях, свободные позиции слева за
полнить нулями». Для обратного преобразования применяем функцию
int, которой передаётся второй аргумент 16 — основание системы
счисления.
В этом примере мы принципиально изменили внутреннее устройство
объекта — вместо строкового поля для хранения цвета теперь исполь
зуется целочисленное. Однако другие объекты даже не «догадаются» о
такой замене, потому что сохранился интерфейс — свойство color по-
прежнему имеет строковый тип. Таким образом, инкапсуляция позво
ляет как угодно изменять внутреннее устройство объектов, не затраги
вая интерфейс. При этом все остальные объекты изменять не требуется.
Скрытие данных чаще всего увеличивает длину программы, однако
мы получаем и важные преимущества. Код, связанный с объектом, раз
делён на две части: общедоступную часть (интерфейс) и закрытую. Их
можно сравнить с надводной и подводной частями айсберга (рис. 1.6).
Рис. 1.6
26
Скрытие внутреннего устройства §4
27
1 Программирование на языке Python
Выводы
• Обмен данными между объектами выполняется с помощью
общедоступных свойств и методов, которые составляют интерфейс
класса.
• Скрытие внутреннего устройства объектов называется инкапсуля
цией. Инкапсуляцией также называют объединение в одном объ
екте данных и методов работы с ними.
• Изменение внутреннего устройства объектов (реализации) не вли
яет на взаимодействие с другими объектами, если не меняется
интерфейс.
• Имена закрытых (частных) полей и методов в Python должны
начинаться с двух знаков подчёркивания.
• Читать и изменять значения закрытых полей класса можно толь
ко с помощью методов этого класса.
• Свойство — это способ доступа к внутреннему состоянию объек
та, имитирующий обращение к его полю. При создании свойства
нужно указать метод чтения и метод записи.
• Свойство, предназначенное только для чтения, не имеет метода
записи. Другие объекты не могут изменить значение этого свойства.
Вопросы и задания
1. Что такое интерфейс объекта?
2. Что такое инкапсуляция? Оцените её достоинства и недостатки.
3. Как различить общедоступные и закрытые данные и методы в опи
сании классов?
4. Почему рекомендуют делать доступ к полям объекта только с помо
щью методов?
5. При каких ошибочных значениях параметра не сработает наша
защита от неправильных данных в методе setcolor (из парагра
фа)? Исправьте метод так, чтобы исключить эту ошибку.
6. Что такое свойство? Зачем во многие языки программирования вве
дено это понятие?
7. Почему методы чтения и записи, которые использует свойство,
обычно делают закрытыми?
8. Зачем нужны свойства «только для чтения»? Приведите свой пример.
9. Сравните два варианта вычисления площади прямоугольника в
классе TRect (из параграфа): с помощью метода-функции и с помо
щью свойства. Какой из них вам больше нравится?
10. Подумайте, в каких ситуациях может быть нужно свойство «только
для записи» (которое нельзя прочитать). Предположите, как ввести
такое свойство в описание класса. Проверьте вашу догадку экспери
ментально.
11. Проект. Измените программу игры «Торпедная атака» (из §2) так,
чтобы все поля у объектов были закрытыми. Используйте свойства
для доступа к данным.
28
Иерархия классов §5
§5
Иерархия классов
Ключевые слова'.
• классификация • абстрактный класс
• базовый класс • абстрактный метод
• производный класс • полиморфизм
• класс-наследник • исключение
• иерархия
Наследование
Как в науке, так и в быту важную роль играет классификация —
разделение объектов на группы (классы), объединённые общими при
знаками. Значительно проще не полностью описывать каждый объект
«с нуля», а сравнивать его с уже известными объектами.
Например, есть много видов фруктов1) (яблоки, груши, бананы,
апельсины и т. д.), но все они обладают некоторыми общими свой
ствами. Если перевести этот пример на язык ООП, у нас есть класс
Фрукт. У этого класса есть производные классы {классы-наследни
ки, классы-потомки, подклассы) — Яблоко, Груша, Банан, Апельсин
и др. В свою очередь, для этих классов класс Фрукт — это базовый
класс, суперкласс, класс-предок (рис. 1.7).
классы-наследники
Рис. 1.7
Стрелка с белым наконечником на рис. 1.7 указывает на базовый
класс. Например, класс Яблоко — это наследник класса Фрукт, а
класс Фрукт — базовый класс для класса Яблоко. Это означает, что
все объекты класса Яблоко обладают всеми свойствами и методами
класса Фрукт.
Классический пример научной классификации — классификация
животных или растений. Она представляет собой иерархию — много
уровневую структуру. Например, лев — это вид рода пантер семей
ства кошачьих отряда хищных и т. д. Говоря на языке ООП, класс
Лев — это наследник класса Пантеры, а тот, в свою очередь, наслед
ник класса Кошачьи, который является наследником класса Хищные
ит. д. Часть этой классификации показана на рис. 1.8.
29
Программирование на языке Python
Рис. 1.8
30
Иерархия классов §5
Рис. 1.9
Итак, для того чтобы не описывать несколько раз одинаковые свой
ства и методы, классы в программе должны быть построены в виде
иерархии. Теперь можно дать классическое определение объектно-
ориентированного программирования.
Базовый класс
Начнём с описания класса Игровой объект (TGameObject). Он дол
жен объединить общие свойства и методы всех объектов в игре. Свой
ства — это координаты базовой точки (х и у), ширина (англ, width) и
высота (англ, height). Можно начать с такого варианта:
class TGameObject:
def __init ( self, x, у, width, height ):
self.__ x = x
self.__ у = у
self.__ width = width
self.__ height = height
31
1 Программирование на языке Python
@property
def х( self ): return self.__ x
@property
def y( self ): return self.__ у
@property
def width( self ): return self.__ width
@property
def height ( self ): return self.__ height
Все четыре поля (_ x, __ у, __ width и _ height) — закрытые,
они заполняются при создании объекта. Для чтения их значений мы
добавили четыре свойства «только для чтения», а изменять значения
полей могут только сами объекты класса TGameObject.
Заметим, что не существует игрового объекта «вообще», так же
как не существует «просто фрукта», не относящегося к какому-то
виду. Поэтому понятие «игровой объект» — это просто набор каких-
то свойств и методов, т. е. абстракция. А класс, описывающий та
кую абстракцию, называется абстрактным. Создавать объекты такого
класса бессмысленно.
TGameObject
х
У
width
height
update
Рис. 1.10
32
Иерархия классов §5
33
1 Программирование на языке Python
Доступ к ПОЛЯМ
В приведённом варианте класса TGameObject все поля напрямую недо
ступны, возможно только чтение их значений через свойства. Однако в
дальнейшем такая ситуация будет нам мешать. Например, движущим
ся объектам нужно изменять свои координаты. Если поля закрыты и
нет методов для работы с ними, сделать это невозможно.
Для правильной работы классов-наследников можно обеспечить
им доступ к полям базового класса. Но желательно как-то отме
тить, что никто другой, кроме наследников, не должен изменять
значения этих полей. Для этого в начале имени поля ставят одно
подчёркивание. По соглашению, принятому в сообществе Python-
программистов, это говорит о том, что переменную не следует изме
нять, несмотря на то что она общедоступна. То же самое относится
и к методам, названия которых начинаются с одного подчёркива
ния.
Вот окончательный вариант базового класса:
34
Иерархия классов §5
class TGameObject:
def _ init__ ( self, x, y, width, height ):
self._x = x
self._y = у
self._width = width
self._height = height
if not hasattr( self, "update" ):
raise NotlmplementedError(
"Нельзя создать такой объект!" )
@property
def x( self ): return self._x
@property
def у( self ): return self._y
@property
def width( self ): return self._width
@property
def height( self ): return self._height
Во многих объектно-ориентированных языках программирования
(C++, С#, Java, Delphi) кроме общедоступных (англ, public) и за
крытых (англ, private) элементов класса есть ещё защищённые (англ.
protected). Доступ к ним имеет сам класс, в котором объявлены эти
данные и методы, и наследники этого класса. К сожалению, в языке
Python защищённые поля и методы сделать невозможно.
Выводы
• Объектно-ориентированное программирование — это такой подход
к программированию, при котором программа представляет со
бой множество взаимодействующих объектов, каждый из которых
является экземпляром определённого класса, а классы образуют
иерархию наследования.
• Классы-наследники обладают всеми свойствами и методами базово
го класса. Они могут изменять методы базового класса или добав
лять к ним собственные свойства и методы.
• Абстрактный метод — это метод класса, реализация которого в
классе не приводится.
• Абстрактный класс — это класс, который не предназначен для
создания объектов (экземпляров). Любой класс, в котором есть аб
страктный метод, является абстрактным.
• Полиморфизм — это возможность классов-наследников по-разному
реализовать метод базового класса.
• Исключение — это аварийная ситуация, которая возникает в слу
чае ошибки при выполнении программы. Программа может сама
создать (сгенерировать, «выбросить») исключение.
• Поля и методы, названия которых начинаются с одного знака
подчёркивания, предназначены для изменения только в данном
классе и его классах-наследниках.
35
Программирование на языке Python
Вопросы и задания
1. Приведите свои примеры отношений «наследование» и «часть —
целое».
2. Как вы думаете, стоит ли в рассмотренной в параграфе игре вво
дить ещё класс Неподвижный объект — наследник класса Игровой
объект?
3. Используя дополнительные источники, составьте свою классифика
цию каких-то объектов (например, фотокамер, веб-сайтов, печатных
изданий и т. п.). Используйте 2-3 уровня наследования.
4. Постройте классификацию, в которую войдут: молоко, масло, говя
дина, свинина, огурцы, помидоры, овсяная каша, хлеб.
5. Найдите ошибки в проектировании системы классов.
36
Классы-наследники (I) §6
§6
Классы-наследники (I)
Ключевые слова:
• базовый класс • переопределение метода
• наследник • полиморфизм
• конструктор
Классы-наследники
Теперь будем строить классы-наследники. Начнём с простого клас
са Чёрная дыра (TBlackHole). Как следует из диаграммы классов на
рис. 1.9, класс TBlackHole должен быть прямым наследником базово
го класса TGameObject. При объявлении класса-наследника после его
имени в скобках записывается название базового класса:
class TBlackHole( TGameObject ):
37
1 Программирование на языке Python
NUMBER_OF_BLACKHOLES =10
blackHoles = []
for i in range(NUMBER_OF_BLACKHOLES):
blackHoles.append( TBlackHole(
randint(0, SCREEN_WIDTH),
randint(0, SCREEN_HEIGHT),
randint(10, 20) ) )
def update():
for bh in blackHoles:
bh.update();
run ()
В программе создаётся массив blackHoles из объектов клас
са TBlackHole, их количество определяется константой NUMBER_OF_
BLACKHOLES.
38
Классы-наследники (I) §6
Пульсар
Пульсар — это объект, который напоминает чёрную дыру, но его раз
меры постоянно изменяются. Следовательно, можно сказать, что пуль
сар — это разновидность объекта Чёрная дыра, или, говоря на языке
программистов, подкласс (производный класс, класс-наследник) класса
TBlackHole.
В отличие от чёрных дыр пульсары будем закрашивать коричневым
цветом. Получается такой класс:
class TPulsar( TBlackHole ):
def _ init__ ( self, xCenter, yCenter, radius ):
TBlackHole.__ init__ ( self, xCenter, yCenter, radius )
changeFillColor( self._image, "brown" )
В заголовке указываем, что TPulsar — это наследник клас
са TBlackHole, т. е. он обладает всеми свойствами базового класса
TBlackHole. Заметим, что, кроме того, он является и наследником
класса TGameObject.
Конструктор нового класса принимает те же параметры, что и кон
структор базового класса — координаты центра и начальный радиус
круга. После вызова конструктора базового класса мы меняем цвет за
ливки на коричневый с помощью функции changeFillColor из моду
ля graph.
Пока новый класс не отличается от базового ничем, кроме цвета
заливки. А должен отличаться тем, что радиус пульсара меняется слу
чайным образом на каждом шаге анимации. Для этого нужно добавить
в класс TPulsar собственный метод update.
У класса TBlackHole, который служит базовым классом для
TPulsar, тоже есть метод update, поэтому мы выполняем переопреде
ление метода (англ, override) — пишем новую версию метода с таким
же именем для класса-наследника.
39
1 Программирование на языке Python
Полиморфизм
Интересно, что мы можем записывать чёрные дыры и пульсары в один
и тот же массив, и всё равно программа будет работать правильно:
40
Классы-наследники (I) §6
NUMBER_OF_BLACKHOLES =10
allObjects = []
for i in range(NUMBER_OF_BLACKHOLES):
allObjects.append( TBlackHole(
randint ( 0, SCREEN_WIDTH ),
randint( 0, SCREEN_HEIGHT ),
randint(10, 20) ) )
NUMBER_OF_PULSARS =15
for i in range(NUMBER_OF_PULSARS) :
allObjects.append( TPulsar(
randint( 0, SCREEN_WIDTH ),
randint( 0, SCREEN_HEIGHT ),
randint( 10, 20) ) )
Теперь массив allObjects содержит как чёрные дыры, так и пуль
сары. Причём каждый элемент массива «знает» свой тип. Это легко
проверить с помощью такого цикла:
for obj in allObjects:
print( type(obj) )
Для первых 10 элементов мы увидим сообщение
<class '__ main__ .TBlackHole'>
а для остальных:
<class '__ main__ .TPulsar’>
И поэтому в процедуре update пульсары и можно обрабатывать
вместе, в одном цикле:
def update () :
for obj in allObjects:
obj.update()
Для каждого объекта, попавшего в переменную obj, программа во
время выполнения определит тип и вызовет метод update того класса,
к которому этот объект принадлежит. Эта особенность — проявление
полиморфизма.
Выводы
• При объявлении класса-наследника после имени нового класса в
скобках указывают имя базового класса.
• В конструкторе класса-наследника сначала нужно вызвать кон
структор базового класса.
• Чтобы класс не был абстрактным, все абстрактные методы базово
го класса должны быть определены (реализованы).
• Переопределение метода — это создание в классе-наследнике новой
версии метода, определённого ранее для базового класса.
41
1 Программирование на языке Python
Вопросы и задания
1. Проект. Постройте полную программу (из параграфа), которая
выводит на экран чёрные дыры и пульсары и выполняет анимацию.
2. Проект. Напишите вариант программы из предыдущего задания,
в котором классы TBlackHole и Т Puls аг не связаны в иерархию,
а независимы. Сравните её с вариантом, который рассматривался в
параграфе. Какой подход вам больше нравится и почему?
3. Проект. Добавьте в программу из задания 1 ещё один класс непо
движных объектов, которые изображаются квадратом или ромбом.
4. Проект. Добавьте в программу из задания 1 ещё один класс непо
движных объектов, которые при анимации меняют цвет случайным
образом.
§7
Классы-наследники (II)
Ключевые слова'.
• базовый класс абстрактный класс
• класс-наследник суперкласс
• защищённое поле переменные модуля
Подвижные объекты
Класс Подвижный объект из нашей игры отличается от базового клас
са Игровой объект тем, что у него есть два дополнительных свой
ства — скорость и курс (направление движения). Будем хранить их
в защищённых полях _v и _course (по-английски — курс). Курсом
будем называть угол а между вектором скорости объекта и осью ОХ,
измеренный в радианах (рис. 1.11).
Рис.
42
Классы-наследники (II) §7
X (*1, J/1)
v ■ cos а • Д/
Рис. 1.12
За время At объект проходит расстояние v • At, так что из точки
(х, у) перемещается в точку (хг, yj, где:
хг = х + v • cosa • At,
ух = у - v • sina • At.
Из-за того что ось OY на экране направлена вниз, во второй формуле
появился знак «минус».
Для использования математических функций в начале программы
нужно подключить модуль math:
import math
Удобно принять за единицу времени интервал между двумя последо
вательными вызовами процедуры update (она вызывается по таймеру),
тогда At = 1. Будем считать, что в поле _course хранится значение
курса — угла а — в радианах. Тогда метод move можно написать так:
class TMovingObject( TGameObject ):
43
1 Программирование на языке Python
dy = -self._v*math.sin( self._course )
self._x += dx
self._y += dy
moveObjectBy( self._image, dx, dy )
Сначала вычисляются смещения по осям х и у, они записываются
в локальные переменные dx и dy. В следующих строках изменяются
поля объекта _х и _у и вызывается функция moveObjectBy из модуля
graph, которая перемещает круг на холсте.
Обратите внимание, что создать объект класса TMovingObject нель
зя — у него, как и у базового класса, нет метода update. Поэтому
TMovingObject — это тоже абстрактный класс, он объединяет все
свойства подвижных объектов и служит только для создания классов-
наследников.
Космические корабли
Космический корабль — это подвижный объект, т. е. наследник клас
са TMovingObject:
class TSpaceship( TMovingObject ):
44
Классы-наследники (II) §7
45
1 Программирование на языке Python
Модуль с классами
Большие программы обычно разбивают на модули, каждый модуль
хранится как отдельный файл. В модуль объединяются функции, свя
занные между собой. Такой подход используется как в классическом
программировании, так в ООП.
В нашей программе в отдельный модуль gameobjects (в файл
gameobj ects. ру) можно вынести описания всех классов:
46
Классы-наследники (II) §7
class TGameObject:
47
1 Программирование на языке Python
Выводы
• В методах класса-наследника можно вызывать открытые методы
базового класса.
• Чтобы в методе класса-наследника обратиться к объекту базового
класса, используют метод super.
• В больших программах описания классов выделяют в модули,
которые хранятся как отдельные файлы.
• Глобальные переменные в языке Python — это переменные моду
ля, а не всей программы.
Вопросы и задания
1. Проект. Соберите в одном файле программу из параграфа, ко
торая выводит на экран объекты четырёх типов и выполняет их
анимацию.
2. Проект. Измените программу из параграфа так, чтобы космиче
ский корабль, который попал в чёрную дыру или в пульсар, унич
тожался. Вместо него должен появиться новый корабль в случай
ном месте.
3. Переделайте программу из параграфа так, чтобы описание всех
классов находилось в отдельном модуле.
* 4. Проект. Измените программу из параграфа так, чтобы чёрная
дыра постепенно уменьшалась «без пищи», т. е. тогда, когда в неё
долгое время не попадают корабли.
* 5. Проект. Добавьте в игру из параграфа объекты класса
TDestroyer — специальные боевые космические корабли, кото
рые охотятся за кораблями и странниками и уничтожают их при
встрече.
* *6. Проект. Измените поведение объектов класса TDestroyer из пре
дыдущего задания так, чтобы они двигались к ближайшему объек
ту типа TSpaceship или TRanger и уничтожали его при встрече.
* *7.Проект. Придумайте и запрограммируйте собственную игру, где
будут неподвижные и движущиеся объекты. Один объект должен
управляться игроком с клавиатуры.
§8
Событийно-ориентированное программирование
Ключевые слова'.
• сообщение • обработчик события
• событие • форма
• очередь сообщений • виджет
• цикл обработкисообщений • приложение
48
Событийно-ориентированное программирование §8
Сообщение содержит:
• адрес объекта, которому посылается сообщение;
• числовой код сообщения;
• параметры (дополнительные данные), например координаты щелч
ка мышью или код нажатой клавиши.
Сообщение может быть широковещательным, в этом случае вместо
адресата указывается специальный код, и сообщение поступает всем объ
ектам определённого типа (например, всем главным окнам программ).
В классических программах (рис. 1.13), последовательность действий
заранее определена — основная программа выполняется строка за
строкой, вызывая процедуры и функции; все ветвления выполняются с
помощью условных операторов.
49
Программирование на языке Python
Системная очередь
сообщений
Очередь Операционная
программы система
Основная
программа
Рис. 1.14
Основная программа выбирает очередное сообщение из очереди и вы
зывает специальную процедуру — обработчик этого сообщения (если
он есть). Когда пользователь закрывает окно программы, ей посылает
Событийно-ориентированное программирование §8
51
1 Программирование на языке Python
Простейшая программа
Простейшая программа с графическим интерфейсом состоит из трёх
строк:
from simpletk import * # (1)
арр = TApplication( "Первая форма" ) # (2)
app.runO # (3)
Вероятно, вам понятна первая строка: из модуля simpletk
импортируются все функции (для того, чтобы при их использовании
не нужно было каждый раз указывать название модуля). В строке 2
создаётся объект класса TApplication — это приложение (программа,
от англ, application — приложение). Конструктору передаётся заголо
вок главного окна программы.
В последней строке с помощью метода run (по-английски — запуск)
запускается цикл обработки сообщений, скрытый внутри этого метода.
Таким образом, здесь тоже использован принцип инкапсуляции (скры
тия внутреннего устройства).
Приведённую выше программу можно запустить, и вы увидите окно,
показанное на рис. 1.16.
, Первая форма
Рис. 1.16
Свойства формы
Класс TApplication — это класс-наследник класса Тк библиоте
ки t kin ter. У него есть методы, позволяющие управлять свойствами
окна. Например, можно изменить начальное положение окна на экране
с помощью свойства position:
арр.position = (100, 300)
Позиция окна — это кортеж, состоящий из х-координаты и
1/-координаты левого верхнего угла окна на экране. Обе координаты
задаются в пикселях.
Модуль simpletk можно скачать на сайте автора http://kpolyakov.spb.ru/school/
probook/python.htm.
52
Событийно-ориентированное программирование §8
Обработчик события
Рассмотрим простой пример. Многие программы запрашивают под
тверждение, когда пользователь завершает их работу. Это делается для
того, чтобы по ошибке не потерять нужные данные.
Когда пользователь закрывает окно, происходит событие «запрос на
закрытие окна». При обработке этого события нужно спросить пользо
вателя, не ошибся ли он, т. е. выдать сообщение в диалоговом окне
(рис. 1.17) и обработать ответ.
/ Подтверждение
ОК Отмена
Рис- 1.17
53
1 Программирование на языке Python
Выводы
• Обмен данными между современными программами происходит с
помощью сообщений.
• Сообщение — это блок данных определённой структуры, который
используется для обмена информацией между объектами.
• Событие — это переход какого-либо объекта из одного состояния
в другое.
• Современные программы с графическим интерфейсом основаны на
обработке событий, вызванных действиями пользователя и посту
плением данных из других источников. События могут происхо
дить в любой последовательности.
• С любым событием можно связать процедуру — обработчик собы
тия, — которая будет вызываться при наступлении этого события.
• Окна в программе с графическим интерфейсом называют формами
или окнами верхнего уровня.
• Элементы графического интерфейса (поля ввода, списки, кнопки и
др.) называются виджетами или компонентами.
• Для построения программ с графическим интерфейсом в Python
можно использовать библиотеку tkinter.
Вопросы и задания
1. Что такое графический интерфейс?
2. Как графический интерфейс связан с объектно-ориентированным
подходом к программированию?
3. Что такое сообщение? Какие данные в него входят?
4. Что такое широковещательное сообщение?
54
Использование компонентов (виджетов) §9
Интересные сайты
kpolyakov.spb.ru/school/probook/python.htm — модуль simp let к для
создания приложений с графическим интерфейсом на Python
wxpython.org — библиотека wxPython
pygtk.org — библиотека PyGTK
riverbankcomputing.com/software/pyqt/intro — библиотека PyQt
§9
Использование компонентов (виджетов)
Ключевые слова:
» виджет • обработчик события
•. компонент • исключение
родительский объект
Программа с компонентами
В предыдущем параграфе мы научились работать с главным окном
программы (окном верхнего уровня). Для того чтобы пользователь мог
получать информацию от программы и управлять её работой (вводить
55
Программирование на языке Python
Панель
Рисунок TPanel
Т Image
Рис. 1.18
56
Использование компонентов (виджетов) §9
57
1 Программирование на языке Python
Его «родителем» будет главное окно арр, параметр bg (от англ, back
ground — фон) задаёт цвет холста (англ, white — белый). Затем «упа
ковываем» этот объект в окно так, чтобы он заполнял всю оставшуюся
область по вертикали и горизонтали:
image.align = "client"
Теперь построим обработчики событий. Нас интересуют два события:
• щелчок по кнопке (после него должен появиться диалог выбора
файла);
• переключение флажка (при этом нужно изменить режим показа
рисунка).
Сначала определим, что нужно сделать в случае щелчка мышью по
кнопке. Псевдокод выглядит так:
выбрать файл с рисунком
if файл выбран:
загрузить рисунок в компонент image
Для выбора файла используем стандартный диалог операцион
ной системы. Его вызывает функция askopenfilename из модуля
filedialog библиотеки tkinter. Этот модуль нужно импортировать в
начале программы:
from tkinter import filedialog
Теперь вызываем функцию askopenfilename и передаём ей расши
рения, которые будут показаны в выпадающем списке Тип файла'.
fname = filedialog.askopenfilename (
filetypes = [("Файлы GIF", "*.gif"),
("Все файлы", "*.*")] )
Именованный аргумент filetypes — это список, составленный из
кортежей. Каждый кортеж содержит два элемента: текстовое описание
и маску для выбора имён файлов.
После вызова этой функции в переменную fname будет записан
результат её работы — имя выбранного файла или пустая строка, если
пользователь отказался от выбора (нажал на кнопку Отмена). Если
файл всё-таки выбран (строка fname не пустая), записываем имя фай
ла в свойство picture объекта TImage:
if fname:
image.picture = fname
При изменении этого свойства файл будет автоматически загружен
и выведен на экран, эту операцию выполняет класс TImage. Обрати
те внимание, что пустая строка в Python воспринимается как ложное
значение. Поэтому если пользователь не выбрал файл, условие в опе
раторе if будет ложно и рисунок не изменится.
58
Использование компонентов (виджетов) §9
59
1 Программирование на языке Python
арр = TlmageViewer()
арр.run()
Вместо многоточия нужно добавить описание класса: конструктор,
который создаёт и размещает на форме все компоненты, и обработчики
событий.
Начнём с конструктора:
class TlmageViewer ( TApplication ):
def _init__ (self):
TApplication.__ init__ ( self, "Просмотр рисунков" )
self.position = (200, 200)
self.size = (300, 300)
self.panel = TPanel(self, relief = "raised",
height = 35, bd = 1)
self.panel.align = "top"
self.image = TImage( self, bg = "white" )
self.image.align = "client"
self.openBtn = TButton( self.panel,
width = 15, text = "Открыть файл" )
self.openBtn.position = (5, 5)
self.openBtn.onClick = self.selectFile
self.centerCb = TCheckBox( self.panel,
text = "В центре" )
self.centerCb.position = (115, 5)
self.centerCb.onChange = self.cbChanged
Сначала мы вызываем конструктор базового класса TApplication и
настраиваем свойства окна. Вместо имени объекта арр в предыдущей
программе используем self — ссылку на текущий объект.
60
Использование компонентов (виджетов) §9
61
Программирование на языке Python
62
Использование компонентов (виджетов) §9
63
1 Программирование на языке Python
Обработка ошибок
Если в предыдущей программе пользователь введёт в поле ввода не
цифры, а другие символы или пустую строку, программа выдаст сооб
щение о необработанной ошибке и аварийно завершит работу. Хорошая
программа никогда не должна завершаться аварийно. Поэтому нужно
обрабатывать все ошибки, которые можно заранее предусмотреть.
В современных языках программирования для обработки ошибок
применяют исключения. Исключение — это исключительная (ошибоч
ная, аварийная, непредвиденная) ситуация, из-за которой программа
не может нормально продолжать работу.
Все «опасные» участки кода, при выполнении которых может воз
никнуть ошибка, нужно поместить в блок try-except:
64
Использование компонентов (виджетов) §9
try:
# "опасные" команды
except:
# обработка ошибки
Слово try по-английски означает «попытаться», except — «исклю
чать». Программа попадает в блок except только тогда, когда при
выполнении команд между try и except произошла ошибка.
В нашей программе «опасные» команды — это преобразование дан
ных из текста в числа (вызовы функции int). В случае ошибки мы вы
ведем вместо кода цвета знак вопроса, а цвет фона метки-прямоуголь
ника rgbRect изменим на стандартное значение "SystemButtonFace",
которое означает «системный цвет кнопки». При этом прямоугольник
станет невидимым, потому что сольётся с фоновым цветом окна.
Улучшенный обработчик с защитой от неправильного ввода прини
мает вид:
def onChange( sender ):
try:
# получить данные из полей ввода
s = "#{:02Х}{:02Х}{:02Х}".format(г, д, Ь)
bkColor = s
except:
s = "?"
bkColor = "SystemButtonFace"
rgbLabel.text = s
rgbRect.background = bkColor
Здесь переменная s обозначает шестнадцатеричный код цвета (в виде
символьной строки), а переменная bkColor — цвет прямоугольника.
Если при попытке получить новый код цвета происходит ошибка, то
выполняются две команды в блоке except: в переменные s и bkColor
записываются значения, сообщающие о неудаче.
В модели RGB все составляющие цвета должны быть не меньше 0 и не
больше 255, т. е. должны находиться в диапазоне range (256). Поэтому в
обработчик полезно добавить ёщё проверку допустимости введённых чисел:
def onChange( sender ):
s = "?"
bkColor = "SystemButtonFace"
try:
r =int( rEdit.text )
g =int( gEdit.text )
b =int( bEdit.text )
if r in range(256) and \
g in range(256) and b in range(256):
s = "#{:02x}{:02x}{:02x}".format(r, g, b)
bkColor = s
65
1 Программирование на языке Python
except:
pass
rgbLabel.text = s
rgbRect.background = bkColor
Здесь в самом начале процедуры-обработчика мы записываем значе
ния по умолчанию в переменные s и bkColor. В блоке except поме
щён «пустой» оператор pass, т. е. ошибки игнорируются. Если поль
зователь ввёл ошибочные данные (не числа или числа вне отрезка
[0; 255]), программа не выполнит команды в теле оператора if, и
значения переменных s и bkColor не изменятся.
Выводы
• Для организации диалога с пользователем на форме размещают
объекты, называемые виджетами или компонентами.
• Любой компонент может реагировать на некоторый набор собы
тий. С каждым событием, которое нужно обрабатывать, необходи
мо связать обработчик события.
• Обработчик события — это процедура, которая получает управле
ние каждый раз, когда происходит данное событие.
• Один обработчик события может быть связан с несколькими ком
понентами. Ссылка на конкретный компонент, который вызвал со
бытие, передаётся в обработчик события как первый параметр.
• Для выбора файлов на диске можно использовать стандартные
диалоги из модуля filedialog библиотеки tkinter.
• Всё приложение с графическим интерфейсом можно оформить как
один класс. Компоненты добавляются на форму в конструкторе, а
обработчики становятся методами нового класса.
• Для обработки ошибок используют механизм исключений. Ко
манды, которые могут вызвать ошибку, заключаются в отдель
ный блок try. В следующем блоке, который начинается словом
except, записывают команды, выполняемые в случае ошибки.
Вопросы и задания
1. Что такое компоненты? Зачем они нужны?
2. Объясните, как связаны компоненты и идея инкапсуляции.
3. Что такое родительский и дочерний объекты?
4. Какую роль играет свойство align в размещении элементов на
форме?
5. Что такое стандартный диалог? Как его использовать?
6. Назовите основное свойство флажка (TCheckBox). Как его использо
вать?
7. Чем отличается метка от элемента TEdit?
8. Как обрабатываются ошибки в современных программах? В чём, на
ваш взгляд, преимущества и недостатки такого подхода?
66
Создание компонентов §10
§10
Создание компонентов
Ключевые слова:
• компонент поле
• наследование метод
• базовый класс обработчик события
67
1 Программирование на языке Python
68
Создание компонентов §10
Последняя часть этого описания говорит о том, что для чтения вве
дённого числового значения используется лямбда-функция, возвраща
ющая значение скрытого поля _ value. Метод записи _ setvalue
сохраняет переданное число в поле _ value, а затем записывает в
свойство text в виде символьной строки.
Для того чтобы заблокировать ввод нецифровых символов, использу
ем обработчик события onValidate (от англ, validate — проверять на
правильность) компонента TEdit. Установим этот обработчик на скры
тый метод нового класса TIntEdit, который назовём _ validate. Об
работчик должен возвращать логическое значение: True, если символ
допустимый, и False, если нет (тогда ввод этого символа блокирует
ся). В следующем коде оставлены только те строки, который относятся
к установке нового обработчика:
class TIntEdit( TEdit ):
def _ init__ ( self, parent, **kw ):
1001| ЗЕ9
69
Программирование на языке Python
decEdit.onChange = onNumChange
Этот обработчик читает значение свойства value объекта, вызвавше
го событие, и выводит его на метку hexLabel в шестнадцатеричной
системе (формат X). В строке формата можно написать и строчную
букву х, тогда в записи шестнадцатеричного числа будут выводиться
строчные буквы a-f вместо прописных A—F.
Выключатель с рисунком
Построим ещё один новый компонент — выключатель TSwitch,
который имеет два положения: «включено» и «выключено». Он дол
жен менять своё состояние по щелчку мышью (рис. 1.21 и см. цвет
ной рисунок на обороте обложки).
9 Выключатель FZ7]|- а .||«еД 9 Выключатель Г^~11 а |ЬвД
С\ выкл
Рис. 1.21
В библиотеке graph есть компонент TLabel (метка), который может
стать базовым классом для такого выключателя. Перечислим отличия
нового компонента-выключателя от стандартной метки:
70
Создание компонентов §10
71
1 Программирование на языке Python
switchimages = [ PhotoImage("off.gif") ,
PhotoImage("on.gif") ] # (1)
switch = TSwitch( app, switchimages ) # (2)
switch.position = ( 5, 5 ) # (3)
switch.state = True # (4)
app.run()
После создания главного окна приложения загружаем рисунки в
массив switchimages (строка 1). Файлы off.gif и on.gif в текущем
72
Создание компонентов §10
73
1 Программирование на языке Python
switch.onChange = showSwitchState
Наш тестовый обработчик выводит на экран информацию о каждом
изменении состояния выключателя.
Составной компонент
Построим ещё один новый компонент — TEditPanel, который объеди
няет два компонента — метку TLabel и поле ввода TEdit. Окно для
входа пользователя в закрытую часть сайта или базы данных, исполь
зующее два таких составных компонента, показано на рис. 1.22.
Пароль
Войти।
Рис. 1.22
74
Создание компонентов §10
75
Программирование на языке Python
Выводы
• Новые компоненты обычно строятся на основе существующих с
использованием наследования.
• Цель создания новых компонентов — добавить или изменить свой
ства и методы существующих компонентов или связать несколько
компонентов в один составной компонент.
• Составные компоненты могут объединяться в новый компонент с
помощью панели TPanel.
• Составные компоненты могут передавать запросы своим частям,
такой приём называется делегированием.
Вопросы и задания
1. В каких случаях имеет смысл разрабатывать свои компоненты?
Сравните такой подход с использованием готовых компонентов.
2. Почему программисты почти никогда не создают свои компоненты
«с нуля»?
3. Объясните, как связаны рассмотренные в параграфе классы компо
нентов TIntEdit и TEdit с точки зрения объектно-ориентированно
го программирования. Чем они отличаются друг от друга?
4. Какие функции используются в языке Python для преобразования
числового значения в текстовое и обратно?
5. Как получить запись числа в шестнадцатеричной системе счисле
ния?
6. Объясните, как работает свойство value у компонента TIntEdit в
программе из параграфа.
7. Почему мы не обрабатывали возможные ошибки в обработчике
onChange класса TIntEdit в программе из параграфа?
8. Как вы думаете, как можно установить обработчик события во вре
мя выполнения программы?
9. Почему можно использовать в программе из параграфа обработчик
события onChange, который не был объявлен в классе TIntEdit?
10. Проект. Разработайте компонент, который позволяет вводить шест
надцатеричные числа.
11. Проект. Разработайте компонент, который позволяет вводить веще
ственные числа (они относятся к типу float).
76
§11
§11
Модель и представление
Ключевые слова'.
• модель • представление
Рис. 1.23
78
Модель и представление §11
79
Программирование на языке Python
Представление
Теперь построим интерфейс программы. В верхней части окна будет
размещён выпадающий список (компонент ТСошЬоВох), в котором поль
зователь вводит выражение (рис. 1.25).
0 Калькулятор | " а || .» |
5*5-13/2
25*4+190/13=114
13+6/7-4=9
12+15-6*3=9
Рис. 1.25
При нажатии клавиши Enter выражение вычисляется, и его
результат выводится в верхней строке обычного списка — компонент
TListBox.
Выпадающий список, в котором хранятся все вводимые выражения,
полезен для того, чтобы можно было вернуться к ранее введённому
варианту и исправить его.
Итак, импортируем из модуля model функцию calc и создаём при
ложение:
Модель и представление §11
from model inport calc
app = TApplication( "Калькулятор" )
app.size = ( 200, 150 )
На форме разместим компонент TComboBox. Чтобы прижать его к
верху, установим свойство align, равное "top". Назовём этот компо-
нент1) inp (от англ, input — ввод).
inp = TComboBox( app, values = [], height = 1 )
inp.align = "top"
inp.text = "2+2"
При вызове конструктора, кроме родительского объекта мы указа
ли два именованных аргумента: values (в переводе с английского —
значения) — пустой список, и height — высоту компонента, равную
одной текстовой строке.
Добавляем второй компонент — TListBox, устанавливаем для него
выравнивание "client" (заполнить всю свободную область) и имя
answers (в переводе с английского — ответы).
answers = TListBox ( app )
answers.align = "client"
Логика работы программы может быть записана в виде псевдокода:
if нажата клавиша Enter:
вычислить выражение
добавить результат вычислений в начало списка
if выражения нет в выпадающем списке:
добавить его в выпадающий список
Для перехвата нажатия клавиши Enter установим обработчик со
бытия "<Key-Return>" (по-английски — клавиша перевода каретки).
Он подключается стандартным способом библиотеки tkinter — через
метод bind (по-английски — связать):
inp.bind ( "<Key-Return>", doCalc )
Обработчик doCalc должен принимать один параметр — структуру,
которая содержит информацию о произошедшем событии:
def doCalc ( event ):
81
1 Программирование на языке Python
Выводы
• В современных программах принято разделять модель (данные и
алгоритмы их обработки) и представление (способ ввода исходных
данных и вывода результатов).
• Одну и ту же модель можно использовать с разными представле
ниями (в разных проектах).
• Разделение на модель и представление позволяет разрабатывать и
тестировать эти части отдельно друг от друга.
82
Модель и представление §11
Вопросы и задания
1. Что даёт разделение программы на модель и представление? Как
это связано с особенностями современного программирования?
2. Что обычно относят к модели, а что — к представлению?
3. Что от чего зависит (и не зависит) в паре «модель — представле
ние»?
4. Приведите свои примеры задач, в которых можно выделить мо
дель и представление. Покажите, что для одной модели можно
придумать много разных представлений.
5. Пусть требуется изменить программу так, чтобы она обрабатыва
ла выражения со скобками. Что нужно изменить: модель, интер
фейс или и то, и другое?
6. Проект. Измените программу из параграфа так, чтобы модель
осталась та же, а представление было другое.
7. Проект. Измените программу из параграфа так, чтобы она
вычисляла значения выражений с вещественными числами (для
перевода вещественных чисел из символьного вида в числовой
используйте функцию float).
*8. Проект. Добавьте в программу из параграфа обработку ошибок.
Подумайте, какие ошибки может сделать пользователь. Какие
ошибки могут возникнуть при вычислениях? Как их обработать?
*9. Проект. Измените программу из параграфа так, чтобы она
вычисляла значения выражений со скобками. Подсказка: нужно
искать последнюю операцию с самым низким приоритетом, стоя
щую вне скобок.
*10.Проект. Постройте программу «Калькулятор» для выполнения
вычислений с целыми числами:
83
Глава 2
ПРОГРАММИРОВАНИЕ НА ЯЗЫКАХ C++ И C#
§12
Классы и объекты в C++
Ключевые слова’.
• класс • конструктор
• объект • инициализация
• экземпляр • метод
• модификатор доступа • рефакторинг
Рис. 2.1
Карта должна уметь перерисовывать себя, поэтому нужно добавить в
этот класс метод show (в переводе с английского — показать).
Классы и объекты в C++ §12
Рис. 2.2
Свойства карты width и height — это её ширина и высота, а
cells — матрица, в которой хранится информация о содержании кле
ток карты.
Класс СМар
Названия всех классов будем начинать с прописной буквы «С» (от
англ, class — класс). Класс Карта назовём в программе СМар (от англ.
тар — карта).
Выделим место под матрицу с запасом, считая, что в нашей игре не
встретятся карты размером больше, чем 50 на 50 клеток (вы можете
использовать свои максимальные значения). Объявляем две целочис
ленные константы — максимальные размеры карты, MAX WIDTH (мак
симальная ширина), и MAX HEIGHT (максимальная высота):
const int MAX_WIDTH = 50,
MAX_HEIGHT = 50;
Простейший вариант класса Карта выглядит так:
class СМар
{
int width, height;
int cells[MAX_WIDTH][MAX_HEIGHT];
} ;
85
2 Программирование на языках C++ и C#
86
Классы и объекты в C++ §12
87
2 Программирование на языках C++ и C#
class СМар
{
public:
88
Классы и объекты в C++ §12
{
width = max( 0, min( widthO, MAX_WIDTH ) );
height = max( 0, min( heightO, MAX_HEIGHT ) );
init () ;
}
В описании класса мы оставили только объявление нового конструкто
ра, а его реализация записана отдельно. Так обычно делают, если ме
тод содержит более одной-двух строк. При этом описание класса стано
вится короче и легче для понимания.
Теперь у класса СМар два конструктора. Их может быть и больше,
но все они должны различаться по количеству и типам параметров (по
сигнатуре, от англ, signature — подпись). Иначе компилятор не смо
жет различить, когда какой конструктор вызывается.
Обратите внимание, что в конструкторе с параметрами выполняется
проверка правильности входных данных: мы не разрешаем устанавли
вать ширину карты меньше нуля или больше, чем MAX_WIDTH, а высо
ту — меньше нуля или больше, чем MAX_HEIGHT. После того как раз
меры будут установлены, сразу вызывается метод init, и карта готова
к работе.
По команде
СМар mapl;
вызывается первый конструктор, поля объекта обнуляются. Второй
конструктор срабатывает, если после имени нового объекта в скобках
указать размеры карты:
СМар тар2( 20, 25 );
Заметим, что приведённые выше приёмы работают во всех версиях
C++, включая стандарт С++98. А в стандарте C++11 (и во всех бо
лее новых) разрешено задавать начальные значения по умолчанию в
фигурных скобках после имени каждого поля:
class СМар
{
public:
int width{20}, height{25};
int cells[MAX_WIDTH][MAX_HEIGHT] {};
};
Пустые фигурные скобки в последней строке означают, что все ячейки
матрицы cells заполняются нулями.
Рефакторинг
Попробуем улучшить описание класса так, чтобы сделать его более
простым и понятным (выполним рефакторинг).
89
2 Программирование на языках C++ и C#
90
Классы и объекты в C++ §12
class СМар
{
public:
91
2 Программирование на языках C++ и C#
Рисуем карту
Карта состоит из клеток, размер этих клеток на экране лучше задать
как константу, чтобы при необходимости его было легко изменить:
const int CELL_SIZE = 20;
Добавим в класс СМар метод show, который рисует карту:
class СМар
{
public:
92
Классы и объекты в C++ §12
93
2 Программирование на языках C++ и C#
Выводы
• Описание класса в C++ начинается служебным словом class.
• Для ограничения доступа к полям и методам класса использу
ют модификаторы доступа. Модификатор private обозначает за
крытые (частные) данные и методы, обращаться к которым могут
только объекты данного класса. Модификатор public обозначает
общедоступные данные и методы.
• По умолчанию все поля и методы класса — закрытые.
• Класс может иметь несколько конструкторов. Все они должны раз
личаться по сигнатуре, т. е. по количеству и типам параметров.
• Реализация метода класса может быть вынесена из объявления
класса. В этом случае в объявлении класса нужно записать объ
явление (прототип) метода.
• Данные перечисляемого типа (enum) могут принимать одно из за
данного множества значений. Эти значения кодируются целыми
числами.
Вопросы и задания
1. Как можно назвать этап решения задачи, который обсуждается в
первом пункте параграфа?
2. Найдите ошибку в объявлении класса.
class CMarusya { int strength; }
94
Классы и объекты в C++ §12
95
2 Программирование на языках C++ и C#
Интересные сайты
cpp.sh — онлайн-среда для программирования на С++
ded32.ru (txlib.ru) — сайт профессиональной проектной работы по
информатике для школьников, там же расположена библиотека
ТХ Library
§13
Программа с классами (практикум)
Ключевые слова:
• класс • конструктор
• объект • метод
• ссылка • рефакторинг
В этом параграфе мы построим класс Машина и закончим простую
программу, в которой машина двигается по карте. После этого вы смо
жете совершенствовать её самостоятельно.
Класс ССаг
Данные класса Машина (ССаг) — это координаты машины, х и у.
Кроме того, машина должна быть связана с картой, иначе непонятно,
что означают эти координаты и как проверить, свободна ли клетка, в
которую перемещается машина. Чтобы организовать такую связь, нуж
но хранить для каждого объекта-машины ссылку на карту, которую
машина использует. Таким образом, одно из полей в классе ССаг —
это ссылка на объект класса СМар:
СМар& тар;
96
Программа с классами (практикум) §13
97
2 Программирование на языках C++ и C#
98
Программа с классами (практикум) §13
Снова рефакторинг
Итак, классы вроде бы готовы. Но давайте всё-таки попробуем ещё
кое-что улучшить.
Во-первых, функцию выбора цвета клетки карты можно «изъять»
из класса СМар и выделить в отдельную подпрограмму. Она определяет
цвет клетки не по координатам, а по коду объекта, находящегося в
этой клетке:
COLORREF cellColor( cellType type )
{
COLORREF color;
if ( type == EMPTY )
color = RGB(50, 255, 50);
else if( type == SIDE )
color = RGB(80, 0, 0);
else // if( type == WALL )
color = RGB(0, 80, 0);
return color;
}
Операцию рисования клетки карты оформим как отдельную про
цедуру, не входящую ни в какой класс. Она принимает три парамет
ра — координаты клетки и код объекта:
void drawCell( int х, int у, cellType type )
{
COLORREF color = cellColor( type );
txSetColor ( color );
txSetFillColor ( color );
int left = x*CELL_SIZE,
top = y*CELL_SIZE;
txRectangle( left, top,
left+CELL_SIZE, top+CELL_SIZE );
}
В первой строке для определения цвета клетки вызывается функция
cellColor, которую мы только что написали.
99
2 Программирование на языках C++ и C#
Основная программа
В основной программе нужно создать объекты (карту и машину) и
окно для вывода графики:
const int W = 15, L = 20;
CMap map ( W, L ) ;
CCar car( map, 10, 10 );
txCreateWindow( W*CELL_SIZE, L*CELL_SIZE );
Для анимации будем использовать цикл, работающий до нажатия
клавиши Escape:
100
Программа с классами (практикум) §13
Разбиение на модули
Программа получилась достаточно большой, поэтому возникает естест
венное желание разбить её на части, сохранив каждую часть в от
дельном файле. Например, в отдельный модуль, который мы назовём
cargame. срр, можно выделить определение обоих классов1’, СМар и
ССаг.
Сразу возникает вопрос: как собрать исполняемую программу из
нескольких исходных файлов? Для этого нужно создать проект, объе
диняющий несколько файлов. Это умеют все современные среды разра
ботки программ. Кроме того, для сборки проекта из командной строки
можно использовать утилиту make.
В C++ используется раздельная компиляция, т. е. все файлы про
екта проходят компиляцию независимо друг от друга. Для каждого из
’’ Другой вариант — выделить каждый класс в отдельный файл. При желании вы
сможете это сделать самостоятельно, поняв общий принцип.
101
2 Программирование на языках C++ и C#
class CMap
{
public:
int width, height;
cellType cells[MAX_WIDTH][MAX_HEIGHT];
CMap( int widthO = 10, int height0 = 15 );
void init();
void show() const;
bool isFree( int x, int у ) const {
return cells[x][y] == EMPTY;
}
};
class CCar
{
public:
CMap& map;
102
Программа с классами (практикум) §13
int х, у;
ССаг ( СМар& _map, int хО = 0, int уО = 0 ):
тар(_тар), х(хО), у(уО)
{};
void show() const;
bool moveBy( int dx = 0, int dy = 0 );
};
Реализацию всех методов классов поместим в файл cargame. срр:
#include <algorithm>
#include "cargame.hpp"
using namespace std;
void drawee11( int x, int у, cellType type );
void drawCar( int x, int у );
... // далее — реализация всех методов классов
В начале файла подключается библиотека algorithm. В ней объяв
лены функции min и max, которые используются в конструкторе клас
са СМар. Затем подключаем заголовочный файл cargame.hpp и записы
ваем прототипы процедур рисования.
Таким образом, мы построили модуль, содержащий классы СМар и
ССаг. Он состоит из двух частей — заголовочного файла с расширени
ем . h или .hpp (интерфейса) и файла с расширением .срр, в котором
реализованы методы классов.
А что же осталось в файле с основной программой? Только то, что
относится к представлению, т. е. к картинке на экране:
#include "TxLib.h"
#include "cargame.hpp"
const int CELL_SIZE = 20;
COLORREF cellColor( cellType type )
{
}
void drawCe11( int x, int у, cellType type )
{
}
void drawCar( int x, int у )
{
}
int main()
{
const int W = 15, L = 20;
CMap map( W, L ) ;
103
2 Программирование на языках C++ и C#
}
}
Операторы, которые нужно поставить вместо многоточий, можно
посмотреть в середине этого параграфа.
Теперь вы знаете, как разбить объектно-ориентированную программу
на модули. В качестве упражнения можно поместить каждый класс в
отдельный модуль.
Выводы
• Связь с объектом можно установить с помощью специального поля
типа «ссылка». В отличие от указателя, ссылка всегда указывает
на существующий объект и не может быть изменена.
• Для обращения к полям и методам объекта по ссылке использует
ся точечная запись.
• Разделение программы на модель данных и представление позво
ляет легко изменять одну из этих частей, не затрагивая вторую.
Кроме того, модель можно использовать в других программах.
• Классы, которые создаёт программист, обычно оформляются в
виде модулей. Модуль может содержать один класс или несколько
связанных по смыслу классов.
• Модуль состоит из двух частей — заголовочного файла с расши
рением . h или . hpp (интерфейса) и файла с расширением . срр, в
котором реализованы методы классов.
Вопросы и задания
1. Что произойдёт, если поменять местами строки 5 и 6 в основной
программе, приведённой на с. 101?
2. Почему при вызове метода moveBy в основной программе из парагра
фа задаётся только один аргумент, хотя метод имеет два параметра?
3. Что произойдёт, если вызвать метод moveBy без параметров (см. про
грамму из параграфа): car. moveBy ();?
4. Предположите, что произойдёт, если во время работы программы из
параграфа одновременно нажать стрелки «влево» и вправо». Что из
менится, если два условных оператора в цикле основной программы
связать словом else?
5. Подумайте, в каком случае вызов метода isFree в программе из
параграфа может привести к ошибке? Как можно защититься от
этой ошибки?
104
Инкапсуляция §14
Интересные сайты
ideone.com — онлайн-среда для программирования на различных
языках
repl.it — онлайн-среда для программирования на различных языках
§14
Инкапсуляция
Ключевые слова:
• инкапсуляция • модификаторы доступа
• поле • интерфейс
• метод • свойство
105
2 Программирование на языках C++ и C#
106
Инкапсуляция §14
107
2 Программирование на языках C++ и C#
};
При этом необходимо поменять также методы getColor и setcolor,
которые непосредственно работают с этим полем. Обратите внима
ние, что заголовки методов (т. е. интерфейс, способ общения объекта с
«внешним миром») остались прежними, и другие объекты «не заметят»,
что во внутреннее устройство класса СРеп внесены какие-то изменения.
Итак, метод getColor должен вернуть символьную строку, в кото
рой записан шестнадцатеричный код цвета, хранящегося в поле FColor
как целое число. Преобразовывать число в символьную строку будем
с помощью строкового потока (stringstream). В него можно записать
число, как в обычный поток вывода, при этом число превратится в
последовательность символов, т. е. в строку.
Записать в поток число в шестнадцатеричной системе счисления
можно так:
#include <sstream> // (1)
stringstream s; // (2)
s « hex << FColor; // (3)
Для работы co строковыми потоками подключаем библиотеку
sstream (строка 1). Формат hex обозначает «вывести число в шестнад
цатеричной системе счисления». Однако это немного не то, что нужно.
Например, цвет FF16 будет выведен как "FF" вместо "0000FF". Необхо
димо как-то указать, что требуется вывести число в 6 позициях, и все
пустые позиции слева заполнить нулями. Это делается с помощью ма
нипуляторов вывода из библиотеки iomanip, которую тоже надо под
ключить в начале программы:
#include <iomanip>
108
Инкапсуляция §14
109
2 Программирование на языках C++ и C#
Свойства в C#
Мы уже говорили о том, что фактически класс СРеп определяет свойство
«цвет», для работы с которым используются метод чтения («геттер»)
getColor и метод записи («сеттер») setcolor. Однако понятия «свой
ство» нет в языке C++, и эти два метода формально никак не связаны!
Во многих современных языках программирования введено понятие
свойства. Один из таких языков — язык С#, основанный на идеях С
и C++. В C# можно ввести свойство color так (сначала рассмотрим
простейший случай):
class СРеп
{
private string FColor; // закрытое поле
public string color // открытое свойство
{
get { return FColor; } // метод чтения
set { FColor = value; } // метод записи
}
}
110
Инкапсуляция §14
111
2 Программирование на языках C++ и C#
Выводы
• Данные и методы класса, которые нужно скрыть от других объек
тов, в языке C++ объявляются в секции private, общедоступные
данные и методы объявляются в секции public.
• Поля, содержащие данные объекта, обычно делают закрытыми.
Для доступа к ним в класс добавляют методы, в которых можно
выполнять проверку и преобразование данных.
• Если при изменении внутреннего устройства объектов класса
сохранился интерфейс (способ вызова общедоступных методов),
другие классы переделывать не нужно.
• Если в классе нет метода для установки нового значения закрыто
го поля, другие объекты не смогут изменить это поле.
Вопросы и задания
1. Каковы достоинства и недостатки инкапсуляции?
2. Чем различаются секции public и private в описании классов?
Как определить, в какую из них поместить какое-либо свойство
или метод?
3. Почему рекомендуют делать доступ к полям объекта только с по
мощью методов?
4. Как вы думаете, когда можно сделать поле открытым (объявить в
секции public)? Обсудите этот вопрос с коллегами.
5. Зачем нужны свойства «только для чтения»?
6. Как ввести в класс свойство «только для записи» (которое нельзя
прочитать)?
7. Что такое интерфейс объекта? Почему при коллективной разра
ботке программы желательно не изменять заранее согласованные
интерфейсы?
8. Что означает «сохранить интерфейс класса» для класса СРеп, раз
работанного в параграфе?
9. Измените внутреннее устройство объектов класса СРеп из пара
графа так, чтобы три составляющие RGB-кода цвета хранились в
виде массива из трёх элементов.
*10. Проект. Измените программу моделирования движения машины
(см. предыдущий параграф) так, чтобы все поля у объектов были
закрытыми.
Интересные сайты
cppstudio.com — программирование для начинающих на C++
ru.cppreference.com — онлайн-справочник по C++
leamcs.org — интерактивный учебник по C#
dotnetfiddle.net — онлайн-компилятор C#
112
Наследование §15
§15
Наследование
Ключевые слова:
• класс • абстрактный класс
• иерархия классов • виртуальный метод
• базовый класс • защищённые поля
• наследование • утечка памяти
Иерархия классов
Все объекты, о которых мы говорили, обладают общими свойствами.
Эти свойства можно объединить в базовом классе, который мы назовём
COceanObject (в переводе с английского — класс «океанский объект»).
Конечно, создавать такой объект бессмысленно, потому что это просто
набор свойств и методов (т. е. абстракция). Объектов типа Океанский
объект в природе (и в нашей игре) не существует. Поэтому, говоря
на языке программистов, класс COceanObject будет абстрактным, он
Для научного исследования биологических систем используют модели «хищ
ник — жертва». Самая известная из них — модель Лотки-Вольтерры. Этот
материал выходит за рамки пособия, но вы можете самостоятельно найти
информацию о таких моделях в сети Интернет.
113
2 Программирование на языках C++ и C#
Рис. 2.3
Базовый класс
Введём новый перечисляемый тип данных:
enum objectType { STONE, GRASS, FISH, HUNTER };
Здесь STONE обозначает Камень, GRASS — Траву, FISH — Рыбу и
HUNTER — Хищника (от английских слов stone — камень, grass —
трава, fish — рыба и hunter — охотник).
Для простоты будем изображать все объекты кругами разного цве
та: Камни — чёрными, Траву — зелёными, Рыб — синими и Хищни
ков — красными.
В базовый класс COceanObject добавим три поля, общие для всех
объектов: координаты центра круга (х, у) и радиус круга г:
class COceanObject
{
public:
int x, у, г;
};
Теперь определим общие методы, которые должны иметь все океан
ские объекты. Компьютер выполняет моделирование в дискретном време
ни, обновляя картинку на экране с некоторым интервалом Ai. В течение
очередного интервала моделирования каждый объект может измениться.
Могут поменяться размеры или координаты объекта, объект может быть
съеден или, наоборот, сам может кого-то съесть. Все эти изменения мы
поместим в метод change (по-английски — изменить).
114
Наследование §15
COceanObjесt
х
У
z
update
change
show
Рис. 2.4
Запишем эту схему в виде класса на языке C++:
class COceanObject
{
public:
int x, у, г;
void update();
void change();
void show() const;
};
Метод show не должен изменять объект, поэтому он объявлен с описа
телем const.
Мы не определили конструктор для этого класса, поэтому компи
лятор создаст конструктор по умолчанию, который просто размещает
объект в памяти.
Теперь мы даже можем создать объект этого класса и присвоить
значения его полям:
COceanObject obj;
obj.х = 100;
obj.у = 100;
obj.r = 10;
Конечно, при попытке вызвать метод этого объекта
obj.update();
мы увидим сообщение об ошибке, ведь реализации этого метода (про
граммного кода) пока нигде нет.
115
2 Программирование на языках C++ и C#
Абстрактный класс
Разбираться с методами класса начнём с метода update. Его содержа
ние написать очень просто: объект изменяется, и затем на экран вы
водится его новое изображение. Это значит, что сначала вызывается
метод change, а потом сразу — метод show. Так будут обновляться
объекты всех классов, поэтому метод update мы правильно поместили
в базовый класс TOceanObject:
class COceanObject
{
public:
void update() {
change();
show ();
}
};
Методы change и show есть у всех объектов, поэтому они также
должны располагаться именно в базовом классе COceanObject. Но
каждый наследник будет выполнять их по-разному, т. е. наследники
будут переопределять методы базового класса. Такие методы обозна
чаются словом virtual (в переводе с английского — виртуальный) в
начале объявления метода:
class COceanObject
{
public:
116
Наследование §15
117
2 Программирование на языках C++ и C#
public:
void update() {
change ();
show () ;
}
virtual void change() = 0;
virtual void show() const = 0;
};
Теперь наследники класса COceanObject смогут работать с полями
базового класса так же, как с открытыми полями, а другие объекты
не смогут «увидеть» эти поля.
И тут возникает вопрос: а как же мы будем присваивать начальные
значения полям? Имеет смысл делать это сразу при создании объекта,
в конструкторе. Следовательно, нам нужно добавить в описание класса
конструктор, присваивающий начальные значения полям х, у и г.
Поскольку класс COceanObject абстрактный, его конструктор будет
вызываться только классами-наследниками. Поэтому конструктор так
же можно поместить в секцию protected:
class COceanObject
{
protected:
int x, у, г;
COceanObject( int xO, int yO, int rO ):
x(xO) , y(yO), r(rO) { }
};
Неподвижные объекты
В нашей программе будут два класса неподвижных объектов — CStone
{Камень) и CGrass {Трава).
Начнём с класса CStone. Как показано на рис. 2.3, он — прямой
наследник базового класса COceanObject. Поэтому заголовок класса
выглядит так:
class CStone: public COceanObject
{
}
После названия нового класса CStone через двоеточие указан базовый
класс COceanObject. Служебное слово public определяет режим до
ступа при наследовании. В нашем случае режим доступа не меняется:
защищённые (protected) данные базового класса остаются защищён
ными и для класса-наследника, а открытые — открытыми. В этом по
собии мы не будем использовать другие варианты наследования1'.
При желании вы легко найдёте эту информацию в литературе или в Интернете.
118
Наследование §15
119
2 Программирование на языках C++ и C#
Подвижные объекты
Подвижные объекты относятся к классу CMovingObject, который
наследует все свойства и методы базового класса COceanObject:
class CMovingObject: public COceanObject
{
}
В этом классе к тем полям и методам, которые унаследованы от
класса COceanObject, добавляются два новых поля — скорость движе
ния (поле v) и курс (поле course), — и новый метод move (в переводе
с английского — двигаться):
class CMovingObject: public COceanObject
{
protected:
double v, // скорость
course; // курс в градусах
public:
CMovingObject( int xO, int yO, int rO,
double vO, double courseO = 0 ) :
COceanObject( xO, yO, rO ),
v(vO), course(courseO)
{ )
virtual void move()
{
double courseRadians = course*M_PI/180;
x += round(v*cos(courseRadians) ) ;
у -= round(v*sin(courseRadians) ) ;
if ( x-r < 0 ) x += SCREEN_WIDTH;
if ( x+r > SCREEN_WIDTH )
x -= SCREEN_WIDTH;
if ( y-r < 0 ) у += SCREEN_HEIGHT;
if ( y+r > SCREEN_HEIGHT )
у -= SCREEN—HEIGHT;
}
};
Конструктор класса CMovingObject вызывает конструктор базового
класса, а затем записывает в поля v и course дополнительные дан
ные: скорость и курс, которых у базового класса нет. В конструкторе
определён© значение по умолчанию для последнего параметра course:
если курс не задан, он считается равным нулю.
Новый метод move определяет алгоритм движения объекта. Это вир
туальный метод, так что классы-наследники могут его переопределить.
Курсовой угол переводится в радианы, затем изменяются координаты
х и у (подробно мы обсуждали эти формулы в § 7, см. рис. 1.12).
120
Наследование §15
Рыбы
Рыбы (класс CFish) — это подвижные объекты, т. е. наследники клас
са CMovingObject. Класс CFish можно описать так:
class CFish: public CMovingObject
{
public:
CFish( int xO, int yO, int rO,
double vO, double courseO ):
CMovingObject( xO, yO, rO, vO, courseO )
{ }
virtual void change () override
{ move(); }
virtual void show () const override
{ drawObject ( x, y, r, FISH ); }
};
Конструктор класса просто вызывает конструктор базового класса
CMovingObject. Метод change вызывает метод move из базового клас
са, а метод show рисует рыбу с помощью процедуры drawObject, пере
давая ему последний аргумент FISH.
В описании класса CFish определены оба абстрактных метода
базового класса — change и show. Мы знаем, как изменять состояние
рыбы во время моделирования и как нарисовать её на экране. Поэтому
класс CFish — не абстрактный класс, и мы можем (и будем) создавать
экземпляры этого класса.
121
2 Программирование на языках C++ и C#
Хищники
Займёмся хищниками. Класс CHunter (Хищник) — это тоже наслед
ник класса CMovingObject:
class CHunter: public CMovingObject
{
};
У нас на поле будет всего один экземпляр класса CHunter — им
управляет игрок. Хищник должен двигаться с заданной скоростью к
той точке игрового поля, на которую указывает курсор мыши.
Определим, куда переместится хищник за один интервал игрового
времени At. Начальные координаты хищника — х и у, скорость и озна
чает, что за время At он проходит v единиц игрового поля (пикселей).
Пусть хм и ум — координаты курсора мыши. Тогда расстоя
ние между текущим положением хищника и заданной точкой можно
вычислить по теореме Пифагора:
122
Наследование §15
123
2 Программирование на языках C++ и C#
Основная программа
Настало время собрать игру целиком. В начале подключим библиоте
ку ТХ Library, введём перечисляемый тип данных objectType и кон
станты (размеры поля):
#include "TXLib.h"
enum objectType { STONE, GRASS, FISH, HUNTER };
const int SCREEN_WIDTH = 600,
SCREEN_HEIGHT = 400;
Затем запишем все вспомогательные процедуры и функции, которые
были только что рассмотрены. После этого поместим описания всех
классов, начиная с базового.
В начале основной программы необходимо создать все нужные объ
екты. Мы покажем, как добавить на поле камни и хищника, осталь
ные объекты вы сможете построить самостоятельно.
124
Наследование §15
125
2 Программирование на языках C++ и C#
126
Наследование §15
Выводы
• Классы в программе образуют иерархию. Базовый класс объединя
ет общие данные и методы всех объектов.
• Класс-наследник обладает всеми свойствами и методами базового
класса.
• Класс, который предназначен только для создания классов-
наследников, а не экземпляров (объектов), называется абстракт
ным. Абстрактный класс объединяет общие данные и методы
группы классов.
• Виртуальная функция — это метод, который могут переопреде
лять классы-наследники.
• Чистая виртуальная функция только объявляется в классе, но её
программный код (реализация) отсутствует.
• Абстрактный класс в C++ — это класс, который содержит хотя
бы одну чистую виртуальную функцию.
• Чтобы класс-наследник не был абстрактным, он должен пере
определить все чистые виртуальные функции базового класса. Пос
ле этого можно будет создавать экземпляры класса-наследника.
• Если новые объекты создавались с помощью оператора new
(динамически), программа должна освободить эту память с помо
щью оператора delete.
Вопросы и задания
1. Чем полезна иерархия классов? Почему бы не использовать в про
грамме отдельные (не связанные между собой) классы?
2. Что такое метод-«заглушка»? Какие достоинства и недостатки он
имеет?
3. Объясните, почему бессмысленно создавать объекты абстрактного
класса.
4. Что такое виртуальная функция? Почему некоторые виртуальные
функции называют чистыми?
5. Можно ли по описанию класса сразу сказать, абстрактный он или
нет? Рассмотрите разные случаи.
127
2 Программирование на языках C++ и C#
128
Полиморфизм §16
Интересные сайты
cplusplus.com — сайт, посвящённый программированию на языке
C++
rsdn.org — сайт, посвящённый разработке программного обеспечения
(на разных языках)
§16
Полиморфизм
Ключевые слова:
• базовый класс • позднее связывание
• класс-наследник • таблица виртуальных методов
• виртуальный метод • деструктор
• полиморфизм
В § 5 мы познакомились с полиморфизмом. Так называется возмож
ность классов-наследников по-разному реализовать метод базового клас
са. Виртуальные методы change и show в построенных ранее классах
морских объектов — это как раз пример полиморфизма. В этом па
раграфе мы увидим, что виртуальные методы особенно полезны при
работе с коллекцией объектов разных классов через указатели.
129
2 Программирование на языках C++ и C#
Рис. 2.5
Указатель на любой объект класса-наследника хранит начальный адрес
этой области, поэтому через него можно легко «добраться» до всех
данных и методов базового класса.
В переменную pObject, которая объявлена выше, можно записать
адрес нового объекта-камня:
pObject = new CStone( 100, 100, 10 );
Потом можно удалить этот объект-камень из памяти и в ту же пере
менную записать адрес нового объекта-рыбы:
130
Полиморфизм §16
delete pObject;
pObject = new CFish( 100, 100, 5, 2, 120 );
В переменную, объявленную как указатель на объект класса А, мож
но записать адрес любого объекта-наследника класса А.
Используя эту особенность, построим общий массив указателей для
хранения адресов всех океанских объектов. Вот как это выглядит для
камней и травы:
#include <vector>
using namespace std;
Полиморфизм в действии
Теперь возникает вопрос: что делать с этим массивом, в который за
писаны адреса объектов разных типов, и (это важно!) являющихся
131
Программирование на языках C++ и C#
public:
void update() {
change();
show ();
}
};
Но он вызывает методы change и show, которых в базовом классе нет.
Все объекты нарисованы правильно (камни — чёрные, трава — зелё
ная). Это значит, что для объектов класса CStone были вызваны мето
ды change и show из класса CStone, а для объектов класса CGrass —
методы с такими же именами из класса CGrass.
Фактически программа во время выполнения определила, к какому
классу относится конкретный объект, и вызвала именно «его» методы.
132
Полиморфизм §16
133
2 Программирование на языках C++ и C#
Позднее связывание
Если мы хотим использовать полиморфизм при вызове методов через
указатели, важно, чтобы эти методы были виртуальными, для невир
туальных методов такой механизм не работает.
Предположим, что в базовом классе COceanObject методы change
и show не объявлены виртуальными (например, мы поставили вместо
них «заглушки»). Тогда при компиляции в машинный код сразу за
писываются команды перехода по адресам в памяти, где расположены
эти (обычные, невиртуальные) методы.
Пусть р — указатель на объект базового класса, и мы записали в
него адрес класса-наследника:
COceanObject* р = new CStone ( ... );
Теперь вызываем метод update, объявленный в базовом классе:
p->update();
И тут выясняется, что в машинном коде метода update адреса перехо
дов на методы change и show уже прописаны. В итоге программа вы
полнит методы-«заглушки» базового класса и ничего не нарисует.
У нас же методы change и show — виртуальные. В этом случае ком
пилятор «соображает», что наследники могут переопределить эти мето
ды, и поэтому не записывает в машинный код метода update фикси
рованные адреса переходов на методы change и show. Вместо этого он
добавляет довольно сложные команды, при выполнении которых:
• определяется фактический тип объекта;
• в таблице виртуальных методов объекта (англ. VMT: virtual
method table) ищется адрес нужного метода (например, метода
change);
• выполняется метод, расположенный по найденному адресу.
Потом так же происходит вызов виртуального метода show.
Такой механизм называется поздним связыванием. Это означает, что
программа определяет адрес выполняемого метода не при компиляции,
а во время выполнения программы.
При позднем связывании:
• вместе с каждым объектом хранится таблица его виртуальных
методов;
134
Полиморфизм §16
Класс Океан
До этого момента мы предполагали, что в нашей игре все обитатели
океана действуют независимо друг от друга. На самом деле они при
надлежат единой системе Океан. При программировании игр такой
объект иногда называют игровым миром.
Если говорить на языке ООП, можно ввести ещё один «большой»
объект — Океан, — который «владеет» всеми другими объектами.
Назовём этот класс СОсеап:
class СОсеап
{
};
Информацию о его «подчинённых» объектах — камнях, траве и др. —
будем хранить как вектор (массив) указателей в закрытом поле объек
та с именем pObjects:
class СОсеап
{
private:
vector <COceanObject*> pObjects;
};
В классе СОсеап создадим два метода. Первый метод — addObject —
будет добавлять новый объект, а второй — update — будет выполнять
обновление всех объектов в конце каждого интервала моделирования.
Оба этих метода сделаем общедоступными:
class СОсеап
{
private:
vector <COceanObject*> pObjects;
public:
void addObject( objectType type,
int xO, int yO, int rO = 10,
int vO = 0, double courseO = 0 );
void update ();
};
135
2 Программирование на языках C++ и C#
136
Полиморфизм §16
137
Программирование на языках C++ и C#
Деструктор
В методе addObject класса COcean мы создаём новые объекты с помо
щью оператора new, сохраняя их адреса в массиве pObjects. Чтобы не
было утечек памяти, важно сразу определить, где будет освобождаться
выделенная память.
Очевидно, что при уничтожении объекта Океан все другие объекты,
которые находятся в его «владении» (в массиве pObjects), тоже нуж
но удалить. Поэтому возникает вопрос: как «поймать» момент удале
ния объекта?
Для этой цели в каждом классе C++ есть специальный метод, кото
рый называется деструктором.
138
Полиморфизм §16
139
2 Программирование на языках C++ и C#
class COceanObject
{
public:
Выводы
• В переменную, объявленную как указатель на объект класса А,
можно записать адрес любого объекта-наследника класса А.
• При вызове виртуального метода через указатель или ссылку на
объект программа во время выполнения определяет фактический
класс объекта и вызывает метод того класса, к которому этот объ
ект принадлежит.
• При позднем связывании программа определяет адрес выполняе
мого виртуального метода не при компиляции, а во время выпол
нения, в зависимости от типа объекта.
• При позднем связывании используется таблица виртуальных мето
дов, которая хранится вместе с каждым объектом.
• Позднее связывание замедляет работу программы.
• Деструктор — это специальный метод, который вызывается при
удалении объекта из памяти. Если деструктор не указан, компи
лятор построит деструктор по умолчанию.
• Деструктор необходимо определять, когда объект захватывает
ресурсы, например содержит указатели на блоки памяти, выде
ленные во время работы программы.
• Если класс содержит виртуальные методы, его деструктор должен
быть объявлен виртуальным.
Вопросы и задания
1. Найдите все ошибки в этом фрагменте программы (в этом и следую
щих заданиях используются объекты, приведённые в параграфе):
CFish р;
р = new CStone( 100, 100, 10 );
р.show ();
р = new COceanObject( 100, 100, 15 );
р = new CFish( 100, 100, 15, 2, 0 );
p->update;
Предложите способ исправления этих ошибок.
140
Полиморфизм
Интересные сайты
ru.stackoverflow.com — сайт вопросов и ответов для программистов
learncpp.com — онлайн-учебник по языку C++
141
2 Программирование на языках C++ и C#
§17
Взаимодействие объектов
Ключевые слова:
• столкновения • итератор
• «мёртвые» объекты • преобразование типов
Столкновения
Давайте подумаем, а зачем нам вообще нужен класс Океан («игровой
мир»). Если он просто хранит указатели на все объекты, то особой
необходимости в нём нет — можно использовать обычный вектор.
Вспомним, что наши объекты должны взаимодействовать друг с дру
гом. Например, хищник ест рыб, а рыбы едят траву. Организацию это
го процесса мы можем поручить классу СОсеап.
Добавим в класс СОсеап два открытых метода, которые обрабатыва
ют столкновения и их последствия:
class СОсеап
{
public:
void checkcollisions() const;
void removeDead();
};
Константный метод checkcollisions (от англ, «проверить столкно
вения») проверяет для каждой пары объектов, произошло ли столкно
вение, а метод removeDead — удаляет из океана мёртвые объекты.
Добавим вызовы этих методов в код метода update:
void СОсеап::update()
{
for( auto pObj: pObjects )
pObj->update();
checkcollisions ();
removeDead();
}
Таким образом, после того как все объекты изменят своё положе
ние, мы обрабатываем столкновения между ними и удаляем «мёртвые»
объекты.
В методе removeDead какие-то объекты могут быть удалены с поля,
при этом изменится и вектор pObjects. Поэтому метод update нельзя
объявлять константным.
Перед тем как написать код новых методов, нужно решить, кто
(объект-океан или объекты-обитатели) должен уметь «отвечать» на сле
дующие вопросы.
142
Взаимодействие объектов §17
143
2 Программирование на языках C++ и C#
144
Взаимодействие объектов §17
class COceanObject
{
public:
145
2 Программирование на языках C++ и C#
146
Взаимодействие объектов §17
147
2 Программирование на языках C++ и C#
«Умные» указатели
Неаккуратное использование указателей в программе может привести
к очень тяжёлым ошибкам, связанным с выделением и освобождением
памяти. К ним относятся:
• обращение по неверному адресу;
• повторное удаление объекта;
• утечка памяти из-за того, что объект не удалён из памяти, а
ссылка на него потеряна.
Для того чтобы избавить программистов от этих проблем, в стан
дарт С++11 были введены «умные» указатели. «Умный» указатель
типа shared_jptr (от англ, shared pointer — разделяемый указатель)
в любой момент «знает», сколько всего ссылок есть на связанный с
ним объект. Когда такой указатель выходит из области видимости и
удаляется из памяти, счётчик ссылок на этот объект уменьшается на
единицу. Если после этого счётчик ссылок станет равен нулю, объект
становится недоступным, поэтому автоматически удаляется из памяти.
Это значит, что программисту не нужно вручную удалять объект, на
который ссылается «умный» указатель — объект будет удалён именно
тогда, когда станет не нужен.
Для работы с «умными» указателями необходимо подключить биб
лиотеку memory (в переводе с английского — память):
#include <memory>
«Умный» указатель — это переменная типа shared_ptr:
shared_ptr<COceanObject> рОЬ j ;
В угловых скобках записан тип объектов, на которые может ссылаться
этот указатель. Пока этот указатель пустой, он имеет нулевое значение
и нулевой счётчик ссылок.
Значение, которое записывается в «умный» указатель, обычно стро
ится с помощью функции make_shared:
pObj = make_shared<CStone>( 100, 100, 8 );
Здесь создаётся новый объект класса CStone, для этого вызывается
конструктор CStone (100, 100, 8). Затем адрес этого объекта записы
вается в «умный» указатель pObj.
148
Взаимодействие объектов §17
};
В методе addObject тоже нужно перейти на «умные» указатели, ко
торые строятся с помощью функции make_shared:
void СОсеап::addObject ( objectType type,
int xO, int yO, int rO,
int vO, double courseO )
{
shared_ptr<COceanObject> pNewObj = nullptr;
if( type == STONE )
pNewObj = make_shared<CStone>( xO, yO, rO );
149
2 Программирование на языках C++ и C#
bool hasCollisionWith(
std::shared_ptr<COceanObject> pother )
const;
virtual void collideWith(
std::shared_ptr<COceanObject> pother ) {}
};
Тело метода collideWith в каждом классе тоже поменяется, напри
мер, для класса CFish:
void CFish::collideWith(
shared_ptr<COceanObject> pother )
{
auto pHunter =
dynamic_pointer_cast<CHunter> (pother);
if( pHunter ) r = 0;
}
Обратите внимание, что для преобразования типа «умного» ука
зателя во время выполнения программы используется функция
dynamic_pointer_cast, а не dynamic_cast, как для обычных указателей.
Заметно, что текст программы стал немного сложнее по сравнению
с предыдущим вариантом. Однако мы получили важное преимущество:
теперь нам не нужно думать о том, как мы будем освобождать выде
ленную память. Всё произойдёт автоматически и в нужный момент.
150
Взаимодействие объектов §17
Выводы
• Для того чтобы обрабатывать столкновения, каждый объект дол
жен иметь два метода. Один определяет факт столкновения с дру
гим объектом, а второй изменяет объект в результате столкнове
ния (если нужно).
• Вызов этих методов выполняет главный объект («игровой мир»,
у нас — океан), перебирая во вложенном цикле все возможные
пары разных объектов.
• Удалением «мёртвых» объектов с поля занимается главный объ
ект. Все классы должны иметь метод, позволяющий определить,
можно ли удалить с поля данный объект.
• Для определения класса объекта по его адресу используется ин
формация о типах данных, доступная во время выполнения про
граммы (RTTT. Runtime Type Information).
• Для приведения указателя к другому типу во время выполне
ния программы применяется оператор dynamic_cast. Если такое
преобразование закончится неудачно, в указатель будет записано
нулевое значение.
• В стандарте С++11 введены «умные» указатели, которые поддер
живают счётчик ссылок на объект, выделенный в памяти. Когда
счётчик ссылок на какой-то объект становится равен нулю, этот
объект удаляется из памяти автоматически.
Вопросы и задания
1. Предложите какие-нибудь дополнительные свойства и методы, кото
рые могут быть у объекта Океан (можно вспомнить, как меняется
состояние реального океана). В этом и следующих заданиях исполь
зуются объекты, приведённые в параграфе.
2. Пётр хочет сделать так, чтобы всеми взаимодействиями объектов
управлял главный объект — «игровой мир». Обсудите достоинства и
недостатки такого решения.
3. Константин написал циклы в методе checkcollisions класса СОсеап
так:
for( int i = 0; i < pObjects.size (); i++ )
for( int j =0; j < pObjects.size(); j++ )
151
2 Программирование на языках C++ и C#
Интересные сайты
gamesmaker.ru — сайт о программировании игр на С++
tutorialspoint.com — онлайн-учебники по программированию
152
Простая программа на C# §18
§18
Простая программа на C#
Ключевые слова:
• RAD-среда • свойство
• среда .NET • событие
• язык C# • обработчик события
• виртуальная машина • компонент
• проект • статический класс
• форма
153
2 Программирование на языках C++ и C#
154
Простая программа на C# §18
Проект в C#
Разработка программы на C# начинается с создания проекта. Так на
зывается набор файлов, из которых компилятор строит исполняемый
файл программы. В состав проекта на языке C# обычно входят:
• файл проекта с расширением .csproj (от англ. CSharp Project —
проект С#), в котором содержится описание проекта в текстовом
формате XML;
• модули на языке C# (файлы с расширением . cs), из которых
состоит программа;
• файлы ресурсов (с расширением . resx), содержащие, например,
строки для перевода сообщений программы на другие языки.
Проекты в Visual Studio объединяются в решения (англ, solution).
В состав решения может быть включено несколько проектов, напри
мер: программа для пользователя разрабатываемой системы (первый
проект), программа для администратора (второй проект), средства раз
работчика и т. п.
Основная программа проекта на C# находится в файле Program.cs
(здесь и далее указываются имена файлов по умолчанию).
В программе с графическим интерфейсом может быть несколько
окон, которые называют формами. С каждой формой связана пара
файлов с расширением .cs. В одном из них хранятся данные о рас
положении и свойствах элементов интерфейса. В названиях этих фай
лов есть слово Designer, например Forml.Designer.cs. Второй файл
(Forml. cs) содержит программный код обработчиков событий, связан
ных с этой формой.
Одна из форм — главная, она появляется на экране при запуске
программы. Когда пользователь закрывает главную форму, работа про
граммы завершается.
Первый проект
Для создания проекта с графическим интерфейсом нужно выбрать
пункт меню Файл — Создать проект и в появившемся окне отметить
вариант Приложение Windows Forms. При этом автоматически созда
ется работоспособная программа, которую сразу же можно запустить
на выполнение клавишей F5 (рис. 2.6). Все приведённые далее снимки
экрана («скриншоты») были сделаны в среде Visual Studio Community
2017.
155
M WindowsFormsAppI - Microsoft Visual Studio & Быстрый запуск (Ctrl+Q) P — Я X
Д Label с* Program.es
A LinkLabel
ListBox ►
5“ ListView Обозреватель реш... Team Explorer— П..
MaskedTextBox
Свойства ▼ ? X
g] MonthCalendar
Form1.cs Свойства файла
Notifylcon
[Tg NumericUpDown
□ Дополнительно ж
Q PictureBox Действие при сбо Компилировать I
ProgressBar Копировать в вых Не копировать
О RadioButton Пользовательски w
Sa RichTextBox
Дополнительно
В TextBox ж
Рис. 2.6
Простая программа на C# §18
В верхней части окна Visual Studio (см. рис. 2.6) расположены меню
и панель инструментов — кнопки для быстрого вызова команд. В ра
бочей области размещаются несколько окон, их состав может менять
ся. Мы будем использовать следующие окна:
• Панель элементов;
• Обозреватель решений;
• Свойства;
• Конструктор формы;
• окно исходного кода.
На Панели элементов размещены готовые объекты (кнопки, поля
ввода, списки и т. п.), которые можно добавлять на формы с помощью
мыши.
Обозреватель решений позволяет выбрать для редактирования любой
файл проекта (решения).
Вся программа на C# состоит из объектов. Их свойства настраива
ются с помощью окна Свойства. В нём две основные вкладки:
* Гл! Свойства, где можно изменить общедоступные свойства объ-
-T=J екта,
• ж События, где из списка подходящих методов выбираются об-
работники событий, связанных с объектом.
Форма — это тоже объект в программе. На форме можно распола
гать другие объекты, обеспечивающие интерфейс с пользователем, —
кнопки, поля ввода, метки и т. п. Они называются компонентами
или элементами.
После двойного щелчка мышью по названию файла Forml. cs в окне
Обозреватель решений открывается Конструктор формы. В этом ре
жиме можно мышью перетаскивать компоненты с Панели элементов
на форму, изменять их размеры и расположение, настраивать свойст
ва. Таким образом, интерфейс программы полностью строится с помо
щью мыши.
При нажатии клавиши F7 мы увидим в текстовом редакторе содер
жимое файла Forml.cs, где хранятся обработчики событий формы1}:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using Systern.Windows.Forms;
namespace WindowsFormsAppl {
public partial class Forml: Form {
С помощью сочетания клавиш Shift+F7 можно перейти обратно в конструктор
формы.
157
2 Программирование на языках C++ и C#
public Forml () {
InitializeComponent();
}
}
}
Сначала с помощью команды using подключаются пространства
имён среды .NET (вспомните подключение пространства имён std в
C++). Например, если убрать строку
using System.Windows.Forms;
то вместо Form придётся писать
System.Windows.Forms.Form,
полностью указывая пространство имён, в котором определён класс
Form.
Затем вводится пространство имён проекта с именем WindowsFormsAppl
(такое имя имеет проект по умолчанию):
namespace WindowsFormsAppl {
}
В этом пространстве имён определяется новый класс Forml, который
является наследником базового класса Form:
public partial class Forml: Form {
}
Описатель public обозначает, что к объектам этого класса могут
обращаться и другие классы, а описатель partial указывает на то,
что класс Forml описывается по частям в разных файлах.
Единственный метод, определённый в классе Forml, — это конструк
тор, который сводится к вызову процедуры InitializeComponent:
public Forml() { InitializeComponent(); }
Процедура InitializeComponent устанавливает свойства формы и
расположенных на ней элементов. Она находится во втором файле,
связанном с формой, — Forml. Designer. cs, который строится авто
матически. Менять его вручную не рекомендуется, но посмотреть инте
ресно и полезно.
Основная программа (точка входа) расположена в файле Program.cs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace WindowsFormsAppl {
static class Program {
158
Простая программа на C# §18
Свойства объектов
Панель Свойства позволяет просматривать и изменять свойства и
обработчики событий для выделенного объекта, например для формы
(рис. 2.7). _____________________
Свойства ▼ X
Forml System.Windows.Forms.Form
S Padding 0; 0; 0; 0
RightToLeft No
RightToLeftLayout False
Showicon True
ShowInTaskbar True
Size 351; 213
Width 351
Height 213 I
SizeGripStyle Auto
Star Position WindowsDefaultLocatiU
Size
Размер данного элемента управления
в пикселях.
Рис. 2.7
Отметим, что цикл обработки сообщений скрыт внутри метода Run, так что здесь
тоже использован принцип инкапсуляции (скрытия внутреннего устройства).
159
2 Программирование на языках C++ и C#
Обработчики событий
160
Простая программа на C# §18
Свойства ~ х
MainForm System.Windows.Forms.Form
si!®
DragLeave
DragOver
EnabledChanged
Enter
FontChanged
ForeColorChanged
FormClosed_______
FormClosin*
GiveFeedback
HelpButtonClicked
HelpRequested
FormClosing
Возникает при каждом завершении работы
с формой до того, как форма была закрыта...
Рис. 2.8
}
Это закрытый метод (private), который может вызывать только сама
форма.
Как видно из заголовка процедуры, в обработчик передаются два
параметра:
• sender — ссылка на объект, от которого пришло сообщение о
событии (в данном случае такой объект — это сама форма);
• е — изменяемый объект, с помощью которого можно разрешить
или запретить закрытие формы.
Установив значение поля е.Cancel равным true («да»), мы отме
няем команду на закрытие формы. Значение false, которое записа
но по умолчанию, разрешает закрыть форму и завершить работу про
граммы.
161
2 Программирование на языках C++ и C#
Для того чтобы решить вопрос о закрытии окна, нужно задать поль
зователю вопрос и получить на него ответ. В библиотеке C# есть класс
MessageBox, который выводит на экран диалоговое окно с нескольки
ми кнопками (рис. 2.9) и возвращает ответ пользователя — код нажа
той кнопки.
Подтверждение S3
I Да I I Нет I
Рис. 2.9
162
Простая программа на C#
Выводы
• Для быстрой разработки программ применяют системы визуаль
ного программирования, в которых интерфейс строится без «руч
ного» написания программного кода. Как правило, в них исполь
зуется объектно-ориентированный стиль программирования.
• Программы на языке C# компилируются не в коды команд про
цессора, а в программы на промежуточном языке CIL. Программу
на языке CIL выполняет виртуальная машина среды .NET.
• Проект — это набор файлов, которые используются для сборки
исполняемой программы (файла с расширением .ехе).
• В программе с графическим интерфейсом может быть несколько
окон, которые называют формами. С каждой формой связаны два
файла, один из которых (. Designer. cs) строится автоматически.
• Готовые объекты, которые можно использовать в программе (в том
числе кнопки, поля ввода, надписи и т. п.), называются компо
нентами или элементами.
• Интерфейс программы на C# можно строить с помощью мыши.
• В окне Свойства можно изменить свойства выделенного объекта
и назначить обработчики связанных с объектом событий.
Вопросы и задания
©
1. Какие причины сделали необходимым создание сред быстрой разра
ботки программ? В чём достоинства этих сред?
2. Чем отличается разработка программ в RAD-среде от использования
классических сред программирования?
3. Какие достоинства и недостатки, на ваш взгляд, имеют программы,
написанные для среды .NET?
4. В каком смысле используется термин «проект» в программирова
нии?
5. Из каких файлов состоит проект на языке С#?
6. Сравните термины «проект» и «решение» {solution).
7. Что такое форма? Почему для описания формы в Visual Studio
используются два файла?
163
2 Программирование на языках C++ и C#
Интересные сайты
visualstudio.com/ru/vs/community/ — бесплатная среда разработки
Visual Studio Community
docs.microsoft.com/ru-ru/dotnet/csharp/ — руководство по языку C#
от компании Microsoft
bit.ly/2HnSmJo — учебный курс по языку C# от компании Micro
soft
§19
Использование компонентов
Ключевые слова'.
• компонент • поле ввода
• свойство • метка
• событие • статический метод
• обработчик события • обработка ошибок
• родительский объект • исключение
• дочерний объект
164
Использование компонентов §19
Кнопка
Button
Рисунок
PictureBox
Рис. 2.10
165
2 Программирование на языках C++ и C#
Рис. 2.11
Размеры и расположение компонентов изменяются с помощью
мыши. Имена компонентов (свойства Name) тоже можно заменить на
более понятные, например на openBtn и sizeCb.
Если теперь заглянуть внутрь файла Forml. Designer. cs, мы уви
дим, что в состав класса MainForm добавлены новые элементы:
private System.Windows.Forms.Panel panell;
private System.Windows.Forms.Button openBtn;
private System.Windows.Forms.CheckBox sizeCb;
а в методе InitalizeComponent они создаются:
this.panell = new System.Windows.Forms.Panel();
this.openBtn = new System.Windows.Forms.Button();
this.sizeCb = new System.Windows.Forms.CheckBox();
Слово this в методе формы — это ссылка на саму форму, а запись
this.panell говорит о том, что панель (как и другие компоненты)
принадлежит форме.
Компоненты на форме связаны отношениями «родитель — пото
мок». Главный объект — это форма MainForm. Она является роди
тельским объектом для панели panell, а панель — дочерним ком
понентом для формы. Это означает, что при перемещении формы
панель перемещается вместе с ней. Кроме того, «родитель» отвечает
за прорисовку всех дочерних компонентов на экране. В свою очередь,
панель — это родительский объект для кнопки openBtn и флажка
sizeCb.
Теперь добавим на форму специальный компонент Ei PictureBox,
который «умеет» выводить рисунки различных форматов. Для того
чтобы он заполнял всё свободное пространство формы (кроме панели),
для свойства Dock выберем значение Fill (по-английски — запол
нить).
166
Использование компонентов §19
Рис. 2.12
Остаётся запрограммировать два действия:
• выбор файла и его загрузку в компонент img;
• подгонку размера рисунка под размер формы (при включённом
флажке орепСЬ).
К счастью, для этого достаточно использовать готовые возможности
компонентов библиотеки языка С#.
В группе Диалоговые окна есть компонент для выбора файла, он
называется |у] OpenFileDialog. Перетащим его в любое место формы.
Это невизуальный компонент — его не будет видно во время выпол
нения программы. Поэтому он добавляется не на форму, а в нижнюю
часть окна конструктора.
Для краткости изменим название компонента на openDlg и очи
стим свойство FileName (имя файла по умолчанию). В свойство Filter
запишем такую строку:
"Рисунки|*.jpg;*.jpeg;*.gif;*.png;*.bmp"
Это фильтр для отбора файлов: файлы, не подходящие ни под одну из
масок, будут скрыты. В данном случае нас интересуют только файлы
с указанными расширениями. Слева от символа «|» записано название
этого фильтра (любой текст).
Выбор файла нужно выполнять тогда, когда пользователь щёлкнет
по кнопке openBtn. Щелчок по кнопке — это событие, которое назы
вается Click. Для того чтобы добавить обработчик этого события, нуж
но выделить кнопку, перейти на вкладку [jFj События в окне Свойст
ва и выполнить двойной щелчок в поле справа от названия события
Click. После этого откроется окно с шаблоном обработчика, который
был создан автоматически. Тело процедуры-обработчика пока пустое.
Запишем в него команды
if( openDlg.ShowDialog() == DialogResult.OK )
img.Image = new Bitmap( openDlg.FileName );
В этих двух строках происходит довольно много операций, которые
скрыты за вызовами готовых методов:
167
2 Программирование на языках C++ и C#
168
Использование компонентов
Рис. 2.13
На форме расположены:
• три поля ввода (в них пользователь может задать значения со
ставляющих цвета в модели RGB);
• прямоугольная панель (компонент Panel), цвет которой изменяет
ся согласно введённым значениям;
• несколько меток (компонентов Label).
Метки — это надписи, которые пользователь не может редакти
ровать. Однако из программы можно изменять их содержимое через
свойство Text.
Для того чтобы пользователь не мог изменить размеры окна про
граммы (в данной задаче это не нужно!), мы установим свойство фор
мы FormBorderStyle (по-английски — стиль границы формы), равное
FixedSingle (фиксированная, одиночная).
169
2 Программирование на языках C++ и C#
170
Использование компонентов §19
Обработка ошибок
Если в предыдущей программе пользователь введёт не цифры, а другие
символы (или пустую строку), программа выдаст сообщение о необра
ботанной ошибке и предложит завершить работу. Хорошая программа
никогда не должна завершаться аварийно. Для этого все ошибки, ко
торые можно предусмотреть, надо обрабатывать в программе.
В современных языках программирования для обработки ошибок
применяют механизм исключений. В языке C# все «опасные» участки
кода (на которых может возникнуть ошибка) нужно поместить в блок
try — catch:
try {
// "опасные" команды
}
catch {
// обработка ошибки
}
Слово try в переводе с английского означает «попытаться», catch —
«поймать». В данном случае мы ловим исключения (исключительные
или ошибочные, непредвиденные ситуации). Программа выполнит блок
catch только тогда, когда между try и catch произойдёт ошибка.
171
2 Программирование на языках C++ и C#
172
Использование компонентов §19
Выводы
• Интерфейс программы строится с помощью мыши из готовых ком
понентов, которые размещены на Панели элементов.
• Свойства компонентов изменяются с помощью панели Свойства.
На вкладке События можно установить обработчики событий для
выделенного компонента.
• Родительский компонент отвечает за перемещение и вывод на
экран всех дочерних компонентов.
• Невизуальные компоненты, например диалог выбора файла, не
изображаются на форме.
• Поля ввода позволяют пользователю вводить данные.
• Метки — это надписи на форме, которые не может изменить
пользователь. Текст метки можно изменять из программы.
• Для нескольких компонентов можно установить один и тот же
обработчик события. Параметр sender, который получает каждый
обработчик, — это ссылка на объект-источник события.
• Статический метод (метод класса) — это метод, для вызова кото
рого не нужно создавать объект класса.
• Для обработки ошибок в современных программах используют
механизм исключений.
Вопросы и задания
1. Что такое компоненты? Зачем они нужны?
2. Объясните, как связаны компоненты и идея инкапсуляции.
3. Что такое родительский объект? Что такое дочерний объект?
4. Объясните роль свойства Dock в размещении элементов на форме.
5. Назовите основное свойство флажка. Как его использовать?
6. Что такое стандартный диалог? Как его использовать?
7. Объясните, что такое сложное свойство (на примере свойства Size
или Font).
8. Какой шрифт устанавливается для компонента по умолчанию?
9. Сравните свойства метки и поля ввода.
10. Как можно установить один обработчик события для нескольких
компонентов? Как в этом обработчике выяснить, какой компонент
был источником события?
11. Зачем в программе из параграфа используются методы int. Parse и
Color.FromArgb?
12. Как обрабатываются ошибки в современных программах? В чём, на
ваш взгляд, преимущества и недостатки такого подхода?
173
2 Программирование на языках C++ и C#
§20
Создание новых классов
Ключевые слова'.
• модуль • компонент
• модель • статический класс
• представление • методы класса
• класс • свойство
В этом параграфе мы создадим две программы, в которых используют
ся модули — файлы, содержащие функции и описания новых классов
объектов.
Сначала построим программу, которая вычисляет арифметическое
выражение, записанное в символьной строке (см. § 11).
174
Создание новых классов §20
Новый класс
Создадим новый проект Stringcalculator. Для того чтобы выделить
модель задачи в отдельный файл, нужно построить новый класс. Ко
нечно, возникает вопрос: почему нельзя сделать отдельный модуль, со
держащий только функции, не относящиеся к какому-то классу? Но
это действительно сделать невозможно, потому что в языке C# любая
функция обязательно должна быть методом какого-то класса.
Выберем в меню Visual Studio пункт Проект — Добавить класс,
введём в диалоговом окне название класса — Calculator. После этого
в окне Обозреватель решений в списке файлов появляется новый файл
Calculator.cs (рис. 2.14).
Если открыть его двойным щелчком мышью, мы увидим заготовку,
построенную программой автоматически:
using System;
using System.Collections.Generic;
using System.Text;
namespace Stringcalculator
{
class Calculator
{
}
}
175
Программирование на языках C++ и C#
Обозреватель решений ▼ Д X
Л ’ т© - с ® ©
Обозреватель решений — поиск (Ctrl+;) fi *
I> Ссылки
I* 0Р Calculator.cs
Ц=] Form1.cs
l3 Form1.Designer.cs
Ш Forml.resx
t* С* Program.cs
Рис. 2.14
Методы класса
В класс Calculator мы поместим все функции, которые используются
для вычисления выражения (см. § 11):
static class Calculator
{
public static int calc( string expr ) {
}
static int lastOp( string expr ) {
176
Создание новых классов §20
}
static int doOperation(char op, int nl, int n2) {
}
}
В этом объявлении интересны два момента. Во-первых, все функции
объявлены с описателем static, поскольку все методы статического
класса Calculator должны быть статическими, т. е. не привязанны
ми к конкретному объекту. Статические методы называются методами
класса (в отличие от методов объектов).
Во-вторых, для метода calc указан дополнительный описатель
public. Такой метод будет доступен не только в классе Calculator,
но и вне класса, например в методах главной формы. А остальные ме
тоды — закрытые (private), их могут вызывать только методы самого
класса Calculator, в частности метод calc.
Напишем сначала код метода calc:
public static int calc ( string expr )
{
int pos = lastOp ( expr ); // (1)
if ( pos < 0 )
return int.Parse( expr ); // (2)
int nl = calc ( expr.Substring (0, pos) ); // (3)
int n2 = calc ( expr.Substring ( pos + 1 ) ); // (4)
return doOperation( expr[pos], nl, n2 ); // (5)
}
Обратите внимание, что функция calc — рекурсивная, она дважды
вызывает сама себя (в строках 3 и 4).
В строке 1 вызывается функция lastOp, которая возвращает
позицию последней выполняемой операции. Если эта функция вернула
-1 («операция не найдена»), в строке 2 преобразуем всю символьную
строку в число с помощью метода Parse и возвращаем это число как
результат функции.
Строки 3-5 обрабатывают случай, когда операция найдена. В этом
случае вычисляем значения выражений слева и справа от знака опе
рации, а потом выполняем с этими значениями нужную операцию,
вызвав функцию doOperation.
Для того чтобы выделить подстроку из строки, применяется метод
Substring класса string. Первый аргумент этой функции — началь
ная позиция, второй — длина подстроки. Вызов
expr.Substring( 0, pos )
возвращает подстроку строки expr, которая начинается с символа
ехрг[0] и имеет длину pos. Если второй аргумент не задан, метод
177
2 Программирование на языках C++ и C#
178
Создание новых классов §20
Рис. 2.15
В верхней части окна находится выпадающий список — компонент
СошЬоВох. Под ним размещено поле вывода результата — многостроч
ный редактор текста. Это компонент TextBox, у которого установлено
свойство Multiline, равное True.
Пользователь вводит выражение в окне выпадающего списка. При на
жатии на клавишу Enter выражение вычисляется и его результат вы
водится в последней строке поля вывода. Кроме того, каждое новое вы
ражение добавляется в верхний выпадающий список. С помощью этого
списка можно вернуться к введённому ранее выражению и исправить его.
Добавляем компонент СошЬоВох на форму, назовём его input (в пе
реводе с английского — ввод). Чтобы прижать список к верхней гра
нице окна, установим свойство Dock равным Тор («прижать вверх»).
Для компонента TextBox выбираем выравнивание Fill (заполнить всю
свободную область), имя answers (в переводе с английского — ответы)
и свойство Multiline равное True («да», это многострочный редактор).
Чтобы пользователь не мог изменять поле вывода, у компонента
answers устанавливаем логическое свойство Readonly (в переводе с ан
глийского — только для чтения), равное True («да»). При этом фон
компонента станет серым. Если вам это не нравится, нужно изменить
свойство BackColor.
Логика работы программы может быть записана в виде псевдокода:
if( нажата клавиша Enter ) {
х = значение выражения
добавить результат в конец поля вывода
if( выражения нет в списке )
добавить его в список
}
Перехватим нажатие клавиши Enter с помощью обработчика собы
тия KeyPress компонента input. Клавиша Enter имеет код 13, поэто
му условие «если нажата клавиша Enter» запишется так:
if( е.KeyChar == (char) 13 ) {
179
2 Программирование на языках C++ и C#
180
Создание новых классов §20
Рис. 2.16
Для формы запретим изменение размеров: установим свойство
FormBorderStyle равным FixedSingle.
Теперь создадим новый компонент — наследник класса TextBox.
Для этого нужно добавить к проекту новый класс: выбрать пункт меню
Проект — Добавить класс и ввести имя этого класса — IntTextBox.
После этого в окне обозревателя решений появится новый исходный
файл — IntTextBox.cs.
181
2 Программирование на языках C++ и C#
Рис. 2.17
182
Создание новых классов §20
говорит о том, что этот метод будет доступен всем наследникам класса
IntTextBox и недоступен для остальных классов.
В последней строке вызывается метод OnKeyPress базового класса:
base.OnKeyPress (е);
Здесь base — это служебное слово языка С#, которое используется
для обращения к базовому классу из методов класса-наследника. Мы
вызываем метод базового класса для того, чтобы не отключить дей
ствия, которые там выполняются (и о которых мы не знаем!).
Теперь добавим в описание класса новое свойство:
public int Value
{
set { Text = value.ToString(); }
get {
try { return int.Parse ( Text ); }
catch { return 0; }
}
}
В первой строке объявляется свойство с именем Value. Значение
свойства — это целое число (int). Свойство будет доступно всем объ
ектам, на это указывает слово public.
Свойство в C# определяется двумя методами — «сеттером», который
всегда называется set, и «геттером», за ним закреплено имя get.
Метод set служит для изменения значения свойства. Новое значе
ние, которое всегда называется value (все буквы строчные!), преобра
зуется в символьную строку и сразу копируется в свойство Text наше
го компонента.
В методе get (чтение значения свойства) пытаемся преобразовать
строку в целое число1’, и в случае неудачи возвращаем 0.
Вот полное описание нового класса:
class IntTextBox: TextBox
{
protected override void OnKeyPress(
KeyPressEventArgs e)
{
if ( ! (Char.IsDigit( e.KeyChar )
I I e.KeyChar == (char) 8))
e.Handled = true;
base.OnKeyPress( e );
}
1J Здесь можно использовать также метод int. TryParse, который позволяет обна
ружить ошибку при таком преобразовании без использования исключений. Его
описание вы можете найти в литературе или в Интернете.
183
2 Программирование на языках C++ и C#
Выводы
• Для того чтобы объединить несколько функций в отдельный
модуль, нужно создать статический класс. В языке C# нельзя
создавать функции, не относящиеся к какому-то классу.
• Статический класс — это класс, который содержит только мето
ды, у него нет данных. Объекты такого класса не создаются.
• Все методы статического класса должны быть статическими. Их
нужно вызывать с помощью точечной записи, но вместо имени
объекта указывать имя класса.
• Новые классы компонентов обычно строятся на основе существую
щих (как классы-наследники).
• При сборке программы новые компоненты автоматически добавля
ются в Панель элементов. После этого с ними можно работать
так же, как и со стандартными компонентами.
184
Создание новых классов §20
Вопросы и задания
1. Что такое пространство имён? Как пространства имён использу
ются для связи между модулями программ?
2. Что означает описатель static при объявлении классов и мето
дов?
3. Почему в классе Calculator из параграфа только функция calc
объявлена с описателем public?
4. Зачем используют свойство Readonly?
5. Как в C# преобразовать числовое значения в текстовое и обратно?
Как перевести число в шестнадцатеричную систему счисления?
6. Проект. Измените программу Stringcalculator из параграфа
так, чтобы она вычисляла выражения с вещественными числами.
Для перевода вещественных чисел из символьного вида в число
вой можно использовать метод double. Parse.
7. Проект. Добавьте в программу Stringcalculator из парагра
фа обработку ошибок. Подумайте, какие ошибки может сделать
пользователь. Какие ошибки могут возникнуть при вычислениях?
Как их обработать?
8. Изучите исходные тексты файлов программы Stringcalculator из
параграфа. Сделайте предположение, как связаны форма и класс
Calculator (откуда форма «знает» такой класс и его методы).
9. Пусть требуется изменить программу Stringcalculator из пара
графа так, чтобы она обрабатывала выражения со скобками. Что
нужно изменить: модель, интерфейс или и то, и другое?
*10.Проект. Измените программу Stringcalculator из параграфа
так, чтобы она вычисляла выражения со скобками. Подсказка:
нужно искать последнюю операцию с самым низким приорите
том, стоящую вне скобок.
**11.Проект. Измените программу из предыдущего задания так, что
бы в выражении можно было использовать функции abs, sin,
cos, sqrt.
12. Проект. Постройте программу «Калькулятор»
для выполнения вычислений с целыми числами 1 ....... J
(см. рисунок справа). 0000
13. Проект. Постройте программу «Калькулятор» 00ВИ
для выполнения вычислений с вещественными
числами. гамгага
00BQ
185
2 Программирование на языках C++ и C#
Интересные сайты
csharp.net-informations.com — справочник по языку C#
tutorialspoint.com/csharp/ — онлайн-учебник по C#
rextester.com — онлайн-компилятор C#
186
ЗАКЛЮЧЕНИЕ
Язык Python
1. Доусон М. Программируем на Python: Пер. с англ. — СПб.: Питер,
2014.
2. Мэтиз Э. Изучаем Python. Программирование игр, визуализация
данных, веб-приложения.: Пер. с англ. — СПб.: Питер, 2017
3. Любанович Б. Простой Python. Современный стиль программирова
ния.: Пер. с англ. — СПб.: Питер, 2016.
187
Заключение
Язык C++
1. Страуструп Б. Программирование: принципы и практика с исполь
зованием C++, 2-е изд.: Пер. с англ. — М.: ООО «И. Д. Вильямс»,
2016.
2. Липпман С. Б., Лажойе Ж., Му Б. Э. Язык программирования C++.
Базовый курс, 5-е изд.: Пер. с англ. — М.: ООО «И. Д. Вильямс»,
2014.
3. Шилдт Г. C++. Базовый курс, 3-е изд.: Пер. с англ. — М.: ООО
«И. Д. Вильямс», 2010.
4. Лафоре Р. Объектно-ориентированное программирование в C++, 4-е
изд.: Пер. с англ. — СПб.: Питер, 2004.
5. Мюссер Д., Дердж Ж., Сейни А. C++ и STL: справочное руководство,
2-е изд.: Пер. с англ. - М.: ООО «И. Д. Вильямс», 2010.
Язык C#
1. Дрейер М. C# для школьников: Учебное пособие: Пер. с англ. —
М.: Интернет-Университет Информационных Технологий; БИНОМ.
Лаборатория знаний, 2009.
2. Хейлсберг А., Торгерсен М., Вилтамут С., Голд П. Язык програм
мирования С#. Классика Computers Science. 4-е изд.: Пер. с англ. —
СПб.: Питер, 2012.
3. Троелсен Э. Язык программирования C# 5.0 и платформа .NET 4.5,
6-е изд.: Пер. с англ. — М. : ООО «И. Д. Вильямс», 2013.
4. Шилдт Г. C# 4.0. Полное руководство: Пер. с англ. — М.: ООО
«И. Д. Вильямс», 2011.
188
ОГЛАВЛЕНИЕ
Предисловие........................................................................................................ 3
Глава 1. Программирование на языке Python......................................... 5
§1 . Что такое ООП?................................................................................... 5
Проблема сложности программ........................................................... 5
Процедурное программирование........................................................... 5
Объектный подход.................................................................................. 6
Взаимодействие объектов..................................................................... 7
Выводы.......................................................................................................8
§2 . Модель задачи: классы и объекты................................................ 8
Объектно-ориентированный анализ..................................................... 9
«Торпедная атака».................................................................................. 9
Объекты и классы.................................................................................. 9
Классы объектов в игре........................................................................10
Взаимодействие объектов................................................................... 11
Выводы.....................................................................................................12
§3 . Классы и объекты в программе................................................... 13
Объявление класса................................................................................ 13
Поля класса............................................................................................ 14
Конструктор............................................................................................ 15
Конструктор с параметрами............................................................... 16
Данные класса...................................................................................... 17
Методы..................................................................................................... 17
Анимация.............................................................................................. 18
Строим флотилию.................................................................................. 19
Выводы..................................................................................................... 19
§4 . Скрытие внутреннего устройства................................................ 21
Зачем это делать?.................................................................................. 21
Скрытие полей...................................................................................... 22
Доступ к полям через методы........................................................... 23
Свойства (property)................................................................................ 24
Меняем внутреннее устройство........................................................... 25
Свойство «только для чтения»........................................................... 26
Выводы.....................................................................................................28
189
Оглавление
§5 . Иерархия классов............................................................................. 29
Наследование.......................................................................................... 29
Иерархия объектов в игре................................................................. 30
Базовый класс........................................................................................ 31
Доступ к полям.................................................................................... 34
Выводы.....................................................................................................35
§6 . Классы-наследники (I).................................................................... 37
Классы-наследники................................................................................ 37
Пульсар...................................................................................................39
Полиморфизм........................................................................................ 40
Выводы.....................................................................................................41
§7 . Классы-наследники (II).................................................................... 42
Подвижные объекты............................................................................ 42
Космические корабли............................................................................ 44
Модуль с классами................................................................................ 46
Выводы.....................................................................................................48
§ 8. Событийно-ориентированное программирование...................... 48
Особенности современных прикладных программ..........................49
Программы с графическим интерфейсом........................................ 51
Простейшая программа........................................................................52
Свойства формы.................................................................................... 52
Обработчик события.............................................................................. 53
Выводы..................................................................................................... 54
§9 . Использование компонентов (виджетов)................................... 55
Программа с компонентами............................................................... 55
Новый класс: всё в одном................................................................... 60
Ввод и вывод данных..........................................................................61
Обработка ошибок................................................................................ 64
Выводы.....................................................................................................66
§ 10. Создание компонентов.................................................................... 67
Зачем это нужно?.................................................................................. 67
Компонент для ввода целых чисел................................................... 68
Программа с новым компонентом..................................................... 69
Выключатель с рисунком................................................................... 70
Перехват щелчка мышью................................................................... 72
Новое свойство onChange..................................................................... 73
Составной компонент............................................................................ 74
Выводы..................................................................................................... 76
§11 . Модель и представление.................................................................. 77
Зачем это нужно?.................................................................................. 77
Вычисление арифметических выражений: модель........................78
Представление........................................................................................ 80
Выводы.....................................................................................................82
190
Оглавление §16.
191
Оглавление
192
Поле ввода rEdit
TEdit TEdit
Флажок Панель
PictureBox