Обучение программированию. Язык Pascal: Н. Л. Тарануха, Л. С. Гринкруг, А. Д. Бурменский, С. В. Ильина — Санкт-Петербург, Солон-Пресс, 2009 г.- 384 Л. Б. Бузюков, О. Б. Петрова «Современные методы программирования на языках С и С++», Учебное пособие, СПб.




МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук


«УТВЕРЖДАЮ»


Заведующий кафедрой
_ информационной безопасности______






______________ _Корнюшин П. Н.____
(подпись)


«__1__»__сентября______2012__г.


УЧЕБНО-МЕТОДИЧЕСКИЙ КОМПЛЕКС ДИСЦИПЛИНЫ
Технология программирования
090104.65 – Комплексная защита объектов информатизации
Форма обучения - очная

Школа естественных наук
Кафедра информационной безопасности
курс ___3____ семестр __5______
лекции _34__ час.
практические занятия___-__час.
семинарские занятия____-____час.
лабораторные работы__17___час.
курсовая работа ____-______час.
аудиторная нагрузка___51_____ час.
самостоятельная работа студента __49__ час.
реферативные работы (количество)___-___
контрольные работы (количество) ___-___
зачет ____5_______ семестр
экзамен_____-_____семестр

Учебно-методический комплекс дисциплины составлен на основании требований государственного образовательного стандарта высшего профессионального образования № 331 от 14 апреля 2000 года.
Учебно-методический комплекс дисциплины обсужден на заседании кафедры информационной безопасности «_7__» ____июня___2012 г.
Заведующий кафедрой___д.ф-м..н.профессор Корнюшин_П.Н.
Составитель:_ к.ф.-м.н., доцент_Дзенскевич Е.А.

Аннотация учебно-методического комплекса дисциплины «Технология программирования»
Учебно-методический комплекс дисциплины «Технология программирования» разработан для студентов 3_ курса по специальности 090104.65 «Комплексная защита объектов информатизации» в соответствии с требованиями ГОС ВПО по данной специальности.
Дисциплина «Технология программирования» относится к дисциплинам специализации.
Общая трудоемкость освоения дисциплины 100 часов. Учебным планом предусмотрены лекционные занятия (34 час), лабораторная работа студента (17 час.), самостоятельная работа студента (49 часа). Дисциплина реализуется на 3_ курсе в 5_ семестре.
Содержание курса охватывает следующий круг вопросов: парадигмы программирования, автоматное программирование, объектно ориентированное программирование, классы, описание классов, конструктор, деструктор, многопоточное программирование, параллельное программирование.
Дисциплина «Технология программирования» логически и содержательно связана с такими курсами, как «Высшая математика»; «Физика»; «Информатика»; «Теоретические основы компьютерной безопасности».

Автор-составитель учебно-методического комплекса кафедры Информационной безопасности Школы Естественных наук ДВФУ к.ф.-м.н., доцент Дзенскевич Е.А

Зав.кафедрой информационной безопасности д.ф.-м.н.профессор __ Корнюшин П. Н.


Содержание
13 TOC \o "1-3" \h \z \u 1413 LINK \l "_Toc360174808" 14АННОТАЦИЯ 13 PAGEREF _Toc360174808 \h 1421515
13 LINK \l "_Toc360174809" 14РАБОЧАЯ ПРОГРАММА УЧЕБНОЙ ДИСЦИПЛИНЫ 13 PAGEREF _Toc360174809 \h 1441515
13 LINK \l "_Toc360174810" 14КОНСПЕКТЫ ЛЕКЦИЙ 13 PAGEREF _Toc360174810 \h 14171515
13 LINK \l "_Toc360174811" 14МАТЕРИАЛЫ ДЛЯ ПРАКТИЧЕСКИХ ЗАНЯТИЙ 13 PAGEREF _Toc360174811 \h 142701515
13 LINK \l "_Toc360174812" 14МАТЕРИАЛЫ ДЛЯ ОРГАНИЗАЦИИ САМОСТОЯТЕЛЬНОЙ РАБОТЫ СТУДЕНТОВ 13 PAGEREF _Toc360174812 \h 143211515
13 LINK \l "_Toc360174813" 14КОНТРОЛЬНО ИЗМЕРИТЕЛЬНЫЕ МАТЕРИАЛЫ 13 PAGEREF _Toc360174813 \h 143231515
13 LINK \l "_Toc360174814" 14СПИСОК ЛИТЕРАТУРЫ 13 PAGEREF _Toc360174814 \h 143461515
13 LINK \l "_Toc360174815" 14ГЛОССАРИЙ 13 PAGEREF _Toc360174815 \h 143491515
15


МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук

«УТВЕРЖДАЮ»


Заведующий кафедрой
_ Информационной безопасности______






______________ _Корнюшин П. Н.____
(подпись)


«__1__»__сентября______2012__г.





РАБОЧАЯ ПРОГРАММА УЧЕБНОЙ ДИСЦИПЛИНЫ
Технология программирования
090104.65 «Комплексная защита объектов информатизации»
Форма обучения - очная
Школа естественных наук
Кафедра информационной безопасности
курс ___3____ семестр __5______
лекции _34__ час.
практические занятия___-__час.
семинарские занятия____-____час.
лабораторные работы__17___час.
курсовая работа ____-______час.
аудиторная нагрузка___51_____ час.
самостоятельная работа студента __49__ час.
реферативные работы (количество)___-___
контрольные работы (количество) ___-___
зачет ____5_______ семестр
экзамен_____-_____семестр

Рабочая программа составлена на основании требований государственного образовательного стандарта высшего профессионального образования № 331 от 14 апреля 2000 года.
Рабочая программа обсуждена на заседании кафедры информационной безопасности «_7__» ____июня___2012 г.
Заведующий кафедрой___д.ф.-м.н.профессор Корнюшин_П.Н.
Составитель:_ к.ф.-м.н., доцент_Дзенскевич Е.А





Оборотная сторона титульного листа РПУД

I. Рабочая программа пересмотрена на заседании кафедры:
Протокол от «__» _______________20 г. № ______
Заведующий кафедрой _______________________ __________________
(подпись) (И.О. Фамилия)







II. Рабочая программа пересмотрена на заседании кафедры:
Протокол от «_____» _________________ 20 г. № ______
Заведующий кафедрой _______________________ __________________
(подпись) (И.О. Фамилия)

Аннотация
Дисциплина относится к информационному и естественнонаучному циклу учебного плана.
Изучение дисциплины «Технология программирования» способствует формированию у обучающегося логического мышления, воспитанию научного подхода к постановке и решению задач, связанных с разработкой надежных программных средств в конкретных предметных областях; формированию общей технической культуры будущего специалиста.
Для освоения данной дисциплины студент должен владеть знаниями, полученными в результате изучения следующих дисциплин:
«Математика»: основы алгебры и геометрии, основные понятия о векторах и векторных пространствах, основные понятия о матрицах, основы дифференциального и интегрального исчисления, теория вероятностей и математическая статистика.
«Информатика»: понятие информации, алгоритма, свойства алгоритмов, основные алгоритмы типовых численных методов решения математических задач, языки и системы программирования, программные средства общего назначения. Знания и умения, полученные в результате освоения данной дисциплины, используются для изучения следующих дисциплин: «Управление данными», «Технологии обработки информации», «Методы и средства проектирования информационных систем и технологий».

Цели:
владение культурой мышления, способность к обобщению, анализу, восприятию информации, постановке цели и выбору путей ее достижения, умение логически верно, аргументировано и ясно строить устную и письменную речь;
понимание социальной значимости своей будущей профессии, обладание высокой мотивацией к выполнению профессиональной деятельности;
умение применять методы и средства познания, обучения и самоконтроля для интеллектуального развития, повышения культурного уровня, профессиональной компетенции, сохранения своего здоровья, нравственного и физического самосовершенствования;
владение широкой общей подготовкой (базовыми знаниями) для решения практических задач в области информационных систем и технологий;
Задачи:
готовность использовать основные законы естественнонаучных дисциплин в профессиональной деятельности, применять методы математического анализа и моделирования, теоретического и экспериментального исследования;
способность проводить предпроектное обследование объекта проектирования, системный анализ предметной области, их взаимосвязей;
способность оценивать надежность и качество функционирования объекта
проектирования;
способность разрабатывать средства реализации информационных технологий (методические, информационные, математические, алгоритмические, технические и программные);
готовность использовать математические методы обработки, анализа и синтеза результатов профессиональных исследований.
В результате освоения дисциплины обучающийся должен демонстрировать следующие результаты образования:
Знать:
общие понятия формализованного описания процесса обработки данных, и различия между технологией программирования, программной инженерией и методологией программирований;
модель перевода информации из одной формы в другую и источники ошибок в программном средстве;
основные цели, задачи и перспективы производственного использования технологии программирования;
жизненный цикл, и общие принципы разработки программного средства;
основные подходы по созданию внешнего описания программного средства:
определение требований, спецификация качества, функциональная спецификация средства;
методы спецификации семантики функций;
классы архитектур программного средства:
методы разработки структуры программы и модульное программирование,
теорию алгоритмизации и методы разработки алгоритмов;
доказательства свойств программ и методы контроля этапов разработки компонент программного средства;
способы тестирования и отладки программного средства, включая документацию по применению и определению требований, способы обеспечения его функциональности, надежности и качества, способы управления его разработкой и аттестация;
критерии оценки надежности и качества программного средства.
Уметь:
определить требования к программному средству, включающие формулировку математической постановки предметной задачи и выбор метода ее решения, документально их закрепить их;
качественно и концептуально описывать процесс разработки программного средства для конкретной предметной задачи;
разработать спецификацию качества и функциональную спецификацию программного средства предметной задачи, установить степень его надежности и точность
выполняемых расчетов, сформировать документацию по применению и сопровождению программного средства;
построить модульную структуру программы предметной задачи, разработать алгоритмы модулей, закодировать их и провести тестирование и отладку, используя полученные теоретические знания по технологии программирования.
Владеть:
необходимым инструментарием технологии программирования математического и информационного плана для анализа предметной области, обоснования и создания программных средств для насущных ее задач, ориентированных на автоматизацию процессов в различных сферах деятельности человека;
общей подготовкой (базовыми знаниями) для решения практических задач в предметных областях средствами технологии программирования;
соответствующей культурой мышления, способствующей восприятию, анализу и обобщению информации по формулированию предметной задачи и ее реализации в форме программного средства с помощью подходов и методов технологии программирования.
i. СТРУКТУРА И содержание теоретической части курса
МОДУЛЬ 1. Введение в технологию программирования (10 час.)
Раздел 1. Технология программирования (8 час.)
Тема 1. Понятие и предмет технологии программирования (2 час.)
Тема 2. Проектирование программ. Рекомендации по проектированию программ (2 час.)
Тема 3. Эффективность, отладка, тестирование программ(2 час.)
Тема 4. Парадигмы программирования (2 час.)
Раздел 2. Автоматное программирование (2 час.)
Тема 1. Понятие автоматного программирования (2 час.)
МОДУЛЬ 2. Объектно ориентированное программирование (10 час.)
Раздел 1. Классы (10 час.)
Тема 1. Объекты и классы (2 час.)
Тема 2. Перегрузка функций и операций в языке С++ (2 час.)
Тема 3. Наследование (2 час.)
Тема 4. Полиморфизм (2 час.)
Тема 5. Обработка исключений (2 час.)
МОДУЛЬ 3. Многопоточное программирование (14 час.)
Раздел 1. Потоки (6 час.)
Тема 1. Простая многозадачность на уровне приложения (4 час.)
Тема 2. Семафоры (2 час.)
Раздел 2. Параллельное программирование (8 час.)
Тема 1. Многоядерные архитектуры (2 час.)
Тема 2. -THREADING (2 час.)
Тема 3. OMP (2 час.)
Тема 4. MPI (2 час.)

II. СТРУКТУРА И содержание практической части курса
Лабораторные работы (17 час.)
Лабораторная работа 1. Использование исключений C++ (4 час.)
Лабораторная работа 2. Разработка синтаксического анализатора текстов (4 час.)
Лабораторная работа 3. Диалоговые приложения. Обработка сообщений (4 час.)
Лабораторная работа 4. Простая многозадачность на уровне приложения. Потоки (3 час.)
Лабораторная работа 5. Тестирование программы (2 час.)


iii. контроль достижения целей курса
Возпросы к зачету
Основные этапы решения задач на ЭВМ
Способы записи алгоритмов
Стандартные типы данных
Представление основных структур программирования: итерация, ветвление, повторение
Понятие процедуры и типа данных, определяемых пользователем
Записи; файлы; динамические структуры данных
Списки: основные виды и способы реализации
Программирование рекурсивных алгоритмов
Надежное программное средство как продукт технологии программирования.
Программа как формализованное описание процесса обработки данных. программное средство
Надежность программного средства
Технология программирования как технология разработки надежных программных средств
Технология программирования и информатизация общества
Источники ошибок в программном средстве
Основные пути борьбы с ошибками
Общие принципы разработки программных средств
Специфика разработки программных средств
Жизненный цикл программного средства
Понятие качества программного средства
Внешнее описание программного средства. Назначение внешнего описания программного средства и его роль в обеспечении качества программного средства.
Определение требований к программному средству
Спецификация качества программного средства
Архитектура программного средства
Основные классы архитектур программных средств
Разработка структуры программы и модульное программирование
Разработка программного модуля
Структурное программирование
Свойства основных конструкций структурного программирования
Тестирование и отладка программного средства
Обеспечение функциональности и надежности программного средства
Обеспечение качества программного средства
Документирование программных средств
Аттестация программного средства
Объектный подход к разработке программных средств
Компьютерная поддержка разработки и сопровождения программных средств

IV. ТЕМАТИКА И ПЕРЕЧЕНЬ КУРСОВЫХ РАБОТ И РЕФЕРАТОВ
Учебным планом не предусмотрено.

V. УЧЕБНО-МЕТОДИЧЕСОЕ ОБЕСПЕЧЕНИЕ ДИСЦИПЛИНЫ
Основная литература
Технологии программирования C++: В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.
Дополнительная литература:
Васильев Б.К. Технология готовых решений в программировании: Учебное пособие. – Владивосток: Изд-во ВГУЭС, 2004.
Обучение программированию. Язык Pascal: Н. Л. Тарануха, Л. С. Гринкруг, А. Д. Бурменский, С. В. Ильина Санкт-Петербург, Солон-Пресс, 2009 г.- 384 с.
Программирование для Windows на C/C++. В 2 томах. Том 1: Н. Н. Мартынов Санкт-Петербург, Бином-Пресс, 2008 г.- 528 с.
Программирование для Windows на С/С++. В 2 томах. Том 2: Н. Н. Мартынов Москва, Бином-Пресс, 2009 г.- 480 с.
Профессиональное программирование. Системный подход: Игорь Одинцов Москва, БХВ-Петербург, 2006 г.- 624 с.

Интернет ресурсы:
[ Cкачайте файл, чтобы посмотреть ссылку ] Матрос Д.Ш. Теория алгоритмов: учебник / Д.Ш. Матрос, Г.Б. Поднебесова. - М.: БИНОМ. Л аборатория знаний, 2008. - 202 с. : ил. - (Педагогическое образование).
[ Cкачайте файл, чтобы посмотреть ссылку ] Л. Б. Бузюков, О. Б. Петрова «Современные методы программирования на языках С и С++», Учебное пособие, СПб. : Линk, 2008. – 288 с.
[ Cкачайте файл, чтобы посмотреть ссылку ] Немнюгин C.А. Средства программирования для многопроцессорных вычислительных систем: Учебно-методическое пособие. - СПб.: СПбГУ, 2007. - 88 с.






МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук



КОНСПЕКТЫ ЛЕКЦИЙ
по дисциплине «Технология программирования»
090104.65 «Комплексная защита объектов информатизации»























г. Владивосток 2012

Модуль 1. Введение в технологию программирования (10 час.)
Раздел 1. Технология программирования (8 час.)
Тема 1. Понятие и предмет технологии программирования (2 час.)
Цели и задачи: Изучить введение в технологию программирования.
Рассмотреть понятия основная цель программирования .
Учебные вопросы: Требования для программы. Основные источники сложности.
Учебная информация:
Основная цель программирования - производство программных услуг, т.е. программирование нацелено на обслуживание пользователя. Любое настоящее обслуживание руководствуется принципом: “Клиент всегда прав”. В приложении к программированию этот принцип означает, что программный продукт должен быть надежным, робастным, заботливым. Программы, отвечающие этим требованиям, называют дружественными по отношению к пользователю. Что означают эти свойства?
а) Надежность означает, что в программе должно быть мало ошибок. Понятие “малого числа ошибок “ зависит от назначения программы. Естественно, что такие программы обучения, например, языкам программирования или естественным языкам, и программа автопилота должны иметь совершенно разную степень надежности.
б) Слово работоспособность означает, что программа должна сохранять работоспособность в определенном диапазоне неблагоприятных условий, а именно:
- при ограниченных физических ресурсах компьютера (память, число процессоров, каналы и т.д.),
- при перегрузке системы (большие объемы данных, большое число одновременно работающих пользователей и т.д.),
при ошибках пользователей,
при сбоях аппаратуры.
в) Забота о пользователе программного продукта проявляется в том, что программа должна “уметь пояснять” свои действия и ошибки пользователя.
Когда производство программных услуг становится индустрией, появляется теория, обслуживающая эту индустрию. Эта теория называется технологией программирования. Другими словами, технология программирования13 XE "технология программирования" 15 рассматривает подходы и способы создания программных продуктов.
Во всех развитых странах отмечен бурный рост производства программных услуг. Но до сих пор это производство является достаточно дорогим и довольно долгим. А, кроме того, практически невозможно заранее гарантировать создание высококачественного результата. С чем это связано? В настоящее время ответ кратко можно сформулировать так: “ Основная проблема программирования - сложность. Можно надеяться на ее понижение только для определенных, освоенных классов задач”. [1]
Первый источник сложности. Современные компьютеры работают достаточно быстро и имеют потенциально неограниченную память. Причем сохраняется тенденция к увеличению памяти и быстродействия при фиксированной цене (примерно на порядок за десять лет) [1]. При таких условиях существует потенциальная возможность настроить компьютер на предоставление услуг из очень широкого класса (во всяком случае, границы этого класса не известны). Это программные услуги (программы) чрезвычайно далеки от элементарных возможностей компьютера, т. е. от системы команд, представляющих машинный язык, даже для ЭВМ последних поколений. Поэтому любые сколько-нибудь значимые услуги содержат большое число команд (например, программы управления летательными аппаратами или военными объектами содержат миллионы команд), таким программам приходится иметь дело с огромным количеством элементарных объектов (обрабатываемых данных) и огромным количеством связей между ними. Но, как известно, способности человека оперировать большим количеством связей и объектов ограничены. При оценке этих способностей обычно указывают так называемое “число ингве” ( Ingve number13 XE "Ingve number" 15) равное 7 ( 2 , то есть считается, что человек свободно работает с таким числом объектов и произвольным числом связей между ними. До тех пор, пока программирование будет оставаться предметом человеческой деятельности, с этим ограничением приходится считаться.
Таким образом, первый источник сложности можно определить как семантический разрыв между уровнем элементарных операций и требуемым уровнем программных услуг.
Занимаясь конкретным видом услуг (конкретным классом задач), стремятся:
- выделить в нем характерный именно для данного класса набор объектов и операций над ними,
- построить соответствующий исполнитель для выделенного набора операций ( аппаратный или программный ),
- затем программировать на данном исполнителе.
Фактически это означает создать адекватный выбранному классу услуг язык программирования [1]. На практике это самый распространенный способ борьбы со сложностью программирования и одновременно основная причина бурного роста проблемно - ориентированных языков и проблемно - ориентированных систем.
Второй источник сложности состоит в отсутствии у компьютера какой бы то ни было модели реального мира. В чем это проявляется ? Компьютер не в состоянии контролировать соответствие между выполняемыми действиями и теми целями, ради которых эти действия должны быть выполнены. Компьютер не в состоянии исправить или хотя бы информировать о таких действиях. Такой прогнозирующий контроль должна выполнять пользовательская программа, что опять ведет к увеличению числа объектов, связей и, как следствие, к возрастанию сложности программы. В некоторых ситуациях такой контроль обязателен (известен факт взрыва ракеты, летевшей к Венере, из-за ошибки в программе управления полетом ).
Со вторым источником сложности бороться гораздо труднее. Определенные надежды связывают с развитием моделей представления знаний о реальном мире и учете этих знаний.
Вопросы для самопроверки:
Основная цель программирования.
Перечслить источники сложности программирования.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 2. Проектирование программ Рекомендации по проектированию программ (2 час.)
Цели и задачи: Изучить проектирование программ. Рекомендации по проектированию программ.
Рассмотреть понятия прозрачности пользовательского интерфейса.
Учебные вопросы: Понятие проектирование.
Учебная информация:

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

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

Прозрачность пользовательского интерфейса
Как отмечается в [2] , лучшими компьютерными интерфейсами13 XE "компьютерными интерфейсами" \i 15 являются те, которые скрывают сам факт обращения к компьютеру. В этом смысле интерфейс с системой зажигания автомобиля представляет собой выдающийся пример, так как система зажигания, рычаг переключения скоростей, педаль - это простые устройства ввода информации в компьютер, управляющий двигателем автомобиля.
Пользовательский интерфейс13 XE "Пользовательский интерфейс" 15 должен быть функциональным и интуитивно понятным. Проектирование интерфейса часто определяется используемым инструментарием. Приложения, созданные с помощью таких генераторов, как Visual Basic, Delphi, Power Вuilder и других, обычно имеет специфический вид, указывающий на инструмент, который был использован при разработке. Этот специфический облик может не подходить для конкретного проекта, не удовлетворять заказчика. Разработчики должны иметь на выбор несколько генераторов, чтобы использовать тот, который наиболее подходит для данного интерфейса.
Из двух интерфейсов более предпочтительным является тот, который требует меньшего числа нажатий клавиш ( щелчков мышью или других действий пользователя ) при выполнении одних и тех же операций. Другими словами, производительность интерфейса может определяться числом нажатых клавиш [2].

Заказчик всегда прав
Компьютерное программирование, как известно, является индустрией обслуживания Ни одной программе не добиться успеха, если ее проектировщики не будут общаться непосредственно с ее конечными пользователями [2] Опытный проектировщик зачастую может предложить лучшее решение проблемы, чем придуманное заказчиком. Тем не менее, до начала реализации весь интерфейс должен быть согласован.

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

Программы должны быть документированы
Для сопровождения программ особенно важна хорошая документация. Причем основная часть документации должна располагаться в комментариях, а не в отдельном документе. По возможности следует избегать сокращений в комментариях.
Комментарии должны содержать только полезную информацию, необходимую для сопровождения программы.
Хорошо выглядят комментарии в виде блока из нескольких строк, описывающих, что делают несколько следующих за ними срок кода программы, а также выровненные в столбец комментарии, стоящие справа от операторов. В качестве примера приведем следующий фрагмент кода программы на языке С++.
/*-------------------------------------------------------------------------------------
Демонстрация операций с пользовательским классом и бинарным файлом
-------------------------------------------------------------------------------------*/
Symbol symbol ; // Объявление объекта symbol с
//инициализацией по умолчанию
stream in_file ( “c:\\database.dat”, ios::binary); //Открытие входного потока,

//связывание его с файлом
if (in_file ) // Cчитывание информации
in_file >> symbol ; // из входного потока, если
// поток существует
Рекомендуется помечать концы длинного составного оператора, комментарии в данном случае могут полностью описывать управляющий оператор, например:
while ( a < b )
{ while ( c < d )
{ for ( i = 0 ; i ++ ; i < 10 )
{ for ( j = 0 ; j ++ ; j < 10 )
{ // тело цикла содержит много строк кода

} // for ( j = 0 ; j ++ ; j < 10 )
} // for ( i = 0 ; i ++ ; i < 10 )
} // while ( c < d )
} // while ( a < b )


Имена и идентификаторы
Имена играют важную роль. Удачно выбранные имена могут сделать вашу программу поистине самодокументированной, не требуя вовсе или требуя малого количества комментариев. Имена должны наилучшим образом определять те переменные, которые они представляют. В этом смысле более предпочтительна запись price = cost + profit ; чем z = x + y ;
Основные рекомендации по выбору имен можно сформулировать следующим образом:
1. Не используйте имена стандарта ANSI C
Имена, начинающиеся символом подчеркивания и оканчивающиеся на _t, были зарезервированы для разработчиков компиляторов.
2. Не используйте имена Microsoft13 XE "имена Microsoft" 15
Имена функций Microsoft используют соглашение в стиле Паскаля о смеси заглавных и строчных букв и начинаются всегда с заглавной буквы, например: MessageBox ( , , , ), InitApp ( hHinstance ), UpdateWindow (hwnd) и т. д.
Члены - функции в библиотеке основных классов Microsoft (MFC), заменяющий громоздкий интерфейс API, поддерживает это же соглашение. Имена классов данной библиотеки начинаются с заглавной буквы “С”, например: CString, CDialog и т. д.
Многие открытые поля данных этих классов начинаются символами m_ .Имена переменных в программах Microsoft включают тип переменной в ее имя, например:
LPCSTR lpszstr ;
где символ “ l “ указывает на тип long , символ ” p ” говорит о том, что переменная является указателем, ” str ” - о том, что данная переменная является строкой, заканчивающейся нулем. Аналогично констатируется имя типа. ( Следует отметить, что эти имена использованы в системе Borland C++ 4.0, 4.5, 5.0.)
Этот стиль выбора имен называется венгерской нотацией13 XE "венгерской нотацией" \i 15 в честь г-на Чарльза Саймони, руководителя отдела программирования Microsoft , венгра по происхождению. Этот стиль удобен для Ассемблера, поскольку тип переменной говорит о ее длине, но встречает весьма резкую критику при использовании в языках С или С++.
3. Не используйте имена Borland13 XE "имена Borland" 15
Соглашения об именах в системе Borland аналогичны соглашениям Microsoft об именах функций. Имена классов начинаются с заглавной буквой “Т”, например: TWindow, TDialog и т. д.

Использование псевдокода
Первое кодирование программы целесообразно выполнить в так называемом псевдокод13 XE "псевдокод" 15е. Это упрощенная запись реализуемого алгоритма (особенно это удобно при реализации вычислительных алгоритмов с использованием процедурного стиля программирования). Преимущества такого подхода состоит в следующем:
- псевдокоды отражают логику программы гораздо лучше, чем блок - схемы;
- псевдокоды достаточно хорошо понимаются людьми, не занимающимися программированием.
Никаких стандартов в написании псевдокода нет, но связь с операторами языков программирования прямая, так как обычно используются конструкции типа IF THEN ELSE, DO WHILE ( или WHILE DO ), CASE и т. д.
В качестве примера рассмотрим запись алгоритма обработки вершин бинарного дерева P ( объект соответствующего типа) в с использованием прямого обхода.
IF ( P.Left ( () THEN Обработать_ дерево ( P.Left);
//обработка левого поддерева
Обработать_ корневую_вершину (P) ;
IF ( P. Right ( () THEN Обработать_ дерево ( P. Right);
//обработка правого поддерева

Выбор алгоритмов и конструирование объектных типов
Задача выбора оптимального или эффективного вычислительного алгоритма относится, прежде всего, к разработке отдельных процедур и функций. Раньше необходимо было тщательно разрабатывать вычислительные алгоритмы. Но с развитием вычислительной техники, с увеличением скорости и памяти данная задача как бы отступает на второй план, во всяком случае, далеко не всегда является основным критерием качества программы, хотя существует большое количество задач, для которых выбор алгоритма играет существенную роль, например, задачи графики, распознавания образов и т. д.
Если использован объектно-ориентированный подход при программировании, то основная сложность в проектировании программ состоит в разработке объектных типов, содержащих необходимые структуры данных и методы их обработки, определяющие все возможные операции, выполняемые над этим типом данных. ( Данный вопрос подробно будет рассмотрен в последующих главах.) Использование объектных типов резко снижает сложность алгоритмов многих задач. Проблема выбора приемлемого алгоритма при данном подходе может возникать при реализации отдельных членов - функций.
Вопросы для самопроверки:
Что такое проектирование?
Перечислите основные свойства проектирования?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 3. Эффективность, отладка, тестирование программ(2 час.)
Цели и задачи: Изучить эффективность, отладка, тестирование программ.
Рассмотреть понятия эффективность и оптимизация программ.
Учебные вопросы: Понятие оверлейная организация.
Учебная информация:
Эффективность и оптимизация программ
Имеет смысл говорить об эффективности только правильных программ (правильных в том смысле, что они получают правильные результаты ). Если результат неверен, эффективность программы не имеет смысла. Если программа работает корректно, но неэффективна с точки зрения времени выполнения или объема занимаемой памяти, то можно попытаться улучшить эти показатели.
Необходимо помнить, что погоня за эффективностью может вести к злоупотреблениям. Никогда не следует оптимизировать фрагменты программы, повторяющиеся незначительное число раз. Экономию во времени может дать оптимизация только многократно повторяющихся циклов. О необходимости оптимизации программ можно сказать следующее:
- оптимизируйте программу только в случае необходимости, так как это ухудшает ее читабельность, служит источником дополнительных ошибок, может занять много времени;
- попытайтесь использовать другой транслятор или компилятор. Вполне возможно вы получите удовлетворительные результаты;
- никогда не оптимизируйте фрагменты программы, повторяющиеся незначительное число раз. Экономию во времени может дать оптимизация многократно повторяющихся циклов. Если результаты оптимизирующего компилятора вас не удовлетворяют, попытайтесь выполнить локальную оптимизацию внутри этих циклов. Эта оптимизация может состоять в улучшении или замене алгоритма, по которому выполняются вычисления внутри цикла, или в новой организации циклов. Рассмотрим два фрагмента, содержащие циклы.

for ( i =0 ; i < 20 ; i ++ ) //Инициализация цикла 1 раз
{ for ( j =0 ; j < 10 ; j ++ ) // 20 раз
{ for ( k =0 ; k < 5 ; k ++ ) // 200 раз
{ // тело цикла // Проверка на окончание цикла
} // 1000 раз
} // 200 раз
} // 20 раз
for ( k =0 ; k < 5 ; k ++ ) // Инициализация цикла 1 раз
{ for ( j =0 ; j < 10 ; j ++ ) // 5 раз
{ for ( i =0 ; i < 20 ; i ++ ) // 50 раз
{ // тело цикла // Проверка на окончание цикла } // 1000 раз
} // 50 раз
} // 5 раз

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

Оверлейная организация13 XE "Оверлейная организация" 15 программ
Данная организация программ позволяет сократить объем оперативной памяти, занимаемый программой. Под оверлейной организацией понимаем размещение отдельных модулей программы в одной и той же области оперативной памяти ( рабочая область или оверлейный буфер ) в различные моменты времени. Эти модули оформляются как оверлейные секции. При формировании оверлейных секций необходимо помнить:
- в различные секции следует помещать непересекающиеся модули, т.е. не обращающиеся друг к другу, с целью уменьшить число загрузок оверлейных секций в буфер:
- не всякий модуль можно сделать оверлейным. Как правило, в буфер загружается неизменная копия оверлейного модуля. Такой подход облегчает и ускоряет обработку оверлейных структур. Поэтому, если модуль накапливает результаты в каких - либо внутренних структурах то это результаты могут быть потеряны.
Возможность создания оверлейных программ, например, включена в интегрированную среду разработки Turbo Pascal. Для этого в основную программу помимо раздела с инструкциями uses для оверлейных модулей необходимо включить опцию компилятора { $O UnitName }. Сам оверлейный модуль компонуется с ключом { $O+ } и ключом { $F+ } для формирования “дальних” адресов модуля.

Отладка программ13 XE "Отладка программ" 15
Программа, свободная от ошибок есть абстрактное теоретическое понятие [4]. Отладкой называется процесс локализации и устранения ошибки, когда факт ее существования установлен. Не следует путать отладку и тестирование13 XE "тестирование" 15. Отладка имеет место тогда, когда она со всей очевидностью работает неправильно. Если программа работает правильно, то она тестируется.
Какие типы ошибок13 XE "типы ошибок" 15 наиболее характерны для программирования ?
1.Неверная постановка задачи или неверный алгоритм решения задачи, то есть разработчик неправильно или не до конца понимает решаемую проблему. Избежать этих неприятностей помогает вовлечение в работу заказчика, начиная с первых шагов проектирования программного продукта.
2.Ошибки анализа задачи. Как правило, ошибки13 XE "типы ошибок" 15 этого типа связаны с неполным учетом всех требуемых ситуаций, например, отсутствие значений для параметров, которые могут быть заданы по умолчанию, отсутствие обработки исключительных или вырожденных случаев и т.д.
3.Синтаксические ошибки связаны с неправильным написанием языковых конструкций.
4.Семантические ошибки, как правило, связаны с неверной трактовкой ( приписыванием неправильного смысла ) некоторой языковой конструкции. Например, как нужно трактовать синтаксически правильное выражение c = ( a ++ ) + b ? Как c = ( a ++ ) + b или как c = a + ( ++ b ) ? Результаты этих выражений различны. Имеет смысл использовать скобки, чтобы явно задать необходимую конструкцию.
5.Ошибки при выполнении операций обычно появляются при определенных сочетаниях значений параметров. К этому типу ошибок относятся :
- деление на ноль,
- недопустимые значения параметров,
- ошибки ( погрешности ) округления,
- потеря значимости,
- попытка чтения или записи в запрещенную область
- и т. д.
Например, из-за погрешностей округления разные значения имеют суммы 1 - 1/2 + 1/3 - 1/4 ... -1/100000 и -1/10000 + 1/9999 - 1/9998 + ... -1/2 + 1 . Это объясняет, почему не стоит проверять на равенство вещественные выражения.
6.Сбой оборудования. Данные ошибки могут вызвать повреждение или разрушение пользовательских файлов.
7.Ошибки перегрузки системы. Этот ошибки возникают, если нарушаются размеры внутренних таблиц, буферов и т. п. участков памяти и не проявляются, пока не возникнет соответствующая ситуация.

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

Интегрированный отладчик13 XE "Интегрированный отладчик" 15 ( на примере системе Borland С++)
В интегрированную среду проектирования ( IDE ) обычно вставляются несколько функций отладки. Их можно использовать для останова программы в процессе выполнения, чтобы просмотреть значения переменных и член - данных или проследить за построчным выполнением программы. В меню верхнего уровня содержится два подменю - Debug и View .
Меню Debug содержит основные команды, позволяющие:
- запускать программу ( Run ) ;
- выполнять программу по принципу “строка за строкой” ( Step over );
- устанавливать режим трассировки, т.е. отслеживать все обращения к функциям ( Trace info );
- устанавливать контрольные точки в программе ( Toggle breakpoint );
- вернуть курсор в текущую строку, т.е. строку, где произошел останов программы ( Find execusion point ) . Эту строку можно потерять при просмотре текста;
- команда Terminate program прерывает выполнение программы. Это означает, что при следующем запуске программа будет выполняться с начала, а не со следующего шага.
В меню Debug также содержатся команды, позволяющие через диалоговые окна вводить контрольные точки (т.е. строки ) и выражения, значения которых будут отслеживаться в процессе выполнения программы.
Меню View содержит команды, которые открывают различные окна, в том числе и окна, связанные с отладкой программы, например:
- окно Watch содержит список введенных выражений и их значений, которые изменяются в процессе выполнения программы;
- окно Breakpoint содержит список введенных контрольных точек;
- окно Call Stack указывает последовательность вызовов функции;
- окно Registers показывает содержимое регистров.

Автономные отладчики13 XE "Автономные отладчики" 15 ( на примере системе Borland С ++)
Наряду с интегрированным отладчиком можно применять другие средства отладки, например, такие полезные программы как WinSight и WinSpector.
WinSight может отобразить все зарегистрированные системой Windows в данный момент окна, независимо от того, видимые они или нет, причем можно даже отобразить иерархию окон.
WinSpector , если эта программа активна в момент обнаружения ошибки, с помощью трассировки стеков пытается определить, какие разделы программы были выполнены непосредственно перед ошибкой. Это может определить, какая строка программы не работает и какие функции были вызваны перед этим.

Тестирование программ13 XE "Тестирование программ" 15
Когда отладка программы завершена, то это означает, что программа, несомненно, решает какую - то задачу. Вопрос о том, “какую задачу именно” решает тестирование. Таким образом, цель тестирования - убедиться в том, что программа решает требуемую задачу и выдает правильный результат при любых допустимых условиях ( в частности, при любых допустимых значениях входных данных ). Другими словами при тестировании, вообще говоря, осуществляется два вида испытаний:
- соответствие программы и поставленной задачи;
- правильное функционирование программы.
Выбор, определение, построение тестовых данных - задача нетривиальная. Дать общие рекомендации по тестированию любых программных практически невозможно, слишком различным целям могут служить данные программы. Но некоторые полезные замечания сделать можно. Один практический совет при тестировании программ сводится к следующему: тестирование выполняется до тех пор, пока каждая команда программы не будет выполнена хотя бы один раз, то есть тестовые данные должны обеспечить проверку всех ветвей программы. Следует помнить, что ветвь и логический путь - не одно и то же. Следующий простой пример показывает, что такая проверка всех логических путей невозможна.
Пусть в некоторой точке программа разветвляется на три ветки, в данной ситуации эти три ветки определяют три логических пути. Два последовательно стоящих фрагмента дают 9 логических путей. Если такой фрагмент стоит в цикле, повторяющемся 10 раз, то число логических путей равно 910.
Еще один простой пример показывает невозможность проверки всех комбинаций входных воздействий. Пусть х, у - 32-разрядные векторы. Для полного тестирования требуется 264 различных входных наборов. Если программа работает 1 млсек (0.001), то на это тестирование потребуется приблизительно 3 *1013 часов при непрерывной работе. Эти примеры показывают, что необходимо тщательно подбирать тестовые данные, чтобы их значимость не вызывала сомнений.
При тестировании программу рассматривают как “черный ящик”, то есть подают на вход тестовые данные и сравнивают получаемый выход с ожидаемыми результатами.
Тестирование крупных разработок рекомендуется начинать как можно раньше, находясь еще на этапе проектирования программы.

Методы тестирования13 XE "Методы тестирования" 15
Существует два основных метода тестирования13 XE "Тестированя методы" 15 программ - тестирование сверху вниз и тестирование снизу - вверх. (В настоящем разделе мы говорим в основном о тестировании программ, написанных в процедурном стиле, объектно-ориентированный стиль программирования будет рассмотрен ниже. )

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

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

Тестовые данные13 XE "Тестовые данные" 15
Можно выделить три вида тестовых данных:
а ) создаваемые программистом,
б ) реальные модифицированные,
в ) реальные в полном объеме.
Данные типа а ) применяются в первую очередь. Это, как правило, очень простые данные, дающие очевидный результат ( вырожденные, нулевые, единичные значения параметров, пустые последовательности и т.д. ). Но эти данные часто позволяют эффективно проверить многие важные ситуации.
Данные типа б) более сложные, но также определяются или подбираются разработчиком программы по справочной литературе или путем некоторого упрощения реальных данных. Результаты работы программы с этими данными не обязательно очевидны и иногда требуют трудоемкой проверки. Результаты тестирования могут быть проверены несколькими способами: вычислены вручную; получены из таблиц, документации или других источников ; получены с помощью другой программы.
Данные типа в) используются, обычно, при проведении демонстрационных и эксплуатационных испытаний при передаче разработанных программных средств заказчику.
Вопросы для самопроверки:
Что такое коды ошибок?
Какие коды ошибок наиболее характерны для программирования?
Перечилсите виды тестирования.
Перечилсите основные методологии программирования.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.

Тема 4. Парадигмы программирования (2 час
Цели и задачи: Изучить парадигмы программирования .
Рассмотреть понятия модель разработки программы.
Учебные вопросы: Понятие стиль программирования, модель разработки программы .
Учебная информация:

В грамматике под словом “парадигма” понимается таблица склонений, спряжений и тому подобных грамматических форм. Другими словами, обобщенно парадигму можно понимать как совокупность правил языка. В различных областях под парадигмой обычно понимают совокупность свойств, правил, приемов, методов, используемых при работе с объектом из данной области.
Парадигму программирования будем понимать как модель разработки программы, то есть парадигма программирования определяет некоторую совокупность приемов программирования, используемую при разработке программ. Не всякий прием или способ достигает уровня парадигмы. Этого уровня достигает только тот прием, необходимость которого осознана многими, и необходимость поддержки этого приема формулируется в виде требования к языку программирования. Имеется серьезное различие в том, что язык поддерживает определенный стиль программирования или допускает использование приемов данного стиля. Язык поддерживает определенный стиль программирования (поддерживает парадигму), если обладает средствами, простыми и эффективными, для использования приемов данной парадигмы. Если для достижения цели необходимо привлечение дополнительных, неординарных средств, не являющихся средствами данного языка, то говорят, что язык допускает использование данного стиля программирования.
Общим для всех моделей разработки программ является то, что :
1) разработка программы должна базироваться на абстракциях, отвечающих элементам задачи;
2) реализация представляет собой совокупность модулей, которые могут быть использованы в дальнейшем.
Отличия состоят в формировании абстракций и модулей задачи.

Парадигма первая - процедурность
Изначальной, и, вероятно, все еще самой общей парадигмой программирования является парадигма процедурности13 XE "парадигма процедурности" 15, которая может быть сформулирована следующим образом : “ Определите нужные вам функции, используйте для их реализации лучшие из алгоритмов, которые вы можете найти”.
Приемы процедурного или структурного программирования объясняют как разрабатывать и реализовывать функции, составляющие программу. Основными приемами здесь являются проектирование, отладка и тестирование программы сверху вниз. Суть такого проектирования состоит в следующем:
- после того, как задача сформулирована четко и однозначно в ней выделяются основные крупные задачи, в совокупности решающие исходную задачу (задачи первого уровня). Каждая из этих задач реализуется отдельной функцией;
- каждая из задач первого уровня разбивается на подзадачи, реализуемые отдельными функциями (задачи второго уровня);
- и т. д.
Такое разбиение, называемое функциональной декомпозицией13 XE "функциональной декомпозицией" 15 задачи, естественно, неоднозначно. Исходная задача может быть представлена в виде дерева, уровни которого соответствуют уровням разбиения решаемой задачи.
Отладка и тестирование программ сверху вниз, о чем уже говорилось выше, являются логическим продолжением такого подхода к проектированию программ.
Основное средство абстракции - функция. Другими словами, функция, инкапсулирующая в себе последовательность выполняемых действий, рассматривается как единая абстрактная и операция. Обозначением данной абстрактной операции является имя функции.
В языках программирования, поддерживающих только парадигму процедурности, модулем обычно называется отдельная процедура или функция.

Языковые средства поддержки парадигмы процедурности
При рассмотрении языковых средств в основном будем ориентироваться на язык программирования С++, на нем же будем приводить примеры, иллюстрирующие рассматриваемые свойства.
1) Возможность создавать различные функции, как-то : функции, процедуры, макросы и.д.
2) Вызов функции может осуществляться как по имени функции, так и через указатель на нее.
3) Возможностью разными способами передавать аргументы возвращать результаты: по значению, по адресу, по ссылке, через глобальные переменные.
4) Задание аргументов функции по умолчанию также можно рассматривать как поддержку парадигмы процедурности. В языке С++ значения аргументов, задаваемых по умолчанию, указываются при определении функции в списке формальных параметров. Значения отсутствующих фактических параметров при вызове функции замещаются значениями, определенными по умолчанию.
Пример.
. . .
void noName ( int , double , char ); // объявление функции
//определение функции
void noName ( int x = 1, double y = 2.0, char z = A’ )
{ . . . } // тело функции
void main ( )
{ . . .
noName ( 10, 0.1, B’ ) ; //значения всех параметров заданы явно
noName ( 10, 0.1 ) ; // третий параметр по умолчанию A’
noName ( 10 ) ; // второй и третий параметры 2.0 и A’
noName ( ) ; // значения всех трех параметров
} // определены по умолчанию

5) Возможность задания подставляемых функций. Функция объявляется подставляемой с помощью спецификатора inline. Данный спецификатор дает указание компилятору на оптимизацию вызова данной функции, суть такой оптимизации состоит в подстановке тела функции в место вызова. Любая функция в языке С++ может быть объявлена со спецификатором inline, но оптимизация вызова будет проводиться не всегда. Например, не оптимизируются функции, содержащие условные операторы или циклы, не производится оптимизация при вызове функции через указатель.
6) Как средство поддержки парадигмы процедурности можно рассматривать возможность перегрузки функций и операций (операторных функций). Перегрузка позволяет использовать одно и то же имя функции для реализации разных функций, при условии, что эти реализации ( заголовки функций) различаются по числу и типу формальных параметров, то есть сигнатурами ( сигнатура по определению есть список формальных параметров функции). Этот подход целесообразен, когда необходимо выполнить над разными типами данных действия, одинаковые по смыслу. Например, функцию с именем Sort, выполняющую сортировку списка целых чисел или списка символьных строк, можно объявить как перегружаемую следующим образом

typedef char * MyString ; // Тип MyString - символьная строка
void Sort ( char * , int) ; //Объявление функции сортировки строк.
void Sort ( int * , int) ; //Объявление функции сортировки чисел
void Sort ( char * L , int n); // Определение функции сортировки строк
{ . . . }
void Sort ( int * Ll , int n ) // Определение функции сортировки чисел
{ . . . }

Парадигма вторая - локализация данных13 XE "Парадигма локализации" 15
Ключевым и самым сложным моментом при разработке программ с использованием только процедурного подхода является разработка алгоритма, как всей программы, так и отдельных процедур и функций. Резкое увеличение размера программ привело к тому, что основное внимание все более и более начинает смещаться в область организации данных, группировки процедур и функций вокруг выделенных данных.
Множество логически связанных между собой данных, типов, процедур и функций обычно называют модулем. Парадигма процедурности дополняется новой парадигмой - парадигмой локализации данных, которая может быть сформулирована следующим образом: “Определите необходимые вам данные; программу разбейте на модули так, чтобы данные и методы их обработки были спрятаны в модулях”. ( Когда нет необходимости группировки процедур и функций вокруг данных, то соблюдается обычный процедурный стиль программирования). Поддержкой новой парадигмы во многих языках является возможность файловой организации программ. При этом модули обычно представлены отдельными файлами. Такую организацию поддерживают различные версии языка Pascal ( Turbo Pascal, Borland Pascal) , языки С, С++, Ада, и другие. К примеру, в Pascal подключение модуля - файла в программу или другой файл осуществляется с помощью оператора Uses FileName, а стандартные модули содержат переменные, константы, типы, процедуры и функции, предназначенные для решения определенных задач ( работа с экраном в текстовом режиме, работа в графическом режиме и т.д.). В языках С или С++ подключение файлов осуществляется с помощью директивы препроцессора :
#include <имя_файла>
#include ”имя_файла”.

Парадигма третья - абстракция данных13 XE "абстракция данных" 15
Как упоминалось выше, со временем основная сложность в проектировании программ постепенно стала перемещаться в область конструирования данных. При решении многих реальных задач редко бывает достаточно только встроенных типов данных, то есть предоставленных языком программирования. Очень часто программисты вынуждены объединять в одно целое (один тип) элементы различных типов. Примерами таких данных являются данные типа record в языке Pascal или типа structure в языках С и С++. Для манипуляции с такими данными обычно создают набор функций, выполняющих различные действия над ними. При этом следует обратить внимание на следующие недостатки этих данных:
1) в языке программирования нет естественных средств связи между этими данными и обрабатывающими функциями;
2) данные не защищены от случайных модификаций, любая функция, не только из обрабатывающего набора, может изменить любой структурный элемент;
3) данные структурных типов не используются как встроенные, то есть функции обработки этих данных вызываются по имени.
Новые требования по отношению к пользовательским (то есть абстрактным) типам данных привели к возникновению новой парадигмы, которую можно сформулировать следующим образом : “Определите нужные вам типы данных; обеспечьте полный набор операций для них”. Этот набор операций, или обрабатывающих функций, называется интерфейсом абстрактного типа13 XE "интерфейсом абстрактного типа" 15 данных. Основной принцип абстракции данных состоит в том, что никакие функции, кроме функций интерфейса, не имеют доступа к элементам абстрактного типа, то есть доступ к абстрактным данным осуществляется только через интерфейс. Другими словами, абстрактный тип данных - это не встроенный, не реально существующий, а пользовательский тип, определяемый своим интерфейсом. Поэтому, основная задача при создании такого типа - разработка интерфейса, то есть набора необходимых функций, доступных для данного типа.
В языке С++ для реализации механизма создания абстрактных типов введено понятие “класс”. Это понятие оказалось настолько фундаментальным для языка, что первое название языка было “С с классами”.
Класс в С++ - это видоизмененный тип структуры, позволяющий включать в описание типа элементы, содержащие значения (член - данные ) и функции обработки этих элементов (член - функции). Такое совмещение в типе данных структуры и методов их обработки называется инкапсуляцией13 XE "инкапсуляцией" 15.
Хорошим примером абстрактного типа является класс, реализующий понятие комплексного числа. Определение соответствующего класса complex приведено в файле complex.h . Данный файл включается в файл с основной программой.

// файл complex.h
class complex
{ private : double re,
double im ;
pub
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·/файл с основнойю программой
#include ”complex.h”
void main ( )
{ const complex j ( 0, 1 ) ;
const double pi = 3.1415926535 ;
double L = 0.3 , // индуктивность, гн
R = 5000 , // сопротивление, ом
C = 0.02 , // емкость, ф
freq = 60 , // частота, гц
omega = 2 * pi * freq , // частота, рад/сек
complex I = 12 , // ток, а
Z , // импеданс
V ; // напряжение
Z = R + j *omega * L + 1/ ( j * omega *C ) ;
V = Z * I ;
V.print ( ) ;
}
Внутри типа complex выделены две секции - private и public . В закрытой секции private определены члены - данные re и im , которые имеют смысл вещественной и комплексной частей числа. В открытой, доступной секции public объявлены шесть функций, составляющих интерфейс данного типа. Первая из этих функций имя которой совпадает с именем класса, - конструктор, необходимый для инициализации членов - данных. Параметры данного конструктора заданы по умолчанию. Конструктор вызывается при объявлении каждой переменной, имеющей тип complex. Члены-данные re, im переменной j (мнимая единица) принимают значения 0 и 1, переменной I - значения 12 и 0, члены-данные переменных Z, V инициализируются по умолчанию нулевыми значениями. Вызов конструктора для переменных I, Z, V осуществляется автоматически.
Следующие четыре перегруженные операции ( операторные функции) не являются членами - функциями класса, они объявлены как дружественные к нему с помощью спецификатора friend . Это дает возможность явно задавать оба операнда для рассматриваемых арифметических операций. Перегрузка операций,13 XE "Перегрузка опрераций" 15 определенных для встроенных типов, на абстрактные (пользовательские) типы дает возможность использовать в арифметических или логических выражениях перемененные абстрактных типов, так же как и переменные встроенных типов. Например, в выражении Z = R + j *omega * L + 1/ ( j * omega *C ) переменные типа complex использованы как обычные арифметические операнды.
Эти функции могут быть определены в другом файле, например, complex.cpp. Сложение комплексных чисел может быть определено следующим образом:
complex operator + ( complex a, complex b)
{ return complex ( a.re + b.re , a.im + b.im ) ;
}
Последняя функция print ( ) является член - функцией рассматриваемого класса и может быть определена , например, так:
void complex ::print ( )
{ printf ( “ %5.2f , %5.2f \n “, re, im ) ;
}
Что дает использование абстрактных типов ?
Во-первых, упрощается семантика использования типов, набор возможных операций, выполняющийся над данным типом, ограничен только функциями интерфейса. При этом разработчик может менять реализацию интерфейсных функций, не боясь повлиять на программы пользователя данного типа, пока “внешняя” семантика интерфейса сохраняется.
Во-вторых, использование абстрактных типов и перегрузка операций для этих типов дает возможность писать логически более понятные и более компактные программы. Сравните, к примеру, простое арифметическое выражение и эквивалентное ему функциональное -
R + j *omega * L, Add( R, Mult ( Mult ( j, omega), L))
Таким образом, С++ предоставляет возможность разрабатывать необходимые элементы для конкретной проблемной области, а также “думать” и писать программы в терминах соответствующей проблемной области.


Парадигма четвертая - объектно-ориентированная парадигма13 XE "объектно-ориентированная парадигма" 15
Данная парадигма является усилением парадигмы абстракции данных, когда целесообразно использовать несколько абстрактных типов данных, причем, как правило, эти типы могут содержать ряд общих методов и общих элементов. Рассматриваемая парадигма может быть сформулирована следующим образом: “Определите необходимые вам классы, для каждого класса обеспечьте полный набор операций, сделайте общность свойств явной, используя механизмы наследования и виртуальности”. Таким образом, тестом оценки применимости объектно-ориентированного подхода к проектированию программы является количество типов с общими свойствами.
Концепция класса является базисной для данного подхода. Практическая применимость класса обусловлена наличием в языке С++ средств наследования, которые обеспечивают создание нового класса, автоматически наследующего поведение, то есть структуру и методы старого класса. Отдельно взятая возможность создания класса, просто наследующего поведение другого, лишена смысла. Обычно новые классы создаются с целью добавить новые элементы в наследуемое поведение. Язык С++ содержит средства множественного наследования. Множественное наследование появилось сравнительно недавно и содержит ряд моментов, решение которых не является общепризнанным. Например, потенциальный конфликт имен переменных и методов в разных классах, из которых ведется наследование.
Новый класс может наследовать поведение более, чем одного класса. В процессе определения классов появляется так называемая иерархия классов13 XE "иерархия классов" 15. Эта иерархия типов может иметь вид дерева (при одиночном наследовании) или прямого ациклического графа (при множественном наследовании).





В объектно-ориентированном программировании полиморфизмом обычно называют возможность иметь в программе несколько функций с одинаковыми именами. В языке С++ изначально заложена возможность перегрузки пользовательских и операторных функций. Поддержка полиморфизма в языке С++ осуществляется с помощью виртуальных функций. Виртуальные функции позволяют производным классам переопределять функции базовых классов. При этом сигнатура функции, имеющей спецификатор virtual, должна оставаться неизменной. Но помимо этого имеется еще существенное различие между перегрузкой и виртуальностью. Перегруженные функции никак не связаны друг с другом, они просто имеют общее имя, которое компилятором “расщепляется” на несколько различных внутренних имен. Компоновщик заменяет эти имена точными адресами. Такой процесс называется ранним связыванием13 XE "ранним связыванием" 15, поскольку имена функций ассоциируются (связываются ) с физическими адресами до выполнения программы.
При раннем связывании необходимо точно знать, какие функции будут вызваны в каждой конкретной ситуации для любого объекта. При раннем связывании хорошо то, что оно быстро выполняется. Однако это накладывает ограничение на возможности разработчика программы и порой очень трудно реализуется. Механизм виртуальных функций реализуют позднее или динамическое связывание кода программы с кодом функции, т.е. определение того, какую функцию вызывает объект, происходит на шаге выполнения программы. Следует отметить, что позднее связывание13 XE "позднее связывание" 15 имеет смысл только для объектов, являющихся частью иерархии классов.
Существуют чисто объектно-ориентированные языки, осуществляющие только позднее связывание функций. Язык С++ является гибридным языком, предоставляя пользователю как раннее, так и позднее связывание. Программисту доступны как высокая скорость компиляции, так и высокая степень гибкости при разработке программ. Однако компилятор и генерируемый код программы существенно усложняются. Программист сам определяет, когда использовать раннее связывание, а когда - позднее.
Вопросы для самопроверки:
Что такое парадигма программирования?
Объясните общее программирование?
Чем отличаются парадигмы друг от друга?
Перечислите основные стили языка.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Раздел 2. Автоматное программирование (2 час.)
Тема 1. Понятие автоматного программирования (2 час.)
Цели и задачи: Изучить понятие автоматного программирования.
Рассмотреть понятия событие.
Учебные вопросы: Основные подходы программирования. Автоматный подход.
Учебная информация:
Если в программировании в последнее время все шире используется понятие "событие", то автоматный подход базируется на понятии "состояние". Добавляя к нему понятия "входное воздействие", "выходное воздействие", которые могут быть переменными или событиями, определяется понятие "конечного детерминированного автомата".
Особенность рассматриваемого подхода состоит в том, что задаются графами переходов, вершинами которых являются состояния. Входные и выходные воздействия "привязаны" к дугам графов переходов Это позволяет в компактном виде представлять последовательности действий, которые являются реакциями на соответствующие входные воздействия. В рамках автоматного подхода программирование выполняется "через состояния", а не "через переменные" (флаги), что позволяет лучше понять и специфицировать задачу и ее составные части.
Другая важнейшая особенность автоматного подхода состоит в том, что при его применении автоматы могут использоваться триедино:
при спецификации;
при программировании (сохраняются в программном коде);
при протоколировании в терминах автоматов.
Последнее позволяет контролировать правильность функционирования системы автоматов. Это позволило ввести в программирование такое понятие, как "наблюдаемость программ".
Автоматный подход используется в настоящее время и при реализации вычислительных алгоритмов [23–25]. Произвольный итеративный алгоритм может быть реализован конструкцией, эквивалентной циклу do–while, телом которого является оператор switch.
Возникает вопрос, почему же конечно-автоматная модель теории автоматов особенно актуальна сейчас, когда существует огромное количество, как языков программирования, так и сред для разработки ПО? Имеют место две проблемы:
непредсказуемое поведение кода программы, разработанной исключительно средствами RAD (Rapid Application Development – средства быстрой разработки приложений);
«угасание» «культуры программирования».
Примеры RAD: Borland Delphi и C++ , обеспечивающие ускоренную разработку приложений за счет использования объектно-ориентированного и визуального программирования. Они позволяют не только программировать в привычном смысле слова, но и фактически рисовать программы (как интерфейс, так и реализацию), используя визуальные компоненты VCL.
Любой визуальный объект VCL характеризуется рядом свойств, методов и событий. Казалось бы, простой манипуляцией перечисленными атрибутами возможно заставлять разрабатываемую программу делать то, что требует от нее программист-разработчик. Но это далеко не так.
Давно стало ясно, что VCL имеет тенденцию скрывать точную реализацию определенных объектов, тем самым не давая посторонним менять умалчиваемое поведение кода. Как показывает практика, поведение кода программы, созданной с помощью средств RAD, не всегда предсказуемо даже для очень опытного программиста, не говоря уже о начинающем. Программа, несмотря на «очевидность» авторского кода, всегда стремится пойти своим путем, попадая в такие замысловатые обработчики событий, о существовании которых можно даже и не догадываться.
В современном мире объемы и сложность разрабатываемых приложений возрастают с каждым днем, поэтому такой подход резко увеличивает время тестирования и отладки ПО.
Управлять поведением кода дает возможность механизм теории автоматов еще сорокалетней давности.
Стили программирования различаются по базовым понятиям, в качестве которых используются такие понятия как «событие», «подпрограмма», «функция», «класс» («объект») и т. д. Стиль программирования, основанный на явном выделении состояний и применении автоматов для описания поведения программ, назван «автоматное программирование», а соответствующий стиль проектирования программ «автоматное проектирование». Автоматное программирование можно рассматривать не только как самостоятельный стиль программирования, но и как дополнение к другим стилям, например, к объектно-ориентированному, т.к. речь идет не только и не столько об использовании конечных автоматов в программировании, сколько о методе создания программ в целом, поведение которых описывается автоматами. Т.е. как отдельный компонент, так и программа в целом может быть реализована как автомат.
В автоматном программировании существует два направления: SWITH-технология и КА (конечно-автоматная) технология. Switch-технология технология разработки систем логического управления на базе конечных автоматов, охватывающая процесс проектирования, реализации, отладки, верификации (проверки), документирования и сопровождения.
Кодирование/программирование автоматов в рамках КА-технологии основано на следующих принципах:
введено понятие динамичного объекта, который может быть наделен алгоритмом поведения во времени;
алгоритм поведения объекта задается моделью конечного автомата;
язык описания автомата основан на базе табличной формы представления автоматов;
логика поведения объекта (таблица переходов автомата) отделена от методов автоматного объекта (предикатов и действий), связанных с реализацией его поведения во времени;
любые динамичные объекты могут выполняться параллельно.
Рассмотрим эти технологии подробнее:
1) SWITH-технология. Основные положения: предлагается сделать понятие «состояние» первичным, а алгоритмы представлять в виде графов переходов (диаграмм состояний), т.е. представлять программу как систему взаимодействующих конечных автоматов, описываемых графами переходов. Состояния кодируются для того, чтобы их различать. Графы в наглядной для человека форме отражают переходы между состояниями, а также «привязку» выходных воздействий и других автоматов к состояниям и/или переходам.
Внутри программы автоматы могут взаимодействовать:
по вложенности (один автомат вложен в одно или несколько состояний другого автомата) по вызываемости (один автомат вызывается с определенным событием из выходного воздействия, формируемого при переходе другого автомата) обмену сообщениями (один автомат получает сообщения от другого)
по номерам состояний (один автомат проверяет, в каком состоянии находится другой автомат).
Ни число автоматов, вложенных в состояние, ни глубина вложенности не ограничены. Основной критерий оптимальности реализации управляющих автоматов – возможность преобразования графа переходов в программный код. <><><>Известно большое число инструментальных средств для генерации программ, реализующих графы переходов:
Visio2Switch - инструментальное средство Visio2Switch позволяет по графу переходов, построенному с помощью редактора Microsoft Visio, автоматически реализовать его в виде изоморфной программы на языке С. Конвертор Visio2Switch используется в настоящее время при создании программного обеспечения ряда ответственных систем реального времени.
MetaAuto - инструментальное средство MetaAuto позволяет по графу переходов, построенному с помощью того же редактора, автоматически реализовать его в виде изоморфной программы на любом языке программирования, для которого предварительно построен шаблон (C, C# , Turbo Assembler и т.д.).
UniMod - инструментальное средство UniMod предназначено для поддержки автоматного программирования и построения и реализации не только автоматов, но и программ в целом.
Практика показала, что для многих классов программ только 20–30% программного кода строится вручную.
На основе SWITCH-технологии уже разработаны приложения для: устройства продажи газированной воды, банкомата (см. пример 1), светофора, системы управления пассажирским лифтом, система управления автомобильной сигнализации (см. пример 2), автоматизированной системы оплаты мобильного телефона, устройства обмены валюты, устройства для продажи проездных билетов и т.д.
2) КА-технология позволяет реализовать идею параллелизма. Технологии разработки программ “сверху вниз”, “снизу вверх”, структурный подход таких возможностей или не имеют, или они ограничены. Даже в технологии объектно-ориентированного программирования (ООП) вопросы параллельной работы объектов вынесены за ее рамки. Использование других технологий, базирующихся на известных параллельных моделях, сопряжено с трудностями, связанными если не с ограничениями области их применения, то с проблемами последующей реализации на программном и/или аппаратном уровнях.
Параллельные модели - одно из основных и перспективных направлений в области развития программирования и аппаратных средств. Идея параллелизма весьма привлекательна. Но для ее использования необходимо, во-первых, решить проблему описания, т.е. выбора формальной параллельной модели, и, во-вторых, проблему реализации модели. КА-технология использует модель со средствами представления и описания параллелизма, которые по своим возможностям не уступают другим параллельным моделям, а ее реализация во многом проще. Кроме того, применяя стандартные приемы, несложно перейти от параллельного конечно-автоматного представления к последовательному описанию.
Примеры применения КА-технологии: бухгалтерские программы типа расчета заработной платы или учета квартирной платы, проект системы управления технологическим процессом выращивания кристаллов с множеством динамически порождаемых параллельно функционирующих объектов реализующих процессы съема данных с датчиков, выдачу управляющих воздействий на объект, автоматные алгоритмы работы драйверов с разнообразной аппаратурой, процессы отображения и расчета.
3) Основное отличие рассматриваемых технологий заключается в реализации логики автоматных программ. В SWITCH-технологии она реализуется переводом автоматного описания в программный код языка программирования, в КА-технологии реализуется прямое исполнение автоматов путем интерпретации его исходного табличного представления. Это более короткий и наглядный путь реализации автоматов, хотя и более медленный, если рассматривать уровень отдельного компонентного автомата сети автоматов.
Вывод: Автоматное программирование используется в настоящее время при проектировании программного обеспечения систем автоматизации ответственных объектов управления (автомат управления криогенно-вакуумной установкой, дизель-генератором). Конечный автомат работает по принципу «шаг в сторону – недопустимо». Реализовать непредусмотренные действия конечный автомат не даст ни пользователю (исходный вариант кода), ни самой программе (модифицированный вариант кода).
В настоящее время наблюдается бум в области создания компьютерных игр. Все чаще логика управления играми, в которых персонажи, перемещающиеся в области игры, могут действовать в различных режимах (например, персонаж бежит вперед или назад, взбирается по лестнице, падает и т.д.), реализуется в виде конечного автомата.
Вопросы для самопроверки:
Пречислите основные технологии программирования.
Пречислите основные технологии программирования.
Что такое программных подход.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Модуль 2. Объектно ориентированное программирование (10 час.)
Раздел 1. Классы (10 час.)
Тема 1. Объекты и классы (2 час.)
Цели и задачи: Изучить объекты и классы .
Рассмотреть основные концепции объектно-ориентированного программирования .
Учебные вопросы: Понятие объекта, класса, конструктор, деструктор.
Учебная информация:
Концепция объектно-ориентированного программирования основана на понятиях объекта и класса. Каждый объект характеризуется присущими ему признаками и действиями, совершаемыми над ним. В языке С++ объекты - это программные конструкции, формируемые классами. Объект является конкретным экземпляром класса. Класс определяет категорию объектов, это структуры, которые содержат как объявление данных (так называемые члены-данные), так и функции их обработки (члены-функции). Пользователю, как правило, предоставляется описание класса, а не его реализация. Главная забота класса - скрыть как можно больше информации. Это свойство, обычно называемое инкапсуляцией, накладывает определенные ограничения на использование данных и функций класса.
Управление доступом к классу
Существует три типа пользователей класса:
- сам класс (члены-функции класса могут обращаться к другим членам-функциям или членам-данным);
- обычные пользователи, то есть программы пользователя;
- производные классы (члены-функции производного класса могут обращаться к членам-функциям или членам-данным базового класса).

Каждый пользователь обладает разными привилегиями доступа. Каждый уровень доступа ассоциируется с определенным ключевым словом, а именно: private, public, protected.
Приватные члены класса, объявленные в секции private имеют наиболее ограниченную область действия. Обратите внимание, что приватные данные видимы, но недоступны. Такой контроль введен для предотвращения нечаянного доступа к данным или внутренним функциям, в целях уменьшения ошибок при разработке программ, но не для предотвращения “взлома”.
Ко всему, что объявлено в секции public, разрешен неограниченный доступ. В частности, можно все содержимое класса объявить общедоступным и манипулировать им, как заблагорассудится.
При определении класса часть его членов-данных и членов-функций можно скрыть от пользователя, но сделать доступными для производных классов, поместив эти члены и функции в секцию с ключевым словом protected.
Вложенные классы
Сила абстракции может быть увеличена, если другой класс объявляется внутри данного класса. Такой класс называется вложенным. К вложенному классу применимы те же правила, что и к другим членам-данным. Если он объявлен в секции private включающего класса, то его могут использовать только члены включающего класса. Если он объявлен в секции public включающего класса, то его могут использовать и программа пользователя, сам включающий класс. Следующий небольшой пример показывает, что члены-функции включающего класса работают с ним как с произвольным невложенным классом.
class Outer {

private: class Inside {

private : int * value ;

public : Inside ( ) { value = new int [100] ; }

~Inside ( ) { delete value ; }

} // Конец класса Inside

Inside * p ; // Указатель на объект типа Inside

public : Outer ( ) { p = new Inside ; } // Конструктор класса Outer

~Outer ( ) { delete p ; } // Деструктор класса Outer

}; // Конец класса Outer
Основная причина использования таких классов - уменьшение количества глобальных имен. Если один класс используется только для реализации другого класса ( в приведенном примере класс Inside используется только как тип приватного указателя р), имеет смысл сделать его вложенным.
Если бы класс Inside находился в секции public, то программа могла бы обращаться нему, например, создавать объекты не только класса Outer , но и класса Inside.

Outer p1 ; // создание объекта класса Outer

Outer::Inside p2 ; // создание объекта класса Inside.

Следует обратить внимание на синтаксис Outer::Inside для доступа к классу Inside. С++ вводит новую область видимости для идентификаторов, объявленных внутри класса. Такие идентификаторы видимы только в сочетании с объектом класса или именем класса с использованием операции области видимости :: .
Конструктор. Инициализация объекта
Как следует из названия конструктор - это функция, которая строит объект данного класса. С одной стороны конструктор не обязателен, а с другой он выполняет функции, обойтись без которых практически невозможно. Конструкторы перегружаемы, часто в классе объявляется не один, а несколько конструкторов с различными сигнатурами, но все они не имеют возвращаемого значения, то есть в объявление конструктора не следует включать никакой тип, даже void. Если в классе отсутствуют конструкторы, то компилятор автоматически создает один конструктор для данного класса. Имя конструктора всегда совпадает с именем класса. Таким образом, задача конструктора - создать в памяти объект и инициализировать, если это необходимо, его члены-данные. При этом создание объекта происходит по одному из следующих трех вариантов:

- создание объекта с инициализацией по умолчанию;

- создание объекта со специальной инициализацией;

- создание объекта путем копирования других объектов.

Каждый вариант предусматривает свой конструктор.
Конструкторы по умолчанию и конструкторы с аргументами
Конструктор, объявляемый без аргументов, называется конструктором по умолчанию. Естественно, любой класс может иметь не более одного конструктора, не имеющего аргументов. Такие конструкторы обычно инициализируют данные, впоследствии используемые другими членами - функциями.
Большинство конструкторов в типичных классах языка С++ имеет аргументы. Перегрузка конструкторов очень удобна, поскольку появляются различные способы инициализации объекта. Рассматриваемый ниже пример демонстрирует использование трех различных конструкторов.
class Stack

{ private : char * s ;

int max_len;

int top ;

public : Stack ( ) ;

Stack ( int ) ;

Stack ( char *, int) ;

}; // Конец объявления класса Stack

Stack:: Stack ( ) // Определение первого конструктора

{ s = new char [512];

max_len = 512 ;

top = 0 ;

}

Stack:: Stack ( int size ) // Определение второго конструктора

{ s = new char [size] ;

max_len = size ;

top = 0 ;

}

Stack:: Stack ( char * p, int size ) // Определение третьего конструктора

{ s = p ;

max_len = size ;

top = 0 ;

}

void main ( ) {// Использование конструкторов

{ StackS ; //Правильное объявление переменной типа

// Stack, автоматический вызов 1-го конструктора

Stack S11( ) ; //Ошибка ! Это объявление функции, // возвращающей значение типа Stack.

Stack S1 = Stack ( ) ; // Правильный вызов 1-го конструктора

Stack S2 (1000) ; // Правильный вызов 2-го конструктора

Stack S3 ( “12345” , 5 ,0) ; // Правильный вызов 3-го конструктора

Stack* p = new Stack (500);// Создание стека с помощью операции

// new. Вызов 2-го конструктора...

. . . // значение параметра = 500 .

}



Конструкторы копирования
Часто, создавая объект, не обязательно инициализировать его члены-данные новыми значениями. Нужно просто создать объект “такой же” как уже существующий. Для этого случая необходим специальный конструктор, называемый конструктором копирования. Данный конструктор всегда имеет вид Х (Х&), где Х - имя класса. Прежде всего необходимы конструкторы копирования для классов, используемых динамические данные, так как только с помощью этих конструкторов можно предусмотреть создание новых динамических данных и копирование в них необходимых значений. Другими словам, данные конструкторы - единственный способ корректного создания копии существующего объекта. Например, рассматриваемый в качестве примера стек может содержать следующий конструктор:

class Stack

{ private : . . . // Объявления для секции private

public : Stack ( Stack &) ; // Конструктор копирования

. . . // Остальные объявления

}; // Конец объявления класса Stack

Stack:: Stack (Stack & S ) //Определение конструктора копирования

{ max_len = S.max_len ; // Определение длины стека

s = new char [max_len ]; //Выделение динамическ. памяти ля стека

strcpy ( s, S.s ) ; // Копирование содержимого стека S в

// создаваемый стек

top = S.top ; // Задание номера текущего элемента

};

void main ( )

{ . . . // Объявления переменных

Stack S3 ( “12345” , 5 ,0) ; // Вызов 3-го конструктора

Stack S4 ( S3 ) ; // Вызов конструктора копирования

Stack S5 = S3 ; //Другая форма вызов конструктора копир

. . . // копирования.

}

Если конструктор копирования будет отсутствовать, то объявление

Stack S5 = S3 ;

будет означать вызов конструктора, создаваемого компилятором по умолчанию для поверхностного, то есть побайтового копирования структуры. При этом указателю S5.s будет присвоено значение указателя S3.s, и при удалении одного объекта автоматически будет уничтожено содержимое другого.
Приватные конструкторы
Конструктор не обязательно является общедоступной функцией. Он может находиться в секции private. Такие конструкторы не могут быть вызваны обычными пользователями, а требуют выполнения одного из следующих условий:

- конструктор вызывается дружественным классом ;

- конструктор вызывается внутри общедоступной функции.

Кроме того, недопустимо создание глобальных, статических, автоматических объектов класса, в котором имеется приватный конструктор.
Деструкторы
Деструкторы - специальный вид функций класса, предназначенный для автоматического разрушения объектов. В языке С++ определены следующие правила относительно деструкторов:
1. Имя деструктора начинается со знака ~ (тильда), за которым следует с имя класса. Объявление деструктора не содержит указания типа возвращаемого значения.
2. Деструктор не может иметь параметров, следовательно, класс не может иметь более одного деструктора. Если деструктор явно не объявлен в классе, то компилятор создаст его автоматически. Обычно деструктор нужен в классе, содержащем динамические данные.
3. При удалении объекта операцией delete или при выходе объекта из области видимости исполняющая система вызывает деструктор данного класса автоматически.
4. Деструкторы могут быть виртуальными.
В рассматриваемом примере деструктор может иметь следующий вид:

~Stack ( ) { delete s ; }
Если класс Stack не будет содержать этого деструктора, то создаваемый автоматически деструктор будет удалять только структуру, отвечающую членам-данным, динамическая память, адрес которой содержится в указателе s, не будет освобождена, а ее адрес будет потерян.
Иногда имеет смысл вызвать деструктор как явную обычную функцию. Рассмотрим пример, содержащий уже знакомый нам класс Stack .

. . .

class Stack

{ private : . . .

public : ~Stack ( ) { delete s ; }

. . .

}; // Конец объявления класса Stack

. . .

void main ( )

{ char * p1 = new char [ sizeof ( Stack )] ; // Оператор 1

Stack * p2 = new (p1) Stack ( 1024 ) ; // Оператор 2

p2 -> ~Stack ( ) ; // Оператор 3

p2 = new (p1) Stack ( 2048 ) ; // Оператор 4

}

Оператор 1 объявляет указатель р1 и выделяет в динамической памяти область, как последовательность байтов, необходимую для размещения переменной типа Stack . Указатель р1 получает адрес этой памяти. Оператор 2 объявляет указатель р2 и создает объект типа Stack, размещая его с адреса р1.
Оператор 3 - явный вызов деструктора В результате действия деструктора будет удалена динамическая область размером 1024 байта и динамическая область, связываемая с указателем р2. Но адрес этой области сохранится в указателе р1. Оператор 4 по этому адресу разместит новый объект типа Stack и выделит для него новую динамическую область размеров 2048 б.
Как правило классы разрабатываются для обычных пользователей и их конструкторы и деструкторы объявляются общедоступными, т. е. помещаются в секцию public. Но иногда обычным пользователям запрещено разрушать созданные объекты. В этом случае используются приватные деструкторы. Однако Borland C++ запрещает создавать глобальные, статические и автоматические объекты класса, в котором имеется приватный деструктор. Можно лишь создать динамическую переменную данного типа, удалить эту динамическую переменную будет возможно, если создать в классе общедоступную функцию, разрушающую объект. Следующий пример иллюстрирует эту возможность.
class PrivateDestr {

private : int * value ;

~ PrivateDestr ( ) ; // Приватный деструктор



public : PrivateDestr ( ) { value = new int [1000] ; }

void Delete ( ) { delete this ; }

};

static PrivateDestr obj1 ; //Нельзя создать статическую переменную

PrivateDestr obj2 ; //Нельзя создать глобальную переменную

void main ( )

{ PrivateDestr obj3 ; // Нельзя создать автоматическую переменную

static PrivateDestr obj4 ; //Нельзя создать статическую переменную
PrivateDestr * Pobj = new PrivateDestr ;

delete Pobj ; // Нельзя вызывать стандартную операцию

// delete, т.к. она автоматически вызывает

// приватный конструктор

Pobj -> Delete ( ): //Можно вызвать любую функцию из секции

} // public.

Функция Delete ( ) вызывает стандартную операцию delete для освобождения области в динамической памяти, задаваемую указателем this. Данный указатель на текущий обрабатываемый объект всегда неявно передается в качестве первого параметра в любую член-функцию класса, исключая статические члены-функции.
Статические члены-данные и статические функции
Каждый объект класса имеет свою копию членов данных. Но иногда бывает необходимо, чтобы все объекты одного класса имели доступ к одному и тому же полю, а не к копии этого поля в каждом объекте. Такие члены-данные объявляются статическими с помощью ключевого слова static. Члены-данные, как правило, имеют тот же класс памяти, что и объект. Например, если объект объявлен автоматическим, то и его данные будут автоматическими. Статические данные являются исключением из этого правила. Когда в памяти создается объект со статическими данными, память под статические поля не выделяется, чтобы не создавать копии статических членов-данных.

class Example

{ public : static int value ;

int ident ;

} ;

Класс памяти для параметра value всегда остается статическим, даже если переменная класса Exаmple объявлена как автоматическая переменная. Доступ к статическим членам-данным отличается от доступа к нестатическим членам-данным, а именно: к статическому члену необходимо обращаться через операцию разрешения области видимости, независимо от объявления его приватным, защищенным или общедоступным. Функция main(), использующая класс Example, может иметь следующий вид:

int Example::value = 3; // Определение и инициализация

void main ( ) // статического члена данных

{ Example obj1 ; //Определение автоматических переменных

Example obj2 ;

obj1. value = 1 ; //Использование статического параметра

obj2. value = 10; //Повторное использование статического члена

// value

Example::value = 13 ; // Еще одно присваивание члену value

}

Таким образом, доступ к статическому члену класса можно получить несколькими способами:

· используя оператор точка, ( например, obj1. value = 1; ) ;

·используя оператор ->, если задан не объект, а указатель на него ;

·используя идентификатор класса с операцией разрешения области видимости без ссылки на конкретный объект;

·через член - функцию данного класса;

·через дружественную для данного класса функцию.

Когда статические члены-данные объявлены приватными, необходимо использовать последние три вида доступа.

Следует обратить внимание на то, что статические члены-данные можно объявлять, инициализировать и использовать даже тогда, когда не объявлено ни одного объекта данного класса.

Объявление статических членов-функций похоже на объявление статических членов-данных. Отличия от обычных членов-функций состоят в следующем:

·могут использоваться без ссылки на реальный объект, а просто по имени класса с разрешением операции области видимости;

·не имеют неявно передаваемого указателя this, поэтому не имеют доступа к другим членам класса, если указатель this (или указатель на конкретный объект) не передать им в явном виде.

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

int v1, v2, v3 ; // определение глобальных переменных

class Simple

{ public : static void sum ( ) ;

};

Simple::sum ( ) { v3= v1 + v2 ; }

void main ( )

{ Simple s1;

s1.sum ( ) ; //первый способ обращения к статической // функции-члену

Simple::sum ( ) ; // второй способ обращения к члену

void Simple::( * fp ) ( ) = & Simple::sum;

// объявление и инициализация указателя на

//функцию-член класса Simple, не имеющую // аргументов.

( * fp) ( ) ; // вызов статической функции через

} // указатель
Члены-данные - константы, ссылки, объекты другого класса

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

<имя члена_данного> (<аргументы>) {<имя члена-данного>( <аргументы> ) }*.

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

class Example

{ private : int i ;

const int j ;

int & ref ;

public : Example ( int ) ;

};

Example:: Example ( int i_val, int j_val , int ref_val )

: j ( j_val ), ref ( ref_val ) // список инициализации

{ i = i_value ; } // тело конструктора

void main ( )

{ int k = 1 ;

Example object ( 1 , 2 , k );

. . .

}

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

class A

{public : char av ;

A ( char v1 ) { av = v1 ; }

} ;

class B

{ public : char bv ;

A varA ;

B (char v1, char v2) : varA (v1) {bv = v2 ;} // Конструктор

// B ( char v1 , char v2 ) : varA ( v1 ) , bv ( v2) { }

// Другая допустимая форма этого же конструктора

} ;

class С

{ public : char сv ;

B varB ;

C ( char v1 , char v2, char v3 ) : varB (v1, v2 ) { cv = v3 ; }

// C ( char v1 , char v2, char v3 ) : varB (v1, v2 ) , cv ( v3 ) { }

// Другая форма конструктора С

} ;

.

В функции main можно следующим образом объявлять переменные данных классов:

void main ( )

{ С object ( X’ , Y’ , Z’ );

. . .

}

Порядок вызова конструкторов в такой ситуации определяется порядком вложенности объектов. В рассматриваемом примере будем иметь следующий порядок выполнения конструкторов и следующий порядок инициализации полей объекта object:

выполнение конструктора С

выполнение списка инициализации конструктора С

выполнение конструктора В

выполнение списка инициализации конструктора В

выполнение конструктора А (присваивание в теле конструктора )

присваивание в теле конструктора В

присваивание в теле конструктора С.

Члены - функции с константным указателем const
Модификатор const, стоящий после списка аргументов перед телом функции применяется к ней для ограничения ее доступа к переменным. Это новшество языка С++ касается указателя this, неявно передаваемого в любую нестатическую член-функцию. Ключевое слово const преобразует указатель this в указатель на константный объект. Поэтому такой член-функции запрещено вносить любые изменения в члены-данные объекта. То есть данный механизм используется только для защиты объекта от изменения. Библиотека классов Borland C++ такое объявление широко использует.

class Example

{public : int func ( ) const {. . .}

. . .

};
Вопросы для самопроверки:
Что такое класс?
Понятие вложенности класса?
Основные границы доступа к классу.
Что такое конструктор?
Что такое деструктор?
Понятие объекта?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 2. Перегрузка функций и операций в языке С++ (2 час.)
Цели и задачи: Изучить перегрузку функций и операций в языке С++.
Учебные вопросы: Понятие точное соответствие, стандартное преобразование.
Учебная информация:

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

1) ищется экземпляр перегружаемой функции, типы формальных параметров которой точно соответствуют типам фактических параметров при вызове функции;

2) если точного соответствия не фактических аргументов какой-либо сигнатуре не найдено, то делается попытка применения стандартных преобразований к фактическим аргументам вызывающей функции ;

3) если нет стандартных преобразований, то делается попытка достичь соответствия типов, применяя пользовательские преобразования.

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

Точное соответствие
1. Точное соответствие имеет место тогда, когда типы совпадают типы фактических параметров вызываемой функции и формальных параметров перегружаемой функции, например:

void func ( int ) ;

void func ( long * ) ;
. . .
func ( 10 ) ; //точное соответствие с экземпляром void func ( int );
. . .

Специальные случаи точного соответствия :

· фактические аргументы типа char, short и константа 0 точно соответствуют типу int;

· фактический аргумент типа float точно соответствует типу double.

Стандартные преобразования

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

·любой числовой тип в соответствии с данной схемой может быть преобразован к любому числовому типу, при этом сложность типа может как повышаться, так и понижаться;

·константа 0 может быть преобразована к любому типу или указателю на любой тип ( в этом случае указатель будет иметь значение NULL) ;

·любой указатель может быть преобразован к указателю на void ;

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

void func( double * ) ;

void func( void * ) ;

void func ( char ) ;

void func ( char * ) ;

int i;

double c;

func ( i ) ; // преобразование к void func ( char ) ;

func ( &i ); // преобразование указателя к void func (void * ) ;

func ( с ) ; // преобразование к void func ( char ) , так как все // остальные экземпляры функции имеют указатель в // качестве аргументов

func ( &с ) ; // точное соответствие с экземпляром void func (double *)

func ( “строка”); // точное соответствие с экземпляром void func ( char * )

func ( F’ ) ; // точное соответствие с экземпляром void func (char )



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

void func (double ) ;

void func ( long ) ;

func ( F’ ) ; // Ошибка, т.к. допустимы два преобразования :

// char -> double , char -> long

В таких ситуациях рекомендуется явно указать требуемое преобразование, например:

func ( long ( F’ ) ) ; // Соответствие с экземпляром void func (long ).

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

void func ( int, char , double * ) ;

void func ( int, long , double * ) ;

double a = 5, * pd ;

pd = &a;

func ( 6, A’ , pd );

В данном примере есть точное соответствие по первому и третьему аргументам для любого экземпляра перегружаемой функции. Но первый экземпляр имеет соответствие и по второму аргументу, а второй экземпляр требует преобразования char –> long. Поэтому будет выбран первый экземпляр функции.

Пользовательские преобразования

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

void func ( char *) ;

void func ( char ) ;

void func ( double * ) ;

class String

{ private :

int size ;

char* str ;

public :

String ( char * ) ;

operator char * ( ) { return str ; }

} ;

String S ;

func ( A’ ) ; // Точное соответствие с экземпляром void func(char )

func ( 10 ) ; // Стандартное преобразование к void func ( char )

func ( S ) ; // Пользовательское преобразование к void func(char * )

Для третьего случая нет ни точного соответствия, ни стандартного преобразования. Но класс String определяет преобразование переменной типа String в переменную другого типа, в нашем случае в переменную стандартного типа char*. Данное преобразование задается операторной член-функцией приведения типа, объявление которой всегда имеет вид :

operator < новый тип> ( )

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

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

void func (void *);

void func ( double );

class String

{ private : int size ;

сhar * str ;

public : operator int ( ) { return size ; }

operator char ( ) { return str ; }

String ( int ) { . . . } // Конструктор

. . . // Другие функции-члены

} ;

void main ( )

{ func ( 10 ) ; // Стандартное преобразование к void func (double )

long L = 1000 ; // Объявление переменной типа long

String S ( L ) ; //Объявление объекта класса String и вызов

// конструктора для инициализации объекта, для

// аргумента L будет выполнено стандартное

//преобразование long->int

func ( S ) ; // Комбинация пользовательского ( String -> int ) и

// стандартного ( int -> double ) преобразований



Таким образом, в качестве пользовательских преобразований могут использоваться конструкторы (в нашем примере конструктор String ( int ) переменную типа int преобразует в переменную типа String ) и операторные члены - функции приведения типа. Рассмотрим еще один пример, иллюстрирующий ошибку в одновременном использовании конструктора и операторной функции для преобразования типа.

class A

{ private : int a;

public : A ( ) { . . . } // Конструктор по умолчанию

A (int ) { . . . } // Конструктор с аргументом int

A ( B & ){ . . . } // Конструктор , аргумент которого - // ссылка на объект класса В

} ; // Конец класса А

class B

{ private : int b;

public : B ( ) { . . . }

B (int ) { . . . }

operator A ( ) { return A (b); }

// Операторная член-функция приведения к типу В

} ; // конец класса B

void main ( )

{ B objB ;

A objA = objB ; // Ошибка! Двусмысленность в выборе функции:

} // конструктора А ( В & ) или операторной функции

// operator A ( ) из класса В.

Перегрузка операций

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

Синтаксис перегрузки операций задается следующим образом:

< тип результата > operator < знак операции > ( <список аргументов> )

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

. - селектор члена структуры или класса ;

* - оператор доступа к члену структуры или класса через указатель;

:: - оператор разрешения области видимости;

? : - арифметический условный оператор.

При перегрузке операций следует соблюдать ряд правил:

1. Нельзя вводить новый символ операции. Например, нельзя использовать знак @ в качестве новой операции.

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

3. Нельзя определить новую операцию для встроенных типов. Например, нельзя ввести операцию “+ “ для массивов, но можно объявить класс, имеющий массив в качестве своего члена-данного, и определить операцию “ + “ для данного пользовательского типа.

4. Нельзя изменить приоритет операций. Например, мы хотим определить для пользовательского типа complex операцию возведения в степень и выбираем для данной операции знак “^”. Однако выражение 1 + е ^ i * pi будет восприниматься не как

1 + е ^ ( i * pi) , а как ( 1 + е) ^ ( i * pi ) ,

так как операция “^” имеет приоритет 7, а операция “+” - 12. В качестве возведения в степень имеет смысл взять знак другой операции с приоритетом выше 13 (приоритет операции “*” ). Но бинарной операции с подходящей мнемоникой и соответствующим приоритетом нет. Поэтому в данной ситуации рекомендуется использовать либо дополнительные скобки 1 + ( е ^ i * pi), либо функциональную запись вида + F( i, pi).

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

void operator ! ( ) { . . . } // ! - знак встроенной унарной операции

// отрицания ,то допустимо использование

// ! а , но не а ! .

Правда, в Borland C++ это ограничение снято.

6. Операции ++ и – – допускают как префиксное, так и инфиксное использование.

7. Так как в перегружаемой операции по крайней мере один аргумент всегда пользовательского типа, то перегружаемая операция является либо членом - функцией, либо дружественной функцией класса.

8. Унарная член- функция не имеет аргументов, бинарная при объявлении имеет только один аргумент, определяющий второй операнд функции. В качестве первого аргумента, как и любым нестатическим членам-функциям им передается указатель this на текущий объект. Рассмотрим в качестве примера перегрузку операторной член-функции “+” для класс комплексных чисел.

complex complex::operator + ( complex c )
{ return complex ( this-> re + c.re , this -> im + c.im ; }

9. Так как дружественным функциям не передается неявный указатель this, то при объявлении операторных дружественных функции объявляются все аргументы.

10. Операции [ ], ( ), -> перегружаются только как члены-функции класса, поэтому имеют только один явный аргумент - второй операнд функции.

Рассмотрим несколько примеров иллюстрирующих перегрузку операторных функций.

Перегрузка операции индексирования [ ]

Данная функция всегда перегружается только как бинарная член-функция класса. Если х есть объект класса Х , то выражение х [ у ] интерпретируется как х. operator [ ] (у) , где второй операнд у может иметь произвольный тип. Обратимся вновь к классу String.



class String

{ private: char * str ;

int size ;

public: String ( char * );

char & operator [ ] ( int ) ;

} ;

String::String (char * s) { . . . ; } // Определение конструктора

char & String::operator [ ] ( int i ) // Определение операции [ ]

{ if ( i <0 ) { cerr << “ Выход за границы массива “ ;

return str [ 0 ] ;

};

else if ( i >size ) { cerr << “ Выход за границы массива “ ;

return str [ size-1 ] ; }; else return str [ i ] ;

}

Данная член-функция возвращает ссылку на элемент строки str, это дает возможность использовать возвращаемое значение, как в правой, так и в левой части оператора присваивания.

void main ( )

{ String S ( “Индексирование “ ) ;

char с = S [ 9 ] ;

S [ 10 ] = ;

String А [ 10 ] ; // объявление массива объектов типа String

String В (“Строка 1 “ ) ; // объявление объектов класса String

String С (“Строка 1 “ ) ;

А [ 0 ] = В ; // использование встроенной , неперегружен-

А [ 1 ] = С ; // ной операции индексирования

for ( int i =0 ; А [ 0 ] [ i ] != \ 0 ; i++ )

cout << А [ 0 ] [ i ] ;

}

В операторах А [0] = В ; и А [1] = С ; используется встроенная операция индексирования, так как она применяется не к объекту типа String, а к массиву объектов.. Результатом этой операции является элемент массива, то есть объект типа String. Поэтому в выражении А [0] [i] вторая операция индексирования есть перегруженная операция, применяемая к объекту А [0] , то есть данное выражение интерпретируется как А[0].operator [ ] ( i ) .

Перегрузка операции ->

Данная функция также перегружается только как член - функция класса, причем как унарная функция левого операнда. При этом левый операнд может быть либо ссылкой на объект класса либо объектом класса. Если х есть объект класса Х, то выражение х -> интерпретируется как х.operator -> ( ), а выражение х -> у интерпретируется как ( х.operator -> ( ) ) -> у , где вторая операция -> является уже стандартной, имеющей два операнда.

Таким образом, перегружаемая пользователем операция -> должна возвращать указатель на объект так, чтобы вызов встроенной операции ->у был корректным. Если же данная операторная функция возвращает не указатель, а объект некоторого класса, то будет сделана попытка вызвать пользовательскую операцию -> . По крайней мере одна из таких пользовательских операций должна вернуть указатель на объект класса. Обычно такие перегрузки имеют смысл при создании иерархии классов, когда членами классов являются указатели или объекты другого класса. Рассмотрим пример.


class N

{ public : char str [20] ;

};

class Node

{ private: N* ptrN ;

public : N* operator-> ( ) {return ptrN ;}

// Возвращает указатель на объект класса N

Node * next ; // Указатель на следующий

}; // элемент класса Node

class List

{ private: Node * star; // Указатель на первый элемент

// класса Node

public : Node operator -> ( ) { return * start ; }

// Возвращает объект класса Node –

// значение первого элемента списка, т.е. // по содержимое адресу start .

List ( ) {. . . } // Конструктор по умолчанию

. . .. // Функции - члены

};

void main ( )

{. . .

List MyList ; // Объявление объекта типа List

. . .

cout << MyList -> str << \n’ ;

}

Структуру типа List схематично можно представить следующим образом:

Рассмотрим конструкцию MyList -> str более подробно:

1. Выражение MyList -> использует операцию из класса List и возвращает значение первого элемента списка MyList, то есть объект класса Node. Поэтому повторно будет вызвана операция ->, но уже из класса Node.

2. Вторая операция ->, применяемая к объекту класса Node , возвращает член - данное ptrN , которое является указателем на объект класса N.

3. Для полученного указателя вызывается встроенная операция ->, с помощью которой выбирается поле str .

Перегрузка операций new и delete

В первых версиях языка С++ фирмы АТ&Т было запрещено использовать перегрузку данных операторов. Теперь это ограничение снято, и пользователь имеет полный контроль над распределением и освобождением динамической памяти с помощью собственных операторов new и delete. Полагают, что в этом случае можно более эффективно распределять память. Например, если распределять память большими кусками, а не байтами, то память будет не только быстрее выделяться , но и станут ненужными частые запросы на распределение памяти. Перегрузка этих операторов обладает некоторыми особенностями:

- прежде всего операторы new и delete являются статическими, даже, если не объявлены таковыми. Причина этого вполне понятна: объект может быть сконструирован ( инициализирован в памяти ) только после того, как эта память ему выделена ( что выполняется оператором new) , и разрушен до того, как освободится память;

· оператор new можно перегружать произвольное число раз, но все экземпляры должны различаться своими сигнатурами, то есть числом и типом параметров;

· оператор delete всегда должен быть объявлен с одним параметром типа void * и всегда должен возвращать void. Поэтому в классе может быть не более одного оператора delete.

Рассмотрим пример, иллюстрирующий эти особенности.

#include

class Bitmap

{ private: int w , h;

char bit_map [20];

Bitmap ( int, int, int);

void Set ( int, int , int ) ;

void * operator new (size_t, int ) ; //Объявления перегружаемых

void operator delete (void * ) ; // операций

void print ( ) // Операция вывода

{ cout << "width, height" << w << ' ' << h << ' ' << bit_map[0] << ' \n'; }

};

Bitmap::Bitmap ( int x, int y , int color ) // Определение конструктора

{ w = x; h = y;

for ( int k = 0; k < 20 ; bit_map [ k++] = color ) ;

}

void * Bitmap::operator new ( size_t, int k )

{ return new char [k] ; }

void Bitmap::operator delete ( void * a )

{ delete a ; }

void main ( )

{ Вitmap * im = new ( sizeof (Bitmap)*10) Bitmap ( 0, 0, 65 ) ;

// Выделение памяти для 10 объектов // типа Bitmap и инициализация объектов

im -> print( ); // Вывод 0-го объекта

for ( int i = 1; i < 10 ; i++ )

{ im[i] = Bitmap ( i, i, 65+i ); // Изменение полей i -го объекта

( i+im ) -> print ( ) ; // Вывод i -го объекта

};

delete im [10]; // Удаление 10 объектов

im = new ( 30 ) Bitmap (10,10,75 );// Выделение памяти, достаточ- // ной только для одного объекта

im - > print( ); // Обращение к объекту

delete im ; // Удаление объекта

}

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

Вitmap * im = new ( sizeof (Bitmap)*10) Bitmap ( 0, 0, 65 ) ;


Перегружаемый оператор new может выделить любое количество памяти, не обязательно кратное размеру объекта, например,

im = new ( 30 ) Bitmap (10,10,75 );



Перегрузка префиксных и постфиксных операций Borland C++

В языке С++ все унарные операции (за исключением ++ и --) являются префиксными. Но требования пользователей заставили разработчиков языка внести некоторые изменения в этот вопрос. Предложенное ими решение не удовлетворяет всех пользователей. Все унарные операции в Borland C++ , начиная с третьей версии, можно использовать как в префиксной, так и постфиксной формах, например:

а = ! b ; // префиксное отрицание, b = ! b ; а = b ;

а = b ! ; // постфиксное отрицание, а = b ; b = ! b ;

Но перегружаются эти формы одного и того же оператора как отдельные операторы. В Borland - реализации приняты следующие соглашения:

- если операция перегружается без параметров, то имеется в виду префиксная операция;

- если операция перегружается с параметров типа int, то имеется в виду постфиксная операция. При этом аргумент в теле перегружаемого оператора не должен использоваться, он служит только флажком для компилятора, а во время вызова оператора ему всегда присваивается нулевое значение. В качестве примера рассмотрим перегрузку оператора ++ .

class X {

public : int value ;

X & operator ++ ( ) ; // префиксная операция

X & operator ++ ( int ) ; // постфиксная операция

};

X & X ::operator ++ ( ) { value += 1 ; return * this ; }

X & X ::operator ++ ( int ) { value += 1 ; return * this ; }

void main ( )
{ X x ;
x ++ ; // х.value увеличивается на единицу
++ x ; //х.value еще раз увеличивается на единиц
Вопросы для самопроверки:
Что такое точное соответствие?
Что такое стандартное преобразование?
Описать перегрузку функций.
Описать перегрузку операций.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 3. Наследование (2 час.)
Цели и задачи: Изучить наследование.
Рассмотреть парадигмы объектно ориентированного программирования.
Учебные вопросы: Понятие наследования. Спецификаторы и правила доступа при простом наследовании. Инициализация членов - данных при наследовании.
Учебная информация:

Наследование в природе играет громадную роль. Жизнь в своей бесконечной сложности без наследования была бы невозможна. Еще в начале 60-х годов было высказано предположение о том, что наследование в программировании могло бы оказать большую пользу , во-первых , для подготовки кода к будущему использованию, а, во-вторых, для сокращения сложности кода. В языке С++ одно только наследование придает классам огромную силу. Механизм наследования применяется только к абстрактным типам, то есть к классам и их характеристикам. Переменные стандартных типов ничего не могут наследовать от других переменных, а обычные функции ничего не наследуют от других функций.

Наследование позволяет последовательно строить и расширять классы, построенные вами или еще кем-то. При этом создаются структуры нарастающей сложности, но они достаточно просты в отладке и по внутренней структуре.

Язык С++ отличается от большинства объектно-ориентированных языков еще и тем, что допускает не только простое, то есть одиночное наследование, но и множественное наследование, когда порождаемый класс наследует поведение всех своих предков. Используя терминологию С++, класс, от которого происходят новые классы назовем родительским или базовым, а новый - производным классом.

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

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

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

Деструкторы базового класса предназначены для автоматического вызова при удаления объекта. Программе не разрешено вызывать его явно.

Операторы new, delete и операторы присваивания, определенные пользователем.

Любые отношения дружественности , объявленные в классах дерева наследования , не распространяются по дереву ни вверх ни вниз.
Спецификаторы и правила доступа при простом наследовании

Производные классы объявляются с использованием спецификаторов private или public следующим образом:

class Base {

private : . . .

protected : . . .

public : . . .

};

class PrivateDer : private Base { // Спецификатор private можно опустить,

private : . . . // так как он предполагается по умолчанию

protected : . . .

public : . . .

};

class PublicDer : public Base { // Спецификатор public опускать нельзя

private : . . .

protected : . . .

public : . . .

};

void main ( ) {

Base aBase ; // Объявление объекта базового класса

PrivateDer aPriv ; // Объявление объекта класса PrivateDer

PublicDer aPub ; // Объявление объекта класса PublicDer

}

Правила доступа к полям и функциям классов и объектов можно представить с помощью следующей таблицы, где приняты обозначения: “d “ - член- данное произвольного типа, “f ” - произвольная член-функция.

Рассмотрим небольшой пример с использованием наследования. Опять обратимся к классу String.

#include

class String

{protected: char * str ; //Члены-данные str и size должны быть

int size; // доступны в производном классе

String ( ) {str ="ABCDE"; size=256;}

String (int) { . . .} // Различные варианты конструктора

String (char *) { . . .}

String (String&) { . . .}

~String ( ) { . .. } // Деструктор

String& operator == (String & S) { . . .} // Набор перегруженных операций

String& operator != (String & S) { . . .} // для класса String

String& operator < (String & S) { . . .}

String& operator > (String & S) { . . .}

};

Но в этом классе не заложены возможности ввода или вывода переменной типа String с использованием, например стандартных устройств. Поэтому возможности данного класса можно расширить, вводя производный класс IOString с дополнительными функциями ввода - вывода.

class IOString : public String

{ public : IOString () : String ( ) { } // Конструктор производного класса

friend ostream& operator << ( ostream& os, IOString& S) { return os<< S.str<<'\n';} // Перегрузка операции вывода строки

// на стандартное устройство

friend istream& operator >> ( istream& os, IOString& S)

{ cout << "input max_size "; // Перегрузка операции ввода в строку

is >> S.size ; //из стандартного устройства ввода

S.str = new char [S.size];

cout << "input string " ;

return is >> S.str ;

}

}; // Конец класса IOString

void main ( )

{ IOString SS ;

cin >> SS ; // Использование перегруженных опе-

cout << SS ; // раций ввода-вывода

}
Инициализация членов - данных при наследовании

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

IOString () : String ( ) { }.

Список инициализации содержит конструктор базового класса. Так как собственных полей у класса IOString нет, тело его собственного конструктора пусто. Если конструктору базового класса необходимо передать параметры, то они должны быть указаны в списке аргументов производного класса. Другого обращения к конструктору базового класса, иначе, чем через список инициализации, нет. Если конструктор базового класса отсутствует в списке инициализации, то наследуемые члены - данные остаются неинициализированными. Их можно определить с помощью других член - функций базового класса из секций public или protected , если таковые имеются.

Рассмотрим более подробно инициализацию с использованием конструктора вида Х ( Х &), т.е. инициализацию объекта производного класса другим объектом этого же класса. Вообще говоря, возможны четыре варианта :

1) Оба класса, и базовый и производный, не содержат конструкторов данного вида. В этом случае используется предопределенная (встроенная) операция поэлементного копирования. Вначале копируются элементы базового класса, затем производного. Например, если в функцию main( ), рассмотренную выше добавить оператор объявления с инициализацией IOString ХХ = SS; то будет выполнено поэлементное копирование объекта SS в объект ХХ.

2) Базовый класс содержит конструктор данного вида, а производный - нет. Для инициализации наследуемых членов автоматически будет конструктор, а для инициализации собственных членов - данных производного класса - встроенная операция поэлементного копирования.

3) Базовый класс не содержит конструктор данного вида, а производный содержит. Для инициализации наследуемых членов будет вызвана встроенная операция поэлементного копирования, а для инициализации собственных членов - данных - конструктор производного класса.

4) Оба класса, и базовый и производный, содержат конструкторы данного вида. Как уже отмечалось выше, конструктор производного класса отвечает за инициализацию наследуемых членов - данных. Поэтому при определении конструктора производного класса в списке инициализации нужно указать конструктор базового класса.

Следует обратить внимание на порядок вызова конструкторов и деструкторов при создании и удалении объекта производного класса. Порядок этих вызовов фиксирован. Допустим имеется дерево наследования:

class First { . . . } ;

class Second : public First { . . . } ;

class Third : public Second { . . . } ;

При создании объекта класса Third конструкторы

вызываются в следующем порядке:

First:: First( ) , Second:: Second ( ) , Third:: Third ( ) .


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

Деструкторы производных классов вызываются в порядке, обратном вызову конструкторов. Вначале уничтожаются специализированные характеристики (поля) объекта, а затем общие. Таким образом, порядок вызова деструкторов, сгенерированных для объекта класс Third , будет следующим:

~Third:: Third ( ) , ~Second:: Second ( ) , ~ First:: First( ) .



Множественное наследование

Язык С++ имеет механизм, позволяющий производному классу наследовать поведение (то есть структуры и методы обработки ) сразу нескольких базовых классов. Синтаксис такого множественного порождения следующий:

class < имя > : < тип доступа > < имя_базового класса_1> ,

: < тип доступа > < имя_базового класса_2> ,

. . .

: < тип доступа > < имя_базового класса_N>

{ определение класса } ;

Производный класс наследует все компоненты базовых классов, обращаться может (как и в случае одиночного наследования ) к элементам секций public или protected базовых классов.

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

class B { . . . } ; // Базовый класс

class D : B , B { . . . } ; // Ошибка! Класс В передан дважды

class Х : public B { . . . } ;

class Y : public B { . . . } ;

class Z : public X, public Y // Допустима передача класса В через

{ . . . } ; // классы Х и Y.


Конфликт имен

Правила доступа к производным классам и их объектам такие же, как при одиночном наследовании, но в отдельных случаях возникают проблемы, например:

1) производный и один из базовых классов используют одно и то же имя;

2) два различных базовых класса используют одно и то же имя.

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

class Base1

{ protected : int N ;

public : Base1 ( int n1) { N = n1 ; } // Конструктор базового класса

} ;

class Base2

{ protected : int N ;

public : Base2 ( int n2 ) { N = n2;} //Конструкторпроизводного класса

} ;

class Der: public Base1, public Base2

{ private : char ch ;

public : Der ( char c1, int n1, int n2) : Base1 ( n1 ) , Base2 ( n2 )

{ ch = c1 ; }

void print ( ) { cout << \n’ << Base1::N << << \n’ << Base2::N

<< \n’<< ch <<\n’ ;

}

} ;

void main ( )

{ Der d ( A’ , 10, 20 ) ; // Результат работы данной программы

d.print ( ) ; // 10 20 А

}

В подобных ситуациях указания имени класса достаточно для однозначного определения членов - данных или членов – функций.

Вернемся к примеру с классами B, X, Y, Z . Иерархия порождения классов в данном случае следующая:

Объект класса Z дважды включает в себя объекты класса В, наследуемые по правой и левой ветвям, поэтому структуру объекта класса Z можно представить так:

Следует отметить, что в отличие от членов - данных члены - функции не тиражируются, нет двух копий какой-либо член - функции В::F( ). Но любая член - функция класса В работает либо с данными, наследуемыми от класса Х, либо с данными, наследуемыми от класса Y. Поэтому ошибкой является непосредственное обращение к F( ), в данном случае необходимо уточнение ветви (не класса!) , через которую осуществляется обращение к функции F( ) (это же справедливо и для членов - данных класса В):


void main ( ) {

Z zobj ; // Объявление объекта класса Z.

zobj . F ( ) ; // Ошибка !

zobj. В:: F ( ) ; // Ошибка !

zobj. Х:: F ( ) ; // Правильно, обработка структуры В, наследуемой через Х.

}

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

class B

{ public: int Fun ( ) { return 1; }

};

class X : virtual public B // Подробно о виртуальных базовых

{ public: int Fun ( ) { return 2; } // классах см. п. 7.7

}; // Здесь заметим, что Z содержит

class Z : virtual public B, public X // только одну копию структуры В.

{ public: int Fun ( ) { return 3; }

};

void main ( )

{ Z zobj ; // Объявление объекта класса Z.

int k = zobj . Fun ( ) ; // Вызов функции Х:: Fun ( )

}

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

а) Классы Х, Y содержат функции б) Классы В1 , Y содержат функции c

c одинаковыми именами. Классы Х, одинаковыми именами Классы В1, Y

Y находятся на одном уровне ие – - находятся на разных уровнях иераррархии.



Инициализация и порядок вызова конструкторов

На одном из предыдущих примеров рассмотрим инициализацию объектов производного класса:

class Base1 { . . . } ;

class Base2 { . . . } ;

class Der : public Base1, public Base2 { . . . } ;

Существует правило , определяющее порядок вызова конструкторов, стоящих в списке инициализации.:

1) В первую очередь выполняются конструкторы базовых классов в порядке их следования при объявлении производного класса. В данном примере при объявлении класса Der список базовых классов имеет вид public Base1, public Base2. Порядок объявления базовых классов определяет порядок вызова конструкторов, независимо от того, в каком порядке стоят конструкторы базовых классов в списке инициализации. Поэтому, даже если список инициализации конструктора Der имел бы вид Base2 (n2 ), Base1(n1 ), порядок вызова конструкторов был бы Base1 ( n1 ) , Base2 ( n2 ).

2) На втором шаге выполняются конструкторы из списка инициализации, предназначенные для инициализации членов - данных объектов других классов, констант, ссылок. В нашем примере такие члены - данные отсутствуют.

3) Затем выполняется инициализация собственных полей в теле конструктора. В нашем примере это один оператор ch = c1.

Предположим, что в рассматриваемых классах были бы определены следующие конструкторы по умолчанию :

Base1 ( ) { N = 1; } Base2 ( ) { N = 2; }

Der ( ) { ch = а’; }

а в функции main ( ) имелось бы объявление Der d ;

Для объекта d, как известно будет вызван конструктор Der ( ) по умолчанию, а для инициализации наследуемых полей автоматически будут вызваны конструкторы по умолчанию Base1 ( ) и Base2 ( ).



Виртуальные базовые классы

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

Базовый класс определяется как виртуальный заданием ключевого слова virtual перед именем класса в списке базовых классов. Например:

class B { . . . } ; // Базовый класс

class Х : public virtual B { . . . } ;

class Y : virtual public B { . . . } ;

class Z : public X , public Y { . . . } ;

Характеристики public и virtual могут следовать в любом порядке. Иерархия порождения классов в этом случае будет иметь вид:


Если базовый класс объявлен как виртуальный, то каждый объект производного класса будет иметь собственную часть и указатель на базовую часть, хранящуюся отдельно. Схемы памяти для объектов классов X, Y, Z можно представить следующим образом:


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

Инициализация виртуальных базовых классов

Следует иметь в виду, что при инициализации объектов производного класса, как правило, конструктор производного класса явно вызывает конструкторы только непосредственных базовых классов, то есть непосредственных предков. Например, в рассмотренном выше невиртуальном случае (п. 7.3.) конструктор класса Z может иметь вид:

Z (<список параметров>) : Х (< список параметров > ),

Y (< список параметров > )

{ инициализация собственных членов Z }

Наследуемые от класса В члены будут инициализированы конструкторами классов Х и Y, которые в свою очередь могут иметь вид:

Х (<список параметров>) : В (< список параметров > )

{ инициализация собственных членов Х }

Y (<список параметров>) : В (< список параметров > )

{ инициализация собственных членов Y }

Конструктор класса Х инициализирует одну копию структуры В, а конструктор класса Y - другую.

Если В является виртуальным базовым классом, то объект класса Z содержит, как известно только одну копию структуры В, поэтому конструктор класса В допустимо помещать в список инициализации конструктора Z :

Z (<список параметров>) : Y (< список параметров > ),

Х (< список параметров> ),

В (< список параметров>)

{ инициализация собственных членов Z }

Конструкторы виртуальных базовых классов всегда выполняются перед остальными конструкторами, то есть порядок вызова конструкторов в нашем случае будет В( ), Х ( ), Y( ) .

Допустимо одновременное использование виртуальных и невиртуальных базовых классов. Они могут объявляться в любой последовательности и любой конфигурации. Например:

class B { . . . } ;

class Х 1 : public virtual B { . . . } ;

class Х 2 : public virtual B { . . . } ;

class Y : public B { . . . } ;

class Z : public X1, public X2, public Y { . . . } ;

Взаимосвязи между этими классами хорошо видны из следующего прямого ациклического графа наследования:

Класс Z опять наследует две разные копии класса В. Порядок вызова конструкторов в данном случае таков: В, X1, Х2, Y, Z. Вначале определяются поля, соответствующие виртуальным классам, затем все остальные в порядке их объявления в списке базовых классов. Конструктор Y ( ) в свою очередь вызывает инициализацию наследуемых им полей от “правого” класса В.

Следует отметить еще одну особенность: явный вызов деструктора для производных классов неразрешен. Деструктор вызывается только неявно, используя операцию delete. Порядок вызова деструкторов обратный порядку вызова конструкторов. Как обычно, деструкторы не имеют параметров и не возвращают никакого значения.



Преобразование типов при наследовании

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

class B { . . . } ;

class Х : public B { . . . } ;

void main ( )

{ B * b1 = new B ; // Обычное объявление

B * b2 = new X ; // Неявное преобразование

X * x1 = new B ; // Ошибка !

}

При использовании множественного наследования есть определенное усложнение, вызываемое появлением неоднозначности. Обратимся опять к иерархическим структурам, уже приводимым выше, с классами B, Х. Y, Z.

Рассмотрим следующий фрагмент кода:

class B { . . .};

class Х : public B { . . .}; // B – невиртуальный базовый класс

class Y : public B { . . .};

class Z : public X, public Y { . . . } ;

void main ( )

{ B * b1 = new В ; // Правильно

B * b2 = new Х ; // Правильно

X * x1 = new Z ; // Правильно

B * b3 = new Z ; // Ошибка !

B * b4 = ( Y * ) new Z // Правильно

}

Ошибка состоит в том, что объект класса Z содержит две копии класса B, наследуемые через разные ветви. Для корректного преобразования компилятору необходима дополнительная информация в виде явного приведения типа Z* ( возвращаемого операцией new Z) к типу Х* или Y*. Объявление В виртуальным базовым классом сразу делает допустимым объявление B * b3 = new Z ; так как имеется только одна копия класса В.
Вопросы для самопроверки:
Понятие наследования.
Спецификаторы и правила доступа при простом наследовании.
Инициализация членов - данных при наследовании.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.

Тема 4. Полиморфизм (2 час.)
Цели и задачи: Изучить полиморфизм.
Учебные вопросы: Понятие виртуальные функции.
Учебная информация:

Полиморфизм происходит от греческих слов poly ( много) и morphos ( форма ) и означает множественность форм. Это свойство кода С++ вести себя по-разному в зависимости от ситуаций в момент выполнения этого кода. Это поведение лежит за пределами прямого контроля за объектами со стороны программиста. В реальных программах даже с небольшой иерархией классов число возможных способов взаимодействия объектов может быть огромно. Полиморфизм - это не столько характеристика объектов или классов, сколько членов - функций класса. Полиморфными могут быть только функции - члены, но не сами классы. Полиморфная функция “привязывается” к одной из возможных функций ( вызывается вместо этой функции ) тогда и только тогда, когда ей передается конкретный объект. Другими словами исходный код (текст программы) не всегда определяет, какая функция в данном месте будет вызвана для выполнения, то есть исходный код содержит только обозначение функции, но точное указание конкретной функции. Такой процесс известен как позднее или динамическое связывание в отличие от раннего или статического связывания когда компилятор определяет вызов конкретных функций, а компоновщик заменяет фиксированные идентификаторы функций их физическими адресами. При раннем связывании программист должен знать, какие функции будут вызваны в любой ситуации. Хорошей чертой раннего связывания является быстрота вызова функции. Единственные накладные расходы состоят в передаче параметров функции и очистке стека параметров по ее окончании. Однако раннее связывание ограничивает возможности разработчика программ, требуя от него точного указания вызываемых функций. Позднее связывание дает большую гибкость в данном вопросе. С++ - язык гибридный, это не традиционный процедурный язык, но и не чисто виртуальный. Он использует и раннее и позднее связывание, предоставляя программисту возможности и того и другого. Однако следует отметить, что наряду с этими преимуществами намного усложняется и компилятор и генерируемый им код.

Виртуальные функции

В С++ динамически связываемые функции называются виртуальными. Функция объявляется виртуальной с помощью ключевого слова virtual в базовом классе в иерархии порождения. Виртуальные функции используются только в иерархиях классов. Объявление виртуальной функции в классе, не являющимся базовым для других классов, синтаксически корректно, но ничего, кроме ненужной потери времени при вызове функции, не дает. Объявление функции виртуальной не означает, что она обязательно будет переопределена в производном классе. Функция производного класса будет переопределять виртуальную функцию базового класса тогда и только тогда, когда имеет то же имя и ту же сигнатуру, что и виртуальная функция базового класса. В противном случае механизм виртуальности игнорируется. Таким образом, виртуальность обеспечивает для производных классов полиморфное (то есть различное) поведение функции, определенной в базовом классе и передаваемой по наследству в производные . Интерпретация такой функции будет зависеть от типа объекта (то есть от класса) , для которого она вызывается. Объявлять функцию виртуальной в производном классе допустимо, но необязательно, достаточно объявить ее виртуальной в базовом классе.

Рассмотрим очень простой пример, использующий виртуальные функции.

#include

class A

{ public : virtual Print ( ) { cout << “class A \n” ; }

};

class B : public A

{ public : virtual Print ( ) { cout << “class B \n” ; }

};

void Show ( A * a ) // Реальный тип аргумента ( А * или В * ) будет

{ a-> Print ( ) ; // определен только на шаге выполнения

}

void main ( )

{ A* a ; // Объявление указателя на базовый класс

A aa ; // Объявление элемента базового класса

a = &aa;

Show ( a ) ; // Использование А :: Print ( )

a->Print ( ) ; // Использование А :: Print ( )

B bb ; // Объявление элемента производного класса

a = &bb ; // Указатель получает адрес производного класса

a –> Print ( ) ; // Использование В :: Print ( )

Show ( a ) ; // Использование В :: Print ( )

}

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

1) Вызов виртуальной функции осуществляется через имя объекта. Например:


class A

{ public : virtual void F ( ) { . . . }

};

class B : public A

{ public : virtual void F ( ) { . . . }

};

void main ( )

{ A a ; a . F ( ) ; // Вызов А::F ( )

B b ; b . F ( ) ; // Вызов В::F ( )

}

2) При явном использовании операции разрешения области видимости, то есть при указании имени класса компилятор генерирует вызов фиксированной функции и отключает полиморфизм. Для примера используем предыдущие классы А и В.

void main ( )

{ A * pa ;

B b;

pa = & b; // Указатель базового класса получает адрес объекта //производного класса

pa -> А:: F ( ); // Невиртуальный вызов А::F ( )

}

3) Виртуальная функция вызывается в конструкторе или деструкторе базового класса. В этом случае всегда вызывается виртуальная функция , определенная в базовом классе, так как объект производного класса либо еще не создан ( при вызове конструктора) либо уже разрушен ( при вызове деструктора).

Рассмотрим соответствующий пример.

class A

{ public : virtual void F ( ) { . . . }

A ( ) { F ( ) ; } // Конструктор базового класса А.

};

class B : public A

{ public : virtual void F ( ) { . . .}

B ( ) :A ( ) { . . .} // Конструктор базового класса В.

};

void main ( )

{B* b = new B ; // Объявление и инициализация

} // указателя производного класса.

Какая из виртуальных функций будет использована при создании указателя b? Конструктор класса В вызывает конструктор базового класса А::А( ), который устанавливает таблицу виртуальных функций для класса А. Так как выбор виртуальной функции определяется с помощью данной таблицы, то будет выбрана функция А::F ( ). После выполнения конструктора А управление передается конструктору В, который установит таблицу виртуальных функций для класса В. Если бы виртуальная функция вызывалась в теле конструктора В, то выполнялся бы вызов В::F ( ).

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

#include

#include

class Parent

{ protected : char * PName ;

public : Parent ( char * S ) //Конструктор базового класса

{ Pname = new char [strlen(S)+1];

strcpy (Pname, S) ;

}

virtual void AnswerName ( ) // Определение виртуальной

{ cout <<“ \nParent is “<< PName << “ “ ; //функции в базовом классе

}

virtual ~Parent ( ) //Виртуальный деструктор

{ cout << " Удалено поле "<< PName << '\n' ;//Удаление динамического

delete PName; //поля

}

};

class Child : public Parent { //Конструктор произв. класса

protected: char * CName;

public : Child (char* S1, char * S2): Parent (S1)

{ CName = new char [strlen(S2)+1];

strcpy (CName, S2) ;

}

virtual void AnswerName ( ) // Переопределение вирт. функции

{ Parent ::AnswerName ( ) ; // Невиртуальный вызов ф-ии

// AnswerName( )

cout << “Child is “ << CName << “ “ ;

}

virtual ~Child ( )

{ cout << " Удалено поле "<< CName << " " ;

delete CName;

}

};

class GrandChild: public Child {

protected: char * GName ;

public : GrandChild (char * S1, char * S2 , char * S3 ) : Child ( S1, S2 )

{ GName = new char [strlen(S3)+1];

strcpy (GName, S3) ;

}

virtual void AnswerName () //Переопределение вирт. функции { { { { Child::AnswerName ( ) ;

cout << “GrandChild is “ << GName << “ “;

}

virtual ~GrandChild ( )

{ cout << " Удалено поле "<< GName << " " ;

delete GName ;

}

};

void main ( )

{ Parent * family [3]; // Массив указателей на базовый класс

Parent * p = new Parent (“Jones ”) ;

Child * c = new Child(“Jones ” , “Henry ”);

GrandChild * g = new GrandChild(“Jones ” , “Cynthia”, “Robert ” );

family [0] = p; // Указатели базового класса прини-

family [1] = c; // мают значения указателей производ-

family [2] = g; // ного класса

for ( int i = 0 ; i <3 ; i ++ ) // Вызов через указатели базового

family [i] -> AnswerName () ; // класса

// Еще один фрагмент, эквивалентный предыдущему, но

// использующий не указатели на базовый класс, а ссылки

Parent p1 (“Jones ”); //Объявление и инициализация объектов.

Child c1 (“Jones ” , “Henry ”);

GrandChild g1 (“Jones ” , “Cynthiа”, “Robert ”);

Parent & f0 = p1; // Объявление и инициализация ссылок

Parent & f1 = c1; // на базовый класс.

Parent & f2 = g1;

f0 . AnswerName ( ); // Вызов через ссылки базового класса

f1 . AnswerName ( );

f2 . AnswerName ( );

}

Оба фрагмента дают один и тот же результат:

Parent is Jones

Parent is Jones Child is Henry

Parent is Jones Child is Cynthia GrandChild is Robert



Замечание о виртуальных деструкторах

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

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

#include

class Parent

{ private:

virtual void F1 ( ) {cout<< “ Parent :: F1( ) “; }

void F2 ( ) { co
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
// Переопределение виртуальной функции F1 ( )

public: Child ( ) { }

};

void main ( )

{ Parent p; Child c; // Объявление объектов

cout << “p.F4( ) вызывает последовательность функций: \n” ;

p . F4 ( ) ;

cout << “\n c.F4( ) вызывает последовательность функций: \n” ;

c . F4 ( ) ;

}

Которая из функций Parent :: F4 ( ) или Child :: F4 ( ) вызывается в каждом из этих случаев? В данном случае необходимо иметь в виду, что первым параметром любой нестатической член-функций является неявно передаваемый указатель this на обрабатываемый объект. Поэтому вызов невиртуальной функции F4( ), определенной в базовом классе, генерирует такую последовательность вызовов функций:

this –> F3 ( ) // Вызов Parent :: F3 ( )

this –> F2 ( ) // Вызов Parent :: F2 ( )

this –> F1 ( ) // Вызов виртуальной функции F1 ( )

В зависимости от реального значения указателя this на шаге выполнения будет выбран вариант виртуальной функции. Когда данный указатель имеет значение адреса объекта р типа Parent , осуществляется вызов Parent :: F1 ( ), когда указатель содержит адрес объекта с типа Child, вызывается функция Child :: F1 ( ). Особенностью данного примера является то, что виртуальная функция является приватной и ее вызов осуществляется без явного использования указателей или ссылок на объекты базового класса.

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



Чистые виртуальные функции и абстрактные базовые классы

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

Функция Fun ( ) в этом дереве не нужна классам А и В. Но для того, что- бы функция Е :: Fun ( ) была доступна через ссылку или указатель на класс А, ее необходимо в классах А и В объявить как виртуальную. Но поскольку этим классам данная функция не нужна , ее можно объявить пустой::

virtual void Fun ( ) { }

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

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

С++ позволяет ограничить использование “пустых” классов, объявляя их абстрактными. Попытка создать объект такого класса приводит к сообщению об ошибке. Другими словами, абстрактный класс может быть использован только как базовый для последующего порождения новых производных классов. Сделать класс абстрактным можно, если включить в него хотя бы одну чистую виртуальную функцию, которая объявляется следующим образом:
virtual < тип > < имя_функции > ( < список_параметров > ) = 0 ;
Запись “ = 0 ” означает, что функция не определена и код для нее не генерируется. Попытка вызвать ее приводит к ошибке во время выполнения программы. Данная запись отличается от объявления пустой функции
virtual < тип > < имя_функции > ( < список_параметров > ) { }
Несмотря на то, что создавать объекты абстрактных классов запрещено, можно объявить на него ссылку или указатель. Класс, производный от абстрактного, опять может быть абстрактным. Например, если чистую виртуальную функцию вновь переопределить в производном классе как чистую функцию, то по определению производный класс оказывается абстрактным. Существует правило: все чистые виртуальные функции должны обязательно переопределяться в производных классах. Нарушение этого правила приводит к ошибке компиляции. Приведем пример правильного использования абстрактного класса.
#include

class A //Абстрактный базовый

{ public: // класс

void virtual Print ( ) = 0; //Чистая виртуальная функция

};

class B: public A

{ public: void virtual Print ( )

{ cout << "Обращение к функции B::Print( ) \n" ; }

}; // Переопределение чистой виртуальной функции

class C : public B

{ public: void virtual Print ( )

{ cout << "Обращение к функции С::Print( ) \n" ; }

}; // Переопределение чистой виртуальной функции

void Show ( A * a ) // Указатель на абстрактный базовый класс

{ a-> Print ( ) ; // допустим

}

void main ( )

{ A * b = new B; //Создание динамических объектов производных

A * c = new C; // классов и указателей на них

Show ( b ) ; // Использование В :: Print ( )

Show ( c ) ; // Использование С :: Print ( )

}



Дружественные виртуальные функции

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


#include

class C;

class A

{ public: int a;

A (int i) { a = i; }

virtual void Print ( C& );

//Объявление виртуальной функции А::Рrint()

};

class B: public A

{ public: int b;

B( int i, int j ): A (i) { b = j ;}

virtual void Print (C&);

// Объявление виртуальной функции В::Print()

};

class C // Внешний по отношению к А,В. класс

{ private:

friend void A::Print (C&);

friend void B::Print (C&);

int a, b, c ;

public: C (int i, int j, int k) {a = i; b = j; c = k; }

};

void A::Print (C& c) // Определение функции A::Print( )

{ cout << "Вызов функции A::Print( ) для объекта класса А:\n"

<< " поле а в объекте класса A = " << a

<< ", поле а в объекте класса С = "<< c.a << '\n';

};

void B::Print (C& c ) // Определение функции В:: Print ( )

{ cout << "Вызов функции B::Print() для объекта класса B:\n"

<<" поле b в объекте класса B = " << b

<< ", поле b объекте класса С = " << c.b<< '\n';

};

void main ( )

{A * pa = new A ( 11 ) ;

А * pb = new B ( 21 , 22) ;

C c ( 31 , 32, 33 ) ;

pa ->Print ( c ) ; // Вывод pa->а, c.а

pa = pb;

pa ->Print ( c ) ; // Вывод pb->b, c.b

}

Класс С является внешним по отношению к классам А, В, в с
· "
·
·ЪЬЮ
·ъекции private данного класса присутствуют две дружественные функции, определенные как виртуальные в классах А. В. Как известно, дружественные к классу функции имеют доступ ко всем членам класса, в том числе и к членам секции private. Эти функции выполняют определенные действия над собственными полями (т.е. над полями объектов типа А или В ) и полями объекта типа С, который передается в эти функции в качестве параметра. Если их не объявить дружественными классу С, то члены данные С::а, С:: b, С::с будут недоступны в классах А и В.

Следует обратить внимание на то, что непосредственно к объекту типа С или к указателю на объект типа С эти функции применить нельзя.

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



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

Так как операторы могут быть реализованы

как член - функции, то они могут быть oбъяв-)

лены как виртуальные. Рассмотрим простой при-

мер наследования с использованием виртуально-

го оператора +=.





#include

class A

{ public: int a;

A ( int i ) { a = i; }

virtual A& operator += ( A & t )

{a += t.a ; return* this ; }

virtual int Get_a ( ) { return a ; }

virtual int Get_b ( ) { return 0 ; }

};

class B: public A

{ public: int b;

B ( int i, int j ): A (i) { b = j ; }

virtual int Get_b ( ) { return b; }

virtual A& operator += (A & t)

{a+= t.Get_a( ); b+= t.Get_b( ); return * this ; }

};

void main ( )

{ A a1 (10) ;

B b1 (20 , 30) ;

B b2 (40 , 50) ;

cout << " Исходные значения:"<< " a1= " << a1.a

<< ", b1= " << b1.a << " "<< b1.b << ", b2= " << b2.a << " "<< b2.b << '\n'

<< " Результаты :\n" ;

a1 += b1;

b1 += b2;

b2 += a1;

cout << " a1+= b1= " << a1.a<<'\n';

cout << " b1+= b2= " << b1.a << " "<< b1.b << '\n';

cout << " b2+= a1= " << b2.a << " "<< b2.b << '\n';

}

Данный код работает вполне корректно, но, если попытаться сделать виртуальным оператор "+", а не "+=", то возникает следующая проблема: оператор "+" должен возвращать объект класса A, так как виртуальные функции в иерархии порождения классов имеют один и тот же тип аргументов и результатов. В результате объект типа В при возврате преобразуется к типу А, и часть В может быть потеряна. Поэтому виртуальные операторы типа "+" , "-" практически не используются.


Техническая реализация виртуальных функций (на примере Borland C++)

Для того, чтобы понять как компилятор отыскивает виртуальные функции во время выполнения программы, рассмотрим структуры объектов в Borland C++, в которых есть виртуальные функции.

class A

{ protected: int a;

public: void f ( ) { . . . } // Невиртуальная функция

virtual void g ( ) { . . . } // Виртуальная функция

};

class B : public A

{ private: int b;

public : void f ( ) { . . . } // Невиртуальная функция

virtual void g ( ) { . . . } // Виртуальная функция

};

void Do ( A & a ) { a . f ( ) ; a .g ( ) ;}

void main ( )

{ A a ; Do ( a ) ;

B b ; Do ( b ) ;

}

Структуры объектов а и b в памяти будут следующими:

Cмещение показывает с какого байта в поле памяти, выделенной для конкретного объекта, располагается переменная, являющаяся членом - данным или указателем на таблицу виртуальных функций A::vtab или В::vtab (точнее таблицу адресов кодов виртуальных функций соответствующего класса). Обратите внимание, указатель vptr на таблицу виртуальных функций и у базового, и у производного объектов всегда находится в одной и той же позиции относительно начала объекта, а именно: после членов - данных базового класса. Поэтому функция Do получает доступ к правильной функции , независимо от того какой объект ей передан в качестве аргумента.

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

class A {

protected: int a;

public: virtual void f ( ) { . . . }

virtual void g ( ) { . . }

virtual void h ( ) { . . }

};

class B : public A {

private: int b;

public: virtual void f ( ) { . . . }

virtual void g ( ) { . . . }

};

void Do (A & a)

{ a.f ( ) ; a.g ( ) ; a.h ( ) ;

}

void main ( )

{ A a; Do ( a ) ;

B b; Do ( b ) ;

}

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

В таблицу виртуальных функций класса В включены не только функции, переопределяемые в нем, но и непереопределяемая функция А:: h ( ). Следует обратить внимание также на то, что функции с одними и теми же именами стоят в одних и тех же позициях.

Множественное наследование - более сложный тип наследования, чем наследование простое. Предположим, дана следующая схема

class A

{ public: int a1, a2;

virtual void VF1 ( ) { . . .}

virtual void VF2 ( ) { . . .}

};

class B: public A

{ public: int b1, b2;

virtual void VF1 ( ) { . . .}

virtual void VF2 ( ) { . . .}

};

class C

{ public: int c1, c2;

virtual void VG1 ( ) { . . .}

virtual void VG2 ( ) { . . .}

};

class D: public C

{ public: int d1, d2;

virtual void VG1 ( ) { . . .}

virtual void VG2 ( ) { . . .}

};

class E: public B, public D

{ public: virtual void VF1 ( ) { . . .}

virtual void VF2 ( ) { . . .}

virtual void VG1 ( ) { . . .}

virtual void VG2 ( ) { . . .}

};

void Do_AB ( A* a) { a – >VF1 ( ) ; a –> VF2 ( ) ; }

void Do_CD ( C* c) { c –> VG1 ( ) ; с –> VG2 ( ) ; }

void main ( )

{ B* b = new B ; D* d = new D ; E* e = new E ;

Do_AB (e) ; Do_CD (e ;

}

Таким образом, при множественном наследовании объект может содержать несколько указателей на таблицы виртуальных функций. Порядок размещения подобъектов в производных классах определяется порядком объявления в них базовых классов. Рассмотрим вызовы двух функций в функции main ( ).

Do_AB ( e ) ; Do_CD ( e ) ;

В обоих случаях происходит неявное преобразование аргумента к требуемому типу. Явное преобразование выглядело бы следующим образом:

Do_AB ( (А*) ( e ) ); Do_CD ( (С*)( e ) );

Первое преобразование тривиально, так как начала объекта е и подобъекта а в памяти совпадают, поэтому преобразования значения указателя, то есть адреса, не требуется и для вызова первой функции используется поле vptr, определенное в подобъекте а. Второе преобразование несколько сложнее, оно состоит в преобразовании адреса объекта е на начало подобъекта с. Поэтому при вызове второй функции используется поле vptr, определенное в подобъекте с.
Вопросы для самопроверки:
Что такое полиморфизм?
Что такое виртуальные функции?
Что такое абстрактные функции?
Что такое абстрактные операторы?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.

Тема 5. Обработка исключений (2 час.)
Цели и задачи: Изучить обработку исключений.
Рассмотреть понятия исключительные ситуации .
Учебные вопросы: Понятие генерации и перехватывания исключений .
Учебная информация:

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

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

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

3. Устанавливается функция обратного вызова обработки ошибок (callbak fun-ction) . Это специальная функция, которая вызывается функциями высокого уровня при обнаружении ошибки в функциях низкого уровня. Данный прием в языке С++ используется вместе с предыдущим приемом, позволяя иметь специальные обработчики ошибок. Примером такой функции является функция set_new_handler ( ), которая может перекачать блоки памяти на диск и вновь распределить память. Если повторное распределение памяти удачно, то инцидент исчерпан, иначе выдается сообщение об ошибке.

4. Подается сигнал. Сигнал - это синхронное прерывание, требующее от системы вызова заранее установленного обработчика данного сигнала. Обработчики сигнала сходны с функциями обратного вызова, но сигналы более присущи операционным системам, а не языкам программирования.

Систематизированная обработка исключений сложна по ряду причин. Прежде всего, трудно определить исключение, а обработать его унифицированным способом еще труднее. Разработчики фирмы АТ&T , занимаясь данной проблемой так сформулировали основные требования, предъявляемые к механизму обработки исключений:

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

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

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



Генерация и перехватывание исключений

Технология обработки исключений в языке С++ основана на концепции глобального перехода. Когда происходит исключительная ситуация, программа генерирует исключение ( throw ). Где - то в программе должен быть код, перехватывающий и обрабатывающий исключение ( catch. Примером генерации исключений может служить следующий код:

#include

const int CATCH_ERROR = - 1 ;

void WriteCashBlock ( unstgned char * data, long size )

{if ( size > 1000 ) // слишком большой объем данных throw CATCH_ERROR ;

. . . . // запись данных на диск

}

При выполнении оператора throw функция WriteCashBlock прекращает работу. Операторы, следующие за throw пропускаются. В этом смысле данный оператор подобен оператору return за исключением того, что управление не передается на команду, следующую за вызовом функции. Оператор throw может использовать переменную исключения ( в нашем случае CATCH_ERROR ). Переменная исключения не используется , если исключение передается на другой вышестоящий обработчик исключений.

Рассматриваемый механизм обработки исключений заключается в том, что операторы, в которых могут возникнуть исключительные ситуации располагаются в специальном try - блоке. За таким блоком может следовать один или несколько catch - блоков, которые определяют и обрабатывают исключение. Другими словами после выполнения в try - блоке оператора throw управление передается на ближайший catch - блок. Синтаксис этих блоков имеет вид:

try

{ . . . // код, в котором может возникнуть

} // исключительная ситуация

catch ( T1 [ X1 ] )

{ . . . // Обработка исключительной ситуации типа Т1

}

catch ( . . . )

{ . . . // Обработка всех остальных исключительных

} // ситуаций

Тип Т1 представляет любой встроенный тип, а также структуру или класс, поддерживающий определяемый пользователем тип исключительной ситуации. Необязательный параметр Х1 может иметь тип Т1, Т1 & , const T1 или const T1 &. Если параметр catch - блока имеет вид (. . .), то этот блок принимает параметры любого типа и может обработать все оставшиеся ситуации, не учитываемые в типизированных блоках. Приведем пример с использованием try - и catch - блоков.

#include

class SizeError

{ private: int size;

public: SizeError (int value ) { size = value ; }

char * ErrorMessage ( ) { return “Size Error” ; }

int Size ( ) { return size ; }

};

void WriteCashBlock ( unsigned char * data , long size )

{ if ( size > 1000 ) // Cлишком большой объем данных

{ SizeError error ( size ) ; // Создание объекта типа SizeError

throw error ; // Генерация исключения с

} // параметром типа SizeError

. . . // Запись данных на диск

}

void main ( )

{ unsigned char * cashe ;

. . . . // Определение параметра cashe

try

{ WriteCashBlock ( cashe, 100 ) ; // Код, в котором может возникнуть

} // исключение

catht ( SizeError error ) // Обработка исключения

{ cout << error. ErrorMessage ( )

<< “:” << error. Size ( ) << endl ;

}

. . . // Какие-то другие действия

} // функции main


Использование нескольких catch- блоков

Нередко try - блоки обнаруживают разные типы исключений. Использование одного и того же типа для разных исключений может стать весьма громоздким и неуклюжим, так как в этой ситуации неизбежно придется пользоваться операторами if. . . else и switch. Целесообразно установить для каждого исключения свой тип и для каждого типа создать свой catch- блок, например.

#include

const int CATCH_ERROR = – 1 ;

const signed char RANGE_ERROR = – 2 ;

void WriteCashBlock ( unstgned char * data , long size )

{ if ( size <0 ) throw RANGE_ERROR ; // нет данных

if ( size > 1000 ) throw CATCH_ERROR ; // слишком большой объем

// данных

. . . . // запись данных на диск

}

void main ( )

{ unsigned char * cashe ;

// . . . // Определение параметра cashe

try

{ WriteCashBlock ( cashe, 100 ) ; // Код, в котором может возникнуть

} // исключение

catсh ( int )

{ cout << “Range Error” << endl ; // Обработка исключения типа int

} // без передачи параметра

catch (signed char ) // Обработка исключения типа

{ cout << “Catch Error” << endl ; // signed char

}

// . . . //Какие-то другие действия

} //функции main

Компилятор сам генерирует код , выполняющий вызов catch- блока, ассоциированного с конкретным типом. О разных catch- блоках можно думать как о перегружаемых операторах. Чем больше работы перекладывается на компилятор, тем больше используются достоинства языка С++ и тем меньше вероятность возникновения ошибок.

Примеры. Объектно- ориентированный подход к обработке исключений

Примеры на исключения
1. Исключение типа CFileException

void F1(char* MyFileName)

{ CFile MyFile1;

CFileException MyFileEx; //Создание объекта типа CFileException

if ( ! MyFile1.Open ( MyFileName,

CFile::modeRead |CFile::shareDenyNone, &MyFileEx ) )

{ TRACE ("Can't open file %s,\n error num = %u \n lOsError = %u\n",

MyFileEx.m_strFileName, // имя файла

MyFileEx.m_cause, // № ошибки

MyFileEx.m_lOsError ); // № ошибки ввода-вывода ( обычно =m_cause )

}

else { . . . . . }
}

Замечание. Информацию об ошибке получим только в отладочном режиме в окне отладки.

Объектно-ориентированный подход к обработке исключений
// . . . .Это базовый класс с чистой виртуальной функцией ErrorHandler ()
class ErrorMsg

{ public: int numError;

char* ar_msg[2];

ErrorMsg (int n)

{ numError = n ;

ar_msg[0] = "Memory Exception" ;

ar_msg[1] = "Range Exception" ;

}

void Message( )

{ cout << "Error number = "<< numError

<< " Error Description= " << ar_msg[numError]<< "\n";

}

~ErrorMsg () {};

virtual void ErrorHandler () = 0;

};

// ---------------------------------------------------------------



// . . . . . Это класс для обработки первого исключения.



class ErrorMsg1 : public ErrorMsg

{

public : int** ptr ;

int size ;

//. . . . . . . . . . . . .Конструктор . . . . . . . . . . . . . . . . . . .



ErrorMsg1(int num,int s, int** p ):ErrorMsg(num)

{ size = s;

ptr = p;

}



//. . . . . . . . . . . .Обработчик ErrorHandler(). . . . . . . . . . . . .

// Функция ErrorHandler () выводит информацию об ошибке и выполняет

// выделение памяти.



void ErrorHandler()

{ Message();

//спец. действия по обработке ошибки - выделение памяти

*ptr = new int [size];

if ( *ptr ) cout << "Memory is got. Size = "<< size

<< " Addr = " << *ptr << "\n" ;

else cout << "Memory coudn't be got. \n" ;

}

}; // конец класса



// ---------------------------------------------------------------

// . . . . . Это класс для обработки второго исключения.



сlass ErrorMsg2 : public ErrorMsg

{

public: int * addr_var; // результат обработки ошибки

int * addr_arr; // указатель на массив



//. . . . . . . . . . . . .Конструктор . . . . . . . . . . . . . . . . . . .



ErrorMsg2 (int num, int * a_v, int * a_a ) : ErrorMsg ( num )

{ addr_var = a_v;

addr_arr = a_a;

}



//. . . . . . . . . . . .Обработчик ErrorHandler(). . . . . . . . . . . . .

// Функция ErrorHandler () выводит информацию об ошибке и формирует

// исправленное новое значение переменной addr_var.



void ErrorHandler()

{ Message();

// спец. действия по обработке ошибки - исправление индекса

// при обращении к массиву

*addr_var = addr_arr[0];

}

}; // Конец класса



// ---------------------------------------------------------------

// . . . . . Это класс для демонстрации исключений.

class CMyArray

{ public: int size;

int * arr;

CMyArray () { size = 0; }

void GetMemory ( int n)

{ size = n;

arr = new int [n];

arr = 0; // принудительно делаем ошибку...

if ( !arr )

{ ErrorMsg * er_ptr = new ErrorMsg1 (0, size, &arr );

throw ( er_ptr ) ;

}

}

void GetElem ( int k, int * var_ptr )

{ if ( k < 0 || k > size )

{ ErrorMsg * er_ptr = new ErrorMsg2 ( 1, var_ptr, arr ) ;

throw ( er_ptr );

}

else *var_ptr = arr [ k ] ;

}

} ;

// ---------------------------------------------------------------



int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

CMyArray * myAr= new CMyArray; // Cоздаем объект



try { myAr -> GetMemory(100);} // Выделяем память

catch ( ErrorMsg* pp)

{ pp->ErrorHandler (); // Вызываем функцию

} // базового класса



for ( int i =0; i<100; i++) // Определяем элементы массива

myAr -> arr [ i ] = 'A' + i;



int var; // Обращаемся к элементу массива

for ( int i = -50; i<200; i+=100)

{ try { myAr ->GetElem ( i, &var ) ;

}

catch ( ErrorMsg * pp )

{ pp->ErrorHandler ();

}

cout << var << "\n";

}

return 0;

}

Результаты работы

Error number = 0 Error Description= Memory Exception

Memory is got. Size = 100 Addr = 004216D0

Error number = 1 Error Description= Range Exception
65

115
Error number = 1 Error Description= Range Exception
65
Использование блоков try - except

Блок try-except - это предлагаемое Microsoft расширение для использования в 32-разрядных приложений для обработки исключений, которые обычно прерывают программу. (Следует отметить, что использование конструкции try-trow-catch является более мощным и гибким).

Синтакстс блока try-except следующий:

__try
{
// guarded code
}

__except ( expression )

{

// exception handler code

}

Данный блок работает по следующему алгоритму:
1. Выполняется содержимое секции _try.
2. Если никакого исключения не возникает, то управление передается коду, следующему за блоком __except.
3. Если исключение возникает, то вычисляется значение выражения ( expression ), которое будет определять
режим обработки исключения. Предусмотрены три возможности:
EXCEPTION_CONTINUE_EXECUTION (–1) Исключение пропускается.
Приложение будет пытаться продолжить выполнение с той точки , в которой произошло исключение.
EXCEPTION_CONTINUE_SEARCH (0) Исключение не распозначется системой.
Приложение будет пытаться найти обработчик, отвечающий данному исключению.
EXCEPTION_EXECUTE_HANDLER (1) Исключение распозначется системой.
Происходит передача управления соответствующему обработчику.
Пример использования блоков try-except
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int * p = 0x00000000; // pointer to NULL

puts ("Begin");

__try { puts("try: level 1");

__try

{ puts("try : level 2");

*p = 13; // causes an access violation exception;

}

__finally

{ puts("in finally");

}

}

__except ( 1 ) // ( -1 ) ( 0 )

{

puts("exception handler code");

}

puts("End");

return 0;

}



Результаты работы данного фрагмента для различных значений

параметра expression





1. __except ( 1 )



Begin

try: level 1

try : level 2

in finally

exception handler code

End



2. __except ( -1 )



Begin

try: level 1

try : level 2



Приложение зависает



3. __except ( 0 )



Begin

try: level 1

try : level 2

Приложение завершается с ошибкой вида:

Unhandled exception at 0x004011c2 in except.exe:

0xC0000005: Access violation writing location 0x00000000.

Структура EXCEPTION_POINTERS
Данная структора содержит машинно-независимое описание исключения (ExceptionRecord) и машинно-независимое описание состояния процессора в момент исключения (ExceptionRecord).
Структура объявлена как
typedef struct _EXCEPTION_POINTERS

{ PEXCEPTION_RECORD ExceptionRecord;

PCONTEXT ContextRecord;

} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

Структура EXCEPTION_RECORD объявлена следующим образом:
typedef struct _EXCEPTION_RECORD

{ DWORD ExceptionCode;

DWORD ExceptionFlags;

struct _EXCEPTION_RECORD

*ExceptionRecord;

PVOID ExceptionAddress;

DWORD NumberParameters;

ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

Параметры этой структуры:
ExceptionCode - код произошедшего исключения, этот код может генерироваться как аппаратно, так и программно.

Некоторым наиболее распространенным программным ошибкам отвечают следующие коды:

EXCEPTION_ACCESS_VIOLATION - попытка чтения или записи данных по недопустимому виртуальному адресу

EXCEPTION_ARRAY_BOUNDS_EXCEEDED - выход индекса массива за допустимые границы.

EXCEPTION_BREAKPOINT - исключение в точке прерывания.

EXCEPTION_DATATYPE_MISALIGNMENT - попытка чтения или записи данных, когда адрес устройства не

"выровнен" соответствующим образом. (Например, 16-разрядные

значения выставляются по границе 2-байтовых слов, 32-разрадные -

по границе 2-байтовых слов.)

EXCEPTION_FLT_DENORMAL_OPERAND - один из операндов при выполнении операции с плавающей точкой слишком мал для представления как float.

EXCEPTION_FLT_DIVIDE_BY_ZERO - деление на 0 при выполнении операции с плавающей точкой.

EXCEPTION_FLT_INVALID_OPERATION - недопустимый операнд при выполнении операции с плавающей точкой.

EXCEPTION_FLT_OVERFLOW - слишком большой операнд при выполнении операции с плавающей точкой.

EXCEPTION_FLT_STACK_CHECK - переполнение стека при выполнении операции с плавающей точкой.

EXCEPTION_FLT_UNDERFLOW - экспонента оперетора слишком мала при выполнении операции с плавающей точкой (потеря значимости).

EXCEPTION_ILLEGAL_INSTRUCTION - встретилась недопустимая инструкция.

EXCEPTION_IN_PAGE_ERROR - попытка доступа к странице, которой нет в памяти, а система не может ее загрузить.

EXCEPTION_INT_DIVIDE_BY_ZERO - целочисленное деление на 0.

EXCEPTION_INT_OVERFLOW - перенос старшего значащего бита при выполнении целочисленной операции.

EXCEPTION_PRIV_INSTRUCTION - попытка выполнения инструкции, отвечающей запрещенным операция в данном режиме. (Например, у пользователя есть право только на чтение файлов, он пытается выполнть запись в файл.)
ExceptionFlags

Этот параметр может быть нулем (указывает на то, что исключение может быть обработано) или значением EXCEPTION_NONCONTINUABLE, указывающим на то, что обработка исключения не может быть продолжена. Любая попытка продолжить выполнение после необрабатываемого исключения приволит к исключенияю EXCEPTION_NONCONTINUABLE_EXCEPTION.
ExceptionRecord

Указатель на связанную структуру EXCEPTION_RECORD. Это дает возможность связать вложенные исключения.
ExceptionAddress
Адрес, где произошло исключение.
NumberParameters
Число параметров, связанных с искдючением (число элементов массива ExceptionInformation).
ExceptionInformation
Дополнительные аргументы для описания исключения. Для большинства исключений этот параметр NULL. (Функция RaiseException может задавать этот массив).

Исключение EXCEPTION_ACCESS_VIOLATION имеем два допольнительных параметра:
1-й параметр = 0, если выполняется попытка чтения данных из недоступной области,
1, если выполняется попытка записи данных в недоступную область.

2-й параметр указывает виртуальный адрес недопустимой области.
Пример использование структуры EXCEPTION_POINTERS

class CExcp // класс перехватчик

{ public: //назначение данного класса - преобразовать

DWORD dwCode ; //системное исключение в исключение С++

DWORD dwFlags; //Члены данные класса соответствуют полям

void* dwAddr ; //структуры, в которую помещается описание

DWORD dwNums ; //системного исключения.

DWORD dwArg0 ;

DWORD dwArg1 ;

CExcp(DWORD nC, DWORD nF, void* nA, DWORD nN, DWORD nA0, DWORD nA1)

{ dwCode = nC ;

dwFlags = nF ; // конструктор данного класса

dwAddr = nA ;

dwNums = nN ;

dwArg0 = nA0;

dwArg1 = nA1;

};

DWORD Code() const {return dwCode;};

};

void SEH_TR(unsigned int nCode, EXCEPTION_POINTERS* pexp) // Получили SEH

{ //cout<< "nCode =" << nCode <
//Эта функция будет автоматически вызываться при

//возникновении системного исключения.Описание

// системного исключения помещается в структуру

// типа EXCEPTION_POINTERS. Значения полей данной

// структуры использованы для инициализации объекта

// класса - перехватчика. Создаваемый объект класса

// CExcp передается к4ак объект опературу throw,.т.е.

// исключение SEH преобразовано в исключение С++.

DWORD dwC = pexp->ExceptionRecord->ExceptionCode;

DWORD dwF = pexp->ExceptionRecord->ExceptionFlags;

DWORD dwN = pexp->ExceptionRecord->NumberParameters;

void *dwA = pexp->ExceptionRecord->ExceptionAddress;

DWORD dwA0 = pexp->ExceptionRecord->ExceptionInformation[0];

DWORD dwA1 = pexp->ExceptionRecord->ExceptionInformation[1];

throw CExcp(dwC, dwF, dwA, dwN, dwA0, dwA1);

// Генерируем исключение с переменной типа класса CExcp

}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

cout<< "Address p =" <<&p << "\n\n";

_se_translator_function fnOld; // указатель на функцию

// перехвата

fnOld = _set_se_translator(SEH_TR); //Старый обработчик запоминается

// в переменной fnOld, новым

//обработчиком назначена

//пользовательская функция SEH_TR

int *p ;

for ( int i = 0; i< 2; i++)

{ try

{ if (i == 0)

{ *p = 0 ; // операция разадресации невозможна,

// так как не выделена память
}
else i == 1

{ / Используем функцию API

// void RaiseException ( dwExceptionCode,

// dwE xceptionFlags,

// dwExceptionNums,

// dwExceptionArgs

// );

// Эта функция порождает исключение с соответствующими параметрами

RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 1, 0, NULL);
}
} //конец try - блока
catch(CExcp& e) // обрабатываем ошибку

{ cout<<'\n';

cout<< "dwExceptionCode = " << e.dwCode <
cout<< "dwExceptionFlags = "<< e.dwFlags <
cout<< "dwExceptionNums = "<< e.dwNums <
cout<< "dwExceptiondwAddr= "<< hex<
if (e.dwNums)

cout<< "dwArgs = "<//Дополнит. обработка для исключения - нарушение
//правил доступа к данным
if (e.Code() ==EXCEPTION_ACCESS_VIOLATION)

{ cout << "EXCEPTION_ACCESS_VIOLATION" << endl;

if (e.dwArg0) // e.dwArg0 == 1

cout << "Attempt to write to an inaccessible address ";

else // e.dwArg0 == 0

cout << "Attempt to read the inaccessible data at virtualaddress ";

cout <
}

cout << endl;

} //конец catch

} //конец for

_set_se_translator(fnOld); // восстановить старую функцию обработки

//ошибок
unsigned long Code =0xc0000005;
cin>> *p; // Ввод числового значения в переменную, память под которую не

cout<< p; // выделена.

// Будет сгенерирована ошибка с кодом 0xc0000005 (= 3221225477 =

// EXCEPTION_ACCESS_VIOLATION ).

// Внимание! исключение будет происходить только при попытке

// записать числовые данные. При попытке записать текстовые

// данные исключение не генерируется, но адрес переменной

// р возвращается как СССССССС

return 0;

}

/* Результаты работы программы 3
Address p =0012FF5C
dwExceptionCode = 3221225477
dwExceptionFlags = 0
dwExceptionNums = 2
dwExceptiondwAddr= 00401432
dwArgs = 1 cccccccc
EXCEPTION_ACCESS_VIOLATION
Attempt to write to an inaccessible address cccccccc
dwExceptionCode = 3221225612
dwExceptionFlags = 1
dwExceptionNums = 0
dwExceptiondwAddr= 77E73887

qwe
CCCCCCCC
Press any key to continue

Вопросы для самопроверки:
Что такое обработтка исключений?
Покажите на примере обработку исключений?
Опишите генерацию и перехватывание исключений.
Опишите использование нескольких CATCH блоков.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


МОДУЛЬ 3. Многопоточное программирование (14 час.)
Раздел 1. Потоки (6 час.)
Тема 1. Простая многозадачность на уровне приложения (4 час.)
Цели и задачи: Изучить простую многозадачность на уровне приложения .
Учебные вопросы: Понятие потока, мьютекса, состояние гонки, dead lock.
Учебная информация:

Поток - ветвь выполняющейся программы. Любое приложение имеет всегда имеет один поток, который является главным или первичным. Главный поток выполняется до тех пор, пока приложение активно. В многозадачном приложении можно создавать произвольное количество дополнительных потоков, запускать, приостанавливать и уничтожать. При закрытии приложения все потоки уничтожаются. С точки зрения операционной системы приложение - это отдельный процесс. Фактически процесс состоит из двух компонент:
объекта ядра (дескриптора процесса), через который операционная система управляет процессом;
адресного пространства процесса.
Поток - часть процесса, которая выполняется в адресном пространстве процесса, использует ресурсы процесса, работает независимо от других потоков процесса , но имеет собственные
объект ядра (дескриптора потока),
стек, который содержит параметры функций, локальные переменные для работы потока.
Процессы инертны, они ничего не исполняют, но служат "контейнером " для потоков..
Для создания потока в приложении, использующем библиотеку MFC, достаточно создать функцию, которая должна выполняться параллельно с главным потоком. Когда функция завершит работу поток будет уничтожен. Вызов данной функции (запуск потока) выполняется с помощью вызова функции , прототип которой может иметь вид:

CWinThread* AfxBeginThread (
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

Функция возвращает указатель на объект потока.
pfnThreadProc - имя выполняемой в потоке процедуры.
pParam - 32-разрядный параметр, который можно передавать потоку. (Часто это дескриптор окна).
nPriority - приоритет потока. Значение приоритета определяется следующими константами:

Константа Значение
THREAD_PRIORITY_NORMAL 0
THREAD_PRIORITY_BELOW_NORMAL 1
THREAD_PRIORITY_ ABOVE _NORMAL -1
THREAD_PRIORITY_HIGHEST 2
THREAD_PRIORITY_ LOWEST -2
THREAD_PRIORITY_ IDLE -15 Устанавливает базовый приоритет равным 1. Для
процесса REALTUM_PRIORITY_CLASS
устанавливает приоритет равным 16.
THREAD_PRIORITY_TIME_CRITICAL Устанавливает базовый приоритет равным 15. Для
процесса REALTUM_PRIORITY_CLASS
устанавливает приоритет равным 30.
nStackSize - специфицирует размер стека в байтах для нового потока. Если значение 0, то размер стека такой же, как у создающего потока.
dwCreateFlags - специфицирует дополнительные флаги для создания потока. Параметр может принимать значения:
CREATE_SUSPENDED задерживает выполнение созданного потока до вызова функции ResumeThread.
0 - созданный поток выполняется немедленно.
lpSecurityAttrs - указатель на структуру, содержащую параметры безопасности. Если 0 , то параметры те же, что и у создающего потока.

Создание и запуск простейшего потока

Шаг 1. С помощью AppWizard создать SDI - приложение (My - имя проекта).
Шаг 2. С помощью редактора ресурсов добавить в меню команду "Запуск потока" с идентификатором ID_STARTTHEAD .
Шаг 3. С помощью ClassWizard связать команду "Запуск потока" с функцией класса СМyThreadView::OnStarttread().
Шаг 4. В файл MyView.cpp добавить глобальную функцию ThreadProc ( ) ( перед методом OnStarttread() ).

UINT ThreadProc (LPVOID param)
{ MessageBox(0,"Поток стартовал","Сообщение о потоке", MB_OK);
}
void CMyThreadView::OnStartthread()
{ HWND hwnd = GetSafeHwnd () ;
AfxBeginThread (ThreadProc, hwnd, 0) ;
}

Запустить вторичный поток, представленный функцией ThreadProc (), можно, запустив данное приложение и вызвав в нем команду "Запуск потока".

Взаимодействие потоков

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

Использование глобальных переменных

Данный механизм является простейшим способом взаимодействия потоков. Глобальные переменные, используемые различными потоками объявляются с ключевым словом volatile., что означает, что переменные, объявленные в этом потоке могут быть доступны ( в том числе и изменены) другими потоками.
Для демонстрации данного механизма можно слегка модифицировать пример 1.
Пример 2.

Шаг 1. В начало файла MyView.cpp добавить
volatile char Global_Var ;.
Шаг 2. Добавить в меню команду "Остановка потока" с идентификатором ID_STOPTHEAD.
Связать команду с функцией OnStop(). Функции OnStop(), OnStart()
можно определить следующим образом:

void CMyThreadView::OnStop ()
{ Global_Var = 0;
}
void CMyThreadView::OnStart()
{ HWND hwnd = GetSafeHwnd();
AfxBeginThread (ThreadProc, hwnd, 0);
Global_Var = 1;
}

Шаг 3. Функцию потока можно задать так:

UINT ThreadProc (LPVOID param)
{

while (Global_Var == 0) ::Sleep(50);
while (Global_Var == 1)
{ // . . . какие-то действия
} // цикл потока выполняется, пока Global_Var =1
return 0;
}
Запустить поток можно командой "Запуск потока", завершить - командой "Остановка потока" .


Использование сообщений

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

BOOL PostMessage ( HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam );

hwnd - дескриптор окна, которому адресовано сообщение,
msg - номер сообщения ( должен быть не меньше , WM_USER).
wparam, lparam - параметры сообщения.

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

Пример 3 ( модификация Примера 2).

1. Определяем константу WM_USERMSG = WM_USER +100;
2. В карту сообщений заголовочного файла оконного класса СMyView добавить
afx_msg LONG OnThreadMessage (WPARAM wparam,LPARAM lparam );
3. В карту сообщений файла .срр добавить
ON_MESSAGE (WM_USERMSG, OnThreadMessage )
4. В функцию потока вставить вызов :: PostMessage ().

UINT ThreadProc ( LPVOID param)
{ . . . .
::PostMessage ((HWND)param, WM_USERMSG, wparam, lparam);
. . . .
}

5. В файл MyThreadView.срр добавить определение функции
LONG СMyThreadView::OnThreadMessage ( WPARAM wparam,
LPARAM lparam )
{ . . .
}


Взаимодействие потоков с помощью событий

Для представления объекта события можно использовать класс MFC CEvent. Объект может находиться в одном из двух состояний - сигнализирует или молчит.
Конструктор класса CEvent имеет достаточно много параметров, но часто все параметры задаются по умолчанию. Создать и перевести объект в состояние "сигнализирует " можно следующим образом:
CEvent MyEventObject; //Объявление переменной типа CEvent
MyEventObject. SetEvent();
Состояние объекта определяется с помощью функции API
DWORD WaitForSingleObject
( HANDLE , // дескриптор объекта
DWORD // интервал ожидания события в млсек.
// Если 0, то событие уже наступило
);

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

WAIT_OBJECT_0
Объект перешел в состояние "сигнализирует"

WAIT_TIMEOUT
Объект не перешел в состояние "сигнализирует" в течении заданного времени.

В случае неудачного завершения функции возвращается значение WAIT_FAILED.

Пример 4 ( модификация Примера 3).

1. В файл MyView.срр включить заголовочный файл afxmt.h.
#include
2. В файл MyView.срр добавить объявления
CEvent StopEvent;
CEvent StartEvent;
3.В меню добавить новые команды
Событие "Запуск потока"
Событие "Остановка потока"
Данные команды имеют идентификаторы ID_EVENTSTART и ID_EVENTSTOP и обрабатываются функциями класса CMyThreadView:: nEventstart, и CMyThreadView::OnEventstop.

4. Функции меню OnEventStart(), OnEventStop(), а также функцию потока можно определить следующим образом:

UINT ThreadProcEvent (LPVOID param)
{ ::WaitForSingleObject(StartEvent.m_hObject, INFINITE);
while (1)
{ int res=::WaitForSingleObject(StopEvent.m_hObject, 0);
// . . .выполнение каких-либо действий
if (res == WAIT_OBJECT_0) break;
}
AfxMessageBox( "Поток завершен");
return 0;
}

void CMyThreadView::OnEventstart()
{ HWND hwnd = GetSafeHwnd();
AfxBeginThread (ThreadProcEvent, hwnd, 0);
StartEvent.SetEvent();
}

void CMyThreadView::OnEventstop()
{ StopEvent.SetEvent();
}

Синхронизация потоков

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

Критические секции

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

class CMyClass
{ private :
. . . // общие данные , используемые разными потоками

CCriticalSection CritSect;
// объект критическая секция
public:
CMyClass ( ); // конструктор
~CMyClass ( ); // деструктор
void Function1 (. . . );
void Function2 (. . . );
}

Открытые методы Function1( ) и Function2 ( ) обычно представляют операции чтения и записи защищаемых данных. Для монопольного доступа к защищаемым данным эти методы вызывают функции Lock ( ) и UnLock ( ) класса CCriticalSection . Реализация этих функций имеет примерно такой вид:

void CMyClass:: Function1 (. . . )
{ CritSect. Lock ( ) ;
. . . // обработка данных
CritSect. UnLock ( ) ;
}

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

Пример использования критической секции

В качестве разделяемого ресурса будем использовать текстовую строку. Поток - поставщик будет формировать строку символов, поток - потребитель
· читать. Класс, содержащий данный разделяемый ресурс, находится в файле MyClass.h и имеет вид:

#include "afxmt.h"

class CMyClass
{ private:
int Num;
char str[10];
public:
CCriticalSection CritSect;
CMyClass() {}
~CMyClass() {}
void SetStr(char *);
void GetStr(char *);
};

Определение методов void SetStr(char *) и GetStr(char *) назодится в файле MyClass.срр и имеет вид

void CMyClass::SetStr( char * sss)
{ CritSect.Lock ( ) ;
for (int i=0; i<10; i++) str[i]=sss[i];
CritSect.Unlock ( ) ;
}

void CMyClass::GetStr(char * sss)
{ CritSect.Lock ( ) ;
for (int i=0; i<10; i++) sss[i]=str[i];
CritSect.Unlock ( ) ;
}

В меню добавлена команда "Запуск поставщика и потребителя". По этой команде запускаются оба потока. Процесс-поставщик формирует в цикле 10 раз строку и помещает ее в разделяемый ресурс.
Объявление переменной данного класса, функции новых потоков и функция запуска потоков помещены в файл МyView.cpp и определены следующим образом:

CMyClass CCC;

UINT ThreadProcSup1 (LPVOID param) // Процесс-поставщик
{ char sss[10];
sss[9]='\n'; // последний символ строки всегда \n
for (int i=0; i<10; i++)
{ for (int j=0; j<9; j++) sss[j]='A'+i;
{ CCC.SetStr (sss);
::Sleep(10); // Задержка потока на 10 млсек
}
}
return 0;
}

UINT ThreadProcCus1 (LPVOID param) // Процесс- потребитель
{ char sss[100];
int code;
for (int i=0; i<10; i++)
{ CCC.GetStr (sss+10*i);
::Sleep(10);
}
sss[99]='\0';
::MessageBox((HWND)param, sss,"ThreadProcCus", MB_OK);
// Вывод сразу всех прочитанных строк
return 0;
}

void CMyThreadView::OnSupCust ()
// Запуск потоков поставщика и потребителя
{ HWND hwnd = GetSafeHwnd();
AfxBeginThread (ThreadProcSup1, hwnd, 0);
AfxBeginThread (ThreadProcCus1, hwnd, 0);
}

Результаты совместной работы данных потоков

Использование защелок
Механизм защелок (класс MFC CMutex) похож на критические секции, но обеспечивает разделение доступа к ресурсу не только для потоков одного приложения, но и для потоков разных приложений.
Вопросы для самопроверки:
Что такое поток?
Что такое сообщение?
Описать использование защелок?
Что такое событие?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 2. Семафоры (2 час.)
Цели и задачи: Изучить семафоры.Написать примерную реализацию класса семафора.
Рассмотреть работу класса на примере.
Учебные вопросы: Понятие семафоров, их устройство. Передача сообщений в потоки.
Учебная информация:

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

Конструктор класса CSemaphore имеет вид

CSemaphore (
LONG lInitialCount = 1, // Начальное значение счетчика,
// должно быть >0 и <= lMaxCount
LONG lMaxCount = 1, // Макс. значение счетчика
// (макс число потоков, которым предоставлен
// доступ к ресурсу )
LPCTSTR pstrName = NULL, // имя семафора, используется, если доступ к
// семафору осуществляется через границы
// адресного пространства
LPSECURITY_ATTRIBUTES lpsaAttributes = NULL //
);

При работе с семафором обычно можно выполнить следующие действия:

1) Создать класс для общего разделяемого ресурса
2) В этом классе следует объявить члены-данные типа CSemaphore.
3) В конструкторе класса следует инициализировать эти члены-данные.
4) Для увеличения счетчика обращений следует использовать функция
CSingleLock::Lock() или CMultiLock::Lock(). Объект типа CSingleLock
или CMultileLock можно сделать локальным в функциях, использующих ресурс.
Инициализируются эти объекты объектом - семафором.

Пример

class CMyResource
{ public:
CSemaphore * mysemaphore;
CString str ;
CMyResource ( )
{ mysemaphore = new CSemaphore(2,2);
str = _T("0");
}
~CMyResource () { delete mysemaphore;
}
void MyUse (char ch)
{ CSingleLock mylock ( mysemaphore ) ;
mylock.Lock ( ) ;
for ( int i = 0 ; i < 10 ; i ++ )
{ str.AppendChar ( (TCHAR) ch ) ;
Sleep(1);
}
Sleep(5000);
}
void ShowRes() { AfxMessageBox(str);
}
} ;

CMyResource MyResource; // объект, представляющий ресурс

UINT MyProcSem (LPVOID param) // поток
{ char* ptr = (char*)param;
char ch = ptr[0];
MyResource.MyUse (ch);
return 0;
}
void CThreadsView::OnThreadsSemaphore() // Запуск 3 потоков.
{ // TODO: Add your command handler code here
char * ch1 = "A";
char * ch2 = "B";
char * ch3 = "C";
AfxBeginThread(MyProcSem, ch1);
AfxBeginThread(MyProcSem, ch2);
AfxBeginThread(MyProcSem, ch3);

}
void CThreadsView::OnMenu() // Просмотр измененного ресурса
{ MyResource.ShowRes();
// TODO: Add your command handler code here
}

Передача сообщений в потоки

Сообщения в потоки передаются с использованием функции PostThreadMessage().
Внутри потока следует организовать цикл обработки очереди сообщений.
Очередь сообщений изначально пуста, точнее даже не создана. Для создания
очереди сообщений потоку следует принудительно послать сообщение.
Состояние очереди можно проверить с помощью функции PeekMessage().

Синтаксис функции PeekMessage() следующий:

BOOL PeekMessage(
LPMSG lpMsg, // адрес переменной типа Msg
HWND hWnd, // дескриптор окна. Если NULL, то принимаются
// сообщения для всех окон, принадлежащих потоку
UINT wMsgFilterMin, // min и max значение идентификаторов
UINT wMsgFilterMax, // принимаемых сообщений
UINT wRemoveMsg // удалять или не удалять сообщение из
); // очереди

Синтаксис функции PostThreadMessage() следующий:

BOOL PostThreadMessage (
DWORD idThread, // идентификатор потока, которому должно
// быть послано сообщение
UINT Msg, // идентификатор сообщения
WPARAM wParam,
LPARAM lParam
);
Рассмотрим пример функции потока, в которой создается очередь сообщений
и затем обрабатывается.

DWORD threaded ; // глоб. перем.
UINT MyThread(LPVOID param)
{ CString str;
MSG msg;
char sss[100];
threadID = GetCurrentThreadId(); // идентификатор текущего потока.
sprintf (sss, "Поток %d стартовал", threadID);
AfxMessageBox (CString(sss));

int i = 0;
while ( 1 )
{ BOOL t = PeekMessage(&msg, NULL, WM_USER, WM_USER+100, PM_NOREMOVE);
// проверка наличия очереди сообщений
result = t;
if (t) break; // выход из цикла, если очередь создана
i++;
if (i == 10) i = 0;
if (i == 0) AfxMessageBox (_T("Очередь для потока не создана!"));
Sleep(250);
}
AfxMessageBox (_T("Очередь для потока создана 1"));

// Обработка сообщений, посланных потоку
while (1)
{ while (PeekMessage(&msg, NULL, WM_USER, WM_USER+100, PM_REMOVE))
{
UINT lParam = (UINT)msg.lParam;
UINT wParam = (UINT)msg.wParam;
sprintf (sss, "msg.message = %d wParam = %d lParam = %d",
msg.message, wParam, lParam);
AfxMessageBox(CString(sss));
}
AfxMessageBox (_T("Очередь потока пуста "));
}
// Функция запуска потока
void CThreadsView::OnStart()
{
// char sss[100];
// DWORD cur_theadID = GetCurrentThreadId();
// sprintf(sss, "Текущий поток имеет идентификатор %d ", cur_theadID);
// AfxMessageBox (CString(sss));

int* param = 0;
AfxBeginThread ( MyThread, param, 0);
}

// Функция отправки сообщения потоку может
// быть привязана к какому-либо пункту меню
int iNum = 0;
void CThreadsView::OnPost()
{
BOOL t ;
int i = 0;
while(result == false)
{ t=PostThreadMessage(threadID, WM_USER+10+iNum, 1000+10*iNum, 1000+10*iNum);
++iNum;
}

}
Переменная result может быть переменной volatile? Тем самых она будет определять продолжительность цикла while. Как сделать так, чтобы поток, например, получал данные о нажатии клавиши мыши

void CThreadsView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
PostThreadMessage( threadID, WM_LBUTTONDOWN, point.x, point.y);
CView::OnLButtonDown(nFlags, point);
}

Но WM_LBUTTONDOWN = 513, для этого можно расширить диапазон обрабатываемых
сообщений. Например в функции PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)
использовать границы диапазона сообщений 0, 0 - это все возможные сообщения.

Вопросы для самопроверки:
Что такое семафоры?
Опишите примерный класс работы семафоры?
Опишите передачу сообщения в поток?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Раздел 2. Параллельное программирование (8 час.)
Тема 1. Многоядерные архитектуры (2 час.)
Цели и задачи: Изучить многоядерные архитектуры .
Рассмотреть понятия параллельного программирования.
Учебные вопросы: Понятие закона Мура.Состояние гонки. Классификация многопроцессорных систем по доступу к памяти.
Учебная информация:

Закона Мура гласит, что количество транзисторов, размещенных на полупроводниковой микросхеме, удваивается каждые два года, что приводит, с одной стороны, к повышению производительности, а с другой стороны, к снижению стоимости производства микросхем. Несмотря на важность и действенность этого закона в течение долгих лет, оценивая перспективы дальнейшего развития, время от времени предсказывали его неминуемый провал.
На протяжении длительного времени прогресс в области микропроцессоров фактически отождествлялся со значением тактовой частоты. В 2001 году в корпоративных планах производителей микропроцессоров значилось, что уже к концу десятилетия будет преодолен барьер 10 ГГц. Планы эти оказались неверны. Тактовую частоту процессоров стало наращивать все труднее и труднее, так как росло число транзисторов, проводников и выделяемое тепло. Тем более каждый транзистор потребляет энергию, в итоге, по данным IDC (International Data Corporation аналитическая фирма, специализирующаяся на исследованиях рынка информационных технологий), сегодня затраты на электричество, необходимое для питания центров обработки данных, составляют свыше 80% от затрат на приобретение компьютерного оборудования.
Конец гонке тактовых частот микропроцессоров был положен благодаря нерешенной проблеме токов утечки и неприемлемому росту тепловыделения микросхем .
Дальнейшее повышение тактовой частоты упирается в ряд фундаментальных физических барьеров:
во-первых, с уменьшением размеров кристалла и с повышением тактовой частоты возрастает ток утечки транзисторов. Это ведет к повышению потребляемой мощности и увеличению выброса тепла;
во-вторых, преимущества более высокой тактовой частоты частично сводятся на нет из-за задержек при обращении к памяти, так как время доступа к памяти не соответствует возрастающим тактовым частотам;
в-третьих, для некоторых приложений традиционные последовательные архитектуры становятся неэффективными с возрастанием тактовой частоты из-за так называемого «фон-неймановского узкого места» – ограничения производительности в результате последовательного потока вычислений. При этом возрастают резистивно-емкостные задержки передачи сигналов, что является дополнительным узким местом, связанным с повышением тактовой частоты [3].

В 2002 году был исчерпан ресурс повышения тактовой частоты процессора. Для рядового пользователя это прошло незамеченным, поскольку организация массового производства процессоров с тактовой частотой более 3 ГГц растянулась на несколько лет. К 2005 году был освоен серийный выпуск 3-гигагерцевых процессоров, и оказались в основном исчерпанными ресурсы архитектурного совершенствования отдельно взятого процессора. В апреле 2005 года Intel и AMD одновременно приступили к продаже двуядерных процессоров для персональных компьютеров по сути, двух процессоров на одной подложке.
К 2006 году все ведущие разработчики микропроцессоров создали двуядерные процессоры. Первыми появились двуядерные RISC-процессоры Sun Microsystems (UltraSPARC IV), IBM (Power4, Power5) и HP (PA-8800 и PA-8900).
Теперь забота о повышении скорости исполнения программ полностью ложится на плечи кодировщиков.

Так как основной характеристикой процессора является его тактовая частота, то введем частоту в формулу производительности процессора:

производительность = IPC * F

где IPC = кол-во_инструкций /кол-во_тактов - количество инструкций, выполняемых за один такт (Instruction Per Clock, IPC);
F = кол-во_тактов / время_выполнения - количество тактов процессора в единицу времени (тактовая частота процессора, F или Frequency).
Таким образом, производительность процессора зависит не только от его тактовой частоты, но и от количества инструкций, выполняемых за такт .
Полученная формула определяет ДВА разных подхода к увеличению производительности процессора (F). Первый - увеличение тактовой частоты процессора, а второй – увеличение количества инструкций программного кода, выполняемых за один такт процессора (IPC).
Увеличение тактовой частоты не может быть бесконечным и определяется технологией изготовления процессора. При этом рост производительности не является прямо пропорциональным росту тактовой частоты, то есть наблюдается тенденция насыщаемости, когда дальнейшее увеличение тактовой частоты становится нерентабельным.
Количество инструкций, выполняемых за время одного такта, зависит от микроархитектуры процессора: от количества исполнительных блоков, от длины конвейера и эффективности его заполнения, от блока предвыборки, от оптимизации программного кода к данной микроархитектуре процессора. Поэтому сравнение производительности процессоров на основании их тактовой частоты возможно только в пределах одной и той же архитектуры (при одинаковом значении количества выполняемых операций в секунду - IPC процессоров)

Как известно, существуют технологии, позволяющие реализовать параллельное выполнение нескольких задач(потоков) на одном процессоре. Такая многозадачность реализована в том или ином виде во ВСЕХ современных процессорах. Отход от последовательного исполнения команд и использование нескольких исполняющих блоков в одном процессоре позволяют одновременно обрабатывать несколько процессорных микрокоманд, то есть организовывать параллелизм на уровне инструкций (Instruction Level Parallelism, ILP), что, разумеется, увеличивает общую производительность [1].
Многоядерный процессор имеет два или больше "исполнительных ядер". Ядром процессора можно назвать его систему исполнительных устройств (набор арифметико-логических устройств), предназначенных для обработки данных. Операционная система рассматривает каждое из исполнительных ядер, как дискретный процессор со всеми необходимыми вычислительными ресурсами. Поэтому многоядерная архитектура процессора, при поддержке соответствующего программного обеспечения, осуществляет полностью параллельное выполнение нескольких программных потоков.
Десятилетиями программирование для персональных компьютеров существовало в оранжерейных условиях: оптимизация программ находилась если не на последнем, то далеко не на первом месте. Много внимания уделялось процессу групповой разработки, объектным паттернам программирования, сокращению времени создания программ. Считалось, что скорость работы программы увеличивается независимо от разработчика, просто в силу постоянного роста мощности процессора, ускорения обмена данными с памятью и Программирование на нескольких процессорах проблема не новая, но для ее решения от разработчика требуются специальная подготовка, особый образ мышления и высокая квалификация.

Классификация многопроцессорных систем по доступу к памяти

Очевидно, что многоядерные процессоры произошли из многопроцессорных систем. А вариантов создания многопроцессорных систем много. Однако существует их общепринятая классификация по доступу к памяти:

1 . SMP-системы (Symmetrical Multi Processor systems)
В подобной системе все процессоры имеют совершенно равноправный доступ к общей оперативной памяти. Работать с такими системами программистам намного легче, поскольку не возникает никаких специфичных "особенностей", связанных с архитектурой компьютера. Но, к сожалению, создавать подобные системы крайне трудно: 2-4 процессора - практический предел для стоящих разумные деньги SMP-систем. Такой тип системы использовался в процессорах Intel.
-------- -------- --------
|CPU | |CPU | |CPU |
-------- -------- --------
| | |
---------------------------------
| Общая среда связи |
---------------------------------
|
------------------------------------------
| Общая оперативная память |
------------------------------------------

2. NUMA-системы (Non-Uniform Memory Access systems) . Здесь доступ в память становится "неоднородной": один её кусок "быстрее", другой - "медленнее". В системе при этом образуются своеобразные "островки" со своей, быстрой "локальной" оперативной памятью, соединенные относительно медленными линиями связи. Обращения к "своей" памяти происходят быстро, к "чужой" - медленнее, причем чем "дальше" чужая память расположена, тем медленнее получается доступ к ней. Создавать NUMA-системы куда проще, чем SMP, а вот программы писать сложнее - без учета неоднородности памяти эффективную программу для NUMA уже не написать. Такой тип системы использовался в процессорах AMD.

3. Кластерный тип многопроцессорных систем . Просто берется некоторое количество «почти самостоятельных» компьютеров (узлы кластера или «ноды») и объединяются быстродействующими линиями связи. «Общей памяти» здесь может и не быть вообще. Но на практике обычно удобнее работать с кластером в «явном» виде, явно описывая в программе все пересылки данных между его узлами. То есть, если для NUMA еще можно создавать программы, почти не задумываясь над тем, как это работает и откуда берутся необходимые для работы потоков данные; то при работе с кластером требуется очень четко расписывать кто, что и где делает. Это очень неудобно для программистов, и, вдобавок, накладывает существенные ограничения на применимость кластерных систем, но зато эта система очень дешевая в своей реализации. Кластерная архитектура чаще всего используется в процессорах для суперкомпьютеров.
Дополнительное замечание об SMP-системах:
Главной особенностью систем с архитектурой SMP является наличие общей физической памяти, разделяемой всеми процессорами. Память служит, в частности, для передачи сообщений между процессорами, при этом все вычислительные устройства при обращении к ней имеют равные права и одну и ту же адресацию для всех ячеек памяти. Поэтому SMP-архитектура называется симметричной. Последнее обстоятельство позволяет очень эффективно обмениваться данными с другими вычислительными устройствами.
Самый простой и самый общий метод построения SMP-платформы - соединить процессоры и память с помощью высокоскоростной системной шины (SGI PowerPath, Sun Gigaplane, DEC TurboLaser), к слотам которой подключаются функциональные блоки типов: процессоры (ЦП), подсистема ввода/вывода (I/O) и т. п. Для подсоединения к модулям I/O используются уже более медленные шины (PCI, VME64). Положительная сторона этого решения состоит в том, что процессоры легко взаимодействуют как с памятью, так и между собой. Недостаток же в том, что по шине в каждый момент времени может передаваться только одно сообщение. Таким образом, именно шина становится потенциально узким местом.
Известно, что в SMP-системе любой процесс может выполняться на любом процессоре, но повторный запуск отложенного ранее процесса на другом процессоре приводит к отрыву того от кэшированных данных, и новый процессор, включившийся в работу, должен будет работать со скоростью основной памяти до тех пор, пока процесс не наберет достаточно ссылок для перезагрузки данных в кэш-память данного процессора. Ответственность за смягчение остроты этой проблемы ложится на ОС. Поддержка SMP должна быть встроена в операционную систему. Иначе дополнительные процессоры будут оставаться не загруженными, и система будет работать как однопроцессорная.
Этот подход оказался очень успешным в случае четырехпроцессорных серверов, однако ему присущ ряд ограничений, которые не позволяют применять его в среде с большим числом обрабатываемых транзакций. Другое ограничение данного подхода - низкая готовность. Отказ одного из компонентов может сделать шину недоступной для других. Определить, какой именно компонент вышел из строя, очень сложно, в особенности, если нельзя запустить ОС, и тогда вся система выводится из эксплуатации на период тестирования и ремонта.

Применение многопроцессорных систем так и не получило очень широкого распространения, так как требует сложных и дорогостоящих многопроцессорных материнских плат. Поэтому было решено добиваться дальнейшего повышения производительности микропроцессоров другими средствами. Самым эффективным направлением была признана концепция многопоточности, зародившаяся в мире суперкомпьютеров, – это одновременная параллельная обработка нескольких потоков команд
Вопросы для самопроверки:
Что такое многопроцессорное программирование?
Опишите закон Мурра?
Опишите состояние гонки?
Классификация многопроцессорных систем по доступу к памяти?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 2. -THREADING (2 час.)
Цели и задачи: Изучить общие принципы -THREADING.
Рассмотреть понятия многоядерного подхода.
Учебные вопросы: Пример описания технологии Hyper-Thread.
Учебная информация:

Предшественником многоядерного подхода можно считать технологию Intel – Hyper-Threading. Анонсированная в 2002 году компанией Intel технология Hyper-Threading – пример многопоточной обработки команд. Данная технология является чем-то средним между многопоточной обработкой, реализованной в мультипроцессорных системах, и параллелизмом на уровне инструкций, реализованном в однопроцессорных системах. Фактически технология Hyper-Threading позволяет организовать два логических процессора в одном физическом. Таким образом, с точки зрения операционной системы и запущенного приложения в системе существует два процессора, что даёт возможность распределять загрузку задач между ними точно так же, как при SMP-мультипроцессорной конфигурации.
В конструктивном плане процессор с поддержкой технологии Hyper-Threading состоит из двух логических процессоров, каждый из которых имеет свои регистры и контроллер прерываний (Architecture State, AS), а значит, две параллельно исполняемые задачи работают со своими собственными независимыми регистрами и прерываниями, но при этом используют одни и те же ресурсы процессора для выполнения своих задач. После активации каждый из логических процессоров может самостоятельно и независимо от другого процессора

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

Идея технологии Hyper-Threading тесно связана с микроархитектурой процессора Pentium 4 и является в каком-то смысле её логическим продолжением. Микроархитектура Intel NetBurst позволяет получить максимальный выигрыш в производительности при выполнении одиночного потока инструкций, то есть при выполнении одной задачи. Однако даже в случае специальной оптимизации программы не все исполнительные модули процессора оказываются задействованными на протяжении каждого тактового цикла. В среднем при выполнении кода, типичного для набора команд IA-32, реально используется только 35% исполнительных ресурсов процессора, а 65% исполнительных ресурсов процессора простаивают, что означает неэффективное использование возможностей процессора. Было бы вполне логично реализовать работу процессора таким образом, чтобы в каждом тактовом цикле максимально использовать его возможности. Именно эту идею и реализует технология Hyper-Threading, подключая незадействованные ресурсы процессора к выполнению параллельной задачи.

Пример 0

Есть гипотетический процессор, в котором имеется четыре исполнительных блока:
- два блока для работы с целыми числами (арифметико-логическое устройство, ALU),
- блок для работы с числами с плавающей точкой (FPU),
- блок для записи и чтения данных из памяти (Store/Load, S/L).

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

В этом случае вся программа будет выполнена за два такта процессора:
- в первом такте задействуются два блока ALU процессора ,
- во втором блок записи и чтения данных из памяти S/L.
Вопросы для самопроверки:
Что такое Hyper Threading?
Опишите принцип многоядерного подхода?
Покажите технологию на примере.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 3. OMP (2 час.)
Цели и задачи: Изучить общие принципы OMP.
Рассмотреть понятия OpenMP.
Учебные вопросы: Понятие стандарт OpenMP.
Учебная информация:

Лидером по решению задач программирования многоядерного ПО является корпорация Intel. Она предлагает свои пакеты разработки для решения проблемы паралелизации ПО (Intel® Parallel Studio). Также существуют стандартные API-интерфейсы распараллеливания, встроенные в современные операционные системы и языки программирования (OpenMP, MPI).

Стандарт OpenMP был разработан в 1997 г. как API, ориентированный на написание портируемых многопоточных приложений. Сначала он был основан на языке Fortran, но позднее включил в себя и C/C++. Последняя версия OpenMP 2.0; ее полностью поддерживает Visual C++ 2005.

Для активизации средств OpenMP служит появившийся в Visual C++ 2005 параметр компилятора /openmp.
(Вы можете активизировать директивы OpenMP на страницах свойств проекта, выбрав
Configuration Properties
C/C++
Language
и установив значение свойства OpenMP Support в YES



OpenMP прост в использовании и включает лишь два базовых типа конструкций:
- директивы pragma
- функции исполняющей среды OpenMP.
Директивы pragma, как правило, указывают компилятору реализовать параллельное выполнение блоков кода. Все эти директивы начинаются с
#pragma omp.
Как и любые другие директивы pragma, они игнорируются компилятором, не поддерживающим конкретную технологию в данном случае OpenMP.

Функции OpenMP служат в основном для изменения и получения параметров среды. Кроме того, OpenMP включает API-функции для поддержки некоторых типов синхронизации. Чтобы задействовать эти функции библиотеки OpenMP периода выполнения (исполняющей среды), в программу нужно включить заголовочный файл omp.h. Если вы используете в приложении только OpenMP-директивы pragma, включать этот файл не требуется.

#pragma omp <директива> [раздел [ [,] раздел]...]

OpenMP поддерживает директивы
parallel,
for,
parallel for,
section,
sections,
single,
master,
critical,
flush,
ordered
atomic,
которые определяют или механизмы разделения работы или конструкции синхронизации.

Раздел (clause) это необязательный модификатор директивы, влияющий на ее поведение. Списки разделов, поддерживаемые каждой директивой, различаются, а пять директив (master, critical, flush, ordered и atomic) вообще не поддерживают разделы.

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

#pragma omp parallel [раздел[ [,] раздел]...]
структурированный блок

Эта директива сообщает компилятору, что структурированный блок кода должен быть выполнен параллельно, в нескольких потоках. Каждый поток будет выполнять один и тот же поток команд, но не один и тот же набор команд все зависит от операторов, управляющих логикой программы, таких как if-else.

Пример 1.В качестве примера рассмотрим классическую программу «Hello World»:

#pragma omp parallel
{
printf("Hello World\n");
}

В двухпроцессорной системе вы, конечно же, рассчитывали бы получить следующее:

Hello World
Hello World

Тем не менее, результат мог бы оказаться и таким:

HellHell oo WorWlodrl
d

Второй вариант возможен из-за того, что два выполняемых параллельно потока могут попытаться вывести строку одновременно. Когда два или более потоков одновременно пытаются прочитать или изменить общий ресурс (в нашем случае им является окно консоли), возникает вероятность гонок (race condition). Это недетерминированные ошибки в коде программы, найти которые крайне трудно. За предотвращение гонок отвечает программист; как правило, для этого используют блокировки или сводят к минимуму обращения к общим ресурсам.

Пример 2.
Пример определяет средние значения двух соседних элементов массива и записывает результаты в другой массив. В этом примере используется -конструкция
#pragma omp for
, которая относится к директивам разделения работы (work-sharing directive). Такие директивы применяются не для параллельного выполнения кода, а для логического распределения группы потоков, чтобы реализовать указанные конструкции управляющей логики. Директива #pragma omp for сообщает, что при выполнении цикла for в параллельном регионе итерации цикла должны быть распределены между потоками группы:

#pragma omp parallel
{
#pragma omp for
for ( int i = 1; i < size; ++i )
x[i] = ( y[i-1] + y[i+1] ) / 2 ;
}

Если бы этот код выполнялся на четырехпроцессорном компьютере, а у переменной size было бы значение 100, то выполнение итераций 125 могло бы быть поручено первому процессору, 2650 второму, 5175 третьему, а 7699 четвертому. Это характерно для политики планирования, называемой статической. Политики планирования мы обсудим позднее.

Следует отметить, что в конце параллельного региона выполняется барьерная синхронизация (barrier synchronization). Иначе говоря, достигнув конца региона, все потоки блокируются до тех пор, пока последний поток не завершит свою работу.

Если из только что приведенного примера исключить директиву #pragma omp for, т.е.
#pragma omp parallel
{
for(int i = 1; i < size; ++i)
x[i] = (y[i-1] + y[i+1])/2;
}
, то каждый поток выполнит полный цикл for - 100 итераций, проделав много лишней работы:

Обратите внимание, что в этом цикле нет зависимостей, т. е. одна итерация цикла не зависит от результатов выполнения других итераций. А вот в двух следующем цикле есть зависимость:
for(int i = 1; i <= n; ++i) // цикл 1
a[i] = a[i-1] + b[i];
Распараллелить цикл проблематично потому, что для выполнения итерации i нужно знать результат итерации i-1.

Пример 3.
Программа перемножения двух матриц c использованием прагм языка OpenMP


#define SIZE 1000
static double a[SIZE][SIZE],b[SIZE][SIZE],c[SIZE][SIZE];

void MatrixMultiply_1 (double a[][SIZE], double b[][SIZE], double result[][SIZE])
{
int i,j,k;
#pragma omp parallel for
for(i=0;i { for(j=0;j { for(k=0;k { result[i][j]=result[i][j]+a[i][k]*b[k][j];
}
}
}
}
void MatrixMultiply _2(double a[][SIZE], double b[][SIZE], double result[][SIZE])
{ int i,j,k;
#pragma omp parallel sections
{
#pragma omp section
{
for(i=0;i { for(j=0;j { for(k=0;k { result[i][j]=result[i][j]+a[i][k]*b[k][j];
}
}
}
}
#pragma omp section
{ for(i=SIZE/2;i { for(j=0;j { for(k=0;k { result[i][j]=result[i][j]+a[i][k]*b[k][j];
}
}
}
}
}
}

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

DWORD t_0 = GetTickCount();
// фрагмент программы..
DWORD tс = GetTickCount() - t_0;

Общие и частные данные

Разрабатывая параллельные программы, нужно понимать, какие данные являются общими (shared), а какие частными (private), от этого зависит не только производительность, но и корректная работа программы. В OpenMP это различие очевидно, к тому же вы можете настроить его вручную.

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

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

#pragma omp parallel shared(U, U1) private(i)
{
#pragma omp for
////////////////////////////////////////////////////////////////////
for ( int j=1; j { tj = th*j;
// вычисление j-го слоя и вывод информац., если t = (j-1) % N_out == 0 есть true
. . .
for ( i=1; i< M; i++)
{ . . . .
}
} // for ( int j=1; tj < 1.0; j++)
////////////////////////////////////////////////////////////////////
}

Вопросы для самопроверки:
Что такое OMP?
Основные понятие OpenMP?
Покажите OpenMP на примере.
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.


Тема 4. MPI (2 час.)
Цели и задачи: Изучить общие принципы MPI .
Рассмотреть понятия MPI.
Учебные вопросы: Общие процедуры MPI.Основные функции.
Учебная информация:

MPI - message passing interface - библиотека функций, предназначенная для поддержки работы параллельных процессов в терминах передачи сообщений.
Номер процесса - целое неотрицательное число, являющееся уникальным атрибутом каждого процесса.
Атрибуты сообщения - номер процесса-отправителя, номер процесса-получателя и идентификатор сообщения. Для них заведена структура MPI_Status, содержащая три поля: MPI_Source (номер процесса отправителя), MPI_Tag (идентификатор сообщения), MPI_Error (код ошибки); могут быть и добавочные поля.
Идентификатор сообщения (msgtag) - атрибут сообщения, являющийся целым неотрицательным числом, лежащим в диапазоне от 0 до 32767.  Процессы объединяются в группы, могут быть вложенные группы. Внутри группы все процессы перенумерованы. С каждой группой ассоциирован свойкоммуникатор. Поэтому при осуществлении пересылки необходимо указать идентификатор группы, внутри которой производится эта пересылка. Все процессы содержатся в группе с предопределенным идентификатором MPI_COMM_WORLD.
При описании процедур MPI будем пользоваться словом OUT для обозначения "выходных" параметров, т.е. таких параметров, через которые процедура возвращает результаты.
Общие процедуры MPI
int MPI_Init( int* argc, char*** argv)
MPI_Init - инициализация параллельной части приложения. Реальная инициализация для каждого приложения выполняется не более одного раза, а если MPI уже был инициализирован, то никакие действия не выполняются и происходит немедленный возврат из подпрограммы. Все оставшиеся MPI-процедуры могут быть вызваны только после вызова MPI_Init.
Возвращает: в случае успешного выполнения - MPI_SUCCESS, иначе - код ошибки. (То же самое возвращают и все остальные функции, рассматриваемые в данном руководстве.)
int MPI_Finalize( void )
MPI_Finalize - завершение параллельной части приложения. Все последующие обращения к любым MPI-процедурам, в том числе к MPI_Init, запрещены. К моменту вызова MPI_Finalize некоторым процессом все действия, требующие его участия в обмене сообщениями, должны быть завершены.  Сложный тип аргументов MPI_Init предусмотрен для того, чтобы передавать всем процессам аргументы main:
int main(int argc, char** argv)
{
MPI_Init(&argc, &argv);
...
MPI_Finalize();
}
int MPI_Comm_size( MPI_Comm comm, int* size)
Определение общего числа параллельных процессов в группе comm.
comm - идентификатор группы
OUT size - размер группы
int MPI_Comm_rank( MPI_Comm comm, int* rank)
Определение номера процесса в группе comm. Значение, возвращаемое по адресу &rank, лежит в диапазоне от 0 до size_of_group-1.
comm - идентификатор группы
OUT rank - номер вызывающего процесса в группе comm
double MPI_Wtime(void)
Функция возвращает астрономическое время в секундах (вещественное число), прошедшее с некоторого момента в прошлом. Гарантируется, что этот момент не будет изменен за время существования процесса.
Прием/передача сообщений между отдельными процессами
Прием/передача сообщений с блокировкой
int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm)
buf - адрес начала буфера посылки сообщения
count - число передаваемых элементов в сообщении
datatype - тип передаваемых элементов
dest - номер процесса-получателя
msgtag - идентификатор сообщения
comm - идентификатор группы
Блокирующая посылка сообщения с идентификатором msgtag, состоящего из count элементов типа datatype, процессу с номером dest. Все элементы сообщения расположены подряд в буфере buf. Значение count может быть нулем. Тип передаваемых элементов datatype должен указываться с помощью предопределенных констант типа. Разрешается передавать сообщение самому себе.
Блокировка гарантирует корректность повторного использования всех параметров после возврата из подпрограммы. Выбор способа осуществления этой гарантии: копирование в промежуточный буфер или непосредственная передача процессу dest, остается за MPI. Следует специально отметить, что возврат из подпрограммы MPI_Send не означает ни того, что сообщение уже передано процессу dest, ни того, что сообщение покинуло процессорный элемент, на котором выполняется процесс, выполнивший MPI_Send.
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Status *status)
OUT buf - адрес начала буфера приема сообщения
count - максимальное число элементов в принимаемом сообщении
datatype - тип элементов принимаемого сообщения
source - номер процесса-отправителя
msgtag - идентификатор принимаемого сообщения
comm - идентификатор группы
OUT status - параметры принятого сообщения
Прием сообщения с идентификатором msgtag от процесса source с блокировкой. Число элементов в принимаемом сообщении не должно превосходить значения count. Если число принятых элементов меньше значения count, то гарантируется, что в буфере buf изменятся только элементы, соответствующие элементам принятого сообщения. Если нужно узнать точное число элементов в сообщении, то можно воспользоваться подпрограммой MPI_Probe.
Блокировка гарантирует, что после возврата из подпрограммы все элементы сообщения приняты и расположены в буфере buf.
В качестве номера процесса-отправителя можно указать предопределенную константу MPI_ANY_SOURCE - признак того, что подходит сообщение от любого процесса. В качестве идентификатора принимаемого сообщения можно указать константу MPI_ANY_TAG - признак того, что подходит сообщение с любым идентификатором.
Если процесс посылает два сообщения другому процессу и оба эти сообщения соответствуют одному и тому же вызову MPI_Recv, то первым будет принято то сообщение, которое было отправлено раньше.
int MPI_Get_count( MPI_Status *status, MPI_Datatype datatype, int *count)
status - параметры принятого сообщения
datatype - тип элементов принятого сообщения
OUT count - число элементов сообщения
По значению параметра status данная подпрограмма определяет число уже принятых (после обращения к MPI_Recv) или принимаемых (после обращения кMPI_Probe или MPI_Iprobe) элементов сообщения типа datatype.
int MPI_Probe( int source, int msgtag, MPI_Comm comm, MPI_Status *status)
source - номер процесса-отправителя или MPI_ANY_SOURCE
msgtag - идентификатор ожидаемого сообщения или MPI_ANY_TAG
comm - идентификатор группы 
OUT status - параметры обнаруженного сообщения 
Получение информации о структуре ожидаемого сообщения с блокировкой. Возврата из подпрограммы не произойдет до тех пор, пока сообщение с подходящим идентификатором и номером процесса-отправителя не будет доступно для получения. Атрибуты доступного сообщения можно определить обычным образом с помощью параметра status. Следует обратить внимание, что подпрограмма определяет только факт прихода сообщения, но реально его не принимает.
Прием/передача сообщений без блокировки
int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm, MPI_Request *request)
buf - адрес начала буфера посылки сообщения
count - число передаваемых элементов в сообщении
datatype - тип передаваемых элементов
dest - номер процесса-получателя
msgtag - идентификатор сообщения
comm - идентификатор группы
OUT request - идентификатор асинхронной передачи
Передача сообщения, аналогичная MPI_Send, однако возврат из подпрограммы происходит сразу после инициализации процесса передачи без ожидания обработки всего сообщения, находящегося в буфере buf. Это означает, что нельзя повторно использовать данный буфер для других целей без получения дополнительной информации о завершении данной посылки. Окончание процесса передачи (т.е. того момента, когда можно переиспользовать буфер bufбез опасения испортить передаваемое сообщение) можно определить с помощью параметра request и процедур MPI_Wait и MPI_Test.  Сообщение, отправленное любой из процедур MPI_Send и MPI_Isend, может быть принято любой из процедур MPI_Recv и MPI_Irecv.
int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Request *request)
OUT buf - адрес начала буфера приема сообщения
count - максимальное число элементов в принимаемом сообщении
datatype - тип элементов принимаемого сообщения
source - номер процесса-отправителя
msgtag - идентификатор принимаемого сообщения
comm - идентификатор группы
OUT request - идентификатор асинхронного приема сообщения
Прием сообщения, аналогичный MPI_Recv, однако возврат из подпрограммы происходит сразу после инициализации процесса приема без ожидания получения сообщения в буфере buf. Окончание процесса приема можно определить с помощью параметра request и процедур MPI_Wait и MPI_Test.
int MPI_Wait( MPI_Request *request, MPI_Status *status)
request - идентификатор асинхронного приема или передачи
OUT status - параметры сообщения
Ожидание завершения асинхронных процедур MPI_Isend или MPI_Irecv, ассоциированных с идентификатором request. В случае приема, атрибуты и длину полученного сообщения можно определить обычным образом с помощью параметра status.
int MPI_Waitall( int count, MPI_Request *requests, MPI_Status *statuses)
count - число идентификаторов
requests - массив идентификаторов асинхронного приема или передачи
OUT statuses - параметры сообщений
Выполнение процесса блокируется до тех пор, пока все операции обмена, ассоциированные с указанными идентификаторами, не будут завершены. Если во время одной или нескольких операций обмена возникли ошибки, то поле ошибки в элементах массива statuses будет установлено в соответствующее значение.
int MPI_Waitany( int count, MPI_Request *requests, int *index, MPI_Status *status)
count - число идентификаторов
requests - массив идентификаторов асинхронного приема или передачи
OUT index - номер завершенной операции обмена
OUT status - параметры сообщений
Выполнение процесса блокируется до тех пор, пока какая-либо операция обмена, ассоциированная с указанными идентификаторами, не будет завершена. Если несколько операций могут быть завершены, то случайным образом выбирается одна из них. Параметр index содержит номер элемента в массивеrequests, содержащего идентификатор завершенной операции.
int MPI_Waitsome( int incount, MPI_Request *requests, int *outcount, int *indexes, MPI_Status *statuses)
incount - число идентификаторов
requests - массив идентификаторов асинхронного приема или передачи
OUT outcount - число идентификаторов завершившихся операций обмена
OUT indexes - массив номеров завершившихся операции обмена
OUT statuses - параметры завершившихся сообщений
Выполнение процесса блокируется до тех пор, пока по крайней мере одна из операций обмена, ассоциированных с указанными идентификаторами, не будет завершена. Параметр outcount содержит число завершенных операций, а первые outcount элементов массива indexes содержат номера элементов массива requests с их идентификаторами. Первые outcount элементов массива statuses содержат параметры завершенных операций.
int MPI_Test( MPI_Request *request, int *flag, MPI_Status *status)
request - идентификатор асинхронного приема или передачи
OUT flag - признак завершенности операции обмена
OUT status - параметры сообщения
Проверка завершенности асинхронных процедур MPI_Isend или MPI_Irecv, ассоциированных с идентификатором request. В параметре flag возвращает значение 1, если соответствующая операция завершена, и значение 0 в противном случае. Если завершена процедура приема, то атрибуты и длину полученного сообщения можно определить обычным образом с помощью параметра status.
int MPI_Testall( int count, MPI_Request *requests, int *flag, MPI_Status *statuses)
count - число идентификаторов 
requests - массив идентификаторов асинхронного приема или передачи
OUT flag - признак завершенности операций обмена 
OUT statuses - параметры сообщений 
В параметре flag возвращает значение 1, если все операции, ассоциированные с указанными идентификаторами, завершены (с указанием параметров сообщений в массиве statuses). В противном случае возвращается 0, а элементы массива statuses неопределены.
int MPI_Testany(int count, MPI_Request *requests, int *index, int *flag, MPI_Status *status)
count - число идентификаторов
requests - массив идентификаторов асинхронного приема или передачи
OUT index - номер завершенной операции обмена
OUT flag - признак завершенности операции обмена
OUT status - параметры сообщения
Если к моменту вызова подпрограммы хотя бы одна из операций обмена завершилась, то в параметре flag возвращается значение 1, index содержит номер соответствующего элемента в массиве requests, а status - параметры сообщения.
int MPI_Testsome( int incount, MPI_Request *requests, int *outcount, int *indexes, MPI_Status *statuses)
incount - число идентификаторов
requests - массив идентификаторов асинхронного приема или передачи
OUT outcount - число идентификаторов завершившихся операций обмена
OUT indexes - массив номеров завершившихся операции обмена
OUT statuses - параметры завершившихся операций 
Данная подпрограмма работает так же, как и MPI_Waitsome, за исключением того, что возврат происходит немедленно. Если ни одна из указанных операций не завершилась, то значение outcount будет равно нулю.
int MPI_Iprobe( int source, int msgtag, MPI_Comm comm, int *flag, MPI_Status *status)
source - номер процесса-отправителя или MPI_ANY_SOURCE
msgtag - идентификатор ожидаемого сообщения или MPI_ANY_TAG
comm - идентификатор группы
OUT flag - признак завершенности операции обмена
OUT status - параметры обнаруженного сообщения
Получение информации о поступлении и структуре ожидаемого сообщения без блокировки. В параметре flag возвращает значение 1, если сообщение с подходящими атрибутами уже может быть принято (в этом случае ее действие полностью аналогично MPI_Probe), и значение 0, если сообщения с указанными атрибутами еще нет.
Объединение запросов на взаимодействие
Процедуры данной группы позволяют снизить накладные расходы, возникающие в рамках одного процессора при обработке приема/передачи и перемещении необходимой информации между процессом и сетевым контроллером. Несколько запросов на прием и/или передачу могут объединяться вместе для того, чтобы далее их можно было бы запустить одной командой. Способ приема сообщения никак не зависит от способа его посылки: сообщение, отправленное с помощью объединения запросов либо обычным способом, может быть принято как обычным способом, так и с помощью объединения запросов.
int MPI_Send_init( void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm, MPI_Request *request)
buf - адрес начала буфера посылки сообщения
count - число передаваемых элементов в сообщении
datatype - тип передаваемых элементов
dest - номер процесса-получателя
msgtag - идентификатор сообщения
comm - идентификатор группы
OUT request - идентификатор асинхронной передачи
Формирование запроса на выполнение пересылки данных. Все параметры точно такие же, как и у подпрограммы MPI_Isend, однако в отличие от нее пересылка не начинается до вызова подпрограммы MPI_Startall.
int MPI_Recv_init( void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Request *request)
OUT buf - адрес начала буфера приема сообщения
count - число принимаемых элементов в сообщении
datatype - тип принимаемых элементов
source - номер процесса-отправителя
msgtag - идентификатор сообщения
comm - идентификатор группы
OUT request - идентификатор асинхронного приема
Формирование запроса на выполнение приема данных. Все параметры точно такие же, как и у подпрограммы MPI_Irecv, однако в отличие от нее реальный прием не начинается до вызова подпрограммы MPI_Startall.
MPI_Startall( int count, MPI_Request *requests)
count - число запросов на взаимодействие
OUT requests - массив идентификаторов приема/передачи
Запуск всех отложенных взаимодействий, ассоциированных вызовами подпрограмм MPI_Send_init и MPI_Recv_init с элементами массива запросов requests. Все взаимодействия запускаются в режиме без блокировки, а их завершение можно определить обычным образом с помощью процедур MPI_Wait иMPI_Test.
Совмещенные прием/передача сообщений
int MPI_Sendrecv( void *sbuf, int scount, MPI_Datatype stype, int dest, int stag, void *rbuf, int rcount, MPI_Datatype rtype, int source, MPI_Datatype rtag, MPI_Comm comm, MPI_Status *status)
sbuf - адрес начала буфера посылки сообщения
scount - число передаваемых элементов в сообщении
stype - тип передаваемых элементов
dest - номер процесса-получателя
stag - идентификатор посылаемого сообщения
OUT rbuf - адрес начала буфера приема сообщения
rcount - число принимаемых элементов сообщения
rtype - тип принимаемых элементов
source - номер процесса-отправителя
rtag - идентификатор принимаемого сообщения
comm - идентификатор группы
OUT status - параметры принятого сообщения
Данная операция объединяет в едином запросе посылку и прием сообщений. Принимающий и отправляющий процессы могут являться одним и тем же процессом. Сообщение, отправленное операцией MPI_Sendrecv, может быть принято обычным образом, и точно также операция MPI_Sendrecv может принять сообщение, отправленное обычной операцией MPI_Send. Буфера приема и посылки обязательно должны быть различными.
Коллективные взаимодействия процессов
В операциях коллективного взаимодействия процессов участвуют все процессы коммуникатора. Соответствующая процедура должна быть вызвана каждым процессом, быть может, со своим набором параметров. Возврат из процедуры коллективного взаимодействия может произойти в тот момент, когда участие процесса в данной операции уже закончено. Как и для блокирующих процедур, возврат означает то, что разрешен свободный доступ к буферу приема или посылки, но не означает ни того, что операция завершена другими процессами, ни даже того, что она ими начата (если это возможно по смыслу операции).
int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int source, MPI_Comm comm)
OUT buf - адрес начала буфера посылки сообщения
count - число передаваемых элементов в сообщении
datatype - тип передаваемых элементов
source - номер рассылающего процесса
comm - идентификатор группы
Рассылка сообщения от процесса source всем процессам, включая рассылающий процесс. При возврате из процедуры содержимое буфера buf процессаsource будет скопировано в локальный буфер процесса. Значения параметров count, datatype и source должны быть одинаковыми у всех процессов.
int MPI_Gather( void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int dest, MPI_Comm comm)
sbuf - адрес начала буфера посылки
scount - число элементов в посылаемом сообщении
stype - тип элементов отсылаемого сообщения
OUT rbuf - адрес начала буфера сборки данных
rcount - число элементов в принимаемом сообщении
rtype - тип элементов принимаемого сообщения
dest - номер процесса, на котором происходит сборка данных
comm - идентификатор группы
OUT ierror - код ошибки
Сборка данных со всех процессов в буфере rbuf процесса dest. Каждый процесс, включая dest, посылает содержимое своего буфера sbuf процессу dest. Собирающий процесс сохраняет данные в буфере rbuf, располагая их в порядке возрастания номеров процессов. Параметр rbuf имеет значение только на собирающем процессе и на остальных игнорируется, значения параметров count, datatype и dest должны быть одинаковыми у всех процессов.
int MPI_Allreduce( void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
sbuf - адрес начала буфера для аргументов
OUT rbuf - адрес начала буфера для результата
count - число аргументов у каждого процесса
datatype - тип аргументов
op - идентификатор глобальной операции
comm - идентификатор группы
Выполнение count глобальных операций op с возвратом count результатов во всех процессах в буфере rbuf. Операция выполняется независимо над соответствующими аргументами всех процессов. Значения параметров count и datatype у всех процессов должны быть одинаковыми. Из соображений эффективности реализации предполагается, что операция op обладает свойствами ассоциативности и коммутативности.
int MPI_Reduce( void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)
sbuf - адрес начала буфера для аргументов
OUT rbuf - адрес начала буфера для результата
count - число аргументов у каждого процесса
datatype - тип аргументов
op - идентификатор глобальной операции
root - процесс-получатель результата
comm - идентификатор группы
Функция аналогична предыдущей, но результат будет записан в буфер rbuf только у процесса root.
Синхронизация процессов
int MPI_Barrier( MPI_Comm comm)
comm - идентификатор группы
Блокирует работу процессов, вызвавших данную процедуру, до тех пор, пока все оставшиеся процессы группы comm также не выполнят эту процедуру.
Работа с группами процессов
int MPI_Comm_split( MPI_Comm comm, int color, int key, MPI_Comm *newcomm)
comm - идентификатор группы
color - признак разделения на группы
key - параметр, определяющий нумерацию в новых группах
OUT newcomm - идентификатор новой группы
Данная процедура разбивает все множество процессов, входящих в группу comm, на непересекающиеся подгруппы - одну подгруппу на каждое значение параметра color (неотрицательное число). Каждая новая подгруппа содержит все процессы одного цвета. Если в качестве color указано значениеMPI_UNDEFINED, то в newcomm будет возвращено значение MPI_COMM_NULL.
int MPI_Comm_free( MPI_Comm comm)
OUT comm - идентификатор группы
Уничтожает группу, ассоциированную с идентификатором comm, который после возвращения устанавливается в MPI_COMM_NULL.
Другие предопределенные типы
MPI_Status - структура; атрибуты сообщений; содержит три обязательных поля:
MPI_Source (номер процесса отправителя)
MPI_Tag (идентификатор сообщения)
MPI_Error (код ошибки)
MPI_Request - системный тип; идентификатор операции посылки-приема сообщения
MPI_Comm - системный тип; идентификатор группы (коммуникатора)
MPI_COMM_WORLD - зарезервированный идентификатор группы, состоящей их всех процессов приложения
Константы-пустышки
MPI_COMM_NULL
MPI_DATATYPE_NULL
MPI_REQUEST_NULL
Константа неопределенного значения
MPI_UNDEFINED
Глобальные операции
MPI_MAX
MPI_MIN
MPI_SUM
MPI_PROD
Любой процесс/идентификатор
MPI_ANY_SOURCE
MPI_ANY_TAG
Код успешного завершения процедуры
MPI_SUCCESS
Вопросы для самопроверки:
Что такое MPI?
Основные функции MPI?
Опишите рабоу с группами процессов?
Коллективные взаимодействия процессов?
Список литературы:
Технологии программирования C++ (+ CD-ROM): В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.





МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук




МАТЕРИАЛЫ ДЛЯ ПРАКТИЧЕСКИХ ЗАНЯТИЙ
по дисциплине «Технология программирования»
090104.65 «Комплексная защита объектов информатизации»

































г. Владивосток 2012



Лабораторные работы (17 час.)
Лабораторная работа 1. Использование исключений C++ (4 час.)
Цели и задачи:
Знакомство c механизмами обработи исключений. В ходе выполнения работы требуется:
Изучить структуру try-catch и try-finally-excepr блоков, объектно-ориентированный подход к обработке исключений.
Изучить способы перехватывания системных исключений и преобразовие их в исключения С++.
Создать приложение в среде Visual Studio C++ 6.0 или Visual Studio 2005, содержащее классы для объектно-ориентированной обработки исключений.
1. Генерация исключений
Технология обработки исключений в языке С++ основана на концепции глобального перехода. Когда происходит исключительная ситуация, программа генерирует исключение  с помощью оператора   throw переменная Где - то в программе должен быть код, перехватывающий и обрабатывающий исключение.  Оперратор throw может использовать переменную исключения, тип пкоторой опрделяет обработчик исключения. За try - блоком, содержащим операторы throw, может следовать один или несколько catch - блоков, которые содержат код обработки исключения. Синтаксис этих блоков имеет вид:
try
{ //проверяемый код
. . .
throw переменная ;
. . .
throw переменная ;
. . .
}
catch ( тип_1 [ Х_1] )
{ // код, обрабатывающий исключение типа1
. . .
}
catch ( тип_2 [ Х_2] )
{ // код, обрабатывающий исключение типа2
. . .
}
catch (. . . )
{ // код, обрабатывающий все оставшиеся исключения
. . .
}
Типы представляют любой встроенный тип, структуру или класс. Необязательные параметры Х_1, Х_2 могут иметь тип тип_1,   тип_1 & , const или   тип_1     const &.
2. Объектно - ориентированный подход к обработке исключений
Преимущество использования классов весьма ощутимо, когда есть много типов исключений, которые можно объединить в иерархию, а в обработке использовать виртуальные функции. Единственным требованием к оператору catch является требование перехватывать исключения по ссылке или указателю а не по значению. Иерархию классов обработки ошибок и структуру try - catch блоков можно представить следующим образом:
class ErrorCatch
{public:
// члены-данные класса
. . .
// члены-функции класса
virtual void ErrorHandler () = 0;
// чистая виртуальная функция, которая должна быть
// перегружена в производных классах
};
class ErrorType_1 public ErrorCatch
{public:
// члены-данные класса
. . .
// члены-функции класса
virtual void ErrorHandler () { . . . }
// перегружаемая виртуальная функция, которая
// обрабатывает ошибку типа ErrorType_1
};
class ErrorType_2 public ErrorCatch
{public:
// члены-данные класса
. . .
// члены-функции класса
virtual void ErrorHandler () { . . . }
// перегружаемая виртуальная функция, которая
// обрабатывает ошибку типа ErrorType_2
};
. . .
try
{
ErrorCatch * ptrEr;
{ //указатель базового класса
. . .
ptrEr = new ErrorType_1(. . .);
throw ptrEr ;
// генерация исключения типа ErrorType_1
. . .
ptrEr = new ErrorType_2(. . .);
throw ptrEr ;
// генерация исключения типа ErrorType_1
. . .
}
catch (ErrorCatch * ptrEr )
{ // код, обрабатывающий исключение ErrorType_1 и ErrorType_2
ptrEr -> ErrorHandler ();
}

3. Исключения в библиотеке MFC
При работе с библиотекой MFC генерируются исключения типа CException или типа, производного от CException. Библиотека MFC поддерживает следующие исключения:
CMemoryException Нет памяти
CFileException Исключение при работе с файлами
CArchiveException Исключения при выполнении сериализации
CResourceException Исключения при размещении ресурсов Windows
CDaoException Исключение при работе с классами DAO
CDBException Исключение при работе с классами ODBC
COleException Исключение при работе с СОМ-объектами
COleDispatchException Исключение при автоматизации СОМ-объектов
и др.
Класс CException является абстрактным классом, следовательно, в приложениях непосредственно им не пользуются. Целесообоазно использовать указанные классы исключений или создавать свой класс, производный от CException или любого из указанных классов.  Рассмотрим исключение типа CFileException. Ниже прведены основные члены данные и членвы функции данного класса.
ЧЛЕНЫ - ДАННЫЕ
m_cause Код исключения
m_lOsError Номер ошибки, определяемый операционной системой
m_strFileName Имя файла

ЧЛЕНЫ - ФУНКЦИИ
CFileException Конструктор класса
ErrnoToException Возвращает код исключения, которое произошло во время
выполнения
OsErrorToException Возвращает номер ошибки, определяемый операционной системой
ThrowErrno Генерирует исключение типа CFileException
ThrowOsError Генерирует исключение типа CFileException
 
Пример использования исключения CFileException

CFile f;
CFileException e;

TCHAR * pFileName = _T("Rtest1.dat");
TCHAR w_text[256];

// Открытие файла для чтения. Rtest1.dat - такого файла нет
try
{
if( !f.Open( pFileName, CFile::modeRead, &e ))
{
#ifdef _DEBUG
// Этот фрагмент кода работает только в отладочном режиме при открытии файла.
afxDump << "File could not be opened " <<
"\n e.m_strFileName = " << e.m_strFileName <<
"\n e.m_cause = " << e.m_cause <<
"\n e.m_lOsError = " << e.m_lOsError <<
"\ne.accessDenied = " << e.accessDenied<<
"\n";
e.GetErrorMessage(w_text, 255);
// Получаем системное сообщение об ошибке в переменную w_text
afxDump << w_text << "\n";
#endif

// В режиме отладки при наличии ошибки всегда будет происходить
// принудительное звершение приложения
// Этот фрагмент кода работает при наличии ошибки в режиме исполнения.
// Генерируем исключение,
// аргументы функций ThrowErrno и ThrowOsError - номер
// ошибки и имя файла(необязательный )

e.GetErrorMessage(w_text, 255);
e.ThrowErrno( e.m_cause, e.m_strFileName );
// или e.ThrowOsError( e.m_lOsError, e.m_strFileName );
// или throw &e;
}
else
{
// Корректное открытие файла
AfxMyMsg(" File is opened ");

char buf[100];
f.Read(buf, 10);
buf [10]= '\0';
cout << buf << endl;
f.Close();
}
} // конец try


catch( CFileException * e )
{
cout << " My file exception handler e \n" ;
cout<< "File could not be opened " <<
"\n m_strFileName = " << e->m_strFileName <<
"\n m_cause = " << e->m_cause <<
"\n m_lOsError = " << e->m_lOsError <<
"\n accessDenied = " << e->accessDenied<<
"\n";

// член-данное e->m_strFileName имеет тип CString, для вывода ее
// следует преобразовать к типу char [] или char *

char sss[256] ;

int i = 0;
while( sss[i] = e->m_strFileName[i++] );
cout << " e->m_strFileName = " << sss << "\n";

// Получаем текст системного сообщения об ошибке
e->GetErrorMessage(w_text, 255);

i = 0;
while(sss[i] = w_text[i++]);
cout << " e->msg = " << sss << "\n";
}
Если запустить приложен е в режиме отладки, в окно отладки будут выведены результаты:
File could not be opened
e.m_strFileName = Rtest1.dat
e.m_cause = 2
e.m_lOsError = 2
e.accessDenied = 5
Rtest1.dat was not found.
CFile exception: fileNotFound, File Rtest1.dat, OS error information = 0.
First-chance exception at 0x7c81eb33 in Except1.exe: Microsoft C++ exception: CFileException at memory location 0x0012fc38..
Если запустить приложен е в режиме исполнения, в окно приложения будут выведены результаты:
My file exception handler e
File could not be opened
m_strFileName = 00359CC0
m_cause = 2
m_lOsError = 0
accessDenied = 5
e->m_strFileName =
·Rtest1.dat
e->msg = Rtest1.dat was not found.
Для продолжения нажмите любую клавишу . . .
4. Требования к защите лабораторной работы
Для защиты необходимо предоставить приложения на языке C++ в среде Visual C++ 6.0 или Visual Studio 2005, реализующие задачи:  1) ЗАДАЧА 1.  Реализовать класс, представляющий пользовательские данные, например массив.  Реализовать иерархию классов, обеспечивающих обработку ошибок, например, ошибок, связанных с выделением памяти для размещения массива и выходом индекса массива за допустимые границы.  Предусмотреть объектно-ориентироованную обработку исключений.  2) ЗАДАЧА 2.  Выполнить копирование существующего файла в новый файл, используя исключения типа CFile exception

Лабораторная работа 2. Разработка синтаксического анализатора текстов (4 час.)
Цели и задачи: Цель данной работы – закрепление навыков как структурного, так и объектно-ориентированного программирования, а также знакомство с простейшим методом синтаксического анализа текстов – методом рекурсивного спуска.
1. Основные понятия и определения
Неформально язык программирования можно как множество предложений из слов или символов некоторого основного словаря.
Алфавитом А называется непустое конечное множество элементов. Элементы алфавита обычно называются символами.
Цепочкой называется всякая конечная последовательность символов из алфавита А.
Например, цепочками в алфавите А = (a, b, c ( являются последовательности a, b, c, ac , ab, ac, bbcc и т.д. Допускается пустая цепочка, обозначаемая (.
Продукцией или правилом подстановки называется упорядоченная пара (U, x ) , которая записывается так:
U ::= x , ( 1 )
где U – символ алфавита А, а x – непустая конечная цепочка символов.
Грамматикой G[Z] называется конечное непустое множество правил, где символ Z должен встретиться в левой части по крайне мере одного правила ( этот символ называется начальным или целевым символом). Символы, которые встречаются в левых частях правил называются нетерминальными (нетерминалами) или синтаксическими единицами языка. Символы, которые встречаются только в правых частях правил, называются терминальными (терминалами).
Грамматика называется контекстно-свободной, если правила имеют вид (1), где х – произвольная цепочка из терминальных и нетерминальных символов.
При записи правила нетерминальные символы заключаются в угловые скобки < >. Например, грамматику, описывающую целое число, можно определить следующим образом:

< число >. ::= < чс >
< чс > ::= < цифра > | < цифра > < чс >
< цифра > ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Вертикальная полоса разделяет однотипные варианты. Приведенный пример содержит три правила подстановки, т.е три нетерминальных символа и десять терминальных – 0, 1, 2,. . . 9.
Такая форма записи называется нормальной формой Бэкуса – Наура.
Таким образом, текст программы на языке программирования – это цепочка терминальных символов соответствующего словаря, выводимая из начального нетерминального символа.
Лексема – простейшая логически неделимая конструкция языка. Лексемы можно разделить на следующие категории:
Идентификаторы – пользовательские имена переменных, функций и т.д.
Служебные или зарезервированные слова.
Константы.
Разделители, к которым можно отнести пробелы, знаки пунктуации, знаки операций и т.д.

Пример грамматики
Синтаксический анализатор – программа, которая выполняет разбор текста, построенного по определенным синтаксическим правилам. Рассмотрим пример грамматики, который далее будет использован для построения синтаксического анализатора
< Инструкция > ::= <Переменная > = < Выражение > |
if < Выражение > then < Инструкция > |
if < Выражение > then < Инструкция > else < Инструкция >

<Переменная > : := <Идентификатор > | <Идентификатор > [< Выражение > ]
< Выражение > : := < Терм > ( + < Терм >(*
< Терм > : := < Множитель > ( * <Множитель >(*
<Множитель > : := <Переменная > | (< Выражение >)
<Идентификатор > : := <Буква > ( <Буква > | <Цифра >(*
<Оператор > : := < Инструкция > ;
Фигурные скобки со знаком ”*” обозначают, что конструкция, указанная внутри скобок, может повторяться произвольное число раз или отсутствовать.
Данная грамматика определяет оператор присваивания и условный оператор. В качестве переменных могут быть использованы простые переменные или переменные с индексами. Выражением может быть только сумма произведений. Идентификатор, определяемый пользователям, представляет последовательность букв и цифр, начинающуюся с буквы. Таким образом, допустимыми с точки зрения данной грамматики являются, например, следующие предложения:
ab = сd + ( a [i] + b[j] + с[к]) ;
if adc then a[ i] = a1 + a2 else b[ i] = a1 + a2 ;
Вид оператора (оператор присваивания или условный ) легко определяется по первой лексеме. Если первая лексема совпадает со служебным словом if, то данный оператор должен быть только условным, в остальных случаях – имеет место оператор присваивания. В рассмотренном ниже примере будет выполнен синтаксический анализ текста, состоящего из операторов данного вида. В конце каждого оператора стоит символ ”;”.
При анализе текста, составленного из данных инструкций, предусмотрено выявление следующих типов ошибок:
Ожидается идентификатор (первый символ не буква. (номер ошибки 1) .
Ожидается символ ; .
Ожидается символ : или символ = .
Ожидается символ + или символ * .
Ожидается символ then’.
Ожидается символ else или символ ; .
Ожидается символ ] .
Неожиданный конец текста


Замечания по разработке программы

3.1. Работа с файлами

Анализируемый текст целесообразно читать из входного файла.
Для непосредственного управления процессами ввода \ вывода библиотека MFC предоставляет классы CFile и производные от него. Класс CFile инкапсулирует все функции, связанные с обработкой файлов любого типа.
Продемонстрируем на примерах основные функции при работе с файлами: открытие, запись данных, чтение

Пример 1. Создание и запись.

CString strHello ((LPCSTR)IDS_STRING2);
//создание текстовой строки
char * ch=" ! \n";
CFile myFile(_T("File.txt"),
CFile::modeCreate | CFile::modeReadWrite);

for ( int i=0; i<10 ; i++)
{ myFile.Write ((LPCTSTR)strHello,
strHello.GetLength());
myFile.Write (ch, lstrlen(ch));
}
myFile.Close();

Пример2. Открытие и чтение.

myFile.Open( _T("File.txt"), CFile::modeReadWrite, &e ) ;
char buf[30];
for ( int i=0; i<10 ; i++)
{ ReadLine( myFile, buf);
cout < }
myFile.Close();

Вспомогательная функция чтения строки ReadLine() .может быть определена следующим образом
void ReadLine(CFile & F, char *buf, char del='\n')
{int j=0;
do F.Read(&buf[j],1);
while (buf[j++]!=del);
buf[j]='\0';
}
Чтение осуществляется до первого символа, указанного в качестве третьего аргумента функции.

3.2. Определение класса Syntax
В некоторых компиляторах синтаксический анализатор содержит по одной рекурсивной процедуре для каждого нетерминала, т.е. каждое синтаксическое правило обрабатывается своей процедурой. Процедуре сообщается, с какого символа текста следует начать разбор предложения. Тип оператора ( оператор присваивания или условный оператор) определяется, как правило, по первой лексеме. Если первая лексема совпадает со служебным словом if , то данный оператор должен быть только условным, иначе имеет место оператор присваивания. Поэтому в первом случае для разбора предложения будет использована правило
if < Выражение > then <Инструкция > |
if < Выражение > then <Инструкция > else <Инструкция > ,
иначе
<Переменная > := < Выражение > .
Функции State(), Expr(), Term(), Factor(), Var() соответственно будут выполнять разбор нетерминалных символов <Инструкция >, < Выражение >, < Терм >, <Множитель >, <Переменная >. Следует обратить внимание, что функции Expr(), Term(), Factor(), Var() в данном примере на выходе будут формировать следующую лексему, которая следует за выражением, термом, произведением или переменной и помещать ее в переменную lex.

Дополнительными функциями класса являются:
Функции Error( )выводит сообщение об обнаруженной ошибке в выходной поток. (При обнаружении ошибки выполнение дальнейшего анализа прекращается.)
Функции Scan() выделяет лексему из текста предложения и помещает ее в строку lex.
Функции ReadStr() читает текст (предложение) из входного файла и помещает его в строку str.
Переменная i -определяет номер текущего символа разбираемой строки текста.
Эти функции и переменные являются членами класса Syntax . Данный класс можно объявить следующим образом:
class Syntax
{ private:
int i;
char str [256],lex[100];
public:
int num_err;
void Scan(){. . .}
int State(){. . .}
void Expr(){. . .}
void Term(){. . .}
void Factor(){. . .}
int Var(){. . .}
void Error(int num, int i) {. . .}
void ReadStr(){. . .}
};

3.3. Оосновные алгоритмы
Обобщенно алгоритмы основных функций можно определить следующим образом:

Алгоритм функции main().
(для консольного приложения )
Создать объекти типа Syntax.
Пока не достигнут конец файла
{ Прочитать очередной оператор (до символа ;’ или конца файла) в
переменную str ;
i = 0;
lex = “’
Scan();
State();
Если lex !=";" , то
{ Error(2, i);
Выход;
}
}

Алгоритм функции State().
Функция State () выполняет обработку правила <Инструкция >. При этом в переменной lex находится первая лексема оператора, которая должна быть либо ключевым словом "if " , либо идентификатором переменной.

Если lex ="if", то
{ Scan(); //Выделение лексемы
Expr(); // Разбор выражения, стоящего после "if"
Если lex !=" then", то
{ Error(5, i):
Выход.
}
Scan();
State(); // Разбор инструкции после " then "
Если lex =" else", то
{
Scan();
State(); // Разбор инструкции после " else "
}
Если lex !=";" , то
{ Error(6, i):
Выход;
}
// Разбор оператора присваивания.
Var(); // Разбор переменной слева от "="
Если lex != ":=" , то
{ Error(3, i):
Выход.
}
Scan();
Expr(); // Разбор выражения справа от ":="

Если lex !=";" , то
{ Error(2, i):
Выход.
}

Алгоритм функции Expr()

Данная функция выполняет обработку правила
< Выражение > : := < Терм > ( + < Терм >(*,
т.е. обрабатывает сумму термов. При входе в функцию Expr() переменная lex содержтит первую лексему выражения.

Term(); // Разбор первого терма а выражении
Пока (lex = "+") // Продолжение разбора выражения
{ Scan(); // выделение очередной лексемы,
Term(); //разбор очередного (слагаемого)
}

Алгоритм функции Term()
Данная функция выполняет обработку произведения , сомножителями которого являются конструкты, описываемые правилом
< Терм > : := < Множитель > ( * <Множитель >(*

{ Factor(); // Разбор первого сомножителя
Пока ( lex, = " * ") // Продолжени разбора
{ Scan(); // выделение очередной лексемы,
Factor(); // разбор очередного сомножителя
}


Алгоритм функции Var()
Данная функция выполняет разбор переменной, идентификатор которой помещен в строку lex . Переменная может быть простой или с индексом. Первый символ переменной всегда должен являться буквой.

Если ( lex[0] не буква.),
{ Error(1, i);
Выход;
}
Scan();
если (lex, = "[" ) то // Выделенная лексема  - "["
{ Scan();
Expr(); // Разбор индексного выражения
если (lex, ! ="]")
{ Error (7,i );
Выход ;
}
Scan(); // Выделение лексемы после символа "]"
}


5.Требования к защите лабораторной работы
Для защиты необходимо предоставить программу на языке C++ в среде Visual Studio.Net , реализующую синтаксический анализатор текстов
Срок выполнения и сдачи данной работы - весенний семестр.

6 . Примеры заданий
Функции языка Visual Basic Тело функции - операторы присваивания.
Процедуры языка Visual Basic Тело процедуры - операторы присваивания.
Функции языка С++ Тело функции - операторы присваивания.
Операторы while и dowhile языка С++. Тело цикла - операторы присваивания.
Операторы FOR и FOREACH языка Visual Basic Тело цикла - операторы присваивания.
Операторы LOOP UNTIL и LOOP WHILE языка Visual Basic Тело цикла - операторы присваивания.
Оператор for языка С++. Тело цикла - операторы присваивания.
Объявление структуры в языке С++. Определение функций внутри объявления структуры опустить.
Объявление класса в языке С++. Определение функций внутри объявления класса опустить.
Простейшая линейная программ на языке С++. ( include, объявления переменных, функция main без аргументов, операторы присваивания)
Простейшая линейная программ на языке Visual Basic. (объявления переменных, процедура, операторы присваивания)
Выражения в С++ ( учесть арифметические, логические операции и операции сдвига в выражениях)


Лабораторная работа 3. Диалоговые приложения. Обработка сообщений (4 час.)
Цели работы :
– изучение структуры диалогового приложения, основных элементов управления, используемых в диалоговых окнах, генерации и обработки событий в диалоговых приложениях С++, разрабатываемых в среде Visual Studio с использованием библиотеки MFC .
В ходе выполнения работы требуется разработать диалоговое приложение и предусмотреть в нем:
элементы управления;
обработку событий, генерируемых элементами управления, в том числе и нотификационные сообщения;
работу с элементами управления оформить в виде справочной системы приложения.
1. Создание диалогового приложения в Visual Studio
Первое, что необходимо определить, приступая к созданию приложения с помощью мастера создания приложений AppWizard, сколько окон будет поддерживать данное приложение: будет ли оно простым диалоговым прилоджением, однооконным SDI-приложением или многооконным MDI-приложением. SDI-приложение позволяет в каждый момент времени открывать только один документ. MDI-приложение позволяет в каждый момент времени открывать произвольное количество документов. Простое диалоговое приложение вообще не открывает никаких документов и не имеет меню, кроме системного. Для создания простого диалогового приложения необходимо выполнит следующие шаги:
В диалоговом окне NewProject выбрать тип Visual C++ / MFC, шаблон проекта MFC Application, папку и имя проекта, например, Dial2008.
В диалоговом окне Welcome to MFC Application Wizard – выбрать Application Type.
В диалоговом окне Application Type выбрать Dialog Based. Нажать кнопку Next для перехода к следующему шагу.
В диалоговом окне User Interface Feathures можно отменить или оставить по желанию предлагаемые опции. Нажать кнопку Next для перехода к следующему шагу.
В диалоговом окне Advanced Feathures можно отменить предлагаемые опции. Для поддержки справки в приложении включить опцию Context-sensitivy Help. Нажать кнопку Next для перехода к следующему шагу.
В диалоговом окне Generated Classes можно изменить предлагаемые по умолчанию имена классов. Нажать кнопку Finish для завершения генерации приложения.
Контекст устройства

Вывод информации на экран в системе Windows существенно отличается от вывода, используемого в DOS. Windows – это операционная система, работающая в графическом режиме. Windows использует элемент, называемый контекстом устройства13 XE "контекстом устройства" \i 15 ( DC – device context ). В действительности DC13 XE "DC" \i 15 - это структура, соединяющая приложение с драйвером устройства и содержащая информацию о параметрах, необходимую для формирования вывода, а именно:
цвет фона
режим фона
битовая карта
кисть
начальное положение кисти
область вывода
цветовая палитра
режим рисования
режим масштабирования
перо
текущее положение пера
шрифт
межсимвольный промежуток
режим переноса
выравнивание текста
цвет текста
координаты области просмотра текста
и др.
Перед тем, как приложение начнет вывод информации в окно, оно должно получить или создать контекст устройства.
СDC – корневой класс для создания контекстов устройств.
Класс СDC содержит большое число функций, используемых для вывода различных графических примитивов:
прямоугольников,
дуг,
эллипсов,
отрезков прямых,
кривых,
закрашивание объектов,
вывод текста,
выбор шрифта и размера текста ,
вывод битовой матрицы
и т.д.
Для вывода текста или графических примитивoв обычно пользуются не классом СDC, а производным от него классом СPaintDC. Создать контекст устройства для окна, в которое будет осуществляться вывод, можно, используя конструктор класса СPaintDC, например:.

CPaintDC dc(this); //Объявление и инициализация контекста
//устройства.this – указатель текущего окна.
CRect rect; //Объявление прямоугольника
GetClientRect (&rect); //Инициализация. Прямоугольник
//rect занимает клиентскую
// часть окна
int xc = rect.Width()/ 2; //Определение координат точки, int yc = rect.Height()*4/7; //в которую помещено начало
//логической системы координат.
double pi = 3.1415926535;
double sina = sin(5*pi/12);
double cosa = cos(5*pi/12);
double dd = 200*sina; int ddx = int(dd);
dd = 200*cosa; int ddy = int(dd);

CPen pen1(PS_SOLID, 1, RGB(190,190,190));
//Выбор цвета линий - светло-серый
dc.SelectObject(&pen1);
//Установка цвета в контексте устройства
dc.MoveTo(xc,yc); dc.LineTo(xc,yc-150); //Вывод осей координат
dc.MoveTo(xc,yc); dc.LineTo(xc-ddx,yc+ddy);
dc.MoveTo(xc,yc); dc.LineTo(xc+ddx,yc+ddy);
dc.TextOut (100,20,
"Непосредственный вывод в контекст устройства осей координат"
);













Рис.1.1.
Данный фрагмент обычно находится в теле функции OnPaint()

2.1. Преобразование координат

Построение поверхности или пространственной кривой (вычисление точек) обычно осуществляется в логической системе координат. Для отображения полученной точки необходимо перевести реальные координаты в логические координаты, логические координаты - в экранные координаты.
Вначале рассмотрим простой двумерный случай.
Требуется построить график функции f(x) на отрезке [0, r] (не нарушая общности рассуждений, можно рассматривать построение на произвольном отрезка [a, b] ). Пусть отрезок, реальная длина которого есть r , имеет экранную длину L пикселей. Экранное начало координат – левый верхний угол экрана. Примем следующие допущения;
- начало логической системы координат помещено в точку О, имеющую экранные координаты Xo, Yo;
- единицы измерения расстояний в логической системе координат пропорциональны единицам экранной системы. ( В рассматриваемом случае единицы измерения совпадают);
- отрезок [0, r] реальной системы координат преобразуется в отрезок [0, L] логической системы координат:
- расстояние по оси ординат увеличиваеься в K раз.

Тогда точка Р c реальным координатами XR, YR имеет логические координаты
XL = L / r *XR
YL = K * YR .

Точка c логическими координатами XL, YL имеет экранные координаты
XS = X0 + XL , YS = Y0 - YL



Таким образом, экранные координаты точки следующим образом выражаются через реальные координаты точки:
XS = X0 + L / r *XR
YS = Y0 - K * YR
Gthtqnb jn rhfyys[ rjjhlbyfn
Перейти от экранных координат к реальным можно по формцлам:
XR = (XS - X0)* r / L
YR = - (YS - Y0)/K

Рассмотрим трехмерный случай.
Как правило, экранная ось абсцисс направлена по горизонтали вправо, экранная ось ординат - по вертикали вниз. Предположим, что логические и экранные координаты измеряются в одних и тех же единицах. Центр логической системы координат помещен в точку, имеющую экранные координаты хс, ус. Оси х, у логической системы координат на экране образуют угол
· с вертикальной прямой (см рис. 1.2.)
В этом случае экранные координаты xs, ys определяются следующим образом по значениям логических координат x, y, z:

xs = xc + int (sin(a) * (y - x ));
ys = yc + int (cos(a) * (y + x) - z );
где sina, cosa - синус и косинус угла
· соответственно.

Рис. 1.2.

Любое движение можно разбить на вращательное и поступательное.
При поступательном движении точки (x, y, z) в логической системе координат ее экранные координаты определяются как
x1 = x + dx ;
y1 = y + dy ;
z1 = z + dz ;
xs = xc + int (sina* (y1 - x1)) ;
ys = yc + int (cosa* (y1 + x1)-z1 ) ;
где dx, dу , dz - смещения вдоль координатных осей.

Если тело совершает вращательное движение (поворот на угол d ) относительно координатных осей z , x или y в логической системе, то экранные координаты точки определяются по следующим формулам:

x2 = x1*cos(d) - y1*sin(d); // поворот вокруг оси z
y2 = x1*sin(d) + y1*cos(d);
z2 = z1;

x2 = x1; // поворот вокруг оси х y2 = y1*cos(d) - z1*sin(d);
z2 = y1*sin(d) + z1*cos(d);

x2 = z1*sin(d)+x1*cos(d); // поворот вокруг оси у
y2 = y2;
z2 = z1*cos(d)-x1*sin(d);

3. Требования к защите лабораторной работы
Для защиты необходимо предоставить программу на языке C++ в среде Visual C++ 6.0, демонстрирующую обработку событий, в том числе и нотификационных, вывод графической информации с использованием контекста устройства и памяти.
Срок выполнения и сдачи данной работы - весенний семестр.

Лабораторная работа 4. Простая многозадачность на уровне приложения. Потоки (3 час.)
Цели и задачи: изучение и использование таких средств Visual C++, как потоки, средства синхронизации, критические секции, защелки
Поток - ветвь выполняющейся программы. Любое приложение имеет всегда имеет один поток, который является главным или первичным. Главный поток выполняется до тех пор, пока приложение активно. В многозадачном приложении можно создавать произвольное количество дополнительных потоков, запускать, приостанавливать и уничтожать. При закрытии приложения все потоки уничтожаются. С точки зрения операционной системы приложение - это отдельный процесс. Фактически процесс состоит из двух компонент:
объекта ядра (дескриптора процесса), через который операционная система управляет процессом;
адресного пространства прцесса..
Поток - часть процесса, которая выполняется в адресном пространстве процесса, использует ресурсы процесса, работает независимо от других потоков процесса , но имеет собственные
объект ядра (дескриптора потока),
стек, который содержит параметры функций, локальные переменные для работы потока.
Процессы инертны, они ничего не исполняют, но служат "контейнером " для потоков..
Для создания потока в приложении, использующем библиотеку MFC, достаточно создать функцию, которая должна выполняться параллельно с главным потоком. Когда функция завершит работу поток будет уничтожен. Вызов данной функции (запуск потока) выполняется с помощью вызова функции , прототип которой может иметь вид:

CWinThread* AfxBeginThread (
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

Функция возвращает указатель на объект потока.
pfnThreadProc - имя выполняемой в потоке процедуры.
pParam - 32-разрядный паметр, который можно передавать потоку. (Часто это дескриптор окна).
nPriority - приоритет потока. Значение приоритета определяется следующими константами:
Константа Значение
THREAD_PRIORITY_NORMAL 0
THREAD_PRIORITY_BELOW_NORMAL 1
THREAD_PRIORITY_ ABOVE _NORMAL -1
THREAD_PRIORITY_HIGHEST 2
THREAD_PRIORITY_ LOWEST -2
THREAD_PRIORITY_ IDLE -15 Устанавливает базовый приоритет равным 1. Для
процесса REALTUM_PRIORITY_CLASS
устанавливает приоритет равным 16.
THREAD_PRIORITY_TIME_CRITICAL Устанавливает базовый приоритет равным 15. Для
процесса REALTUM_PRIORITY_CLASS
устанавливает приоритет равным 30.
nStackSize - специфицирует размер стека в байтах для нового потока. Если значение 0, то размер стека такой же, как у создающего потока.
dwCreateFlags - специфицирует дополнительные флаги для создания потока. Параметр может принимать значения:
CREATE_SUSPENDED задерживает выполнение созданного потока до вызова функции ResumeThread.
0 - созданный поток выполняется немедленно.
lpSecurityAttrs - указатель на структуру, содержащую параметры безопасности. Если 0 , то параметры те же, что и у создающего потока.
Взаимодействие потоков

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

Использование глобальных переменных

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

Шаг 1. В начало файла MyThreadView.cpp добавить
volatile char Global_Var [80];.
Шаг 2. С помощью редактора ресурсов добавить в меню команду "Остановка потока" с идентфткатором ID_STOPTHEAD.
Шаг 3. С помощью ClassWizard связать команду " Остановка потока" с функцией OnStopthread().
Данную функцию можно определить следующим образом:

void CMyThreadView::OnStopthread()
{ char * sss ="Поток завершен.";
int i=0;
while (sss[i]) Global_Var[i]=sss[i++];
Global_Var[i]='\0';
}
Шаг 4. Аналогично функцию :OnStartthread() можно определить следующим образом:
void CMyThreadView::OnStartthread()
{ HWND hwnd = GetSafeHwnd();
AfxBeginThread (ThreadProc, hwnd, 0);
char * sss ="Поток стартовал ";
int i=0;
while (sss[i]) Global_Var[i]=sss[i++];
Global_Var[i]='\0';
}

Шаг 5. Функцию потока мохно определить так:

UINT ThreadProc (LPVOID param)
{ char sss [80];
int i=0;
while (Global_Var[i]) sss[i]=Global_Var[i++];
sss[i]='\0';
::MessageBox((HWND)param, sss, "Значение глобальной переменной"
, MB_OK);

while (!strcmp(sss,"Поток стартовал "))
{ i=0;
while (Global_Var[i]) sss[i]=Global_Var[i++];
sss[i]='\0';
} //бесконечный цикл потока
//Цикл выполняется, пока в Global_Var содержится
//строка "Поток стартовал ".

i=0;
while (Global_Var[i]) sss[i]=Global_Var[i++];
sss[i]='\0';
::MessageBox((HWND)param, sss, "Значение глобальной переменной"
, MB_OK);
return 0;
}

Запустить поток можно командой "Запуск потока", завершить - командой "Остановка потока".


Использование сообщениий

Роостым, но удобным способом передачи информации от вторичного потока основному являкется механизм сообщений. Предопределенная константа WM_USER задает первый из доступный номер, которые могут быть присвоены пользовательским сообщениям. Для передачи сообщения необходимо вызвать функцию API
BOOL PostMessage ( HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam );
hwnd - дескриптор окна, которому адресовано сообщениею
msg - номер сообщенипя ( должен быть больше , WM_USER).
wparam, lparam - параметры сообщения.
Данная функция обычно вызывается в теле функции, задающей поток.
Таким образом для передачи сообщения от вторичного потока основному и выполлнения обработки этого сообщения следует выполнить действия:

1. В заголовочный файд MyThreadView. h добавить константу, например
const WM_USERMSG = WM_USER +100;
2. В карту сообщений заголовочного файла (объявление класса СMyThreadView) добавить
afx_msg LONG OnThreadMessage (WPARAM wparam,
LPARAM lparam );

3. В карту сообщений файла MyThreadView.срр добавить
ON_MESSAGE (WM_USERMSG, OnThreadMessage )

4. В функцию потока вставить вызов :: PostMessage ().
UINT ThreadProc ( LPVOID param)
{ . . . .
:: PostMessage ((HWND)param, WM_USERMSG, wparam, lparam) ;
. . . .
}
5. В файл MyThreadView.срр добавить определение функции

LONG СMyThreadView::OnThreadMessage (WPARAM wparam,
LPARAM lparam )
{ . . .
}



Взаимодействие потоков с помощью событий

Для представления объекта события можно использовать класс MFC CEvent. Объект может находиться в одном из двух состояний - сигнализирует или молчит.
Конструктор класса CEvent имет достаточно много параметров, но часто все параметры задаются по умолчанию. Создать и перевести объект в состояние "сигнализирует " можно следующим образом:
CEvent MyEventObject; //Объявление переменной типа CEvent
MyEventObject. SetEvent();
Состояние объекта определяется с помощью функции API DWORD WaitForSingleObject(
HANDLE , // дескриптор объекта
DWORD // интервал ожидания события в млсек.Если 0, то
//событие уже наступило
);
Константа INFINITE задает бесконечный интервал ожидания.
Возвращаемые функцией значения
WAIT_ABANDONED
Поток, использующий объект завершился, оставив данный о данный объект в состоянии "сигнализирует" Объект переведен в состояние "молчит"

WAIT_OBJECT_0
Объект перешел в состояние "сигнализирует"

WAIT_TIMEOUT
Объект не перешел в состояние "сигнализирует" в течении заданного времени.

В случае неудачного завершения функции возвращается значение WAIT_FAILED.

Модифицировать пример для демонстрации механизма событий можно следующим образом:

1. В файл MyThreadView.срр включить заголовочный файл afxmt.h.
#include "MyThreadView.h"
#include // сразу после MyThreadView.h
2. В файл MyThreadView.срр добавить объявления
CEvent StopEvent;
CEvent StartEvent;
3.В меню добавить новые команды
Событие "Запуск потока"
Событие "Остановка потока"
Данные команды имеют идентификаторы ID_EVENTSTART и ID_EVENTSTOP и обрабатываются функциями класса CMyThreadView:: nEventstart, и CMyThreadView:: OnEventstop.

4. Эти функции, а также функцию потока можно завдать следующим образом:

UINT ThreadProcEvent (LPVOID param)
{::WaitForSingleObject(StartEvent.m_hObject, INFINITE);
::MessageBox((HWND)param, "Поток стартовал", "Использование объекта событий"
, MB_OK);
while (1)
{ int res =::WaitForSingleObject(StopEvent.m_hObject, 0);
if (res ==WAIT_OBJECT_0) break;

}
::MessageBox((HWND)param, "Поток завершен", "Использование объекта событий"
, MB_OK);
return 0;
}

void CMyThreadView::OnEventstart()
{ // TODO: Add your command handler code here
HWND hwnd = GetSafeHwnd();
AfxBeginThread (ThreadProcEvent, hwnd, 0);
StartEvent.SetEvent();
}

Синхронизация потоков

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

Критические секции

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

class CMyClass
{ private :
. . . // общие данные , используемые разными потоками

CCriticalSection CritSect; // объект критическая секция
public:
CMyClass ( ); // конструктор
~CMyClass ( ); // деструктор
void Function1 (. . . );
void Function2 (. . . );
}

Открытые методы Function1( ) и Function2 ( ) обычно представляют операции чтения и записи защищаемых данных. Для монопольного доступа к защищаемым данным эти методы вызывают функции Lock ( ) UnLock ( ) класса CCriticalSection . Реализация этих функций имеет примерно такой вид:

void CMyClass:: Function1 (. . . )
{ CritSect. Lock ( ) ;
. . . // обработка данных
CritSect. UnLock ( ) ;
}
Суть данного механизма сводится к следующему. Если во время обработки данных одним потоком, происходит вызов функции Lock ( ) из другого потока, то обработка данных вторым потоком будет задержана до того момента, пока первый поток не вызовет функцию UnLock ( ) , т.е. не освободит критическую секцию.

Пример использования критической секции

В качестве разделяемого ресурса будем использовать текстовую строку из 10 символов. Поток - поставщик будет формировать строку, поток - потребитель
· читать. Класс, содержащий ресурс, находится в файле MyClass.h и имеет вид:

#include "afxmt.h"

class CMyClass
{ private:
int Num;
char str[10];
public:
CCriticalSection CritSect;
CMyClass() {}
~CMyClass() {}
void SetStr(char *);
void GetStr(char *);
};

Определение методов void SetStr(char *) и GetStr(char *) назодится в файле MyClass.срр и имет вид

void CMyClass::SetStr( char * sss)
{ CritSect.Lock ( ) ;
for (int i=0; i<10; i++)
str[i]=sss[i];
CritSect.Unlock ( ) ;
}

void CMyClass::GetStr(char * sss)
{ CritSect.Lock ( ) ;
for (int i=0; i<10; i++)
sss[i]=str[i];
CritSect.Unlock ( ) ;
}
В меню добавлена команда "Запуск поставщика и потребителя". Объявление переменной данного класса, соответствующие функции новых потоков и функция запуска потоков помещаются в файл СmyThreadView.cpp и определены следующим образом:

CMyClass CCC;

UINT ThreadProcSup1 (LPVOID param) // Процесс- поставщик
{ char sss[10]; // формирует в цикле
sss[9]='\n'; // 10 строк
for (int i=0; i<10; i++)
{ for (int j=0; j<9; j++) sss[j]='A'+i;
{ CCC.SetStr (sss);
::Sleep(10); // Задержка потока на 10 млсек
}
}
return 0;
}

UINT ThreadProcCus1 (LPVOID param) // Процесс- потредитель
{ char sss[100]; // 10 раз читает ресурс,
int code; // накапливая результаты
for (int i=0; i<10; i++) // в строке sss.
{ CCC.GetStr (sss+10*i);
::Sleep(10);
}
sss[99]='\0';
::MessageBox((HWND)param, sss, "ThreadProcCus" , MB_OK);
// Вывод всех прочитанных значений
return 0;
}

Использование защелок

Механизм защелок (класс MFC CMutex) похож на критические секции, но обеспечивает разделение доступа к ресурсу не только для потоков одного приложения, но и для потоков разных приложений.


Пример 1

CWinThread* m_ptr_thread; // in class declaration, CAaaView.h
volatile int threadController; // in class definition, CAaaView.cpp
UINT ThreadProc(LPVOID param)
{ :: MessageBox((HWND)param, "MyThread activate","Thread", MB_OK);
while (threadController)
{ //The1 type thread code . . . . .
}; //The end of thread
::MessageBox((HWND)param, "MyThread disactivate","Thread", MB_OK);
return 0;}
void CAaaView::OnStartthread() {// To run the thead
threadController =1;
HWND hwnd =GetSafeHwnd();
m_ptr_thread = AfxBeginThread(ThreadProc, hwnd, THREAD_PRIORITY_NORMAL); //0
//_BELOW_NORMAL); //1 //_ABOVE_NORMAL); //-1
//_HIGHEST); //2 //_LOWEST); //-2 //_IDLE); //-15
}
void CAaaView::OnLButtonDown(UINT nFlags, CPoint point)
{// To run timer
m_timer = SetTimer(1, 500, 0);
::MessageBox(0, "Timer run","Thread", MB_OK);
}
void CAaaView::OnTimer(UINT nIDEvent)
{threadController++; //The thread uses common memopy
}
void CAaaView::OnStopthread()
{char sss[50]; //To stop the tread
sprintf(sss,"Priority =%d Controller =%d\n The tread disactivate",
m_ptr_thread->GetThreadPriority() , threadController );
::MessageBox(0, sss,"Thread", MB_OK);
threadController =0;
KillTimer (m_timer);
}

Пример 2. Сообщения от вторичного потока основномуvolatile int threadController;
volatile char *buffer =0;

UINT ThreadProc2(LPVOID param)
{ //Supplier Code
::MessageBox((HWND)param, " ", "Thread", MB_OK);
int i=0;
threadController=1;
while (threadController) { i++ ; char sss[20];
sprintf (sss, " %d" , i ) ;
while ( buffer ) {}; buffer=sss;
};
return 0;
}
void CAaaView::OnThreadtype2Startsupplier()
{//
HWND hwnd =GetSafeHwnd();
AfxBeginThread(ThreadProc2, hwnd, THREAD_PRIORITY_NORMAL);
}
UINT ThreadProc3(LPVOID param)
{ // Customer Code
::MessageBox((HWND)param, " ", "Thread", MB_OK);
while (!threadController){};
while(threadController) { while ( !buffer ) {};
if ( threadController )
::SendMessage((HWND)param,WM_FROMCUSTOMER,0,0);
}
return 0;
}
void CAaaView::OnThreadtype2Startcustomer()
{// To Run Customer
HWND hwnd =GetSafeHwnd ( );
AfxBeginThread(ThreadProc3, hwnd, THREAD_PRIORITY_NORMAL); //0
}
LONG CAaaView::OnFromcustomer(WPARAM wparam, LPARAM lwparam)
{ Invalidate ( ) ; return 0;
}
void CAaaView::OnDraw(CDC* pDC)
{ CAaaDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
if (buffer) { char sss[20]; sprintf(sss, "%s", buffer);
pDC->TextOut(20,20, sss );
}
buffer=0;
}
void CAaaView::OnThreadtype2Stopsupplier()
{ threadController=0;
}

Пример 3
CEvent StartEvent;
CEvent StopEvent;

UINT ThreadProc4(LPVOID param)
{ ::MessageBox((HWND)param, "MyThread activate","Thread", MB_OK);
::WaitForSingleObject(StartEvent.m_hObject, INFINITE);
while (1)
{ threadController++;
if (threadController % 50'000==0)
::SendMessage((HWND)param,WM_FROMTHREAD,0,0);
int iii=::WaitForSingleObject(StopEvent.m_hObject, 0);
if (iii==WAIT_OBJECT_0) break;
};
::MessageBox((HWND)param, "MyThread disactivate","Thread", MB_OK);
return 0;}
void CAaaView::OnStartevent()
{StartEvent.SetEvent();
}
void CAaaView::OnStopevent()
{StopEvent.SetEvent();
}
LONG CAaaView::OnFromthread(WPARAM wparam, LPARAM lwparam)
{ Invalidate();
return 0;
}
int CAaaView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{ if (CView::OnCreate(lpCreateStruct) == -1) return -1;
HWND hwnd =GetSafeHwnd();
AfxBeginThread(ThreadProc4, hwnd, THREAD_PRIORITY_IDLE); //-15
return 0;
}

Требования к защите лабораторной работы
Для защиты необходимо предоставить программу на языке C++ в среде Visual C++ 6.0, реализующую многодокументный интерфейс, модальные и немодальные диалоговые окна, передачу текстовой, графической информации и метафайлов через буфер обмена.
Срок выполнения и сдачи данной работы - осенний семестр.

Лабораторная работа 5. тестирование программы (2 час.)
Цели и задачи:  тестирование программ методами “белого ящика”
 написание программы для решения поставленной задачи с её последующим тестированием.
Задание: идентифицировать треугольник по трем сторонам (остроугольный, прямоугольный, тупоугольный, равносторонний, равнобедренный).
Листинг:
#include
#include
void main ()
{
int a,b,c;
cout<<"Enter the size of 'a' side: ";
cin>>a;
cout<<"Enter the size of 'b' side: ";
cin>>b;
cout<<"Enter the size of 'c' side: ";
cin>>c;
if (((a<=0)||(b<=0)||(c<=0))||((a>=b+c)||(b>=a+c)||(c>=a+b)))
{
cout<<"ERROR: Triangle is incorrect\n";
exit (1);
}
if ((a==b)&(a==c)&(b==c))
cout<<"Triangle is 'ravnostoronniy'\n";
else if ((a==b)||(b==c)||(a==c))
cout<<"Triangle is 'ravnobedrenniy'\n";
if ((a*a==(b*b+c*c))||(b*b==(a*a+c*c))||(c*c==(a*a+b*b)))
{
cout<<"Triangle is 'pryamougolniy'\n";
}
if ((a*a<(b*b+c*c))&(b*b<(a*a+c*c))&(c*c<(b*b+a*a)))
{
cout<<"Triangle is 'ostrougolniy'\n";
}
if ((a*a>(b*b+c*c))||(b*b>(a*a+c*c))||(c*c>(b*b+a*a)))
{
cout<<"Triangle is 'tupougolniy'\n";
}}





МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук



МАТЕРИАЛЫ ДЛЯ ОРГАНИЗАЦИИ САМОСТОЯТЕЛЬНОЙ РАБОТЫ СТУДЕНТОВ
по дисциплине «Технология программирования»
090104.65 «Комплексная защита объектов информатизации»
















г. Владивосток 2012
Темы рефератов:
1. Языки высокого уровня.
2. Современные технологии программирования.
3. Интегрированная среда программирования Турбо Паскаль.
4. Алгоритмические языки программирования.
5. Программное средство.
6. Технология разработки программ и их реализация.
7. Методологии программирования.
8. Анализ и оценка сложности алгоритмов.
9. Алгоритмы сортировки. Сортировка массивов.
10. Алгоритмы сортировки. Сортировка файлов.
11. Алгоритмы поиска.
12. Алгоритмы обработки строк.
13. Жизненный цикл программы.
14. Базовая структура программирования.
15. Инструментальные среды программирования.
Процесс подготовки рефератов охватывает несколько этапов.

Выбор темы.
Подбор и предварительный анализ литературы.
Определение цели, задач и составление плана работы.
Изучение литературы.
Сбор, анализ данных.
Подготовка текста работы.
Оформление работы.
Защита работы.




МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук



КОНТРОЛЬНО ИЗМЕРИТЕЛЬНЫЕ МАТЕРИАЛЫ
по дисциплине «Технология программирования»
090104.65 «Комплексная защита объектов информатизации»


















г. Владивосток 2012
1. Описание класса Object Pascal можно поместить в
головной программе
раздел интерфейса модуля
раздел реализации модуля
подпрограмме
Описание класса Object Pascal как типа данных содержит
описание полей
описание свойств
заголовки методов
описание методов
Поле класса Object Pascal быть
произвольного типа
только простого типа
только структурного типа
только ссылочного типа
Текущее значение переменной классового типа Object Pascal -
указатель на объект класса
объект класса
адрес объекта класса
указатель на RTTI класса
К системным действиям конструктора Object Pascal не относится
инициализация полей значениями пользователя
выделение памяти под объект
инициализация полей нулевыми значениями
связывание объекта с RTTI класса
Собственный деструктор необходимо описать в классе Object Pascal для освобождения памяти занимаемой полем
ссылочного типа
типа AnsiString
типа динамический массив
структурного типа
Свойства property Object Pascal можно использовать для
прямого доступа к полю объекта
доступ к полю объекта посредством методов
вызова методов
освобождения памяти занимаемой объектом
Метод, используемый свойством для чтения в Object Pascal, -
функция без параметров с возвращаемым результатом того же типа что и у свойства
процедура с одним параметром-значением того же типа что и у свойства
произвольной функцией
произвольной процедурой
Метод, используемый свойством для записи в Object Pascal, -
функция без параметров с возвращаемым результатом того же типа что и у свойства
процедура с одним параметром-значением того же типа что и у свойства
произвольной функцией
произвольной процедурой
Каждый объект класса Object Pascal имеет
свой набор полей
свой конструктор
свой деструктор
свой набор методов
свой набор свойств
При описании метода Object Pascal в область видимости попадают
поля
свойства
методы класса
формальные параметры других методов
Дано следующее описание:
T = class

constructor Create(p: real);
end;
var a: T;
объект класса T можно создать вызовом оператора
a:= T.Create(5);
new(a);
a.Create(10);
a:= T.Create;
Размер объекта a класса T можно определить вызовом
a.InstanceSize;
T.InstanceSize
sizeof(T)
sizeof(a)
Размер памяти выделяемой под объект Object Pascal зависит от
количества и типов полей класса и классов предков
количества методов описанных в классе
количества и типа свойств, описанных в классе
количества и типа свойств класса и классов предков
При наследовании дочерний (производный) класс наследует
поля свойства и методы всех родительских классов
поля свойства и методы класса, являющегося прямым предком
свойства и методы всех родительских классов
методы всех родительских классов
Для переопределения статического Object Pascal метода предка в дочернем классе необходимо
описать статический метод под тем же именем
описать виртуальный метод под тем же именем
описать виртуальный метод под тем же именем и с тем же набором формальных параметров
описать статический метод под тем же именем и с тем же набором формальных параметров
Совместимость по присваиванию для классов Object Pascal полагает, что
переменная типа класс может указывать на объект любого дочернего класса
переменная типа класс может указывать на объект любого класса предка
переменная типа класс может указывать на объект прямого предка
переменная типа класс может указывать на объект любого класса
Одноимённый метод предка можно вызвать, используя директиву Object Pascal
inherited
class
automated
protected
Предком по умолчанию для всех классов Object Pascal является класс
TObject
Exception
Tlist
TObjectList
Абстрактный метод объявляется в описании класса с помощью директивы Object Pascal
abstract
class
message
override
Виртуальный метод объявляется в описании класса Object Pascal с помощью директив
virtual
override
dynamic
class
Перекрыть виртуальный метод Object Pascal в описании производного класса можно с помощью директивы
override
virtual
dynamic
class
Динамический метод объявляется в описании класса Object Pascal с помощью директив
override
dynamic
virtual
class
Классовый метод объявляется в описании класса Object Pascal с помощью директивы
class
virtual
override
dynamic
Полное определение классового метода Object Pascal должно содержать в начале заголовка директиву
class
virtual
override
dynamic
Через идентификатор класса в Object Pascal можно вызвать
классовый метод
конструктор
статический метод
виртуальный метод
Указатель на класс в Object Pascal передаётся по умолчанию при вызове
классового метода
конструктора
статический метод
виртуальный метод
В коде . метода Object Pascal нельзя ссылаться на поля и свойства объекта
классового метода
динамического
статический метод
виртуальный метод
Абстрактным в Object Pascal может быть
виртуальный метод
динамический метод
статический метод
классовый метод
Абстрактным в Object Pascal называется метод, который
не имеет кода
вызывается объектом класса
вызывается идентификатором класса
можно вызвать до создания объекта класса
Раннее связывание состоит в следующем:
связывание объекта с кодом статического метода на этапе компиляции приложения
связывание объекта с кодом метода на этапе компиляции приложения
связывание объекта с кодом виртуального метода на этапе компиляции приложения
связывание объекта с кодом виртуального метода на этапе выполнения приложения
Раннее связывание в Object Pascal используется для .. методов
статических
динамических
виртуальных
абстрактных
Позднее связывание в Object Pascal используется для .. методов
динамических
виртуальных
абстрактных
статических
Позднее связывание это
связывание объекта с кодом статического метода на этапе компиляции приложения
связывание объекта с кодом метода на этапе компиляции приложения
связывание объекта с кодом виртуального метода на этапе компиляции приложения
связывание объекта с кодом виртуального метода на этапе выполнения приложения
Позднее связывание в Object Pascal обеспечивается
наличием связи между объектом и структурой RTTI класса
наличием в RTTI класса связи с таблицей виртуальных методов
наследованием методов предков
общим предком классом TObject
Значением типа указатель на метод в Object Pascal является
указатель на метод любого класса с определённым в типе набором формальных параметров и возвращаемым значением
указатель на метод заданного класса с определённым в типе набором формальных параметров
указатель на метод любого класса
указатель на метод заданного класса
Делегирование в Object Pascal состоит в том, что
объект одного класса передаёт объекту другого класса свой метод
один класс передаёт объекту другого класса свой метод
один класс передаёт другому классу свой метод навсегда
один класс передаёт другому классу свой метод на время
Операция «o is T» в Object Pascal возвращает значение типа
boolean
string
integer
byte
Операция «o is T» в Object Pascal отвечает на вопрос
o является объектом класса T или класса производного от него
o является объектом класса T
o является объектом класса производного от T
o является объектом класса
Операция «o as T» в Object Pascal возвращает значение типа
T
boolean
TObject
byte
Операция «o as T» в Object Pascal позволяет
рассматривать объект «o» как объект класса «T»
рассматривать объект «o» как объект класса «TObject»
рассматривать объект «o» как объект класса потомка
рассматривать объект «o» как объект класса предка
Операция «o as T» в Object Pascal выполнима
если операция «o is T» возвращает значение true
если объект, на который указывает «o», является объектом класса T или производного от T типа
всегда
если объект, на который указывает «o», является объектом производного от T типа
При отсутствии директив (по умолчанию) поля методы и свойства класса Object Pascal имею уровень доступа
public
private
protected
published
Поля методы свойства раздела класса T, следующего за директивой public Object Pascal
видимы отовсюду
видимы только в модуле, в котором описан класс T
видимы только в модуле, в котором описан класс потомок T
видимы только в модуле, в котором описан класс предок T
Поля методы свойства раздел класса T, следующего за директивой private Object Pascal
видимы только в модуле, в котором описан класс T
видимы только в модуле, в котором описан класс потомок T
видимы отовсюду
видимы только в модуле, в котором описан класс предок T
Поля методы свойства раздел класса T, следующего за директивой protected Object Pascal
видимы только в модуле, в котором описан класс потомок T
видимы только в модуле, в котором описан класс T
видимы отовсюду
видимы только в модуле, в котором описан класс предок T
Cвойства визуальных компонентов раздела класса T, следующего за директивой published Object Pascal
видимы отовсюду
можно настраивать в режиме визуального проектирования
видимы только в модуле, в котором описан класс потомок T
видимы только в модуле, в котором описан класс T
Класс TList реализует список
указателей типа pointer
строк ShortString
объектов Tobject
строк Ansistring
Добавить элемент в конец списка TList можно операцией
Add
Push
Pop
Insert
Вставить элемент в список TList на заданную позицию можно операцией
Insert
Add
Push
Pop
Удалить из списка элемент на заданной позиции TList можно операцией
Delete
Add
Push
Pop
Удалить узлы списка TList можно операцией
Clear
Delete
Add
Pop
Число узлов в списке TList содержится в свойстве
Count
Add
AtLeast
Capacity
Доступ к элементам списка TList обеспечивает свойство
Items
Count
AtLeast
Capacity
Класс TObjectList реализует список
объектов Tobject
указателей типа pointer
строк ShortString
строк Ansistring
Класс TObjectStack реализует стек
объектов Tobject
указателей типа pointer
строк ShortString
строк Ansistring
Число узлов в стеке TObjectStack содержится в свойстве
Count
Items
AtLeast
Capacity
Класс TStack реализует стек
указателей типа pointer
указателей типа TObject
строк ShortString
строк Ansistring
Число узлов в стеке TStack содержится в свойстве
Count
Items
AtLeast
Capacity
Класс TObjectQueue реализует очередь
объектов Tobject
указателей типа pointer
строк ShortString
строк Ansistring
Число узлов в очереди TObjectQueue содержится в свойстве
Count
Items
AtLeast
Capacity
Класс TQueue реализует очередь
указателей типа pointer
указателей типа TObject
строк ShortString
строк Ansistring
Число узлов в очереди TQueue содержится в свойстве
Count
Items
AtLeast
Capacity
Класс TstringList реализует список
список строк и ассоциированных с ними объектов Tobject
указателей типа pointer
строк ShortString
строк Ansistring
Число узлов в списке TstringList содержится в свойстве
Count
Items
AtLeast
Capacity
Доступ к строке списка TstringList обеспечивает свойство
Strings
Count
Objects
Capacity
Доступ к объекту списка TstringList обеспечивает свойство
Objects
Count
Strings
Capacity
Добавить строку в конец списка TstringList можно операцией
Add
Push
Pop
Insert
Добавить строку и ассоциированный с ней объект в конец списка TstringList можно операцией
AddObject
Push
Add
Insert
На очереди TObjectQueue реализованы операции
Push
Pop
Peek
Add
Insert
Добавить элемент в очередь TObjectQueue можно операцией
Push
Add
Pop
Insert
Извлечь элемент из очереди TObjectQueue можно операцией
Pop
Push
Add
Insert
Получить доступ к элементу в очереди TObjectQueue можно операцией
Peek
Push
Pop
Insert
Число элементов в очереди TObjectQueue содержится в свойстве
Count
Items
AtLeast
Capacity
Ответ на вопрос: в очереди TObjectQueue не меньше заданного числа элементов содержится в свойстве
AtLeast
Count
Items
Capacity
На классе TQueue реализованы операции
Push
Pop
Peek
Add
Insert
Добавить элемент в очередь TQueue можно операцией
Push
Add
Pop
Insert
Извлечь элемент из очереди TQueue можно операцией
Pop
Push
Add
Insert
Получить доступ к элементу в очереди TQueue можно операцией
Peek
Push
Pop
Insert
Число элементов в очереди TQueue содержится в свойстве
Count
Items
AtLeast
Capacity
Ответ на вопрос: в очереди TQueue не меньше заданного числа элементов содержится в свойстве
AtLeast
Count
Items
Capacity
На классе TObjectStack реализованы операции
Push
Pop
Peek
Add
Insert
Добавить элемент в стек TObjectStack можно операцией
Push
Add
Pop
Insert
Извлечь элемент из стека TObjectStack можно операцией
Pop
Push
Add
Insert
Получить доступ к элементу в стеке TObjectStack можно операцией
Peek
Push
Pop
Insert
Число элементов в стеке TObjectStack содержится в свойстве
Count
Items
AtLeast
Capacity
Ответ на вопрос: в стеке TObjectStack не меньше заданного числа элементов содержится в свойстве
AtLeast
Count
Items
Capacity
На классе TStack реализованы операции
Push
Pop
Peek
Add
Insert
Добавить элемент в стек TStack можно операцией
Push
Add
Pop
Insert
Извлечь элемент из стека TStack можно операцией
Pop
Push
Add
Insert
Получить доступ к элементу в стеке TStack можно операцией
Peek
Push
Pop
Insert
Число элементов в стеке TStack содержится в свойстве
Count
Items
AtLeast
Capacity
Ответ на вопрос: в стеке TStack не меньше заданного числа элементов содержится в свойстве
AtLeast
Count
Items
Capacity
Для работы с файлом из приложения предназначен класс
TFileStream
TStringStream
TMemoryStream
TStream
Создать новый файл и открыть поток для работы с ним или, если указанный файл уже существует, создать поток и открыть файл в режиме записи можно методом класса TFileStream
Create
Write
Read
Insert
Закрыть поток (объект типа TFileStream) и связанный с ним можно методом класса
Free
Delete
Read
Insert
Добавить элемент в поток TFileStream можно методами
WriteBuffer
Write
Add
Insert
Извлечь элемент из потока TFileStream можно методами
ReadBuffer
Read
Pop
Delete
Установить указатель на заданный элемент в потокt TFileStream можно операцией
Seek
Conunt
Peek
AtLeast
Число байтов в потоке TFileStream содержится в свойстве
Size
Items
Count
Capacity
Текущая позиция потокового указателя, отсчитанная в байтах от начала потока TFileStream содержится в свойстве
Position
Count
Items
Capacity
Раздел модуля Object Pascal, в котором размещены ресурсы, экспортируемые модулем, называется
раздел интерфейса
раздел реализации
раздел инициализации
раздел финализации
раздел заголовка
Раздел модуля Object Pascal, в котором размещена реализация ресурсов, экспортируемых модулем, называется
раздел реализации
раздел интерфейса
раздел инициализации
раздел финализации
раздел заголовка
Раздел модуля Object Pascal, в котором помещено имя модуля, называется
раздел заголовка
раздел реализации
раздел интерфейса
раздел инициализации
раздел финализации
Раздел модуля Object Pascal, в котором помещен код, выполняемый перед началом работы приложения, называется
раздел инициализации
раздел финализации
раздел заголовка
раздел реализации
раздел интерфейса
Раздел модуля Object Pascal, в котором помещен код, выполняемый перед завершением работы приложения, называется
раздел финализации
раздел инициализации
раздел заголовка
раздел реализации
раздел интерфейса
Раздел интерфейса модуля Object Pascal, может содержать следующие разделы
раздел констант
раздел переменных
раздел типов
заголовки подпрограмм
предложение uses
раздел подпрограмм
раздел меток
Раздел реализации модуля Object Pascal, может содержать следующие разделы
раздел констант
раздел переменных
раздел типов
предложение uses
раздел подпрограмм
раздел меток
заголовки подпрограмм






МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук



СПИСОК ЛИТЕРАТУРЫ
по дисциплине «Технология программирования»
090104.65 «Комплексная защита объектов информатизации»















г. Владивосток 2012
Основная литература
Технологии программирования C++: В. Г. Давыдов Санкт-Петербург, БХВ-Петербург, 2005 г.- 672 с.
Технологии программирования и хранения данных: Арлазаров В.Л., Емельянов Н.Е. Санкт-Петербург, 2009 г.- 456 с.
Языки программирования и методы трансляции: Э. А. Опалева, В. П. Самойленко Москва, БХВ-Петербург, 2005 г.- 480 с.
Языки программирования. Концепции и принципы: В. Ш. Кауфман Санкт-Петербург, ДМК Пресс, 2010 г.- 464 с.
Дополнительная литература:
Васильев Б.К. Технология готовых решений в программировании: Учебное пособие. – Владивосток: Изд-во ВГУЭС, 2004.
Обучение программированию. Язык Pascal: Н. Л. Тарануха, Л. С. Гринкруг, А. Д. Бурменский, С. В. Ильина Санкт-Петербург, Солон-Пресс, 2009 г.- 384 с.
Программирование для Windows на C/C++. В 2 томах. Том 1: Н. Н. Мартынов Санкт-Петербург, Бином-Пресс, 2008 г.- 528 с.
Программирование для Windows на С/С++. В 2 томах. Том 2: Н. Н. Мартынов Москва, Бином-Пресс, 2009 г.- 480 с.
Профессиональное программирование. Системный подход: Игорь Одинцов Москва, БХВ-Петербург, 2006 г.- 624 с.

Интернет ресурсы:
[ Cкачайте файл, чтобы посмотреть ссылку ] Матрос Д.Ш. Теория алгоритмов: учебник / Д.Ш. Матрос, Г.Б. Поднебесова. - М.: БИНОМ. Л аборатория знаний, 2008. - 202 с. : ил. - (Педагогическое образование).
[ Cкачайте файл, чтобы посмотреть ссылку ] Л. Б. Бузюков, О. Б. Петрова «Современные методы программирования на языках С и С++», Учебное пособие, СПб. : Линk, 2008. – 288 с.
[ Cкачайте файл, чтобы посмотреть ссылку ] Немнюгин C.А. Средства программирования для многопроцессорных вычислительных систем: Учебно-методическое пособие. - СПб.: СПбГУ, 2007. - 88 с.



МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
«Дальневосточный федеральный университет»
(ДВФУ)

Школа естественных наук




ГЛОССАРИЙ
по дисциплине «Технология программирования»
090104.65 «Комплексная защита объектов информатизации»






























г. Владивосток 2012

А
АЛФАВИТ (alphabet) – упорядоченный определённым образом набор символов, из которых можно составлять слова и фразы данного языка.

АТРИБУТ (attribute) – признак, содержащий одну из характеристик данной величины.
Б
БЕЗУСЛОВНЫЙ ПЕРЕХОД – конструкция, при которой выполнение команд определяется вне зависимости от каких–либо условий.

БИБЛИОТЕКА (library) – совокупность программ, организованная определенным образом.
В
ВЫРАЖЕНИЕ – конструкция на языке программирования, предназначенная для выполнения вычислений.
И
ИНКАПСУЛЯЦИЯ – сокрытие внутренней структуры данных и реализации методов объекта от остальной программы.

ИНТЕРЛИНЬЯЖ – расстояние между базовыми линиями двух соседних строк.

ИНТЕРПРЕТАТОР (interpreter) – транслятор, последовательно (по одной команде) переводящий команды программы, написанной на языке высокого уровня, в команды машинного языка и выполняющий каждую команду сразу после перевода.
К
КЛАСС ОБЪЕКТОВ – совокупность объектов, обладающих одинаковыми свойствами и поведением.

КОМАНДА (command) – мнемонический набор символов, задающий тип операции, подлежащей выполнению процессором, а также участвующие в операции данные.

КОММЕНТАРИИ – пояснительный текст, который можно записывать в любом месте программы.

КОМПИЛЯТОР (compiler) – транслятор, преобразующий целиком программу, написанную на языке высокого уровня в эквивалентную программу на машинном языке.

КОНКАТЕНАЦИЯ (concatenation) – объединение двух и более значений строковых переменных или текстов в одно значение или текст без нарушения порядка следования символов в каждом из них.

КОНСТАНТА (constant) – величина, которая не изменяет своё значение в процессе выполнения алгоритма или программы.
Л
ЛИСТИНГ (listing) – совокупность всей последовательности команд программы.
М
МАКРОС – программный объект, который во время вычисления заменяется на новый объект, создаваемый определением макроса на основе его аргументов, затем выражается обычным образом.

МАССИВ (file) – данные, объединённые по какому-то признаку.

МЕТОДЫ – это действия, выполняемые над объектом.
О
ОБЪЕКТ (в программировании) – это инкапсуляция данных вместе с кодом, предназначенным для их обработки.

ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ – технология программирования, при которой программа рассматривается как набор дискретных объектов, содержащих, в свою очередь, наборы структур данных и процедур, взаимодействующих с другими объектами.

ОПЕРАТОР (operator) – законченное выражение на языке высокого уровня, предписывающее процессору выполнение некоторого действия.

ОПЕРАТОР ВЫБОРА – является обобщением условного оператора и позволяет сделать выбор из произвольного числа имеющихся вариантов.

ОТЛАДКА ПРОГРАММЫ – устранение в программе синтаксических, семантических и алгоритмических ошибок.

ОТЛАДЧИК – программа, предназначенная для поиска, обнаружения и исправления ошибок в других программах, позволяющая программистам выполнять программы по шагам, испытывать данные и контролировать значения переменных.
П
ПЕРЕМЕННАЯ (variable) – величина, которая изменяет своё значение в процессе выполнения алгоритма или программы.

ПОВЕДЕНИЕ ОБЪЕКТА – действия, которые могут выполняться над объектом или которые может выполнять сам объект.

ПОДПРОГРАММА (subroutine) – вызываемая основной программой последовательность операторов, оформленная в виде отдельной программы.

ПОЛИМОРФИЗМ – способность объекта выбирать правильный метод в зависимости от типа данных, полученных в сообщении.

ПРОГРАММА (program) – алгоритм, записанный на языке программирования, понятном для ЭВМ.

ПРОГРАММНЫЙ МОДУЛЬ – «контейнер» для размещения текстов процедур и функций, вызываемых системой во время исполнения в определенные моменты времени.

ПРОГРАММИРОВАНИЕ – запись решающего алгоритма на языке программирования и последующая трансляция на машинный язык.

ПРОЦЕДУРА – часть программы, предназначенная для выполнения некоторых стандартных действий, зависящих, в общем случае, от входных параметров.
Р
РЕЗУЛЬТАТИВНОСТЬ – каждый шаг после своего завершения создает среду, в которой все объекты однозначно определены.
С
СВОЙСТВА ОБЪЕКТОВ – атрибут объекта, определяющий его характеристики: размер, цвет, положение на экране или состояние (доступность, видимость).

СИСТЕМА КОМАНД (system of commands) – совокупность всех команд, которые может выполнить данный исполнитель.

СОБЫТИЯ ОБЪЕКТОВ – действия, распознаваемые объектом.

СОСТАВНОЙ ОПЕРАТОР – группа из произвольного числа операторов, заключенная в операторные скобки.

СПИСОК – список констант выбора состоит из произвольного количества значений и диапазонов, разделенных запятыми.

СТРУКТУРИРОВАННЫЕ ОПЕРАТОРЫ – структуры, построенные из других операторов по определенным правилам.
Т
ТРАНСЛЯТОР (translator) – программа, осуществляющая перевод команд программы, написанной на исходном языке программирования, в команды машинного языка.
У
УСЛОВНЫЙ ОПЕРАТОР – структура, позволяющая в зависимости от истинности условия выполнить один или другой оператор.
Ф
ФУНКЦИЯ (в программировании) – это именованная часть программы, к которой можно обращаться из других частей программы столько раз, сколько потребуется.
Ц
ЦИКЛ – оператор языка программирования, позволяющий многократно повторять одну и ту же последовательность команд (тело цикла).
Э
ЭЛЕМЕНТ МАССИВА (element of a file) – пронумерованная переменная.
Я
ЯЗЫК ВЫСОКОГО УРОВНЯ (language of a high level) – язык программирования, содержащий элементы естественного языка.
V
VBA (Visual Basic Assembler) – язык объектно–ориентированного программирования.
C
С (в русском варианте – Си) – язык программирования общего назначения, хорошо известный своей эффективностью, экономичностью и переносимостью.


Ссылка на источник:
[ Cкачайте файл, чтобы посмотреть ссылку ]










13PAGE \* MERGEFORMAT14415







х Программа
y

Непосредственный вывод в контекст устройства осей координат


·


·


·

xc,yc

xc,yc


·


·

0, 0

ys

xs

z

y

x

x, y, z

хс,ус (0,0)



Эђ Заголовок 1Эђ Заголовок 2Эђ Заголовок 3Эђ Заголовок 4Эђ Заголовок 5Эђ Заголовок 6Эђ Заголовок 7Эђ Заголовок 8Эђ Заголовок 915

Приложенные файлы

  • doc 87365950
    Размер файла: 2 MB Загрузок: 0

Добавить комментарий