Книга
Книга
Сергей Бобровский
Технологии
C++Builder
Разработка приложений для бизнеса
к УЧЕБНЫЙ КУРС.
Основы
программирования на языке C++
Технологии ООП
Визуальное проектирование приложений в среде C++Builder
^ППТЕР*
ББК 32.973.23-018.2я7
УДК 004.42(075)
Б72
Бобровский С. И.
Б72 Технологии C++Builder. Разработка приложений для бизнеса. Учебный курс. —
СПб.: Питер, 2007.
ISBN 978-5-91180-213-4
5-91180-213-9
Книга представляет собой учебный курс для студентов, изучающих разработку информационных систем и программирование на языке высокого уровня. Курс развернут над системой программирования C++Builder 2006 и комплексом информационных технологий, реализуемых ее средствами. Главы книги объединены в шесть тематических разделов. Разделы охватывают: основы программирования на языке C++ и объектно-ориентированное программирование, работу с базами данных и создание приложений, взаимодействующих с ними, создание клиентских и серверных приложений для Интернета, развертывание многоуровневых приложений. Рассмотрены также средства управления требованиями к программным проектам, технологии коллективной работы и методика локализации приложений. Курс имеет обновленный и расширенный понятийный аппарат, отличается архитектурной целостностью и способностью гибко адаптироваться к реалиям конкретного учебного процесса. Теоретические разделы поддержаны мощным практическим компонентом, который позволяет использовать данную книгу в качестве пособия для дополнительного или самостоятельного образования.
ББК 32.973.23-018.2я7
УДК 004.42(075)
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги.
ISBN 978-5-91180-213-4ISBN 978-5-91180-213-4© ООО «Питер Пресс», 2007
ISBN 978-5-91180-213-4
ISBN 978-5-91180-213-4
© Издательство «Развитие», 2007
Содержание
Введение24
Раздел 1. C++ как язык программирования высокого уровня
ГЛАВА 1. Экспресс-обзорзо
Система Borland Developer Studio30
Первый запуск среды C++Builder31
Создаем консольное приложение32
Работа с проектом34
Запуск программы35
Сборка приложения35
Процесс создания программы35
Запись программы на языке C++36
Структура программы37
Объявление переменных38
Оператор присваивания39
Ввод и вывод39
Ошибки компиляции41
Вопросы41
ГЛАВА 2. История развития языка программирования C++43
Структурное программирование43
Императивное программирование43
Язык программирования Си44
Язык программирования C++47
Среда программирования Turbo C/C++47
Среда программирования C++Builder48
Язык программирования Java50
Язык программирования C#51
Turbo C++ возвращается52
ГЛАВА 3. Простые типы данных54
Фундаментальные типы данных55
Числовые типы55
Тип Целое55
Тип Символьноел57
Тип Вещественное59
Битовые поля60
Тип Логическое61
Тип Строковое61
Перечислимые тины61
Преобразование типов данных62
Явное преобразование типов62
Неявное преобразование типов63
Преобразование с потерей значащих цифр64
Преобразования вещественных значений64
Арифметические преобразования65
Вопросы66
ГЛАВА 4. Константы, переменные, выражения67
Константы67
Именованные и неименованные константы67
Запись константы68
Переменные69
Понятие переменной69
Объявление переменной69
Регистровые переменные70
Комментарии70
Многострочные комментарии71
Однострочные комментарии71
Оператор присваивания71
Преобразование типов в операторе присваивания72
Составной оператор присваивания72
Выражения73
Операторы для целых и дробных значений73
Операторы логической обработки75
Логические операторы, обрабатывающие арифметические значения75
Логические операторы, обрабатывающие логические значения75
Поразрядные операторы76
Сокращенный условный оператор76
Операторы определения отношения77
Оператор получения информации о типе77
Оператор определения размера объекта78
Значение оператора присваивания,78
Приоритеты операторов78
Порядок вычисления выражения79
Порядок работы логических операторов80
Вопросы80
ГЛАВА 5. Производные типы81
Массивы81
Описание массива81
Тип массива и тип ссылки82
Индексация массива82
Ошибки неверной индексации83
Многомерные массивы84
Инициализация массивов84
Определение числа элементов массива85
Обработка ссылок на массивы и другие объекты85
Тип Строковое86
Массивы строк88
Структуры88
Описание структуры89
Обращение к элементам структуры90
Инициализация структур90
Вложенные структуры91
Анонимные структуры91
Эквивалентность структур91
Объединения92
Объявление синонимов типа93
Создание новых типов данных из сложных типов данных93
Вопросы94
ГЛАВА 6. Указатели и ссылки95
Объявление указателей95
Пустой тип 96
Определение адреса объекта97
Доступ к значению указателя97
Ссылки99
Массивы и указатели99
Имя массива как указатель99
Указатель на массив100
Операции над указателями101
Доступ к элементам структуры102
Указатели в структурах102
Указатели-константы103
Динамическое выделение оперативной памяти104
Динамическое создание набора элементов104
Удаление элементов из памяти105
Динамическое создание массивов105
Удаление массива из памяти106
Вопросы106
ГЛАВА 7. Управление последовательностью действий: условные операторы107
Пустой оператор107
Составной оператор (блок кода)107
Оператор группировки108
Видимость объектов программы108
Скрытие переменных109
Оператор доступа к глобальной переменной111
Условный оператор111
Сокращенная форма условного оператора112
Расширенная форма условного оператора112
Вложенные условные операторыИЗ
Оператор выбора114
Вопросы116
ГЛАВА 8. Управление последовательностью действий: операторы цикла117
Безусловный оператор цикла117
Вложенные циклы119
Объявление счетчика цикла внутри цикла120
Условный оператор цикла120
Первая форма условного оператора цикла121
Вторая форма условного оператора цикла121
Управление работой циклов122
Оператор прерывания цикла122
Оператор продолжения цикла123
Оператор перехода goto123
Вопросы124
ГЛАВА 9. Функции126
Объявление и вызов функций127
Значение, возвращаемое функцией127
Параметры функции129
Формальные и фактические параметры129
Передача фактических параметров по значению и по имени130
Передача параметра по значению130
Передача параметра по имени131
Дополнительный способ передачи параметра по имени131
Передача массивов в качестве параметров132
Неизменяемые параметры133
Необязательные параметры134
Неопределенные параметры134
Доступ к значениям неопределенных параметров...;135
Параметры быстрого вызова136
Вопросы136
ГЛАВА 10. Функции: основные возможности138
Объявление и определение функций138
Перезагрузка функций139
Рекурсия141
Встраиваемые функции142
Указатели на функции142
Синонимы указателей на функции144
Массивы указателей на функции144
Статические переменные145
Нестабильные переменные145
Дополнительные расширения функций146
Гибкое размещение описаний147
Формат вызова динамических библиотек147
Формат невозвращаемого вызова147
Подготовка к созданию собственного пролога и эпилога функции148
Работа без прерываний148
Формат вызова функций Си148
Формат вызова функций Паскаля148
Стандартный формат вызова функций148
Вопросы149
ГЛАВА 11. Общая структура программы150
Понятие модуля150
Заголовочные файлы150
Оптимизация процесса компиляции заголовочных файлов151
Структура модуля153
Структура главной программы155
Вопросы157
ГЛАВА 12. Внешние библиотечные модули158
Внешние определения158
Внешние переменные159
Динамически загружаемые библиотеки161
Пакеты 165
Вопросы166
Раздел 2. Технологии объектно-ориентированного программирования
ГЛАВА 13. Архитектура операционных систем168
Понятие об операционной системе и ее архитектуре168
Архитектура операционной системы Windows169
Архитектура платформы Microsoft .NET170
Оболочка .NET Framework171
Среда поддержки выполнения CLR171
ГЛАВА 14. Введение в объектно-ориентированное программирование174
Ранняя история объектного подхода174
Зарождение объектного программирования175
Современные объектные технологии177
Основные понятия объектно-ориентированного программирования178
Наследование, полиморфизм и инкапсуляция179
Наследование179
Полиморфизм179
Инкапсуляция180
Обмен сообщениями180
Вопросы180
ГЛАВА 15. Классы и объекты182
Описание класса182
Видимость элементов класса182
Базовая иерархия классов VCL183
Вложенные классы185
Отложенное определение класса185
Наследование классов186
Множественное наследование186
Виртуальные классы187
Создание объектов188
Объекты и указатели189
Присваивание и копирование объектов190
Вопросы191
ГЛАВА 16. Поля класса193
Определение поля в программе193
Доступ к полю класса193
Доступ к полю через указатель194
Видимость полей класса194
Наследование и скрытие полей класса195
Приведение типов196
Статические поля197
Поля структур и объединений198
Вопросы199
ГЛАВА 17. Методы классов200
Объявление и реализация методов200
Интерфейс и реализация класса201
Вызов метода202
Указатель на себя203
Статические методы203
Наследование методов204
Видимость полей и методов204
Скрытие методов205
Перезагрузка методов206
Связывание метода с объектом206
Статическое связывание207
Виртуальное (динамическое) связывание208
Связывание абстрактных методов209
Вопросы210
ГЛАВА 18. Конструкторы и деструкторы211
Понятие конструктора211
Инициализация объекта212
Конструктор копирования213
Перегрузка конструкторов214
Порядок вызова конструкторов 214
Скрытые конструкторы215
Наследование конструкторов215
Деструкторы216
Явный и неявный вызов деструктора217
Виртуальные деструкторы217
Перегрузка операторов new и delete218
Вопросы219
ГЛАВА 19. Свойства и дружественные функции220
Свойства220
Л. Создание свойств221
Индексаторы223
Оригинальная возможность объявления свойств225
Дружественные классы и функции225
Глобальные дружественные функции225
Дружественные классы и дружественные функции других классов226
Вопросы227
ГЛАВА 20. Перегрузка операций и шаблоны228
Перегрузка операторов228
Перегрузка преобразования типов231
Параметризованные классы и функции (шаблоны)232
Функции-шаблоны232
Классы-шаблоны233
Особенности использования угловых скобок234
Вопросы235
ГЛАВА 21. События и исключительныеситуации236
События236
Закрытый вызов обработчика237
Обработка событий в Проектировщике237
Исключительные ситуации239
Обработка исключительных ситуаций240
Перехват любых исключений242
Анализ ошибки242
Программная генерация исключительной ситуации242
Вложенные операторы try243
Гарантированное завершение блока кода244
Создание собственных классов исключений244
«Молчаливые» исключения245
Вопросы246
ГЛАВА 22. Объектные интерфейсы и пространство имен247
Обработка сообщений Windows247
Создание собственного обработчика системных сообщений248
Рассылка сообщений249
Объектные интерфейсы249
Декларация интерфейса250
Реализация интерфейса с помощью класса250
Идентификатор GUID251
Пространство имен252
Определение пространства имен253
Вложенные пространства имен254
Безымянные пространства имен254
Доступ к элементам пространства имен255
Пространство имен по умолчанию255
Альтернативные имена257
Вопросы258
Раздел 3. Технологии визуального проектирования
ГЛАВА 23. Работа со средой C++Builder260
Установка системы C++Builder 2006260
Технические требования к системе C++Builder 2006260
Анализ готовности операционной системы260
Управление процессом установки261
Основное окно среды C++Builder263
Репозиторий объектов265
Палитра инструментов;265
Проектирование форм267
Интерактивный Проектировщик и направляющие268
Проектировщик документов HTML268
Инспектор объектов269
Менеджер проектов269
Настройка среды C++Builder270
Вопросы271
ГЛАВА 24. Принципы создания приложений в среде C++Builder272
Рабочий стол272
Мастера приложений273
Технология создания простых приложений273
Создание пустой заготовки273
Проектирование формы274
Компиляция и запуск274
Программирование и создание обработчиков событий274
Добавление новых форм к проекту275
Использование компонентов275
Тематические категории свойств277
Вопросы279
ГЛАВА 25. Работа в редакторе C++Builder280
Редактор среды C++Builder280
Визуализация кода281
Анализ кода282
Шаблоны кода283
Автоматическое завершение описаний285
Средства свертывания кода286
Организация закладок286
Синхронное редактирование 287
Ведение списка задач288
Вопросы289
ГЛАВА 26. Технологии компиляции290
Компиляция проекта290
Сборка проекта290
Формирование исполнимой программы291
Сообщения об ошибках 293
Настройки компилятора294
Создание настроечных конфигураций295
Настройки сборщика296
Препроцессорная обработка297
Структура команд препроцессора298
Определение препроцессорной константы298
Условная компиляция298
Подстановка файла300
Сообщение об ошибке301
Директивы компиляции301
Макросы302
Вопросы303
ГЛАВА 27. Базовые технологии отладки ,.304
Отладочные настройки проекта305
Функция Assert306
Пошаговое выполнение программ307
Установка точки прерывания308
Анализ значений переменных309
Отладочная раскладка рабочего стола310
Пошаговое выполнение процедур и функций310
Дополнительные возможности пошагового выполнения311
Вопросы312
ГЛАВА 28. Расширенные технологии отладки313
Условные точки прерывания313
Прерывание по изменению значения315
Протокол отладки315
Подключение протокола работы315
Настройка протокола работы316
Просмотр протокола316
Отладка машинного кода317
Инспектор отладки318
Система CodeGuard319
Вопросы320
ГЛАВА 29. Технологии визуального проектирования приложений321
Компоненты группы Standard321
КомпонентНадпись321
Компонент Текстовое поле321
КомпонентМеню321
КомпонентКонтекстное меню322
Компонент Текстовая область322
Компонент Флажок323
Компонент Переключатель323
Компонент Группа переключателей323
Компонент Список324
Компонент Поле со списком325
Компонент Полоса прокрутки325
Компоненты группы Win32326
Компонент Набор страниц326
Компоненты группы System 326
Компонент Таймер326
Обработка щелчка мыши327
Вопросы328
ГЛАВА 30. Технологии работы с графикой и мультимедиа329
Работа с графикой329
Понятие холста329
Принцип отрисовки изображений в C++Builder330
Технология графического вывода330
Основные графические операции330
Подготовка холста330
Использование инструментов рисования331
Вывод текста332
Точечные графические изображения333
Принципы компьютерной анимации333
Спрайты334
Воспроизведение музыкальных и видеоклипов338
Вопросы340
ГЛАВА 31. Технологии информационной безопасности341
Модели защиты 342
Модели многоуровневой защиты343
Модель MILS как наиболее передовое решение344
Технология обеспечения информационной безопасности346
Обеспечение безопасности данных346
Повышение общей надежности346
Борьба с угрозами347
Стандартная технология повышения уровня информационной безопасности347
Подходы к обеспечению безопасности программного продукта347
Приемы использования программных технологий защиты348
Средства защиты приложений для сети349
Защита Интернет-каналов350
Получение сведений о системе351
Криптографические средства шифрования данных353
Вопросы356
Раздел 4. Работа с файлами и базами данных
ГЛАВА 32. Введение в системы управления базами данных358
Недостатки файлового подхода358
История развития СУБД360
Реляционная модель данных361
Двенадцать правил Кодда, описывающих реляционную модель базы данных361
Первые реляционные СУБД363
Вопросы364
ГЛАВА 33. Принципы проектирования баз данных365
Технология нормализации365
От реляционных СУБД к объектным366
Требования к современным СУБД367
Проектирование баз данных368
Этапы нормализации370
Метод проектирования «сущность-связь»372
Проектирование баз данных как искусство372
Вопросы373
ГЛАВА 34. Технологии работы с файлами375
Типизированные и нетипизированные файлы375
Файловые операции376
Открытие файла376
Обработка файла377
Закрытие файла377
Общая технология работы с файлами в системе C++Builder378
Технология работы с типизированными файлами378
Объявление файловой переменной378
Открытие файла379
Обмен данными с файлом379
Закрытие файла380
Проверка завершения файла380
Технология работы с нетипизированными файлами381
Работа с файловым указателем383
Форматированный ввод-вывод383
Структура формата ввода-вывода383
Форматированный вывод384
Форматированный ввод385
Форматирование строк385
Вопросы386
ГЛАВА 35. Объектные технологии работы с файлами397
Стандартная библиотека stream.h388
Файловый потоковый обмен388
Объектная технология потокового обмена VCL389
Базовый класс для работы с файлами389
Ввод и вывод компонентов391
Копирование файлов392
Классы для типизированного файлового обмена392
Сохранение списков в файлах393
Технология работы с файлами .INI394
Вопросы395
ГЛАВА 36. Файл-серверные технологии396
Технология работы с файл-серверной СУБД396
Технология Borland Data Engine397
Набор данных397
Реляционная таблица398
Технология взаимодействия с базой данных в проекте C++Builder 398
Технология создания базы данных и таблиц399
Тип поля400
Ключевое поле401
Индексация401
Вопросы404
ГЛАВА 37. Технологии создания файл-серверных приложений405
Создание модуля данных405
Доступ к таблице базы данных405
Создание запросов:.406
Поля набора данных407
Добавление псевдополей в текущий проект407
Источники данных408
Создание пользовательского интерфейса408
Пользовательская таблица данных409
Другие элементы интерфейса410
Технологии программной работы с таблицами410
Открытие и закрытие набора данных411
Модификация записей в наборе данных411
Фиксация изменений и отказ от изменений411
Дополнительные возможности412
Индексы412
Вычисляемые поля412
Закладки413
Фильтры413
Поиск413
Вопросы414
ГЛАВА 38. Клиент-серверные технологии415
Недостатки файл-серверной архитектуры415
Понятие о клиент-серверной архитектуре416
Принципы создания клиент-серверных приложений в среде C++Builder416
Технология создания клиент-серверного приложения с использованием СУБД Interbase417
Программная работа с наборами данных Interbase419
Технология работы с серверами SQL421
Компоненты dbExpress421
Технология создания клиент-серверного приложения dbExpress422
Вопросы424
ГЛАВА 39. Технология многоуровневой работы с базами данных..425
Понятие многоуровневой архитектуры425
Веб-архитектура — развитие клиент-серверной архитектуры.... 425
Особенности многоуровневой архитектуры426
Создание многоуровневого приложения с помощью технологии DataSnap427
Дополнительные компоненты DataSnap430
Связь с сервером приложений через сокеты430
Связь с сервером приложений по протоколу HTTP431
Другие возможности связи с сервером приложений431
Вопросы 432
ГЛАВА 40. Формирование запросов к базам данных на языке SQL433
Штатное средство для работы с командами SQL434
Запись команд SQL434
Логические выражения SQL435
Отбор записей с помощью команды SELECT435
Отказ от дублирования 435
Фильтрация записей436
Специальные условия отбора436
Сортировка набора437
Проверка неопределенных полей438
Агрегирование и группировка438
Уточнение работы функций439
Скалярные аргументы439
Группировка результатов439
Фильтрация групп записей440
Вопросы 441
ГЛАВА 41. Язык запросов SQL: дополнительные возможности442
Вычисляемые поля442
Соединение таблиц442
Эквисоединение444
Работа с копиями таблицы445
Вложенные запросы446
Соединение таблиц во вложенном запросе447
Проверка пустого запроса447
Проверка набора на наличие значений448
Обновление записей449
Добавление новой записи449
Удаление записей:449
Модификация значений полей449
Команды обновления и команда SELECT450
Обработка таблиц450
Создание таблицы450
Уникальные поля и первичные ключи451
Ограничения на значения полей451
Значения полей по умолчанию452
Создание индексов452
Модификация структуры таблицы 452
Удаление таблицы453
Представления453
Вопросы454
Раздел 5. Технологии создания веб-приложений
ГЛАВА 42. История развития веб-технологий456
Ключевые технологии Интернета456
Возникновение гипертекста 457
Язык HTML и развитие браузеров459
ГЛАВА 43. Современные веб-технологии463
Технология XML464
Технологии веб-служб465
Семантическая сеть467
Веб 2.0467
ГЛАВА 44. Технологии Indy470
Компоненты Indy 10470
Принципы создания серверных приложений Indy470
Технология создания сервера HTTP473
Обработка базового события OnCommandGet474
Анализ пользовательского запроса475
Технология создания почтовых программ476
Прием электронной почты476
Отправка почтового сообщения476
Технологии кодирования почтовых вложений477
Использование протокола SSL478
Вопросы479
ГЛАВА 45. Технологии XML480
Использование языка XML для представления наборов данных481
Штатное средство XML Mapper481
Программная работа с данными в формате XML485
Технология программного преобразования данных в формат XML485
Технология двусторонних преобразований наборов данных486
Технология использования удаленных поставщиков DCOM.... 487
Технология анализа документов XML488
Вопросы489
ГЛАВА 46. Технология IntraWeb491
Обзор технологии IntraWeb491
Множественные формы проекта IntraWeb495
Масштабируемость системы IntraWeb497
Управление состояниями497
Организация множественных серверов497
Серверы транзакций498
Дополнительные возможности технологии IntraWeb498
Вопросы498
Раздел 6. Технологии управления жизненным циклом приложений
ГЛАВА 47. Технология проектирования программных систем500
Сложность процесса разработки500
Снижение проектной сложности501
Технология объектного проектирования502
Новые подходы к проектированию504
ГЛАВА 48. Архитектура, управляемая моделью507
Концепция архитектуры, управляемой моделью507
Модельные точки зрения и модели MDA509
Вопросы510
ГЛАВА 49. История программной инженерии511
Зарождение программной инженерии511
Модель «водопад»512
Переход к гибким методологиям513
Современное состояние программной инженерии514
Структура жизненного цикла программного обеспечения в среде Borland515
Вопросы517
ГЛАВА 50. Технологии контроля версий и организации групповой работы518
Диспетчер версий518
Визуальное сравнение содержимого файлов519
Взаимодействие с системами управления версиями исходных текстов520
Контроль версий в системе C++Builder522
Работа с системой StarTeam522
Слияние файлов проекта525
Вопросы526
ГЛАВА 51. Технология управления проектными требованиями с помощью системы Caliber527
Управление требованиями и срез проекта527
Система Borland Caliber528
Работа с клиентом Caliber530
Дерево требований530
Представление требования в системе Caliber531
Функциональные требования531
Бизнес-требования533
Организация ролевой работы533
Отслеживание взаимосвязей между требованиями533
Связь требований с исходным кодом проекта535
Оценка трудозатрат538
Вопросы539
ГЛАВА 52. Технологии локализации приложений540
Классическая технология локализации540
Средства автоматической локализации542
Дополнительные возможности локализации543
Учет методов ввода текста543
Региональные настройки...543
Программное определение параметров локализации544
Язык локализации системы544
Настройки экрана545
Глобализация545
Перспективы развития систем локализации545
Вопросы546
Алфавитный указатель547
ВВЕДЕНИЕ
Экскурс в историю
Впервые термин «технология» предложил в XVIII веке немецкий ученый Иоганн Бекман, одно время преподававший физику и историю в Петербургской гимназии. В конце XVIII века он приехал в германский город Геттинген, где получил звание профессора философии и политической экономики. В 1777 году вышла его книга «Общая технология» («Allgemeine Technologie»), где Бекман ввел это понятие как научный термин. Он попытался обобщить множество ремесленных подходов и выделил основные приемы, характерные для обработки материалов и производственной деятельности самых разных направлений, которые и назвал технологией. В серии трудов по технологии Бекман описал типичные рабочие действия, единые приемы труда и универсальные подходы к использованию средств производства, чем существенно упростил процесс изучения многих технических дисциплин. В частности, при описании ремесел он активно использовал подход «цель — средства».
Однако синтетический подход Бекмана отличался достаточно простым и очевидным обобщением известных приемов труда и не опирался на глубокий научный анализ разных сфер человеческой деятельности. В то же время, Бекман целенаправленно систематизировал формы работы различных производственных структур, существенно упростил изучение типичных процессов производства, а главное — определил технологию как самостоятельную научную дисциплину, не зависящую от конкретной ремесленной школы, коих в те годы насчитывались сотни.
В дальнейшем идеи Бекмана развил немецкий профессор Тюбингенского университета Иоганн Генрих Мориц Поппе, преподававший физику и математику. Он издал в 1821 году книгу «Руководство к общей технологии», в которой ввел свое определение технологии. Согласно Поппе, технология — это наука о ремеслах, которая изучает производственные процессы, машины и орудия труда независимо от их конкретного назначения.
Труды Бекмана и Поппе впоследствии пропагандировал Эрнст Гартиг, первый ректор Дрезденской высшей технической школы. Он настаивал на необходимости изучения технологии как отдельной науки и дополнил ее исследованиями из области логики. В России технологию как науку представил крупный ученый, инженер и изобретатель Петр Клементьевич Эшельмейер, дополнивший
существовавшие подходы к технологии теориями творчества и изобретательства. Он считается теоретиком отечественного инженерного дела, а наиболее известен журналом «Техник» и трудом «Философия техники» (1912 г.), которым заложил глубокий философский фундамент инженерной профессии.
От производственных технологий к информационным
Сегодня термин «технология» различными словарями и справочниками расшифровывается по-разному. Пожалуй, наиболее точное определение дает Большая советская энциклопедия:
«Технология — совокупность приемов и способов получения, обработки или переработки сырья, материалов, полуфабрикатов или изделий, осуществляемых в различных отраслях промышленности, в строительстве и т. д.; научная дисциплина, разрабатывающая и совершенствующая такие приемы и способы...»
Применительно к информационной сфере технология в чистом виде представляет собой набор способов и инструментов производства информационного (в частности, программного) продукта. В данной книге рассматривается набор воплощенных технологий, предлагаемых системой Borland C++Builder. Все они прямо или косвенно поддерживают процессы создания программного продукта.
Технологии программирования
Основной прием получения программного продукта — это создание исходного текста программы на языке программирования высокого уровня и перевод его в исполнимый код с помощью компилятора. Этим технологиям посвящены первые две части книги (главы 1-22). В первой части (главы 1 — 12) рассмотрены базовые средства языка, без которых не обходится ни одна программа, во второй (главы 13 3) — технологии объектного программирования, существенно повышающие производительность труда и упрощающие создание крупных систем.
Язык C++ представляет собой версию языка Си, созданного в начале 1970-х годов. Си предназначался для создания переносимой, независимой от конкретной аппаратной платформы, версии операционной системы Unix, которая до его появления была написана на ассемблере. Авторы Си постарались объединить в языке наглядность и компактность, характерные для универсальных языков программирования, с высокой эффективностью компиляции и исполнения результирующего кода. По этой причине на Си и C++ и по сей день создается немало системного программного обеспечения.
В начале 1980-х годов, с развитием объектных технологий, на базе Си был создан язык C++, который дополнил синтаксис Си средствами объектно-ориентированного программирования. К недостаткам C++ следует отнести определенную сложность начального изучения и отсутствие средств контроля за распространенными ошибками, в результате чего программист с помощью гибкого инструмен-
тария может создавать очень производительный код, однако и ответственность за его качество полностью возлагается на разработчика.
За счет своих сильных сторон язык C++ завоевал наибольшую популярность у программистов всего мира. Недаром на его основе была создана технология программирования Java для создания кросс-платформенных приложений, включающая в себя одноименный язык программирования, по написанию ключевых операторов схожий с C++. Корпорация Microsoft также учла опыт миллионов проектов на C++ и свой новый язык программирования C# для платформы .NET основала на нем и Java.
Технологии визуального проектирования приложений
В третьей части книги (главы 23-31) рассмотрены три группы технологий создания программных продуктов средствами системы Borland C++Builder. Первая группа охватывает технологии визуального проектирования (подготовки исходных текстов и разработки оконных форм) на основе объектно-компонентного подхода, вторая группа основана на современных технологиях компиляции и отладки приложений (получения безошибочного исполнимого кода), а третья охватывает затрагивает технологические приемы построения мультимедийных приложений и организации информационной защиты.
Программа создается в среде C++Builder в визуальном режиме — с помощью встроенного Проектировщика форм. Разработчик размещает на форме элементы управления из обширной библиотеки визуальных компонентов, настраивает их свойства в наглядном Инспекторе объектов и при необходимости задает реакции на различные действия пользователя, например на нажатие кнопки, выбор пункта меню, изменение состояния переключателей. Реакция программы на подобные действия вводится записью непосредственно на языке программирования C++, в специализированном редакторе, имеющем специальные средства наглядной визуализации исходного кода и контекстной помощи.
Компиляция, сборка и запуск программы выполняются полностью в автоматическом режиме. При обнаружении ошибок среда подскажет сомнительные места в исходном тексте, а при сбое в работающей программе выдаст подробный протокол, раскрывающий возможную неисправность. Специальный модуль CodeGuard способен выявить в программе ошибки, специфичные для языка C++, однако не отлавливаемые стандартным компилятором. Этот модуль следит за программой непосредственно в процессе ее работы и при возникновении ошибочной ситуации подсказывает связанное с ней место в исходном коде.
Технологии создания приложений, работающих с базами данных
В четвертой части книги (главы 32-41) рассмотрены технологии C++Builder, позволяющие создавать программы, взаимодействующие с системами управле-
ния базами данных (СУБД). Практически любая более-менее крупная программная система интенсивно работает с базами данных — внешними хранилищами больших объемов информации, организованной в табличном виде. СУБД автоматизируют многие аспекты такой работы: ведение справочников, одновременное обслуживание большого количества пользователей, передачу таблиц по сети, обеспечение надежного хранения данных и защищенность от возможных сбоев.
Однако СУБД сами по себе — автономные и весьма сложные программные системы. Их эксплуатация требует специальных знаний и опыта. В систему C++Builder включено большое количество средств технологической поддержки, упрощающих стыковку создаваемых приложений с внешними СУБД. Эти средства отлаживались и совершенствовались во многих версиях системы на протяжении не одного десятка лет и сегодня представляют собой набор, готовый к максимально простому и эффективному использованию. Показательно, что создание приложения, которое отображает содержимое нескольких таблиц различных баз данных разных типов СУБД и предоставляет средства навигации и редактирования их содержимого, возможно в рамках C++Builder полностью в визуальном режиме. Программисту для организации взаимодействия с СУБД не требуется вводить команды на языке C++: достаточно разместить в рамках текущего проекта несколько стандартных компонентов C++Builder, ответственных за стыковку с СУБД и визуализацию содержимого таблиц, и настроить их на конкретные базы данных. Все остальное среда C++Builder сделает сама. При этом разработчик может создавать небольшое приложение для индивидуального пользования, работающее с маленькой базой данных на локальном компьютере, или же корпоративное решение, обслуживающее множество клиентских подключений к базе — технологии построения приложений во всех подобных случаях остаются почти одинаковыми.
Взаимодействие приложения с базами данных осуществляется посредством команд языка запросов SQL. Этот язык задает требования к набору данных, который должен быть получен от СУБД программой, написанной на языке C++. Язык SQL слабо зависит от конкретной реализации, поэтому базовый набор языковых конструкций SQL, описанный в четвертой части, может быть с успехом использован при работе с СУБД самых разных типов.
Технологии создания приложений для Интернета
В пятой части книги (главы 42-46) приведены технологии, с помощью которых создаются приложения для Интернета. Всемирная сеть является огромной сферой приложения усилий миллионов разработчиков, поэтому система C++Builder предлагает программистам сразу несколько категорий Интернет-технологий.
Базовая группа технологий на основе набора компонентов Indy включает готовые средства построения собственных веб-серверов и клиентских программ для обращения к собственным или произвольным веб-серверам по различным сетевым протоколам (HTTP, FTP, POP/SMTP и так далее). Набор компонентов для работы с документами в формате XML автоматизирует большинство задач, связанных с анализом и другими формами обработки информации из Сети. Утилита
XML Mapper автоматизирует процесс трансформации наборов баз данных и информацией в формате XML, автоматически генерируя нужные правила преобразования.
Как и в случае с технологиями СУБД, технологии C++Builder для Интернета нередко позволяют обойтись без ручного кодирования на языке высокого уровня и ограничиться лишь визуальными средствами построения сетевых приложений. На эту задачу ориентирован технологический набор Intra Web. Разработка сетевого приложения с его помощью ведется почти в точном соответствии с процессом создания обычных локальных, однопользовательских приложений. Программист готовит обычную оконную форму, размещает на ней стандартные элементы управления и задает реакции на них. Такой проект автоматически преобразуется в серверное веб-приложение, обслуживающее множество пользователей по единой схеме.
Технологии сопровождения жизненного цикла программного обеспечения
Заключительная часть книги (главы 47-52) посвящена технологиям, повышающим качество создаваемой продукции и эффективность коллективной работы над проектом. Со средой C++Builder тесно состыкована система контроля версий StarTeam, которая хранит проект в централизованном Репозитории. Когда над проектом трудится множество разработчиков, а модификации исходных текстов и других ресурсов происходят практически непрерывно, согласование содержимого копий файлов превращается в трудоемкую задачу. Ее решает система StarTeam, предлагающая развитые сетевые механизмы внесения и согласования изменений. В оболочку C++Builder включено клиентское приложение, соединяющее текущий проект C++Builder с сервером StarTeam.
Работа над проектом среднего и крупного размера начинается с выработки требований к итоговому продукту. Они подразделяются на функциональные, пользовательские и бизнес-требования. Современные проекты насчитывают сотни тысяч требований, и эффективный процесс их согласования и контроля за выполнением возможен лишь с привлечением средств автоматизации этой деятельности. Управлением требованиями к программным проектам занимается сетевая система Caliber. Клиентское средство, необходимое для взаимодействия с этой системой, встроено в оболочку среды C++Builder. С его помощью программист в любой момент может узнать, какие требования и в каком порядке надлежит выполнять, каковы их текущие сроки и приоритеты.
Книга завершается технологиями локализации приложений — средствами C++Builder, с помощью которых проекты переводятся на различные национальные языки.
^Т) РАЗДЕЛ
C++ как язык программирования высокого уровня
Язык C++ и среда разработки C++Builder
Типы данных
Переменные, выражения
Управление порядком выполнения операторов
Массивы, указатели
Функции
Библиотеки, модули, пакеты
ГЛАВА 1
Экспресс-обзор
В данной книге подробно рассматриваются технологии, лежащие в основе системы разработки Borland Developer Studio и реализуемые с ее помощью. Эта система представляет собой интегрированную оболочку.
Интегрированная оболочка —- это система программирования, объединяющая средства визуального проектирования окон приложений, средства редактирования исходных текстов программ, а также средства их компиляции и отладки.
В состав системы разработки Borland Developer Studio входят следующие средства:
редактор исходных текстов программ на языке программирования высокого уровня;
дизайнер (проектировщик форм?), позволяющий проектировать внешний вид окон программы и размещать в них элементы управления;
компиляторы, преобразующие исходный текст в исполнимый код для вычислительных платформ Win32 и .NET;
отладчики, позволяющие анализировать процесс выполнения программ в пошаговом режиме.
Система Borland Developer Studio
В книге рассмотрена разработка приложений для вычислительной платформы Windows 32 (далее — Win32') на языке программирования C++. По этой причине часто говорится о среде или системе программирования C++Builder (или просто С ++Builder), под которой понимается оболочка Borland Developer Studio 2006 и средства создания программ на языке C++. Корпорация Borland сохраняет названия Delphi, C++Builder и C#Builder как всемирно известные брэнды. Разработчики могут установить либо полную версию системы Borland Developer Studio, включающую эти три продукта, либо любой из них по отдельности.
В систему Borland Developer Studio 2006 включены компиляторы для языков программирования C++, Delphi (Delphi Language) и С#. Создание программ на C++ возможно только для платформы Windows 32. На языке Delphi создают программы как для стандартной платформы Windows 32, так и для новой платформы Microsoft .NET Framework 1 .х, а создание программ на C# возможно только для платформы .NET. Вместе с тем процессы разработки приложений на любом из этих языков с помощью Borland Developer Studio почти одинаковы и разнят-
ся, в основном, использованием операторов того или иного языка. Поэтому среда C++Builder 2006 объединила средства создания программ на всех языках в одной интегрированной оболочке.
В данной главе кратко описываются основные возможности среды C++Builder и языка C++, которые востребованы практически в любой программе. Здесь также приведены основные шаги по созданию программ в среде C++Builder. После прочтения данной главы усвоить последующий материал будет гораздо легче.
Первый запуск среды C++Builder
После запуска системы C++Builder на экране открывается начальное окно среды разработки (рис. 1.1). На первом шаге необходимо создать новый проект C++Builder. Проект C++Builder (или просто проект) представляет собой группу файлов, содержащих исходный текст программы и вспомогательные данные. Все они объединены тематикой решаемой задачи.
Borland Developer Studio 2006
’J <i^ 3’J <i^ 3Не М 3<ЙгМ ^C^ipohent ^Help : |Test LayoutНе М 3<ЙгМ ^C^ipohent ^Help : |Test Layout^ Welcome Page^ Welcome Page[bdsi/def ault.htm[bdsi/def ault.htm8 сентября 2006 г.8 сентября 2006 г.Releese NotesReleese NotesResoutresResoutresRecent ProjectsRecent Projectse Projectl .bdsproj ^ Projectl.bdsproje Projectl .bdsproj ^ Projectl.bdsproj- Readme; Installi DeployDocumentation- Readme; Installi DeployDocumentationH' Tool Palette* Other Files - C++Builder ProjectsConsole ApplicationStatic Library a Package ^ Dynamic-link LibraryH' Tool Palette* Other Files - C++Builder ProjectsConsole ApplicationStatic Library a Package ^ Dynamic-link Library* X* X; Getting StartedHow-To Guide; Delphi Training Services; User's Guide (PDF)■ Language Reference, (PDF)■ Reviewers Guide (PDF)Intpnratprl Partners; Getting StartedHow-To Guide; Delphi Training Services; User's Guide (PDF)■ Language Reference, (PDF)■ Reviewers Guide (PDF)Intpnratprl Partnersw WebApplicationl.bdsprojProjectGroupI .bdsgroup ® Projectl.bdsprojw WebApplicationl.bdsprojProjectGroupI .bdsgroup ® Projectl.bdsprojHeadlinesHeadlinesGetting StartedGetting StartedWhat is Borland Developer Studio?Borland Developer Studio is an integrated development environment(IDE)tor building Delphi. Delphi tor .NET, С#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cyclePar Eci or. И ->. rrsCfs ЛИ:;What is Borland Developer Studio?Borland Developer Studio is an integrated development environment(IDE)tor building Delphi. Delphi tor .NET, С#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cyclePar Eci or. И ->. rrsCfs ЛИ:;BrowserBrowser; ^ Control Panel Application| Iffi Service Application О SDI Applicationi ^ MDI Application; + C++Builder Projects | C++Builder Files i :+< Web Documents+ C++Builder Projects ] WebSnap t C++Builder Projects | WebServices ;+ C++Builder Projects | WebBroker- +i C++Builder Projects | Intraweb+ Delphi Projects | Intraweb;+- C++Builder Projects J ActiveX; ^ Control Panel Application| Iffi Service Application О SDI Applicationi ^ MDI Application; + C++Builder Projects | C++Builder Files i :+< Web Documents+ C++Builder Projects ] WebSnap t C++Builder Projects | WebServices ;+ C++Builder Projects | WebBroker- +i C++Builder Projects | Intraweb+ Delphi Projects | Intraweb;+- C++Builder Projects J ActiveX
’J <i^ 3
’J <i^ 3
Не М 3<ЙгМ ^C^ipohent ^Help : |Test Layout
Не М 3<ЙгМ ^C^ipohent ^Help : |Test Layout
^ Welcome Page
^ Welcome Page
[bdsi/def ault.htm
[bdsi/def ault.htm
8 сентября 2006 г.
8 сентября 2006 г.
Releese Notes
Releese Notes
Resoutres
Resoutres
Recent Projects
Recent Projects
e Projectl .bdsproj ^ Projectl.bdsproj
e Projectl .bdsproj ^ Projectl.bdsproj
- Readme
; Install
i Deploy
Documentation
- Readme
; Install
i Deploy
Documentation
H' Tool Palette
* Other Files - C++Builder Projects
Console Application
Static Library a Package ^ Dynamic-link Library
H' Tool Palette
* Other Files - C++Builder Projects
Console Application
Static Library a Package ^ Dynamic-link Library
* X
* X
; Getting Started
How-To Guide
; Delphi Training Services
; User's Guide (PDF)
■ Language Reference
, (PDF)
■ Reviewers Guide (PDF)
Intpnratprl Partners
; Getting Started
How-To Guide
; Delphi Training Services
; User's Guide (PDF)
■ Language Reference
, (PDF)
■ Reviewers Guide (PDF)
Intpnratprl Partners
w WebApplicationl.bdsproj
ProjectGroupI .bdsgroup ® Projectl.bdsproj
w WebApplicationl.bdsproj
ProjectGroupI .bdsgroup ® Projectl.bdsproj
Headlines
Headlines
Getting Started
Getting Started
What is Borland Developer Studio?
Borland Developer Studio is an integrated development environment(IDE)tor building Delphi. Delphi tor .NET, С#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cycle
Par Eci or. И ->. rrsCfs ЛИ:;
What is Borland Developer Studio?
Borland Developer Studio is an integrated development environment(IDE)tor building Delphi. Delphi tor .NET, С#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cycle
Par Eci or. И ->. rrsCfs ЛИ:;
Browser
Browser
; ^ Control Panel Application
| Iffi Service Application О SDI Application
i ^ MDI Application
; + C++Builder Projects | C++Builder Files i :+< Web Documents
+ C++Builder Projects ] WebSnap t C++Builder Projects | WebServices ;+ C++Builder Projects | WebBroker
- +i C++Builder Projects | Intraweb
+ Delphi Projects | Intraweb
;+- C++Builder Projects J ActiveX
; ^ Control Panel Application
| Iffi Service Application О SDI Application
i ^ MDI Application
; + C++Builder Projects | C++Builder Files i :+< Web Documents
+ C++Builder Projects ] WebSnap t C++Builder Projects | WebServices ;+ C++Builder Projects | WebBroker
- +i C++Builder Projects | Intraweb
+ Delphi Projects | Intraweb
;+- C++Builder Projects J ActiveX
Рис. 1.1. Начало работы в среде C+ + Builder
Основу для создания нового проекта выбирают из набора стандартных проектов Borland Developer Studio 2006. Для доступа к заготовкам проектов служит команда File > New > Other (Файл > Создать > Прочее). В диалоговом окне New Items (Создать объект), показанном на рисунке 1.2, представлены категории проектов,
Рис. 1.2. Выбор типа проекта
ориентированных на разные языки программирования и технологии. В правой части окна приведены конкретные типы проектов из выбранной категории. Нам нужен раздел раздел C++Builder Projects (Проекты C++ Builder).
Создаем консольное приложение
На первом этапе знакомства с системой C++Builder создадим так называемое консольное приложение. Это особая форма программы для Windows, не содержащая специализированных окон и графических элементов управления. Для взаимодействия с пользователем используется текстовый интерфейс.
Выберем в диалоговом окне New Items (Создать объект) категорию C++Builder Projects (Проекты C++Builder) и дважды щелкнем на значке Console Application (Консольное приложение). Система C++Builder приступит к созданию нового проекта.
Рис. 1.3. Окно настройки характеристик консольного приложения
Откроется промежуточное окно настройки характеристик создаваемого приложения (рис. 1.3). В разделе Source Туре (Тип исходного приложения) выберем требуемый язык программирования C++.
Среда C++Builder позволяет создавать консольные приложения как на языке C++, так и на его предшественнике, языке Си, в котором отсутствует поддержка концепции объектов. Обычно возможность программирования на Си задействуется в учебных целях или при использовании ранее наработанного кода. В последнем случае можно задать исходные тексты программы в поле Specify project source (Выбрать исходные тексты проекта).
Флажок Use VCL позволяет подключить к проекту библиотеку визуальных компонентов VCL, которая требуется при создании полноценных графических приложений. Как правило, в консольных приложениях библиотека VCL применяется в исключительных случаях.
Флажок Multi Threaded во включенном состоянии приказывает компилятору генерировать код с поддержкой режима многопоточности (параллельного выполнения нескольких потоков команд в рамках одной программы). Программист должен самостоятельно создавать и определять каждый из потоков.
Флажок Console Application должен быть включен. Он определяет наличие консольного окна формируемого приложения.
Нажмем кнопку ОК — среда откроет новый проект и переключится в редактор исходных текстов (рис. 1.4).
Рис 1.4. Окно нового проекта консольного приложения
При создании нового проекта автоматически генерируется «пустая», но полностью работоспособная заготовка будущего приложения. Она представляет собой готовый код со всеми дополнительными ресурсами. От разработчика не
требуется усилий по настройке исходного текста — он может быть сразу преобразован в исполнимый код и запущен на выполнение. В исходный код создаваемой программы вставлены следующие строки:
//
#pragma hdrstop
//
#pragma argsused,
int main(int argc, char* argv[]) {
return 0;
}
//
Работа с проектом
Реальные программные проекты состоят из большого числа команд языка C++ — от нескольких тысяч до десятков миллионов. Создаются они не за один день, поэтому проект в паузах между работой необходимо сохранить. Это делают командой File > Save АН (Файл > Сохранить все). В ходе выполнения этой команды среда C++Builder предложит выбрать каталог, в который будут записаны файлы проекта. К файлам проекта относятся:
файлы с исходными текстами программы (расширение имени .СРР, от английского С Plus Plus, язык программирования C++, произносится как «си плас плас»);
файлы описания проектируемых форм и иных ресурсов проекта (расширения имени .DFM и .RES);
файлы описания самого проекта (головной файл с расширением .BDSPROJ, файлы настроек, конфигураций и так далее);
прочие файлы, используемые в проекте.
Каждый проект следует хранить в отдельном каталоге. Сохраним главный файл проекта в файле под названием MyProject.bdsproj. Впоследствии для загрузки проекта дайте команду File > Open Project (Файл > Открыть проект). В открывшемся диалоговом окне выберите головной файл проекта с расширением имени .BDSPROJ.
После компиляции в подкаталоге Debug_Build каталога проекта формируется исполнимый файл .EXE, имя которого совпадает с именем файла проекта.
По умолчанию файл получает имя MyProject.exe (по названию проекта и с расширением .EXE). Такой файл автоматически запускается средой C++Builder при тестировании созданного кода. Он будет полноценным приложением Win32. Исполнимый файл можно переименовывать, копировать и передавать другим лицам для использования.
Пока что эта программа не выполняет никаких функций, но даже консольное приложение с текстовым интерфейсом несложно наполнить полезным и интересным содержанием.
Запуск программы
Чтобы на базе автоматически сгенерированного исходного текста создать работоспособное приложение, достаточно дать команду Run > Run (Запуск > Выполнить) или нажать клавишу F9. Среда C++Builder вызовет подходящий компилятор, выполнит действия по сборке программы, на долю секунды покажет черный экран запущенного консольного приложения и снова вернется в прежнее состояние. Так произойдет потому, что консольная программа пока что не содержит команд, созданных разработчиком. Она не выполняет никаких действий и поэтому закрывается сразу после запуска.
Сборка приложения
Рассмотрим более подробно, как происходил процесс запуска программы. Нажатие клавиши F9 — это команда на запуск программы, создаваемой в рамках текущего проекта. При этом вначале автоматически вызывается компилятор. Он проверяет исходные тексты на отсутствие ошибок, транслирует их и создает исполнимый файл с расширением .EXE.
Сборка проекта (сборка приложения) — это процесс создания исполнимого файла .EXE из исходных текстов и других ресурсов проекта.
Полученный в результате сборки файл запускается средой C++Builder на выполнение. Это происходит автоматически, без вмешательства разработчика. Запущенная программа получает управление, и разработчик сразу может начать работу с ней.
Программа, запущенная из среды C++Builder, функционирует независимо. Можно, не закрывая окна консольного приложения, переключиться в среду C++Builder, которая находится в так называемом состоянии отладки — отслеживания работы запущенного приложения.
Отладка — это процесс изучения правильности работы программы, ее тестирования и устранения найденных ошибок.
Как только работа созданной программы завершится (например, пользователь ее закроет), система C++Builder вновь получит управление.
Процесс создания программы
При разработке программы в среде C++Builder многократно повторяются однотипные шаги.
С помощью редактора готовится исходный текст программы, реализующий нужную функциональную возможность.
Текст программы компилируется, и формируется промежуточная версия программы, исполнимый файл .EXE.
Промежуточная версия запускается.
Разработчик проверяет работу программы.
Разработчик возвращается в среду C++Builder, исправляет выявленные ошибки и продолжает вводить и модифицировать исходный код до реализации всех запланированных возможностей.
Принцип пошагового создания Программ сформулировал в 1971 году швейцарский профессор Никлаус Вирт (Niclaus Wirt), автор языка программирования Паскаль, предшественника языка Delphi, который реализован в среде Borland Developer Studio в виде системы Delphi.
Запись программы на языке C++
Программа на языке программирования C++ записывается с помощью набора символов в кодировке ANSI. Строчные и прописные буквы считаются разными. Так, слова Console, console и CONSOLE означают разные элементы.
Текст программы представляет собой последовательность предложений {операторов), разделенных символом; (точка с запятой). Каждое предложение представляет собой команду (программное действие), выражение (вычисление значения) или описание элемента программы, используемого в командах и выражениях.
К внутренним элементам предложений относятся: зарезервированные слова, идентификаторы, константы (числа, символы и строки), специальные символы (операторы и знаки пунктуации) и директивы. Для наглядности внутренние элементы предложения отделяют пробелами, пустыми строками и символами табуляции.
В тексте программы на языке C++ используются ключевые (зарезервированные, служебные) слова и пользовательские идентификаторы. Ключевые слова являются синтаксическими элементами языка C++. Их нельзя модифицировать или использовать в целях, отличных от заданных стандартом языка. К ключевым относятся, например, слова class, void, for, goto и еще несколько десятков других. Их нельзя использовать в качестве идентификаторов. В окне редактора C++ ключевые слова выделяются цветом и полужирным шрифтом.
Пользовательские идентификаторы применяются в качестве имен объектов (сущностей), используемых в программе. Идентификаторы не должны совпадать с ключевыми словами языка. Идентификаторами обозначают переменные величины, константы, функции, типы данных, свойства, поля, библиотеки, пакеты, ' модули и программы. Длина идентификатора может быть любой, но учитываются - толькопервые 250 его символов.
Идентификатор может содержать латинские буквы, цифры и символы подчеркивания _, нсГнедолжен начинаться с цифры. Запись идентификатора не должна разрываться знаками пробела или конца строки.
Структура программы
Изучим структуру консольной программы. В ее начале и середине имеются такие строки:
//
Этот текст является так называемым комментарием и не влияет на работу программы. Нередко комментарии генерируются средой C++Builder автоматически.
Комментарий в программе на языке C+ + представляет собой пояснительный текст. Комментарии всегда пропускаются компилятором.
Программист добавляет комментарии, чтобы сделать программу более наглядной. Обычно комментарии начинаются с двух символов //и весь последующий текст до конца строки считается комментарием. Допускаются также многострочные комментарии. Они заключаются в пары символов /* и */. Все, что записано внутри них, считается комментарием.
Первая команда
#pragma hdrstop
инструктирует компилятор о способе оптимального хранения содержимого так называемых заголовочных файлов (файлов, описывающих внешние подключаемые библиотеки программ). Пока в программе не используется ни один заголовочный файл.
Следующая команда
#pragma argsused
также является инструкцией компилятору, а не оператором языка C++. Она исключает выдачу сообщения о том, что заданные для функций параметры нигде не используются.
Инструкция
int main(int argc, char* argv[])
задает функцию (логически законченный набор исполняемых команд) с именем main. Ключевое слово int (сокращение от английского integer, целое) определяет тип значения, которое эта функция рассчитывает, а идентификаторы в круглых скобках за именем main описывают параметры этой функции при ее вызове на выполнение.
Программист обычно задает название функции самостоятельно. Но консольная программа на C++ обязательно должна включать функцию с именем main (). Компилятор автоматически считает такую функцию основной, и выполнение программы начинается с вызова функции main (). Поэтому в данном случае идентификатор main модифицировать нельзя.
Пара фигурных скобок { и } («начало» и «конец») ограничивает область программы, в пределах которой записываются выполняемые команды. Команды программы на языке C+ + всегда выполняются последовательно, одна за другой.
Внутри пары скобок {} уже имеется некоторый текст:
return 0;
Он представляет собой оператор return, который задает значение, возвращаемое функцией main() в процессе ее работы. В данном случае таким значением будет число 0.
Объявление переменных
При работе программы используемые ею данные хранятся в ячейках памяти компьютера. Для доступа к ячейкам памяти необходимо дать им имена, представленные идентификаторами. В программе для доступа к элементам данных используются переменные.
Переменная — это представление в языке программирования области памяти, отведенной для хранения значения. В программе к переменной обращаются по ее имени (идентификатору).
Значение, представленное в виде переменной, относится к определенному типу данных (или просто к определенному типу).
Тип данных определяет объем и структуру элемента данных, представленного в виде переменной.
Для хранения целочисленных значений используют переменные целого типа, для хранения строк — переменные текстового типа и так далее. Компилятор, встречая инструкцию по созданию переменной, отводит в оперативной памяти место для хранения значения соответствующего типа. Для доступа к этой области памяти используется имя переменной. Переменная должна быть явно декларирована до ее использования.
Объявление (декларация, описание) переменной — это инструкция компилятору о создании новой переменной заданного типа.
Объявление переменной записывают следующим образом: тип идентификатор ;
В качестве типа данных используется одно из зарезервированных слов языка C++. Слово int описывает переменные целого типа, слово float — дробного типа, слово char — символьного типа.
В одном описании можно объявить несколько идентификаторов, указываемых через запятую. Все переменные будут относиться к одному типу данных.
Приведем несколько примеров описания переменных. Переменная MyValue предназначена для хранения целочисленных значений:
int MyValue; -
Две переменные S1 и у 2 служат для хранения символов:
char Cl, С2;
; Инструкции объявления переменных могут располагаться практически в ■ любом месте исходного кода. Однако для наглядности и удобства все объявления лучше располагать в начале соответствующего раздела, например в начале описания функции.
Оператор присваивания
Важнейший оператор (команда) языка C++ — это оператор присваивания.
Оператор присваивания служит для задания или изменения значений переменных.
Записывается оператор присваивания следующим образом:
идентификатор = значение;
Идентификатор — это имя ранее описанной переменной, а значение представляет собой величину, тип которой соответствует типу переменной в левой части оператора:
int MyValue; // декларируем переменную MyValue
MyValue =5 ; //в переменную MyValue занесено // значение (число) 5
В правой части оператора присваивания разрешено использовать выражения языка C++. Результатом расчета выражения должно быть значение, тип которого соответствует типу переменной в левой части. При записи выражений используют общепринятые символы арифметических операций, числа и переменные:
int N, Zn5;
N = 1;
Zn5 = N + 2;
N = (Zn5 - 1) * 10;
N = N + 1;
Ввод и вывод
Дополним пустую заготовку консольного приложения первыми командами. Прикажем, чтобы программа после запуска остановилась и ожидала ввода пользователем некоторого числа и нажатия клавиши ENTER. Такое действие выполняется в несколько шагов.
Определяется переменная, в которую будет введено значение с клавиатуры.
В программе с помощью инструкции #include задается заголовочный файл iostream, h, который подключает набор стандартных функций ввода- вывода.
Задействуется стандартное устройство ввода с клавиатуры с in и оператор
>>, предназначенный для занесения введенного значения в переменную.
Результирующий программный код запишется так:
■// заголовочный файл
// библиотеки ввода-вывода
#include <iostream.h>
#pragma hdrstop
#pragma argsused
int main(int argc, char* argv[])
{
// декларация переменной п: int п ;
// считывание значения из cin -
// стандартного устройства ввода
// с клавиатуры, в переменную п: cin » п;
// завершение программы
return 0;.
}
Если теперь нажать клавишу F9, то консольное приложение запустится, но теперь уже не закроется, а останется на экране. Оно будет ожидать ввода человеком с клавиатуры целого числа. Лишь после нажатия клавиши ENTER приложение завершится и управление вернется обратно в среду C++Builder.
Команда вывода значений из программы на экран (в консольное окно программы, так называемое стандартное устройство вывода cout) записывается с помощью оператора «, противоположного по смыслу оператору »:
cout << п;
Следующая простая программа вводит с клавиатуры значение, которое записывается в переменную п. Затем программа выводит удвоенное значение с предварительным комментарием "Результат: ".
int main(int argc, char* argv[])
int n ;
cin » n;
cout << "Результат:";
cout << n*2;
cin » n;
return 0;
}
Предпоследняя команда cin»n нужна, чтобы показать пользователю финальный результат и дождаться нажатия им клавиши ENTER, иначе программа сразу после вывода результата закроется.
Данный код можно переписать и так:
int main(int argc, char* argv[])
{
int n ;
cin » n;
cout << "Результат: ";
n = n * 2;'
cout « n;
cin » n;
return 0;
}
Результирующее значение предварительно сохраняется в переменной п.
Понятия переменной и оператора присваивания считаются важнейшими в любых современных языках программирования — C++, С#, Delphi, Java и многих других.
Ошибки компиляции
Далеко не всегда сразу удается ввести в редакторе корректный текст. Нередко человек ошибается, и тогда компилятор подсказывает, какие ошибки выявлены в тексте программы. Например, если вместо слова с in записать несуществующий идентификатор cim и нажать клавишу F9, то среда C++Builder подсветит красным цветом строку с неверным словом.
Рис. 1.5. Автоматическое обнаружение ошибки в программе
В нижней части окна среды C++Builder появится панель Messages (Сообщения) с подробным описанием выявленной ошибки. В данном случае сообщение компилятора гласит: Undefined symbol 'cim' (Неопределенный идентификатор cim).
Вопросы
Каково назначение среды программирования C++Builder?
Что такое проект C++Builder?
Как формируется заготовка будущего приложения?
Как из исходного текста создается работающая программа?
Из каких элементов состоит исходный текст программы на языке C++?
Как записываются комментарии к исходным текстам?
Что такое идентификатор?
Что такое переменная?
Что такое тип данных?
Как декларируется переменная?
Как работает оператор присваивания?
Как функционируют команды ввода с клавиатуры и вывода на экран в консольной программе?
ГЛАВА 2 История развития языка программирования C++
Язык C++ был разработан ученым Бьерном Страуструпом (Bjarne Stroustrup) в начале 1980-х годов. Он представляет собой вариант уже существовавшего в то время языка Си, расширенного технологиями объектного программирования.
Структурное программирование
В языке C++ воплощены лучшие идеи структурного программирования, которое зародилось в середине 1960-х годов. Структурная методология разработки программного обеспечения была изобретена голландским ученым Эдсгером Дейкстрой (Edsger Dijkstra). Он впервые сосредоточил внимание на эффективных способах внутренней организации программы. В 1969 году в Риме прошла научная конференция НАТО по программированию. Дейкстра выступил на ней со знаменитым докладом «Структурное программирование». Он показал, что при программной реализации любого алгоритма можно обойтись без оператора перехода. Достаточно придерживаться последовательного выполнения частей программы. Сама программа создается методом «сверху-вниз*. Сначала в реализуемом алгоритме выделяются крупные модули. Их функционирование детализируется до более и более мелких элементов, но все они выполняются последовательно.
Структурное программирование — технология создания программ в виде иерархически связанных модулей, представляющих собой линейную последовательность операторов цикла, присваивания и условных операторов, а также вызовов вложенных модулей.
Концепция структурного программирования позволила полностью отказаться от применения оператора перехода goto, обусловила возможность автономного написания и отладки крупных модулей и стала необходимой предпосылкой для перехода к концепции объектно-ориентированного программирования.
Императивное программирование
Язык C++ считается объектно-ориентированным языком программирования, однако конкретные алгоритмы реализуются так называемым императивным подходом, так как синтаксис C++ основан на синтаксисе императивного языка Си.
Императивный язык — это язык программирования, инструкции которого предназначены для последовательного, пошагового изменения состояния вычислительного устройства.
Программа на C++ конструируется из сравнительно небольшого набора операторов: условного, присваивания, цикла. Они выполняются последовательно и работают с данными явно заданных типов. C++ считается языком со строгой типизацией — все определяемые в программе переменные принадлежат конкретным типам данных. Это позволяет избежать ошибок неверной обработки данных разных типов еще на этапе компиляции. Группы команд можно выделять в подпрограммы (функции и методы классов).
В императивном программировании основным синтаксическим элементом является оператор. Императивным оператором считается как простейший оператор (условный, присваивания, цикла), так и составной, объединяющий последовательность простейших операторов. Понятие составного оператора стало основой структурной методологии и проектирования программ по принципу «сверху вниз».
У императивного подхода имеется существенный недостаток. Он заключается в том, что сложность разработки крупных проектов на императивных языках растет быстрее, чем число операторов в программе. Понимание и модификация программы из сотен тысяч и миллионов операторов сильно затрудняется, а нередко становится невозможной.
Императивный подход не является единственно возможным. Другие подходы порождают особые категории языков программирования.
Объектные языки программирования ориентированы на манипуляции с объектами. К ним относятся, например, C++, Java, С#, современный вариант языка Delphi. Программирование в объектной модели происходит не столько кодированием мелких шагов, сколько организацией взаимодействия объектов. Для этого в объектно-ориентированных языках поддерживаются механизмы построения иерархии объектов, наследования их свойств, автоматического распознавания и приведения типов и так далее. Практически все современные и массово распространенные системы программирования ориентированы на объектную разработку.
Функциональные языки программирования опираются на теорию лямбда- исчислений. Они описывают алгоритм в терминах функциональных преобразований. Это, например, LISP и РЕФАЛ.
Логические языки программирования позволяют описать набор фактов и правил их обработки. Поиск результата выполняется автоматически системой логического вывода.
Язык программирования Си
Пожалуй, самым далеким предком C++ можно считать язык BCPL (Basic Combined Programming Language)1, полноценная реализация которого была соз
* В начале 1960-х годов существовала и более ранняя версия BCPL, которая называлась CPL.
дана в 1966 году британским ученым Мартином Ричардсом (Martin Richards) из Кэмбриджского университета для компьютеров IBM 7094 для системы CTSS. Синтаксически язык BCPL напоминал смесь Алгола и Бейсика, но именно в нем появились фигурные скобки как средство объединения группы операторов.
Язык BCPL первоначально предназначался для создания компиляторов и показал в этой области высокую эффективность. Так, некоторые созданные с его помощью высокопроизводительные компиляторы умещались в 16 килобайтах оперативной памяти. Высокие показатели производительности программ на BCPL стимулировали использование этого языка в системном программировании. На базе BCPL было написано несколько успешных операционных систем. В частности, он был первоначально выбран базовой системой, с помощью которой в лаборатории исследовательского центра Пало-Альто Xerox PARC развертывался проект по созданию первого персонального компьютера.
Оригинальной оказалась структура транслирующей системы самого языка BCPL. Ее исходный текст транслировался в промежуточный О-код (уже тогда буква О обозначала Объект) для виртуальной машины (программной эмуляции компьютера), который затем переводился в код конкретного процессора. Благодаря такому подходу система BCPL легко адаптировалась к разным платформам — достаточно было модифицировать лишь около 20% исходных текстов.
Ставшая благодаря BCPL популярной концепция трансляции в промежуточный код впоследствии была реализована в компиляторе языка Паскаль. Программа на этом языке тоже первоначально транслировалась в промежуточный P-код (Р — от английского pseudo, псевдокод). Но самым первым концепцию виртуальной машины и промежуточного, виртуального ассемблера, предложил Мартин Ричардс. Язык BCPL также стал первым языком программирования, на котором была написана знаменитая программа «Hello, World!» (вывод приветственной строки «Привет, Мир!»). Эту простую программу обычно пишут многие программисты во всем мире, когда начинают изучать новый язык программирования.
Такая особенность языка BCPL, как слабая ориентация на конкретных компьютерные архитектуры, являлась не только его сильной стороной, обеспечивающей независимость от платформы. Из типов данных язык поддерживал только «слово» (фиксированный набор битов), что затрудняло формирование высокопроизводительного машинного кода, а его языковые конструкции не очень эффективно переводились в команды существовавших в те годы компьютеров.
Язык BCPL, тем не менее, отлично проявил себя при создании операционных систем, и два специалиста из лаборатории Bell — Дэннис Ритчи (Dennis Ritchie) и Кен Томпсон (Ken Thompson), большой специалист по BCPL, трудившиеся над
операционной системой Unix, выбрали его в качестве основы для инструмента, способного существенно повысить эффективность деятельности по созданию Unix.
Авторы Си утверждают, что к созданию Си их побудили не столько прямые рабочие обязанности по совершенствованию Unix, сколько проблемы с производительностью популярной в те годы среди программистов игры Space Travel (Космическое путешествие). Она работала на мэйнфреймах с ограниченными ресурсами, которые объединяли в крупных сетевых сражениях до ста участников, нередко не справлялись с нагрузкой и начинали ошибаться при расчете шансов столкновений кораблей с астероидами. Для переноса игры Space Travel на машины PDP требовалось сначала создать версию Unix для PDP. Но система Unix была написана на ассемблере мэйнфрейма, и для создания переносимой версии операционной системы и игры пришлось изобретать новый язык программирования. Ритчи и Томпсон задействовали наработки проекта BCPL и создали на его основе в 1969 году язык Би. В нем воплотилось немало идей, впоследствии вошедших в язык Си, однако из типов данных по прежнему использовалось лишь компьютерное слово.
В 1960-е годы операционные системы целиком создавались на ассемблере, так как компьютеры работали медленно, объемы оперативной памяти были крайне ограничены, и программистам приходилось экономить каждый байт и каждый такт процессора. Ритчи и Томпсон, в частности, написали на ассемблере первые версии Unix для компьютеров DEC PDP 7 и PDP 11 и на собственном опыте поняли, что программирование на ассемблере — занятие медленное, трудоемкое и чреватое большим числом ошибок. Они быстро подготовили первые компиляторы Би для PDP 7/11 и сделали на Би прототип популярной программы уасс (генератор программ, разбирающих выражения различных формальных грамматик).
Язык Би менялся с появлением новых моделей компьютеров серии PDP, поддерживающих механизмы обработки разных типов данных. В начале 1970-х годов была создана версия New В (Новый Би) с возможностью обработки значений разных типов, и в конце 1972 года на свет появился компилятор нового языка с названием Си. В него, в частности, был добавлен препроцессор. Всего за полгода Ритчи и Томпсон переписали на Си ядро Unix для PDP 11. За следующий год на Си удалось реализовать еще несколько операционных систем для разных моделей ЭВМ. В ходе этой деятельности была выработана концепция переносимых пакетов и стандартных библиотек функций Си для ввода-вывода.
Язык Си отличался от всех известных на то время языков наподобие Паскаля, Фортрана и Алгола тем, что позволял строить очень компактные выражения, компилировавшиеся в эффективный код, не уступающий ассемблерному, и предоставлял разработчику гибкие механизмы прямого доступа к ресурсам операционной системы. Правда, такая сильная сторона Си, как гибкость, считается и его слабым местом: экономия на средствах контроля за доступом к оперативной памяти и преобразованию данных часто приводит к сложно выявляемым ошибкам на этапе работы программы.
Система Unix, особо популярная в учебных заведениях, вскоре была полностью перенесена на язык Си, в 1975 году была выпущена как коммерческий продукт IS/1 и вскоре завоевала всемирную популярность. Эта популярность сделала массово известным и язык Си. Немалый вклад в нее внесла книга «Язык программирования Си», которую написали в 1978 году Дэннис Ритчи и Брайан Керниган (Brian Kernighan), автор языка программирования AMPL, предназначенного для сложных математических вычислений. Описанная в ней версия Си стала фактическим стандартом этого языка. Она была названа K&R — по именам авторов, и многие современные компиляторы поддерживают ее наряду с рядом стандартных вариантов.
Язык программирования C++
В 1980 году ученый Техасского университета Бьерн Страуструп, одно время работавший с Ритчи и Томпсоном в лаборатории Bell, в ходе работы над своей диссертацией решил дополнить язык Си концепцией классов, убрав также из него ряд слабых конструкций, наиболее часто приводивших к ошибкам. Он назвал свой проект «Си с классами», а потом для большей выразительности задействовал в названии оператор увеличения значения ++. Схожую идею впоследствии использовали специалисты Microsoft, когда выбирали название для языка C# (символ # подразумевает комбинацию двух символов +, только расположенных не последовательно, а с наложением).
В 1983 году появился полноценный компилятор C++, который переводил код на C++ в текст на Си. В этом же году язык получил окончательное название C++, хорошо известное и по сей день. Через два года Страуструп выпустил классическую книгу «Язык программирования C++», в которой описал все возможности нового языка — абстрактные типы, классы, ссылки на объекты, наследование, полиморфизм, поддержку исключительных ситуаций, шаблоны и так далее. Объектные механизмы C++ упростили реализацию больших и сложных проектов, и этот язык быстро стал, пожалуй, самым популярным в мире среди профессиональных программистов. На популярность C++ существенное влияние оказало массовое распространение языка Си — программисты могли быстро переключиться с него на новый язык, поддерживающий объектную концепцию. При этом программы на C++ и Си обратно совместимы, то есть компилятор C++ почти всегда понимает исходный текст программы на Си и формирует на его основе работоспособное приложение. Поэтому переход с Си на C++ может происходить постепенно.
Среда программирования Turbo C/C++
Компания Borland выпустила собственную среду разработки Turbo С в 1987 году. Она основывалась на системе Wizard С, которую в одиночку написал программист Боб Джарвис (Bob Jarvis), впоследствии устроившийся на работу в Borland. Среда Turbo С характеризовалась самым быстрым в мире компилятором Си и позволяла создавать программы для операционной системы MS DOS. При этом интегрированная среда, редактор и компилятор умещались в 384 килобайтах one-
ративной памяти. В итоге в первый месяц было продано сто тысяч копий этой системы. Вскоре последовала версия Turbo С 1.5, дополненная большим числом примеров, а в 1989 году Borland выпустила продукт Turbo С 2.0, в который вошёл встроенный отладчик, компилятор ассемблера и средства создания графических приложений.
Версия Turbo C++ 1.0 появилась в 1990 году. Она поддерживала только операционную систему OS/2, но вскоре появилась версия Turbo C++ 1.01 для MS DOS. Входивший в неё компилятор был полностью переписан, а оригинальный компилятор языка Си корпорация Borland продала компании TopSpeed. Вскоре последовала промежуточная версия Borland C++ 2.0 для OS/2, в которой наметился переход от старой текстовой оболочки Turbo к новой, более совершенной и поддерживающей оконные графические интерфейсы. Версии компиляторов Borland с номером 2.x были умышленно пропущены, чтобы не возникло отставание от номеров версий Windows.
В 1991 году были выпущены две версии среды Turbo C++ 3.0 — одна с компилятором для MS DOS, другая с компилятором для Windows 3.0. Они также были объединены вместе в рамках одного продукта Borland C++ 3.0. Версия Turbo C++ 3.0 включила первый в мире компилятор для MS DOS, поддерживавший шаблоны C++, генерацию защищенного кода и возможность выбора целевого процессора. В версии Turbo C++ 3.0 для Windows появился графический интерфейс разработчика.
Через год поддержка версии операционной системы Windows 3.1 была реализована в версии Turbo C++ 3.1, которая существенно улучшилась в плане дизайна. В конце 1993 года появилась полностью переработанная среда Borland C++ 4.0, которая обладала полноценным графическим интерфейсом для Windows, хотя и не таким быстрым, как у линейки Turbo C++. В нее также вошла богатая библиотека средств поддержки графических интерфейсов Object Windows Library (OWL).
В 1995 году появилась последняя версия Turbo C++ 4.5, одновременно с которой в продажу поступила среда Borland C++ 4.5. Однако и ее время подходило к концу.
В 1996 году существенный успех завоевала среда визуального программирования Borland Delphi 2.0 с компилятором языка Delphi, расширенного Паскаля. Ее популярность оказалась столь высокой, что корпорация Borland перебросила ресурсы на создание ее варианта с компилятором C++.
Среда программирования C++Builder
Новая среда визуального программирования получила название Borland C++Builder, и ее первая версия появилась в 1997 году, одновременно с системой Borland C++ 5.0. Для последнего продукта вышли два небольших обновления, исправляющих мелкие недочеты, и на версии Borland C++ 5.02 завершилась серия компиляторов, начавшаяся в 1987 году с Turbo С 1.0.
Borland C++Builder 1.0 отличалась от семейства Borland C++ мощными средствами проектирования и программирования работы экранных форм. В нее вошли средства поддержки популярных СУБД, в том числе клиент-серверных.
Впоследствии выпуск среды C++Builder был согласован с выходом системы Delphi. В частности, версия C++Builder 2.0 была пропущена, а версия C++Builder 3.0 появилась в 1998 году, через полгода после выхода Delphi 3.0. И по сей день версии C++Builder выходят следом за Delphi с небольшой задержкой.
С тех пор версии среды программирования выходят почти каждый год. Версия C++Builder 3.0 обзавелась быстрым 32-разрядным компилятором, поддерживала программные интерфейсы появившейся в те годы операционной системы Windows 95, пополнилась улучшенными наборами компонентов для работы с базами данных и построения визуальных табличных элементов, связанных с СУБД. В нее также были введены компоненты, позволившие использовать веб-технологии на корпоративном уровне. Появилась возможность отладки библиотек DLL, создания шаблонов и пакетов компонентов. Добавились компоненты построения отчетов и анализа данных, средства оптимизации работы в редакторе.
Возможность создания распределенных приложений стала отличительной чертой версии C++Builder 4.0 от 1999 года. В нее были включены средства работы с компонентной архитектурой CORBA, высокопроизводительные драйверы СУБД, технологии стыковки с модулями Java, интерфейсы доступа к автономным и серверным приложениям Microsoft и поддержка интерфейсов Windows 98.
Стабильная, производительная и многофункциональная версия C++Builder 5.0 (2000 год) стала продуктом, который завоевал массовое признание программистов во всем мире. В нее был добавлен большой набор компонентов для создания приложений для Интернета и поддержки технологии XML. Среда C++Builder 5.0 позволяла настраивать и хранить несколько вариантов рабочих столов среды, в ней появились средства локализации. Доступ к СУБД из программ на C++ мог организовываться по новой технологии Microsoft ADO.
Версия C++Builder 6.0 (2002 год) отличалась развитой поддержкой веб-сервисов. Был реализован набор «тонких» компонентов dbExpress для быстрого доступа к СУБД.
В 2002 году корпорация Borland выпустила последнюю версию Delphi 7 для платформы Win32. В ней, в частности, появился компилятор языка Delphi для .NET, что отвечало стратегическому направлению развития линейки средств разработки Borland — ориентации на платформу .NET. По этой причине версия C++Builder на основе Delphi 7 уже не создавалась. В 2003 году был выпущен продукт C++BuilderX с принципиально новой оболочкой, взятой из системы Borland JBuilder (средство программирования на языке Java). Он ориентировался на нужды корпоративных разработчиков и реализацию крупных проектов, однако коммерческого успеха не снискал и был снят с производства. Между тем программисты со всего мира активно требовали полноценной поддержки языка C++ в продуктах Borland, и в 2004 году было официально объявлено о скором возобновлении линейки C++Builder по старой схеме «почти одновременно с Delphi». Через год появилась среда Borland Developer Studio 2006, в которую помимо сред программирования на языках Delphi (система Delphi) и C# (система C# Builder) вошел очередной C++Builder 2006.
Язык программирования Java
Практика показала, что существенным недостатком C++ явилась его независимость от платформы — этой особенностью языка его авторы как раз гордились. В типовую версию языка не входили стандартные библиотеки, и в ней не существовало универсальных средств расширения иерархии классов. В результате на рынке появилось множество несовместимых друг с другом наборов стандартных библиотек, которые охватывали самые разные сферы — от построения пользовательских интерфейсов до разнообразных математических расчетов. Как правило, подобные библиотеки отличались закрытой архитектурой, да и в самом языке концепция полиморфизма оказалась не продуманной до конца — все это существенно затрудняло эффективное использование и расширение стандартных пакетов. Попытки создавать библиотеки на базе механизма шаблонов также не принесли заметного успеха — шаблоны существенно ограничивали возможность стыковки классов из разных иерархий наследования. Невозможность отделения описаний классов от их реализации требовала поставки стандартных библиотек в исходных текстах, потому что любые изменения в реализации стандартных классов подразумевали полную перестройку всей библиотеки. Со временем стало ясно, что попытка избавиться от слабых сторон Си (излишней гибкости и сложности создания надежных программ) не дала особого эффекта, а вот эффективность программ на C++ стала существенно ниже. Однако языки Си и C++ столь популярны в мире и на них написано так много кода, что забвение им не грозит. История с возобновлением выпуска среды C++Builder — тому подтверждение. Кроме того, C++ послужил родоначальником еще двух языков программирования: Java и С#. На языках этой тройки (C++, Java и С#) сегодня реализуется 90% всех программных проектов в мире.
В декабре 1990 года компания Sun запустила проект Green, в рамках которого три ее сотрудника — Патрик Нотой (Patrick Naughton), Майк Шеридан (Mike Sheridan) и Джеймс Гослинг, разрабатывали новый язык с предварительным названием Oak, призванный унаследовать популярность и сильные стороны C++. Группа Green занималась проектированием первого персонального ассистента («наладонника») Star? (*7) на базе процессора SPARC. Проект Star? был успешно завершен осенью 1992 года. Основную роль в его успехе сыграла среда разработки Oak, которая впоследствии была переименована в Java. В ходе ее проектирования Гослинг проанализировал ряд существовавших языков программирования и решил основать новый язык на C++. Благодаря этому решению практически все основные операторы управления, инструкции декларации переменных и названия типов совпадают в Java с принятыми в C++. Значительная совместимость Java с синтаксисом Си и C++ существенно упрощает перенос написанных на C++ программ на новый язык. Разница между этими языками заключается в способах описания и использования классов и объектов и в организации общей структуры программы. Из языка Java исключен ряд сложных и потенциально опасных конструкций (указатели, множественное наследование, оператор перехода goto). Одной из принципиально новых элементов стали предложенные Гослингом интерфейсы — описания функциональных возможностей, отделенные от реализации.
Ключевым отличием технологии Java от существовавших систем разработки на C++ явилась концепция виртуальной машины и компиляции в байт-код (эту идею впервые реализовал Мартин Ричардс в системе BCPL). Программа на языке Java транслируется не в исполнимый код процессора, а в промежуточный виртуальный ассемблер — байт-код. Этот байт-код впоследствии исполняется (интерпретируется) виртуальной машиной — своеобразной операционной системой в миниатюре, которая берет на себя также роль эмулятора процессора. Таким образом создателям Java удалось добиться независимости исходных текстов и двоичного исполнимого кода от программных и аппаратных платформ. Достаточно создать версию виртуальной машины для конкретной платформы, после чего с ее помощью можно будет запускать любой существующий байт-код, который генерируется компилятором Java вне зависимости от целевой системы. Для технологии Java был придуман специальный лозунг «Пишется однажды, выполняется везде», призванный отразить ее главное преимущество — способность программ работать на разных платформах не только без модификации исходных текстов (совместимость на уровне исходных текстов), но и даже без перекомпиляции (совместимость на уровне байт-кода). Кроме того, в виртуальную машину Java встроены механизмы отслеживания ошибок и обеспечения безопасной работы, исключающие возможность прямого воздействия байт-кода на текущее содержимое компьютера. Контролируются, в частности, такие потенциально опасные возможности, как обращение к файловой системе или взаимодействие с объектами в сети. Встроенная в виртуальную машину служба сборки мусора автоматически оптимизирует использование оперативной памяти. В стандарт Java вошла спецификация JDBC — унифицированный способ взаимодействия программ Java с системами управления базами данных. Особую популярность Java завоевала при создании приложений для Интернета, корпоративных серверных модулей, независимых от платформы, и встраиваемых программ для мобильной техники.
Базовые комплекты Java распространяются свободно и бесплатно, а многие их составляющие — в исходных текстах. Поэтому в конце 1990-х годов, когда Java стремительно набрала популярность, корпорация Microsoft распознала в ней серьезного конкурента и поступила в соответствии со своей корпоративной стратегией. Она выпустила версию виртуальной машины Java для Windows, которая оказалась несовместима с оригинальной версией Sun и содержала ряд механизмов прямого взаимодействия с операционной системой Microsoft.
Язык программирования C++
Первой попыткой корпорации Microsoft создать собственный клон Java стал выпуск в 1996 году языка программирования Blackbird — в ответ на Hotjava. Новый язык сочетал характеристики языков C++ и Visual Basic и был ориентирован на создание приложений для Интернета, хотя результирующий код исполнялся только в Windows. Однако интереса он не вызвал, так как механически сочетал слабо совместимые подходы и унаследовал недостатки языков, не предназначенных для создания кросс-платформных сетевых приложений. Поэтому корпорация Microsoft переманила к себе Андерса Хейлсберга (Anders Hejlsberg) — веду-
щего специалиста компании Borland, главного конкурента империи Билла Гейтса на рынке систем программирования.
Андерс Хейлсберг известен как проектировщик архитектуры всех версий среды Turbo Pascal и первых трех версий одной из наиболее развитых сред визуальной разработки Delphi. Она впервые вышла на рынок в 1993 году и позиционировалась как «убийца Visual Basic». Обеспокоенная быстрым ростом популярности Delphi, корпорация Microsoft предложила Хейлсбергу оклад в несколько миллионов долларов в год, и уже в 1996 году он перешел к конкурентам, где ныне отвечает за развитие архитектуры .NET.
В 1998 году Хейлсберг возглавил проект по созданию на базе Си (а фактически, на базе Java) принципиально нового языка COOL (C-style Object Oriented Language), впоследствии переименованного в С#. Хейлсберг планировал сделать его первым компонентно-ориентированным языком в семействе C++. Из конкурирующей системы Java он позаимствовал немало объектных идей — интерфейсы, свойства, индексаторы, расширяемые библиотеки классов, встроенную поддержку исключений, развитые механизмы полиморфизма, технологию сборки мусора, компиляцию «на лету» (преобразование кода виртуального ассемблера в машинный код конкретного процессора непосредственно во время выполнения программы), выделение управляемого (безопасного) кода, и ряд других идей. Одновременно Хейлсберг активно включился в проект по созданию новой операционной платформы .NET, который впоследствии возглавил. Язык C# сегодня заложен в основу этой платформы. В результате получившийся язык оказался достаточно целостным — исходный текст, подготовленный на языке С#, не нуждается в дополнительных заголовочных файлах, что характерно для программ на C++.
Благодаря тесной интеграции компилятора С#, платформы .NET и системы Windows двоичный код скомпилированных программ на C# получается компактным и не нуждается в дополнительных внешних библиотеках (громоздкость кода характерна для программ, написанных на большинстве других популярных языков). Е1емаловажно, что синтаксически многие конструкции языка C# совместимы с конструкциями языков C++ и Java, что позволяет быстро переносить на новую платформу большие объемы наработанного исходного кода на любом из этих трех языков.
Turbo C++ возвращается
В 2006 году руководители корпорации Borland приняли решение разделить ее на две независимые компании. Одна из них, сохраняющая название Borland, должна сосредоточиться на развитии корпоративных продуктов, связанных с сопровождением жизненного цикла программного обеспечения, — управление требованиями к проекту и версиями файлов, организация групповой работы, средства моделирования и так далее. Другая компания нацелена исключительно на выпуск средств разработки. Именно в рамках этого подразделения в сентябре 2006 года вышла целая линейка продуктов Borland с хорошо знакомой программистам приставкой Turbo. Сайт turboexplorer.com предложил программистам всего мира версии популярных систем разработки под названием Turbo C++, Turbo С#, Turbo Delphi
для Win32 и Turbo Delphi для .NET. Бесплатные копии этих продуктов разрешается устанавливать только в одном экземпляре на один компьютер (не допускается одновременная установка двух систем линейки Turbo). Кроме того, разработчик не может расширять возможности Turbo-среды наработками третьих фирм (например, оригинальными наборами компонентов). Эти ограничения отсутствуют в коммерческих версиях продуктов Turbo, которые продаются по цене около 400 долларов.
Системы Turbo представляют собой несколько упрощенные версии визуальных сред, входящих в комплект Borland Developer Studio. Так, система C++Builder представлена в нем в виде оболочки Turbo C++. Несомненная выгода новой стратегии обновленной Borland — это общедоступность мощных многофункциональных компиляторов для студентов, непрофессиональных программистов и всех, кто интересуется вопросами разработки программ.
ГЛАВА 3
Простые типы данных
Язык программирования C++ поддерживает работу только с так называемыми типизированными данными. Любая обрабатываемая программой информация должна принадлежать к известному типу. Такой подход обеспечивает ряд преимуществ. Во-первых, несоответствия при обработке данных часто выявляются еще на этапе компиляции.
Во-вторых, удается заранее оценить размер оперативной памяти для хранения значений определенного типа, что существенно повышает скорость обработки данных программой во время ее работы.
Тип данных выступает в роли схемы, модели построения и структурирования информации, определяющей диапазон допустимых значений и возможные операции.
Тип определяет, каким образом на уровне физической реализации значение элемента данных складывается из отдельных битов, какой смысл оно несет и какие операции над ним допустимы.
В ряде задач невозможно обойтись без преобразования значений из одного типа в другой. Так, целое число при выводе на экран требуется преобразовать в строковое представление, а дробную величину нередко требуется округлить до целого значения. Кроме того, системные механизмы Windows нередко работают с универсальным представлением данных, когда любой документ или мультимедийный файл обрабатывается как двоичный объект, не имеющий специфических свойств. Поэтому в языке C++ введены механизмы гибкого преобразования типов и работы с нетипизированными данными.
В язык C++ введено множество типов, которые с точки зрения прикладного программиста можно разделить, прежде всего, на простые (говорят также: фундаментальные или базовые) и производные (говорят также: сложные, составные или структурированные) типы. Производные типы формируются путем комбинации других типов.
Простой тип данных определяет базовые значения, в которых нельзя выделить составляющие их элементы.
Производный тип данных содержит элементы, относящиеся к простым и другим сложным типам.
Оперативная память для хранения значений базовых типов выделяется на этапе компиляции. Поэтому имя любой переменной фундаментального типа можно связать с отведенной для нее областью памяти. Значения производных типов (прежде всего объекты) могут формироваться динамически, во время работы программы. Заранее не всегда известно, какой объем памяти они будут занимать, поэтому переменные таких типов хранят не сами объекты, а ссылки на них (указатели).
Фундаментальные типы данных
Фундаментальные типы данных определяют числовые значения. Целочисленные величины задаются типами int и char, дробные (вещественные) величины задаются типами float и double. Допускается модифицировать диапазоны значений этих величин с помощью ключевых слов long и short. Кроме того, целочисленные значения могут быть либо со знаком, либо без него (принимать только неотрицательные значения). Значения фундаментальных типов данных представлены в программе в виде констант.
Стандарт языка C++ не задает строго определенные диапазоны значений для конкретных типов языка. Поэтому в дополнение к стандартному набору типов в версию языка C++, поддерживаемого компилятором среды C++Builder, введен ряд дополнительных типов, диапазоны значений которых явно привязаны к числу двоичных разрядов ячеек оперативной памяти.
Числовые типы
Целочисленные типы задают целые числа различных диапазонов. Целочисленное значение записывается в языке C++ в виде последовательности цифр, перед которой могут стоять символы - (число считается отрицательным) или + (число считается положительным). Если никакого знака перед числом не указано, то оно считается положительным. Целочисленные типы дополнительно бывают знаковые (значения могут быть как положительными, так и отрицательными) и беззнаковые (значения могут быть только неотрицательными).
Тип Целое
Стандартным целым типом в языке C++ принят тип int, охватывающий диапазон значений от -2147483648 до +2147483647. Для хранения таких значений отводится 32 двоичных разряда.
Примеры констант типа int:
О
+ 5
12345
-999
Язык C++ допускает запись чисел не только в десятичном, но и в восьмеричном и шестнадцатеричном форматах. Число в восьмеричном формате всегда начинается с нуля в первой позиции. Соответственно, запрещена запись чисел в десятичном формате, которые начинаются с нуля (кроме нуля как отдельного числа).
Примеры констант типа int в восьмеричном формате:
012345
-0777
+ 010
При записи шестнадцатеричных величин разряды, соответствующие цифрам 10, 11, 12, 13, 14, 15 и 16, записываются соответственно символами a, b, с, d, е и f (или их прописными вариантами). Перед шестнадцатеричным числом надо дополнительно поставить префикс 0х или ох, например:
OxFFOOFF
-0xla2b3c4d
Знаковые и беззнаковые константы
Значения типа int могут представляться как в знаковом виде (со знаком + или -), так и в беззнаковом виде (как неотрицательные значения). В последнем случае положительный диапазон допустимых значений беззнакового типа увеличивается в два раза. Для различения знаковых и беззнаковых типов в языке C++ применяются ключевые слова signed (знаковое) и unsigned (беззнаковое). Тип int по умолчанию считается знаковым типом. Беззнаковый тип unsigned int охватывает диапазон целых значений от 0 до 4294967295. Беззнаковые константы отличаются от знаковых констант суффиксом: в конце беззнакового числа ставится буква и (или и).
Примеры беззнаковых констант:
98765U
0и
12000U
Длинные и короткие константы
Язык C++ позволяет расширять или сокращать диапазоны значений числовых типов. Тип с расширенным диапазоном значений называется «длинный» (например, длинное целое) и сопровождается ключевым словом long. Тип с укороченным диапазоном значений называется «короткий» и сопровождается ключевым словом short. Такая возможность введена в язык C++ с целью добиться независимости программ от конкретной реализации (в частности, от разрядности целого числа в конкретных марках компьютеров). Подразумевается, что ключевые слова long и short в названии типа могут применяться по нескольку раз, например: long long int. Подобным образом задаются дополнительно расширенные диапазоны значений.
Возможность многократного использования слов long или short связана с конкретной реализацией языка C++. В версии компилятора среды C++Builder тип long int охватывает такой же диапазон значений, как и int: от -2147483648 до +2147483647. Но укороченный целый тип short int представлен более кратким диапазоном: от -32768 до +32767.
Константы удлиненных типов записываются с суффиксом L (или 1):
1234567L
-111
Модификации типа int
В языке C++ существуют типы, полученные комбинацией базового типа int и различных его модификаторов (знаковые или беззнаковые, длинные или короткие типы). Список всех таких типов приведен в таблице. Если константа должна быть явно представлена в тексте программы как значение модифицированного типа, задействуется комбинация суффиксов и и ь (в произвольном порядке):
123UL
100001U •
Таблица 3.1. Список целочисленных типов
Название типа
Диапазон значений
Число двоичных разрядов
short int
-32768.. 32767
Знаковое 16 бит
unsigned short int
0.. 65535
Беззнаковое 16 бит
unsigned int
0.. 4294967295
Беззнаковое 32 бита
unsigned long int
0.. 4294967295
Беззнаковое 32 бита
long long int
-9223372036854775808.. 9223372036854775807
Знаковое 64 бита
Тип int считается в языке C++ типом по умолчанию. Ключевое слово int в названии типа можно опускать. Тип short соответствует типу short int, тип long — типу long int, тип unsigned — типу unsigned int.
Варианты типа int, зависящие от реализации
Тип int и его модификации (беззнаковые или удлиненные значения) считаются в языке C++ независимым от реализации. Заранее неизвестно, какие диапазоны значений этих типов будут поддерживаться тем или иным компилятором C++. Если создается программа, в которой надо ориентироваться на точное число двоичных разрядов, отводимых для хранения различных значений, лучше воспользоваться предопределенными типами, поддерживаемыми компилятором среды C++Builder. Эти типы называютсяint *, где вместо звездочки ставится одно из чисел 8, 16, 32 или 64 (число двоичных разрядов). Типint8 в версии компилятора C++Builder соответствует типу char, тип int 16 — типу short, тип int32 — типу int, типint64 — типу long long. Если требуется явно ввести константу одного из этих типов, надо задействовать суффикс формата i*, где вместо звездочки подставляется число разрядов.
Например:
100i8 — значение типаint 8 i0509i64 — значение типаint64 •
Тип Символьное
Тип char используется на практике для хранения символов и обработки строк. Однако формально этот тип относится к целочисленным типам и охватывает диапазон значений от -128 до 127. Разрешается при записи констант типа char
использовать не только числа, но и одиночный символ, который при этом берется в одиночные кавычки.
Примеры символьных констант:
-й-
' z '
' 5'
Символьная константа автоматически преобразуется компилятором в целое число. Ее типом по умолчанию считается int, а не char. Так принято потому, что большой диапазон значений типа int позволяет закодировать гораздо больше символов, нежели при использовании типа char. Однако если значение символьной константы укладывается в диапазон от 0 до 127, то она может быть представлена типом char.
Одиночный символ представляет значение типа int в 16-разрядной кодировке Unicode. Первые 128 значений (от 0 до 127) отведены для представления символов в стандартной кодировке ANSI.
Символы можно записывать не только в кодировке Unicode, но и в виде escape- последовательностей (последовательностей управляющих кодов). В таком случае символ записывается с префиксом \х и занимает более одного разряда:
'\x6321'
Кроме того, существует набор предопределенных и часто используемых esca- ре-последовательностей, которые записываются в формате
'\х'
где х —- некоторый символ. Например:
'\п' - символ новой строки
'\0' - символ с кодом О
Полный список таких последовательностей приведен в таблице 3.2.
Таблица 3.2. Стандартные escape-последовательности
Escape-последовательность
Назначение
\0
Нулевое значение
V
Одинарная кавычка
V
Двойная кавычка
\\
Символ обратной наклонной черты
\а
Звуковой сигнал
\Ь
Возврат назад (стереть один символ)
\Г
Новая страница
\п
Новая строка
V
Возврат каретки
\t
Горизонтальная табуляция
\v
Вертикальная табуляция
Расширенные символы
При представлении символьных констант некоторых национальных языков не удается обойтись стандартным диапазоном типа char (256 значений). Для представления констант можно использовать тип int, однако при этом теряется наглядность исходного кода. Кроме того, немало стандартных функций обработки строк ориентировано на взаимодействие с данными символьных типов.
В языке C++ версии C++Builder имеется тип wchar„t, который позволяет представлять расширенные символьные константы с помощью двухбайтовых величин (65536 значений). Он соответствует типу unsigned short. При записи констант типа wchar__t используется префикс ь:
L'A'
Ь'Й'
Константы типа wchar„t могут также представляться не одним символом, а их последовательностью, если для компьютерной записи некоторой буквы или иероглифа требуется несколько клавиатурных символов. В таком случае подобная последовательность с префиксом ь берется в двойные кавычки:
L"ABCD"
На хранение каждого символа в подобной последовательности отводится по два байта.
В дополнение к расширенным константам компилятор C++Builder поддерживает мультисимвольные константы, состоящие из нескольких символов (от двух до четырех). Они представляют собой значения типа int. Такие константы записываются в одинарных кавычках:
'1234'
'аб'
Тип Вещественное
Вещественные типы служат для представления дробных значений. Вещественное значение состоит из целой и дробной части, разделенной десятичным разделителем (символ «.»). Перед числом может ставиться знак + или -.
Вещественные (дробные, с плавающей запятой) значения представляются не абсолютно точно, а с определенной погрешностью.
В языке C++ имеются два вещественных типа: float и double. Типу float отвечают 32-разрядные значения одинарной точности, а типу double — 64-раз- рядные значения двойной точности.
Абсолютные значения типа float лежат в диапазоне от 3,4x1038 до 3,4х10+38 (с точностью 7 значащих цифр). Абсолютные значения типа double лежат в диапазоне от 1,7x10 308 до 1,7х10+308 (с точностью 15-16 значащих цифр).
Для различения чисел типа float используется суффикс f (или f). Например, число 3.14 имеет тип double, а число 3.14f — тип float.
При записи вещественных чисел может использоваться символ е (или в), который дополняет число десятичной экспонентой. За этим символом задается десятичная степень, на которую умножается (или делится, если степень отрицательна) значение до символа е.
Например:
lei — число 10
100е-2 — число 1
3.14Е+5 — число 314000
В C++ существует длинный вещественный тип long double. Он охватывает диапазон от 3,4x10 4932 до 1,1х10+4932. В качестве суффикса при записи значений этого типа задействуется символ L (или 1).
Допускается запись дробных констант, у которых часть до или после десятичного разделителя опущена. В таком случае она считается равной нулю.
Например:
.5 // 0,5
10. // 10.0
-.123 // -0.123
Тип Валютное
В компиляторе среды C++Builder реализована поддержка дополнительного вещественного типа Currency. Он представляет собой 64-разрядное значение и охватывает диапазон от -922337203685477,5808 до +922337203685477,5807. Этот тип обычно используется в задачах автоматизации финансовой деятельности, так как позволяет выполнять операции над денежными величинами с высокой точностью, минимальной погрешностью и без потери значащих цифр.
Битовые поля
Язык C++ допускает явное определение числа битов, отводимых для хранения значения некоторого типа. Для этого следом за идентификатором в инструкции декларации через двоеточие вводится целая константа (или константное выражение) типа int. Эта константа задает число битов, выделяемых для данной переменной.
Битовое поле — это область памяти, отводимая для хранения значений, длина которой задается в битах.
Например:
int п : 15;
char log : 1;
Переменной (битовому полю) п выделено 15 битов, что существенно ограничивает диапазон ее значений. Переменной log выделен всего один бит. Это позволяет использовать log лишь в качестве логического значения.
Число битов может превосходить физический размер переменной. Например: char с : 2 0;
По умолчанию символьные значения хранятся в одном байте (восьми битах). Так как задано большее число битов (20), компилятор действует по следующему
алгоритму: выделяет переменной число битов, равное заданному минус число битов, отводимое на исходный тип по умолчанию. Следом за этим пространством в памяти формируется область, равная длине исходного типа. В целях оптимизации она будет использоваться для реальной обработки значения. В данном примере компилятор сначала выделит в памяти 20-8 = 12 битов, за которыми разместит восьмибитовый участок для хранения значения переменной с. Кроме того, компилятор может формировать между битовыми полями и областями памяти для обычных переменных промежуточные зоны, необходимые для выравнивания ячеек памяти по границе машинного слова.
Битовые поля поддерживаются в C++ из соображений совместимости с языком Си. В 1970-е годы объем оперативной памяти был крайне ограничен, и программистам приходилось вести учет каждому доступному биту. В современных компьютерных архитектурах битовые поля лишь ухудшают производительность и делают программу зависимой от способов организации данных в памяти, специфичных для конкретных компьютерных архитектур.
Тип Логическое
Логический тип (bool) представляет всего два возможных значения: true (истина) и false (ложь). Значение false соответствует целочисленному значению 0 (тип int), а значение true -- целочисленному значению 1. В общем случае значением true полагается любое ненулевое значение.
Тип Строковое
Строковый тип задает последовательность, состоящую из символов (значений типа char). Более подробно данный тип рассматривается в главе 5 (в разделе, посвященном массивам).
Перечислимые типы
Перечислимые типы формально относятся к фундаментальным типам данных. Однако диапазон значений таких типов не предопределен заранее, а задается программистом, поэтому перечислимые типы нередко классифицируются как производные, формируемые разработчиком (см. главу 5). Но способ их использования наиболее близок к способу работы с фундаментальными типами, поэтому перечислимые типы рассматриваются в данной главе.
Перечислимый тип — это пользовательский тип данных, для которого явно задан допустимый перечень значений.
Перечислимый тип задается в прикладной программе с помощью ключевого слова enum:
enum название-типа { перечень-значений } перечень-переменных;
Имя типа в записи перечислимого типа, а также перечень переменных можно опускать.
Например:
enum { Cat, Dog, Hamster };
Таким образом определяется тип данных animal. Экземплярами этого типа будут допустимые значения Cat, Dog или Hamster.
Считается, что каждому значению любого перечислимого типа соответствует некоторое целочисленное значение. Целым значением первой константы перечислимого типа считается ноль, а каждое последующее значение больше предыдущего на единицу. По умолчанию в качестве основного типа таких целочисленных значений задействован тип int.
Для любого из значений перечислимого типа можно явно задать собственное целочисленное значение. Оно вводится следом за символом = после названия элемента:
enum animal { Cat = 12, Dog = 27, Hamster } ;
В подобной записи допускаются дублирующие значения.
Если для элемента не введено собственное оригинальное целое значение, то оно считается на единицу большим, чем у предшественника. Так, элемент Hamster получит целое значение 28.
После того как перечислимый тип в программе определен, его значения используются как значения любых других предопределенных типов. С помощью названия типа можно вводить новые переменные. Они определяются так же, как и переменные других существующих типов.
Например:
animal MyCat;
Переменная MyCat имеет тип animal и может принимать значения, заданные при его определении:
MyCat = Cat;
MyCat - Hamster;
В случае, когда тип, задаваемый после ключевого слова enum, опускается, анонимная конструкция задействуется для прямой декларации переменных. Например:
enum animal { Cat, Dog, Hamster } MyCat;
Переменная MyCat декларирована как анонимный перечислимый тип и может принимать значения только из явно заданного для нее диапазона.
Преобразование типов данных
В ходе расчета значений в выражениях C++ могут появляться величины разных типов. Однако итоговый результат выражения должен соответствовать явно заданному или подразумеваемому программистом типу. В языке C++ существуют правила, в соответствии с которыми значения автоматически преобразуются между типами. Кроме того, разработчик может явно задавать целевой тип преобразования.
Явное преобразование типов
В ряде случаев требуется гарантировать, что результат вычисления некоторого выражения принадлежит конкретному типу. Например, расчеты проводятся над
значениями типа long, а результат должен укладываться в диапазон, допустимый для типа short. Для этого задействуются операции преобразования (или приведения) типов.
Преобразование (или приведение) типов — это преобразование значения из одного типа в другой.
Основная форма преобразования типа значения записывается так:
(целовой-тип-данных) исходное-значение
Результат, возвращаемый таким выражением, гарантированно будет принадлежать целевому типу данных. Например, приведение
(int)N
гарантирует, что значение переменной n будет укладываться в диапазон целых чисел (тип int). В качестве исходного значения может быть использовано выражение, например:
(int)(N+5)
В таком случае к типу int преобразуется результат вычисления выражения (N+5) .
Неявное преобразование типов
Программист не всегда может уследить за потенциально сомнительными вычислениями, где могут возникать проблемы с диапазоном результата. Например, умножение двух чисел типа char может в результате дать значение, выходящее за диапазон этого целочисленного типа. В ходе работы программы подобные ошибки автоматически обычно не отслеживаются. В таких случаях значение сначала преобразовывается в величину типа int, а если этот тип все равно не обеспечивает нужный диапазон, выполняется преобразование в тип unsigned int. При этом поддерживается стандартное правило сохранения оригинального значения — при преобразовании типа само значение не должно меняться. Однако это правило не распространяется на знак числа. В связи с этим, хотя двоичное представление значения при преобразовании типа не меняется, фактическое значение, используемое в программе, может в результате преобразования стать другим. Например:
char i = - 5;
unsigned char j; j = i;
В переменную j запишется число 251, являющееся беззнаковым представлением отрицательного числа -5. Аналогично выполняется и обратное преобразование — из беззнаковых в знаковые числа.
Значения перечислимых типов приводятся к целочисленным значениям типа int, если они укладываются в соответствующий диапазон, и к типу long в противном случае.
Преобразование с потерей значащих цифр
Некоторые преобразования, как явные, так и неявные, могут приводить к потере значащих цифр исходной величины. Например, приведение (unsigned char) 257 преобразует число 257 к значению, укладывающемуся в диапазон, отведенный для типа unsigned char (от 0 до 255). При этом возможна потеря значащих цифр, если исходное значение не укладывается в допустимый диапазон. Подобные преобразования с потерей значащих цифр нередки, когда значение переменной одного типа заносится в переменную с типом меньшего диапазона, например:
int п = 3 0 0 ;'
char b = п;
В результате в переменную ь занесется значение 44, но ни компилятор, ни работающая программа не выдадут никаких сообщений об ошибке.
Процесс преобразования происходит так: берется остаток от деления приводимого значения на максимально допустимое значение целевого типа плюс один, и прибавляется к нижней границе типа, а результат сложения становится результатом преобразования. Например, значение 257, приведенное к типу unsigned char, станет числом 1, так как максимально допустимым значением типа unsigned char является 255:
257 : (255+1) = 1; 0 (нижняя граница типа unsigned char) +1=1
Ответственность за отслеживание и корректность таких преобразований возлагается на программиста. Нередко ошибки возникают, когда переменная типа с ограниченным диапазоном задействуется в качестве счетчика. Например, переменная описана как имеющая тип char, после чего к ней многократно применяется оператор увеличения значения ++.
После того, как переменная достигнет значения +127, следующее «увеличение» модифицирует ее значение на -128, причем никаких сообщений об ошибке не появится.
Преобразования вещественных значений
Взаимные преобразования типов float и double выполняются в языке C++ корректно и безопасно. Значения этих типов представляются приближенно, с некоторой предопределенной точностью, поэтому не возникает конфликтов в ходе подобного приведения. Но так как каждый тип подразумевает максимально возможный порог представления значений, при преобразовании из типов с большим диапазоном к типу с меньшим диапазоном (например, из float к double) возможны прямые конфликты. Однако далеко не всегда они выявляются компилятором или обозначаются во время работы программы, что нередко приводит к трудновы- являемым ошибкам.
Например:
float е = 1е+50;
Максимально допустимое значение типа float — 1038, поэтому значение 1е+50 не может быть корректно представлено в этом диапазоне. В переменную е запи-
шется условная величина, обозначающая бесконечность. Однако пользователь об этом факте не будет извещен6.
Вещественные значения преобразуются к целым значениям отбрасыванием дробной части числа. В ходе преобразования исходное значение приводится к типу long и из типа long — к конечному.
Например:
int п ;
double d;
d = 3.9;
n = d;
// в переменную n занесено целое значение 3
d = -5.5;
n = d;
// в переменную п занесено целое значение -5
Преобразование целых чисел в вещественные выполняется без каких-либо потенциальных конфликтов. Единственным исключением считается преобразование из типа unsigned long в тип float, когда знаковый бит нередко не учитывается, и результирующее значение отличается от исходного. Кроме того, если целое значение насчитывает большое число разрядов, которые не могут быть полностью представлены вещественным значением, возможна потеря младших разрядов и округление до чисел с меньшей точностью значащих разрядов.
Арифметические преобразования
Наибольший процент ошибок, связанных с некорректным преобразованием типов, вносят выражения, в которых используются значения разных типов. В стандарте языка C++ имеется набор правил, который помогает оценить возможный ход преобразования типов. Они применяются к выражению последовательно, начиная с первого. Любое последующее правило применяется к текущему контексту, только если не подходят все предыдущие правила.
Если один из операндов имеет тип long double, то другой операнд также преобразуется к этому типу.
Если один из операндов имеет тип double, а тип другого неясен (подразумевается, что он относится к целочисленному типу), то другой операнд также преобразуется к этому типу.
Если один из операндов имеет тип float, а тип другого неясен, то другой операнд также преобразуется к этому типу.
Если один из операндов имеет тип unsigned long, то другой операнд также . преобразуется к этому типу.
Если один из операндов имеет тип long, а другой — unsigned int, то оба операнда преобразуются к типу unsigned long.
Если один из операндов имеет тип long, то другой операнд также преобразуется к этому типу.
6 В отладочном режиме разработчик видит бесконечные значения как +INF или -INF, положительная или отрицательная бесконечность.
Если один из операндов имеет тип unsigned int, то другой операнд также преобразуется к этому типу.
Если ни одно из вышеприведенных правил не применилось, оба операнда преобразуются к типу int.
Вопросы
Что такое ти п данных?
Какие типы относятся к фундаментальным типам C++?
Как записываются значения типа int?
Как записываются значения типа double?
Как записываются значения типа char?
Как записываются значения типа bool?
Что такое битовые поля и в чем их недостатки?
Как формируются значения перечислимых типов?
Что такое приведение типов?
Как выполняется преобразование типов данных?
В чем отличие явного преобразования типов от неявного?
В каких случаях преобразований возможна потеря значащих разрядов числа?
Каков порядок арифметических преобразований типов?
ГЛАВА 4
Константы, переменные, выражения
В ходе работы программа постоянно имеет дело с элементами данных. Обрабатываемые данные могут быть представлены константами, переменными, выражениями. Сам процесс такой обработки обычно выполняется с помощью оператора присваивания.
Константы
Используемые значения, которые не изменяются и не могут быть изменены в ходе работы программы, называют константами. Тип данных константы определяется контекстом, в котором она использована. Если выражение арифметическое, в нем применяют числовые константы (конкретные числа). Если выражение обрабатывает строки, то в нем задействуют строковые константы (последовательности символов).
Константа — явно записанное значение, относящееся к определенному типу данных языка C+ + и допустимое к применению в выражениях.
Если выражение арифметическое, то в нем могут применяться числовые константы (конкретные числа или символы, транслируемые в числовое представление). Если выражение обрабатывает строки, то в нем задействуются строковые константы (последовательности символов).
Именованные и неименованные константы
Используемые константы могут быть именованными и неименованными.
Неименованная константа — это значение, представленное в программном коде в точном соответствии со своей величиной.
Именованная константа —- это константа, которая представлена именем, ассоциированным с неименованной константой.
Например, число 123, записанное в программе как 123, будет неименованной константой. Неименованные константы не требуют предварительного объявления. Именованная константа должна быть декларирована, после чего в программе далее используется уже не значение константы, а ее имя. Значение именованной константы нельзя изменить.
Свойство неизменяемости констант позволяет определить часто применяемые в программе значения в одном месте. Эти значения размещают в исходном тексте в виде именованных констант. В этой роли могут выступать физические величины, активно и многократно используемые в расчетах, размеры графических элементов оформления, постоянно применяемые в приложениях с настраиваемым пользовательским интерфейсом, и другие группы значений.
Практика многократной записи одних и тех же значений в виде неименованных констант плоха тем, что на этапе разработки программы неоднократно возникает необходимость изменения этих значений. Например, положение элементов управления может настраиваться подбором, и каждый раз значения координат и размеров объектов требуется изменить в исходном коде программы. Если такие значения используются в нескольких местах программы, необходимо модифицировать все команды, содержащие эти константы. Если одна и та же величина применяется в десятках и сотнях команд, то процесс такого редактирования будет долог и трудоемок. Существует опасность, что какой-то из операторов будет упущен и не исправлен. При использовании именованных констант эти проблемы снимаются. Нужное значение именовано, и в дальнейшем используется лишь его имя. Достаточно модифицировать одну строку кода с описанием константы.
Запись константы
Неименованные константы (значения) в языке C++ бывают числовые (например, 123 или 3.14) и строковые (например, «Это строка», «12345»). Строковые константы — это последовательности символов, заключенные в двойные кавычки.
Именованные константы в языке C++ могут быть типизированные и нетипи- зированные.
Типизированная константа — это именованная константа, тип значения которой задан явно.
Нетипизированная константа — это именованная константа, тип значения которой не указан и определяется автоматически.
Типизированная константа записывается так:
const тип идентификатор = значение ;
Например:
const int MyNum = 1000;
const char CH = ' Б' ;
Нетипизированная константа записывается так:
const идентификатор = значение ;
Тип константы определяется по ее значению. Например:
const MyNum = 1000; // тип MyNum - int
const СН = 'Б' ; // тип СН - int const F = 3.14; // тип СН - double
Переменные
Программа работает с данными, которые хранятся в оперативной памяти. Используемые ячейки памяти представлены в программе как переменные. Понятие переменной — ключевое в любом языке программирования.
Понятие переменной
В языке программирования переменные различаются по именам. Именно имя переменной служит для доступа к данным.
Переменная — это именованная область (ячейка) оперативной памяти компьютера, содержимое которой может меняться на протяжении работы программы.
Для доступа к именованной области памяти, для записи и считывания ее значения применяется имя — идентификатор, допустимый правилами языка C++.
Каждая переменная относится к определенному типу данных. Это означает, что в переменной может храниться только значение заданного в ее описании типа.
Объявление переменной
Переменная перед использованием должна быть в программе объявлена (еще говорят — определена, описана, декларирована). Объявление переменной выполняется с помощью следующей записи:
тип идентификатор;
Идентификатор — это имя переменной, которое в дальнейшем используется в программе. Тип — это тип хранимого в переменной значения. Например:
int Sum;
double number;
В результате в программе будут описаны переменные с именами sum и number. К их содержимому можно обращаться по этим именам.
Если требуется объявить несколько переменных одного типа, их названия перечисляются через запятую:
double х, Y, N;
char Str, Name;
В инструкции декларирования переменную можно проинициализировать — присвоить ей начальное значение3.
Для этого задействуется расширенная форма инструкции описания: тип идентификатор = константа ;
Значение константы заносится в объявляемую переменную на этапе компиляции.
3 Переменные инициализируются нулевым значением по умолчанию.
Например:
int N = 5;
Переменная n сразу получает начальное значение 5.
Регистровые переменные
Когда создавался язык Си, разница в быстродействии команд процессора, обрабатывающих значения в регистрах и в оперативной памяти, была весьма большой. Эта разница существует и в современных процессорах, однако на общем фоне высокой производительности уже далеко не так актуальна, как в 1970-е годы. В язык Си был введен дополнительный модификатор типа, называемый register, который предлагает компилятору постоянно хранить значение некоторой переменной только в регистре. Такой подход ранее действительно давал значительный выигрыш в производительности. Однако число регистров процессора обычно невелико (один-два десятка), и далеко не все регистры постоянно доступны для хранения значений переменных — они используются прежде всего в основных расчетах и высвобождаются лишь время от времени. Использование регистровых переменных в современных программах уже не оправданно: компилятор с включенным режимом оптимизации автоматически сгенерирует эффективный код и сам задействует регистры, если это возможно.
Возможность объявления регистровых переменных поддерживается в языке C++ с целью совместимости с языком Си. Формат объявления регистровой переменной такой:
register тип идентификатор ;
Например:
register int х;
register char* pChar;
He гарантируется, что регистровые переменные обязательно будут реализованы в регистрах процессора, а не в оперативной памяти.
Комментарии
Программа на языке C++ выглядит значительно нагляднее, если ее текст сопровождается комментариями разработчика. Рекомендуется всегда комментировать исходные тексты. Назначение конкретных операторов в объемном коде довольно быстро забывается, и комментарии оказываются подчас единственным средством вспомнить смысл команд.
Комментарий — это часть исходного текста, которая не влияет на расчеты и применяется только для повышения наглядности.
В тексте комментария можно использовать любые символы. Компилятор пропускает любые комментарии в исходном тексте программы и никак их не анализирует.
Комментарии в языке C++ бывают двух типов: многострочные и однострочные.
Многострочный комментарий может занимать несколько строк.
Однострочный комментарий всегда умещается в одной строке.
Многострочные комментарии
Многострочные комментарии заключаются в пары символов: открывающую /* и закрывающую */. Между таких скобок комментарий может занимать несколько строк.
/* Это начало комментария.
Еще строка комментария.
*/
Однострочные комментарии
Однострочный комментарий начинается с пары символов //. Весь текст, следующий за такой парой символов, считается комментарием, но только до конца текущей строки. Закрывать однострочный комментарий не требуется.
double Speed; // скорость автомобиля:
В среде C++Builder любую часть кода можно быстро превратить в комментарий. Для этого нужную часть надо выделить в редакторе и нажать комбинацию клавиш CTRL+/. В результате в начало каждой выделенной строки добавятся символы комментария //.
Оператор присваивания
Один из важнейших операторов языка C++ и любого другого языка программирования — это оператор присваивания.
Оператор присваивания — это оператор языка C++, который заносит в переменную новое значение.
Оператор присваивания записывается в следующей форме:
переменная = значение;
Символ =, разделяющий правую и левую части оператора, рассматривается как знак присваивания4.
В левой части оператора присваивания стоит идентификатор — имя декларированной к данному моменту переменной. В правой части записано выражение. Результат его вычисления записывается в переменную, указанную в левой части. Старое значение этой переменной теряется. Тип значения выражения должен соответствовать типу переменной в левой части оператора. В конце оператора присваивания ставится точка с запятой5.
Формально оператором присваивания считается только символ =. Но обычно всю Команду языка с этим оператором называют оператор присваивания. Термином «оператор» в стандарте языка C++ также называются арифметические операции. В частности, символ сложения + называется оператор сложения.
Точка с запятой ставится в конце каждого оператора языка C++, за единичными исключениями, которые оговариваются особо.
Например:
int X, Y;
X = 1; Н в переменную X записано значение 1
Y = 2; //в переменную Y записано значение 2
// в переменную X записывается число 5:
X = Y + 3;
// 5 - это значение выражения Y + 3 (2+3=5)
double R;,
R = 3.14 * 2.72;
// в переменную R запишется дробное значение 8,54
При выполнении оператора присваивания сначала всегда рассчитывается значение правой части. Только потом это значение заносится в переменную левой части. Пример:
X = 5;
// значение переменной X увеличивается на 1
// и становится равным 6:
X = X + 1;
Преобразование типов в операторе присваивания
Тип выражения правой части оператора присваивания не обязательно должен точно соответствовать типу переменной в левой части. Если проводятся числовые расчеты и тип результата укладывается в диапазон типа переменной левой части, то выполняется так называемое расширяющее преобразование типа. Результат расчета автоматически приводится к типу переменной.
Например:
short Ь = 5;
int I = b;
Переменная I имеет тип int, и ей присваивается значение переменной Ь типа short int. Так как диапазон значений типа short int укладывается в диапазон значений типа int, преобразование выполняется автоматически.
Другой пример:
int п = 1000;
double х = п;
Значение типа int также корректно преобразуется в значение типа double, у которого больший диапазон значений.
Составной оператор присваивания
В расчетах часто используется следующая форма записи:
переменная = переменная операция выражение;
Например:
i = i + 1;
х = х * 2;
Если имя переменной в левой и начале правой части такого оператора одно и то же, то запись подобного оператора можно сократить:
переменная операция= выражение;
Например:
i + = 1;
х *= 2;
Составной оператор присваивания — это сокращенная форма оператора присваивания, в которой имя переменной из левой части по умолчанию задействовано и в выражении правой части.
В языке C++ определено 10 составных арифметических операторов присваивания: += (сложение), -= (вычитание), *= (умножение), /= (деление), %= (остаток отделения), <<= (сдвиг влево), »= (сдвиг вправо), &= (побитовое И), ~= (побитовое исключающее ИЛИ), I = (побитовое ИЛИ)6.
Выражения
Последовательность действий, используемых для вычисления значений, представлена в виде выражения. В записи выражений используются константы и переменные, а также допустимые в языке C++ операции (например, арифметические). Для управления последовательностью вычислений в выражениях применяют круглые скобки.
Выражение — это синтаксически корректная последовательность констант, переменных, вызовов функций, скобок и допустимых над ними операторов.
Оператор в выражении — это предопределенная форма обозначения действий, выполняемых над константами и переменными.
Набор допустимых операторов задается конкретными типами значений, участвующих в вычислениях.
Операторы для целых и дробных значений
Операторы языка C++ над значениями целых и дробных типов приведены в таблице 4.1.
Знак «-» можно поставить перед значением для изменения знака числа. Знак «+» также можно ставить перед числом. Никакого влияния на значение он не оказывает и применяется лишь для наглядности.
Например:
-1
+ 5
2+2
* 2.78
2 + (12 * 4 - '1.5) / 5
10 % 3
6 Составные операторы присваивания выполняются быстрее, нежели обычные операторы присваивания.
Таблица 4.1. Арифметические операторы
Запись арифметического оператора
Смысл оператора
+
Сложение
-
Вычитание
*
Умножение
/
Деление. Если делятся целые числа, то результат усекается до ближайшего целого
%
Целый остаток от деления. Операция допустима только для целых значений
В языке C++ также имеются операторы ++ (увеличение значения на единицу) и — (уменьшение значения на единицу), которые применимы только к одному операнду. Этот операнд должен быть переменной. Например:
х = 4; II -в переменную х записано число 4
// значение переменной х увеличено на 1 и равно 5
При использовании операторов ++ и - - имеет значение их расположение относительно имени модифицируемой переменной. Если такой оператор расположен справа от имени переменной, то сначала запоминается значение этой переменной. Оно будет формальным результатом работы данного оператора, и лишь потом содержимое переменной изменяется. Если же такой оператор расположен слева от имени переменной, то сначала модифицируется значение этой переменной, и лишь потом оно становится формальным результатом работы данного оператора.
Например:
X = 26;
У = х+ + ;
При расчете значения переменной у сначала будет взято значение 26 переменной х (так как оператор ++ расположен справа от ее имени), затем значение переменной х изменится на 27, а потом величина 26 запишется в переменную у.
Другой пример:
X = 26;
у = ++х;
При расчете значения переменной у сначала значение переменной х будет увеличено до 27 (так как оператор ++ расположен слева от ее имени), и потом это значение запишется в переменную у.
Использование операторов ++ и в выражениях имеет побочный эффект; дополнительную модификацию значения переменной, расположенной в правой части оператора присваивания. Момент такой модификации далеко не всегда очевиден и может иметь непредвиденные последствия, например:
у = х+ + * ++х;
Каким будет значение переменной у, сразу определить сложно. Поэтому желательно не злоупотреблять операторами ++ и -- в выражениях, а использовать их как автономные операторы «присваивания» (модификации значения переменной):
Операторы логической обработки
Операторы языка C++, выполняющие логический анализ значений, можно применять как к данным логического типа, так и к арифметическим величинам.
Логические операторы, обрабатывающие арифметические значения
Операторы языка C++ для логической обработки выполняют логический анализ значений числовых операндов. Они часто применяются в условных операторах, выполняющих те или иные действия в зависимости от условия. В качестве такого условия выступает логическое выражение. Оно формируется с помощью логических операторов && (логическое И) и I I (логическое ИЛИ). В качестве операндов логических операторов должны выступать числовые значения. Результатом логического оператора будет значение 1 или 0 (типа int).
Оператор && возвращает значение 1, только если оба его операнда не равны нулю. В противном случае оператор && возвращает значение 0. Оператор I I возвращает значение 1, если хотя бы один его операнд не равен нулю. В противном случае оператор && возвращает значение 0.
В языке C++ существует также унарная логическая операция ! (НЕ, логическое отрицание). Она возвращает значение 1, если ее аргумент не равен нулю, и 0 в противном случае.
Например:
1 && 0 // равно 0 0.1 && 1 // равно 1 1 II 0 // равно 1 0.1 II 1 // равно 1 ! 1 // равно 0
Логические операторы, обрабатывающие логические значения
Операторы, обрабатывающие логические значения, совпадают по записи с логическими операторами обработки числовых значений. Только в качестве их операндов задаются значения логического типа bool. Оператор && возвращает значение true, если оба его операнда равны true, и значение false в противном случае. Оператор I I возвращает значение true, если хотя бы один его операнд равен true, и значение false в противном случае.
Например:
true I I false // равно true true && false // равно false
Поразрядные операторы
Над целыми числами в языке C++ могут выполняться не только арифметические, но и поразрядные логические (битовые) операции. В этом случае целые значения трактуются как наборы битов. Поразрядная операция выполняется над каждой парой битов в операндах. Перечень доступных в языке C++ поразрядных операторов дан в таблице 4.2.
Таблица 4.2. Поразрядные операторы
Запись поразрядного оператора
•Смысл оператора
&
Логическое побитовое И
1
Логическое побитовое ИЛИ
Л
Логическое исключающее побитовое ИЛИ
Унарная побитовая операция, меняющая логическое значение на противоположное
«
Побитовый сдвиг влево на число битов, заданное вторым операндом
»
Побитовый сдвиг вправо на число битов, заданное вторым операндом
Например:
123 & 124 // равно 120
128 >> 2 // равно 32
(j & 4) // проверка, установлен ли третий бит в значении переменной j
Оператор << заполняет освобождающиеся младшие разряды нулями. Оператор » учитывает знак числа и заполняет старшие разряды либо нулем, если число положительное или беззнаковое, либо единицей, если число отрицательное.
В языке C++ также имеется ряд составных операторов присваивания, которые поддерживают логические поразрядные операции: &= (присваивание с битовым логическим И), I = (присваивание с битовым логическим ИЛИ), ~= (присваивание с логическим исключающим ИЛИ), <<= (присваивание с побитовым сдвигом влево), »= (присваивание с побитовым сдвигом вправо).
Например:
п <<= 1 ; // удваивание значения переменной п
Сокращенный условный оператор
Язык C++ содержит развитые средства управления последовательностью вычислений. Специально предназначенные для этого операторы рассматриваются далее. Непосредственно в выражениях можно использовать сокращенный условный оператор, который формирует значение в зависимости от истинности или ложности некоторого условия. Этот оператор записывается так:
условие ? значение-1 : значение-2
Условие — это выражение, возвращающее логическое значение. Если это значение равно true (или не равно нулю), то результатом данного условного оператора считается значение-1, в противном случае — значение-2.
Например:
х = (п>0 ? 3.13 : 2.72) ;
Если значение переменной п больше нуля (значение выражения п>0 истинно), то в переменную х запишется число 3.13. В противном случае запишется число 2.72. Расчет конкретного значения (если оно задано выражением) в сокращенном условном операторе происходит, только если это значение выбрано в ходе вычисления выражения.
Операторы определения отношения
Операторы определения отношения выполняют сравнение значений двух операндов. Результатом такого сравнения становится логическое значение. Операторы отношения, имеющиеся в языке C++, представлены в таблице 4.3.
Таблица 4.3. Операторы определения отношения
Запись операторов определения отношения
Значение результата отношения
==
true, если операнды равны
!=
true, если операнды не равны
>=
true, если первый операнд больше или равен второму
>
true, если первый операнд больше второго
<=
true, если первый операнд меньше или равен второму
<
true, если первый операнд меньше второго
Например:
п == 5
z > 3.14
i != (m+80)
х >= у
Операторы сравнения применимы к любым арифметическим значениям, а также к значениям типа указатель (см. главу 6). Кроме того, можно сравнивать значения true и false типа bool, для которых установлено следующее отношение упорядоченности: значение true, трактуемое как целое число 1, больше значения false, трактуемого как целое число 0.
Оператор получения информации о типе
В версию языка среды C++Builder включен оператор typeid, формирующий подробную информацию о типе любого значения программы (константы, переменной, объекта) непосредственно во время выполнения. В качестве параметра оператора typeid задают название интересующего типа. Для использования это-
го оператора в программе надо задействовать стандартную библиотеку typeinfo.h. Оператор typeid выдает объект класса type_info, который содержит подробную справочную информацию об анализируемом типе.
Например:
bool b=true;
typeid(b) // возвращает строку "bool" typeid(int) // возвращает строку "int"
Доступ к текстовому представлению результата в формате AnsiString осуществляется через метод name ():
AnsiString s = typeid(int).name();
Оператор определения размера объекта
Оператор sizeof определяет объем в байтах, выделяемый для хранения значения, заданного в качестве параметра. Параметром может быть наименование типа, тогда рассчитывается объем, необходимый для хранения значения этого типа. Например, значением выражения sizeof (int) будет 4 (4 байта), а значением выражения sizeof (ch), если переменная ch имеет тип char, будет 1.
Значение оператора присваивания
Оператор присваивания считается обычным «вычислительным» оператором языка C++, который в результате своей работы формирует некоторое значение, так же, как арифметические, логические или операторы сравнения.
Значением оператора присваивания считается величина, записываемая в переменную его левой части. Например, значением оператора х=2 + з будет значение 5.
Операторы присваивания в силу своей вычислительности можно выстраивать в цепочки:
а=Ь=с=100;
Такое выражение рассчитывается справа налево. Порядок его расчета можно выразить круглыми скобками:
а = (Ь = (с = 100) ) ;
Таким образом каждая переменная а, Ь и с получит значение 100.
Приоритеты операторов
В ходе вычисления выражения сначала выполняются операторы с более высоким приоритетом, затем — с более низким. Приоритеты операторов языка C++ приведены в таблице 4.4. Некоторые из этих операторов рассматриваются в следующих главах.
Если последовательность вычислений необходимо изменить или наглядно выразить, используют круглые скобки (операцию с высоким приоритетом). Например:
(п != 0) && ( (т % п) == 0)
test && ( (х <= 123.4567) I I ((х=у+1) != 1))
Таблица 4.4. Приоритеты операторов C++
Название операторов
Группа операторов
Приоритет оператора
Определение области действия
Первый (наивысший)
() (взятие выражения в скобки),
() (задание параметров функции), [] (индексация), sizeof, -> (выбор элемента)
Первичные
Второй
!, ~, ++, --, +(унарный), - (унарный),
(тип) (приведение-типа), & (определение адреса),
* (получение значения по адресу), new (создание объекта), delete (удаление объекта)
Унарные
Третий
*,/, %
Мультипликативные
Четвертый
+, -
Аддитивные
Пятый
«. »
Сдвига
Шестой
>, с, >=, <=
Проверки отношений
Седьмой
=- ! =
Эквивалентности
Восьмой
&
Побитовое И
Девятый
Л
Побитовое исключающее ИЛИ
Десятый
I
Побитовое ИЛИ
Одиннадцатый
&&
Логическое И
Двенадцатый
II
Логическое ИЛИ
Тринадцатый
Условное выражение
Четырнадцатый
= (присваивание), операция= (составное присваивание)
Присваивание
Пятнадцатый
, (последовательность вычислений)
Последовательность
Шестнадцатый (наинизший)
Порядок вычисления выражения
Если в выражении используются операторы с одинаковыми приоритетами, то унарные операторы и оператор присваивания вычисляются справа налево, а все прочие операторы вычисляются слева направо.
Например, выражение а=Ь=с эквивалентно выражению а= (Ь=с), а выражение а*Ь/с эквивалентно выражению (а*Ь) /с. Данное правило действует всегда, независимо от наличия круглых скобок. Если программист рассчитывает на побочный эффект и вводит выражение а* (Ь/с), в котором пытается изменить порядок расчета элементов с равным приоритетом, компилятор все равно будет придерживаться порядка (а*Ь)/с.
Если в некотором операторе вычисляется несколько выражений, то последовательность расчета выражений не определена и может быть любой. Например,
в левой части оператора присваивания может располагаться элемент массива с индексом-переменной, а в правой — оператор модификации этого индекса (массивы рассматриваются в главе 5):
а [ i + 5 ] = i + +;
При расчете выражения (i+5) в левой части оператора присваивания переменная i может хранить свое исходное значение, а может быть равна исходному значению плюс один, если первым будет рассчитано выражение i++ из правой части. В подобных сомнительных случаях надо задействовать промежуточные переменные, явно задающие порядок расчета, или разделять один оператор на несколько:
а [ i + 5 ] = i ;
i + + ;
Порядок работы логических операторов
При использовании операторов && и I | гарантируется, что сначала будет вычислено значение левого операнда, и только потом значение правого операнда. Данное правило используется для оптимизации компилируемого кода. Если известно, что левый операнд оператора && равен false (или левый операнд оператора I I равен true), то результат уже определен и второй операнд можно не рассчитывать. Поэтому следующие конструкции могут нести потенциальную ошибку:
b = (х>0) && (i + + < 0) ;
Если значение переменной х меньше или равно нулю, правый операнд рассчитываться не будет и переменная 1 не увеличится на единицу, хотя программист может ошибочно полагать, что будут вычислены оба операнда и выражение 1 + + выполнится всегда.
Вопросы
Что такое константа?
Что такое именованная константа?
Как записываются типизированные именованные константы?
Какие операторы допустимы над целыми и дробными значениями?
Какие операторы допустимы над логическими значениями?
Какие операторы допустимы над поразрядными битовыми значениями?
Какие операторы используются для определения отношений?
Что такое приоритет оператора и как его можно изменить?
В чем особенность расчета операндов в логических операторах?
Как строятся выражения языка C++?
Что такое побочный эффект и как его избежать?
Что такое переменная и как она декларируется?
Как записывается оператор присваивания и как он работает?
Какие бывают комментарии и как они записываются?
ГЛАВА 5 Производные типы
В дополнение к фундаментальным типам — предопределенному набору int, char, double и float с различными модификаторами, язык C++ предлагает производные типы. Такие типы конструируются разработчиком самостоятельно на основе фундаментальных и других производных типов.
К производным типам относятся:
массивы;
структуры;
классы;
функции;
указатели;
ссылки на другие типы;
объединения.
В данной главе рассматриваются массивы, структуры и объединения, а также строковый тип, введенный в версию компилятора среды C++Builder для удобства работы с текстовыми данными. Отдельные главы посвящены указателям и ссылкам, а также функциям. Классы изучаются в разделе книги, посвященном объектно-ориентированному программированию.
Массивы
Практически в каждом программном проекте возникает необходимость обработки большого числа единообразно организованных данных. Например, информация о пользователях в виде набора строк или результат физического эксперимента в виде длинной серии дробных значений подразумевают выполнение одних и тех же действий над каждым элементом набора. В таком случае подобный набор удобно обрабатывать как одну сущность, для чего он представляется в виде массива — именованной последовательности данных одного типа.
Массив — это набор элементов одного типа, доступ к которым осуществляется по номеру (или индексу).
Описание массива
Массив декларируется в программе следующим образом:
тип-элемента имя-массива [ описание-диапазона ];
Описаний диапазона, заключенных в квадратные скобки, может быть несколько. С их помощью задается число измерений массива. Для каждого из измерений массива задается независимое число элементов.
Номер элемента массива по определенному измерению должен находиться в заданном диапазоне. Нумерация элементов в массиве всегда начинается с нуля.
Тип элемента определяет единый тип данных для каждого элемента массива. Имя массива — это пользовательский идентификатор (название переменной), который хранит ссылку на массив.
Массив с одним измерением называют одномерным массивом или вектором.
В следующем примере описывается массив МуАггау из 100 целых чисел (элементов типа int), в котором первый элемент будет иметь номер 0, а последний — номер 99:
int МуАггау [100];
Тип массива и тип ссылки
Переменные типа массив не хранят все данные массива целиком. Они лишь именуют некоторую область в оперативной памяти, где расположены элементы массива, и считаются переменными ссылочных типов. Такие переменные хранят не объекты, а лишь указатели на них. Фактически переменная типа массив указывает на местонахождение в оперативной памяти первого элемента массива (элемента с нулевым номером).
Ссылочный тип (или тип ссылок) — это тип, задающий ссылку (указатель ) на область памяти, в которой хранится некоторый, обычно сложно организованный, объект.
Индексация массива
Имя массива в равной степени относится ко всем его элементам. Для обращения из программы к определенному элементу массива необходимо указать его индекс.
Индекс элемента массива — это значение базового типа, определяющее номер этого элемента в массиве. Индекс должен укладываться в диапазон (границы индекса), заданный при описании массива. Нумерация элементов массива всегда начинается с нуля.
Индекс элемента массива может быть вычисляемым. Это позволяет организовывать компактную и эффективную обработку наборов данных одинаковой внутренней структуры.
Для доступа к элементу массива указывают имя переменной (массива соответствующего типа) и вслед за ним — индекс в квадратных скобках. В качестве индекса может выступать произвольное выражение языка C++, значение которого укладывается в диапазон, заданный при описании массива. Например:
int MyArray[100] ;
int N;
MyArray[0] = 10;
N = ,5;
MyArray[N+l] = 500;
MyArray[99] = MyArray[N-2] - 50;
Ошибки неверной индексации
Возможны ситуации, когда происходит обращение к несуществующему элементу массива: значение индекса не укладывается в допустимый диапазон.
Пример:
int MyArray[100];
int N;
N = 100;
MyArray[N] = 500;
// ошибка! 100-го элемента не существует
В таком случае при попытке неверного обращения возникнет ошибка времени выполнения программы “Access violation at address 000 in module xxx” (попытка обращения программы xxx к адресу оперативной памяти 000, выходящему за допустимые пределы). К сожалению, подобные ошибки фиксируются далеко не всегда. Если ошибочный индекс отстоит далеко от начала массива и сильно выходит за пределы, ошибка возникнет с большой вероятностью. Например:
MyArray[N+10000] = 500;
Данный код скорее всего приведет к возникновению вышеупомянутой ошибки. Но в оригинальном коде:
MyArray[N] = 500;
индекс N не уложился в допустимый диапазон лишь на одну позицию. Поэтому, вполне возможно, область памяти, расположенная непосредственно за сотней элементов массива MyArray, принадлежит текущей прикладной программе. Обращение к ней не расценивается операционной системой как ошибка, и работа программы будет продолжена без каких-либо нарушений. Однако логика ее работы будет неверна. Не исключено, что следом за элементами массива в памяти располагаются области, отведенные для переменных, и подобная ошибочная модификация несуществующего элемента массива приведет фактически к побочной порче значений других переменных программы.
Ошибки, связанные с отсутствием контроля за правильность индексации массивов, являются наиболее распространенным классом ошибок в программах на C++. Множество ежедневно выявляемых ошибок в самых разных прикладных программах и операционных системах, о которых постоянно предупреждают мировые информационные агентства по компьютерной безопасности, в большинстве связаны именно с неконтролируемой обработкой массивов. В среду C++Builder включена система CodeGuard, которая позволяет выявлять подобные ошибки на этапе разработки и отладки.
Многомерные массивы
Для многих задач одномерных массивов, представляющих собой простые списки элементов, недостаточно. В таких случаях используют многомерные массивы с несколькими индексами.
Многомерный массив — это массив, имеющий несколько измерений.
Каждое измерение выделяется отдельной парой квадратных скобок и характеризуется собственным диапазоном или размером (числом элементов):
тип-элемента имя-массива [ описание-диапазона-1, описание- диапазона-2, ... J ;
Матрица — это массив с двумя измерениями (двумерный).
Матрица фактически представляет собой двумерную таблицу, первое измерение которой считают, например, столбцом, а второе — строкой (или наоборот).
Запись
double mtx [10][20];
задает таблицу размером 10x20 элементов (дробных значений).
При доступе к элементу многомерного массива значения индексов перечисляют в квадратных скобках — каждый индекс в своих скобках. Например:
double mtx [10][20];
mtx[5][2] = 3.14;
mtx[9][19] = -0.007;
Инициализация массивов
В момент определения массива в него можно сразу занести начальные значения элементов. Для этого следом за описанием массива ставится символ «=» и задается набор значений элементов в фигурных скобках. Число элементов массива в квадратных скобках можно не вводить — оно определяется автоматически числом элементов инициализации. Например:
int MyArray[] = { 1, 2, 4, 8, 16 };
Такой командой объявлена переменная-массив MyArray из пяти элементов.
При инициализации многомерных массивов значения по каждому измерению выделяются в отдельную последовательность, охваченную фигурными скобками. Сначала размещаются элементы, входящие в первое измерение (массивы с числом измерений, на единицу меньшим), затем элементы второго измерения, и так далее. Может быть опущено число элементов только по первому измерению, для остальных измерений оно должно быть задано явно.
Например:
double mtx[][3] = {
(3.14, 2.87, 0.01} ,
(-2.34, 100.0, +5.67}
} ;
Массив mtx представляет собой матрицу из двух строк по три элемента в каждой.
Ключевое слово const позволяет превратить массив в константу:
const int MyArray[] = { 1, 2, 4, 8, 16 };
Теперь к элементам массива MyArray можно обращаться, но нельзя менять их значения.
Определение числа элементов массива
Массивы относятся в языке C++ к ссылочным типам. Это означает, что содержимое массива хранится в оперативной памяти в виде набора значений, доступ к которым организован с помощью указателя (имени массива), который лишь указывает на массив, но не хранит его непосредственно. Стандартный оператор sizeof применительно к массиву возвращает его длину в байтах. Зная тип элементов массива, можно определить число его элементов по формуле
sizeof(имя-массива)/sizeof(тип-элемента-массива)
Например:
int MyArray[] = { 1, 2, 4, 8 , 16 } ;
Значение sizeof (MyArray)/sizeof (int) будет равно 5. Если тип массива быстро определить не удается, вместо типа можно ввести имя любого из элементов:
sizeof(MyArray)/sizeof(MyArray[0]).
Обработка ссылок на массивы
Переменные, декларированные как массивы и другие объекты ссылочного типа, хранят не сами объекты, а указатели на них. Поэтому сравнение таких переменных или копирование их значений не приведет к обработке самих массивов. Сравниваться и копироваться будут не данные, а указатели на них (более подробно об указателях см. главу 6).
Например:
int al [ 10 ] ;
int а2[10] ;
Переменные al и а2 декларированы как массивы с одинаковым описанием. Однако каждая из этих переменных хранит не содержимое массива, а указатель на начало набора значений (элементов массива) в оперативной памяти. Поэтому оператор
а2 = al;
вызовет ошибку компиляции.
Сравнение переменных типа массив также не приводит к сравнению их реальных значений (наборов элементов). Сравниваются лишь указатели на некоторые объекты, и результат сравнения будет истинен, если переменные хранят одинаковые ссылки на один и тот же объект. Поэтому процесс копирования и сравнения массивов надо программировать вручную. Чаще всего эту задачу решают с помощью циклов (см. главу 8).
Тип Строковое
Тип char в языке C++ позволяет обрабатывать отдельные символы, которые трактуются как целые значения. В C++ также допустимы текстовые константы — последовательности символов, взятые в двойные кавычки. В стандартной версии языка C++ нет специального строкового типа, хотя разрешается использовать строковые константы. В версии C++ среды C++Builder поддерживается тип Ansi string, позволяющий определять переменные строкового типа.
Строковая константа при записи в тексте программы заключается в двойные кавычки, например:
"Это строка!"
"1234567890"
Для отображения кавычки внутри строки применяется escape-последовательность \" :
"в эту строку здесь: \" вставлена двойная кавычка"
С помощью символа \ в строку также можно вводить шестнадцатеричные и восьмеричные константы.
Строковый объект создается, как объекты простых типов:
Ansistring MyString;
Переменная MyString будет хранить в программе значения строкового типа:
MyString = "это строка!";
Имя типа AnsiString — не является ключевым словом, а представляет собой имя производного типа данных. Существует его более короткий синоним string.
Допускается сразу инициализировать текстовую переменную строкой:
AnsiString MyString = "это строка!";
Над строками типа Ansistring разрешена операция «сложения» — соединения двух строк в одну:
AnsiString S1 = "это " ;
AnsiString S2 = "строка";
Оператор
SI = SI + S2 ;
занесет в переменную S1 значение "это строка".
Помимо типа AnsiString разработчику доступен тип wideString, в котором символы кодируются двумя байтами. Строковые константы типа WideString записываются с префиксом 1 (или ь):
L" строка"
1"по два байта на символ"
В языке C++ существует еще один способ инициализации и создания строковых переменных, основанный на использовании массива символов. Этот способ достался по наследству из языка Си. В программе первоначально объявляется массив типа char и задается его начальное значение:
char s[] = { ' с' , ' т' , ' р' , 'о', ' к' , ' а' } ;
В C++ разрешается и более компактная запись:
char s[] = "строка";
Однако во втором случае в переменную s заносится ссылка на последовательность символов длиной не шесть, а семь символов. Компилятор автоматически добавляет в конец массива дополнительный символ с кодом 0, который обозначает завершение строки. В противном случае будет невозможно динамически определить длину массива символов. Подобная схема хранения строк активно применяется во многих программах на языке C++. Она востребована еще и потому, что операционная система Windows обрабатывает строки именно в таком формате.
Строка с нулем в конце — это массив символов, завершающим элементом которого выступает символ с нулевым кодом. При обработке такой строки этот последний нулевой символ не учитывается — он служит только для определения окончания массива.
Переменная s не является полноценным хранилищем строки, как в случае с переменными типа Ansistring. Она представляет собой массив символов со всеми ограничениями на обработку массивов. Разработчику доступна обширная библиотека стандартных функций обработки строк, представленных массивами символов (строками с завершающим нулем). Она называется string.h. Например, соединение двух строк (массивов символов) в одну выполняет функция strcat (), копирование строк — функция strcpyO, подсчет числа символов в строке — функцияstrlen().
Чтобы массив символов можно было обрабатывать как строку с завершающим нулем, соответствующий нулевой символ в конце последовательности инициализирующих символов надо задать явно:
char s[] = { ' с ' , ' т' , ' р' , 'о', ' к' , ' а' , 0} ;
Количество символов в массиве, представляющем строку с нулем в конце, можно задать явно. В этом количестве необходимо учитывать и нулевой символ:
char s [ 7 ] = {' с' , 'т', 'р', 'о', ' к' , ' а' , 0};
char s[7] = "строка";
Если длина символьного массива задана, и заносимая в него строка короче этой длины, то оставшиеся в массиве элементы заполняются нулями. Например:
char digits[5] = "123";
Для хранения массива digits отводится семь элементов типа char. Первые три из них получат значения ' 1', ' 2 ' и ' 3 ', а в последние два запишутся нули.
Преобразование массива символов в формат Ansistring происходит автоматически:
AnsiString ans;
char s[] — "12345";
ans = s;
Переменная ans получит значение "123 45".
Значение типа Ansi st ring может трактоваться как массив. К нему, как и к массиву символов, применима операция индексации — выделения нужного элемента. Она позволяет обрабатывать отдельные символы в строке:
AnsiString ans;
char s[] = "12345";
ans = s;
s[2] = 'O'; // значение переменной s - строка "12045"
ans[2] = 'O'; // значение переменной ans - строка "10345"
Так как переменная s — обычный массив, то индексация его элементов всегда начинается с нуля. Для типа AnsiString принята индексация с единицы, поэтому обращение к переменным типов char [ 1 и AnsiString с одинаковым индексом даст разный результат.
Стандартный метод Length типа AnsiString возвращает длину строки. Например:
AnsiString S = "abcdefgh";
int n = s.Length(); // n равно 8
Для нахождения позиции подстроки в строке применяется метод AnsiPos (), которому в качестве параметра передается искомая подстрока:
int n = S.AnsiPos("cde");
// переменная п получит значение 3
Если подстрока не обнаружена, метод AnsiPos () вернет нулевое значение.
Для формирования новых строк на основе существующих обычно применяется метод Substring^, выделяющий подстроку в родительской строке. Первым его параметром задается позиция подстроки, вторым — ее длина.
Например:
S = "abcdefgh";
S = S.Substring(3, 4) ;
В переменную s запишется новая строка "cdef".
Массивы строк
Язык C++ допускает создание массивов, состоящих из строк. Такие массивы описываются стандартным образом, например:
AnsiString Weekf] = { "понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье" };
Элементы массива строк доступны по своим индексам — как для считывания, так и для модификации:
AnsiString s = Week[0];
Week[б] = "sunday";
Структуры
Структура дополняет в языке C++ концепцию массива. Обращение к элементам массива происходит по номерам, а обращение к элементам структуры — по именам.
Структуры хороши, если необходимо быстро обрабатывать большие объемы данных малой и средней сложности. Структуры как экземпляры типа данных хранятся непосредственно в переменных (в отличие от массивов), поэтому скорость их обработки высока. Однако если структура велика по размеру, могут возникнуть проблемы с эффективным расходованием оперативной памяти. Задачи, решаемые с помощью структур (объединение составных элементов в одном понятии), лучше программировать с помощью объектно-ориентированных средств C++. Структура представляет собой усеченный вариант класса (см. главы по объектно-ориентированному программированию), и в процессе проектирования программы желательно применять полноценные объектные возможности языка C++.
Описание структуры
Описание структуры (определение нового структурного типа данных) выглядит так:
struct имя-структуры {
тип поле; тип поле;
} ;
Элементы структуры (их также называют полями) могут иметь разный тип. При описании структуры надо задать как имя каждого элемента, так и его тип данных.
Структура — это набор элементов, доступ к каждому из которых осуществляется по имени (названию элемента).
Описание структуры должно располагаться вне области исполнимых команд C++ (вне функций и методов).
Названием структуры (именем нового структурного типа) может быть любой допустимый идентификатор языка C++. Описание структуры заключено в фигурные скобки. В них расположены объявления полей, которые записываются как обычные объявления переменных. Перед точкой с запятой, завершающей описание структуры, можно вводить идентификатор. В таком случае он считается именем переменной данного структурного типа.
Например, структура, описывающая животное, может выглядеть так:
struct Animal {
AnsiString Name;
int FootNum;
double Mass;
} ;
Поле структуры Name хранит текстовое название животного, поле FootNum — число лап (целое значение), поле Mass — массу в килограммах (вещественное значение).
Имя структуры выступает как тип данных. На его основе можно декларировать переменную типа структура:
Animal cat;
Переменная cat — это экземпляр структуры Animal.
В отличие от массивов переменные структурного типа хранят экземпляры структур, поэтому они свободно копируются с помощью оператора присваивания:
Animal catl, cat2; catl = cat2;
Обращение к элементам структуры
При обращении к элементу структуры сначала вводится имя переменной типа структура, затем ставится точка и, в заключение, следует имя соответствующего поля:
имя-переменной.имя-поля-структуры
Например:
Animal cat; cat.Name = "Барсик"; cat.FootNum = 4; cat.Mass = 2.72;
Инициализация структур
Поля структуры должны быть инициализированы до их первого использования. Это делается либо обращением к конкретным полям с помощью обычных операторов присваивания, либо способом, схожим с инициализацией массивов. Перечень значений полей задается последовательно, в фигурных скобках.
Например:
struct D3Point {
double X; double Y; double Z; } ;
D3Point p.3d = { 1.0, 10.5, 33.7 };
Если некоторое поле должно хранить текстовую информацию, а структуру с этим полем надо инициализировать, вместо типа Ansistring используют массив символов. Тип AnsiString — не фундаментальный, и вместо него задействуют тип char [ ] с явно заданным числом элементов:
struct Animal { char Name[80]; int FootNum; double Mass;
Animal dog = { "Дик", 4, 8.9 };
Ключевое слово const превращает структуру в константу:
const Animal dog = { "Дик", 4, 8.9 };
Теперь к элементам структуры dog можно обращаться, но нельзя менять их значения.
Вложенные структуры
В определения структур можно вкладывать другие определения структур. Например:
struct MyRect { char name[80];
struct { int Left; int Top;
struct { int Rigth; int Bottom; };
Внутри структуры MyRect, определяющей координаты прямоугольника, имеется текстовое поле name, а также две анонимные структуры, описывающие, соответственно, координаты верхнего левого и нижнего правого углов. Обращение к полям вложенных структур осуществляются напрямую:
MyRect гс;
rc. Left = 5;
Анонимные структуры
При декларации структуры ее имя (наименование нового типа) можно опускать. Чтобы подобная конструкция имела смысл, перед завершающей запятой задают имя переменной, которая будет хранить экземпляр структуры. Например:
struct {
int х ;
int у ;
} MyPoint;
Идентификатор MyPoint — не название типа, а имя переменной:
MyPoint.x = 10;
Эквивалентность структур
Явное описание массива или структуры создает уникальный тип для соответствующей переменной. Даже если такие описания полностью совпадают, считается, что объявленные переменные относятся к разным типам. Например:
struct {
char name[ 10 ];
} xl;
struct { char name[10];
} x2 ;
Переменные xl и x2 описаны одинаково, однако они принадлежат к разным типам:
xl = х2; // ошибка!
Чтобы переменные xl и х2 принадлежали к одному типу, надо предварительно объявить этот тип явно:
struct MyStr { char name[10];
MyStr xl, x2 ;
xl = x2 ; // корректно
Объединения
В некоторых случаях одни и те же данные желательно представлять как значения разных типов. Например, двухбайтовое значение типа short int иногда требуется рассмотреть как две однобайтовые величины (старший байт и младший байт). Для этого можно задействовать операции двоичного сдвига, но можно обойтись и без дополнительных вычислений, просто задав несколько описаний для одной и той же области памяти. В этом помогают объединения.
Объединение — это тип данных, содержащий несколько описаний одной области памяти.
Программист может использовать любое из описаний по своему усмотрению.
Декларация объединения схожа с декларацией структуры, за исключением начального ключевого слова:
union имя-объединения {
тип поле;
тип поле;
};
Например:
union MyShort { short sint;
char schar[2];
MyShort ms;
Для хранения переменной ms выделено два байта. Они могут трактоваться как одно двухбайтовое значение типа short (если задействуется поле sint), либо как два однобайтовых элемента массива schar.
Имеется следующий код:
ms . sint = 17 00 0;
ms.schar[0] = 0;
Значение 17000, заносимое в поле sint, хранится так: в первом байте расположена младшая часть числа (104), во втором байте ■— старшая часть числа (66): (17000-104)>>8 = 66. После выполнения первого оператора содержимое переменной ms будет доступно в нескольких формах:
ms.si nt // равно 17 00 0;
ms.schar[0] // равно 104
ms.schar[l] // равно 66
После выполнения второго оператора младшая часть поля sint заменяется с 104 на 0, в результате чего модифицируется и все значение:
ms.sint // равно 16896;
ms.schar[0] // равно 0
ms.schar[l] // равно 66
Использование объединений, как и битовых полей, реализовано в языке C++ с целью обеспечения совместимости с языком Си. Эти возможности нацелены на оптимизацию использования оперативной памяти и эффективное использование всех битов каждой переменной, что при эксплуатации современных компьютерных архитектур неактуально.
Объявление синонимов типа
Запись типа данных может быть достаточно длинной. Например, неудобно часто вводить комбинацию unsigned short int. Ключевое слово typedef позволяет ввести синоним любому существующему типу. Оно используется в следующем формате:
typedef существующий-тип синоним ;
Например:
typedef unsigned short int usi;
Данная инструкция вводит идентификатор usi, который служит альтернативным названием типа unsigned short int:
usi N = 5;
Создание новых типов данных из сложных типов данных
Язык C++ допускает конструирование составных типов данных с одновременным использованием как массивов, так и структур. Допускаются массивы структур, структуры, включающие массивы, и любые другие синтаксически корректные конструкции. Например:
// массив структур
Animal dogs[10];
dogs[0].FootNum = 3;
// структура с элементом-массивом:
struct MyDog {
char Name[80] ;
int FootNum;
Animal DogChildren[3];
MyDog dog;
dog.DogChildren[0].Mass = 500;
Вопросы
Что такое массив?
Как происходит обращение к элементу массива?
Как создаются многомерные массивы?
Как инициализируются массивы?
Как в C++ обрабатываются текстовые данные?
Что такое структура?
Как происходит обращение к элементам структура?
Что такое объединение?
Как организуются сложные типы данных?
ГЛАВА 6
Указатели и ссылки
Указатель условно можно считать номером ячейки оперативной памяти — по аналогии с индексом массива, номером элемента, хранящего значение. Указатели позволяют напрямую обращаться к различным областям оперативной памяти и в ряде ситуаций повышают эффективность программ. Они особенно важны, если создается системное приложение, предназначенное для обработки ресурсов операционной системы. Указатели были реализованы в языке Си для повышения эффективности создаваемых программ, которые тогда работали в рамках крайне ограниченных ресурсов, так что доступ к оперативной памяти приходилось организовывать весьма сложными способами.
Указатель — это значение, описывающее физический адрес оперативной памяти.
Способ хранения физического адреса (называемого также ссылка) скрыт от разработчика. Указатель может указывать на местонахождение в памяти элемента конкретного типа — в таком случае это типизированный указатель. Указатель также может содержать адрес места в памяти, содержимое и структура которого неизвестны, — это нетипизированный указатель. Сам по себе указатель не хранит значение, которое может использоваться в программе напрямую, — он только указывает на область памяти, в которой находится нужная величина.
Указатели относятся к наиболее опасным механизмам языка C++. Некорректное использование указателей приводит к серьезным ошибкам, которые вызывают крах системы или открывают доступ к ней хакерам. Ведь с помощью указателей программа способна обращаться к произвольным областям оперативной памяти, в которых, например, хранятся различные системные сведения и исполнимый код, и модифицировать их. Контроль же за значениями указателей крайне сложен и далеко не всегда осуществим.
Объявление указателей
При объявлении типизированных указателей используется следующая форма записи:
тип * имя-указателя ;
Тип — это тип данных, на элементы которого будет указывать переменная, чье имя задано далее. Например:
char* pChar; // указатель на символы int* pint; // указатель на целые числа
Сочетание «тип» с символом * в языке C++ представляет собой не расширение типа дополнительным модификатором, а описание нового типа «тип*». Как с помощью ключевого слова struct в программу вводится новый пользовательский структурный тип, так и с помощью символа * следом за названием типа в программу вводится новый пользовательский тип указателя. Например, int* — это отдельный тип данных (тип указателя на значения типа int).
Синтаксически символ * в команде декларации считается префиксом. Он относится только к единственному следующему за ним идентификатору, а не задает единый тип для всех вводимых переменных. Например, в одной инструкции определяются две переменные:
short* рВ1 , рВ2 ;
Переменная рвх имеет тип указателя short*, но звездочка в этой записи относится только к ней. Переменная рв2 декларирована как обычная переменная типа short. Более наглядно данную инструкцию можно записать, разделив указатель и простую переменную:
short *рВ1, рВ2 ;
Пустой тип
В языке C++ имеется фундаментальный тип void, который не определяет никакого значения и поэтому называется пустой тип. Язык C++ является языком со строгой типизацией, он требует, чтобы все значения в программе имели собственный тип, поэтому использование ключевого слова void при описании переменных запрещено:
void V; // неверно! ошибка компиляции!
Но в языке C++ разрешены нетипизированные указатели, которые указывают на заранее не определенные «значения» типа void:
void* pV; // корректно
Впоследствии в переменные типа void* можно заносить адреса областей памяти с любым содержимым.
Фактически работа с пустым типом является работой с нетипизированным указателем. Например:
void* Р;
double X;
char С;
void* Р;
Р = &Х;
Р = &С;
В переменную р записываются указатели на данные произвольных типов.
Нетипизированные указатели можно явным образом приводить к типизированным указателям. При этом возможны ошибки преобразования значений указателей, но они средой выполнения не отслеживаются. Например:
double X;
double* РХ;
void* Р, Р2;
Р = &Х;
РХ = (double*)Р;
Р2 = РХ;
В переменной рх будет храниться указатель на дробное число. Он получен преобразованием нетипизированного указателя из переменной р.
Определение адреса объекта
Указатели нужны для того, чтобы обращаться к объектам, на которые они указывают. Предполагается, что доступ к этим объектам обычным способом затруднен или невозможен.
В языке C++ имеется операция получения адреса объекта, которая записывается с помощью символа &. Этот символ ставится перед именем переменной, адрес содержимого которого надо определить. Например:
int* pint;
int n = 0;
pint = &n;
В переменную-указатель pint запишется адрес переменной п (адрес оперативной памяти, где хранится ее содержимое, значение 0).
Переменная типа указатель — обычная переменная, которая хранит короткий адрес, как правило, 32-разрядное значение. Это значение само расположено по некоторому адресу, связанному с именем переменной, которое трактуется как указатель. Таким образом в программе можно декларировать указатели на указатели и более длинные цепочки ссылок. Определение очередного указателя формируется с помощью отдельного символа * следом за названием типа или другим символом *.
Например:
int* pint; // указатель на значение int** pplnt; // указатель на указатель
int п = 0;
pint = &п;
pplnt = &plnt;
В переменную pplnt записан адрес переменной-указателя pint, которая хранит указатель на значение целого типа. В языке C++ разрешаются неограниченные цепочки указателей на указатели (неограниченное значение символов * в описании указателя).
Доступ к значению указателя
После того, как в переменную типа указатель занесен адрес некоторой области памяти, к этой переменной можно обратиться, чтобы получить доступ к содержимому данной области. Для этого перед именем переменной-указателя ставится
символ *. Таким образом компилятору сообщается, что надо использовать не значение самого указателя (адрес), а содержимое памяти по этому адресу, на которое он указывает. Например:
int* pint;
int n = 10 , m;1
pint = &n;
m = *plnt;
Значение выражения *pint эквивалентно значению переменной п, ведь указатель pint указывает на область памяти, отведенную для переменной п. Идентификатор п при этом всегда связан с конкретной областью памяти на всем протяжении работы программы (эта связь выполнена на этапе компиляции), а значение идентификатора pint (физический адрес) может динамически меняться.
Схожим способом можно не только считывать значения по указателю, но и модифицировать их. Для этого задействуется оператор присваивания, в левой части которого размещается обращение к указателю:
int* pint;
int n = 10;
pint = &П;
*plnt = 5;
В результате значение переменной п, на которую указывает переменная pint, изменится с 10 на 5, хотя мы не обращались к переменной п явно. Этот пример наглядно показывает, почему опасно бесконтрольное использование указателей — , переменные программы могут модифицироваться косвенным и плохо отслеживаемым способом.
Оператор получения значения по указателю применяется не только к переменным типа указатель, но и к любым выражениям такого типа, которые возвращают указатель в качестве своего значения.
Переменные типа указатель могут иметь нулевые значения (не указывать ни па какие объекты):
pint = 0;
В данном случае символ * перед именем переменной-указателя не ставится, так как значение «нулевого» указателя записывается в саму переменную, хранящую адрес, а не в область памяти, на которую она указывает. Нулевое значение указателя используют, чтобы отметить, например, что в некоторую переменную типа указатель пока не записано значения и она не содержит допустимого адреса.
Значение 0 в указателе автоматически преобразуется к любому типу типизированного указателя.
Ссылки
В дополнение к указателям язык C++ поддерживает ссылки - механизм альтернативных имен для переменных. Он позволяет создать альтернативное имя (фактически, тот же указатель) для переменной, однако работа с ним осуществляется обращением к имени без ввода дополнительных символов операций обращения по адресу.
Ссылка формируется в операции объявления переменной символом &, который ставится следом за названием типа:
тип & альтернативное-имя = имя-исходной-переменной;
Например:
int X = 5 ;
int& Y = X;
X = 10;
Переменная y — это не отдельное хранилище данных, а еще одно обозначение переменной х. После изменения значения переменной х (например, с 5 на 10) обращение к «переменной» Y как второму имени х даст значение 10, хотя явно эта «переменная» не модифицировалась. Верно и обратное: при изменении значения альтернативной переменной меняется и значение основной переменной, так как они связаны с одной и той же ячейкой памяти.
Ссылки могут использоваться и как обычные переменные. Для этого в момент их объявления после символа = вводится не имя другой переменной такого же типа, а константа или имя переменной другого типа. В таком случае в памяти создается временный объект, и работа с ним происходит, как с обычной переменной:
double X = 5.0;
int& Y = X; // создана новая переменная Y
X = 10; // значение переменной Y не меняется
Y = 15; // значение переменной X не меняется
Ссылки введены в язык C++ в основном для поддержки механизма передачи параметров в функции — по имени или значению (см. главу 9).
Массивы и указатели
Имя массива, как уже говорилось, не хранит непосредственно весь массив, а лишь именует область в памяти, где расположены его элементы. Фактически имя массива выступает в роли указателя на начало массива (самый первый элемент с нулевым индексом).
Имя массива как указатель
Обращение к значению типа указатель может записываться с помощью квадратных скобок, как обращение к элементу массива. В квадратных скобках задается
сдвиг (в элементах, соответствующих типу указателя) по отношению к текущему значению указателя. Например, если указатель р указывает на область памяти, содержащую последовательность значений 1, 2, 4, 8, 16,..., то выражение р [2] указывает на значение 4.
Указатели нередко применяются, когда надо организовать быструю обработку массивов. Для этого в указатель заносится адрес массива (адрес его первого элемента с нулевым индексом). Сделать это можно двумя способами, например:
int а[5]= {1,2,3,4,51;
int* рА;
рА = а;
Имя массива можно считать указателем, поэтому корректно его использование в роли значения указателя. Возможен и другой способ определения начала массива — с помощью оператора &, который применяется к самому первому элементу массива:
рА = & (а [ 0 ] ) ;
Оба эти способа эквивалентны.
После того, как в указатель записан адрес массива, к нему можно применять операцию взятия значения по индексу []. Например:
int а[5]= {1,2,3,4,51;
int* рА, п;
рА = а ;
п = рА [ 3 ] ;
В переменную п будет занесено значение 4, а указатель рА можно использовать наравне с именем массива а.
Указатель на массив
Так как имя массива можно считать указателем на его самый первый элемент, то указателем на массив теоретически может выступать двойной указатель (указатель на указатель, задаваемый двумя звездочками **). Однако компилятор различает тип «массив» и тип «указатель» и не допускает подобные конструкции.
Например:
int а[5]= {1,2,3,4,51;
int* рА, **ррА;
рА = а ;
ррА = &а; // ошибка 1
Формально тип переменной а — не указатель, а массив. Поэтому переменная ррА должна получать не адрес значения указателя, а адрес массива (фактически же, адрес указателя на первый элемент массива с индексом 0).
Конструкция, описывающая указатель на массив, выглядит так:
тип (Идентификатор) [];
Она явно определяет идентификатор как указатель, а квадратные скобки показывают, что этот указатель указывает на массив. Круглые скобки нужны, чтобы задать первоочередную принадлежность символа * идентификатору. Так как приоритет операции [] выше приоритета операции *, следующая конструкция:
тип * идентификатор [];
будет расценена не как указатель на массив, а как массив указателей. Например:
int (*рА)[ 10 ];
- int* рВ[10];
Переменная рА — это указатель на массив из 10 целых чисел. При декларации переменной рв компилятор сначала формирует массив рв [ 10] из 10 элементов, и лишь потом фиксирует тип его элементов, который будет равен int* (указатель на целые числа).
После определения указателя на массив его можно использовать для обработки адреса массива:
int а[] = {1,2,3,4,5};
int* рА, (*ррА)[];
рА = а;
ррА = &а;
Операции над указателями
Над указателями разрешены простые операции. Значения указателей можно сравнивать друг с другом, причем не только на равенство (операции == и !=), , но и проверять отношения их величин (операции >, <, >=, <=). Способ определения отношения может зависеть от компилятора языка C++. Подразумевается, что один указатель больше другого, если он указывает на область памяти, расположенную в более высоких адресах (ближе к верхней границе оперативной памяти). Сравнивать можно типизированные указатели, указывающие на данные разных типов, они приводятся при этом к типу void*.
Значения указателей можно сдвигать вперед и назад с помощью операций + и -. Сдвиг указателя подразумевает смещение на заданное число единиц соответствующего типа. Например, если указатель р имеет тип int* и указывает на первый элемент последовательности чисел 1, 5, 10, 100, 50, 20... то после выполнения оператора
Р = Р + 2;
указатель р, сдвинутый вперед на две ячейки, отведенные для хранения целых чисел, будет указывать на последовательность 10, 100, 50, 20 ...
С помощью арифметических операций над указателями можно переопределить операцию обращения к массиву с помощью скобок [ и ]. Запись р [ Е ], где р — указатель, а Е — выражение целого типа, эквивалентна записи * (р+е). Сначала значение указателя сдвигается на число элементов, заданное значением выражения е, а затем определяется величина, на которую указывает временно рассчитанное значение указателя. Так как приоритет оператора взятия значения по указателю высокий, выражение р+е, рассчитывающее значение указателя, необходимо заключить в круглые скобки. В противном случае выражение *р+е будет равно значению *р (величина, хранимая по адресу р), сложенному со значением Е.
Операция - может применяться только к данным типа указатель, когда в качестве обоих операндов выступают указатели. В таком случае она определяет целочисленную разность между указателями (в единицах соответствующих типов).
Например, указатель р указывает на самый первый элемент последовательности
5, 10,100, 50, 20,..., а указатель Р2 указывает на третий элемент этой последовательности. Тогда выражение р2-р будет равно двум.
Над переменными-указателями допустимы операторы увеличения и уменьшения значения на единицу + + и
Доступ к элементам структуры
Если указатель указывает на данные, объединенные в структуру, то доступ к значениям полей этой структуры с помощью переменной-указателя происходит с помощью комбинации символов " - >".
Например:
struct MyPoint {
int X ;
int Y ;
} ;
MyPoint pnt;
MyPoint* pPnt;
pPnt = &pnt;
pPnt->X = 10;
pPnt->Y = 20;
Обращение к элементам структуры, на которую указывает переменная pPnt, происходит только с помощью комбинации " - >" — символы * и . не используются.
Указатели в структурах
Структуры часто используют для организации типов данных, значения которых связываются друг с другом посредством указателей. Например, данные могут быть организованы в виде последовательного списка, когда каждый элемент списка хранит указатели на следующий и предыдущий элементы. Эти указатели могут быть нетипизированными, но корректнее описывать их как указатели на структуры.
Язык C++ разрешает вводить в описание структуры указатели на экземпляры этой же структуры еще до завершения ее описания. Например:
struct MyList {
MyList* PredList;
MyList* NextList;
int data;
} ;
Внутри структуры MyList имеются ссылки на значения ее собственного типа. Такая запись не вызывает ошибки компиляции.
Если необходимо определить две структуры, в каждой из которых имеется указатель на экземпляр другой структуры, можно воспользоваться упреждающим описанием структуры — предварительной декларацией ее названия. Например, следующий пример приведет к ошибке компиляции:
struct strl { str2* pStr2; };
struct str2 { strl* pStrl; };
В момент разбора строки "str2* pstr2;" компилятор встречает незнакомый тип данных str2 и фиксирует ошибку. Чтобы этого избежать, надо перед декларацией структуры strl ввести сокращенное упоминание структуры str2:
struct Str2;
struct strl {
Str2* pStr2; } ;
struct str2 { strl* pStrl; };
Теперь компилятор знает, что str2 — это структура, описанная где-то далее по тексту, и корректно обрабатывает исходный код.
Указатели-константы
В языке C++ имеется ключевое слово const, которое превращает любой декларированный идентификатор в константу: его значение можно считывать, но нельзя модифицировать. Однако смысл декларации меняется в зависимости от того, где в инструкции определения расположено это слово. Если оно находится перед типом указателя, то защищает от изменения не указатель, а объект, на который этот указатель указывает.
Е1апример:
const char* abc = "abc";
Идентификатор abc — указатель-константа на строку " abc". К значению, на которое он указывает, можно обращаться, но нельзя его менять:
abc[l] = 'd'; // ошибка
Однако значение самого указателя (адрес объекта, местонахождение строки " abc" в памяти) может быть модифицировано:
const char* abc = "abc";
char* al2 3 = "123";
abc = a!2 3; // корректно
Чтобы защитить от модификации сам указатель, ключевое слово const надо разместить непосредственно перед его именем так, чтобы префикс * применялся также к слову const.
Например:
char* const abc = "abc";'
char* al23 = "123";
abc = al23; // ошибка
Однако теперь описание char* const abc не защищает от модификации объект, на который указывает переменная abc:
abc[l] = 'd'; // корректно
Чтобы запретить модификацию как указателя, так и значения, на которое он указывает, ключевое слово const надо задействовать в двух местах инструкции декларации:
const char* const abc = "abc";
char* al23 = "123";
abc = al23; // ошибка
abc[1] = 'd'; // ошибка
Динамическое выделение оперативной памяти
Объем оперативной памяти для хранения значений различных переменных рассчитывается на этапе компиляции. Размеры переменных и структур в байтах известны и определяются заданными типами данных. При декларации массивов число их элементов также вводится явно или определяется начальной инициализацией. Однако язык C++ допускает и динамическое выделение оперативной памяти для нужд программы непосредственно в процессе ее работы.
Динамическое создание набора элементов
Доступ к динамически выделенным областям памяти осуществляется через указатели. Динамическое выделение памяти выполняет оператор new. Форма его записи такова:
new тип(число-элементов)
Данный оператор выделяет в оперативной памяти пространство, в котором будет храниться заданное число элементов указанного типа. Оператор new возвращает ссылку на самый первый элемент последовательности. Эта ссылка представлена типом void*, и программист может явно преобразовать ее к нужному типу, хотя компилятор автоматически выполняет такое приведение.
Например:
int* а;
а = new int(100);
Переменную а можно использовать как указатель на последовательность значений из 100 элементов. Память для этой последовательности отводится не на этапе компиляции, а во время работы программы — непосредственно в момент
выполнения данного оператора. Если память выделить не удалось (например, запрошен слишком большой объем), возвращается нулевой указатель.
Следующий код модифицирует значение шестого от начала элемента динамически выделенной последовательности:
*(а+5)4;
Допустима и более наглядная запись — переменная а может трактоваться как массив:
а[5] = 5;
Удаление элементов из памяти
Оператор delete освобождает оперативную память, ранее выделенную программе оператором new, и возвращает ее операционной системе (в случае сложных объектов это действие формально выполняет деструктор — см. главу 18). В качестве параметра оператора delete выступает указатель на начало соответствующей области.
Например:
int* pint;
pint = new int(5) ;
// используем указатель pint
delete pint;
// далее использовать указатель pint нельзя - // он указывает на область памяти, которая // более не принадлежит программе.
Динамическое создание массивов
Существует форма оператора new, предназначенная для динамического создания массивов. Следом за ключевым словом new и именем типа в таком случае задается перечень измерений массива в квадратных скобках.
Пример:
char* pArr;
pArr = new char[80];
В указатель pArr заносится ссылка на массив из 80 элементов типа char.
С помощью оператора new можно динамически создавать многомерные массивы, например:
char (*pArr)[80];
pArr = new char [ 5 ][80];
pArr[0] [50] = ' Б' ;
Удобство данной формы оператора new в том, что размер массива можно задавать непосредственно во время работы программы:
char* pArr;
int п = 15;
pArr = new char[n];
Переменная pArr будет ссылаться на массив из 15 элементов. На этапе компиляции определить его размер невозможно, так как неизвестно, какое значение будет храниться в переменной п.
Удаление массива из памяти
Динамически созданный массив удаляется из оперативной памяти оператором delete. В данной форме оператора delete следом за ключевым словом ставится пустая пара квадратных скобок.
Например;
char* pArr;
int n = 15;
pArr = new char[nJ;
// обрабатываем массив pArr
delete[] pArr;
// массив, на который ссылался
// указатель pArr, удален.
Вопросы
Что такое указатели и почему их нежелательно использовать?
В чем разница между типизированными и нетипизированными указателями?
Как декларируются указатели?
Как происходит доступ к значению указателя?
Какие операции разрешены над указателями?
Как организуется работа с нетипизированными указателями?
В чем разница между указателями и ссылками?
В чем связь массивов и указателей?
В чем особенности использования указателей на структуры?
Как организовать работу с указателями-константами?
Для чего нужен оператор new?
Как освобождаются динамически выделенные области памяти?
ГЛАВА 7
Управление последовательностью действий: условные операторы
Мы изучили оператор присваивания, с помощью которого можно сформировать линейную последовательность вычислений. Для программы со сложной логикой этого недостаточно. Как минимум, требуются механизмы ветвления — управления последовательностью выполнения в зависимости от определенных условий. Для создания эффективного и компактного кода нужны еще средства многократного повторения групп команд.
Пустой оператор
Операторы языка C++ разделяются друг от друга точкой с запятой. Символ точки с запятой сам по себе обозначает пустой оператор — оператор, который не выполняет никаких действий.
; // этот оператор ничего не делает
Составной оператор (блок кода)
Нередко требуется поместить несколько операторов в такое место программы, где синтаксис языка допускает наличие только одного. В таких случаях используют- составные операторы.
Составной оператор — это группа операторов и описаний языка C++, объединенных в один блок.
Составной оператор начинается с открывающей фигурной скобки {, а завершается закрывающей фигурной скобкой }. Как правило, за закрывающей скобкой точка с запятой не ставится.
Составной оператор удобен тем, что позволяет компоновать в одну группу набор из нескольких команд и обращаться к такому набору как к одному оператору. Например:
{
N = 50;
Myval = N % 10;
}
Составные операторы могут быть вложены друг в друга. Допускается также пустой составной оператор:
{ }
Он нередко применяется для наглядности или как пустая заготовка для будущего кода.
Оператор группировки
Язык C++ допускает объединение двух выражений в одно целое с помощью оператора группировки «,». Два выражения разделяются символом запятой, и результатом такого оператора будет значение последнего выражения. Но левое выражение в операторе группировки всегда вычисляется первым. Например:
int х, у;
х = 0, у = 1;
Сначала выполнится оператор х=0, а затем — оператор у=1.
Оператор группировки используется в языке C++ в основном для того, чтобы объединять несколько выражений в тех частях кода, где разрешено только одно выражение и не допускается составной оператор. Например:
int х, у=3 ,- z;
z — (х = 1, у + 2 ) ;
В переменную z будет занесено значение 5(у+2). При этом предварительно выполнится выражение до запятой (х=1), где переменной х будет присвоена единица.
Видимость объектов программы
Когда создается большая программа и задействуется множество переменных, декларировать их в одном месте не очень удобно. Длинные перечни определений затрудняют поиск и группировку переменных, имеющих отношение к разным частям алгоритма. Кроме того, переменные, предназначенные для промежуточных расчетов, нежелательно декларировать вместе с ключевыми переменным, которые хранят важные данные.
Язык C++ допускает ввод новых переменных практически в любых местах исходного текста. Однако если переменная декларируется внутри какого-либо блока (составного оператора), то считается, что ее можно использовать только в пределах этого блока. Если же переменная не входит ни в один блок, то она доступна в пределах текущего файла с исходным текстом.
Локальная переменная — это переменная, которая определена внутри блока {} и доступна только внутри него.
Глобальная переменная — это переменная, которая определена вне любого блока {} и доступна в любой точке текущего файла.
Например:
int X = 5;
int main(int argc, char* argv[]) {
int N = X;
return 0;
}
// здесь переменная N уже недоступна
Переменная х — глобальная. Она может использоваться в любом месте, в том числе и внутри блока функции main (). Переменная N — локальная, ее определение локально и доступно только внутри функции main ().
Так как блоки кода можно вкладывать друг в друга, в каждом из них допустимы свои определения локальных переменных. В следующем примере заданы три блока:
{ int nl = l; {
int n2=nl;
{ int n3=nl+n2; } } }
Переменная nl доступна во всех блоках, переменная п2 — в двух вложенных блоках, и переменная пЗ — только в третьем вложенном блоке. Вне своих блоков локальные переменные считаются неопределенными:
{
int nl = l;
{ int n2=nl;
{ int n3=nl+n2; }
пЗ=1; // ошибка компиляции!
п2=1; // ошибка компиляции! }
Область действия (или видимость) переменной — это часть исходного кода, в котором эта переменная определена и доступна.
Область действия переменной можно ограничить текущим процессом, если создается многопроцессорная программа. Для этого в ее определении размещается конструкция declspec (thread), и локальный экземпляр переменной будет храниться в локальном пространстве потока (Thread Local Storage).
Скрытие переменных
Когда переменные декларируются в нескольких вложенных блоках, не исключены ситуации совпадения имен. Например:
{
int nl;
{
int nl;
{
int nl;
}
}
}
В данном коде определены сразу три переменные nl. В языке C++ действует правило скрытия переменных, согласно которому глобально определенные переменные или локально определенные переменные более высокого (внешего) уровня всегда скрываются текущим локальным определением. Считается, что работа ведется с ближайшим локальным определением:
int п;
int nl = l;
n = nl; // значение п равно 1
int nl = 2;
n = nl; // значение п равно 2
int nl=3;
n = nl; // значение п равно 3 }
// значение nl равно 2
// значение nl равно 1
Обычно трудно разобраться в коде, где используются локальные и глобальные переменные с одинаковыми именами, поэтому желательно всегда использовать неповторяющиеся имена переменных.
Переменная становится доступной с момента ее определения. До декларации некоторой локальной переменной можно использовать ее ранее определенное имя:
int П=1;
{
// в переменную nl записывается значение 1
// из глобальной переменной п:
int nl=n;
// вводится локальная переменная п,
// которая скрывает глобальную переменную п:
int п=2;
nl = п; // значение nl равно 2
}
Если создаются большие программы, нередки случаи многократного определения одной и той же глобальной переменной в заголовочных файлах (см. главу 11). В результате возможны конфликты имен, когда компилятор не может разобраться, случайно ли совпали имена переменных, или же это замысел разработчика. Чтобы подсказать компилятору, что встречаемые в разных файлах имена глобальной переменной — это одно определение, надо в этом определении разместить конструкциюdeci spec (select any):
declspec(selectany) int X = 1;
Оператор доступа к глобальной переменной
Локальная переменная может скрывать не только другие локальные переменные вышестоящих блоков, но и глобальные переменные, определенные вне любых блоков. Например:
int N = 5 ;
int main(int argc, char* argv[])
{
int N = 10;
// локальная переменная N скрывает
// глобальную переменную N
return N; // используется локальная переменная N
}
Локальные переменные вводятся для решения локальных задач, а глобальные переменные могут хранить важные сведения, которые востребованы в любых частях программы. Чтобы получить доступ к скрытой глобальной переменной, надо воспользоваться оператором :: (два двоеточия). Он записывается перед именем переменной как префикс.
Например:
int N - 5;
int main(int argc, char* argv[])
{
int N = 10;
// локальная переменная N скрывает
// глобальную переменную N
::N = N+1;
// в глобальную переменную N заносится значение
// локальной переменной N плюс 1
return ::N; // используется глобальная переменная N }
Условный оператор
Во многих программах требуется гибко управлять порядком выполнения операторов. Выбор одного из нескольких возможных действий достигается с помощью ветвления. Оно реализуется с помощью условного оператора.
Условный оператор — это оператор, который выполняет команду или группу команд в зависимости от определенного условия. Условие в условном операторе представляется выражением логического типа.
Условный оператор записывают в сокращенной или в полной форме.
Сокращенная форма условного оператора
Условный оператор содержит два основных элемента: условие и один или несколько операторов.
Сокращенный условный оператор выполняет действие, если условие истинно. Если условие ложно, никаких действий не производится.
Сокращенная форма условного оператора выглядит следующим образом:
if ( условие ) оператор ;
Если значение условия равно true, то Выполняется команда, следующая за закрывающей круглой скобкой. Если значение условия равно false, то управление передается на следующий оператор. При необходимости значение условного выражения приводится к типу bool. В языке Си тип bool отсутствовал и в качестве условия задействовалось целое выражение (истинным считалось любое ненулевое значение). В целях совместимости язык C++ также поддерживает целочисленные выражения в качестве условия, автоматически преобразуя их значение к типу bool — к значению true, если результат выражения не равен нулю, и к значению false в противном случае.
В качестве оператора, выполняемого в зависимости от условия, может использоваться любая команда или оператор C++. Если требуется выполнить в зависимости от условия группу команд, то ее надо охватить скобками { и } (представить как составной оператор).
Например:
if( х == 0 ) str = "икс равен нулю!";
В переменную str строка "икс равен нулю" будет занесена только в том случае, если значение переменной х равно нулю.
Другой пример:
if( (I > 0) && (I < 12) )
{
а [ I ] = 5 ;
I + +;
}
Составной оператор (группа команд) выполнится, если значение переменной I укладывается в диапазон от 1 до 11.
Расширенная форма условного оператора
С расширенным условным оператором связаны два действия, одно из которых обязательно выполняется.
Расширенный условный оператор выбирает для выполнения один из связанных с ним операторов в зависимости от значения условия.
Расширенная форма условного оператора записывается следующим образом: if( условие )
оператор-1;
else оператор-2;
Если условие истинно, выполняется оператор-1 (или группа команд, объединенная в составной оператор) и не выполняется оператор-2. Если условие ложно, выполняется оператор-2 и не выполняется оператор-1. Например:
if( х == 0 )
str = "икс равен нулю!";
else str = "икс не равен нулю!";
if( (I > 0) & (I < 12) ) { all] = 5;
I + +;
}
else
{
all] = 0; I
Если перед ключевым словом else используется составной оператор, то после его закрывающей фигурной скобки точка с запятой не ставится.
Вложенные условные операторы
В качестве команд, выполняемых в зависимости от значения логического выражения, нередко используются другие условные операторы, что приводит к многоуровневым вложениям условий. Например:
if( условие-1 )
if( условие-2 )
оператор-1;
else оператор-2;
В каком случае выполняется оператор-2? В языке C++ действует правило: команда, указанная после слова else, относится к ближайшему ключевому слову if. В данном случае слово else относится к условию-2, и оператор-2 выполнится, если условие-1 истинно, а условие-2 — ложно. Если же надо связать оператор-2 с условием-1, то вложенный условный оператор надо заключить в логические скобки:
if( условие-1 )
if ( условие-2 ) оператор-1;
}
else оператор-2;
Оператор выбора
Оператор выбора — это альтернативная форма условного оператора. Он наглядно представляет процесс выбора из множества альтернатив (условный оператор предлагает на выбор не более двух возможностей).
Оператор выбора — это оператор, выполняющий одну команду из заданного набора в зависимости от значения выражения.
Оператор выбора записывается следующим "образом:
switch( выражение ) {
case константа-1: оператор-1; break;
case константа-2: оператор-2; break;
case константа-3: оператор-3; break;
default: финальный-оператор; break;
Сначала вычисляется значение выражения, расположенного после ключевого слова switch. Оно должно иметь целочисленный (int, short, byte; знаковые и беззнаковые), символьный (char) или перечислимый тип. Далее это значение последовательно сравнивается с константами (или константными выражениями), введенными после ключевых слов case, и при совпадении значений выполняется оператор, следующий за двоеточием, после чего работа оператора switch завершается. Если ни одного подходящего значения не найдено, то выполняется финальная команда после ключевого слова default.
Например:
х = 2;
switch( х+2 ) {
case 1: у = 3; break;
case 4: у = 0; break;
case -2: у = 10; break;
} ;
Сначала в этом операторе рассчитывается выражение х+2 (его значение равно 4). Затем внутри блока switch начинается последовательное сравнение этого значения со значениями 1, 4 и -2. Совпадение происходит на втором шаге, и в результате выполняется оператор у=0. Далее управление сразу передается команде, следующей за закрывающей фигурной скобкой. Все остальные варианты выбора не анализируются и пропускаются.
В операторе выбора выполняется только один (или вообще ни одного) оператор из списка команд внутри блока switch. Если в зависимости от условия надо выполнить группу команд, то ее не обязательно заключать в фигурные скобки:
х = 2;
switch( х+2 ) {
case 1: у = 3; break;
case -2: у = 10; break;
default:
у = 0;
z = 1 ;
break;
В этом примере значение выражения х+2 (оно равно 4) не совпадает с проверочными значениями 1 и -2. Поэтому управление будет передано в раздел default, в котором объединено два оператора, подлежащих выполнению.
В списках значений оператора выбора могут указываться не только отдельные целые числа, но и любые константные выражения целого и символьного типов, которые может рассчитать компилятор:
с = 'х' ;
switch( с ) {
case 'a': str = "Буква A"; break;
case 'х': str = "Буква X" ; break;
Допустимо задавать не одно значение, а их перечень: каждое значение со своим словом case. Соответствующая команда выполнится при совпадении с любым из них:
switch( х+2 ) {
case 15: у = 3; break;
case 2+3: case 10: у = 0; break;
case -2: case 0: у = 10; break;
};
Оператор break обозначает окончание раздела, относящегося к обработке выбора той или иной константы. Этот оператор можно опускать, тогда выполнение оператора switch не прервется, а будет продолжено выполнением операторов следующего раздела case.
Например:
п=5;
switch( п ) {
х = 1;
х++; break;
х = 0; break;
Сначала будет выбран первый раздел, где выполнится оператор х=1. Однако далее оператор break отсутствует, поэтому оператор switch не прерывается и выполняется следующий по тексту оператор х+ + из раздела "case 6:". Если бы и в этом разделе оператор break отсутствовал, тогда выполнение команд внутри оператора switch продолжилось далее. Но так как следом за оператором х++ следует оператор break, работа оператора switch завершается.
Данная возможность C++ потенциально чревата ошибками: программист иногда забывает поставить завершающий оператор break, и работа оператора switch : не прерывается в нужный момент.
7.7. Вопросы
Что такое пустой оператор?‘
Как записывается составной оператор?i
Зачем нужен оператор группировки?!
Что такое видимость переменных?j
Что такое локальные и глобальные переменные?t
Как происходит скрытие переменных?<
Как организовать доступ к скрытому глобальному идентификатору?$
Как работает сокращенный условный оператор?<
Как работает расширенный условный оператор?j
Как выполняются вложенные условные операторы?
Как записывается оператор выбора?
ГЛАВА 8
Управление последовательностью действий: операторы цикла
Во многих задачах программирования, прежде всего при обработке массивов, необходимо многократно выполнять одну и ту же последовательность команд. Число повторений может достигать сотен и тысяч, и каждый раз вводить вручную набор операторов нерационально. Оператор цикла автоматизирует подобную деятельность.
Оператор цикла - это оператор, организующий многократное выполнение определенной команды или группы команд. Многократно выполняемую команду или группу команд в операторе цикла называют телом цикла.
Число повторов тела цикла может рассчитываться разными способами. Оно может быть задано заранее или определяться по некоторому условию. Обычно в зависимости от выбранного способа используют безусловный оператор цикла или условный оператор цикла. Однако в языке C++ нет строгого разделения операторов цикла на условные и безусловные. Любой из них можно задействовать как для выполнения тела цикла фиксированное число раз, так и для итераций до выполнения заданного условия. Программист самостоятельно определяет, в каком случае лучше выбрать ту или иную форму оператора цикла. На практике для задач, где лучше подходит безусловный оператор цикла, удобнее задействовать оператор for. В задачах, где лучше подходит условный оператор цикла, удобнее применять операторы while и do.
Безусловный оператор цикла
Если заранее известно, сколько раз должны быть повторены команды в теле цикла, используют безусловный оператор цикла, который также называют циклом со счетчиком.
Безусловный оператор цикла — это оператор цикла, в котором тело цикла повторяется фиксированное число раз.
Безусловный оператор цикла записывается следующим образом: for( инициализация ; условие ; итерация ) тело-цикла ;
В разделе инициализации записывается действие, которое будет однократно выполнено перед началом работы цикла. Если данный оператор применяется в качестве безусловного оператора цикла, на фазе инициализации задействуется специальная переменная, которая хранит текущее число выполнений тела цикла.
Она называется счетчик цикла, или просто счетчик. В качестве типа переменной- счетчика используют, как правило, целые и перечислимые типы.
Когда в ходе работы программы встречается безусловный оператор цикла, счетчик принимает начальное значение. Далее происходит проверка условия. Если оно истинно, выполняется тело цикла, по окончании которого выполняется действие, заданное разделом итерации в заголовке цикла. Если же условие ложно, работа оператора цикла заканчивается.
В качестве условия обычно задействуется проверка значения счетчика на превышение конечного значения (числа повторов тела цикла). В части итерации используется оператор увеличения значения счетчика на один шаг. Если счетчик имеет целый тип, то его значение обычно увеличивают на единицу. Когда значение счетчика превысит конечное значение, тело цикла уже не выполняется и управление передается следующему оператору.
В следующем примере находится сумма чисел от 1 до 100:
int I, sum;
sum = 0;
for ( 1 = 1; I <= 100; I ++ ) sum += I;
В этом примере суммируются все элементы массива:
int i, sum; int а[150];
// ... инициализация элементов массива а
sum = 0;
for( 1=0; 1 < 150; 1 + + )
sum += а [ 1 ] ;
В начале работы оператора цикла переменная 1 получает нулевое значение (однократно выполняется оператор 1=0). Затем проверяется условие 1<150 — пока оно истинно, выполняется тело цикла — увеличение значения переменной sum на значение очередного, 1-го элемента массива а. По завершении очередной итерации значение счетчика 1 увеличивается на 1 и цикл повторяется.
Переменная-счетчик может быть не только целого, но и вещественного типа:
double 1, sum;
double а[150];
// ... инициализация элементов массива а
sum = 0;
for( 1=0; 1 < 150; 1 ++ )
sum += а[(int)1];
В качестве тела цикла можно использовать составной оператор:
for( 1=0; 1 < 150; 1 ++ )
{
sum += а[1];
п = а [1] ;
}
Любые элементы заголовка оператора цикла можно опускать. Например, допускается следующая форма оператора for:
for( ; ; ) тело-цикла ;
В подобном случае тело цикла будет выполняться бесконечно, пока работа оператора не будет прервана принудительно. Так произойдет и в случае, если опущено только условие в заголовке цикла.
Раздел инициализации может быть вынесен в отдельный оператор перед оператором цикла, а раздел итерации — внутрь тела цикла, в его завершающую часть:
i = 0;
for( ; i < 150; ) {
sum += a [ i ] ;
i + +; }
Однако лучше полноценно пользоваться возможностями оператора for — это повышает наглядность кода, снижает вероятность внесения ошибки и ускоряет выполнение программы (компилятор успешно оптимизирует работу операторов циклов).
В некоторых случаях можно опускать само тело цикла: все необходимые расчеты записываются в заголовок цикла. Например, программу суммирования первых 100 чисел можно переписать так:
int I, sum;
sum - 0;
for( 1=1; I <= 100; sum i= 1++ ) ;
Здесь в одном выражении объединены команда увеличения переменной sum, хранящей текущее значение суммы, и оператор увеличения значения счетчика I. Данный код более компактен, однако менее нагляден, нежели оригинальный.
В операторе цикла иногда задействуется оператор группировки в качестве кода разных частей заголовка вводится несколько команд, например:
int I, sum;
for( sum = 0, I = 1; I < = 100; T + + ) ; sum += I;
В качестве выражения инициализации задействованы два оператора присваивания. В данном случае их последовательное выполнение вполне допустимо.
Вложенные циклы
Циклы можно вкладывать друг в друга, как и любые другие операторы. Обычно вложенные циклы используют для обработки многомерных массивов.
Например:
int м[Ю][20] ;
// инициализация массива М...
int i, j , sum=0;
for( 1 = 0; i < 10; i ++ )
for( j = 0; j <20; j ++ ) sum += M[i] [j ] ;
В данном примере с помощью двух циклов суммируются все значения в матрице м последовательным перебором всех ее элементов.
Объявление счетчика цикла внутри цикла
Во многих случаях переменная-счетчик требуется лишь на время работы оператора цикла, и множить декларации переменных в коде нежелательно. Язык C++ допускает декларацию переменной внутри инициализирующего раздела оператора цикла, например:
int sum;
sum = 0;
// переменная I не определена
f or ( int 1=1; I <= 100; I++ ) {
// в данном составном операторе
// переменная I определена
// как локальная.
sum + = I;
}
// переменная I снова не определена
Переменная т объявлена как локальная внутри цикла for. Она недоступна за его пределами — там ее можно объявлять заново.
Условный оператор цикла
Оператор for обычно используют для повтора тела цикла заранее определенное число раз. Это число вычисляется в момент первого выполнения заголовка цикла и на протяжении последующих итераций остается неизменным (хотя значение переменной-счетчика можно изменить в теле цикла, однако такой прием считают некорректным).
Во многих случаях число выполнений тела цикла заранее неизвестно. Прекращение итераций должно происходить во время работы программы в зависимости от определенного условия.
При этом теоретически допустимо, что итерации продолжаются бесконечно.
Условный оператор цикла — это оператор цикла, в котором тело цикла повторяется заранее неизвестное число раз. Работа этого оператора завершается в зависимости от определенного условия.
Условный оператор цикла в языке C++ представлен в двух формах. Отличаются они только способом проверки условия окончания (или продолжения) цикла. В условном операторе цикла выполнение новой итерации в зависимости от условия представлено в более явном виде, нежели в операторе for.
Первая форма условного оператора цикла
Условный оператор цикла первой формы (цикл с предусловием) записывается следующим образом:
while( условие ) тело-цикла;
Условие — это выражение языка C++, которое вычисляет логическое значение. Если это значение равно true, то тело цикла выполняется, после чего условие проверяется вновь. Эти операции повторяются, пока значение выражения не станет равно false. После этого итерации прекращаются, и управление передается следующему оператору.
В качестве тела оператора цикла указывается обычный или составной оператор языка C++. В следующем примере суммируются первые 10 целых чисел:
int Sum = 0;
int 1 = 1;
while( I <= 10 )
{
Sum += I;
I ++ ;
}
Если в этом примере убрать из тела цикла оператор увеличения переменной I на единицу, то цикл будет выполняться бесконечно, что приводит к так называемому зависанию программы (прервать цикл можно только с помощью средств операционной системы).
Оператор while похож на оператор for, в котором также имеется часть, связанная с проверкой условия выполнения следующей итерации. Однако оператор for в значительной степени рассчитан на использование счетчика (и, в частности, на обработку массивов). В условном операторе цикла понятия счетчика цикла не существует и явные средства его поддержки не предлагаются. Программист должен самостоятельно реализовывать подсчет числа повторений. Поэтому область применения условных операторов цикла отличается от области применения оператора for.
Вторая форма условного оператора цикла
Условный оператор цикла второй формы (цикл с постусловием) записывается следующим образом:
do
тело-цикла while( условие );
Условие, как и в предыдущем случае, — это выражение, вычисляющее логическое значение. Только в данной версии оператора цикла оно проверяется не до, а после выполнения тела цикла, что гарантирует как минимум однократное его выполнение. Если значение условия равно true, то выполняется новая итерация, в противном случае оператор цикла завершается и управление предается далее.
В операторе do телом цикла может быть либо одиночный оператор, либо группа (два и более) операторов, заключенных в фигурные скобки. Данный оператор синтаксически напоминает оператор while, поэтому рекомендуется всегда использовать фигурные скобки для повышения наглядности.
Вот как суммируют первые 10 положительных чисел с помощью цикла с постусловием:
int Sum = 0;
int 1 = 1;
do {
Sum + = I;
I ++;
1010} while( I <
10
10
Управление работой циклов
Циклы, используемые в программах, могут быть очень сложными и многократно вложенными друг в друга. Для подобных случаев в языке C++ предусмотрены дополнительные средства управления работой циклов.
Оператор прерывания цикла
Нередко работу оператора цикла требуется прервать раньше, нежели выполнятся все итерации. Для этого предназначен оператор break.
Как только оператор break встречается в теле цикла, цикл немедленно прекращается и управление передается оператору, следующему за ним. Проверку массива а на наличие в нем нулевого элемента можно выполнить следующим образом: int п;
int А [ 10 0 ] ;
bool f = false;
for( n = 0; n < 100; n ++ ) if( A[n] == 0 )
{
f == true;
break;
} ;
Переменная f примет значение true, если нулевой элемент был обнаружен. Последующие элементы массива а уже не проверяются.
Подобную проверку можно реализовать и без оператора break:
bool f = false;
for( n = 0; n < 100; n ++ ) iff A[n] == 0 )
{
f = true;
} ;
Однако в этом случае, даже если самый первый элемент массива а нулевой, программа продолжит перебор всех элементов массива. При обработке больших массивов бессмысленное выполнение циклов до конца создает ненужную дополнительную нагрузку на процессор.
Оператор продолжения цикла
Для управления работой цикла предназначен и оператор continue. Он немедленно прерывает текущую итерацию, однако не завершает работу цикла, а принудительно вызывает следующую итерацию.
Оператор continue удобен, если требуется на текущей итерации пропустить все оставшиеся команды в теле цикла. Например, если требуется обработать только нулевые элементы массива, то соответствующий код с использованием оператора continue можно записать так:
for( n = 0; n < 100; n ++ )
действия-1;
// перейти к следующей итерации,
// если n-й элемент массива А не равен 0:
if ( А[п] != 0 ) continue;
// выполнять, если n-е значение
// элемента массива А равно нулю: действия-2;
Если не использовать оператор continue, то данный код можно менее наглядно записать с помощью условного оператора:
for( n = 0; n < 100; n ++ )
действия-1;
if( A[n] == 0 ) {
// выполнять, если n-e значение
// элемента массива А равно нулю: действия-2;
} } ;
Оператор перехода goto
Оператор перехода не рекомендован к использованию, так как он нарушает принципы структурного программирования. Доказано, что любой вычислительный алгоритм можно запрограммировать с помощью операторов присваивания, цикла и условия, выполняемых строго последовательно.
Однако не всегда удается наглядно запрограммировать алгоритм с помощью операторов цикла и условия. Например, если требуется прервать работу двух вложенных циклов, оператор break не поможет: он завершает лишь ближайший к нему оператор цикла. В таком случае придется либо вводить дополнительные переменные, отслеживающие необходимость прерывания внешнего цикла, либо использовать оператор перехода. Поэтому в исключительных случаях оператор перехода допустим. Его использование считается корректным, если с его помощью управление передается вперед, к последующим операторам, без нарушения прямой последовательности выполнения команд.
Оператор перехода — это оператор, который передает управление другому оператору, помеченному меткой.
Оператор перехода записывают следующим образом:
goto метка;
Метка располагается перед оператором, который требуется выполнить сразу после оператора goto, и отделяется от него двоеточием. Метка представляет собой обычный идентификатор языка C++, который нигде более не определен. Метка должна располагаться в одной функции с оператором goto.
Ниже в примере оператор перехода используется для выхода из нескольких вложенных циклов. Команда break для этого не подходит, так как прерывает работу лишь одного, ближайшего к ней, оператора цикла. В двух циклах отыскивается нулевой элемент матрицы м. Если он найден, происходит выход из всех циклов:
bool f;
int I, J;
int M[10][10] ;
f = false;
f or ( I = 0; I < 10; I ++ )
for( J = 0; J < 10; J ++ ) iff M[I,J] == 0 )
f = true;
// нулевой элемент найден -
// дальнейший анализ не имеет смысла
goto NextOp; // выйти из всех циклов } ;
NextOp: // сюда выполняется переход goto: х = 100;
Вопросы
Зачем нужны операторы цикла?
Как работает оператор цикла for?
Как работает оператор цикла while?
4.
5.
6.
Как работает оператор цикла do?
Какой командой прерывается оператор цикла?
Какой командой принудительно завершается текущая итерация в операторе цикла?
7.
Укажите недостатки оператора перехода goto.
ГЛАВА 9 Функции
Концепция функций (они иногда называются подпрограммы), существовала уже в самых первых языках программирования.
Подпрограмма — это явно выделенный в исходном тексте набор команд и операторов, имеющий уникальное имя. Этот набор вызывают на выполнение обращением к его имени. Выполняемый подпрограммой набор операторов называют телом подпрограммы.
Использование функций, во-первых, существенно сокращает размер исходного текста, который к тому же становится более понятным. Во-вторых, появляется возможность создания повторно используемых модулей кода. На идее подпро- грамм, в частности, построены программные библиотеки стандартных функций.
Важнейшая особенность функции — настраиваемость. Достигается она за счет параметров. Когда функция вызывается по имени, то конкретные значения, предназначенные для обработки, задаются как параметры в круглых скобках непосредственно за этим именем.
Параметр — это переменная, значение которой должно быть определено при вызове функции. Для вычислений значение параметра передается в тело подпрограммы.
Если, например, вызывается функция расчета синуса sin (), то величина исходного угла задается в качестве ее параметра. Последовательность расчета остается всегда одной и той же, а значения, используемые в этой последовательности, программист задает как параметры в каждом конкретном случае обращения к функции.
Особенность функции в том, что в ее теле вычисляется некоторое значение, доступное во внешней программе.
Функция — это подпрограмма, в теле которой вычисляется значение, доступное вызывающей программе.
Значение заданного типа, которое вычисляется в теле функции и затем передается в вызвавшую программу, называют значением, возвращаемым функцией.
Возвращаемое функцией значение формируется в ее теле с помощью оператора return. Функция может быть описана как возвращающая «пустое» значение типа void. В таком случае не требуется явно формировать возвращаемое значение в теле функции. Подобные функции, не возвращающие реальное значение,
создаются для выполнения действий, непосредственно не связанных с расчетами, например для вывода данных.
Функция схожа с переменной. Обращение к имени функции формирует значение, как и обращение к имени переменной. Только это значение не хранится, а вычисляется телом функции.
Объявление и вызов функций
При описании функции в исходном тексте программы вводится ее заголовок — имя и список параметров, а также тело — набор команд. Описание функции не вызывает никаких действий программы — набор команд в теле функции сам по себе не выполняется, он просто служит для определения ее содержимого.
Заголовок функции описывает набор ее параметров, возвращаемое значение и дополнительные характеристики.
Тело функции — это ее часть, содержащая исполняемый код. Она выделяется фигурными скобками.
Функция объявляется следующим образом:
тип-функции имя-функции( список-параметров )
{
тело-функции
Тип функции — это тип возвращаемого ей значения. Имя функции — любой допустимый идентификатор языка C++. Все определения типов, констант и переменных в теле функции считаются локальными. Они действуют исключительно в рамках текущей функции и не «видны» за ее пределами. Вызов тела функции происходит только внутри одного из блоков { }. Для этого надо указать имя функции и, при необходимости, набор значений параметров.
Значение, возвращаемое функцией
Функции предназначены для расчета значения и возврата его в вызывающую программу. Возвращаемое функцией значение формируется внутри ее тела с помощью оператора return. В правой его части ставится выражение, соответствующее типу возвращаемого функцией значения. Этот оператор может располагаться в любой точке внутри функции (не обязательно последним). Но как только он встречается, работа функции немедленно прекращается.
Например, определена функция Dva (), которая возвращает значение 2:
int Dva()
int n ;
n = 2;
return n;
}
Вызов функции Dva () осуществляется вводом ее имени в некоторой последовательности команд. Пара круглых скобок за именем подсказывает, что используется не переменная, а функция:
int Rez;
Rez = Dva(); // вызываем функцию Dva()
// переменная Rez получает значение 2
Оператор return нередко используют, если работа функции должна быть прервана до момента ее естественного завершения (достижения закрывающей фигурной скобки). Так бывает, если, например, все необходимые расчеты выполнены. Как только оператор return встречается в коде любой функции, та завершается и управление передается оператору, следующему за командой, где эта функция вызывалась.
Если функция по замыслу разработчика не должна явно возвращать значение, то в качестве типа возвращаемого ей значения вводится тип void. Оператор return в теле такой функции записывается без возвращаемого выражения — за ним сразу ставится точка с запятой.
Например:
void print ()
{
// действия
// . . .
return;
}
Оператор return нужен, если функция завершается не естественным порядком (достижением финальной фигурной скобки), а раньше. В противном случае его можно опустить.
Например:
void Print()
{
int n=l;
if( n==l ) return; // немедленное завершение
// дополнительные действия...
// оператор return в конце можно не ставить.
}
Если функция вычисляет некоторое значение, оно не обязательно должно использоваться. Например, функция Dva () может быть вызвана так:
n = Dva();
или так:
Dva();
Функция Dva () всегда рассчитывает некоторое значение, но в первом случае это значение используется — оно заносится в переменную п. Во втором случае код Dva () выполняется, но сформированное ей значение теряется. Подобная схема
вызова используется, если надо просто выполнить некоторый набор команд, например вывести значения на экран.
Параметры функции
Параметры предназначены для передачи в функцию набора значений, которые могут варьироваться в момент вызова функции. Ведь полезность функций заключается в их способности обрабатывать различные входные значения (аргументы), рассчитывая на их основе итоговый результат. Например, функции вывода текстовых сообщений на экран сначала получают эти сообщения в качестве параметра и лишь потом отображают его на экране.
Параметр функции — это внутренняя переменная, в которую записывается передаваемое в функцию входное значение. Параметр функции доступен в теле функции по своему имени.
Аргумент функции — это входное значение параметра функции, которое задается при ее вызове.
Функция может иметь несколько параметров или ни одного. Список параметров заключается в круглые скобки. Каждый параметр записывается в формате:
тип-параметра имя-параметра
Параметры отделяются друг от друга запятой. Если список параметров пуст, то вводится лишь пара круглых скобок.
Внутри функции параметры трактуются как локальные переменные, поэтому некорректно пытаться переопределять их в качестве собственных локальных переменных с такими же именами.
Например:
double Kvadrat(double х)
return х*х;
}
Функция Kvadrat () рассчитывает квадрат параметра и возвращает его в качестве своего значения:
double Y = Kvadrat(1.2);
Формальные и фактические параметры
При создании функции значения передаваемых в нее параметров еще неизвестны. При описании в заголовке функции указываются формальные параметры. При вызове функции указываются фактические параметры, которые и передаются в нее.
Формальный параметр — это переменная, которая определена в заголовке описания подпрограммы.
Фактический параметр — это значение, которое подставляется наместо соответствующего формального параметра при вызове подпрограммы.
Пусть имеется описание функции Kvadrat:
double Kvadrat(double x)
return x*x ;
}
Параметр x — это формальный параметр. Он доступен в теле функции по своему имени. Пусть происходит вызов этой функции:
double Y = Kvadrat(1.2) ;
Значение 1.2, введенное в списке параметров вызываемой на выполнение функции, — это фактический параметр. Это значение будет занесено в формальный параметр — переменную х, которая используется в теле этой функции.
Передача фактических параметров по значению и по имени
Параметры могут передаваться по значению и по имени. В первом случае значение фактического параметра не меняется, во втором оно может быть модифицировано в теле функции.
Передача параметра по значению
В случае передачи параметра по значению все фактические параметры при вызове функции копируются в формальные параметры (промежуточные внутренние переменные) и только потом передаются внутрь функции. Это гарантирует неизменность переменных, представленных как фактические параметры.
Например:
// описание функции VarX void VarX( int X ) {
X = 1;
}
// основной текст программы
int X = 5;
VarX( X ); // вызов функции VarX() // с аргументом X
int n = X; // значение переменной X -
// по-прежнему 5
После вызова функции VarX(x) значение переменной х в основном коде не изменится, хотя внутри тела VarX происходит явное обращение к содержимому параметра (фактически — к внутренней переменной х). Так как параметр передан по значению, для него предварительно формируется промежуточное хранилище (внутренняя переменная х, формальный параметр функции), временное содержимое которого и модифицируется оператором х=1. По завершении функции это
хранилище (формальный параметр х) просто уничтожается. Таким образом, передача аргумента по значению подразумевает создание его копии и работу с копией фактического параметра.
Передача параметра по имени
Передача фактического параметра по имени (или ссылке) разрешает модификацию его значения внутри процедуры или функции. Перед идентификатором формального параметра, передающегося по имени, надо поставить символ &.
Вернемся к описанию метода Varx (). Его параметр, целочисленный аргумент х, передается по значению. Зададим для него модификатор &:
void VarX( int& X )
{
X = 1;
}
Теперь копия аргумента х в теле функции создаваться не будет и модификации подвергнется сам фактический параметр.
// основной текст программы int X = 5 ;
VarX(X); // вызов метода VarX() с аргументом X // параметр X целочисленного типа // передается по ссылке!
int n = X;
// значение переменной X изменилось с 5 на 1 !
В качестве фактического параметра, передаваемого по имени, может быть использовано только имя существующей переменной (ссылочное значение). Поэтому недопустимы вызовы наподобие varx (х+1) — ведь выражение х+1 формирует не ссылку (имя), а конкретное значение, которое не может быть изменено в теле подпрограммы.
Дополнительный способ передачи параметра по имени
Если параметр представляет собой большой объект (например, структуру с множеством полей), то на его постоянное копирование в формальный параметр при частых вызовах функции уйдут значительные ресурсы. Программист может использовать простой способ передачи параметра по имени с помощью указателей. Формальный параметр описывается как указатель, а при вызове функции передается адрес нужного объекта. Объем самого указателя всегда невелик (обычно он занимает 4 байта), поэтому накладных расходов не возникнет. Недостаток такого подхода заключается в необходимости работы в теле функции с указателями, что потенциально чревато ошибками.
Модифицируем функцию Varx () так, чтобы ее параметр представлял собой указатель:
void VarX ( int* X )
{
*Х = 1;
}
При вызове такой функции параметр должен представлять собой указатель.
Для его формирования воспользуемся оператором получения адреса &:
int X;
VarX(&Х) ;
В переменную х заносится значение 1.
Передача массивов в качестве параметров
Массивы в языке C++ никогда не передаются по значению. Внутри тела функции программисту доступна лишь ссылка на массив, что подразумевает возможность модификации значений массива, переданного как фактический параметр. Однако язык C++ содержит средства достаточно наглядного представления параметров- массивов. Формальный параметр для представления массива записывается так: тип идентификатор[]
Например, требуется функция sum (), которая суммирует значение всех элементов переданного ей массива. Число элементов массива заранее известно и передается вторым параметром:
int sum(int inv[], int len )
int s = 0;
for( int i = 0; i < len; i ++ ) s += inv[ i ] ;
return s;
}
В качестве параметра функции sum () можно задавать любой массив, составленный из элементов типа int:
int а[3] = {1, 2, 3};
int n = sum(a, 3) ;
Массив можно передавать и как обычный указатель: int sum(int* inv, int len ) {
Здесь параметр inv описан не как массив, а как указатель, но так как в языке C++ нет принципиального различия между указателями и именами массивов, последующий код не нуждается в модификации. Однако желательно придерживаться более наглядного варианта с квадратными скобками.
В заголовке функции разрешено явно задавать размер массива. В таком случае второй параметр не требуется, но функция сможет работать только с массивами предопределенной длины:
int sum(int inv[3])
int s = 0;
for( int i=0;i<3;i++)
s += inv[ i ] ;
return s;
}
При этом параметр inv по-прежнему передается по имени. Поэтому любая модификация его элементов внутри функции приведет к модификации исходного массива:
int sum(int inv[3])
{
int S = 0;
for( int i = 0; i < 3; i ++ )
s += inv[ i ] ;
inv[1] = 5;
return s;
}
При вызове
int a[3] = { 1, 2 , 3 } ;
int n = sum(a);
список значений массива а станет таким: 1, 5, 3.
Если передача одномерных массивов не вызывает затруднений и допустима в разных формах, то передача многомерных массивов не столь проста. В случае, когда размеры измерений массива неизвестны, невозможно определить местонахождение его элементов по отношению к началу (адресу нулевого элемента, заданному именем массива). Поэтому при передаче многомерных массивов можно опускать длину лишь по первому измерению — остальные размеры массива должны быть заданы явно.
Например, исходный массив описан как матрица:
int а[3] [2] = {{1,2}, {3,4},{5,6}};
Для его обработки нельзя задать параметр как двумерный массив с неизвестными размерами:
int sum(int inv[][]) // ошибка!
Как минимум, необходимо ввести длину массива по второму измерению, а длину по первому измерению можно передавать дополнительным параметром:
int sum(int inv[][2], int len)
{
int s = 0;
for( int i = 0; i < len; i ++ )
for( int j=0;j<2;j++)
s += inv[i][j];
return s;
}
Вызов функции sum () запишется так:
int n = sum(a, 3) ;
Неизменяемые параметры
Параметр можно передавать по имени с запретом его модификации. Такой подход полезен, если требуется объединить эффективность схемы передачи параметров
по ссылке с необходимостью защиты фактического параметра от изменения. Для этого задействуются константные параметры, значения которых можно лишь считывать. Однако константные параметры можно сопровождать модификатором &, подразумевающим передачу значения по ссылке.
Константные параметры формируются вводом ключевого слова const перед именем параметра:
void VarX( const int& X )
{
int n = X;
}
Переменная x доступна внутри тела функции только для считывания. Запрещены операторы модификации значения х наподобие х=1.
Необязательные параметры
В списке параметров функции допускаются необязательные элементы (аргументы, передаваемые по умолчанию), которые в вызове функции можно не указывать. Они всегда должны следовать в конце списка параметров. Обязательные параметры между ними не допускаются. Необязательные параметры записываются по схеме:
тип идентификатор = значение
Если соответствующий параметр опущен, то вместо него автоматически подставляется заданное значение.
Например:
void CallTest(int N, char* S = "успешно") { ...
Вызов CallTest (1) будет преобразован в вызов Call Test (1, "успешно").
Неопределенные параметры
Язык C++ позволяет определять функции, список параметров которых заранее неизвестен. Дополнительные неизвестные параметры размещаются в конце списка. В заголовке функции наличие неопределенных параметров задается тремя идущими подряд точками (запятую перед ними ставить не обязательно). Три точки в списке аргументов в языке C++ называются эллипсисом.
Например:
void test(int n, .. .);
void test(int, ...);
void test(int . . . ) ;
Функция test () во всех трех случаях описана как имеющая один фиксированный целочисленный параметр и неопределенное (возможно, равное нулю) число дополнительных параметров. Она может быть вызвана любыми способами, например:
test(1) ;
test(1, 2 , 3 ) ;
testd, 2.2, "333");
Компилятор не делает никаких проверок числа и типов параметров, поэтому использование неопределенных параметров потенциально чревато ошибками и должно применяться в крайних случаях.
Доступ к значениям неопределенных параметров
Так как список неопределенных параметров функции заранее неизвестен, невозможно получить к ним доступ в теле функции с помощью предопределенных идентификаторов. В языке C++ имеется стандартная библиотека stdarg.h, предлагающая функции доступа к списку неопределенных параметров.
Технология обработки списка неопределенных параметров такова.
В файл включается библиотека stdarg. h:
#include <stdarg.h>
В теле функции объявляется идентификатор, ответственный за список параметров. Он задается в формате
va_list идентификатор ;
Определяется последний явно заданный формальный параметр в списке параметров функции. Для этого вводится инструкция вида va_start(идентификатор, параметр);
где идентификатор — это имя, заданное в пункте 2. Параметр — это последний введенный программистом формальный параметр в заголовке функции перед эллипсисом.
Организуется последовательный доступ к элементам списка неопределенных параметров с помощью функции va_arg (). Она получает два параметра: имя списка параметров, заданное в пункте 2, и тип, к которому приводится очередное значение списка.
По окончании обработки списка параметров вызывается функция va_end () с именем идентификатора, связанного со списком.
Например:
void test(int n, ...) {
va_list plist;
va_start(plist, n) ;
double x = va_arg(plist,double);
char* s = va_arg(plist,char*);
va_end(plist);
В данном коде условно подразумевается, что при вызове функции test () будут введены три аргумента: один явно задан как целочисленный, а два заранее неопределены. Предполагается, что их типы — вещественное и указатель на строку. Значения этих параметров заносятся во внутренние переменные X И S.
Вызов функции может быть таким:
testd, 3.14, "abc") ;
На практике заранее обычно неизвестно, сколько параметров будет у функции, ведь именно в этом смысл использования эллипсиса. Чтобы подсказать программе, когда заканчивается список параметров, можно ввести некоторое значение, обозначающее завершение списка. Как правило, в качестве такого значения используется ноль, если передается набор строк. В следующем примере функция обрабатывает список строк неизвестной длины:
void test(...) {
va_list plist;
va_start(plist, n) ;
for( ; ; ) {
// считываем очередной параметр: char* s = va_arg(plist,char*);
// если это ноль, завершаем работу: if( s==0 ) break;
// обработка очередной строки ...
}
va_end(plist);
}
В вызове такой функции можно задавать произвольное число строк. Последовательность завершается параметром с нулевым значением:
test(l, "123", "abc", "абвгд", 0);
Параметры быстрого вызова
В некоторых технологиях, где особое внимание уделяется производительности, i востребован особый, скоростной режим вызова функций. Он подразумевает, что | значения параметров вызова передаются в функцию, по возможности, в регистрах j процессора как самых быстрых элементах хранения данных. Такой режим опреде- j ляется ключевым словомfastcall, которое ставится в заголовок функций:
void fastcall test(int n) {}
Вопросы
Что такое функция?
Как записываются функции?
Как задается возвращаемое функцией значение?
Какой командой экстренно завершается работа функции?
Зачем нужны параметры функции?
Что такое формальные и фактические параметры?
В чем разница между передачей параметров по имени и значению?
Как передать массивы в качестве параметров?
Что такое неизменяемые параметры?
Что такое неопределенные параметры?
Что такое необязательные параметры?
ГЛАВА 10
Функции: основные возможности
Мы рассмотрели способы объявления и вызова функций и механизмы передачи им параметров. Теперь познакомимся с различными приемами и особенностями использования функций.
Объявление и определение функций
Функцию нельзя использовать, если она еще не описана до ее вызова в тексте программы. Следующая запись неверна:
void А()
{
ВО; // ошибка - функция В еще не описана!
)
void В()
{
АО ;
}
В функции а () происходит обращение к функции в о , однако функция в () описывается лишь далее по тексту. Поэтому компилятор к моменту анализа определения функции А () еще не знает о существовании функции в ().
Чтобы обойти данное ограничение, пока еще неопределенную функцию можно заранее объявить в виде одного заголовка, завершенного точкой с запятой. Он называется прототипом функции. Прототип подскажет компилятору, что реализация данного заголовка (собственно определение функции) расположена далее.
// Подсказываем компилятору, что В - это функция.
// Она будет описана где-то далее.
void В ( ) ;
void А()
{
ВО; // корректный вызов
В()В()void { АО;
В()
В()
Отдельно расположенный заголовок функции и заголовок функции в ее полном определении должны полностью совпадать.
Имена параметров в заголовке функции можно опускать. Например:
void В(int);
void А() { в (1) ;
} void В(int п) {
■А ( ) ; }
Так как параметр п в теле функции в () не используется, его можно опустить и в заголовке основного определения:
void В(int) {
А () ;
}
Это правило распространяется и на параметры, передаваемые по умолчанию: void CallTest( int, char* = "успешно") { ...
Если в качестве типа параметра по умолчанию задействован тип указателя, надо обязательно ставить пробел между символом * и символом =, так как если они будут записаны вместе, то компилятор распознает их как составной оператор присваивания *=.
Например:
void CallTest( int, char*S= "успешно") // корректно void CallTest( int, char*= "успешно") // ошибка void CallTest( int, char* = "успешно") // корректно
Перезагрузка функций
В программах часто востребованы функции одинакового предназначения, различающиеся лишь типами используемых параметров. Например, можно сложить два целых числа и получить целый результат, но можно сложить и два вещественных числа и получить вещественный результат. Алгоритм реализации не всегда одинаков, поэтому требуется разнести обработку данных разных типов по разным функциям. Задачу подбора подходящей подпрограммы для параметров разного типа можно переложить на компилятор. Разработчику достаточно задать одно и то же имя функции1.
Перезагрузка (или перегрузка) функции — это создание новой функции с именем уже существующей функции. Заголовок новой функции отличается от заголовка существующей функции только типом параметров.
В ранних версиях языка C++ использовалось ключевое слово overload, обозначающее перегружаемые функции. В сегодняшней реализации C++ оно запрещено.
Пусть в программе определены три перегруженные функции с одинаковым именем Test:-
void Test(int n);
void Test(double x) ;
void Test(char* s);
Их реализация будет разной:
void Testtint n) { cout << "Целое";
}
void Test(double x) { cout << "Дробное";
}
void Test(char* s) {
cout << "Текстовое";
В коде программы разместим вызовы этих функций, различающиеся лишь типами параметров:
Test(1) ;
Test(1.0);
Test ( "1") ;
В каждом случае корректно вызывается правильный экземпляр функции — он автоматически определяется компилятором по типу фактического параметра. В итоге на экран выведутся три строки:
Целое
Дробное
Текстовое
Не менее корректно определяются перегруженные функции, список параметров которых разнится числом аргументов:
void Test(int);
void Test(int, double);
void Test(int)
{
cout << "Первая";
void Test(int, double)
{
cout << "Вторая";
)
При вызове
Test (1) ;
Test(1,1.0);
на экран будут выведены строки:
Первая
Вторая
Не перегружаются функции с одинаковыми параметрами, но разными типами возвращаемых значений.
Рекурсия
Язык C++ допускает рекурсию.
Рекурсия — это способ организации функции, при котором функция в ходе выполнения своих операторов обращается сама к себе.
Механизм рекурсивного управления очень мощный. Существуют языки программирования (например, LISP), которые полностью построены на идее рекурсивного управления последовательностью вычислений, без условных операторов и операторов цикла. Рекурсия позволяет компактно записывать алгоритмы, которые при реализации классическим императивным походом требуют гораздо большего числа операторов. Основной недостаток рекурсии — медленная работа рекурсивного процесса и повышенные требования к оперативной памяти.
Рассмотрим классический пример применения рекурсии для расчета факториала числа. Факториал числа N равен произведению чисел 1 х2хЗх.. х (N-l) х N. Так, факториал числа 5 равен 1х2хЗх4х5 = 120. С помощью оператора цикла функция расчета факториала запишется так:
int F(int N)
{
int I, Mui;
Mui = 1;
for ( I = 2; I <= N; I ++ )
Mui * = I;
return Mui;
}
Вот как факториал реализуется с помощью рекурсии:
int F (int N)
{
if( N == 1 ) return 1;
else return N * F(N-l); // вызываем саму себя
// с новым параметром
)
Рекурсивный вызов f (з) автоматически развернется в такую цепочку вызо
вов:
F(3) 3*F(2) 3*2*F(1) 3*2*1 3*2 6
Встраиваемые функции
В целях повышения быстродействия результирующего кода функцию можно описать как встраиваемую в код. Для этого в ее заголовке ставится ключевое слово inline:
inline int F(int N)
{
int I, Mui;
Mui = 1;
f or ( 1=2; I <= N; I ++ )
Mui *= I;
return Mui;
}
Компилятор старается включать исходный текст встраиваемых функций по месту их вызова. В результирующий набор машинных инструкций попадут не вызовы таких функций, а напрямую их операторы, наравне с другими операторами, окружающими вызов. Пусть, например, имеется текст:
х = 5;
У = F (х) ;
Z = х + у;
Если функция f () помечена как inline, компилятор попробует сформировать примерно такой исходный код (предварительно объявив в текущем блоке локальные переменные I и Mui):
х = 5;
Mui = 1;
for( I = 2; I <= X; I++ ) Mui *= I;
у = Mui;
z = x + у;
Вызов функции f () корректно заменен на ее тело.
Существует немало ограничений на «раскрытие» встраиваемых функций в исходном тексте, однако за всеми возможными ограничениями следит компилятор. В случае невыполнения одного из требований он просто не совершает никаких преобразований, игнорируя ключевое слово inline. Поэтому часто вызываемые функции, влияющие на общую производительность, можно смело объявлять как встраиваемые.
Указатели на функции
Язык C++ допускает создание указателей не только на данные (области памяти, отведенные для переменных), но и на исполнимый код (машинные коман-
ды скомпилированных функций). Программист может определять переменные, которые по ходу работы программы будут указывать на разные функции, и динамически вызывать эти функции, например, в зависимости от некоторого условия. Указатели на функции полезны, если требуется сделать некоторую часть алгоритма настраиваемой или быстро задать связи между элементами пользовательского интерфейса и сменяемым набором функций.
При записи указателей на функции используется обычный синтаксис C++, как и при записи указателей других типов. Объявление указателя на функции должно включать все элементы заголовка указуемой функции. Впоследствии в переменную типа указатель на функцию можно заносить ссылки на функции, заголовки которых точно соответствуют заголовку, заданному при декларации указателя.
Так как приоритет оператора задания параметров функции () выше приоритета оператора определения указателя *, при записи указателя на функцию необходимо задействовать скобки. Они подскажут компилятору, что в первую очередь необходимо декларировать переменную как указатель на функцию, и лишь потом фиксировать список параметров этой функции.
Например:
int (* f) (int) ;
Переменная f декларирована как указатель на функции. Эта функция имеет один целочисленный параметр и возвращает целое значение. Следующая форма определения переменной типа указатель на функцию неверна:
int *f (int);
По упомянутой выше причине она трактуется компилятором так:
int* f(int);
Эта запись представляет собой объявление функции f, которая имеет один целочисленный параметр и возвращает указатель на целое значение.
После того, как переменная типа указатель на функцию определена, в нее можно заносить ссылки на существующие в программе функции. Для этого используется оператор присваивания:
int Fl(int){
return 1;
}
int (* f) (int);
f = Fl;
В переменную f заносится ссылка на функцию fi. В операторе присваивания вводится только имя функции F1 без круглых скобок. В противном случае запись F1 () означала бы не ссылку на конкретную функцию, а ее вызов.
Типы параметров и возвращаемого значения присваиваемой функции должны точно соответствовать типам, введенным при определении указателя. Пусть, например, имеется описание функции F2:
int F2(char);
При попытке зафиксировать ссылку на нее возникнет ошибка компиляции;
f = F2; // неверно!
Компилятор сообщит, что невозможно преобразовать ссылку, заданную именем F2(int(*) (char) ), к типу указателя, заданному переменной f () (int (*) (int) ).
Когда в переменную, указывающую на функцию, занесена ссылка, ее можно использовать для вызова исполнимого кода. Вызов записывается с учетом упомянутых приоритетов операторов () и *.
Например:
(*f) (5) ;
Таким образом происходит вызов функции fi (5), на которую указывает переменная f.
Синонимы указателей на функции
Ключевое слово typedef позволяет задавать синонимы не только обычным типам, но и типам указателей на функции. Для этого используется следующая форма записи:
typedef декларация-указателя-на-функцию ;
Имя указателя в данной конструкции будет доступно в качестве альтернативного названия соответствующего типа. Например:
typedef int (*F)(int);
Имя f задано в данном случае как альтернативное название типу int (*) (int). Оно может использоваться как обычное имя типа:
F f;
Данная запись эквивалентна полной записи:
int * f (int) ;
Массивы указателей на функции
С помощью синонимов типа удобно компактно определять массивы функций.
Например, определены три функции с одинаковыми заголовками:
int Fl(int){return 1;}
int F2(int){return 2;}
int F3(int){return 3;}
Переменную af опишем как массив указателей на эти функции:
typedef int (*F)(int);
F af[3] = {Fl, F2, F3} ;
Приоритет оператора выделения элемента массива [ ] выше приоритета оператора взятия значения по адресу *, поэтому дополнительные скобки при обращении к переменной af не нужны:
(*af[1] ) (100) ;
Таким образом будет вызвана функция F2 (10 0).
Статические переменные
Переменные, определенные внутри тела функции, считаются локальными. Они существуют только в пределах функции и не доступны вне ее тела. Каждый раз, когда функция вызывается, локальные переменные создаются заново (для них выделяется оперативная память), а когда она завершается, они уничтожаются (память освобождается).
Переменные могут быть описаны с ключевым словом static. В таком случае они считаются статическими, не уничтожаются между вызовами функции, а их значения сохраняются. При этом они по-прежнему доступны только внутри тела функции.
Например, определена функция fi (), содержащая статическую переменную count:
int Fl(bool start){
static int count;
if( start ) count=0;
else count + +;
return count;
}
Если значение параметра равно true, переменная count получает начальное, нулевое значение. Впоследствии при каждом обращении к функции fi () с аргументом false значение статической переменной count будет увеличиваться на единицу.
Статические переменные можно считать аналогом глобальных переменных, за тем исключением, что статические переменные доступны только в рамках тела функции, где они определены.
Нестабильные переменные
Современные компьютеры оборудуются несколькими процессорами, или же сами процессоры состоят из нескольких «ядер» — процессоров, настроенных на тесную синхронную работу. Поэтому современные языки программирования определенное внимание уделяют организации параллельной работы программы. Операционные системы допускают создание в рамках одной программы нескольких «потоков», которые выполняются одновременно и в значительной степени независимо. Поддержка потоков внутри прикладной программы — своего рода аналог поддержки одновременной работы нескольких программ в операционной
системе.
Поток — это последовательность команд программы, которая может выполняться одновременно с другими потоками в пределах этой же программы.
Потоки создаются в прикладной программе для решения определенной задачи, что подразумевает обмен данными между потоками и синхронизацию их деятельности. Для этого предназначены так называемые нестабильные переменные, которые описываются с помощью модификатора volatile. Значение нестабильной переменной в некотором потоке может быть модифицировано в процессе работы программы неявно (например, другими потоками или операционной системой). Поэтому когда в коде программы происходит обращение к такой переменной, среда выполнения предпринимает особые меры к анализу ее значения.
Например:
volatile int х;
int у, Z ;
void F() {
х = 5;
У = 10;
z = у + х;
Переменная х описана как нестабильная, поэтому хотя из вышеприведенного примера ясно, что к моменту расчета значения переменной z в переменной х должно находиться значение 5, не исключено, что на самом деле оно было изменено неким внешним процессом. Поэтому компилятор не будет оптимизировать данный код (ведь значение переменной z можно определить еще на фазе компиляции) и дополнительно проверит истинное значение переменной х при обращении к ней.
Нестабильные переменные часто используются так: в одном потоке непрерывно опрашивается подобная переменная в ожидании, когда она примет некоторое контрольное значение. В другом потоке происходит обработка основной информации, и когда выполняется заданное условие, в нестабильную переменную заносится нужное значение, сигнализирующее о сложившейся ситуации другому процессу.
Дополнительные расширения функций
Хотя существует общепринятый стандарт языка C++, каждая прикладная система разработки поддерживает собственный набор языковых расширений. Он требуется почти всегда, так как программы создаются под конкретную операционную систему с собственным набором технологий и соглашений на внутренние системные вызовы и организацию пользовательского интерфейса.
В частности, существенно различаются формы вызова функций, например, в зависимости от версии операционной системы или способа хранения функции в библиотеках различного предназначения. Язык C++ не зависит от платформы исполнения, поэтому каждая среда разработки включает ряд расширений языка, ориентированных на конкретную операционную систему. Корпорация Microsoft,
создатель системы Windows, выпускает собственный набор средств разработки на разных языках, включая, C++, который называется Microsoft Visual Studio (Visual C++ для языка C++) и во многом ориентирован на специфику системных вызовов Windows. Языковые расширения версии Microsoft C++ поддерживаются и в компиляторе C++Builder. Для этого активно используется ключевое слово deci spec, которое явно разделяет возможности языка C++, зависящие от конкретной марки компилятора, от стандартных определений. Этим словом пред- вараются специализированные синтаксические конструкции, которые берутся в круглые скобки. Подобная комбинация элементов должна размещаться в самом начале расширяемого определения, например:
decispec(thread) int N;
Переменная n получает дополнительную характеристику thread и может использоваться в многопоточных приложениях (она будет храниться в локальном пространстве потока).
Версия Borland C++ предлагает разработчику следующие дополнительные средства описания функций.
Гибкое размещение описаний
Ключевое словоdeci spec позволяет размещать ряд дополнительных описаний функций, классов и идентификаторов не в строго заданных позициях операции определения, а более свободно (фактически, в любом месте до объявляемого идентификатора). Следом за ключевым словом deci spec в круглых скобках вводится конкретное описание. Следующие три декларации идентичны:
declspec(dllexport) void cdecl ff(} {} void declspec(dllexport) cdecl ff() {} void cdecl declspec(dllexport) ff() {}
Формат вызова динамических библиотек
С помощью конструкции declspec (dllexport) объявляется функция или какой-либо объект, экспортируемый из динамически загружаемых библиотек DLL. Для обратной операции импорта сведений в библиотеку DLL задействуется конструкцияdeclspec (dllimport). Данный формат также применяется для обеспечения совместимости с программами, подготовленными компилятором Microsoft C/C++.
Формат невозвращаемого вызова
В некоторых случаях (например, при возникновении серьезной ошибки) вызывается функция, управление из которой уже не передается обратно в вызывающую программу. Такая функция определяется с конструкциейdeci spec (noreturn). Любой код, расположенный за ее вызовом, недостижим.
Подготовка к созданию собственного пролога и эпилога функции
Конструкцияdeci spec (naked) отключает режим стандартной передачи параметров через регистры и сохранения их в стеке. Она используется, например, при создании драйверов, когда программист создает собственный пролог и эпилог вызова функции на ассемблере с помощью ключевого слова asm.
Работа без прерываний
Если функция выполняется, гарантированно не формируя внутри себя исключительные ситуации, она может быть описана с конструкциейdeci spec (nothrow).
В результате компилятор способен существенно оптимизировать ее код. Стандарт C++ допускает другое оформление подобных функций вводом ключевого слова throw и пары скобок следом за ним. Следующие описания функции ff эквивалентны:
void ff() throw() {}
declspec(nothrow) void ff(){}
Формат вызова функций Си
Внутренняя структура вызова функций в языках Си и C++ различается. Пусть требуется, чтобы компилятор в целях совместимости скомпилировал программный код с функциями, которые можно вызывать через стандартные интерфейсы Си. Очень часто они используются при взаимодействии с динамически загружаемыми библиотеками DLL. Тогда перед именем функции надо поставить одно из ключевых слов cdecl, _cdecl илиcdecl:
cdecl void ff() {}
Формат вызова функций Паскаля
Функции и переменные можно представить в скомпилированном коде в соответствии с соглашениями компилятора Паскаля (Delphi). В частности, имена идеи- тификаторов записываются только на верхнем регистре; меняется также порядок = передачи параметров в функцию. Для этого в описании идентификатора ставится . одно из ключевых слов pascal, _pascal илиpascal.
Стандартный формат вызова функций
В целях обеспечения совместимости кода с различными внешними приложе- : ниями в заголовке функции размещают ключевое слово _stdcall (или stdcall). Таким образом функция приводится к стандартному формату, при- { пятому в системных вызовах интерфейса Windows API 32. В частности, проверя- ( ется корректность числа параметров и их типов объявленному заголовку, в отли- ; чие от формата вызова функций Си, где подобные проверки не выполняются. f
Вопросы
В чем разница между описанием заголовка функции и ее определением?
Можно ли вкладывать описания подпрограмм друг в друга?
Что такое перезагрузка функций?
Что такое статические переменные?
Что такое нестабильные переменные?
Что такое рекурсия?
Как описываются встраиваемые функции?
Как работать с указателями на функции?
Перечислите дополнительные расширения C++ для описания функций.
ГЛАВА 11
Общая структура программы
Объем современной программы обычно настолько велик, что, как правило, невозможно и бессмысленно представлять ее себе как целое. Программа формируется из отдельных фрагментов исходного текста, представленных в автономных модулях.
Понятие модуля
В языке C++ реализована идея разбиения программы на независимые модули, которые можно транслировать независимо друг от друга. Более подробно механизм компиляции и сборки проекта из отдельных частей рассматривается в главе 26. В данной главе изучаются способы организации исходного текста программы при ее построении из множества модулей.
Модуль — это набор определений типов, переменных и функций, выделенный в отдельный файл и представляющий собой автономную единицу компиляции.
Скомпилированный модуль может использоваться в других проектах в качестве поставщика функциональных возможностей, заданных в интерфейсном разделе. Головная программа, выполненная в подобной модульной архитектуре, представляет собой небольшой набор команд с вызовами подпрограмм из модулей проекта.
Принцип разделения программы на модули важен, потому что в крупных проектах невозможно держать все функции в одном файле. В таком случае, во-первых, коллективная разработка подразумевает одновременную работу нескольких человек над исходными текстами проекта. Во-вторых, при внесении любого изменения в исходный код приходится перекомпилировать весь файл, что может занять немалое время. В-третьих, программисту очень неудобно разбираться в объемном файле с большим числом разнообразных функций.
Заголовочные файлы
Разделение программы на модули выполняется с помощью заголовочных файлов.
Заголовочный файл — это файл, в котором хранятся определения различных типов и идентификаторов и заголовки внешних подключаемых функций.
Функциональные возможности модуля, которые должны быть доступны сторонним пользователям, выделяются в отдельный заголовочный файл с расширением .h или .hpp. Этот файл распространяется между всеми пользователями модуля. Каждый разработчик трудится только над набором своих модулей, а другие программисты используют возможности этих модулей посредством добавления в собственный код соответствующих заголовочных файлов. При этом исходный текст самих модулей, как правило, скрыт от стороннего изучения.
Подключение заголовочного файла выполняется следующей инструкцией:
#inclide название-заголовочного-файла
Инструкция include включает содержимое заданного файла в текущий текст.
Заголовочные файлы делятся на стандартные и пользовательские. Стандартные заголовочные файлы содержат описания функций и определений, содержащиеся в различных стандартных библиотеках, которые входят в поставку системы компиляции или приобретаются отдельно. Пользовательские заголовочные файлы содержат описания функций и определений, которые были выработаны в рамках текущего проекта и доступны только в его пределах. Названия стандартных заголовочных файлов обычно берутся в угловые скобки. Названия пользовательских заголовочных файлов обычно заключаются в двойные кавычки.
Например:
/V подключение стандартного
// заголовочного файла stdio.h: ttinclude <stdio.h>
Н подключение пользовательского
// заголовочного файла mycode.h:
#include "mycode.h"
С помощью заголовочных файлов модуль делится на раздел интерфейса — описание возможностей модуля (заголовочный файл .Н) и раздел реализации — определение переменных и функций вместе с их телами (файл с кодом .СРР) Пользовательские заголовочные файлы хранятся в текущем каталоге проекта, а стандартные — в подкаталоге Include каталога установки C++Builder.
Оптимизация процесса компиляции заголовочных файлов
В крупной программе может насчитываться не один десяток заголовочных файлов. В каждом из таких файлов, в свою очередь, могут храниться сотни и тысячи определений типов и функций. В результате компиляция файла даже с коротким исходным кодом, но с большим числом заголовочных файлов может занять длительное время. В системе C++Builder поддерживается типовая возможность предварительной компиляции заголовочных файлов. В таком режиме выбранные заголовочные файлы просматриваются компилятором лишь один раз. Он формирует промежуточное скомпилированное представление содержимого этих файлов и впоследствии использует уже его, а не транслирует код заново.
Заголовочные файлы, которые должны быть скомпилированы однократно, размещаются всегда в самом начале файла с исходным текстом программы. За списком инструкций подключения этих файлов ставится следующая команда:
#pragma hdrstop
Например:
#include <iostream.h>
#include <math.h>
#pragma hdrstop
#include "myheaders,h"
Стандартные заголовочные файлы iostream.h и math.h, ограниченные инструкцией pragma hdrstop, будут транслироваться только один раз. Содержимое файла myheaders.h будет компилироваться всегда — каждый раз при компиляции текущей программы.
Вынесение некоторых заголовочных файлов за пределы инструкции pragma hdrstop нужно, если эти файлы часто модифицируются. В таком случае не получается выигрыша от предварительной компиляции заголовочных файлов. Как правило, предварительно транслируются стандартные заголовочные файлы, описывающие возможности подключаемых стандартных библиотек. Их содержимое не меняется, поэтому такие заголовочные файлы достаточно скомпилировать один раз.
Следом за ключевым словом hdrstop в круглых скобках разрешается задавать название файла, в котором будут храниться скомпилированные заголовочные файлы. Как правило, расширение такого файла равно .csm. Если путь к файлу .csm не введен, он будет создан в одном каталоге с файлом проекта с исходным текстом.
Например:
#include <iostream.h>
#include <math.h>
ttpragma hdrstop("stdhdrs.csm")
Инструкция pragma hdrstop сама по себе не выполняет действий по созданию файлов .csm. Она нужна лишь для того, чтобы подсказать компилятору точку исходного текста, в которой завершается режим предварительной компиляции. По умолчанию система C++Builder автоматически выполняет компиляцию заголовков и самостоятельно ведет файл .csm. Доступ к настройкам режимов компиляции заголовков осуществляется командой Project > Options (Проект > Настройки).
Флажок Generate and use precompiled headers задает основной режим формирования и использования скомпилированных заголовочных файлов. Если используется несколько заголовочных файлов, полезно включать флажок Precompiled header cache (кэш скомпилированных заголовочных файлов), который ускоряет процесс объединения файлов с одинаковыми описаниями. Этот режим нередко используется одновременно с возможностью интеллектуального кэширования в оперативной памяти (флажок Enable smart cached precompiled headers). В этом режиме убыстряется процесс анализа скомпилированных заголовочных файлов, однако в некоторых
*• Project Options for Project-1.exe-
El C++ Compiler (bcc32))гPaths and DefinesiOutput settings1Warningsj■OptimizationsjDebuggingi;CompilingjInstruction set!Data alignmentJCalling convention!Pre-compiledheadersj;Floating point:Language complianceI:C++CompatibilityiException handlingIOutputI: Templates Virtual tables Source Member Pointers Register variables CodeGuard compile support Assembler options Other options;El C++ Compiler (bcc32))гPaths and DefinesiOutput settings1Warningsj■OptimizationsjDebuggingi;CompilingjInstruction set!Data alignmentJCalling convention!Pre-compiledheadersj;Floating point:Language complianceI:C++CompatibilityiException handlingIOutputI: Templates Virtual tables Source Member Pointers Register variables CodeGuard compile support Assembler options Other options;guildConfiguration: |aH ConfigurationsguildConfiguration: |aH Configurationsу] Configurations... | ^nd Option,.. |у] Configurations... | ^nd Option,.. |^■-^e-compfledhea^fS^E^^:P^ Generate: a^? ^ Enable precompiled headers witheitteMti type Яе$ (-He)*Ж P Specifythe name of the file touse for precompiled headers (-H=) |Ea|Sw^^1 ^ Precompiled header cache (-He)J Г Stop prero^(-Hh=)^■-^e-compfledhea^fS^E^^:P^ Generate: a^? ^ Enable precompiled headers witheitteMti type Яе$ (-He)*Ж P Specifythe name of the file touse for precompiled headers (-H=) |Ea|Sw^^1 ^ Precompiled header cache (-He)J Г Stop prero^(-Hh=);i- Г\ Include the gfite(s) (-Hi);i- Г\ Include the gfite(s) (-Hi)с Г~ Replace header name: -Игнате! «пете2 (-Hr)с Г~ Replace header name: -Игнате! «пете2 (-Hr)tv Enable smart cached precompiled headers (-Hsf Г Use but do not generate precompiled headers (-Hu)tv Enable smart cached precompiled headers (-Hsf Г Use but do not generate precompiled headers (-Hu)Browse.Browse.Г DefaultГ Default
El C++ Compiler (bcc32))
гPaths and Definesi
Output settings1
Warningsj
■Optimizationsj
Debuggingi
;Compilingj
Instruction set!
Data alignmentJ
Calling convention!
Pre-compiledheadersj
;Floating point:
Language complianceI
:C++Compatibilityi
Exception handlingI
OutputI
: Templates Virtual tables Source Member Pointers Register variables CodeGuard compile support Assembler options Other options;
El C++ Compiler (bcc32))
гPaths and Definesi
Output settings1
Warningsj
■Optimizationsj
Debuggingi
;Compilingj
Instruction set!
Data alignmentJ
Calling convention!
Pre-compiledheadersj
;Floating point:
Language complianceI
:C++Compatibilityi
Exception handlingI
OutputI
: Templates Virtual tables Source Member Pointers Register variables CodeGuard compile support Assembler options Other options;
guildConfiguration: |aH Configurations
guildConfiguration: |aH Configurations
у] Configurations... | ^nd Option,.. |
у] Configurations... | ^nd Option,.. |
^■-^e-compfledhea^fS^E^^
:P^ Generate: a^
? ^ Enable precompiled headers witheitteMti type Яе$ (-He)*
Ж P Specifythe name of the file touse for precompiled headers (-H=) |Ea|Sw^^
1 ^ Precompiled header cache (-He)
J Г Stop prero^(-Hh=)
^■-^e-compfledhea^fS^E^^
:P^ Generate: a^
? ^ Enable precompiled headers witheitteMti type Яе$ (-He)*
Ж P Specifythe name of the file touse for precompiled headers (-H=) |Ea|Sw^^
1 ^ Precompiled header cache (-He)
J Г Stop prero^(-Hh=)
;i- Г\ Include the gfite(s) (-Hi)
;i- Г\ Include the gfite(s) (-Hi)
с Г~ Replace header name: -Игнате! «пете2 (-Hr)
с Г~ Replace header name: -Игнате! «пете2 (-Hr)
tv Enable smart cached precompiled headers (-Hsf Г Use but do not generate precompiled headers (-Hu)
tv Enable smart cached precompiled headers (-Hsf Г Use but do not generate precompiled headers (-Hu)
Browse.
Browse.
Г Default
Г Default
Projectl.bdspiоj • Proje... ^Projectl.bdspiоj • Proje... ^J^&tivate ▼ i^New.... ^File L.ZZZ..Z.ZZ';§§ ProjectGroupl- "^ Projectl.exeJ^&tivate ▼ i^New.... ^File L.ZZZ..Z.ZZ';§§ ProjectGroupl- "^ Projectl.exeProjectl .cpplProjectl .cppl[ Щ Projectl.res;® ® Uniti.cpp<j>)£^Project... j ^jDataE.1[ Щ Projectl.res;® ® Uniti.cpp<j>)£^Project... j ^jDataE.1
Projectl.bdspiоj • Proje... ^
Projectl.bdspiоj • Proje... ^
J^&tivate ▼ i^New.... ^
File L.ZZZ..Z.ZZ'
;§§ ProjectGroupl
- "^ Projectl.exe
J^&tivate ▼ i^New.... ^
File L.ZZZ..Z.ZZ'
;§§ ProjectGroupl
- "^ Projectl.exe
Projectl .cppl
Projectl .cppl
[ Щ Projectl.res
;® ® Uniti.cpp
<j>
)£^Project... j ^jDataE.1
[ Щ Projectl.res
;® ® Uniti.cpp
<j>
)£^Project... j ^jDataE.1
Puc. 11.1. Настройка режима компиляции заголовочных файлов
случаях среда не замечает внесения модификации в заголовочный файл и продолжает использовать старый вариант скомпилированных файлов.
В ряде ситуаций бывает актуальна обратная ситуация: надо использовать скомпилированные файлы несмотря на то, что в заголовочные файлы вносятся изменения. Так бывает, когда в заголовочном файле выполняется постоянная правка некоторого определения, однако в текущем файле оно не используется, поэтому желательно обойтись без перестройки файла .csm, которая подчас занимает продолжительное время. В таком случае надо включить флажок Use but do not generate precompiled headers (Использовать, но не перестраивать скомпилированные заголовочные файлы).
Структура модуля
Модули — это своеобразные библиотеки, к функциональным возможностям которых возможен свободный доступ через заголовочные файлы. Главная программа исполняется по алгоритму, заданному в главном файле проекта с расширением .СРР. По названию этот файл совпадает с названием проекта. Если, например, название проекта по умолчанию Projectl, то название главного файла будет Projectl .срр. Этот файл доступен для просмотра и редактирования в Менеджере проекта.
Рассмотрим типичную структуру пустой заготовки приложения (исходного текста файла главной формы), которая формируется при создании проекта командой File > New > VCL Forms Application - C++Builder (Файл > Создать > Приложение VCL - C++Builder).
В начале файла (по умолчанию он получает имя Unit 1 .срр) расположены директивы включения заголовочных файлов. В частности, подключается стандартный заголовочный файл vcl.h. Он содержит описание библиотеки визуальных компонентов C++Builder, на основе которой ведется визуальное проектирование приложения. Далее следует инструкция прекращения компиляции заголовков pragma hdrstop, а за ней размещена команда включения пользовательского заголовочного файла Unitl .h. В этом файле хранятся определения прикладного класса TForml, описывающего пользовательскую форму проекта, а также подключаются специализированные стандартные библиотеки. Так как структура и содержание формы и отражающего ее класса в процессе создания программы постоянно меняются, данный заголовочный файл вынесен за пределы инструкции pragma hdrstop.
ttinclude <vcl.h>
ttpragma hdrstop
ttinclude "Unitl.h"
Далее следует инструкция
ttpragma package(smart_init)
Она задает порядок инициализации модулей программы. Пока в текущем проекте имеется только один модуль Unitl .срр, в котором определена главная форма проекта. Если проект расширяется новыми модулями, некоторые из которых могут быть связаны друг с другом, желательно учитывать порядок создания форм проекта и инициализации объектов в каждом модуле. Данная инструкция определяет так называемый интеллектуальный порядок инициализации (smart initialization). Если, например, модуль Unit3.cpp зависит от модуля Unit5.cpp (в модуль Unit3.cpp включен заголовочный файл Unit5.h), то в первую очередь будут выполняться необходимые инструкции из модуля Unit5.cpp, а во вторую — инструкции из модуля Unit3.cpp.
Типовая директива препроцессора pragma resources включает в файл проекта основные ресурсы (описание файла формы с расширением .DEM):
ttpragma resource "*.dfm"
Определение переменной Formi (название по умолчанию) вводит в программу ссылку на экземпляр класса TForml, текущую форму проекта:
TForml *Forml;
Имя этой переменной можно изменить в Инспекторе объектов через свойство Name формы.
Далее следуют два стандартных метода класса формы: конструктор TForml () (метод создания экземпляра класса) и обработчик события создания окна формы FormCreate():
fastcall TForml::TForml(TComponent* Owner)
: TForm(Owner)
{
}
void fastcall TForml:-.FormCreate (TObject ‘Sender)
{
//
В обработчике FormCreate () обычно размещается код инициализации всех прикладных элементов проекта, относящихся к главной форме.
В дальнейшем содержимое модуля расширяется реализацией методов, входящих в определенный в ней класс. Как правило, в проекте C++Builder каждый модуль содержит описание только одного крупного класса и одной формы.
Структура главной программы
Структура любого из модулей, определяющих прикладные формы проекта, всегда практически одна и та же. Но в проекте имеется главный файл Project 1 .срр, структура которого оригинальна. Следом за инструкцией подключения заголовочного файла расположены макрокоманды подключения форм проекта. Используется последовательность макрокоманд вида
USEFORM("имя-файла", имя-переменной);
где имя файла — название файла с описанием формы, а имя переменной хранит ссылку на экземпляр соответствующей формы (оно совпадает со значением свойства Name формы). Например, следующая запись подключает к проекту две формы FormiИFormS:
USEFORM("Unitl.срр", Formi);
USEFORM("Unit2.срр", Form2);
Эти макрокоманды генерируются автоматически при создании проекта и подключении к нему новых форм через меню среды.
Далее следует исполнимая часть программы. Она представляет собой функцию winMain (). Этой функции система Windows всегда передает начальное управление при загрузке любой исполнимой программы.
Точка входа — это функция прикладной программы, которой система Windows автоматически передает управление при запуске этой программы.
Функция winMain () имеет четыре параметра, которые в прикладных проектах используются редко. Первый параметр — указатель на текущий экземпляр программы, второй — указатель на предыдущий экземпляр данной программы (если он существует), третий — указатель на строку с завершающим нулем, которая содержит перечень параметров приложения, четвертый — целая константа, которая задает начальный вид окна (например, запущенное с размерами по умолчанию или минимизированное).
Внутри функции winMain () записываются основные команды управления приложением. По умолчанию в программе уже имеется набор автоматически сгенерированных команд. Они создают формы, входящие в приложение, и активизируют главную форму:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
Application->lnitialize();
Application->CreateForm(classid(TForml), &Forml);
Application->Run();
catch (Exception Sexception)
Application->ShowException(&exception);
catch (...)
(
try
throw Exception("") ;
catch (Exception &exception)
Application->ShowException(&exception); } } return 0;
}
Группа основных исполняемых команд заключена в блок контроля за исключительными ситуациями try, который рассматривается позже. Остальные команды нужны для обработки ошибочных ситуаций, например, при невозможности создать главное окно, при нехватке системных ресурсов и так далее.
Первым вызывается стандартный метод initialize стандартной глобальной переменной Application:
Application->Initialize ();
Затем с помощью метода CreateForm () создается экземпляр Formi единственного прикладного класса TForml единственной формы проекта:
Application->CreateForm(classid(TForml), &Forml);
Если форм в проекте несколько, то в данном месте будет расположена последовательность вызовов метода CreateForm (). Далее вызывается метод Run() (Запустить) текущего приложения:
Application->Run();
Завершение метода Run () произойдет, когда внутри него каким-то образом (например, вследствие закрытия пользователем формы) будет сгенерировано системное сообщение Windows о завершении работы программы.
Редактирование главного файла проекта вручную допускается только в исключительных ситуациях. Как правило, все необходимые модификации данного файла среда C++Builder выполняет сама.
Вопросы
1.
2.
3.
Зачем нужны модули?
Что такое заголовочный файл и как он создается?
Как эффективно обрабатывать большое число заголовочных файлов, подключаемых к модулю?
4.
5.
Как организуется главная программа в проекте?
Какова типичная структура модуля, автоматически генерируемого средой C++Builder в заготовке проекта?
ГЛАВА 12
Внешние библиотечные модули
Функции могут храниться не только в исходном тексте текущей программы, но и в библиотеках, которые подключаются к программе на этапе компиляции, и во внешних модулях. Различают несколько способов использования внешних модулей в зависимости от их формата.
Внешние определения
Внешние модули могут представлять собой файлы с исходными текстами, объектные файлы (файлы, полученные после компиляции, но еще не преобразованные в исполнимый формат .EXE, обычно имеют расширение .OBJ), библиотеки объектных файлов (с расширением .LIB) или динамические библиотеки DLL. Если функция физически размещена во внешнем модуле, то достаточно записать в исходном тексте ее заголовок и пометить эту функцию как внешнюю с помощью ключевого слова extern.
Например:
extern double Mylmport( double X );
Пусть в проекте имеются две формы (два модуля). Добавление новой формы выполняется командой Add New > Form (Добавить новое > Форма) контекстного меню названия проекта в Менеджере проектов. В модуле Unit2.cpp определена функция Mylmport():
double Mylmport(double X) { return X*2; }
Она доступна только в рамках своего модуля, поэтому попытка использовать . ее в модуле Unitl .срр приведет к ошибке компиляции “Call to undefined function Mylmport” (Вызов неопределенной функции Mylmport):
fastcall TForml::TForml(TComponent*Owner): : TForm(Owner);
{{
double x = Mylmport(3.14);;
){
В модуль Unitl.срр можно включить заголовок функции Mylmport () и без клю- ■ чевого слова extern:[
double Mylmport( double X ) ;;
При этом компиляция и сборка проекта выполнятся нормально. Однако клю- ) чевое слово extern позволяет разделить обычное описание заголовка функции i
(оно, как правило, включается в файл с исходным текстом) от описания, которое включается в заголовочный файл и таким образом встраивается в другие файлы проекта. В данном случае правильнее всего определить функцию Myimport () так:
extern double Myimport( double X );
Это определение включается не в файл Unit 1 .срр, а в заголовочный файл Unit2.h, который надо подключить в файле Unitl .срр. Таким образом гарантируется, что в случае изменения формата этой функции в исходном тексте (например, при модификации типа возвращаемого значения) компилятор выявит рассогласование между фактической реализацией и описанием заголовка с ключевым словом extern, который расположен в заголовочном файле.
Внешние переменные
В большинстве случаев можно обойтись без ключевого слова extern в описании функции. Оно чаще применяется для обозначения внешних переменных, которые определены в одном модуле, но доступ к ним должен быть организован и в других модулях. В таком случае не удается задействовать схему с заголовками функций, так как описания переменных, в отличие от описания функций, не могут многократно повторяться. Если, например, имеется функция г (), то ее заголовок может корректно включаться во множество файлов. Но если определена переменная f, то ее описание
int f ;
допустимо только в одном модуле. Если такая переменная описана в двух разных файлах, то на этапе сборки программы будет сформировано предупреждение о том, что глобальная переменная f определена сразу в двух модулях. В ряде ситуаций такое многократное описание может привести к ошибке.
Ключевое слово extern позволяет отделить заголовочное описание переменной от ее определения:
int f; // определение переменной f
extern int f; // описание переменной f как внешней
Инструкция "extern int f,-" может многократно включаться в разные модули и не приведет к конфликтам. Она лишь извещает компилятор, что любые обращения к переменной f целого типа корректны, но сама эта переменная определена не обязательно в текущем модуле.
ПРИМЕР 12.1
Использование модуля в качестве стандартной библиотеки
Пусть требуется создать новый модуль, в котором определена функция вычисления синуса двойного угла. Модуль предназначен для использования в качестве самодельной «стандартной» библиотеки.
Создание пустого модуля.
Создадим пустой модуль командой File > New > Unit - C++Builder (Файл > Создать > Модуль C++Builder).
Сохраним его в файле MyMath.cpp. При этом заголовочный файл автоматически сохранится под названием MyMath.h.
Реализация функции в модуле.
В заголовочном файле введем заголовок функции Sin2:
extern double Sin2( double a );
В файле MyMath.cpp введем полное описание этой функции. Общий текст модуля станет следующим:
#include <math.h>
#pragma hdrstop
#include "MyMath.h"
#pragma package(smart_init)
double Sin2( double a )
{
return sin(2*a);
}
Заголовок math. h добавлен для подключения стандартной библиотеки, содержа- ■ щей функцию расчета синуса sin () ■!
Вызов модуля из других проектов.!
Для того чтобы модуль MyMath стал доступен в другом проекте, надо включить - его в проект в исходном или скомпилированном виде. Создадим новый проект : командой File > New > VCL Forms Application — C++Builder (Файл > Создать > : Приложение VCL).'
Добавим модуль MyMath.cpp в проект командой Add контекстного меню названия ' проекта в Менеджере проектов..
Чтобы обращаться из кода основного модуля Unitl .срр проекта к функции Sin2(), надо включить заголовочный файл в этот модуль:,
#include "Unitl.h"■
#include "MyMath.h"
Теперь в коде текущего модуля можно вызывать функцию Sin2():
fastcall TForml::TForml(TComponent* Owner)
: TForm(Owner)
{
// вызов функции Sin2()
// из модуля MyMath:
double x = Sin2(3.14);
}
Чтобы заголовочный файл MyMath.h стал доступен (он может быть расположен в каталоге, отличном от текущего проектного), надо в настройках проекта ввести путь к каталогу, где хранится файл MyMath.h и сам подключаемый модуль в исходном или скомпилированном виде. Для этого дается команда Project > Options (Проект > Настройки), раздел Paths and Defines (Каталоги и определения) и в поле Include search path (Включить путь поиска) добавляется нужный каталог.
Не всегда удобно менять настройки проекта и задавать дополнительный путь к каталогу, где хранится нужный модуль. В программе можно явно задать местонахождение файла с исходным кодом заголовочного файла. Для этого в инструкции
include вводится полный путь к файлу (с учетом того, что символ наклонной черты представляется в текстовых константах языка C++ двумя символами):
#include "d:\\borland\\projWMyMath.h"
Подключение объектного файла.
Далеко не всегда удается задействовать внешние модули, доступные в исходных текстах. Как правило, исходный код различных алгоритмов стандартных библиотек скрыт от конечных пользователей. Поэтому модуль можно представить в объектном, скомпилированном виде, и уже в таком закрытом формате включать его в проект.
Загрузим в редактор файл MyMath.cpp текущего проекта и выполним его компиляцию нажатием клавиш Alt + F9. В подкаталоге Debug_Build проекта (отладочные версии результирующих файлов) появится файл MyMath.obj — его можно задействовать в качестве объектной версии модуля.
Удалим из проекта файл MyMath.cpp с исходным текстом функции Sin2(). Если теперь выполнить сборку проекта, система сообщит об ошибке “Unresolver external Sin2” (Неразрешимая ссылка на внешний элемент Sin2). Отметим, что если бы функция Sin2() не была описана как внешняя (extern) в заголовочном файле MyMath.h, что допускает ее расположение, в частности, в объектных файлах, подключаемых на этапе сборки модулей, то еще на начальном этапе компиляции возникла бы ошибка “Call to undefined function Sin2” (Вызов неопределенной функции Sin2).
Добавим к проекту объектный файл MyMath.obj командой Add контекстного меню названия проекта в Менеджере проектов. Теперь сборка приложения выполнится успешно.
Динамически загружаемые библиотеки
Язык C++ позволяет создавать динамически загружаемые библиотеки — файлы .DLL для платформы Win32. В виде подпрограмм, вынесенных в динамическую библиотеку, удобно реализовывать алгоритмы расчета, не связанные с работой пользовательского интерфейса. При обновлении и улучшении алгоритма потребуется лишь сменить файл библиотеки и не вносить в дистрибутив дополнительные модификации.
Динамически загружаемая библиотека — это библиотека функций, которая подключается к программе уже в процессе ее работы.
Среда C++Builder способна создать пустую заготовку библиотеки, напоминающую по структуре обычный модуль. Однако библиотека отличается от главного файла обычного проекта тем, что ее точка входа называется DIIEntryPoint(). В ней можно выполнять действия по начальной инициализации внутренних переменных при загрузке библиотеки и действия по освобождению ресурсов при выгрузке.
В модуле динамической библиотеки в произвольной форме вводятся функции, реализующие нужные действия. Если какие-то них должны быть доступны внешним программам, загружающим данную библиотеку, то их имена надо сделать экс-
портируемыми, для чего задействуется дополнительный модификатор описания функции dllexport.
ПРИМЕР 12.2
Создание динамически загружаемой библиотеки
Пусть по аналогии с вышеупомянутым модулем MyMath требуется создать библиотеку DLL, содержащую реализацию функции Sin2().
Создание заготовки библиотеки.
Сформируем заготовку (пустой проект) библиотеки .DLL командой File > New > Other (Файл > Создать > Другое). Выберем значок Dynamic-link Library в кате
Рис. 12.1. Настройка точки входа в библиотекуРис. 12.1. Настройка точки входа в библиотекугории C++Builder Projects (Проекты C++Builder).
Рис. 12.1. Настройка точки входа в библиотеку
Рис. 12.1. Настройка точки входа в библиотеку
По умолчанию точка входа в библиотеку называется DIIEntryPoint. Если в начальном окне формирования заготовки библиотеки включить флажок VC++ Style DLL (Библиотека .DLL, выполненная в соответствии с соглашением среды Microsoft Visual Studio), то точка входа получит название DIIMain.
Программирование содержимого библиотеки.
Код библиотечной функции записывается как обычно. Однако библиотека должна содержать указание, что данная функция доступна другим приложениям.
В качестве имени библиотеки зададим MyMath.срр, а в качестве названия проекта — MyMath. bdsproj.
Введем в модуле MyMath.срр полную реализацию функции Sin2.
Название функции Sin2() дополним модификатором dllexport. Таким образом функция Sin2() объявляется доступной для вызова внешними программами.
Текст всей библиотеки запишется следующим образом: #include <vcl.h> #include <math.h> #include <windows.h> #pragma hdrstop
#pragma argsused
int WINAPI DIIEntryPoint(HINSTANCE hinst, unsigned long reason, void* IpReserved)
return 1;
}
M Реализация функции Sin2 .
// Ее заголовок экспортируется.
declspec( dllexport ) double Sin2 ( double a ) {
return sin(2*a);
}
Выполним компиляцию проекта, и в его подкаталоге Debug_Build появится файл mymath.dll. Он может использоваться в других проектах. В дополнение к файлу .DLL в этом подкаталоге находится также файл mymath.lib — библиотечный файл импортируемых функций. Он требуется, если библиотека DLL связывается с проектом статически (см. далее).
Использование динамической библиотеки.
Подключение динамически загружаемой библиотеки к проекту (так называемое связывание библиотеки с проектом) возможно двумя способами: статическим и динамическим. В первом случае приложение настраивается на использование функций библиотеки еще на этапе компиляции и сборки проекта. В таком случае в проект включается файл импорта с расширением .LIB, который содержит описание всех экспортируемых функций библиотеки .DLL и форматов их вызова. Во втором случае файл библиотеки .DLL загружается в программу уже во время ее работы и заранее невозможно проверить корректность вызовов нужных функций.
Статическое связывание.
Если в некотором проекте требуется статически задействовать библиотеку MyMath.dll, созданную на предыдущих шагах, то в его исходном тексте надо описать функцию Sin2(), экспортируемую из этой библиотеки (с модификатором dllimport):
// декларация заголовка импортируемой функции decispec( dllimport ) double Sin2 ( double a );
Вызов функции Sin2 теперь возможен обычным способом:
// обращение к функции Sin2 в момент создания формы void fastcall TForml::FormCreate(TObject ‘Sender) { // вызываем функцию из динамической библиотеки Sin2(3.14);
Чтобы в проекте стала доступной информация о функции Sin2(), размещенной во внешней библиотеке, в него надо включить файл импортируемых функций MyMath.lib. Он добавляется к проекту командой Add контекстного меню названия проекта в Менеджере проектов.
Скомпилируем и запустим программу — загрузка динамической библиотеки произойдет в нужный момент автоматически. Отметим, что хотя в проект включен файл импорта .LIB, код функции Sin2() физически хранится в файле DLL, поэтому файл MyMath.dll должен поставляться совместно с основным исполнимым файлом проекта.
Динамическое связывание.
Динамическое связывание используется, когда нет доступа к файлу импорта LIB, например, если отсутствуют исходные тексты или библиотека .DLL написана на языке программирования, отличном от C++. Разные компиляторы придерживаются подчас несовместимых схем вызова библиотечных функций, по-раэному преобразовывают имя функции при записи ее в библиотеку и по-разному передают параметры при вызове. Поэтому описание экспортируемой функции в библиотеке .DLL обычно сопровождается дополнительными описаниями, гарантирующими, что функция будет представлена в списке экспорта библиотеки в некотором стандартизированном виде (как правило, принятом в языке Си).
Модифицируем заголовок и описание функции Sin2() в модуле MyMath.cpp: extern "С"
{
double declspec( dllexport ) Sin2( double a );
)
double Sin2( double a )
{
return sin(2*a);
)
С помощью ключевого слова extern идентификатор (в частности, имя функции) описывается как внешний. В данном случае задействована расширенная версия extern со строковым элементом “С”, которая в соответствии с правилами вызова функций в языке Си предохраняет экспортируемое имя функции от дополнительной модификации. Различные компиляторы C++ вносят разные изменения в имена экспортируемых функций. В одних случаях они дополняются начальным подчеркиванием, в других — представляются в так называемом уточненном (decorated) виде, включающем условные обозначения типов параметров и другие характеристики. Чтобы не задавать имя функции в программе, вызывающей библиотеку, в виде “_Sin2” или “?Sin2@ @AA@Z”, надо охватить ее заголовок описателем extern “С".
При вызове библиотеки в основной программе придерживаются следующего типового шаблона вызова библиотечных функций:
// описание типа TMyF, указывающего на функцию
// описание указателя на функцию MyF TMyF MyF;
// загрузка библиотеки
HINSTANCE dllcall =
LoadLibrary("название-библиотечного-файла") ; if( dllcall 1=0) { // получение ссылки на библиотечную функцию
MyF = (TMyF) GetProcAddress(dllcall, "имя-функции");
// вызов библиотечной функции через указатель MyF if( MyF != О ) (*MyF) (параметры);
// освобождение библиотеки FreeLibrary(dllcall);
В нашем примере данный шаблон реализуется так:
typedef double (*TMyF)(duouble);
TMyF MyF;
HINSTANCE dllcall = LoadLibrary("Project1.dl1"); if( dllcall != 0 ) (
MyF = (TMyF) GetProcAddress(dllcall, "Sin2");
if( MyF != 0 )
double x = (*MyF) (3.14);
}
FreeLibrary(dllcall);
Алгоритмы расчета, не связанные с работой пользовательского интерфейса, удобно реализовывать в виде функций, вынесенных во внешний файл (динамическую библиотеку). При обновлении и улучшении алгоритма потребуется лишь сменить файл библиотеки и не вносить в дистрибутив какие-либо другие модификации.
Пакеты
В дополнение к динамически загружаемым библиотекам — стандартной форме библиотечного хранения кода и ресурсов в Windows, в системе C++Builder реализована собственная версия подобной технологии, называемая пакет. Если планируется создавать проект только с использованием средств разработки Borland, то вместо динамически загружаемых библиотек .DLL лучше ориентироваться на пакеты.
Пакет — это библиотека функций, которая подключается к программе в процессе ее работы или включается в ее тело на этапе сборки приложения.
Пакеты могут загружаться во время работы программы, включаться в ее тело на этапе сборки или встраиваться в саму среду разработки C++Builder, расширяя ее, например, новыми наборами компонентов. Один и тот же пакет может использоваться как во время выполнения прикладных программ, так и для встраивания в оболочку.
Файлы пакетов для платформы Win32 имеют расширение .BPL. Каждый пакет содержит точку входа DIIEntryPoint.
ПРИМЕР 12.3
■ •Создание пакета
Рассмотрим, как создать пакет, который будет предоставлять его пользователям функцию Mul2(). Эта функция возвращает значение, вдвое большее, чем переданное ей в качестве параметра.
Создание пакета.
Создадим пустой пакет командой File > New > Other (Файл > Создать > Другое). В категории C++Builder Projects (Проекты C++Builder) выберем значок Package (Пакет).
Добавление функции в пакет.
В общем случае к пакету добавляются модули, которые включают реализацию экспортируемых функций. В случае единственной функции можно обойтись более простым решением.
Опишем в головном файле пакета ProjectLcpp функцию Mul2():
int Mul(int n) {
return n*2;
)
В ее заголовке не задействованы какие-либо дополнительные описатели, в отличие от ситуации с библиотекой .DLL. Пакеты представляют информацию во внутреннем формате и поэтому прозрачно поддерживаются средой C++Builder.
Скомпилируем проект. Результирующий пакет помещается не в текущий каталог проекта, а в каталог, заданный в глобальных настройках среды. Для просмотра или изменения соответствующей настройки дайте команду Tools > Options > Environment Options > C++ Options > Paths and Directories (Сервис > Настройки > Настройки среды > Настройки C++ > Пути и каталоги). Используемый каталог представлен в поле Package output directory (Каталог для записи скомпилированных пакетов).
Использование пакета.
Рассмотрим, как результирующий файл пакета .BPL задействуется в других проектах.
Создадим новый проект командой File > New > VCL Forms Application - C++Builder (Файл > Создать > Приложение VCL C++Builder).
Добавим в проект файл экспортируемых из пакета функций. Он размещается в одном каталоге с файлом пакета .BPL и имеет расширение .LIB. Для этого дадим команду Add контекстного меню названия проекта в Менеджере проектов.
Добавим в тело текущего модуля заголовок импортируемой функции:
int Mui (int n);
Теперь к этой функции можно свободно обращаться:
void fastcall TForml::FormCreate(TObject *Sender) {
int z = Mui(2) ;
)
Содержимое пакета либо включается в тело исполнимого модуля создаваемой программы, либо поставляется независимо в виде внешнего файла .BPL. Если необходимо оставить пакет во внешнем файле и поставлять его вместе с исполнимым файлом, следует установить флажок Build with runtime packages (Собрать проект с внешними пакетами времени выполнения) в окне настройки проекта и ввести имя пакета в соответствующее поле.
Вопросы
Как определить идентификатор в качестве внешнего, доступного во всех модулях проекта?
Как создаются и как вызываются динамически загружаемые библиотеки?
В чем разница между статическим и динамическим связыванием библиотек с проектом?
Зачем нужны пакеты и как они строятся из модулей?
Как сделать содержимое пакетов доступным из программы?
РАЗДЕЛ
Технологии объектно-ориентированного программирования
Архитектура операционных систем и приложений
Принципы объектно-ориентированного программирования
Классы и объекты
Методы, свойства, события
Исключительные ситуации
Объектные интерфейсы, пространство имен
Перегрузка операций, шаблоны
ГЛАВА 13
Архитектура операционных систем
С помощью системы C++Builder программисты разрабатывают программы для конкретной операционной системы Windows. Существуют так называемые кросс- платформные среды разработки, способные на базе одного исходного кода генерировать исполнимое приложение для любой из поддерживаемых средой операционных систем. Однако такие среды отличаются повышенной сложностью, высокой стоимостью и имеют ограниченное распространение.
Практически все современные системы программирования широкого применения ориентированы на конкретную операционную систему. Так, среда C++Builder . ориентирована на Windows, которая предоставляет разработчику набор программных интерфейсов Windows API (Win32), а в перспективе сможет поддерживать и платформу .NET (так называемые управляемые расширения'). Чем лучше разработчик разбирается во внутреннем устройстве Windows и .NET, тем производительнее и надежнее создаваемые им программы. Как минимум, программист должен представлять устройство и внутреннюю архитектуру операционной системы, для которой он разрабатывает программное обеспечение.
13.1. Понятие об операционной системе и ее архитектуре
Операционная система выполняет много функций. Она запускает прикладные программы и управляет их работой, распределяет между ними процессорное время и оперативную память, обеспечивает работу файловой системы. Важная функция операционной системы — управление устройствами: ввода (клавиатура, мышь, микрофон, сканер), вывода (монитор, принтер, звуковые колонки), хранения информации (жесткие диски, CD- и флэш-устройства) и взаимодействия компьютеров (сетевая аппаратура).
Операционная система (ОС) — это базовое программное обеспечение, управляющее аппаратными и программными ресурсами компьютера.
В основе ОС лежит ядро. Оно первым загружается в память компьютера при запуске ОС и обеспечивает взаимодействие с оборудованием на самом низком аппаратном уровне.
Ядро реализует функции управления оперативной памятью, процессами (переключение между приложениями, планирование их работы и распределение ресурсов процессора) и устройствами (в частности, доступ к жесткому диску).
Обслуживание устройств конкретных марок и типов выполняется специальными программами-драйверами.
Драйверы обычно создаются производителями оборудования и обеспечивают доступ операционной системы к соответствующим устройствам по стандартному программному интерфейсу. Ядро также занимается организацией взаимодействия прикладных программ с операционной системой. Так достигается определенная независимость от аппаратной конфигурации конкретного компьютера и оборудования разных марок.
Как правило, ядро постоянно находится в оперативной памяти и не выгружается во внешний «файл подкачки», как нередко происходит с прикладными программами. Оно способно напрямую работать с физической памятью компьютера (как оперативной, так и долговременной) и выполняется в привилегированном режиме с повышенным приоритетом.
Взаимодействие пользователя с ОС происходит, как правило, через графический интерфейс. Средства организации графического интерфейса пользователя обеспечивают формирование окон и элементов управления, обработку команд человека, поступающих через клавиатуру и мышь. Они представляют собой дополнительную надстройку над ядром системы и не работают напрямую с оборудованием (клавиатурой, монитором), а взаимодействуют с драйверами. Эти средства . выполняются с более низкими приоритетами, нежели процессы ядра, и не всегда располагаются в оперативной памяти.
В состав ОС обычно включается немало системных утилит, которые автоматизируют настройку функций операционной системы, управление файлами и многие другие операции. В дополнение к системным утилитам в типовую постав- ■ ку современных ОС нередко включают и наборы прикладных программ; браузеры, музыкальные проигрыватели, редакторы и средства просмотра файлов.
I 13.2. Архитектура операционной системы Windows
Разные операционные системы выполняются в разных архитектурах, которые определяют степень взаимосвязи между ядром, драйверами, средствами организации пользовательского интерфейса, системными и прикладными программами. От системы к системе разнятся функции, включаемые в ядро.
Система Windows характеризуется тесной интеграцией всех составляющих архитектуры. В ядро включено множество функций, которые имеют отношение не только к начальной загрузке, но и управлению памятью и процессами. В него входят средства управления файловой системой, средства организации взаимодействия с прикладными программами, вспомогательные библиотеки и другие ресурсы.
Степень интеграции элементов архитектуры Windows столь велика, что в ядро даже включены части прикладных программ, в частности код браузера Internet Explorer, без которого многие прикладные программы не смогут работать. С одной стороны, монолитное многофункциональное ядро обеспечивает высокую производительность, а программы могут обращаться к нему напрямую. С другой стороны, тесная интеграция множества функций в одном элементе системы затрудняет
ее расширение и приводит к множеству проблем с безопасностью из-за отсутствия механизмов разграничения доступа к ресурсам разных уровней архитектуры.
Большинство возможностей системы Windows доступны прикладным программам через так называемый набор программных интерфейсов Windows (Win32 API). Он содержит множество стандартных функций, размещенных в стандартных библиотеках ядра Windows. Программист задает в прикладной программе обращение к той или иной стандартной функции, например к функции создания окна или открытия файла. Код этой функции в прикладную программу не включается, а вызывается непосредственно из ядра в процессе ее работы, что позволяет существенно экономить на объеме исполнимого кода.
Однако интерфейсы Win32 API разрабатывались десятки лет назад, не претерпели за это время особых изменений и уже давно не отвечают нуждам и возможностям современных программных технологий. В частности, в них отсутствуют средства визуального проектирования современных графических интерфейсов и механизмы обеспечения безопасной и надежной работы.
Архитектура платформы Microsoft .NET
Корпорация Microsoft учла недостатки классической архитектуры Windows и выпустила программную платформу Microsoft .NET. Ее вторая версия .NET 2.0 вышла в ноябре 2005 г.
Поддержка C++ в среде .NET предназначена в первую очередь для обеспечения совместимости с ранее наработанным программным кодом. Это достигается за счет использования управляемых расширений (managed extensions), позволяющих компилировать модули, написанные на языке C++ в код для среды .NET. При этом семантика модуля не меняется, но программа, откомпилированная таким образом, способна использовать возможности времени выполнения, предоставляемые средой .NET. Управляемые расширения поддерживаются компиляторами напрямую и не требуют специальных настроек среды или отладчика со стороны разработчика.
Платформа .NET представляет собой надстройку над низкоуровневыми операционными системами наподобие Windows и при этом не зависит ни от типа операционной системы, ни от марки процессора. Функции ядра в ней разделены и выполнены в модульном и расширяемом виде. Платформа .NET базируется на объектном представлении ресурсов и функциональных возможностей. Разработчик организует в прикладной программе доступ к возможностям .NET через объектно-ориентированные механизмы. Для этого в .NET включена обширная библиотека из нескольких тысяч классов.
Ядро платформы .NET складывается из нескольких модулей, важных, прежде всего, для прикладного разработчика.
Платформа разработки .NET Framework ориентирована на быстрое визуальное создание надежных приложений. Включает множество стандартных классов и готовых компонентов для повторного использования.
Общеязыковая среда выполнения Common Language Runtime (CLR) представляет собой единую оболочку поддержки выполнения программ для .NET.
Технология ADO.NET предназначена для работы с базами данных.
Система серверных сценариев ASP.NET используется для быстрого создания веб-приложений.
Другие встроенные технологии обеспечивают безопасность работы, надежность и устойчивость приложений.
Познакомимся с этими элементами платформы .NET более подробно.
Оболочка .NET Framework
Оболочка .NET Framework ориентирована на нужды программистов. Она поддерживает объектно-ориентированную разработку, и в нее включено несколько тысяч стандартных классов. Программисты могут также создавать собственные классы как в прикладных программах, так и на системном уровне, расширяя стандартные возможности платформы .NET. Обычно в прикладных проектах разработчики создают новые классы в качестве наследников стандартных классов .NET Framework..
Библиотеки классов .NET Framework охватывают множество областей, связанных с функционированием операционной системы, предоставляя программистам доступ, в частности, к следующим возможностям .NET:
работа в сети;
обращение к базам данных;
организация безопасного функционирования;
система ввода/вывода;
готовые элементы пользовательского интерфейса, с помощью которых удается создавать компактные прикладные приложения, потому что базовые функции графических элементов управления поддерживаются на системном уровне и нет никакой нужды включать их реализацию в свою про' грамму;
стандартные возможности, востребованные разработчиками: удобный набор базовых типов данных, от строк до массивов, средства их гибкой обработки, богатые наборы стандартных функций (наподобие средств преобразования и форматирования данных);
поддержка интерфейса программирования Win32 для совместимости с действующими версиями Windows.
Отметим входящую в состав .NET Framework библиотеку классов Windows Forms. Она содержит формы графического интерфейса пользователя Windows и элементы управления и сильно упрощает создание графических оконных приложений.
Среда поддержки выполнения CLR
Среда CLR, являющаяся частью оболочки .NET Framework, обеспечивает непосредственную работу программ, выполненных в соответствии с соглашениями .NET. Она связывает программы, которые могут быть написаны на любом языке программирования, с операционным окружением .NET, обеспечивая и обслуживая системные вызовы, создавая экземпляры системных классов и подключая при
необходимости те или иные стандартные библиотеки. Однако двоичный код программы для .NET должен отвечать определенным требованиям.
Компилятор преобразует исходный текст программы для .NET не в машинный код процессора (как делает компилятор программ Win32), а в промежуточный код на языке Microsoft Intermediate Language (MSIL). Этот низкоуровневый язык представляет собой своеобразный виртуальный ассемблер, независимый от конкретной аппаратной платформы. Благодаря этому свойству любое приложение .NET теоретически может быть перенесено на другие операционные системы.
Двоичный код программы .NET представляет собой набор команд MSIL, в котором работа ведется с единой системой типов данных Common Type System (CTS) и обеспечиваются единые требования к безопасности (в частности, универсальным способом происходит проверка выхода индекса за границы массивов). Типичный компилятор .NET создает из исходных текстов на любом из поддерживаемых языков программирования (например, С#, Delphi, C++) результирующий код MSIL, который не содержит особенностей исходного языка, а отвечает только требованиям .NET и СЕН. При этом, конечно, конкретная система программирования может охватить одни возможности .NET, но не реализовать другие в силу ограничений синтаксиса и семантики определенного языка программирования. Так бывает, например, из-за различий в поддержке концепций инкапсуляции и видимости элементов классов.
Среда CLR поддерживает два вида выполнения программ в коде MSIL: она либо заранее переводит приложение в машинный код конкретной платформы (система Windows на процессоре Intel или AMD), либо выполняет такую компиляцию «на лету» (концепция Just In Time), непосредственно в ходе выполнения программы.
В любое приложение .NET включается метаописание как самого приложения, так и используемых им данных. В результате среда CLR работает с данными оптимальным способом, представляя их в единой внутренней схеме. Благодаря этому, в частности, в CLR функционирует автоматическая служба сборки мусора, которая занимается освобождением областей оперативной памяти, временно использовавшихся прикладными программами, но не переданных обратно операционной системе.
Если приложение выполнено в полном соответствии с требованиями CLR, то его код называется управляемым. Управляемый код не всегда является безопасным. Несмотря на все соглашения, программа может выполнить запрещенную операцию наподобие обращения к неразрешенной области памяти, например, из- за ошибки прикладного разработчика или излишне гибких возможностей конкретного языка программирования. Во избежание подобных недоразумений универсальный код MSIL можно дополнительно проверить на предмет соответствия политике безопасности .NET, настроив его, в частности, на допустимый диапазон выполняемых инструкций MSIL. Как правило, модели безопасности работы приложения задают в исходных текстах с помощью специальных атрибутов, которые затем транслируются в метаописание приложения. Поэтому отметим, что выданный компилятором код MSIL сам по себе не является управляемым — степень его управляемости определяется метаинформацией.
Среда .NET предлагает специальный программный интерфейс Platform Invocation Interface, который обеспечивает прямые вызовы функций базовой операционной системы Windows. Впрочем, его использование делает приложение .NET менее надежным: использование определенных системных функций нарушает целый ряд правил безопасности и поэтому рекомендовано к применению только в исключительных ситуациях.
ГЛАВА 14
Введение в объектно-ориентированное программирование
Императивные языки программирования не очень хорошо подходят для,разработ- ки крупных программных проектов. При разработке приложений, которые содержат миллионы строк кода, в настоящее время применяется объектный подход.
Ранняя история объектного подхода
Первым компьютерным решением, воплотившим в себе объектный подход, стал программно-аппаратный графический Планшет (Sketchpad), использовавший оборудование DEC PDP. В 1963 году его разработал 25-летний Айвен Сазерленд (Ivan Sutherland), помогавший в создании симуляторов вертолетов военному научному агентству США DARPA. С помощью светового пера и системы выпадающих меню пользователь Планшета мог рисовать на аналоговом дисплее несложные изображения, перемещать их и точно располагать в определенных позициях экрана, а также хранить. В качестве понятия класса Сазерленд использовал определение «мастер», разделяя описание и реально существующий на экране объект (экземпляр «мастера»).
Основоположниками объектного подхода в программировании считаются норвежцы Оле-Йохан Дал (Ole-Johan Dahl) и Кристен Нигорд (Kristen Nygaard), авторы языка программирования Симула. История Симулы началась в 1962 году с проекта Simulation Language, предназначенного для моделирования методом Монте-Карло. Нигорд, занимавший в то время должность директора по науке Норвежского компьютерного центра NCC, приступил к созданию языка дискретного моделирования. Он привлек к сотрудничеству Оле-Йохана Дала, коллегу по экспериментальной группе Министерства обороны Норвегии, возглавляемой Йаном Гарвиком (Jan Garvik), основоположником информатики в этой стране.
Одновременно готовились две версии Симулы. Первая, Симула I, формировалась по контракту с подразделением Univac корпорации Sperry Rand. Эта версия предназначалась для машины UNIVAC 1107, обошедшейся NCC в 990 тысяч долларов. Американские заказчики желали видеть язык императивного типа, от чего, впрочем, авторы категорически отказались. В результате прародителем Симулы, как и Паскаля, стал Алгол 60. Последний был выбран благодаря блочной архитектуре, хорошим средствам сокрытия данных, а также вследствие высокой популярности в европейских научных центрах. А работы над вторым вариантом спонсировал NCC.
В 1965 году авторам Симулы пришла в голову фундаментальная идея объединить в одно целое данные с процедурами, их обрабатывающими. На основе этой идеи впоследствии родилось понятие класса. Объединение кода и данных по сей день является ключевым отличием классов и объектов от других структур организации кода и данных. После успешного обсуждения возможностей языка Симула I на саммите стран участниц блока НАТО в 1966 году решено было продолжить его совершенствование. В язык вошли новые средства моделирования и имитации выполнения множества одновременно работающих процессов. Авторы также придумали термины класс и объект. Тогда же возникла и технология организации наследования. Создатели Симулы ввели в язык возможность использования разными классами общих свойств — для этого имя класса задавалось в виде префикса. После публичного анонса новая технология вызвала интерес в Дании, Германии и СССР. В СССР в конце 1960-х годов появилась реализация языка Симула для ЭВМ УРАЛ-16.
Новая версия языка Симула была закончена в январе 1967 года. Она поддерживала проектирование «сверху вниз» с помощью виртуальных процедур и технологии статического и динамического связывания объектов и методов. В центре NCC была сформирована рабочая группа Simula Standards Group. Вскоре к ней подключился Якоб Палме (Jacob Palme), специалист Шведского института оборонных исследований. Он добавил в Симулу механизм сокрытия переменных. Первый законченный компилятор обновленной Симулы 67 увидел свет в 1969 году и работал на машинах Control Data. Затем последовали реализации для UNIVAC 1108 и IBM. Все они стоили очень дорого, что, вероятно, помешало активному распространению языка, однако именно с его помощью были спроектированы первые сверхбольшие интегральные схемы Intel.
Зарождение объектного программирования
В конце 1950-х годов Алан Кей (Alan Kay), человек, впоследствии придумавший первый персональный компьютер, работал на Денверской военно-воздушной базе «Рандольф» и писал на машинном коде программы для ЭВМ Burroughs 220. Он создавал микропрограммы, которые содержали в себе всю необходимую для работы информацию и после запуска на других машинах через простой пользовательский интерфейс самостоятельно разворачивали нужные данные. Такие программы Кей первоначально назвал «модулями», объединяющими данные и код. В 1966 г. он занялся научной деятельностью в области молекулярной биологии в Университете штата Колорадо и внимательно изучал идеи, заложенные в Планшет, Симулу и еще два оригинальных языка: LISP, применявшийся при реализации искусственного интеллекта, и LOGO, предназначенный для обучения базовым понятиям программирования.
В ходе ознакомления с этими языками Кей придумал новую концепцию разработки. В соответствии с ней набор последовательно выполняющихся инструкций (концепция императивного программирования) мог быть без ущерба заменен коллекцией объектов, общающихся друг с другом путем асинхронного обмена сообщениями. Работа подобной среды могла поддерживаться не одним, а множеством компьютеров, объединенных в сеть.
В процессе изучения биологических структур Кей задумал создать программную систему автономных модулей (возможно, уже тогда он дал им название «объекты»), объединяющих данные и алгоритмы их обработки. Подобные модули взаимодействуют друг с другом через интерфейсы, определяемые разработчиком, но при этом они не имеют представления о том, что происходит за их пределами. Кей активно использовал аналогии с биологическими объектами и механизмами взаимодействия клеток в живом организме.
Позже Кей перешел в Стэнфордскую лабораторию по искусственному интеллекту, а в 1972 году устроился на работу в научный центр Xerox PARC, всемирно известный как уникальными технологиями, так и немалым числом гениальных выходцев из него. Кей воплотил свои идеи в новом объектном языке SmallTalk, первоначально названном им Biological System и смоделированном на Бейсике, а затем реализованном на ассемблере. В процессе этой деятельности он предложил знаменитый термин «объектно-ориентированное программирование». Заложенные в SmallTalk объектные идеи и по сей день остались не превзойденными ни в каких других языках и системах. Мировую известность получила версия SmallTalk 80, коммерческие реализации которой вышли в 1981 году.
В 1974 году Марвин Мински (Marvin Minski), основоположник теории искусственного интеллекта, предложил идею фрейма, отделившего описание класса (структуры объекта) от его конкретного представления (экземпляра, физического объекта). Эта идея быстро завоевала популярность в языках искусственного интеллекта. Фрейм стал прямым предшественником современного понятия класса в языках C++, Delphi и С#1.
В 1980 году Бьерн Страуструп в ходе работы над диссертацией решил дополнить язык Си концепцией классов, основанной на фреймах и объектных механизмах Симулы. Спустя три года он дал своему творению окончательное название C++, хорошо известное и по сей день.
Популярность объектных подходов росла, и в Портленде в 1986 году прошла первая всемирная конференция по объектно-ориентированным системам программирования. Возможно, она оказала стимулирующее влияние на Билла Аткинсона (Bill Atkinson), инженера корпорации Apple, который через год после этого события спроектировал систему HyperCard — прообраз современных визуальных сред быстрой разработки наподобие C++Builder.
Эффективность объектного подхода оказалась столь высокой, что уже в 1989 году одиннадцать компаний (ЗСот, American Airlines, Canon, Data General, Hewlett-Packard, Philips Telecommunications, Sun Microsystems, Unisys и другие) основали группу OMG (Object Management Group). Она была призвана формировать индустриальные стандарты на объектное программирование и упрощать интеграцию приложений с помощью универсальных кросс-платформных технологий. Эта группа первым делом приступила к выработке единого стандарта компонентной модели CORBA (Common Object Request Broker Architecture): набора
В системах Delphi и C#BuiIder задействованы так называемые паттерны проектирования — шаблоны, реализующие при построении программы еще более высокий уровень абстракций, нежели классы и объекты. Впервые идею шаблонов реализовал в 1976 году Кристен Нигорд в созданном им языке программирования ВЕТА.
спецификаций, определяющих способы объектно-ориентированного взаимодействия компонентов промежуточного уровня в гетерогенных средах без привязки к конкретным языкам программирования. С самого начала CORB А нацеливалась на поддержку крупных, индустриальных проектов, и этот подход со временем себя полностью оправдал.
Параллельно с CORBA развивались и другие объектные технологии. Так, корпорация Microsoft в 1993 году выпустила первую версию компонентной модели COM (Component Object Model), идея которой была, видимо, позаимствована из модели SOM (System Object Model), уже давно существовавшей в IBM OS/2 (компания Microsoft одно время принимала в создании OS/2 активное участие). В том же году была предложена более развитая компонентная технология Microsoft ActiveX, ориентированная на прикладные пользовательские интерфейсы и основанная на элементах управления OLE, пришедших из языка Visual Basic.*
1994 год вошел в историю объектно-ориентированного программирования решением двух известных специалистов по программной инженерии — Гради Буча (Grady Booch) и Джеймса Рамбо (James Rumbaugh) из компании Rational Software — создать новый, унифицированный язык моделирования UML (Unified Modeling Language) на основе существовавших методологий визуального моделирования Booch и ОМТ.
. Через год на свет появилась первая версия среды быстрой визуальной разработки Delphi 1, основанная на концепции библиотек стандартных компонентов. Эта среда дополнилась поддержкой языка C++ в 1997-м, а языка C# — в 2003 году.
Одновременно корпорация Microsoft сообщила о выходе новой технологии DCOM — распределенной версии СОМ, позволившей собирать приложения из компонентов, выполняемых на разных компьютерах. А компания Sun Microsystems выпустила общедоступную технологию Java для создания сетевых приложений. Многие из заложенных в нее идей (виртуальная машина, объектные механизмы) были заимствованы из языка SmallTalk.
Современные объектные технологии
В 1997 году специалисты Sun, воодушевленные массовым интересом к Java, предложили новую концепцию Enterprise JavaBeans — технологию создания корпоративных компонентов Java, которые исполняются на серверах приложений и реализуют логику крупных, хорошо масштабируемых и защищенных систем на самых разных платформах. Эта технология особенно активно развивается в последние годы.
Эксперты группы OMG также учли растущую потребность в универсальных методологических концепциях проектирования крупных систем. Они определились со стратегическим видением объектных технологий и приступили к созданию концепции Архитектура, управляемая моделью (Model Driven Architecture, MDA). В ее основу закладывается платформно-независимая модель на языке UML, несколько платформно-зависимых моделей и коллекция определений программных интерфейсов. Первую реализацию этой универсальной концепции (так называемое «отображение в объектный стандарт») группа OMG выполнила для технологии CORBA.
2000 год ознаменовался анонсированием объектной платформы Microsoft .NE1 и новым языком программирования С#, сочетающим лучшие свойства C++ и Java. Он был предложен корпорацией Microsoft в противовес конкурирующей технологии Sun Java. Связка .NET и C# себя хорошо оправдала, и версия .NET 2.0 появилась в конце 2005 года. В дальнейшем Microsoft рассчитывает базировать на платформе .NET новые версии своих операционных систем.
Сегодня самой распространенной в мире программной технологией остается объектно-ориентированное программирование. Язык C# можно считать лучшим на сегодня объектно-ориентированным средством создания графических приложений (формально — для разных платформ, но фактически, конечно, прежде всего для .NET). Правда, лучшим C# считается среди небольшого числа коммерческих альтернатив (Delphi, Java и отчасти C++). Создание программ для платформы Win32, а также разработка системных программ и мультимедийных приложений с помощью библиотек DirectX и OpenGL, где быстродействие остается одним из решающих факторов, еще долго будет проходить с использованием C++.
В ближайшее десятилетие развитие объектно-ориентированного программирования будет проходить под влиянием двух концепций: Microsoft .NET (прежде всего .NET Framework, языков C++, Delphi, C# и других, ориентированных на платформу .NET) и кросс-платформенных технологий Java. Методология MDA станет основной объединяющей платформой моделирования для этих технологий, прежде всего в силу своей независимости от конкретной среды разработки. Продолжится совершенствование шаблонов проектирования (концепция высокоуровневого объектного программирования), их внедрение в среды разработки и дальнейшее сочетание шаблонного подхода с активно развивающимся языком моделирования UML.
Основные понятия объектно-ориентированного программирования
В основу объектного подхода заложено понятие класса. Класс представляет собой описание, схему, модель реально существующего объекта.
Класс — это сложный тип данных, в котором объединены элементы данных (поля) и методы, обрабатывающие эти данные и выполняющие операции по взаимодействию с окружающей средой.
Поле (или атрибут) класса — это элемент класса, описывающий данные.
Метод класса — это процедура или функция, включенная в описание класса. Метод класса вызывается конкретным экземпляром класса и привязан к описанию и структуре класса.
В дополнение к полям часто применяется более абстрактное понятие свойства класса. Оно расширяет понятие поля и подразумевает использование промежуточного исполняемого кода, который предварительно обрабатывает хранимые в поле данные перед их выдачей или записью.
Во многих средах программирования, в том числе и C++Builder, понятие класса также дополнено понятием события.
Событие класса — это формально определенное внешнее событие, на которое данный класс может реагировать, выполняя определенные действия.
Класс обеспечивает целостность вводимого в программу формализуемого прикладного понятия и не допускает разрыва между статическими и динамическими характеристиками некоторой сущности. На этапе проектирования программы с помощью классов удается формировать достаточно точные модели реальных объектов и строить их с помощью систем визуального моделирования.
Объект (или экземпляр класса) — это представитель класса, построенный по хранящемуся в классе описанию.
Объекты (экземпляры классов), как и любые другие значения допустимых типов, хранятся в программе в переменных объектных типов.'
Наследование, полиморфизм и инкапсуляция
Объектно-ориентированное программирование базируется на трех ключевых понятиях: наследование, полиморфизм и инкапсуляция.
Наследование
Важнейшая характеристика класса — возможность создания на его основе новых классов с наследованием всех его свойств и методов и добавлением собственных. Наследование позволяет создавать новые классы, повторно используя уже готовый исходный код и не тратя времени на его переписывание.
Наследование — это отношение, связывающее классы, один из которых является базовым и называется родительским, а другой создается на его основе и называется наследником. Наследование заключается в том, что класс-наследник приобретает все свойства и методы родительского класса и добавляет к ним собственные.
В ходе наследования новые классы наследуют поля и методы всех родительских классов, вышестоящих по иерархии наследования. Одни из этих полей и методов могут быть заменены на собственные, другие могут представлять собой точные копии родительских. Например, класс «Кошка» может быть наследником класса «Млекопитающее» и будет содержать как все типичные характеристики млекопитающего, так и собственные характеристики, специфичные для кошек.
Полиморфизм
Во многих случаях методы родительского класса должны быть переопределены в классе-наследнике. Все переопределяемые методы по имени совпадают с методами родительского объекта, однако компилятор по типу объекта распознает, какой конкретно метод надо использовать.
Полиморфизм —- это принцип автоматического выбора метода, вызываемого объектом, в соответствии с типом данного объекта и с учетом иерархии наследования.
Пусть в классе «Млекопитающее» имеется метод «Бежать», который в классе «Кошка» переопределен с учетом конкретных возможностей данного животного. Если в программе создан экземпляр класса «Кошка» и происходит обращение к методу «Бежать», то будет вызван правильный метод. Это не общий метод «Бежать» класса «Млекопитающее», а метод, переопределенный в классе «Кошка». Таким образом, правильная версия вызываемого метода выбирается автоматически.
Инкапсуляция
Данные и методы объектов должны быть защищены от внешнего вмешательства или неправильного использования. Другим объектам доступен только интерфейс объекта, через который осуществляется все взаимодействие с ним.
Инкапсуляция — это механизм разграничения доступа к полям и методам класса.
Некоторые поля и методы внутри класса можно сделать свободно видимыми и доступными для использования в программе. Другие можно скрыть и предоставить возможность обращения к ним только, например, внутри текущего модуля или только в классах-наследниках. Инкапсуляция позволяет скрыть особенно. сти реализации классов и предостеречь пользователей от обращения к внутренним возможностям объектов, напрямую не связанным с их предназначением. Инкапсуляция также обеспечивает защиту определенных элементов классов от изменения.
В классе «Кошка», например, может быть свойство «Сердце»; моделирующее внутреннюю работу организма животного. Однако модифицировать его характеристики прямым обращением к этому свойству из внешней программы, очевидно, недопустимо.
Обмен сообщениями
Современные объектно-ориентированные системы наподобие C++Builder в дополнение к трем вышеописанным механизмам объектного программирования поддерживают концепцию сообщений', объекты могут отправлять и получать сообщения, обмениваться ими друг с другом и с внешней средой. На этой концепции построена, в частности, схема работы пользовательского интерфейса. Программа получает от пользователя и системы Windows сообщения о наступлении тех или иных событий (например, о нажатой кнопке в окне программы) и при необходимости реагирует на них.
Вопросы
В каком языке впервые появились понятия «класс» и «объект»?
Какие объектные технологии будут определять развитие программной индустрии в текущем десятилетии?
Что такое класс?
Что такое объект?
5.
6.
7.
8.
9.
10.
Что такое поле класса?
Что такое метод класса?'
Что такое событие?
Что такое наследование классов?
Что такое полиморфизм?
Что такое инкапсуляция?
"1
ГЛАВА 15 Классы и объекты
Класс — это сложный тип данных, в котором объединены элементы данных и методы, обрабатывающие эти данные и выполняющие операции по взаимодействию с окружающей средой.
Объект (или экземпляр класса) — это представитель класса, построенный по хранящемуся в классе описанию. Программа работает только с объектами, а классы нужны для задания их внутреннего устройства. Условно говоря, класс — это чертеж, проектное описание устройства (например, автомобиля), а объект — это сам физически существующий автомобиль, построенный по чертежу.
Описание класса
Описание класса выглядит следующим образом:
class имя-класса {‘
список-членов-класса
};
Определение класса в программе начинается с ключевого слова class. За ним следует имя класса — допустимый идентификатор языка C++. Перед словом class может располагаться набор ключевых слов, уточняющих структуру и другие характеристики класса1.
За именем класса ставится пара фигурных скобок, завершаемая точкой с запятой. Скобки охватывают перечень всех членов класса: поля, методы, события и дополнительные элементы (все они подробно рассматриваются далее)2. ■
Список членов класса может быть пустым, например:
class MyClass { } ;
Однако объекты подобного типа все равно будут корректно создаваться и занимать некоторый объем памяти.
Видимость элементов класса
Все поля, свойства и методы внутри класса могут быть разделены на несколько групп в зависимости от их видимости.
В стандарте языка C++ под термином «класс» понимаются любые структуры, определяемые разработчиком и начинающиеся ключевыми словами class, struct или union.
Поля класса описываются внутри класса так же, как обычные переменные.
Видимость элемента класса — это признак, определяющий режим наследования данного элемента или режим его доступности за пределами класса.
Видимость задается одним из ключевых слов public, private или protected. Следом за ним размещается двоеточие. Когда внутри описания класса встречается одно из этих слов, соответствующие правила видимости распространяются на все последующие поля и методы. Зона влияния заканчивается либо при появлении следующего подобного ключевого слова, либо при окончании описания класса (закрывающей фигурной скобки). Если описатель видимости в начале класса отсутствует, считается, что ближайшие элементы класса по умолчанию имеют видимость уровня private.
Члены класса, описанные как private, недоступны для других модулей программы. Это наиболее строгая форма сокрытия элементов класса. По умолчанию считается, что к членам класса можно обращаться только в рамках этого класса.
Члены класса, описанные как protected, могут быть доступны как в текущем классе, так и в классах-наследниках.
Члены класса, описанные как public, видимы везде, где доступно описание их класса.
Например:
class Animal { int FootNum;
public:
■ char Name[80 J;
protected: double Mass;
};
Поле FootNum имеет видимость private (по умолчанию), поле Name — видимость public, поле Mass — видимость protected. '
Система C++Builder в дополнение к стандартным группам видимости поддерживает также видимость элементов автоматизации, которая задается ключевым словомautomated. К такой группе видимости относят методы, для которых надо дополнительно генерировать информацию о поддержке серверов автоматизации OLE (технология взаимодействия с другими запущенными программами в Windows, чаще всего с различными офисными приложениями).
Базовая иерархия классов VCL
Познакомимся с базовой иерархией классов стандартной библиотеки VCL, поддерживаемой средой C++Builder. В основе всей иерархии лежит базовый класс
TObject. Он обладает наиболее общими методами, присущими любому объекту программы, и опйсывает основные принципы поведения объектов во время работы программы (создание, уничтожение, обработка событий). Его метод ClassNameQ возвращает текстовое имя класса, а функция ClassNamels() позволяет проверить, относится ли объект к указанному в параметре типу. В класс TObject включены стандартные конструктор и деструктор, а также функция удаления объекта и освобождения памяти Free(), которую рекомендуется применять вместо деструктора.
Следующим за TObject в иерархии классов следует класс TPersistent. На его уровне реализованы основные методы копирования содержимого объектов. В частности, копирование другого объекта в текущий объект выполняется методом Assign(), а копирование содержимого текущего объекта в другой объект — методом AssignTo().
Компоненты VCL базируются на родительском классе TComponent (наследник класса TPersistent). Он содержит важное свойство Nahe (идентификатор, представляющий конкретный объект в программном коде), а также ряд свойств, определяющих внутреннее состояние объекта и подчиненные ему элементы.
Наследником TComponent является класс TControl, на основе которого строятся все визуальные элементы управления (для элементов управления Windows введен еще один промежуточный класс TWinControl). Так, используемый в проектах Windows класс формы TForm, содержащий множество свойств, методов и событий, которые определяют настройку окна Windows, является прямым наследником класса TWinControl.
Для работы с объектами, хранящими списки строк, предназначен абстрактный класс TStrings (наследник класса TPersistent). Он предоставляет базовую функциональность для хранения, доступа, сортировки, поиска, копирования, добавления и удаления строк. На его основе создан прикладной класс TStringList, который может использоваться в коде напрямую. Добавление элементов в заданную позицию списка выполняется методом lnsert(), доступ к отдельным элементам с помощью индексации (как в случае обычного линейного массива), удаление всех строк - методом ClearQ, сохранение в файле и восстановление из него — соответственно, методами SaveToFile() и LoadFromFile().
Для работы с коллекциями (наборами объектов одинаковой структуры, например визуальных компонентов) предназначен класс TCollection (наследник класса TPersistent). Он позволяет обрабатывать перечень элементов-наследников класса TCollectionltem, которые хранятся в свойстве коллекции Items (физически в нем находятся ссылки на соответствующие экземпляры наследников класса TCollectionltem), Для работы с коллекциями система C++Builder предлагает специальный редактор (рис. 15.1), который вызывается при обработке свойств компонентов, представляющих собой коллекции (например, при работе со свойством Panels компонента TStatusBar). Он позволяет добавлять новые элементы в коллекцию и индивидуально настраивать значения их свойств в Инспекторе объектов.
Еще одним полезным классом VCL считается класс TList (прямой наследник класса TObject). Он похож на коллекцию, но позволяет хранить элементы произвольных типов (например, экземпляры разных классов) с помощью указателей. Он содержит свойство Items (набор указателей на объекты), свойство Count (текущее число хранимых элементов) и предоставляет методы добавления (Add) и уда-
Рис. 15.1. Редактор для работы с коллекциями объектов
ления (Delete) элементов и их сортировки (процедура Sort). Для сортировки необходимо подготовить функцию сравнения двух объектов, так как заранее их типы неизвестны.
Вложенные классы
Внутри класса допускается описание новых классов. Описания этих классов будут доступны только внутри текущего класса.
Например:
class А {
class В {
};'
};
Класс в доступен только внутри описания класса а.
Отложенное определение класса
В некоторых случаях желательно задействовать имя класса до его полного определения. Для этого достаточно ввести конструкцию вида
class имя ;
Впоследствии класс с данным именем должен быть полностью определен. Например:
class X; // объявление класса X
X* р;
// используем переменную р типа указатель на X
class X {
// отложенное описание структуры класса X
Наследование классов
За именем класса через двоеточие может следовать имя класса, выступающего в качестве родительского класса {наследуемого или базового'). Тогда текущий класс наследует все доступные элементы родительского класса — они автоматически включатся в его состав, а дополнительные их объявления не требуются. Таким образом в C++ реализуется принцип наследования классов.
Например, описан класс Animal с двумя полями, и его наследник, класс Cat:
class Animal {
public:
int FootNum;
double Mass;
class Cat : Animal {
} ;
Класс Cat унаследует все описанные как public и protected элементы класса Animal, то есть получит два поля FootNum и Mass. В явном описании класса Cat они отсутствуют, так как достались по наследству.
По умолчанию элементы категории public включаются в класс-наследник из родительского класса с видимостью, измененной на private. В данном примере поля FootNum и Mass в классе Cat будут видимы как private, то есть только в пределах описания класса и его наследников. Чтобы сохранить видимость наследуемых элементов на уровне public, надо перед именем родительского класса в заголовке описания класса поставить ключевое слово public:
class Cat : public Animal {
};
Теперь поля FootNum и Mass в классе Cat видимы как public.
Схожим образом при необходимости модифицируют видимость других родительских элементов — вводом ключевого слова private или protected перед именем наследуемого класса.
Множественное наследование
У класса может быть несколько родительских классов. Все они перечисляются через запятую, причем для каждого из них можно задать свой уровень видимости. Класс-наследник включает все элементы с соответствующей видимостью из всех родительских классов по цепочке наследования.
Например:
class Animal {
public:
int FootNum;
};
class Target {
int X, Y;
};
class Cat : public Animal, Target { double Mass;
} ;
Класс Cat наследует как поле FootNum класса Animal (оно получает видимость public), так и поля х и Y класса Target.
Виртуальные классы
При использовании множественного наследования возможна ситуация, когда класс наследует несколько «экземпляров» одного и того же вышестоящего класса. Например:
class А {
public: int а ;
class Bl: public A {
public:
int Ы ;
};
class B2: public A { public:
int b2;
};'''
class C : public Bl, public B2 {
public: int c;
};
Формально класс с наследует характеристики двух разных классов в1 и В2. Однако каждый из этих классов, в свою очередь, является наследником одного класса а, элементы которого наследуются классом с через цепочки а-в1иа-в2. В результате в перечне полей класса с будут как поля ы, Ь2 и с, так и две «копии» поля а (по одному от каждой цепочки множественного наследования). В частности, при просмотре содержимого экземпляра класса с в отладчике будут доступны два одинаковых названия поля а. Корректный доступ к каждому из них возможен, только если выполнять явное приведение типа объекта последовательно по всей цепочке наследования:
С с;
((А*)(В1*)(&с))->а; // доступ к первому "экземпляру" поля а
((А*)(В2*)(&с))->а; // доступ ко второму "экземпляру" поля а
Чтобы избежать ошибок, связанных с подобным размножением полей, надо соответствующий родительский класс объявить виртуальным. Его элементы наследуются всегда в единственном экземпляре. Ключевое слово virtual ставится в заголовке класса, наследующего виртуальный класс:
class Bl: public virtual A {
};
class B2: public virtual A {
. . .г
) ;
class C : public Bl, public B2 {
} ;
Теперь в классе с будет только одно поле а базового класса А, наследуемого классами В1 и в2 как виртуальный класс.
Создание объектов
Переменная, декларированная как класс, предназначена для хранения не экземпляра этого класса, а лишь ссылки на него. Сам экземпляр класса (объект) размещается в «куче» (cheap) — специальной области памяти приложения, отведенной для хранения всех объектов. Однако с точки зрения прикладного программиста переменная любого типа, простого или объектного, «хранит» свое значение.
Например, имеется описание переменной animal:
Animal animal;
В момент создания переменной animal автоматически запускается конструктор (см. главу 18) — специальный метод создания экземпляра соответствующего класса. Он формирует объект, который записывается в переменную animal (на самом деле в нее записывается ссылка на объект, но на уровне исходного текста C++ это различие незаметно).
У класса может быть несколько конструкторов, различающихся лишь списком параметров. С их помощью обычно выполняется начальная инициализация полей объекта (при условии, что программист подготовил соответствующий конструктор), например:
Animal MyCat("Барсик", 4, 3.14);
Если класс используется только для организации иерархии и создания на его основе классов-наследников, а конкретные его экземпляры в программе никогда не создаются, то такой класс называют чисто интерфейсным (pure interface
class). Чтобы повысить эффективность обработки подобных классов, в их описание добавляется конструкцияdecispec (novtabie), которая разрешает компилятору не генерировать стандартные обращения к конструктору и деструктору, что приводит к экономии кода.
Пример класса, объявленного как чисто интерфейсный:
decispec(novtabie) class А { } ;
Объекты и указатели
Часто работа с объектами организуется на базе указателей. Объекты могут представлять собой крупные конструкции, поэтому постоянные копирование и создание промежуточных временных объектов при каждой их обработке (в отличии от значений базовых типов) может быть весьма накладным. Указатели часто формируются с помощью оператора получения адреса &:
Animal cat;
Animal* ра;
pa = Scat;
В переменную pa записывается указатель (ссылка) на объект, хранящийся в переменной cat.
В языке C++ имеется оператор new, который изучался в главе 6 (в разделе, посвященном динамическому созданию объектов). С его помощью можно связывать переменную с экземпляром класса не статически, на этапе компиляции (с помощью инструкции декларации объекта), а динамически, во время работы программы. Оператор new записывается так:
new имя-класса
Он создает экземпляр заданного класса непосредственно в момент вызова и возвращает указатель на этот экземпляр.
Например:
Animal* ра;
pa = new Animal;
В переменную ра занесен указатель, ссылающийся на экземпляр класса Animal. Он автоматически создается и размещается в оперативной памяти оператором new. Впоследствии этот объект можно удалить из памяти оператором delete, который ликвидирует объект, доступный по заданному указателю:
Animal* ра;
ра = new Animal;
// используем переменную ра для
// доступа к полям класса Animal
// удаляем объект, адресуемый
// переменной ра:
delete ра;
// далее обращение к переменной ра М приведет к ошибке, так как // объект, на который она указывает, // удален из памяти
Присваивание и копирование объектов
Третья концепция объектно-ориентированного программирования — полиморфизм, реализуется в языке C++ не менее элегантно, чем наследование и инкапсуляция. Так как класс — это обычный тип данных языка C++, то в переменную с помощью оператора присваивания можно записывать значение в виде объекта. Этот объект представляет собой экземпляр соответствующего класса с учетом иерархии наследования. Каждый класс наследует все без исключения члены прямого вышестоящего класса и всех более далеких родительских классов по цепочке вверх, включая самый первый класс. При этом некоторые из свойств и методов могут быть переопределены, однако никакие из них не могут быть удалены из текущего списка наследуемых членов.
В языке C++ поддерживается принцип совместимости объектов по цепочке наследуемых классов. Так, указатель на экземпляр любого из нижестоящих по иерархии наследования классов может быть присвоен переменной, описанной как ссылка на один из вышестоящих классов.
Пусть описаны два класса Animal и Cat, и переменные cat и animal:
class Animal {
public:
int FootNum;
};
class Cat : public Animal { } ;
Animal* animal;
Cat* cat;
В переменную cat в соответствии с принципом полиморфизма можно записывать экземпляры как класса Animal, так и класса Cat. Компилятор и среда выполнения сами разберутся, экземпляр какого конкретного класса хранится в переменной в ходе выполнения программы:
cat = new Cat;
/ / далее к переменной cat можно обращатьсяА
// как к указателю на экземпляр класса Cat
В переменную animal в соответствии с принципом полиморфизма можно записать ссылку на экземпляр класса Cat, хранящийся в переменной cat:
animal = cat;
При этом явное приведение типов выполнять не требуется. Однако компилятор выдаст ошибку, если в процессе наследования не учитывается видимость элементов. Исключим из описания класса Cat ключевое слово public, сохраняющие уровень видимости элементов родительского класса, описанных как public:
class Cat : /* public закомментирован */ Animal {
при компиляции оператора "animal = cat;" возникнет ошибка “Cannot convert Cat* to Animal*” (He могу преобразовать тип Cat* к типу Animal*). Так происходит, потому что элементы класса Animal с видимостью public после наследования представлены в классе Cat с видимостью private, что делает эти классы несовместимыми по структуре.
Язык C++ допускает запись в переменную ссылки на экземпляр базового типа. Например, в переменную cat можно занести указатель на объект класса Animal. Только для этого необходимо явно приводить соответствующие типы:
cat = (Cat)(new Animal);
Явным приведением типов классов следует пользоваться очень аккуратно. Компилятор в таких случаях не проверяет корректность обращений к реально существующим элементам классов приведенных типов. Но в любом случае будет запрещен внешний доступ ко всем элементам, описанным с видимостью private или protected.
Язык C++ поддерживает обратную возможность использования ссылок: в переменную наследуемого класса можно записывать экземпляр класса-наследника:
animal = new Cat;
При этом содержимое переменной определяется не типом конкретного объекта, на который она ссылается, а типом, с которым она была декларирована. То есть переменная animal всегда будет иметь тип Animal, а переменная cat — тип Cat (хотя, возможно, они будут хранить экземпляры разных классов).
Соответственно, переменной самого верхнего класса некоторой иерархии можно присваивать переменные любых классов-наследников. При этом, впрочем, не гарантируется корректная обработка значений полей классов, если структуры родительского класса и класса-наследника сильно разнятся. Не исключено, что их содержимое будет утеряно или искажено, возможны и ошибки времени выполнения. В то же время компилятор корректно воспримет подобные синтаксические конструкции.
Хотя переменная типа класс физически хранит не сам объект, а ссылку на него, с точки зрения программиста она является прямым хранилищем объекта. Поэтому для копирования значения к ней применим обычный оператор присваивания::
Cat catl, cat2;
// в переменную cat2 заносится значение переменной catl -
// копируются значения всех полей:
cat2 = catl;
В результате обе переменные catl и cat2 будут хранить два разных объекта с одинаковыми значениями полей.
Вопросы
Что такое класс?
Что такое объект?
Какие группы видимости существуют в классах языка C++?
Как организуется наследование классов в языке C++?
В чем заключаются недостатки множественного наследования и как они обходятся с помощью виртуальных классов?
Из каких базовых классов состоит иерархия классов VCL?
Как организовать копирование объектов?
Как реализуется в языке C++ концепция полиморфизма?
ГЛАВА 16
Поля класса
Классы дополняют структуры (см. главу 5) возможностью объединения в одно целое не только данных, но и методов (функций), эти данные обрабатывающих. Методы класса рассматриваются в следующей главе, а в данном разделе изучаются средства описания и хранения данных внутри классов. Для этого предназначены элементы класса, называемые поля (или атрибуты).
Определение поля в программе
Поле определяется в классе точно такой же записью, как и при определении обычных переменных.
Поле класса — это переменная, определенная только внутри класса и доступная только после создания экземпляра этого класса. Поле экземпляра класса может иметь любой допустимый тип языка C++.
Например, для определения целочисленного поля MyNumber в классе Myclass надо ввести такой текст:
class Myclass {
int MyNumber;
Связывание полей экземпляра класса с конкретными экземплярами этих классов осуществляется статически, на этапе компиляции. Внутри объекта местонахождение области памяти, отведенной для хранения значения того или иного поля, всегда фиксировано.
В качестве полей класса разрешено использовать массивы заранее неопределенной длины.
Доступ к полю класса
Доступ к значениям полей осуществляется с помощью префикса — переменной, хранящей экземпляр соответствующего класса, за которой следует точка. Далее записывается имя поля.
Допустим, имеется описание класса MyClass:
class MyClass {
public:
int MyNumber;
} ;
Определим экземпляр этого класса:
MyClass me;
Обратимся к его полю MyNumber:
me.MyNumber = 5 ;
Поля экземпляра класса свободно используются в выражениях наряду с другими идентификаторами:
х = me.MyNumber + 1;
Поле класса доступно во внешней программе, только если оно описано с видимостью public.
Доступ к полю через указатель
В программах на C++ часто используются как обычные хранилища объектов — переменные, описанные как класс, так и указатели на них — переменные ссылочных типов. В последнем случае для доступа к полю объекта, на который указывает некоторая переменная, задействуется оператор -> (вместо рассмотренного выше оператора «.»).
Например:
class а {
public: int X ;
А* а = new А;
а - >Х — 5;
Доступ к полю х переменной-указателя а происходит с помощью оператора «->».
Видимость полей класса
Описание любого поля класса может быть явно помещено в раздел с определенной видимостью (см. главу 15), например:
class MyClass {
public:
int MyNumber;
private:
char Name[80];
Если модификатор видимости опущен, то по умолчанию считается, что на поле класса распространяется правило видимости private — поле доступно только внутри текущего класса.
В последнем примере поле MyNumber явно описано в классе с модификатором public. Он позволяет обращаться к данному полю за пределами описания класса. Если бы модификатор public был бы опущен, то поле MyNumber получило по умолчанию видимость private:
class MyClass { int MyNumber;
me.MyNumber =5; // ошибка компиляции!
Тогда при компиляции данного кода появилась бы следующая ошибка: “MyClass. MyNumber is not accessible” (Поле MyNumber класса MyClass недоступно для использования из-за его уровня видимости).
Наследование и скрытие полей класса
В класс-наследник автоматически включаются все поля наследуемого класса. Никаких специальных указаний для этого не требуется.
Поля и другие элементы в классе-наследнике могут переопределяться другими элементами — скрываться.
Скрытие (переопределение) элементов класса — это явное описание в классе-наследнике с новыми характеристиками уже существующих элементов из наследуемого класса.
Например, описан класс OurClass, наследующий описание класса MyClass:
class OurClass : public MyClass {
public:
double* MyNumber;
В нем переопределено поле MyNumber, которое в родительском классе MyClass имело целочисленный тип. Теперь в экземплярах класса OurClass будет доступно поле MyClass, указатель на значение дробного типа. Поле MyClass целого типа из родительского класса скрыто этим новым описанием:
OurClass ос;
double х = 1;
ос.MyNumber = &х; // корректно
Если поле MyNumber не переопределить, то в классе Outclass по умолчанию будет доступно родительское (не скрытое) поле MyNumber целого типа:
class OurClass : MyClass {
// нет переопределенных полей
};
OurClass ос;
ос.MyNumber = 5;
При попытке занести в поле MyNumber ссылку на дробное значение:
ос.MyNumber = &х;
возникнет ошибка компиляции “Cannot convert double* to int” (Нельзя корректно преобразовать значение типа double* в значение типа int).
Доступ к полям вышестоящих классов возможен, если перед именем поля ввести префикс: название класса и два двоеточия. Хотя поля родительских классов могут быть скрыты, физически они существуют внутри экземпляров классов- наследников, поэтому доступ к ним корректен.
Например:
class MyClass {
public:
int MyNumber;
} ;
class Outclass : public MyClass {
public: double* MyNumber;
void f() { double x=5;
// обращение к полю MyNumber
// текущего класса Outclass: MyNumber = &x;
// обращение к полю MyNumber
// родительского класса MyClass:
MyClass::MyNumber = 5; }
Приведение типов
Тип объекта, хранимого в переменной, задается типом, который был явно введен в инструкции ее декларации. При этом переменная реально может хранить ссылку на объект других типов, которые подходят ее типу по иерархии наследования.
Возьмем из предыдущего примера два класса MyClass и Outclass, связанные по иерархии наследования. Определим экземпляр первого класса в виде переменной-указателя и попробуем обратиться к его полю MyNumber:
MyClass* me = new MyClass;
double x = 5;
me->МуNumber = 5;
mc->MyNumber = &х;
На последнем операторе компилятор сообщит об ошибке “Cannot convert double* to int” (Нельзя корректно преобразовать значение типа double* к значению типу int). Ведь поле MyNumber класса MyClass имеет тип int, и в него нельзя записывать указатель на дробное число.
Во время работы программы можно выполнять явное приведение типа объекта, на который указывает некоторая переменная ссылочного типа:
(тип)(выражение)
Выражение приводится к заданному типу. С помощью этой конструкции можно представить содержимое переменной тс как экземпляр класса Outclass (хотя реально это не так). Для этого надо задействовать преобразования не самих классов, а указателей на них. Если же выполнять прямые преобразования объектов, то будут сформированы временные объекты, значения которых после приведения типов потеряются:
((Outclass*)me)->MyNumber = &х;
Выражение ((Ourclass*)mc) выдает ссылку на объект, который хранится в переменной тс и трактуется как экземпляр класса Outclass. Так как в классе Outclass имеется поле MyNumber типа указатель на вещественное значение, его можно выделить с помощью операции «->»,
Данный код становится работоспособным, однако теперь возможны ошибки в процессе выполнения программы. Ведь мы явно задаем преобразование типа некоторой переменной к другому классу, но внутренняя структура этой переменной остается прежней. В ней отсутствует поле MyNumber типа указатель на дробное, поэтому и вероятна ошибка на этапе работы.
Статические поля
Внутри функций язык C++ разрешает декларировать статические переменные (см. главу 10). Оперативная память для них отводится не при каждом вызове функции, а однократно на все время работы программы. В результате удается сохранять значения таких переменных между вызовами функций.
По аналогии со статическими переменными внутри классов разрешается объявлять статические поля. Они декларируются с ключевым словом static, например:
class а { static int n;
} ;
Поле п объявлено внутри класса А как статическое. Это означает, что поле п существует в программе в единственном экземпляре, независимо от числа экземпляров класса А. Значение этого поля всегда доступно в виде конструкции «тип:: поле»:
А: :П
Таким образом в любой момент возможно обращение к статическим полям независимо от того, создан ли вообще экземпляр данного класса. Однако такая общедоступность статического поля накладывает дополнительные требования к его объявлению. В отличие от обычного поля экземпляра класса статическое поле должно быть дополнительно явно определено в программе — в классе располагается лишь формальное объявление этого поля, но физически оно не создается.
Декларация статического поля должна размещаться вне какого-либо блока, определенного фигурными скобками. Например:
class а {
public:
static int n;
};
int A::n = 12 ;
Статическое поле n класса а объявлено и проинициализировано явно, сразу за описанием класса а. Если такую декларацию опустить, возникнет ошибка на этапе сборки (переменная А: :п не определена).
Так как создается только одна копия статического поля, любое обращение к этому полю через различные экземпляры класса А всегда трактуется как обращение к одному и тому же элементу:
A al, а2;
А::п=5;
int N = al.n + a2.n;
Хотя поля al. п и а2 . п не были в программе явно проинициализированы, все они трактуются как одна и та же переменная а :: п, поэтому переменная N получит значение 10.
Поля структур и объединений
В языке C++ структуры и объединения (см. главу 5) относятся к типу класс. Структура отличается от класса тем, что все ее поля имеют видимость public, и может расширяться описанием внутренних методов. Например, два следующих определения типа А эквивалентны:
class а {
public:,
int х, у; void f() {}
struct A {
int x, у;
void f() { }
};
Объединение — это структура, все поля которой начинаются с одного адреса в памяти. За счет такого размещения один и тот же элемент данных можно интерпретировать как значение разных типов.
Вопросы
1.
2.
Что такое поле класса?
Как организуется доступ к полю класса при использовании объектов и указателей на них?
3.
4.
5.
6.
Какие модификаторы видимости допустимы для полей класса?
Как наследуются поля?
Что такое скрытие элементов класса?
Чем различаются поля экземпляра класса и статические поля класса?
ГЛАВА 17
Методы класса
В состав класса входит программный код, предназначенный для обработки данных класса. Подпрограммы, реализующие такую обработку, называют методами.
Метод класса — это функция, включенная в описание класса.
Метод вызывается только с префиксом, указывающим на конкретный экземпляр класса, в котором этот метод определен.
Объявление и реализация методов
При объявлении метода в описании класса задается заголовок этого метода (и, возможно, полное его описание). Заголовок объясняет, как вызывать метод из основной программы. За ним вводится реализация метода (последовательность команд C++), которая размещается в фигурных скобках. Чтобы подсказать компилятору, что данный код — не обычная функция, а метод, принадлежащий некоторому классу, перед именем метода ставится префикс: имя класса и два двоеточия. Таким образом, в разных классах можно создавать методы с одинаковыми именами.
Текст метода не приводит ни к каким действиям в программе — набор команд в его теле сам по себе не выполняется, он просто служит для определения работы метода.
Метод класса объявляется в рамках класса следующим образом:
модификаторы-метода возвращаемый-тип имя-метода ( список-параметров );
В тексте программы вне описания класса метод реализуется с помощью следующей конструкции:
модификаторы-метода возвращаемый-тип имя-класса :: имя-метода ( список-параметров ) {
тело-функции
}
Разрешается объединять в рамках класса заголовок метода и его реализацию. В таком случае используется следующий формат:
модификаторы-метода возвращаемый-тип имя-метода ( список-параметров ) {
тело-функции
Имя метода — любой допустимый идентификатор языка C++. В качестве модификаторов задействуются ключевые слова, уточняющие работу метода (например, определяющие способ его вызова). В теле метода размещаются операторы управления последовательностью действий и инструкции объявления типов, констант и переменных. Декларированные таким образом идентификаторы считаются локальными и действуют исключительно в рамках текущего метода.
Далеко не всегда требуется, чтобы метод возвращал конкретное значение. Например, условный метод WriteLine (), выводящий текстовую строку на экран, никакие величины не рассчитывает. Язык C++ требует, чтобы при описании метода всегда задавался тип возвращаемого им значения. Но если в теле метода никакие значения для вызывающей программы не рассчитываются, в качестве типа функции можно задать ключевое слово void. Оно показывает отсутствие значения, и компилятор не будет требовать от метода с типом void явного формирования возвращаемого значения.
Например, опишем класс Myclass, в котором имеется поле MyNum и метод му Proc, не возвращающий никакого значения:■
class MyClass {
int MyNum;
void MyProc() {}
};
Тело метода MyProc () не содержит никаких операторов и не выполняет никаких действий.
В теле метода можно свободно обращаться к полям и другим методам, определенным в этом же классе:
void MyProc() {
MyNum = 5;
}
Интерфейс и реализация класса
Метод, введенный в классе, можно разделять на заголовок и реализацию, которая выносится за пределы класса. Переопределим описание класса Myclass:
class MyClass {
int MyNum;
public:
void MyProc(); // только заголовок
};
// реализация метода МуProc,
// вынесенная за пределы класса:
void MyClass::MyProc() {
MyNum = 5;
}
Таким образом описание класса разделено на его интерфейс — набор полей и заголовков методов, и реализацию — исполнимый программный код методов. Это важная особенность языка C++, за счет которой реализуется принцип инкапсуляции, сокрытия алгоритмических особенностей реализации и исходных текстов. Конечные пользователи класса получают доступ только к его описанию, вынесенному в заголовочный файл .Н, и, например, к объектному файлу со скомпилированным кодом класса или к библиотеке таких файлов.
Вызов метода
Вызов тела метода допустим только внутри тела другого метода или функции с учетом видимости вызываемого метода. При вызове метода вводится имя метода, пара круглых скобок и, при необходимости, заключенный в них набор значений параметров.
Вызов метода записывается как имя экземпляра класса, за которым через точку следует имя метода. Например:
MyClass тс;
me.MyProc();
В данном случае метод MyProc () не рассчитывает никакого значения, поэтому он не задействован ни в каком выражении. Как правило, поля класса скрываются от прямого внешнего использования в область видимости private, а функциональность их обработки инкапсулируется в методы класса, которые размещаются в области видимости public.
В разных классах можно создавать методы с одинаковыми именами — они будут однозначно различаться префиксами. Если же метод вызывается в рамках метода собственного класса, то префикс не используется.
Например:
class MyClass {
int MyNum;
public:
void MyProcl();
void MyProc2() ;
} ;
void MyClass::MyProcl() { MyNum = 5;
void MyClass::MyProc2() {
Му Prod ( ) ;
MyNum + +;
}
В методе MyProc2 () сначала вызывается метод MyProcl (), который заносит в поле MyNum значение 5, а затем значение этого поля увеличивается на единицу.
Указатель на себя
В языке C++ имеется специальный стандартный идентификатор this (ключевое слово), который всегда указывает на текущий объект (очевидный из контекста). Например, в любом методе определенного класса текущим считается экземпляр этого класса, вызывающий данный метод. По умолчанию внутри методов класса можно использовать имена полей и методов текущего класса без префиксов. Перед каждым из этих имен можно поставить префикс this, хотя обычно его опускают.
Идентификатор this полезен, если локальная переменная совпадает по написанию с полем класса. В этом случае для обращения к полю записывают указатель на себя:
class MyClass {
int MyNum; // поле класса
void MyProcl() {
int MyNum; // далее идентификатор MyNum
// будет рассматриваться как локальная переменная
MyNum =5; // значение 5 присвоено локальной переменной this->MyNum =5; // значение 5 присвоено полю класса }
} ;
Идентификатор this хранит ссылку на текущий объект, поэтому доступ к полям организуется оператором ->.
Статические методы
Выше рассматривались методы, которые правильно называть методы экземпляра класса. В языке C++ существует также формальное понятие метод класса (или статический метод) — такой метод может быть вызван без создания экземпляра класса, только обращением к его имени и имени класса. Фактически такие методы представляют собой самостоятельный код, который не обращается к внутренним элементам класса и поэтому может вызываться независимо от них, как обычная подпрограмма.
Метод класса отличается от обычных методов ключевым словом static в заголовке. Он вызывается с помощью префикса «имя-класса::».
Например, в классе Cat (Кошка) создана функция GetFootNum () — метод класса, который возвращает число лап у кошки. Так как это число всегда равно четырем, то результат работы функции не зависит от внутренних полей класса и может вызываться вне зависимости от наличия реального объекта Cat:
class Cat {
public:
static int GetFootNum()
{ return 4; }
// основной код:
// вызов статического метода класса
// без создания экземпляра:
int n = Cat::GetFootNum();
Статические методы классов не могут обращаться к указателю на текущий экземпляр (this) и к нестатическим методам и полям, подразумевающим наличие экземпляра класса.
Наследование методов
Методы, как поля и другие элементы классов, могут наследоваться. Благодаря принципу наследования исполняемого кода удается существенно сократить объем программы.
Видимость полей и методов
Видимость методов задается так же, как и для полей, с помощью ключевых слов public, private и protected.
Важнейший и не всегда очевидный механизм сокрытия элементов класса заключается в том, что поля родительского класса, описанные как private (по умолчанию), не доступны в классах-наследниках.
Например:
class А
{
int F ;
class В : А
{ void f() { F=l; } // ошибка компиляции } ;
В методе f () класса в происходит обращение к полю F родительского класса а. Однако это поле недоступно в классе-наследнике, так как описано по умолчанию в зоне видимости private и может использоваться исключительно внутри методов класса а. Можно описать поле F в классе а с видимостью public, но это не
лучшее решение, нарушающее принцип инкапсуляции, ведь в таком случае поле F будет напрямую доступно пользователям класса а для модификации.
Оптимальный выход — либо дополнить класс а видимыми методами модификации значения поля F, либо задать для поля F видимость уровня protected, допускающую доступ к родительским полям в методах классов-наследников: class А { protected: int F; class В : A { void f() { F=l; } // корректно } ;
Скрытие методов
Методы в классах-наследниках могут скрывать методы родительских классов, если они совпадают с ними по названию. Например:
class А1
{ public: int F; void f() { F=0 ; }
class В : public A { public: void f() { F=1; } } ;
В b; b.f () ;
Метод f () класса в скрывает одноименный метод f () класса а, поэтому команда Ь. f () вызовет метод, в котором поле F получит значение 1. Для вызова метода родительского класса надо воспользоваться префиксом «имя-класса::»:
class в : public А { public: void f() {
A::f(); // вызов родительского метода f()
Иногда имя пользовательского метода может совпадать с именем уже существующего метода или функций стандартной библиотеки. В таком случае будет
вызван пользовательский метод, а для вызова глобальной функции надо задействовать префикс «::». Например:
#include <math.h>
class MyMath
{
public:
double sin(double x) { return x;
В классе MyMath определен метод sin (), который совпадает по заголовку со стандартной функцией sin () из математической библиотеки math.h. Любое обращение к идентификатору sin () внутри методов класса MyMath компилятор будет трактовать как вызов пользовательского метода. Для вызова глобальной функции sin () воспользуемся префиксом с двумя двоеточиями:
class MyMath
(
public:
double sin(double x) { return : :s in(x) ;
}
Перезагрузка методов
Полиморфизм допускает описание функций с одинаковым названием, но разным списком параметров. Такой прием называется перезагрузкой (см. главу 10). На уровне классов этот принцип работает так же эффективно:
class А
(
int F ;
public:
void f() [F = 0; }
void f(int x) {F=x;}
A a;
a.f(); // поле F получит значение 0
a.f(3); // поле F получит значение 3
В зависимости от числа и типа параметров будут вызваны разные версии метода f о . Компилятор самостоятельно выберет подходящий метод в соответствии с записью вызывающего оператора.
Связывание метода с объектом
Когда создается экземпляр класса, хранить внутри него не только значения полей, но и код методов неразумно. Ведь значения полей в разных экземплярах различ-
ны, а вот скомпилированный программный код всегда один и тот же. Поэтому в особом разделе оперативной памяти исполняемой программы обычно хранится только один экземпляр методов объектов. Вызов метода напоминает вызов обычной подпрограммы. Однако среда разработки должна учитывать, что определенная подпрограмма является методом. Для этого выполняется так называемое связывание вызываемого метода с созданным в программе объектом, этим методом владеющим.
Связывание метода с объектом — это механизм поиска метода объекта.
Существует два способа связывания метода с объектом: статический и виртуальный (динамический). Они определяют способ поиска методов объекта, если классы, их содержащие, переопределены в иерархии наследования.
Статическое связывание
По умолчанию компилятор выполняет статическое связывание метода. Вызываемый метод (конкретный переопределенный метод в иерархии классов) определяется на этапе компиляции в зависимости от конкретного контекста и типов используемых переменных (объектов).
Пусть имеется следующий код:
class Animal {
public:
void Eat () {
}
class Cat : Animal {
public:
void Eat() {
}
Следующая последовательность команд вызовет метод Eat () класса Animal:
Animal а;
а.Eat();
Следующая последовательность команд вызовет метод Eat () класса Cat:
Cat с ;
с.Eat ( ) ;
Следующая последовательность команд вызовет метод Eat () класса Animal, несмотря на то, что в переменную а загружена ссылка на экземпляр класса Cat:
Animal* а = (Animal*)(new Cat);
a->Eat();
В последнем случае явно задан контекст вызова метода Eat () — переменная класса Animal. Вызываемый метод определяется типом текущей переменной (владельца метода), а не типом объекта, ссылка на который в ней хранится.
Можно явно выполнить приведение типа переменной а:
Animal* а = (Animal*)(new Cat);
( (Cat*)a)->Eat() ;
Тогда вызовется метод Eat () класса Cat, несмотря на то, что переменная а имеет тип Animal (но хранит приведенную ссылку на объект Cat).
Виртуальное (динамическое) связывание
Виртуальное (динамическое) связывание выполняется уже не на этапе компиляции, а во время работы программы.
Подходящий метод определяется автоматическим анализом типа динамически созданного объекта. При связывании таких типов не всегда удается корректно проверить на этапе компиляции типы объектов, которым принадлежат те или иные методы. Соответственно, не исключены ошибки, связанные с обращением к несуществующим полям. Они возникают, если метод ошибочно «присвоен» экземпляру не того класса, нежели предполагал разработчик.
Чтобы сделать метод виртуальным, надо в начале его заголовка в описании класса поставить ключевое слово virtual. Использование виртуального метода имеет смысл, только если он впоследствии переопределяется в классах-наследниках. Метод, завершающий цепочку наследования, выступает в качестве перезаписываемого.
Впоследствии подходящий переопределенный метод будет выбран автоматически во время работы программы.
Переопределение (перезапись) виртуального метода — это определение этого метода в классах-наследниках. При этом используется точная копия оригинального заголовка родительского метода, а меняется только его тело.
Вернемся к предыдущему примеру с классами Animal и Cat и изменим описание функции Eat () в родительском классе: сделаем ее виртуальной с помощью модификатора virtual:
class Animal {
public:
virtual void Eat() {
} ;
class Cat : Animal {
public:
void Eat() {
В родительском классе Animjal метод Eat () описан как виртуальный. Таким образом допускается возможность выбора некоторой реализации метода Eat () в цепочке наследования непосредственно во время работы программы.
Теперь при любом использовании экземпляра класса Cat, даже если ссылка на него приведена к другому типу, будет вызван именно его метод Eat, а не метод, задаваемый типом переменной. Ведь при этом выполняется динамическое связывание по реальному типу текущего объекта, а не статическое (по явно приведенному типу):
Animal* а = (Animal*)(new Cat);
a->Eat () ;
Здесь будет вызван перезаписанный метод Eat () класса Cat. Он переопределил родительский виртуальный метод Eat () собственным кодом.
Связывание абстрактных методов
Абстрактный метод представляет собой виртуальный метод, который в текущем классе не реализуется. Это происходит только в классах-наследниках. Абстрактный метод задают, присваивая нулевое зачение в заголовке функции, которая в описании класса помечена как виртуальная (с помощью ключевого слова virtual).
Класс, в котором имеется хотя бы одна абстрактная функция, называется абстрактный. Запрещается создавать его экземпляры, так как не все методы абстрактного класса имеют реализацию. Если в классе-наследнике абстрактный метод не перезаписывается, то он наследуется как абстрактный, и класс-наследник также считается абстрактным.
Изменим предыдущий пример: сделаем метод Eat () и, соответственно, класс Animal абстрактными:
class Animal {
public:
virtual void Eat() = 0 ;
class Cat : public Animal {
public:
void Eat() { }
Теперь, в частности, запрещен следующий код:
Animal* а = new Animal; // ошибка компиляции!
Класс Animal объявлен как абстрактный, поэтому его экземпляры создавать запрещено.
При выполнении кода
Animal а = new Cat;
a->Eat();
будет вызван, как и раньше, перезаписанный метод Eat () класса Cat. Однако использование абстрактных методов имеет как минимум один дополнительный плюс в сравнении с виртуальными методами: не требуется создавать реализацию родительского метода в абстрактном классе.
Вопросы
Что такое метод?
Как разделить описание метода и его реализацию?
Как вызываются методы?
Как обратиться из метода к текущему экземпляру класса?
Как наследуются методы?
Как работают статические методы класса?
Что такое связывание метода с объектом?
Какие существуют способы связывания методов с объектами?
Зачем нужно абстрактное связывание?
Как задается виртуальный метод?
По какому принципу вызываются перезагружаемые методы?
ГЛАВА 18
Конструкторы и деструкторы
При создании экземпляра класса обычно выполняется инициализация его элементов. Такую инициализацию желательно представлять в наиболее наглядном виде, для чего задействуются конструкторы — специальные методы, однократно вызываемые при создании объекта. В момент уничтожения объекта вызывается деструктор, который можно настроить на выполнение завершающих действий, например, освобождение памяти или закрытие файла.
Понятие конструктора
Экземпляр класса в программе — это динамическая структура. Его необходимо создать и проинициализировать нужные поля. Эти действия выполняет метод- конструктор.
Конструктор — это специальный метод, который вызывается для создания экземпляра класса.
Имя конструктора совпадает с именем класса. Для конструктора нельзя задавать возвращаемое значение, оно всегда является ссылкой на текущий класс.
Например:
class MyClass {
int n ;
public:
// Конструктор класса.
// Автоматически возвращает ссылку
//на созданный объект - тип MyClass*
MyClass() {
n = 1;
}
Конструктор по умолчанию обычно не имеет параметров. Он вызывается в момент создания экземпляра класса:
MyClass me; // вызов конструктора
MyClass* рте = new MyClass; // вызов конструктора
Вызов MyClass, расположенный после ключевого слова new — это вызов конструктора по умолчанию, без параметров. В ходе выполнения такой команды создается экземпляр класса MyClass. Конструктор без параметров всегда существует в любом классе, явно или неявно, даже если он не введен программистом.
Конструктор описывается в классе как обычный метод, только в качестве его имени задействуется имя класса, а возвращаемый тип не задается. Соответственно, в теле конструктора оператор return для возврата значения не используется. В вызывающую программу всегда возвращается ссылка на автоматически созданный конструктором объект.
Конструкторы удобны для выполнения начальной инициализации внутренних полей класса. Например, чтобы задать начальное значение ноля п класса MyClass, надо подготовить конструктор с целочисленным параметром, значение которого будет заноситься в это поле:
class MyClass {
int n;
public:
MyClass(int value) { n = value;
}
};
Теперь при определении экземпляров класса MyClass надо вводить начальное значение внутреннего поля п:
MyClass me(5);
MyClass* рте = new MyClass(100);
В каждом случае автоматически вызовется конструктор с явно заданным параметром.
Отметим, что в момент работы конструктора текущий объект еще не существует, поэтому некорректно использовать в теле конструктора указатель «на себя» this.
Инициализация объекта
Если у конструктора один параметр, допускается форма его вызова по умолчанию, с помощью оператора присваивания. При этом список вызова с одним параметром опускается, а значение этого параметра ставится справа от символа присваивания.
Например:
MyClass me = 5; // эквивалентно MyClass me(5);
Допускается также явный вызов конструктора с перечислением всех параметров, например, в операторе присваивания:.
class А {
public:
A (int a, int b) {} };
А* а = new А (1,2 );
Непосредственно в заголовке конструктора допускается ввод списка полей текущего класса с параметрами — их начальными значениями. Такой список ставится после названия конструктора и двоеточия, например:
class MyClass {
int n, x, у, z; '
public:
MyClass(int vl, int v2) :
n(0) , x(vl) , y(v2), z(x) { } };
Поля получают значения из списка инициализации конструктора в порядке их записи, слева направо. Так, в данном примере сначала поле п получит значение О, затем поле х получит значение vl, затем поле у получит значение v2, и наконец поле z получит значение поля х (равное значению параметра vl).
Конструктор копирования
По умолчанию в каждом классе поддерживается так называемый конструктор копирования. Он подразумевает создание текущего объекта на основе уже существующего экземпляра такого же класса. Реально выполняется простое побитовое копирование этого экземпляра (он задается в качестве параметра) в создаваемый объект. Параметр передается в конструктор по ссылке:
MyClass::MyClass(&MyClass)
Использование конструктора копирования допускается в двух формах:
явной, когда параметр задается в конструкторе:
MyClass Ml;
MyClass М2(Ml);
неявной, когда применяется форма записи в виде оператора присваивания:
MyClass Ml;
MyClass М2 = Ml;
Функционально они эквивалентны, однако в случае оператора присваивания необходимо учитывать, что конструктор для объекта не вызывается:
MyClass Ml;
MyClass М2(Ml);
MyClass М3 = Ml;
При создании объекта м2 конструктор сработает, а вот объект мз сформируется без вызова своего конструктора (в отведенное для него пространство памяти просто скопируется содержимое объекта Ml). Поэтому, если в конструкторе выполняются важные действия (например, запрос оперативной памяти под внутренние нужды), некорректно использовать оператор присваивания для его создания.
Перегрузка конструкторов
Разрешается объявлять (перегружать) в классе несколько конструкторов с разным списком параметров и с одинаковым числом параметров, которые имеют разные типы. Если для класса задан хотя бы один конструктор, при декларации переменной соответствующего класса обязательно должен быть явно задействован один из них. Соответственно, если в классе имеются конструкторы, среди которых отсутствует конструктор без параметров (как в последнем примере с классом Myclass), то и декларация переменных с типом этого класса без параметров запрещена:
MyClass me ( 5 ) ; // корректно
MyClass me; // ошибка - нет конструктора MyClass()
Чтобы избежать такой ошибки, в класс надо включить конструктор для создания экземпляра класса по умолчанию:
class MyClass {
int n ;
public:
MyClass() {}
MyClass (int value) { n = value;
}
} ;
Конструкторы должны быть описаны с модификатором видимости public - это необходимо для вызова конструкторов из внешнего кода за пределами класса.
Порядок вызова конструкторов
Конструкторы всегда вызываются по цепочке, начиная с конструктора самого верхнего базового класса в иерархии. Он отрабатывает первым, выполняя базовые действия по формированию экземпляра объекта и инициализации его основных полей, следом запускается конструктор нижестоящего класса, и последним выполняется конструктор класса, непосредственно используемого в программе.
Например:
class А {
int N;
public:
АО {N=0;}
class В : public А {
public:
В() {N=l;}
class С : public В {
public:
СО {N=2;}
При создании экземпляра класса с:
С objс;
переменная N последовательно получит значения 0 (в конструкторе класса а), 1 (в конструкторе класса в) и 2 (в конструкторе текущего класса с).
Скрытые конструкторы
В примерах этой главы конструкторы всегда описывались с модификатором public. Это означает, что конструктор можно вызывать из внешней программы.
Конструктор также можно определять с другими модификаторами, например, с видимостью private. В результате вызов такого конструктора из внешней программы будет невозможен. Это полезно, если, например, желательно запретить создание экземпляров некоторого класса с конструктором без параметров:
class А {
private:
АО {}
public:
A(int) {}
Таким образом запрещается создание экземпляров класса а с конструктором по умолчанию:
А а; // ошибка компиляции
А а (1) ; // корректно
Наследование конструкторов
Конструкторы в языке C++ не наследуются, а вызываются автоматически по дереву иерархии, начиная с самого верхнего (в базовом классе). Если на базовом уровне наследуется несколько классов, то их конструкторы вызываются в порядке, заданном явным перечислением этих классов. Например:
class А {} ;
class В { } ;
class С : public A, public В {};
При вызове конструктора класса с сначала вызовется конструктор класса а (так как класс а указан первым в списке наследования класса с), а затем — конструктор класса в. Однако если какие-то родительские классы — виртуальные:
class С : public A, virtual public В {};
то их конструкторы вызываются в первую очередь. Так, в последнем случае сначала запустится конструктор виртуального класса в, и лишь затем — конструктор класса а.
Программист может явно вызвать конструкторы родительских классов, если они подразумевают вызов со списком параметров. Для этого задействуется описанный выше список инициализации, который дополняется названиями конструкторов родительских классов с параметрами: class А { public: int N ; A (int n) { N=n; } };
class В {1
int M; public: В(int m) { M=m; } };
class C : public A, public В { public:
C(int i) : A(5+i), B(N*2) {} } ;
При вызове конструктора с (1) сначала вызовется конструктор а (5+1), который проинициализирует поле N значением 6, а затем — конструктор в, который получит это значение в качестве параметра в формуле м*2 и проинициализирует поле м значением 12.
Деструкторы
При уничтожении объектов программы нередко необходимы дополнительные операции. Для их корректного выполнения используется метод-деструктор.
Деструктор — это специальный метод, который автоматически вызывается в момент уничтожения объекта.
В деструкторе удобно закрывать файлы или иные каналы взаимодействия с внешними устройствами, освобождать захваченную объектом дополнительную память (собственное пространство объекта обычно освобождается деструктором автоматически) и выполнять другие завершающие операции. Деструктор записывается так же, как и конструктор, только перед названием класса ставится символ ~.
Например:
class MyClass {
int n ;
public:
// конструктор класса: MyClass() {
n = 1; }
// деструктор класса: -MyClass() {
// завершающие действия
} };
Явный и неявный вызов деструктора
Деструктор чаще всего вызывается автоматически средой поддержки выполнения, как правило, при завершении программы. Они вызываются в порядке, обратном вызову конструкторов. Если конструкторы срабатывали в последовательности а о, в о , с (), то деструкторы будут вызываться так: -с () ,~в (),~а ().
Явный вызов деструктора допускается только для объектов, созданных с помощью команды new. Он оформляется в программе двумя способами:
С помощью команды delete, предназначенной для разрушения объектов. В качестве ее параметра вводится ссылка на соответствующий объект. При использовании команды delete вызывается деструктор:
// создаем объект:
А* а = new А () ;
delete а; // вызывается деструктор —А ()
Деструктор вызывается явным вводом его имени:
// создаем объект:
А* а = new А () ;
а->А::-А(); // явный вызов деструктора -А()
Виртуальные деструкторы
Деструктор может быть описан в программе как виртуальный. Для этого перед его именем ставится ключевое слово virtual. Считается, что любой деструктор,
определенный в классе, наследующем класс с виртуальным деструктором, также будет виртуальным. Вызов виртуального деструктора происходит по правилам полиморфизма.
Например:
class А {
public:
virtual —А () {}
} ;
class В : public А {
public:
-во {}
} ;
А* а = new В () ;
delete а;
При уничтожении объекта, на который ссылается переменная а (указатель на значение типа а), сначала корректно вызовется деструктор -в () реально существующего экземпляра класса в, на который ссылается переменная а. Если бы ключевое слово virtual в определении деструктора ~в () отсутствовало, то вызов деструктора -в () не произошел, хотя переменная а указывает именно на экземпляр класса в.
Явный вызов виртуального деструктора осуществляется прямой записью его имени (без префикса класса):
а->~А();
Объект с виртуальным деструктором, на который ссылается указатель, корректно удаляется, если код программы скомпилирован в так называемом режиме идентификации типа во время выполнения (RunTime Type Identification, RTTI; установлен по умолчанию). Чтобы механизм виртуальных деструкторов функционировал корректно, надо также включить режим поддержки полиморфизма деструкторов. Настройки RTTI задаются для проекта командой Project > Options и включением флажка Enable runtime type information в разделе Exception handling (Обработка исключительных ситуаций). Настройки полиморфизма виртуальных деструкторов задаются в этом же разделе флажком Enable destructor cleanup.
Перегрузка операторов new и delete
Операторы new и delete для динамического создания и уничтожения объектов изучались в главе 6. При создании объектов сложной структуры стандартные процедуры формирования и уничтожения желательно заменять на собственные, учитывающие конкретные нюансы работы объекта.
Синтаксис перегружаемых операторов new и delete таков:
void ‘operator new
(size_t blocksize, char initVal);
void operator delete(void*);
Первый параметр оператора new всегда стандартен и не указывается явно (его значение формируется и подставляется в вызов автоматически). Он обозначает объем затребованной оперативной памяти в байтах. Второй параметр — это значение, которое прописывается в каждый байт выделенной памяти.
Единственный параметр оператора delete — указатель на объект, который надо ликвидировать.
Реализация этих операторов может быть такой:
class а {
public:
void ‘operator new (size_t blocksize, char initVal);
void operator delete(void*);
void *A::operator new(size_t blocksize, char initVal) {
void *pBuf = malloc(blocksize);
if( pBuf != 0 )
memset(pBuf, initVal, blocksize);
return pBuf;
}
void A::operator delete(void*P) { free(P) ;
}
Вопросы
1.
2.
Что такое конструктор?
Как происходит перегрузка конструкторов?
Как наследуются конструкторы?
Как вызвать конструктор родительского класса с параметрами?
Что такое деструктор и когда он вызывается?
Как явно вызывать деструктор?
Как учесть правила полиморфизма при вызове деструкторов?
Как перегружаются операторы new и delete?
ГЛАВА 19
Свойства и дружественные функции
Свойства развивают концепцию полей, дополняя их механизмом контроля содержимого. Свойства-индексаторы упрощают обработку свойств, представленных в виде массивов. Дружественные функции упрощают стыковку нескольких классов.
Свойства
Свойства развивают концепцию полей. С точки зрения разработчика, свойства, как и переменные, предназначены для хранения значений. Однако способ считывания значения и записи новой величины в свойство возможен не только прямым действием (оператором присваивания), но и через вызов промежуточного кода.
Свойство — это поле экземпляра класса, доступ к которому осуществляется не напрямую, а через промежуточный программный код.
Обращение к свойству приводит к вызову определенной функции, которая, например, дополнительно обращается к скрытому полю класса и возвращает его значение. При этом возможно полное вычисление результирующего значения исключительно в теле такой функции, без обращения к внешним источникам или внутренним хранилищам данных.
Новое значение свойства устанавливается по этой же схеме: вызывается функция с параметром (значением, которое предполагается записать в свойство), однако какая величина заносится в свойство, решает программист. При этом параметр функции при расчете итоговой величины, заносимой в свойство, может вообще не использоваться.
Свойства хороши тем, что позволяют контролировать процесс обработки своего содержимого. Они применяются, как правило, для уточнения различных характеристик объекта, например, для выдачи числа элементов коллекции. Это число может меняться по некоторому алгоритму, поэтому формирование итогового значения должно происходить «на лету», вызовом исполняемого программного кода (метода класса).
Свойства — это часть языка C++, зависящая от реализации. В версии C++ для C++Builder поддержка свойств отражает структуру визуальной библиотеки компонентов VCL и особенности работы Инспектора объектов, который позволяет настраивать характеристики работы проектируемого пользовательского интерфейса именно с помощью свойств.
Создание свойств
Декларация свойства класса записывается следующим образом:
property тип имя-свойства =
(геаФ=спецификатор-чтения, мг1Се=спецификатор-записи, index=KOHCTaHTa, зСогеФ=логическое-значение, спецификатор умолчания);
Тип определяет тип свойства. Спецификаторы определяют способ доступа к значению свойства и его модификацию. В качестве спецификатора чтения и записи выступает поле или функция (она должна быть снабжена модификатором fastcall, см. главу 9). Если использовано слово read, спецификатор задает способ получения значения свойства (считывание значения из свойства), если использовано слово write, то задается способ записи значения в свойство. В обоих случаях можно указать уже существующее поле экземпляра класса. Это поле станет фоновой копией свойства для физического хранения текущего значения свойства. Любой из спецификаторов доступа может быть опущен. Тогда доступ к свойству возможен либо только на считывание, либо только на запись.
С помощью ключевого слова index организуется универсальный доступ к большому числу схожих свойств (см. ниже).
Ключевое слово stored задает, будет ли для данного свойства использовано значение по умолчанию при доступе к нему (например, через Инспектор объектов). Если следом за словом stored введено значение true, то конкретное поставляемое значение ставится после ключевого слова default. Ключевое слово node fault показывает, что умалчиваемое значение совпадает с нулевым (или пустой строкой).
Свойства, как правило, размещаются в специальном разделе видимости класса, начинающегося с ключевого слова published. Тогда, если создается элемент управления, они будут доступны в Инспекторе объектов.
Например, определен класс Саг, описывающий автомобиль (он наследует стандартный класс Classes: :TPersistent библиотеки VCL, чтобы использование разделаpublished было корректным):
const MAX_BAK_VALUE = 100;
class Car : public Classes::TPersistent {
protected:
int FPetrolTank;
void fastcall SetPetrolTank(int value);
published:
property int PetrolTank =
{read=FPetrolTank, write=SetPetrolTank} ;
void fastcall Car::SetPetrolTank(int value) { FPetrolTank += value;
if( FPetrolTank > MAX_BAK_VALUE ) FPetrolTank = MAX_BAK_VALUE;
}
В нем имеется общедоступное свойство PetrolTank (бензобак), предназначенное для хранения сведений о количестве бензина. Считывание текущего значения происходит прямым обращением к внутреннему полю FPetrolTank, хранящему текущее количество бензина, залитого в бак.
Процесс заправки описывается сложнее. Метод SetPetrolTank() дополнительно проверяет, не заливается ли в бак слишком много бензина, и при необходимости корректирует значение с учетом реальной емкости бака. Число, которое должно быть занесено в свойство, передается в метод через условную переменную value.
В прикладном коде
Car* car = new Саг();
car->PetrolTank = 99;
имеется обращение к свойству PetrolTank. Однако вместо прямого обращения к нему автоматически вызывается метод SetPetrolTank () с параметром 99 (это значение заносится во внутреннюю переменную value), который и выполнит нужные действия.
Свойства можно описывать как предназначенные только для чтения или только для записи. Если опущен раздел write в описании свойства, то оно считается доступным только для чтения, а если опущен раздел read, то оно считается доступным только для записи. Например, свойство Num класса AutoIncrement будет поставлять значение, автоматически увеличивающееся на единицу при каждом к нему обращении:
class AutoIncrement {
int n;
int fastcall GetNum();
public:
property int Num = {read=GetNum};
int fastcall AutoIncrement::GetNum() { return n++;
}'
Так как модификатор write для свойства Num не указан, то в коде программы запрещено явно задавать значение полю Num. А каждое обращение к нему (считывание) вызовет функцию GetNum () и возвратит значение внутреннего поля п, которое при этом увеличится на единицу.
Индексаторы
Объект может иметь несколько однотипных свойств, доступ к каждому из которых выполняется не напрямую, а с помощью промежуточного программного кода (например, для проверки корректности вводимых значений). При этом создавать для каждого из свойств оригинальный набор, содержащий функции считывания и записи, сложно и неэффективно. Ведь при изменении алгоритма считывания или записи придется вносить однообразные модификации во все эти практически одинаковые методы.
Язык C++ позволяет автоматизировать работу с однотипными свойствами. Для этого внутри класса создается специальное свойство ^индексатору которое получает индекс (номер запрошенного элемента) и на основе его значения формирует результат. В дальнейшем обращение к экземпляру такого класса допускается по индексу как к массиву.
Индексатор — это свойство, реализующее механизм обращения к объекту как к массиву.
Класс, к экземпляру которого можно обращаться как к массиву, должен содержать свойство, после имени которого ставятся квадратные скобки и вводится тип индекса. Этот тип может быть практически произвольным, потому что обращение к индексатору реализуется не стандартным механизмом, а прикладным программным кодом. Разработчик должен задать явный алгоритм определения значений по индексам. Каждому из методов доступа к свойству добавляется новый параметр — значение индекса.
Чаще всего индексаторы используются для организации контролируемого доступа к массиву.
ПРИМЕР 19.1
' Использование индексаторов для контролируемого доступа к массиву
Создадим класс МуАггау, работа с которым будет происходить как с обычным массивом. Однако при попытке обратиться к этому «массиву» с индексом, выходящим за допустимые границы, ошибки времени выполнения не возникнет. Внутренний код класса отследит некорректное обращение и выдаст, например, значение -1.
class МуАггау {
// скрытое поле а будет хранить
Н значения массива: int а [ 10 0 ] ;
int fastcall GetArrayVal(int);
void fastcall SetArrayVal(int,int);
public:'
// свойство-индексатор:
property int massiv[int] =
{read=GetArrayVal, write=SetArrayVal};
} ;
// при записи и считывании значений проверяется // нахождение индекса в допустимом диапазоне: int fastcall MyArray::GetArrayVal(int n) { if( n<0 I I n >= 100 ) return -1;
return a[n];
void fastcall MyArray::SetArrayVal(int n, int value) { if( n<0 I I n >= 100 ) return;
a[n] = value; }
Использование этого класса может быть таким:
MyArray аг;
Н инициализируем объект-массив:
for(int I = 0; I < 200; I + +) ar.massiv[I] = 1*2;
В этом коде значения получат только сто элементов массива, но при обращении к элементам с 101 по 200-й (с индексами 100-199) ошибка в процессе работы программы не возникнет.
Доступ к объекту аг осуществляется с помощью индексов с произвольными значениями: int N = аг[-10] + аг[0] + аг[500];
ПРИМЕР 19.2
Использование в индексаторах индексов сложных типов
В качестве типа индекса в описании индексатора не обязательно применять только целый тип (как требуется для обычных массивов). Ведь логику взаимосвязи между значениями индекса и результирующим значением полностью формирует программист. Создадим класс MyWeek, который будет выдавать номер дня недели в зависимости от названия конкретного дня. Это название будет выступать в качестве индекса. Так как менять номер дня в зависимости от его названия, видимо, неразумно, соответствующее свойство будет доступно только для чтения.
class MyWeek {
int fastcall GetDayName(AnsiString name); public:
property int DayName[AnsiString] = [read=GetDayName];
};
// Параметр name - 'строковый.
// Он представляет собой название дня недели.
int fastcall MyWeek:-.GetDayName (AnsiString name) {
if( name == "понедельник" ) return 1;
if(name=="вторник")return2;
if(name=="среда" )return 3;
if(name=="четверг")return4;
if(name=="пятница")return5;
if(name=="суббота")return6;
if( name == "воскресенье" ) return 7;
return 0;
}
Теперь к экземпляру данного класса можно обращаться, используя текстовые индексы:
MyWeek mw;
int n = mw.DayName[ "пятница" ];
Оригинальная возможность объявления свойств
В версии Borland C++ имеется оригинальный механизм создания свойств, напоминающий вышеизложенный. Он позволяет преобразовать существующий элемент класса в свойство (точнее, в вызов функции). Для этого в его описание добавляется конструкцияdeclspec(property(деС=имя-функции-доступа, риС=имя- функции-записи)).
Например:
class А {
int getF() (return 0;}
void setF(int v) (}
public:
declspec(property(get=getF, put=setF)) int F;
Далее явные обращения к полю F компилятор транслирует в вызовы функций getF () ИЛИ setF ().
Дружественные классы и функции
Когда в проекте насчитывается большое число классов и реализована разветвленная иерархия наследования, нередко возникают сложности в организации взаимодействия между конкретными классами. Оказывается, например, что функциональность некоторых классов желательно объединить, однако существующая схема наследования уже не позволяет это выполнить. В таких случаях на помощь приходит ключевое слово friend. Оно позволяет сделать два класса дружественными, либо, как минимум, предоставить некоторой функции доступ к закрытым членам класса.
Дружественные функции также полезны, если создается несколько классов с одинаковыми методами. Например, объекты класса а могут взаимодействовать способом в с объектами класса с, а последние таким же способом могут взаимодействовать с представителями класса а. В таком случае в каждом из классов аис придется описывать свой метод в, однако их реализации будут идентичны. Организация дружественных функций позволит избежать подобного дублирования.
Кроме того, механизм дружественного взаимодействия позволяет скрывать структуру классов от сторонних разработчиков, которые в коллективных проектах могут временно привлекаться для расширения возможности системы.
Глобальные дружественные функции
Дружественная функция не является членом класса и поэтому не может быть вызвана с префиксом (экземпляром этого класса). Однако она получает доступ
ко всем его элементам с видимостью private и protected. Таким образом удобно расширять функциональные возможности классов, не вмешиваясь в специфику наследования.
Дружественность функции обозначается ключевым словом friend в ее заголовке. Доступ к текущему классу организуется через параметр — экземпляр этого класса.
Например:
class А {
protected:
int n ;
friend void IncN(A& a) { a.n++;
}
public:
A(int N) ( n=N;
}
};
A a (10) ;
II вызываем дружественную функцию
// без префикса:
IncN(а) ;
В результате значение поля п объекта а будет равно 11.
Если одна и та же дружественная функция должна быть для наглядности определена в описании нескольких классов, то ее заголовок вводится, как было показано выше, в каждом из них.
Дружественные классы и дружественные функции других классов
Метод одного класса можно сделать доступным в другом классе. Для этого его заголовок с дополнительным ключевым словом friend и префиксом, определяющим исходный класс, размещается в классе, «дружащем» с первым.
Например:
class В;
class А {
public:
void IncN(B& b);
};
class В {
int п ;
public:
friend void A::IncN(B& b) ;
В(int N) (n=N;}
void A::IncN(B& b)
(
b. n + + ;
}
Метод incN (), явно реализованный для класса а, описан в классе в как дружественный, поэтому в нем возможен доступ к скрытым полям класса в:
А а;
В Ь(10);
а.IncN(b) ;
Значение поля b. п станет равным 11.
Если дружественными надо сделать все функции другого класса, а их может быть немало, допускается сокращенная запись. Дружественным при этом объявляется весь класс, а не отдельные функции, для чего задействуется конструкция friend class имя-класса.
Например, описание класса в можно изменить так:
class В { int п;
public:
friend class А;
В(int N) {n=N;}
Теперь все методы класса а дружественны классу в.
Вопросы
Что такое свойство класса?
Как программно контролируют значение свойства внутри класса?
Что такое индексатор?
Как организовать хранение в свойстве массива значений?
Как индексировать свойство значениями нечисловых типов?
Что такое дружественная функция?
Как избавиться от дублирования методов с помощью дружественности функций?
Зачем нужны дружественные классы?
ГЛАВА 20
Перегрузка операций и шаблоны
Перегрузка операций позволяет в рамках стандартного синтаксиса C++ записывать выражения над произвольными типами данных с помощью общепринятых знаков операций. С помощью шаблонов удается создавать классы и функции, не зависящие от конкретных типов элементов и параметров.
Перегрузка операторов
Перегрузка операторов используется, если желательно выполнять действия над элементами нового класса с помощью «обычных» операций, записываемых стандартными символами +, - и аналогичными. Смысл таких операций может, конечно, кардинально отличаться от принятого в арифметике.
Перегрузка операторов — это механизм изменения содержания стандартных операторов и операций.
Перегрузка операторов выполняется с помощью следующей формы записи: тип operator имя (параметры) { тело
}
Это описание может целиком размещаться внутри определения класса, либо быть поделенным на заголовок и реализацию. В качестве типа задается тип возвращаемого оператором значения. Название — это символ перегружаемой операции. В списке параметров вводится один или два параметра (тип и идентификатор), в зависимости от вида операции (если она требует один аргумент, то вводится один параметр, если два аргумента — то два параметра). Если формируется бинарная операция, то она должна быть определена как дружественная (с ключевым словом friend). Хотя бы один из типов параметров должен соответствовать типу класса, в котором переопределяется данный оператор. Таким образом сфера действия переопределенного оператора ограничивается выражениями, в которых задействованы объекты конкретных типов.
В теле записывается программный код, в котором рассчитывается результирующее значение на основе значений параметров.
ПРИМЕР 20.1
**■*■*■ Перегрузка операторов для класса «Вектор»
Создадим класс, который описывает вектор произвольной длины (одномерный динамический массив). Для него надо определить операции сложения, вычитания, умножения и деления векторов с помощью наглядной и компактной формы записи.
Определение класса.
Определим класс Vector (вектор, одномерный массив): class Vector {
// Внутреннее поле-массив, // хранящее значения вектора: double * FVector;
// Число элементов в массиве: int FLen;
double fastcall GetVector(int); void fastcall Setvector(int,double);
H Конструктор вектора.
// В качестве параметра задается
И длина вектора:
public:. ,
Vector(int len) {
FVector = new double[len];
FLen=len;
}
И Свойство, возвращающее // текущую длину вектора - // число элементов в нем: property int length = {read=FLen};
H Индексатор, позволяющий обращаться // к вектору как к массиву: property double vector[int] =
{read=Gepyector, write=SetVector}; } ;
double fastcall Vector::GetVector(int i) { if ( i<0 I I i >= FLen ) return 0;
return FVector[i]; }
void fastcall Vector::Setvector(int i, double x) { if ( i<0 I I i >= FLen ) return;
FVector[i] = x; }
Добавление перегружаемых операторов.
В описание класса Vector включим запись перезагружаемой операции сложения, применимой к экземплярам данного класса. Для этого с помощью ключевого слова operator
и подходящего символа перегружаемой операции (для сложения это будет символ +) добавим в описание класса соответствующий заголовок и код:
class Vector {
// Переопределенный оператор сложения векторов:
friend Vector operator + (Vector vl, Vector v2) {
H Определяем длину результирующего вектора.
// Она будет минимальной из длин векторов-аргументов:
int 1 ;
if( vl.length < v2.length ) 1 = vl.length;
else 1 = v2 .length;
// Создаем результирующий вектор:
Vector v(1);
// Составляем значение результата
// из сумм элементов параметров:
for(int i = 0; i < 1; i++)
v.vector[i] = vl.vector[i] + v2.vector[i];
return v;
}
} ;
В этом коде создается новый вектор v. Его размерность соответствует наименьшей размерности векторов, переданных в качестве параметров. Затем в него заносятся суммы попарно складываемых элементов исходных векторов.
Схожим образом реализуется операция поэлементного умножения:
// Переопределенный оператор умножения векторов:
friend Vector operator + (Vector vl, Vector v2) {
int 1 ;
if( vl.length < v2.length ) 1 = vl.length;
else 1 = v2.length;
Vector v(1);
for(int 1=0; i < 1; i++)
v.vectorfi] = vl.vector[i] * v2.vector[i];
return v;
)
Аналогично создаются операции вычитания (символ -), поэлементного деления (символ /) и другие.
Использование перегруженных операторов.
Главное преимущество перегруженных операторов в том, что обычные выражения над переменными, хранящими векторы, можно записывать с помощью общепринятых знаков сложения, вычитания, умножения и деления. Например:
Vector а(5), b(10), с(50), d(0);
// заносим значения в эти векторы
И . . .
// вычисляем содержимое вектора d по формуле:
d = (а + Ь) * с - Ь / а;
Приоритеты операций сохраняются стандартными: в первую очередь выполняются операции * и /, а потом операции - и +. Кроме того, порядок вычислений определяется круглыми скобками.
Перегрузка преобразования типов
В дополнение к перегрузке операторов язык C++ допускает перегрузку операций преобразования типов. Операции перегрузки преобразований типов записываются в форме, схожей с формой перегрузки операторов:
operator результирующий-тип()
{
тело
Результирующий тип — это тип, к которому выполняется преобразование экземпляра текущего класса. Реализация процесса такого преобразования записывается в теле оператора.
ПРИМЕР 20.2
Перегрузка преобразования класса “Вектор”
Рассмотрим пример перегрузки операции преобразования вектора Vector в целое число. Пусть такая перегруженная операция соответствует определению размерности вектора, а преобразование класса Vector к вещественному типу соответствует нахождению среднего значения элементов вектора.
Описание класса.
Расширим описание класса TVector из примера 20.1 двумя функциями перегрузки преобразования типов.
Реализация приведения к целому типу.
Реализация метода приведения к целому типу записывается следующим образом:
operator into { return FLen; }
Реализация приведения к вещественному типу.
Реализация метода приведения к вещественному типу записывается следующим образом:
operator double() {
double sum = 0;
int len = (int)(*this);
for(int i = 0; i < len; i++)
sum += this->vector[i];
return surn/len;
}
В качестве верхней границы цикла используется число элементов текущего вектора, которое получается обращением к перегруженной операции преобразования вектора к целому типу.
Параметризованные классы и функции (шаблоны)
Шаблоны, как и перегружаемые операторы, существенно расширяют возможности языка C++. Они значительно повышают эффективность обработки данных разных типов и сокращают объемы исходного кода. Программисту нередко приходится готовить один и тот же код для обработки разных типов данных (например, разных классов). Параметризованные типы позволяют выносить тип обрабатываемых данных в отдельный параметр. В результате вместо множества функций, обрабатывающих данные разных типов по единому алгоритму, достаточно реализовать одну шаблонную функцию.
Параметризованное множество классов (или функций) создается следующей записью:
template <список-параметров> описание-класса-или-функции {
тело
В списке параметров задается виртуальный тип, значение которого будет подставляться в конкретной реализации данного класса или вызове функции. В теле метода имя этого типа используется как обычное, наряду с причими именами типов. Текст в угловых скобках является неотъемлемой частью имени класса.
Функции-шаблоны
Следующая функция-шаблон находит наибольшее из двух значений независимо от их типов. При использовании этой функции подразумевается, что пользователь будет задавать только те значения, для которых допустима операция сравнения.
template<class Т> Т МуМах(Т& а, Т& Ь) { if (а > b) return а;
else return b;
}
В качестве параметра шаблона выступает класс т (ключевое слово class перед ним — условное обозначение понятия «тип»). Впоследствии при обращении к функции МуМах () этот параметр примет значение, соответствующее типу конкретных параметров. Если бы возможности параметризации не существовало, то для параметров каждого типа (int, long, double и так далее) пришлось бы реализовывать собственный вариант функции. В данном случае к функции-шаблону МуМах () можно обращаться с параметрами любых типов, допускающих сравнение:
int 1=5, j=6, k;
double x=l.l, у=2.2, z;
AnsiString sl="abc", s2="a6c", s3;
k = МуМах(i,j); // k=6
z = МуМах(x,y); // z=2.2
s3 = МуМах(si,s2); // s3="a6c"
В описании параметризованного шаблона можно задавать несколько типов- параметров через запятую:
templatecclass Т1, class Т2> Т МуМах(Т1& а, Т2& Ь) {
Данная возможность востребована, если типы параметров должны отличаться. Вышеприведенный пример с функцией МуМах () работоспособен, только если типы ее параметров совпадают. Для других ситуаций надо реализовывать функцию-шаблон с разными типами-параметрами.
Если параметры функции-шаблона надо явно привести к конкретному типу, используется запись вида
имя-функции сцелевой-тип> (список-параметров);
Например, следующий вызов даст ошибку компиляции из-за того, что у параметров разные типы:
z = МуМах (i,х);
Однако следующая конструкция корректна (каждый параметр приводится к типу double):
z = MyMaxcdouble>(i,х);
Вместо ключевого слова class в списке параметров шаблона можно вводить конкретные типы:
templatecclass Т, int i> long MyCount(T a, int b) { . . .
Классы-шаблоны
Классы с параметризованными типами нужны, чтобы создать единый механизм обработки данных разных типов. Например, класс, задающий коллекцию, должен обеспечивать хранение экземпляров самых разных классов, и шаблоны очень хорошо для этого подходят.
Класс-шаблон объявляется так:
template ссписок-параметров-типов> class имя { тело-класса
Отличие от стандартного определения класса здесь только в префиксе templateo. Параметры шаблона в угловых скобках, обозначающие тип, используются внутри описания класса как конкретный тип данных.
Например, следующий класс моделирует работу массива из объектов произвольных типов:
templatecclass Т> class А {
// Поле для хранения массива объектов Т* рТ;
// Методы доступа к внутреннему массиву: Т fastcall GetElem(int i)
{ return рТ[i]; }
void fastcall SetElem(int п,Т t)
( рТ[п] = t; }
public:
A(int);
// Общедоступное свойство
// для обращения к элементам массива:
property Т el[int] =
(read=GetElem, write=SetElem};
} ;
Реализацию конструктора для наглядности вынесем за пределы описания класса. Методы класса-шаблона записываются, во-первых, с префиксом template<Tnn>, а во-вторых, перед именем метода с двумя двоеточиями ставится не только имя класса, но и постфикс <параметр>:
// Конструктор класса-шаблона А
template<class Т> А<Т>::A(int п) {
рТ = new Т [п];
}
Теперь к классу а можно обращаться как к хранилищу объектов произвольных типов. Описание экземпляров класса-шаблона выполняется в формате: имя-класса <конкретный-тип> имя-переменной ;
В качестве конкретного типа задается значение параметра шаблона.
Например:
class в { } ;
// Массив та описан как хранилище целых значений:
A<int> та(5);
та.el[4] = 5;
// Массив mb описан как хранилище
// экземпляров класса В:
А<В> mb(10);
В Ь;
mb.el[5] = b;
Особенности использования угловых скобок
При определении конкретных экземпляров класса-шаблона в качестве его параметров допускается использовать константные выражения. Например: template<bool> class MyClass { };
MyClass<true> myobj;
Возможна также запись вида:
const MY_CONST = 5;
MyClass<6==MY_CONST> myobj;
Если в подобной ситуации в выражении задействуются операции сравнения (угловые скобки), возникает ошибка компиляции:
const MY_CONST = 5;
MyClass< 6>MY_CONST > myobj;
Компилятор не способен понять, какой символ > соответствует окончанию пары скобок, а какой — операции сравнения, поэтому выражение надо заключать в круглые скобки:
MyClass < (6>MY_CONST) > myobj;
Вопросы
Зачем нужна перегрузка операций?
Как выполняется перегрузка операторов?
Как выполняется перегрузка типов?
Что такое шаблоны и параметризация типов?
Как создаются функции-шаблоны?
Как создаются классы-шаблоны?
ГЛАВА 21 События и исключительные ситуации
В языке C++ предусмотрены приемы управления порядком выполнения программы, рассчитанные на особые случаи. К таким случаям относят события и исключительные ситуации. Механизм обработки событий позволяет программе гибко реагировать на действия пользователя или на изменение состояния операционной системы. Обработка исключительных ситуаций используется при сбоях в программе по внешним или внутренним причинам.
События
Человек общается с программой через элементы пользовательского интерфейса (окна, кнопки, меню, списки и прочие). При нажатии на кнопку в окне программы, при выборе пункта меню или просто при щелчке мышью операционная система Windows формирует так называемое системное событие — она генерирует сообщение о подобном действии и отсылает его программе.
Событие в языке C++ — это сообщение от системного объекта, получаемое при регистрации операционной системой активности пользователя или изменении состояния объекта и определяющее прикладной программный код, который обрабатывает эти действия.
Приложение взаимодействует с пользователем и операционной системой через механизм событий (говорят, что приложение управляется событиями'). Запущенная программа обычно неактивна — она не выполняет никаких действий и пассивно ожидает сообщений о происходящих событиях от системы Windows. Программист задает реакции программы на определенные события — например, определяет набор команд, который сработает, если пользователь нажмет кнопку.
Когда пользователь активирует элемент управления, операционная система формирует соответствующее событие (записывает информацию о нем во внутренние таблицы) и извещает работающие программы о наступлении события, рассылая им сообщения. Если в какой-то программе реализована обработка конкретного события, вызывается соответствующий обработчик, подготовленный программистом1.
Если реакция на событие не предусмотрена, оно обрабатывается стандартным образом операционной системой.
Система C++Builder позволяет настраивать программу на «перехват» тех или иных событий как на этапе проектирования, так и на этапе выполнения.
Закрытый вызов обработчика
Типичная программа на языке C++ событийно-ориентирована. Ее текст представляет собой набор реакций на события, прежде всего на операции пользователя с элементами интерфейса. Событие представлено внутри класса C++ как его свойство. В нем хранится адрес функции, которая автоматически вызывается при возникновении соответствующего события во время работы программы.
Эта функция должна иметь тип void, поэтому взаимодействие с вызывающей средой обычно осуществляется через параметры, передаваемые по ссылке. По этой причине для динамического связывания обработчика с методом (кодом обработки) используется так называемый механизм закрытого вызова обработчика. Когда разработчик, как будет показано в примере 21.1, связывает обработчик с методом, задействуется скрытый указатель на конкретную функцию конкретного экземпляра класса. Таким образом удается «развязать» зависимость объекта, которому принадлежит обработчик, от метода обработки события.
Событие в общем случае (без конкретизации параметров, отражающих особенность конкретного события) задается типом TNotifyEvent (уведомительное сообщение). Его описание таково:
typedef void fastcall
(closure *TNotifyEvent)(System::TObject* Sender);
Ключевое словоclosure как раз определяет функцию (метод обработчика) закрытого вызова.
Единственным обязательным параметром выступает sender — ссылка на объект, сгенерировавший данное событие.
Обработка событий в Проектировщике
Программист формирует программный обработчик нужного события в окне Проектировщика среды C++Builder. Любой элемент управления, расположенный на форме, описывается не только набором свойств, доступных в Инспекторе объектов, но и набором событий, которые можно программно обрабатывать. Эти события доступны на закладке Events в Инспекторе объектов. По умолчанию в программе не происходит специальной обработки получаемых сообщений. Программист должен явно сформировать собственный обработчик интересующего события.
Это можно сделать тремя способами:
дважды щелкнуть мышью на строке Инспектора объектов с именем нужного события;
дважды щелкнуть мышью на элементе управления на форме (если для этого элемента поддерживается возможность создания подходящего обработчика);
вручную определить обработчик в классе.
Например, чтобы создать обработчик нажатия кнопки, достаточно дважды щелкнуть на образе этой кнопки на форме в окне Проектировщика. Среда C++Builder автоматически сгенерирует программный код обработчика и переключится в режим редактирования исходных текстов.
void fastcall TForml::ButtonlClick(TObject ‘Sender)
{
}
Обработчик события всегда представляет собой метод класса текущей формы. Если создается обработчик нажатия на кнопку или обработчик выбора пункта меню, он всегда будет принадлежать не классу кнопки или меню, а классу родительской формы элемента управления4.
В качестве параметра Sender в обработчик передается объект, который вызвал обрабатываемое событие. Если создается обработчик нажатия на кнопку, то параметр Sender содержит ссылку на эту кнопку. Чтобы задействовать этот объект в программном коде, его надо преобразовать к типу TButton с помощью команды приведения типов:
((TButton)Sender).Caption
ПРИМЕР 21.1
Динамическая обработка событий
Во многих программах одно и то же событие должно вызывать в разных условиях разную реакцию программы. Для динамического изменения способа обработки событий используется прием изменения текущего обработчика (ссылки на функцию).
Допустим, надо программно создать обработчик нажатия на кнопку Button"!.
В разделеpublished класса формы опишем функцию-обработчик MyClick() и реализуем ее.
Файл Unitl .h:
class TForml : public TForm
published: // IDE-managed Components TButton *Buttonl;
// Добавлено вручную:
void fastcall MyClick(TObject ‘Sender);
private: // User declarations
public: II User declarations
fastcall TForml(TComponent* Owner);
} ;
В абсолютном большинстве случаев обработчики стандартных событий создают и настраивают с помощью Проектировщика среды C++Builder.
Файл Unitl.cpp:
void fastcall TForml::MyClick(TObject ‘Sender) {
ShowMessage("Нажата кнопка. ") ;
}
Занесем в свойство OnClick кнопки ссылку на функцию MyClick(). Таким образом организуется реакция этой кнопки на нажатие. Подобное динамическое связывание можно выполнить, например, в момент создания формы (в обработчике события формы FormCreate):
fastcall TForml::TForml(TComponent* Owner)
: TForm(Owner)■
{ Buttonl->OnClick = MyClick;
}
В программе можно поддерживать одновременно несколько обработчиков интересующего события. Ссылки на соответствующие процедуры обработки подставляются в свойство OnClick (или другое) элемента управления в зависимости от каких-то условий.
Исключительные ситуации
В процессе разработки и отладки программ на языке C++ важную роль играет механизм обработки исключительных ситуаций (или сокращенно исключений). Этот механизм обеспечивает устойчивую работу приложения при возникновении в нем ошибок и сбоев, позволяет точно диагностировать места ошибок и причины их возникновения и предпринимает при необходимости защитные действия.
Исключительная ситуация (или прерывание) — это событие, которое генерируется при возникновении ошибки или сбоя в программе.
Механизм обработки исключительных ситуаций позволяет продолжить работу программы в случае возникновения ошибок, препятствующих работе в обычном режиме. Желательно среагировать на ошибку и минимизировать последствия сбоя. Язык C++ содержит средства отслеживания исключительных ситуаций и реакции на них. Для этого задействуется обработчик исключений, который разделяет нормальную и аварийную логику работы программы и берет при необходимости управление на себя.
Обработка исключительных ситуаций, требующих экстренных мер по противодействию ошибке, реализована в языке C++ на объектном уровне и напоминает обработку событий. Каждая исключительная ситуация описывается своим классом, а все подобные классы связаны в общую иерархию. Одна и та же исключительная ситуация может принадлежать сразу нескольким классам. Класс Exception, определенный в системном модуле SysUtils, считается базовым классом всех классов исключительных ситуаций в языке C++.
Исключительная ситуация может быть вызвана двумя принципиально разными способами: либо возникновением ошибочной ситуации в процессе работы про-
граммы (внутри программы или в операционной системе), либо явной ее генерацией в программном коде в процессе корректного функционирования.
Обработка исключительных ситуаций
Когда в программе возникает ошибка, обработчик исключений создает специальный объект, описывающий эту ошибку. Затем он прерывает работу текущего метода и передает управление блоку кода, ответственному за обработку исключений. По умолчанию программа выполняет стандартные действия по обработке ошибки. Как правило, текущая функция немедленно завершается, и управление передается обработчику системных событий.
Программист может задавать оригинальные реакции на исключительные ситуации. Стандартная форма программной обработки исключительной ситуации выглядит следующим образом:
try {
контролируемый-модуль-кода
}
catch (исключение-1) {
набор-реакций-на-исключительную-ситуацию-1
}
catch (исключение-2) {
набор-реакций-на-исключительную-ситуацию-2
}
catch (исключение-п) {
набор-реакций-на-исключительную-ситуацию-п
}
В этой конструкции явно разделены команды основного алгоритма (контролируемый модуль кода) и команды, ответственные за обработку исключительных ситуаций (разделы в фигурных скобках после ключевых слов catch). Каждое описание обрабатываемого исключения заданрв круглых скобках следом за ключевым словом catch. Оно записываются по следующему шаблону:
Класс-исключительной-ситуации Экземпляр-этого-класса
Экземпляр класса можно опускать и использовать только имя класса.
В качестве идентификатора экземпляра класса вводится любой допустимый и ранее не определенный идентификатор языка C++. В качестве типа задается один из стандартных классов, описывающий конкретное исключение. Если идентификатор введен, то он считается локальной переменной и в него записывается объект, описывающий текущую ошибку. Группа команд, расположенная в фигурных скобках в каждом из разделов с ключевым словом catch, выполняется, только если при выполнении контролируемого кода возникла исключительная ситуация, соответствующая заданному типу (классу).
Например, в следующем коде обработчики исключений представлены именами методов pi, Р2 и рз:
try
{
int X = StrToInt("ххх");
int Y = 1 / X;
catch (EConvertErrorS) { Pl () ; }
catch (EDivByZero&) { P2 () ; }
catch (EOSErrorS) { P3 () ; }
При попытке преобразовать строку "ххх" в число возникнет прерывание класса EConvertError (ошибка преобразования). При этом будут опущены все операторы, следующие до закрывающей фигурной скобки раздела try — за оператором присваивания, в котором возникла исключительная ситуация. В данном примере пропустится оператор присваивания значения переменной у. Вместо него вызовется обработчик соответствующего исключения — функция pi (), так как тип ошибки соответствует классу EConvertError. После того, как функция Р1 () исполнится, управление перейдет к команде, следующей за фигурной скобкой, завершающей блок try и все блоки catch.
Выбор нужного обработчика происходит в последовательном порядке. Пусть имеется следующий код:
try
{
int X = StrToInt("ххх");
int Y = 1 / X;
catch (Exceptions) { P2 () ; } catch (EConvertErrorS) { Pl () ; } catch (EOSErrorS) { P3 () ; }
Допустим, в ходе операции сложения возникла ошибка преобразования (класс EConvertError). При подборе нужной реакции на эту исключительную ситуацию сначала выполняется проверка соответствия класса ошибки классу Exception, так как ее обработчик расположен первым в списке раздела catch. В данном случае ошибка переполнения относится и к классу Exception как наиболее общему и к классу EConvertError. Поэтому выполняется первая функция Р2 (), а функция Р1 () будет проигнорирована.
Оператор try контролирует всю заданную область скомпилированного кода, включая и поведение всех функций, вызываемых в рамках этого контроля. Поэтому, если в пределах блока try контролируемое исключение возникнет внутри сторонних вызываемых методов, оно также будет отслежено и обработано.
Перехват любых исключений
Если предусмотренных исключительных ситуаций в контролируемом коде не возникло, никакой код, связанный с ключевыми словами catch, не выполнится. Однако тогда обработка исключительной ситуации будет передана операционной системе, которая выведет сообщение об ошибке на экран, а затем прервет работу программы. Поэтому если от программы требуется повышенная надежность, желательно явно обрабатывать все мало-мальски вероятные классы исключений в прикладном коде.
Можно также задействовать универсальную форму оператора catch, которая позволяет контролировать возникновение любых исключений. В этой форме используется только одно ключевое слово catch, следом за которым ставится тро- еточие в круглых скобках. Например:
try {
int X = StrToInt("ххх");
int Y = 1 / X;
catch(...) {
ShowMessage("Возникло непредвиденнное исключение!");
Анализ ошибки
При возникновении ошибки полезно выполнить ее детальный анализ. Для этого используется специальный объект, описывающий текущую исключительную ситуацию. Среда поддержки выполнения программ C++ создает такой объект автоматически, но для этого надо расширить описание команды обработки исключительных ситуаций. Перед наименованием класса ошибки, в разделе внутри круглых скобок после ключевого слова catch записывают произвольный, ранее необъявленный идентификатор с двоеточием. Например:
catch(EConvertError& е) { Р1(); }
Здесь е — идентификатор, аналог локальной переменной, который при возникновении исключительной ситуации класса EConvert Error автоматически инициализируется экземпляром этого класса, содержащим описание текущего сбоя.
Все классы исключительных ситуаций наследуются от класса System. Exception. В нем имеются, в частности, следующие важные поля, доступные только для чтения:
Message — строка, выводимая в окне аварийного сообщения;
Helpcontext — ссылка на раздел справочной системы по текущей ошибке.
Программная генерация исключительной ситуации
Оператор throw явно генерирует нужные исключительные ситуации из программного кода. Для этого в качестве параметра оператор вводится экземпляр класса соответствующего прерывания:
throw объект-исключение ;
Например:
try
{
throw
EConvertError("Возможно, ошибка преобразования.");
}
catch(Exception &е) {
ShowMessage(е.Message);
}
В этом примере программно генерируется прерывание EConvertError — оно возбуждается ключевым словом throw. В конструкторе класса прерывания можно задавать текстовую строку, которая задействуется как описание исключения. В частности, в данном примере после возбуждения исключения EConvertError текстовое сообщение “Возможно, ошибка преобразования!” будет выдано пользователю автоматически, после чего работа программы продолжится.
С помощью оператора throw, в частности, можно вызывать на выполнение код, размещенный в каком-либо из блоков catch — использованием соответствующего класса прерывания, например:
try
{
throw EConvertError("Вызываем функцию Pl ( ) " ) ;
}
catch (EConvertError&) { Pl () ; }
catch (EDivByZero&) { P2 () ; }
catch (EOSErrorS) { P3 () ; }
При выполнении оператора throw будет вызвана на выполнение функция Р1().
Вложенные операторы try
Операторы try можно вкладывать друг в друга. Таким способом удается гибко разделять форму обработки менее критичных и более критичных ошибок. На малоопасные исключительные ситуации можно реагировать локально — пропуском одного ошибочного оператора. При возникновении серьезных сбоев придется прервать, например, работу всей функции. Однако функционирование всей программы все равно будет продолжено. Например:
try {
f ОГ ( 1=0; I < 100; I ++ )
try {
// тело цикла
catch(локальное-прерывание) {
// обработка локального прерывания
// внутри цикла
}
}
catch(внешнее-прерывание) {
// обработка возможных ошибок // в заголовке цикла и вне его
Возможные ошибки в теле цикла контролируется внутренним оператором try. При возникновении локального прерывания будет однократно пропущен оставшийся участок тела цикла, а затем работа цикла продолжится. Если же возникнет исключительная ситуация в заголовке цикла, то будет завершена вся часть кода во внешнем обработчике.
Если некоторое исключение не обрабатывается внутри вложенного блока try (для него не подготовлен соответствующий оператор catch), то его анализ будет продолжен во внешних операторах t гу. В данном примере, при возникновении в теле цикла некоторого исключения первоначально просматривается список элементов catch внутреннего раздела try — ближайшего к оператору, породившему ошибку. Если в нем обработка исключения не поддерживается, управление передается внешнему блоку catch. В результате произойдет глобальное завершение работы — закончится блок, охваченный внешним оператором try, соответствующим сработавшему оператору catch.
Гарантированное завершение блока кода
Далеко не всегда надо обрабатывать ошибки собственным прикладным кодом. Чаще требуется, чтобы программа гарантированно выполнила определенный набор действий независимо от успешности работы основной группы операторов. В таких случаях удобнее использовать следующий вариант блока try:
try {
операторы
}
finally { завершающие действия
Набор команд, расположенных в фигурных скобках следом за ключевым словомfinally, выполняется всегда.
Если в ходе выполнения операторов раздела try возникнет исключительная ситуация или будет вызван оператор return для выхода из текущей функции, завершающие действия будут выполнены в любом случае.
Операторыfinally и catch взаимно исключающи — их нельзя использовать одновременно.
Создание собственных классов исключений
Для создания оригинального класса исключительной ситуации задействуется базовый класс Exception — он обрабатывает исключения, возникающие внутри прикладной программы. Программист формирует на его основе класс-наслед-
ник. Обычно в новом классе не требуется специально переопределять какие-либо родительские характеристики — в процессе обработки исключений задействуется лишь название класса, а большинство важных функций включено в базовый класс Exception.
Например, опишем пользовательский класс Му Except ion исключительной ситуации, который позволит наглядно отслеживать превышение некоторой переменной критического значения (или любое другое событие, определенное программистом):
class MyException : public Exception {
int FInt;
public:
MyException(int n)
: Exception("MyException") (FInt=n;}
Поле Pint, передаваемое в конструктор, хранит, допустим, ошибочный индекс массива, на котором произошло превышение порога. Все характеристики нового класса унаследованы от родительского, и новых полей и методов в нем нету. Новый класс используется обычным способом, как и любые другие классы исключений:
try {
int sum = 0;
for( int I = 0; I < 10; I ++ ) {
sum += I;
if( sum >25 )
throw MyException(I);
catch(EDivByZero& e) {
catch(MyExceptions e) {
// Обработка собственного прерывания
При выполнении данного кода отслеживается ситуация, когда значение переменной sum превысит порог 25. В таком случае возбуждается пользовательская исключительная ситуация, которая приводит к выводу сообщения на экран. В качестве параметра конструктора исключения MyException передается значение счетчика, на котором был превышен порог. Это значение при желании можно добавлять в демонстрируемое сообщение (хотя при этом область использования пользовательского исключения будет ограничена конкретным контекстом).
«Молчаливые» исключения
При возникновении исключительной ситуации и в ряде прикладных ситуаций не всегда удобно завершать большой блок кода, контролируемый оператором try.
Операторы break, continue и return прерывают либо текущую операцию, либо работу всей функции. Но, например, для организации выхода из нескольких вложенных циклов приходится применять оператор перехода. В качестве его более элегантной замены можно использовать так называемые «молчаливые» исключения — классы исключительных ситуаций, наследуемые от класса EAbort. При возникновении исключения подобного класса прерывается текущая операция — более глобальная, нежели отдельный оператор, но менее глобальная, нежели текущая функция, и никаких сообщений пользователю не выдается. В частности, среда C++Builder предлагает стандартную функцию Abort (), которая генерирует исключение класса EAbort (фактически же — прерывает текущую операцию).
try {
for(int i = 0; i < 10; i ++ )
for(int j = 0; j < 10; j ++ ) { sum += i;
if( sum >25 ) Abort();
}
}catch(EAbortS){}
Вопросы
Что такое событие?
Как программно создается обработчик события?
Как создается обработчик события в окне Проектировщика?
Что такое исключительная ситуация?
Как отследить возникновение исключительных ситуаций?
Как отследить возникновение любого исключения?
Как получить подробную информацию о возникшей ошибке?
Для чего предназначено ключевое слово throw?
Как гарантированно выполнить определенный набор команд при возникновении ошибки на предыдущих операторах?
Как формируется пользовательский класс исключительной ситуации?
ГЛАВА 22
Объектные интерфейсы и пространство имен
Система C++Builder предоставляет программисту ряд дополнительных возможностей. К ним, в частности, относятся низкоуровневые средства обработки сообщений, реализация объектных интерфейсов и управление пространством имен.
Обработка сообщений Windows
Среда C++Builder предназначена для создания на языке C++ программ, работоспособных только в операционной системе Windows, поддерживающей набор программных интерфейсов Win32. По этой причине в язык и поставляемые с ним стандартные библиотеки встроены средства поддержки внутренних механизмов Windows. К ним, прежде всего, относится организация обработки системных сообщений.
Любое взаимодействие пользователя с программой осуществляется посредством передачи сообщений органам управления. Когда человек нажимает мышью на кнопку формы, Windows отслеживает координаты щелчка, определяет, какому элементу управления в каком окне он предназначается, после чего формирует специальное сообщение этому окну и элементу управления и отправляет его. Когда объект управления выполнит свою функцию, он может также отправить собственное уведомительное сообщение о результате действия, например, чтобы изменили свое содержимое другие связанные с ним элементы. По такой схеме взаимного обмена сообщениями и организована вся внутренняя работа Windows. Соответственно, чтобы программа могла включиться в работу, она должна поддерживать набор обработчиков различных сообщений.
В библиотеку визуальных компонентов Borland VCL включена поддержка множества стандартных типов Windows, прямо или косвенно предназначенных для обработки сообщений. Эти типы определены в различных заголовочных файлах, большое семейство которых включается в заготовку программы через единый заголовочный файл vcl.h.
Системное сообщение Windows представляется в программе в виде набора полей. Главными из них считаются поля параметра сообщения, уточняющие его назначение (например, координаты щелчка мыши). Первое из них занимает 4 байта и называется в справочной литературе wParam, второе занимает 8 байтов и называется IParam. В свою очередь, структура этих параметров разнится для разных видов сообщений. Среда C++Builder часто автоматически определяет тип сообщения и формирует его обработчик уже с готовыми параметрами — выделен-
ными частями wParam и IParam. Например, обработчик нажатия на кнопку мыши OnMouseDown уже содержит сведения о кнопке (параметр Button), состоянии служебных клавиш (Shift) и координатах щелчка (х и Y), пересчитанных в координаты объекта формы, на котором этот щелчок выполнен:
void fastcall TForml::FormMouseDown
(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) ;
В общем случае системное сообщение Windows определяется стандартным классом TMessage. В нем имеются два поля WParam и LParam, а также поле Msg — код самого сообщения, и Result — результирующе значение (доступ к его частям осуществляется через поля ResultLo и ResultHi).
Создание собственного обработчика системных сообщений
По умолчанию процесс преобразования внешних сообщений в обработчики программы на C++ выполняется автоматически, специальным программным ядром, которое встраивается в каждое приложение. В некоторых исключительных случаях (например, если создается системная утилита, которой надо детально анализировать содержимое каждого сообщения) разработчик может перезаписать этот обработчик собственным, хотя при этом не гарантируется устойчивая и корректная работа остальной части программы.
Собственный обработчик системных сообщений задается в программе как метод главной формы:
void fastcall TForml::WndProc(TMessage& Message) {
Ее параметр Message полностью характеризует полученное сообщение.
Следующий код выводит в поле-надпись величину текущей прокрутки колесика мыши. Для этого отслеживается системное сообщение wm_mousewheel, параметр которого wParam хранит величину прокрутки:
void fastcall TForml::WndProc(TMessage& Message)
{
if( Message.Msg == WM_MOUSEWHEEL )
{
Buttonl->Caption = IntToStr(Message.WParam);
Message.Result=0;
TForm::WndProc(Message);
}
Полученное сообщение анализируется, и если его значение совпадает с заданным, выполняются действия по его обработке, после чего в поле Result заносится ноль (признак успешно обработанного сообщения). Далее вызывается стандарт-
ный метод обработки сообщений Wndproc () базового класса формы — он нужен, чтобы работа текущей формы, основанная на других сообщениях, происходила в обычном порядке.
Рассылка сообщений
Чтобы программно отправить системное сообщение напрямую некоторому объекту, используется метод Perform(). Его параметры — код сообщения и два значения wParam и IParam. Так, чтобы отправить текущей форме из вышеприведенного примера сообщение wm_mousewheel (например, по нажатию на кнопку Button2), надо записать следующий код (значение 1234 — условная величина прокрутки колесика мыши):
void fastcall TForml::Button2Click(TObject *Sender)
{
this->Perform(WM_MOUSEWHEEL,1234,0);
}
Сообщение может отсылаться не напрямую конкретному объекту, а может помещаться в стандартную системную очередь Windows. Тогда оно обрабатывается не сразу, а в общем порядке. Для этого используется метод SendMessage:
int MsgResult = SendMessage
(Handle, WM-MOUSEWHEEL,1234,0);
Параметр Handle — идентификатор текущего окна формы (ее стандартное свойство). Метод возвращает результат отправки сообщения (нулевое значение означает успешное выполнение). Этот метод надо использовать вместо метода Perform (), если создается многопотоковое приложение.
Пока мы не использовали структуру типа TMessage. Она востребована, если надо разослать так называемое широковещательное сообщение, предназначенное одновременно множеству получателей. Для его отправки используется метод Broadcast () объекта, которому отправляется сообщение, с единственным параметром — экземпляром класса TMessage. Например, отправка сообщения, подготовленного в переменной Msg класса TMessage, всем формам на рабочем столе (они доступны через свойство Forms стандартной глобальной переменной Screen, описывающей текущее состояние экрана) выполняется так:
TMessage *Msg = new TMessage;
// Формируем содержимое сообщения Msg
Н ...
for(int 1=0; i < Screen->FormCount; i++) Screen->Forms[i]->Broadcast(Msg);
Объектные интерфейсы
Объектный интерфейс похож на класс. Он так же описывает набор свойств и методов, однако не требует их обязательной реализации. Объектный интерфейс обычно используется для связи программы на языке C++ с объектами, написанными на других языках программирования.
Объектный интерфейс — это сложный тип данных, в котором объединены свойства и методы, а реализация их не обязательна.
Объектный интерфейс нужен для доступа к внешним, функционирующим в сети объектам, содержимое которых (значения свойств) менять не разрешается. Программа обращается к методам таких объектов через объектный интерфейс, например через Интернет. На определенном компьютере сети работает объект, физически удаленный от программы, и нужный метод выполняется автоматически, если внешняя программа обратилась к нему дистанционно. Объект возвращает результат работы метода внешней программе, которая, возможно, и не подозревает о том, что метод интерфейса выполнялся на другом компьютере.
В программе объектный интерфейс напоминает описание класса C++. Однако объектный интерфейс существует независимо — он не требует реализации и конкретного программного кода. Объектный интерфейс считается абстрактным описанием определенного понятия. Он может включать свойства, которые имитируют поля (хранилища данных).
Обычно поля в интерфейсе определять нельзя, так как они подразумевают физическую реализацию, а работа свойств может целиком моделироваться программным кодом. Однако версия интерфейсов в среде C++Builder ближе к обычным классам — разработчик может определять в интерфейсе переменные. Так сделано в основном в целях базовой поддержки концепции интерфейсов в объектном языке C++, ориентированном на платформу Win32.
Декларация интерфейса
Интерфейс в программе объявляется так:
interface имя-интерфейса { список элементов интерфейса };
Имена интерфейсов принято начинать с буквы i (первой буквы английского слова Interface). Имена обычных типов, напомним, традиционно начинаются с буквы т, от слова Туре. Описание содержимого интерфейса выполняется так же, как и для класса.
Реализация интерфейса с помощью класса
Клиентские приложения вызывают методы объектного интерфейса, который опубликован внешним объектом. В клиентском приложении достаточно описать интерфейс, чтобы потом к нему обращаться. Однако внешний объект должен содержать как описание интерфейса, так и его реализацию. Интерфейс сам по себе не реализуется, поэтому необходимо создать дополнительный класс, который унаследует свойства и методы интерфейса и реализует их. Такой класс записывается обычным образом, только следом за наследуемыми классами в его заголовке дополнительно указывается список наследуемых интерфейсов. В результате будет определен новый класс, который наследует характеристики родительских классов, а также характеристики наследуемых интерфейсов, которых может быть
несколько. Реализация класса должна включать методы, принадлежащие родительским классам и каждому из наследуемых интерфейсов.
Например, класс а реализует интерфейсы та и ib, а также наследует класс в:
interface ТА { public:
int f ( ) ;
};
interface IB {
public: void p () ;
} ;
class В {
public: int x ;
} ;
class A : public В, TA, IB { public:
int f() {return 0;}
void p() {}
};
Идентификатор GUID
Объектный интерфейс описывает (делает общедоступным) определенный программный интерфейс конкретного объекта. В таких случаях говорят: программный интерфейс объекта опубликован. Объекты, интерфейсы которых опубликованы, нередко функционируют на разных компьютерах в сети.
Пользователи (внешние программы) дистанционно обращаются к опубликованным интерфейсам объектов, которые могут быть централизованно собраны, например, в одном репозитории. Не исключено, что создатели этих объектов случайно задали одинаковые имена интерфейсов (например, одно и то же имя ITestlnterface). Тогда разные объекты предлагают разные по содержанию интерфейсы с одним и тем же именем. В результате при попытке публикации интерфейса ITestlnterface возникнет ошибка, если другой объект уже опубликовал интерфейс с таким же именем.
Для разрешения противоречий с именами интерфейсов введено понятие глобального уникального идентификатора Globally Unique Identifier (GUID).
Глобальный уникальный идентификатор — это специально сгенерированное значение длиной 16 байтов, которое используется для идентификации интерфейсов.
Каждый реализуемый интерфейс характеризуется не только именем, но и идентификатором GUID. Этот идентификатор дополнительно характеризует интерфейс значением длиной 16 байтов, что обеспечивает уникальность интерфейса. Оно
генерируется автоматически и случайным образом, поэтому вероятность совпадения как имен интерфейсов, так и их идентификаторов практически равна нулю.
Глобальный идентификатор QUID обычно записывается в программе в следующей форме:
"{хххххххх-хххх-хххх-хххх-хххххххххххх}"
Каждой позиции «х» соответствует шестнадцатеричная цифра от 0 до F.
Система C++Builder поддерживает также стандартный тип _guid, который позволяет задавать константы quid непосредственно в виде числовых значений. Структура типа _guid такова:
typedef struct _GUID {
unsigned long Datal;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
}
Пространство имен
В стандартные библиотеки систем разработки на C++ входит множество стандартных классов, функций и идентификаторов. Поэтому компилятор C++ поддерживает технологию разделения одинаковых имен идентификаторов, включенных в разные библиотеки или в разные модули прикладной программы.
Несколько классов с одинаковым именем по стечению обстоятельств могут быть определены одновременно, например, во внешних библиотеках, созданных разными разработчиками. Для разделения в программе таких классов и других идентификаторов с совпадающими именами в язык C++ введено понятие пространства имен. Оно также позволяет наглядно отразить сложные иерархии классов (например, классы платформы .NET).
Пространство имен — это способ организации определений и идентификаторов программы, упрощающий их обработку и доступность для внешних модулей и приложений.
Пространство имен — одна из фундаментальных концепций языка C++ (и, в частности, платформы .NET). В пределах пространства имен определен конкретный набор идентификаторов (например, типы данных). В разных пространствах имен могут находиться идентификаторы с одинаковыми именами. Язык C++ обеспечивает средства корректного обращения к разным пространствам имен. Программа на языке C++ может складываться из нескольких пространств имен, каждое из которых представляет собой блок кода с уникальным именем.
Прикладная программа иногда использует идентификаторы, совпадающие по именам с идентификаторами, уже существующими в библиотеках системы C++Builder и различных внешних модулях. В соответствии с фундаментальной семантикой C++ любой идентификатор считается локальным. Если его описание не найдено в пределах текущего блока и метода, поиск продолжается в пределах
текущего класса, пространства имен и лишь потом — во внешних пространствах и библиотеках. Таким образом удается представлять программу в виде независимых модулей. С точки зрения реализации пространство имен представляет собой именованный раздел кода, к элементам которого надо обращаться с префиксом — именем этого раздела.
Определение пространства имен
Новое пространство имен описывается так:
namespace имя
содержимое-пространетва-имен }
В качестве имени задается допустимый идентификатор, который в дальнейшем служит именем создаваемого пространства имен. Внутри описания располагаются определения классов, структур, перечислимых типов, функций, переменных, а также другие пространства имен. Все они равно доступны внутри текущего пространства и не видны за его пределами.
Например:
namespace MySpace
{
class А { } ;
}
Данный код задает пространство имен MySpace, в котором определен класс а. Можно создать несколько пространств имен, внутри которых будут описаны понятия с одинаковыми именами:
namespace MySpacel { class А (
int I ;
} ;
}
namespace MySpace2 { class A {
double x;
} ;
}
В каждом из этих пространств определен свой класс а с оригинальной реализацией.
Если пространство имен объемное и содержат множество описаний, оно может быть разделено на несколько блоков (расположенных, возможно, в разных файлах), которые объединяются в одно пространство автоматически. При этом каждый из них должен начинаться с одного и того же заголовка. Например, следующий текст:
namespace MySpace { class А {} ;
}
// данное продолжение
// пространства имен MySpace
// размещено, возможно, в другом файле:
namespace MySpace {
class В {} ;
}
эквивалентен тексту:
namespace MySpace {
class А { } ;
class В f};
И в том, и в другом случае в пространство имен MySpace войдут два класса а и в.
Вложенные пространства имен
В пространство имен может входить, в частности, описание другого пространства имен. В таком случае вложенное пространство считается определенным в рамках родительского пространства, как и любые другие элементы последнего. Например:
namespace MySpace {
class А {} ;
namespace MyAddSpace { class В {} ;
}
}
Пространство имен MyAddSpace определено внутри пространства имен MySpace.
Безымянные пространства имен
При описании пространства имен его имя можно опустить:
namespace
{
class А { } ;
Подразумевается, что такое пространство имен получает условное уникальное имя, недоступное разработчику. Каждый идентификатор, определенный в подобном уникальном пространстве, заведомо будет отличаться от любого другого идентификатора, декларированного где-либо в текущем приложении. Таким образом удается поддерживать уникальность глобальных идентификаторов по всему проекту2.
Использование глобальных переменных считается плохим стилем программирования и допустимо только в исключительных случаях.
Доступ к элементам пространства имен
Все элементы, определенные внутри пространства имен, видимы только внутри него. Для доступа к любому определению из пространства имен надо задействовать префикс — имя пространства имен и два двоеточия. Например:
namespace MySpace {
class А { };
)
class В {
A obj; // ошибка компиляции!
} ;
В данном коде возникнет ошибка компиляции на описании ссылочной переменной obj (“Type name expected” — отсутствует определение типа). Описание класса А доступно только в рамках пространства имен MySpace, в которое класс в не входит. Корректный доступ к классу а возможен с помощью префикса:
class в {
MySpace::A obj; // корректно
}
Создание экземпляров классов, определенных во внешнем пространстве имен, выполняется аналогичным образом:
MySpace::А * obj = new MySpace::А();
Если используется вложенное пространство имен, то для доступа к нему надо ввести полную цепочку родительских пространств:
namespace MySpace {
class А {} ;
namespace MyAddSpace {
class В {} ;
}}
class C {
MySpace::MyAddSpace::В obj;
} ;
Если задействовано безымянное пространство имен, то для доступа к его элементам префикс не требуется:
namespace { int i ;
}
i = 5;
Пространство имен по умолчанию
Неудобно вводить имя пространства имен при каждом обращении к его содержимому. Названия многих пространств достаточно длинны, и при их постоянном
повторении код программы будет плохо читаться. Язык C++ включает директиву using namespace, которая задает пространство имен по умолчанию для текущего файла или текущего пространства имен, в котором она введена:
using namespace пространство-имен ;
При этом вложенные пространства имен остаются невидимыми и их надо подключать явно. Например:
namespace MySpace {
class А {};
}
A al; // ошибка компиляции
// делаем пространство имен MySpace
// доступным по умолчанию:
using namespace MySpace;
А а2 ; // ok
class В {
A obj; // класс А доступен
} ;
Если надо задействовать вложенное пространство имен, то для него требуется собственная директива using:
namespace MySpace {
class A {} ;
namespace MyAddSpace {
class В {} ;
} }
using namespace MySpace::MyAddSpace;
class C {
В bobj; // корректно
A aobj ; // ошибка!
} ;
При объявлении с помощью директивы using namespace пространства имен, вложенного в другое пространство, это родительское пространство остается недоступным и его также надо явно задавать в отдельной директиве. В данном примере класс А определен не в пространстве MySpace: :MyAddSpace, а в его родительском пространстве MySpace, которое явно не введено. Поэтому класс а не виден и компилятор сообщит об ошибке.
Вышеописанный способ подключения пространства имен с помощью using namespace называется декларацией такого пространства. В языке C++ существует также директива using, которая предназначена для подключения одного идентификатора из пространства имен:
using пространство-имен :: идентификатор ;
Например:
namespace MySpace { class А {} ;
class В {} ;
// делаем доступным в программе
// класс А без префикса:
using MySpace::А;
class С {
A aobj; // корректно
В bobj ; // ошибка! класс В доступен // только с префиксом MySpace::
Пространством имен по умолчанию считается область определений текущего проекта. К любому его идентификатору можно обращаться либо как обычно, по имени, либо с предварительным префиксом :: (но без имени пространства имен):
int N;
N = 5;
: : N = 5 ;
Альтернативные имена
Директива namespace позволяет объявлять в программе не только новые пространства имен, но и альтернативные имена (псевдонимы) для уже существующих. Таким образом, в частности, можно сократить длинные цепочки названий в пространстве имен.
Синтаксис второй формы директивы namespace таков:
namespace псевдоним = название ;
В качестве названия выступает уже существующее пространство имен, в качестве псевдонима — новый идентификатор, который далее всегда будет расцениваться как замена исходному названию. Например:
namespace MySpace { class А {} ;
namespace MyAddSpace { class В {} ;
}}
namespace ms = MySpace::MyAddScape;
Далее идентификатор ms будет использоваться как замена префиксу MySpace. MyAddSpace:
ms::В * obj = new ms: : В ( ) ;
Вопросы
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
Как организована обработка сообщений в Windows?
Как создается собственный обработчик сообщений Windows?
Как отослать сообщение Windows конкретному объекту и группе объектов?
Что такое интерфейс?
Чем интерфейс отличается от класса?
Как реализуются интерфейсы?
Как различаются интерфейсы с одинаковыми именами?
Что такое пространство имен?
Как организуется доступ к содержимому пространства имен?
Как записываются префиксы пространства имен?
Как задать в программе пространство имен по умолчанию?
^) РАЗДЕЛ
Технологии визуального проектирования
Принципы работы в среде C++Builder
Создание нового приложения
Редактирование кода программы
Компиляция и сборка приложения
Отладка программы
Визуальное проектирование и визуальные компоненты
Работа с мультимедийными объектами
Информационная безопасность
ГЛАВА 23
Работа со средой C++Builder
Среда C++Builder представляет собой интегрированную оболочку разработчика, в которую входит набор специализированных программ, ответственных за разные этапы создания приложения.
Установка системы C++Builder 2006
Перед тем как начать работу с системой C++Builder, ее необходимо установить на компьютер. Установка выполняется с помощью дистрибутивного комплекта системы. В процессе установки пользователь выбирает устанавливаемые компоненты и системы и определяет их конфигурацию.
Технические требования к системе C++Builder 2006
Система C++Builder 2006, входящая в комплект Borland Developer Studio 2006, работоспособна в операционных системах Windows 2000, Windows Server 2003 и Windows ХР Professional. Последняя из этих систем особо рекомендуется для использования.
Компьютер должен быть оборудован оперативной памятью объемом не менее 512 Мб (рекомендуется 1 Гбайт) и процессором класса Pentium III 1,4 ГГц (рекомендуется Pentium IV 2 ГГц). На жестком диске для системы C++Builder надо выделить не менее 750 Мбайт свободного пространства.
Анализ готовности операционной системы
Система C++Builder устанавливается в процессе инсталляции основного пакета Borland Developer Studio 2006. Можно установить только эту систему или же все элементы пакета для разработчиков.
Для установки системы запускают инсталляционную программу install.exe с первого компакт-диска дистрибутивного комплекта. Сначала эта программа проверяет, имеются ли на компьютере системные и прикладные приложения, нужные для функционирования среды Borland Developer Studio. В допустимых версиях Windows должны быть установлены: браузер Internet Explorer v6.0 SP1, версия среды Microsoft .NET 1.1 и ее обновление .NET 1.1 SP1, библиотека разработчика .NET Framework SDK 1.1, служба Microsoft XML Core Services (MSXML) 4.0 SP2 и среда Microsoft Visual J# .NET vl.l*.
Перед началом работы с системой C++Builder рекомендуется установить все последние обновления операционной системы Windows.
Если какой-либо из этих компонентов отсутствует, предлагается их установить. Все дополнительные компоненты имеются на одном из дисков дистрибутивного комплекта. Для их установки потребуется еще около 1 Гбайт свободного пространства в системном разделе жесткого диска.
Управление процессом установки
После проверки соответствия компьютера разработчика требованиям к наличию программного обеспечения начнется процесс установки системы Borland Developer Studio (рис. 23.1).
г5 Borland Developer Studio 2006 - Installation Wizard
Welcome to Borland Developer Studio 2006
The installation wizard will install Borland Developer Studio 2006 on your computer. Tо continue, dick Next.
Borland
CZZ^ElH^J 1 Cancel j
Puc. 23.1. Начальное окно процесса установки Borland Developer Studio
Выберите набор устанавливаемых средств разработки. Допускается отдельная, выборочнаяустановкакомпилятораязыкаС++для Win32 (C++Builder), компилятора Delphi для платформы .NET, компилятора Delphi для стандартных 32-разрядных приложений Windows (Win32) и компилятора языка C# для .NET (C#Builder). Впоследствии эти компиляторы можно вызывать как из интегрированной среды, так и по отдельности, через Главное меню (Пуск > Все программы > Borland Developer Studio 2006) — рис. 23.2.
Выберите для установки дополнительные компоненты среды C++Builder. Пункт Install CaliberRM означает установку клиентской части среды CaliberRM для управления требованиями к создаваемому программному продукту. Она нужна в коллективных проектах при создании крупных заказных систем. Пункты Install RaveReports и Install ComponentOne Studio установят на компьютер соответственно систему RaveReports для создания отчетов, которые затем можно встраивать в свои приложения, среду визуального проектирования таких отчетов и наборы дополнительных компонентов ComponentOne (рис. 23.3).
k? Borland Developer Studio 2006 - Installation Wizard
Setup Personalities
йГг?с<+ the per^ijnjlitie? you '/■.|зпГ inotelled on yout r.+fiLl’iTie
0 Borland Qelphi for the Microsoft .NET Framework
Choose this option to install Borland Delphi for the Microsoft .NET Framework features
0 Borland Delphi for Microsoft \Vin32
Choose this option to install Borland Delphi for Microsoft Win32 features
0 Borland C#Butider
Choose this option to install Borland C#8uitier features
0 Borland C++Builder
Choose this option to Install Borland C-H-BuSder features
| <Back ]| ^^^t ^ V| ( CanceT
Puc. 23.2. Окно выбора устанавливаемых средств разработки
£? Borland Developer Studio 2006 - Installation Wizard
Install Additional Components
Setup has deter mined that there зге additional program-; needed Рл ell of fne features to be fully functional. Choose the piograriis you 'A'lbh ^o inotell.
You can instil these programs now or install them later from the CD.
0 Install £a№rerRM
г-л Install Component^® Studio Enterprise for Borland Developer
ы Studio 2006
0 Install Rave Reports Borland Edition
Greyed out checkbox item implies that the software program Is not required for the personalities you selected for instate ton. You can however instil ft later from the CD ar by modifying the product from Add/Remove Programs in Control Panel,
[ Derails.■• ] [ <&ack| Cancel ]
Puc. 23.3. Окно установки дополнительных компонентов
Задайте соответствие расширений файлов приложениям комплекта Borland Developer Studio (рис. 23.4).
+? Hot Lin<l Developer Studio 2006 - Installation Wizard[- | j^j
Update File Associations
5+rjjp ha< round the nzdu'A'inn hie rvpe< are dbS+CbsC-d 'АчГIn other application^.. C'<i you
v+ant. Setup hj .д>>О':1зГе these file tvpe< ■•••.'ihr, Бел lurid Devek'per Studio 2wr,'
0 Borland Developer Studio fifes (.bdsproj, .bdsgrotp)
0^phi for the Мелкой ,NET Framework files (,pas, .dpr, .dpk, .nfm)
0 Delphi for Microsoft Win32 files (.pas, .dpr, .dpk, .dpkw, .dfm, .rc)
0£++ for Microsoft Win32 files (.epp, .hpp, .h, .bpr, .bpk, .bpf, .rc)
0 C# files (,cs)
0ASP.NET fifes Caspx. .asex, .ashx, .asax, ,asmx, .bdsdeploy)
[< Sack ] | ^ Next > ' j | ' 1
Pwc. 23.4. Окно настройки соответствий расширений файлов
Сам процесс установки на современных компьютерах проходит весьма быстро (рис. 23.5).
• Borland Developei Studio 2006 • Installation Wizaid
Installing Borland Developer Studio 2006
The Ultimate Windows Development Solution:
Merges Delphi Win32, Delphi .NET.C/C+-. and C# support tn a single highly productive development environment!
New IDE productivity features like a new code templates system, smart block completion, selective symbol table leading and enhanced remote debugging
Object-relational mapping and transparent object persistence with Rnflsnri Enterprise Core Objects ;E€O| HIDUllollti
Status: Copying new files
Estimated time remaining:
t ‘CanceR1 |
Puc. 23.5. Окно визуализации процесса установки
Основное окно среды C++Builder
Среду C++Builder запускают через Главное меню операционной системы: Пуск > Все программы > Borland Developer Studio 2006 > Borland Developer Studio 2006. Сразу после запуска среды на экране появится окно, показанное на рисунке 23.6.
ИН®ИН®, ^ ^ ^ ^ v®.' # S M й^ ► _- I? S3 ^ ,• / ; ФWelcome Page' •^. . ^ « Q IS M |bds:/default.htm▼[ t^, ^ ^ ^ ^ v®.' # S M й^ ► _- I? S3 ^ ,• / ; ФWelcome Page' •^. . ^ « Q IS M |bds:/default.htm▼[ t^|®..,Nsw Project: | g? Open Project 11 ^ 0|>вд гйё] | ^ Hg|p~||®..,Nsw Project: | g? Open Project 11 ^ 0|>вд гйё] | ^ Hg|p~|8 сентябри 2006 r.8 сентябри 2006 r.ResourcesResourcesRecent ProjectsRecent ProjectsRelease NotesReadmeInstallDeployDocumentationGetting StartedRelease NotesReadmeInstallDeployDocumentationGetting Started^ Project!, bdsproj• Project!.bdsproj♦ WebApplicationl .bdsproj5® ProjectGroupI .bdsgroup# Projectl. bdsproj^ Project!, bdsproj• Project!.bdsproj♦ WebApplicationl .bdsproj5® ProjectGroupI .bdsgroup# Projectl. bdsprojHow-To GuideDelphi Training ServicesUser's Guide (PDF)Language Reference (PDF)Reviewers Guide (PDF)1п<рг|гя1рг1 ParfnpreHow-To GuideDelphi Training ServicesUser's Guide (PDF)Language Reference (PDF)Reviewers Guide (PDF)1п<рг|гя1рг1 ParfnpreHeadlinesHeadlinesGetting StartedGetting StartedWhat is Borland Developer Studio?Borland Developer Studio is an integrated development environment (IDE) tor building Delphi, Delphi for.NET, C#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cycle.r>^li3oi:.X}>iiaCC« TD5'VWhat is Borland Developer Studio?Borland Developer Studio is an integrated development environment (IDE) tor building Delphi, Delphi for.NET, C#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cycle.r>^li3oi:.X}>iiaCC« TD5'V■ Browser■ BrowserМб? Tool PaletteV XSt - ^ ^+" Other Hies - C++Builder ProjectsQ Console ApplicationStatic Library^6 PackageDynamic-link Library2 VCLForrM ApptCallon чД^, Control Panel Application® Service ApplicationR SDI ApplicationQ MDI Application * C++Builder Projects 1 C++Builder Files * Web Documents +. C++Builder Projects 1 WebSnap + C++Builder Projects ] WebServices •+ C++Bu8der Projects 1 WebBroker + C++Builder Projects ] Intraweb * Delphi Projects ] Intraweb + C++Builder Projects ] ActiveXМб? Tool PaletteV XSt - ^ ^+" Other Hies - C++Builder ProjectsQ Console ApplicationStatic Library^6 PackageDynamic-link Library2 VCLForrM ApptCallon чД^, Control Panel Application® Service ApplicationR SDI ApplicationQ MDI Application * C++Builder Projects 1 C++Builder Files * Web Documents +. C++Builder Projects 1 WebSnap + C++Builder Projects ] WebServices •+ C++Bu8der Projects 1 WebBroker + C++Builder Projects ] Intraweb * Delphi Projects ] Intraweb + C++Builder Projects ] ActiveX
ИН®
ИН®
, ^ ^ ^ ^ v®.' # S M й^ ► _- I? S3 ^ ,• / ; Ф
Welcome Page' •
^. . ^ « Q IS M |bds:/default.htm▼[ t^
, ^ ^ ^ ^ v®.' # S M й^ ► _- I? S3 ^ ,• / ; Ф
Welcome Page' •
^. . ^ « Q IS M |bds:/default.htm▼[ t^
|®..,Nsw Project: | g? Open Project 11 ^ 0|>вд гйё] | ^ Hg|p~|
|®..,Nsw Project: | g? Open Project 11 ^ 0|>вд гйё] | ^ Hg|p~|
8 сентябри 2006 r.
8 сентябри 2006 r.
Resources
Resources
Recent Projects
Recent Projects
Release Notes
Readme
Install
Deploy
Documentation
Getting Started
Release Notes
Readme
Install
Deploy
Documentation
Getting Started
^ Project!, bdsproj
• Project!.bdsproj
♦ WebApplicationl .bdsproj
5® ProjectGroupI .bdsgroup
# Projectl. bdsproj
^ Project!, bdsproj
• Project!.bdsproj
♦ WebApplicationl .bdsproj
5® ProjectGroupI .bdsgroup
# Projectl. bdsproj
How-To Guide
Delphi Training Services
User's Guide (PDF)
Language Reference (PDF)
Reviewers Guide (PDF)
1п<рг|гя1рг1 Parfnpre
How-To Guide
Delphi Training Services
User's Guide (PDF)
Language Reference (PDF)
Reviewers Guide (PDF)
1п<рг|гя1рг1 Parfnpre
Headlines
Headlines
Getting Started
Getting Started
What is Borland Developer Studio?
Borland Developer Studio is an integrated development environment (IDE) tor building Delphi, Delphi for.NET, C#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cycle.
r>^li3oi:.X}>iiaCC« TD5'V
What is Borland Developer Studio?
Borland Developer Studio is an integrated development environment (IDE) tor building Delphi, Delphi for.NET, C#, and C++ applications. The Borland Developer Studio IDE provides a comprehensive set of tools that streamline and simplify the development life cycle.
r>^li3oi:.X}>iiaCC« TD5'V
■ Browser
■ Browser
Мб? Tool PaletteV X
St - ^ ^
+" Other Hies - C++Builder Projects
Q Console Application
Static Library
^6 Package
Dynamic-link Library
2 VCLForrM ApptCallon чД
^, Control Panel Application
® Service Application
R SDI Application
Q MDI Application * C++Builder Projects 1 C++Builder Files * Web Documents +. C++Builder Projects 1 WebSnap + C++Builder Projects ] WebServices •+ C++Bu8der Projects 1 WebBroker + C++Builder Projects ] Intraweb * Delphi Projects ] Intraweb + C++Builder Projects ] ActiveX
Мб? Tool PaletteV X
St - ^ ^
+" Other Hies - C++Builder Projects
Q Console Application
Static Library
^6 Package
Dynamic-link Library
2 VCLForrM ApptCallon чД
^, Control Panel Application
® Service Application
R SDI Application
Q MDI Application * C++Builder Projects 1 C++Builder Files * Web Documents +. C++Builder Projects 1 WebSnap + C++Builder Projects ] WebServices •+ C++Bu8der Projects 1 WebBroker + C++Builder Projects ] Intraweb * Delphi Projects ] Intraweb + C++Builder Projects ] ActiveX
A Borland Developer Studio 2006
File Edit Search View Refactor Project Run Component Tods StarTeam Window Hefei . pest Layout ^j ^ ^^
Tool Palette®Tool Palette®^ - ■ fe Т+ Other Fries•+ C++Builder Projects+ C++Builder Projects | C++Builder files+ Web Documents+ C++Buflder Projects | WebSnap+ C++Buttder Projects | WebServices+ C++Buffdet Projects | WebBroker■•■ C++Builder Projects | Intraweb+ Delphi Projects | Intraweb+ C++Builder Projects | ActiveX^ - ■ fe Т+ Other Fries•+ C++Builder Projects+ C++Builder Projects | C++Builder files+ Web Documents+ C++Buflder Projects | WebSnap+ C++Buttder Projects | WebServices+ C++Buffdet Projects | WebBroker■•■ C++Builder Projects | Intraweb+ Delphi Projects | Intraweb+ C++Builder Projects | ActiveXPuc. 23.7. Окно групппроектовPuc. 23.7. Окно групппроектов
Tool Palette®
Tool Palette®
^ - ■ fe Т
+ Other Fries
•+ C++Builder Projects
+ C++Builder Projects | C++Builder files
+ Web Documents
+ C++Buflder Projects | WebSnap
+ C++Buttder Projects | WebServices
+ C++Buffdet Projects | WebBroker
■•■ C++Builder Projects | Intraweb
+ Delphi Projects | Intraweb
+ C++Builder Projects | ActiveX
^ - ■ fe Т
+ Other Fries
•+ C++Builder Projects
+ C++Builder Projects | C++Builder files
+ Web Documents
+ C++Buflder Projects | WebSnap
+ C++Buttder Projects | WebServices
+ C++Buffdet Projects | WebBroker
■•■ C++Builder Projects | Intraweb
+ Delphi Projects | Intraweb
+ C++Builder Projects | ActiveX
Puc. 23.7. Окно групппроектов
Puc. 23.7. Окно групппроектов
Puc. 23.6. Начальное окно C++Builder
Главная особенность начального окна — вспомогательное окно Welcome Page (Начальная страница), для отображения которого используется браузер Internet Explorer. В этом окне сосредоточены ссылки на полезные ресурсы Интернета и локальные справочные документы по всем продуктам Borland. В разделе Recent Projects отображаются ссылки на недавно открывавшиеся проекты.
Проектировщик форм и редактор кода пока скрыты, так как проект еще не создан. Список типов доступных проектов можно получить командой File > New > Other (Файл > Создать > Другое), при этом активируется так называемый Репозиторий объектов (см. ниже).
Содержимое активно используемой в ходе работы палитры инструментов (Tool Palette) меняется в зависимости от текущего проекта. На палитре инструментов (рис. 23.7) проекты сгруппированы по категориям. Группы проектов, доступные разработчику, приведены в таблице 23.1.
Одно из существенных усовершенствований
среды Borland Developer Studio — объединение нескольких систем разработки на языках C++, Delphi и C# для разных платформ.
Таблица 23.1. Основные виды проектов C++Builder
Проект
Описание
C++Builder Projects
Приложения VCL для платформы Win32
C++Builder Projects I C++Builder Files
Вспомогательные файлы для проектов C++Builder для Win32
C++Builder Projects I WebSnap
Средства создания приложений WebSnap на языке C++
C++Builder Projects I WebServices
Средства создания веб-служб на языке C++
C++Builder Projects I WebBroker
Средства создания кросс-платформных приложений для Интернета на языке C++
C++Builder Projects I Intraweb
Средства создания проектов IntraWeb для Win32 на языке C++
C++Builder Projects I ActiveX
Компоненты ActiveX, создаваемые с помощью языка C++
Other Files
Вспомогательные файлы для проектов C++
Web Documents
Файлы в различных форматах для веб-проектов
Разработчикам приложений .NET доступна возможность программирования как на языке Delphi, так и на языке С#. Кроме того, в версии компиляторов Borland Developer Studio для .NET входит технология ECO III. Она позволяет строить приложения на базе моделей UML и формировать с помощью языка UML не только статические аспекты модели, но и логику ее работы на базе диаграмм состояний.
Репозиторий объектов
В среде C++Builder имеется удобная и важная возможность ускорения процесса разработки за счет размещения шаблонов и заготовок проектов и форм в едином хранилище, называемом Репозиторий объектов (Object Repository).
Репозиторий объектов — это хранилище шаблонов форм и заготовок проектов.
Доступ к содержимому Репозитория объектов выполняется командой File > New > Other (Файл > Создать > Другое). Окно Репозитория содержит набор пустых заготовок проектов (рис. 23.8).
Элементы Репозитория можно модифицировать и удалять (через контекстное меню). Содержимое Репозитория нетрудно расширить — для этого достаточно добавить, например, текущий проект в качестве шаблона с новым именем. Для этого надо довести текущий проект до желаемого состояния и дать команду Project > Add to Repository (Проект > Добавить в Репозиторий).
Палитра инструментов
Для всех операций, связанных с управлением проектом, в среде C++Builder используется единое средство — палитра инструментов. Состав палитры инструментов автоматически изменяется в зависимости от операции, выполняемой в данный момент.
* New Items
Item Categories:i~ Гд C++BuilderProjectsL^i ActiveXГ2 C++Builder Files121 Intraweb i"""! WebBroker C3 WebServices О WebSnap^- £21 Delphi ProjectsО Intraweb112 Other Files f2: Web DocumentsItem Categories:i~ Гд C++BuilderProjectsL^i ActiveXГ2 C++Builder Files121 Intraweb i"""! WebBroker C3 WebServices О WebSnap^- £21 Delphi ProjectsО Intraweb112 Other Files f2: Web DocumentsConsole Control PanelConsole Control PanelApplication ApplicationApplication ApplicationDynamic-linkLibraryDynamic-linkLibraryMDIApplicationMDIApplicationPackagePackageStatic LibraryStatic LibraryVCL Forms ApplicationVCL Forms ApplicationCancel ICancel I
Item Categories:i~ Гд C++BuilderProjects
L^i ActiveX
Г2 C++Builder Files
121 Intraweb i"""! WebBroker C3 WebServices О WebSnap
^- £21 Delphi Projects
О Intraweb
112 Other Files f2: Web Documents
Item Categories:i~ Гд C++BuilderProjects
L^i ActiveX
Г2 C++Builder Files
121 Intraweb i"""! WebBroker C3 WebServices О WebSnap
^- £21 Delphi Projects
О Intraweb
112 Other Files f2: Web Documents
Console Control Panel
Console Control Panel
Application Application
Application Application
Dynamic-linkLibrary
Dynamic-linkLibrary
MDI
Application
MDI
Application
Package
Package
Static Library
Static Library
VCL Forms Application
VCL Forms Application
Cancel I
Cancel I
Tool Palette|SjTool Palette|Sj^ fe Т _ i+ StandardЖ■+ Additional-^-2 Win32~TTabControl^) TPageControlЩ TRichEdit^ TTrackBaress TProgressBar ^^ fe Т _ i+ StandardЖ■+ Additional-^-2 Win32~TTabControl^) TPageControlЩ TRichEdit^ TTrackBaress TProgressBar ^Рис. 23.9. ПалитраинструментовРис. 23.9. Палитраинструментов
Tool Palette|Sj
Tool Palette|Sj
^ fe Т _ i+ StandardЖ
■+ Additional-^
-2 Win32~
TTabControl
^) TPageControl
Щ TRichEdit
^ TTrackBar
ess TProgressBar ^
^ fe Т _ i+ StandardЖ
■+ Additional-^
-2 Win32~
TTabControl
^) TPageControl
Щ TRichEdit
^ TTrackBar
ess TProgressBar ^
Рис. 23.9. Палитраинструментов
Рис. 23.9. Палитраинструментов
Puc. 23.8. Окно репозитория с заготовками проектов
Новый проект (например, приложение VCL) создают двойным щелчком мышью на элементе палитры инструментов или на подходящем значке в Репозитории. При этом на экране появляется окно Проектировщика — в нем активна закладка Design (Проектирование). Переключиться из Проектировщика в редактор можно через закладку Code (Кодирование).
После открытия окна Проектировщика содержимое палитры инструментов изменяется автоматически. На ней представлены тематические группы компонентов, доступные для размещения на проектируемой форме в окне Проекти
ровщика.
В данном случае компонент палитры инструментов — это заготовка элемента управления или объекта с определенным набором функциональных возможностей. Из таких заготовок в проекте создаются конкретные экземпляры объектов и
элементы управления на форме.
Компонент, размещаемый на форме, выбирают в палитре инструментов щелч
ком мыши. Для автоматического размещения выбранного компонента достаточно дважды щелкнуть на его имени в палитре.
Для настройки внешнего вида палитры инструментов служит раздел Tool Palette (Палитра инструментов) в диалоговом окне общих настроек среды C++Builder, которое открывают командой Tools > Options (Сервис > Параметры). Здесь настраивают размеры значков компонентов (Button Size), режимы размещения групп компонентов и их автоматического сворачивания (Auto Collapse Categories), вертикального расположения
заголовков (Vertical Category Captions), а также цветовые схемы (подраздел Colors) — рис. 23.9.
Рис. 23.10. Быстрый поиск элемента по имениРис. 23.10. Быстрый поиск элемента по имениСодержимое палитры инструментов разделено на тематические группы элементов (категории). Для быстрого поиска нужного элемента достаточно выделить палитру мышью и ввести имя искомого компонента (например, tsql). Список, представленный в палитре, автоматически прокручивается к подходящей строке, а набор доступных элементов сокращается так, чтобы соответствовать введенному тексту, отображаемому в заголовке палитры. Отменяют режим поиска клавишей ESC.
Рис. 23.10. Быстрый поиск элемента по имени
Рис. 23.10. Быстрый поиск элемента по имени
Порядок элементов в определенной категории палитры инструментов настраивают по вкусу. Для этого нужный
элемент перетаскивают в новое место в списке. Схожим образом меняют и поря
док категорий.
Допускается добавление в палитру дополнительных категорий и компонентов, а также шаблонов — групп компонентов, расположенных на форме. Для этого в контекстном меню палитры выбирают пункт Add New Category (Добавить новую категорию) и в открывшемся диалоговом окне вводят имя новой категории. Элементы в нее добавляют перетаскиванием из других категорий палитры.
Проектирование форм
Проектирование форм — одна из ключевых технологий визуальной разработки приложений в среде C++Builder. Разработчик создает и настраивает внешний вид формы и всех связанных с ней элементов управления, шаблоны которых хранятся в палитре инструментов, с помощью Проектировщика.
Проектировщик — это средство визуального проектирования пользовательского интерфейса.
Исходно в Проектировщике создается пустая заготовка главного окна формы. Элементы управления выбирают на палитре инструментов и размещают на форме, а затем программируют логику их работы. Любой элемент правления, размещенный на форме, можно перемещать по форме с помощью мыши и устанавливать в нужное место. Для изменения размера выбранного элемента используют маркеры изменения размера, расположенные на границе элемента.
Компоненты палитры инструментов бывают визуальными и невизуальными. Визуальные компоненты — это элементы пользовательского интерфейса. Невизуальные компоненты задают дополнительную функциональность, не связанную напрямую с работой интерфейса формы, и размещаются в пределах проектируемой формы.
Первоначальное представление элементов управления, перенесенных на форму из палитры инструментов, определяется общими настройками программы: Tools > Options > Windows Forms Designer (Сервис > Параметры > Проектировщик форм Windows) — рис. 23.11.
Шаг сетки, отображаемой на форме, задается в полях Grid Size (Шаг сетки). Элементы управления автоматически выравниваются по этой сетке. Флажок Show grid (Показать сетку) позволяет отключать отображение сетки. Флажок Snap to grid
+' Environment Options Object InspectorFi Tool PaletteColorsEnvironment VariablesExplorer +' Environment Options Object InspectorFi Tool PaletteColorsEnvironment VariablesExplorer Grid OptionsGrid OptionsP Show gridP Show gridlv Snag, to gridlv Snag, to grid[Windows Forms Designer[Windows Forms Designer--• C++ OptionsPaths and DirectoriesType LibraryDelphi OptionsVCL DesignerEditor Options Source OptionsDisplay Key MappingsCode Insight- HTML/ASP.NET Options HTML Formatting+ HTML Tidy Options--• C++ OptionsPaths and DirectoriesType LibraryDelphi OptionsVCL DesignerEditor Options Source OptionsDisplay Key MappingsCode Insight- HTML/ASP.NET Options HTML Formatting+ HTML Tidy Options• Code Generation Bracing Style——■ Г" standard Style Displays braces on the same line as a function declaration For newly generated code.• Code Generation Bracing Style——■ Г" standard Style Displays braces on the same line as a function declaration For newly generated code.Displays braces on the fine following a function<• £ Styledeclaration for newly generated code.Displays braces on the fine following a function<• £ Styledeclaration for newly generated code.
+' Environment Options Object Inspector
Fi Tool Palette
Colors
Environment Variables
Explorer
+' Environment Options Object Inspector
Fi Tool Palette
Colors
Environment Variables
Explorer
Grid Options
Grid Options
P Show grid
P Show grid
lv Snag, to grid
lv Snag, to grid
[Windows Forms Designer
[Windows Forms Designer
--• C++ Options
Paths and Directories
Type Library
Delphi Options
VCL Designer
Editor Options Source Options
Display Key Mappings
Code Insight
- HTML/ASP.NET Options HTML Formatting
+ HTML Tidy Options
--• C++ Options
Paths and Directories
Type Library
Delphi Options
VCL Designer
Editor Options Source Options
Display Key Mappings
Code Insight
- HTML/ASP.NET Options HTML Formatting
+ HTML Tidy Options
• Code Generation Bracing Style——
■ Г" standard Style Displays braces on the same line as a function declaration For newly generated code.
• Code Generation Bracing Style——
■ Г" standard Style Displays braces on the same line as a function declaration For newly generated code.
Displays braces on the fine following a function
<• £ Styledeclaration for newly generated code.
Displays braces on the fine following a function
<• £ Styledeclaration for newly generated code.
Cancel I
Puc. 23.11. Окно настроек Проектировщика форм
(Привязка к сетке) отключает режим автоматического выравнивания элементов по узлам сетки, даже если она показана.
Интерактивный Проектировщик и направляющие
При работе с Проектировщиком форм доступен режим так называемых направляющих линий (Designer Guidelines). Он по умолчанию включен, а настраивается флажком Use designer guidelines (Использовать направляющие в Проектировщике) в свойствах среды: Tools > Options > Environment Options > VCL Designer (Сервис > Параметры > Параметры среды > Проектировщик VCL).
При перемещении компонентов с помощью мыши на форме автоматически возникают и исчезают направляющие — линии, обеспечивающие выравнивание переносимого элемента по границам соседних компонентов формы. Это упрощает выравнивание, нередко проводимое «на глазок», особенно если шаг сетки Проектировщика невелик, а компонентов на форме много.
Проектировщик документов HTML
При подготовке веб-форм следует использовать особенности дизайна, специфичные для страниц HTML. При разработке веб-документов доступен специальный режим работы Проектировщика. Его используют при создании сетевых приложений, в которые помимо текста на языке C++ входят документы на веб-языках (например, HTML, XML, ASP и других). Соответствующий файл обычно доступен на отдельной закладке, а его синтаксис в редакторе маркируется в соответствии с правилами используемого языка разметки.
Редактор кода HTML снабжен усовершенствованиями, повышающими скорость и комфортность обработки файлов стилей .CSS и тегов HTML. Можно просмотреть структуру документа HTML в иерархическом виде в Просмотрщике структуры, вызываемом командой View > Structure (Вид > Структура).
В среду C++Builder встроена система форматирования документов HTML, базирующаяся на общедоступном решении Tidy (tidy.sourceforge.net). Оно разработано при поддержке консорциума WWW, вырабатывающего стандарты для Сети. При использовании решения Tidy представление готового документа HTML максимально приближено к официально рекомендованному. Настройки режима форматирования Tidy доступны в разделе Tools > Options > HTML/ASP.NET Options > HTML Tidy Options (Сервис > Параметры > Параметры HTML/ASP.NET > Параметры HTML Tidy).
Инспектор объектов
Для задания свойств объектов и определения их реакции на события в среде C++Builder предусмотрено специальное средство. Это Инспектор объектов, важный компонент среды разработки. В окне Инспектора объектов настраиваются свойства и события элемента проекта, выбранного в Проектировщике. Содержимое этого окна зависит от того, какой объект выбран в Проектировщике в данный момент.
В окне Инспектора объектов на закладке Properties (Свойства) представлены свойства выбранного объекта, разделенные на тематические группы. На закладке Events (События) приводится список событий, доступных для обработки. Для настроек с помощью Инспектора объектов доступны свойства любого объекта Проектировщика форм. Для каждого свойства указано текущее значение, которое может быть изменено разработчиком.
Размещенные на форме элементы обычно упоминаются в исходном коде программы. Доступ к элементу осуществляется по его имени, которое представлено в Инспекторе объектов свойством Name. Оно имеется у всех объектов без исключения.
Некоторые компоненты (прежде всего невизуальные) имеют встроенные редакторы свойств. Так, компоненты связи с базами данных обычно содержат внутренние редакторы для формирования запросов SQL к базам данных. Для доступа к подобным редакторам служат ссылки в окне Инспектора объектов (рис. 23.12).
Менеджер проектов
Менеджер проектов — это элемент среды, традиционный для всех версий Borland Developer Studio. Окно Менеджера открывают командой View > Project Manager (Вид > Менеджер проектов). Менеджер проекта по умолчанию совмещен с Проводником данных Data Explorer, предназначенным для визуального взаимодействия с СУБД. Проводник данных позволяет устанавливать соединения с
базами данных и тесно интегрирован с Проектировщиком. При переносе на форму таблицы, участвующей в соединении, компоненты, обеспечивающие связь с базой, добавляются и настраиваются автоматически.
Менеджер проекта предназначен для визуального сопровождения проекта. Проект представлен в виде дерева файлов (исходных текстов, описаний форм, исполнимого кода, библиотек).
Структура проекта представлена в Менеджере проекта в виде дерева. Корневой узел представлен группой проектов (по умолчанию — ProjectGroupI). Группа может объединять несколько проектов, между которыми легко переключаться. Уровнем ниже располагаются конкретные проекты. Они, в свою очередь, складываются из файлов исходных текстов и форм, а также всевозможных вспомогательных
Project!.bdsproj - Project... ®Project!.bdsproj - Project... ®,T$Pv$tivMe ▼ g?№sw @FГрйе\i@ ProjectGroupI^H {^j Projectl.exe!+;■ Щ Project!.cppI В Project!,res! S И Unitl.cpp-® SE0E3;@ Unitl.dfmi®uniti.h"j^yProjectl..., f^Data Exp.,,'Puc. 23.13. Менеджерпроектов,T$Pv$tivMe ▼ g?№sw @FГрйе\i@ ProjectGroupI^H {^j Projectl.exe!+;■ Щ Project!.cppI В Project!,res! S И Unitl.cpp-® SE0E3;@ Unitl.dfmi®uniti.h"j^yProjectl..., f^Data Exp.,,'Puc. 23.13. Менеджерпроектовфайлов, которые для удобства и наглядности размещаются в отдельных узлах дерева (рис. 23.13).
Project!.bdsproj - Project... ®
Project!.bdsproj - Project... ®
,T$Pv$tivMe ▼ g?№sw @F
Грйе\
i@ ProjectGroupI
^H {^j Projectl.exe
!+;■ Щ Project!.cpp
I В Project!,res
! S И Unitl.cpp
-® SE0E3
;@ Unitl.dfm
i®uniti.h
"j^yProjectl..., f^Data Exp.,,'
Puc. 23.13. Менеджерпроектов
,T$Pv$tivMe ▼ g?№sw @F
Грйе\
i@ ProjectGroupI
^H {^j Projectl.exe
!+;■ Щ Project!.cpp
I В Project!,res
! S И Unitl.cpp
-® SE0E3
;@ Unitl.dfm
i®uniti.h
"j^yProjectl..., f^Data Exp.,,'
Puc. 23.13. Менеджерпроектов
Настройка среды C++Builder
Рис. 23.14. Окно настройки расцветок синтаксисаРис. 23.14. Окно настройки расцветок синтаксисаСреда C++Builder выполнена в стиле, сходном с классическим интерфейсом среды разработки Microsoft Visual Studio. Немало приемов работы с ней приведены в соответствие с неформальным стандартом Microsoft на средства разработки. Этот подход весьма удобен для программиста, так как отпадает необходимость изучения новых интерфейсов. Все настроечные свойства среды C++Builder доступны по команде Tools > Options (Сервис > Параметры). Пример настроек расцветок синтаксиса для языка C++ приведен на рисунке 23.14.
Рис. 23.14. Окно настройки расцветок синтаксиса
Рис. 23.14. Окно настройки расцветок синтаксиса
Настройку начинают с выбора категории параметров на левой панели окна. Набор доступных элементов управления соответствует выбранной категории. Основные категории настроек приведены в таблице 23.2.
Таблица 23.2. Основные категории настроек среды C++Builder
,Категория
Описание
Environment Options (Параметры среды)
Общие настройки среды
Editor Options (Параметры редактора)
Настройки редактора исходных текстов
HTMUASP.NET Options (Параметры HTMUASP.NET)
Настройки редактора HTML и средств поддержки технологии ASP.NET
Optimizelt (Оптимизировать)
Настройки системы оптимизации кода
WebSnap
Настройки технологии WebSnap
CORBA
Настройки технологии CORBA
Debugger Options (Параметры отладчика)
Настройки встроенного отладчика
Вопросы
Каковы технические требования к среде C++Builder?
Каковы основные группы проектов, поддерживаемых средой C++Builder?
Как создается новый проект?
Как работать с палитрой инструментов?
Как работать с Проектировщиком форм?
Как работать с Инспектором объектов?
Зачем нужен Менеджер проекта?
ГЛАВА 24 Принципы создания приложений в среде C++Builder
Интегрированная среда разработки C++Builder ориентирована, прежде всего, на визуальные способы создания, проектирования, программирования и развертывания приложений. С этой целью в нее встроено множество вспомогательных мастеров, утилит, средств настройки и визуализации создаваемой программы.
Рабочий стол
Работа со средой C++Builder строится на концепции рабочего стола. Рабочий стол — это пространство разработчика, которое настраивается под нужды кон
кретных проектов и персональные пристрастия.
Программист может настроить внешний вид практически всех элементов среды C++Builder. Каждый из них, обычно представленный в отдельном окне или на отдельной вкладке, может быть состыкован с другими окнами. Состыкованные окна перемещаются по рабочему столу как целое.
Настройки рабочего стола можно сохранить командой View > Desktops > Save Desktop (Вид > Рабочие столы > Сохранить рабочий стол). Впоследствии легко переключить вид рабочего стола выбором сохраненной настройки из списка. Для этого надо обратиться к панели, расположенной в строке меню. С ее помощью можно выбрать сохраненную настройку, сохранить текущее представление рабочего стола, а также выбрать готовую раскладку рабочего стола, предназначенную для
отладки программ, командой Set debug desktop (Установить рабочий стол отладки).
Default LayoutlDefault Layoutl|<None>[Debug Layout [Default Layout|<None>[Debug Layout [Default LayoutСтандартная настройка рабочего стола Default Layout (Раскладка по умолчанию) поддерживает режим стыковки для всех окон. Еще один предопределенный вариант настроек, Classic Undocked (Классический без стыковки), задает самостоятельное существование каждого из окон. Размеры их можно менять
Default Layoutl
Default Layoutl
|<None>
[Debug Layout [Default Layout
|<None>
[Debug Layout [Default Layout
произвольно, но подстройка под окна-соседи не выполняется.
Основные операции на рабочем столе выполняются в редакторе исходного текста программы (Code Editor) или в Проектировщике форм (Designer).
Проектировщик предназначен для проектирования окон приложения и создания пользовательского интерфейса. Разработчик в визуальном режиме размещает на форме будущего окна элементы управления. Одни и те же приемы проектирования используются при создании обычных форм . .
Windows, веб-форм и страниц HTML. Для переключения меж- : Т0^®рв.5!9П/^
ду Проектировщиком и редактором служат закладки Design (Проектирование) и Code (Кодирование).
Мастера приложений
Для ускорения операций по созданию программ в состав среды C++Builder входят специальные средства ускоренного визуального создания приложения или его элементов, называемые Мастерами. Как правило, Мастер C++Builder создает работоспособный шаблон, пустую заготовку программы, автоматически. Механизмы создания такой заготовки приложения поддерживают следующие технологии.
Классические оконные формы (VCL Forms) на основе библиотеки визуальных компонентов VCL создают командой File > New > VCL Forms Application - C++Builder (Файл > Создать > Приложение VCL Forms — C++Builder). На палитре инструментов доступны следующие наборы компонентов.
Standard (Стандартные). Стандартные элементы пользовательского интерфейса: кнопки, меню, поля ввода и вывода, списки и другие.
Additional (Дополнительные). Дополнительные элементы управления с расширенными возможностями.
Win32. Элементы управления, введенные в последние версии операционной системы Windows.
System (Системные). К системным элементам относятся: таймер, область рисования и мультимедийный проигрыватель.
Dialogs (Диалоговые окна). Стандартные диалоговые окна для работы с файлами, настройки шрифтов, печати.
Data Access (Доступ к данным), Data Controls (Управление данными), dbExpress, DataSnap, BDE, Interbase, dbGo. Наборы компонентов для работы с СУБД.
Internet (Интернет), Indy, Intra Web, WebSnap. Наборы компонентов для работы с Интернетом.
Создание многоуровневых приложений (Multitier) подразумевает программирование систем, в которых взаимодействие с СУБД выделено в отдельный логический уровень.
Разработка веб-сервисов (веб-служб) обычно не связана с проектированием интерфейса, так как такие службы выполняются как серверные компоненты. Однако на палитре инструментов представлены наборы компонентов для доступа к СУБД.
Неисполняемые проекты, связанные с созданием и редактированием сетевых документов в форматах XML и смежных с ним, доступны на закладке Web Documents (Веб-документы) в окне Репозитория.
Технология создания простых приложений
Последовательность разработки приложения не зависит от его назначения. Для всех приложений используется примерно одинаковый порядок действий.
Создание пустой заготовки
Пустое приложение не выполняет никаких функций, но тем не менее представляет собой готовую программу, которую можно запустить. Его создают с помощью Мастера.
Проектирование формы
Мастер приложения формирует пустую заготовку и вызывает Проектировщик форм. Проектировщик предоставляет разработчику текущую, исходно пустую, форму создаваемого приложения. На ней размещают элементы управления (кнопки, текстовые поля, надписи, списки и другие) и вспомогательные объекты, с помощью которых реализуется алгоритм работы программы. Для размещения объекта на форме его выбирают в палитре инструментов (соответствующая строка палитры подсвечивается) и устанавливают на форму щелчком мыши в нужном месте1.
Компиляция и запуск
После того как пользовательский интерфейс создан, программу можно скомпилировать и запустить. Компиляцию без запуска выполняют комбинацией клавиш CTRL+F9, компиляцию с последующим запуском — клавишей F9. При этом элементы управления, размещенные на форме, формально работоспособны (на кнопки можно нажимать, элементы меню можно выбирать), однако полезных действий такая пустая программа, конечно, не выполняет.
Программирование и создание обработчиков событий
Программа, решающая нужную задачу, должна содержать программный код, реализующий логику алгоритма. Этот код выполняется по команде пользователя, например по нажатию клавиши или по щелчку мыши в определенной области окна. Связь кода с действием человека обеспечивается так называемым обработчиком пользовательского действия (или события). Имеется два способа формирования такого обработчика.
Выберите элемент управления (например, кнопку), в Инспекторе объектов. Перейдите на закладку Events (События) и найдите имя события, подлежащего обработке. Дважды щелкните на нем мышью. Откроется окно редактора, в котором появится автоматически сгенерированный код обработчика.
Если обрабатываемое действие допускает такой подход, обработчик создают непосредственно из проектируемой формы. Если дважды щелкнуть мышью на кнопке формы, среда C++Builder автоматически переключится в редактор, где появится обработчик события нажатия на кнопку. Подобным способом формируют обработчики выбора пункта меню, переключения флажка и другие.
В обоих случаях в редакторе появится примерно такой код: void fastcall TForml::ButtonlClick(TObject *Sender) ( )
Данная функция (обработчик события) вызывается автоматически при наступлении соответствующего события в программе.
1 Для автоматического добавления объекта дважды щелкните на нужной строке палитры инструментов. В этом случае объект обычно добавляется на форму.
Обработчик всегда принадлежит классу формы, а не классу конкретного элемента управления. Он расположен в отдельном модуле, хранящем описание текущей формы. По умолчанию модуль с расширением .СРР называется unitl (или unit2, Unit3... — в зависимости от числа форм проекта), а класс формы по умолчанию называется TFormi. Этот класс генерируется средой C++Builder автоматически. Исходное содержимое модуля выглядит следующим образом:
#include <vcl.h>
#pragma hdrstop
#include "Unitl.h"
//
#pragma package(smart_init)
#pragma resource "*.dfm"
TFormi *Forml;
//
fastcall TFormi::TFormi(TComponent* Owner)
: TForm(Owner)
(
}
При желании имя класса формы (TFormi) изменяют с помощью свойства формы Name в Инспекторе объектов. При его модификации автоматически модифицируется исходный код — имена классов в префиксе процедур-обработчиков изменятся. Сама форма как объект доступна из других модулей через глобальную переменную-указатель Formi.
Добавление новых форм к проекту
В относительно крупных проектах задействуется не одна, а несколько форм. Одна форма проекта является главной и отображается при запуске проекта по умолчанию. Прочие могут предназначаться, например, для ввода настроек программы или отображения дополнительной информации. Новую форму добавляют в проект командой File > New > Other (Файл > Создать > Другое). В открывшемся окне New Items (Новые объекты) выбирают категорию C++Builder Projects > C++Builder Files (Проекты C++Builder > Новые файлы). Новую форму создают двойным щелчком на значке VCL Form (Форма VCL) для проекта VCL (рис. 24.1).
Новую главную форму задают в окне настройки проекта, открываемом командой Project > Options (Проект > Параметры). Главную форму из числа доступных форм проекта выбирают в раскрывающемся списке Main form (Главная форма) в категории Forms (Формы).
Использование компонентов
В проектах обычно присутствуют не только визуальные компоненты, в том или ином виде представленные на экране, но и невизуальные. К таковым относятся, например, таймер, компоненты управления базами данных, средства поддержки работы в Интернете и другие. Если сформирован проект VCL, то такие компоненты размещаются на форме наряду с визуальными.
^ New Items
Item Categories:': i-- 7 । C++Builder Projects' О ActiveX7'' C++Builder Filesi7" Inheritable Items7 Intraweb'Multitier1.7; WebBrokerf‘7 WebServices7'7 WebSnap7 ■ XML7. Other Files i Web DocumentsItem Categories:': i-- 7 । C++Builder Projects' О ActiveX7'' C++Builder Filesi7" Inheritable Items7 Intraweb'Multitier1.7; WebBrokerf‘7 WebServices7'7 WebSnap7 ■ XML7. Other Files i Web DocumentsThread ObjectThread ObjectReconcile Error DialogReconcile Error Dialog
Item Categories:
': i-- 7 । C++Builder Projects
' О ActiveX
7'' C++Builder Files
i7" Inheritable Items
7 Intraweb
'Multitier
1.7; WebBroker
f‘7 WebServices
7'7 WebSnap
7 ■ XML
7. Other Files i Web Documents
Item Categories:
': i-- 7 । C++Builder Projects
' О ActiveX
7'' C++Builder Files
i7" Inheritable Items
7 Intraweb
'Multitier
1.7; WebBroker
f‘7 WebServices
7'7 WebSnap
7 ■ XML
7. Other Files i Web Documents
Thread Object
Thread Object
Reconcile Error Dialog
Reconcile Error Dialog
Puc. 24.1. Окно добавления новой формы
В окне Проектировщика каждый объект (визуальный элемент управления, невизуальный компонент, сама форма) настраивают с помощью Инспектора объектов. Одно из основных свойств объекта — это свойство Name. Оно задает наименование (идентификатор) этого объекта в программном коде. Например, если ввести в свойство Name для формы строку MyForm, в исходный текст автоматически вносятся изменения, касающиеся реализации методов формы и описания соответствующей переменной (ее имя совпадает с именем класса без первой буквы т):
TMyForm *MyForm;
В существовавшем до переименования формы описании конструктора:
fastcall TForml::TForml(TComponent* Owner)
: TForm(Owner)
(
}
имя метода и класса изменится автоматически:
fastcall MyForm::MyForm(TComponent* Owner)
: TForm(Owner)
{
}
По мере добавления на форму новых элементов управления ее описание в виде класса автоматически расширяется новыми членами. Например, разместим на форме приложения VCL кнопку (компонент TButton из группы Standard палитры инструментов), надпись (компонент TLabel) и два текстовых поля (компонент TEdit). Исходный текст класса формы (файл Unitl .h) станет таким:
class TMyForm : public TForm
(
published:Н IDE-managed Components
TLabel *Labell;
TEdit *Editl;
TEdit *Edit2;
TButton *Buttonl;
private:H User declarations
public:// User declarations
fastcall TMyForm(TComponent* Owner);
К надписи можно обращаться с помощью переменной Label 1, к текстовым полям — с помощью переменных Editl и Edit2 и так далее.
Тематические категории свойств
Свойства объектов в Инспекторе объектов разбиты на тематические категории. Для настройки представления свойств в Инспекторе объектов по категориям или в виде общего алфавитного списка служат команды контекстного меню: Arrange > by Category (Упорядочить > По категориям) или Arrange > by Name (Упорядочить > По имени).
Основные категории свойств приведены в таблице 24.1.
Таблица 24.1. Основные категории свойств, представленных в Инспекторе объектов
Категория
Описание
Action (Действие)
Доступность и видимость элемента, связь с подсказкой и справкой
Drag, Drop (Перетаскивание), Docking (Стыковка)
Поддержка режима перетаскивания
Help and Hints (Помощь и подсказки)
Настройка подсказки и помощи
Input (Ввод)
Настройка доступности объекта для модификации пользователем
Layout (Оформление)
Представление внешнего вида элемента на форме
Legacy (Устаревшие)
Поддержка устаревших свойств
Linkage (Связь)
Связь с вложенными и внешними компонентами, например с контекстным меню
Locale (Местоположение), Localizable (Локализуемость)
Настройки локализации
Miscellaneous (Разное)
Дополнительные настройки
Visual (Визуальное представление)
Настройки визуального представления
Свойства элементов управления, чаще всего используемые в программах, приведены в таблице 24.2.
Таблица 24.2. Часто используемые свойства элементов управления
Свойство
Описание
Align (Выравнивание)
Положение элемента по отношению к родительскому элементу (чаще всего к форме)
AutoSize (Автоматический подбор размера)
Если свойство имеет значение True, размеры элемента соответствуют его содержанию (например, длине визуализируемой текстовой строки)
Caption (Заголовок)
Текстовый заголовок элемента. Для формы это текст в строке заголовка окна, для надписи — текст надписи, для кнопки — текст, показанный на кнопке, и так далее
Color (Цвет)
Цвет элемента управления
Cursor (Курсор)
Форма указателя мыши при его наведении на элемент управления
Enabled (Включено)
Если свойство имеет значение True, объект доступен для взаимодействия. В противном случае он заблокирован
Text(Текст)
Текстовые данные в объекте. В компоненте TEdit свойство представляет редактируемую строку
Left (Слева), Тор (Сверху)
Относительные координаты левого верхнего угла элемента управления на форме
Width (Ширина), Height (Высота)
Ширина и высота элемента управления
Font (Шрифт)
Параметры шрифта, которым выполнены надписи на элементе управления
Readonly (Только чтение)
Если свойство имеет значение True, содержимое объекта доступно только для просмотра — его модификация запрещена
Visible (Видимый)
Если свойство имеет значение True, объект отображается на форме. В противном случае он полностью скрыт от пользователя
В дополнение к свойствам каждому элементу управления соответствует набор обрабатываемых им событий. Наиболее востребованные события приведены в таблице 24.3.
Таблица 24.3. Основные события для элементов управления
Событие
Описание-
OnClick
Реакция на щелчок мыши (для кнопок — на нажатие)
OnDbICIick
Реакция надвойной щелчок
OnKeyPress
Реакция на нажатие алфавитно-цифровой (не служебной) клавиши
OnMouseDown
Реакция на нажатие кнопки мыши
OnMouseMove
Реакция на перемещение мыши
OnChange
Реакция на изменение содержимого элемента управления
Вопросы
Для чего служит рабочий стол C++Builder?
Какие окна образуют рабочий стол C++Builder?
Какие типы приложений можно создавать с помощью среды C++Builder?
Как создаются заготовки приложений?
Как создаются обработчики пользовательских событий?
Каковы основные свойства компонентов C++Builder?
ГЛАВА 25
Работа в редакторе C++Builder
Для подготовки исходного текста программы в системе C++Builder используется специализированный редактор.
Редактор среды C++Builder
Встроенный в систему C++Builder редактор исходного текста программы на нескольких языках программирования (C++, Delphi, С#, веб-языки) по базовым возможностям не отличается от многих современных текстовых редакторов. Основные различия связаны с передовыми технологиями редактирования, которые позволяют:
ускорить процесс ввода текста;
повысить качество текста по критерию минимума синтаксических ошибок;
повысить уровень восприятия и выявления логических ошибок в исходных текстах благодаря визуальным подсказкам;
обеспечить комфортную работу с файлами проекта;
снизить нагрузку на зрение и объем ручного ввода.
Стандартные возможности редактора настраивают в диалоговом окне параметров среды, открываемом командой Tools > Options (Сервис > Параметры). Параметры редактора приведены в категории Editor Options (Параметры редактора).
Отметим, в частности, такие возможности, как отмена групповых операций редактирования Group undo (Групповой откат), отмена действий после сохранения файла Undo after save (Откат после сохранения), создание архивных копий редактируемого файла Create backup files (Создание резервных копий), которые хранятся в скрытом каталоге проектаhistory. Схему допустимых клавиатурных комбинаций выбирают в раскрывающемся списке Editor SpeedSetting (Быстрая настройка). Например, возможна эмуляция редактора среды Microsoft Visual Studio.
Особенности редактирования кода на том или ином языке (прежде всего, способы использования пробелов и символов табуляции) задают в категории Source Options (Параметры исходного текста). Цветовую схему для представления синтаксических элементов текста настраивают в категории Color (Цвет), подсказки в коде — в разделе Code Insight (Анализ кода) — рис. 25.1.
Работа с исходным текстом в C++Builder не ограничена только возможностями редактора. Так, средство просмотра структуры программы (Structure View) представляет иерархическую структуру кода в виде дерева. Узлами дерева явля-
OptionsOptions-; C++ OptionsPaths and DirectoriesType LibraryDelphi OptionsVCL Designer-; C++ OptionsPaths and DirectoriesType LibraryDelphi OptionsVCL Designer[Editor1 Options,[Editor1 Options,Source OptionsDisplay Key Mappings-1 Code InsightHTML/ASP.NET Options HTML Formatting- HTML Tidy Options New TagsOptimizedWebSnapCORBA[~i Debugger OptionsEvent LogBorland DebuggersSource OptionsDisplay Key Mappings-1 Code InsightHTML/ASP.NET Options HTML Formatting- HTML Tidy Options New TagsOptimizedWebSnapCORBA[~i Debugger OptionsEvent LogBorland Debuggers- Editor options - • - • -- •. tv jnsert modetv Group mdoГ" Cursor beyond EOFГ £oubte dick line. Г" Force cut and copy enabled■ v Auto-complete text to Find’ w Create backup Fites- Editor options - • - • -- •. tv jnsert modetv Group mdoГ" Cursor beyond EOFГ £oubte dick line. Г" Force cut and copy enabled■ v Auto-complete text to Find’ w Create backup Fitesiv Undo after saver" BRIEF regular expressionsГ" Persistent Mocks p Overwrite blocks p Find text at cursor |v preserve line endsiv Undo after saver" BRIEF regular expressionsГ" Persistent Mocks p Overwrite blocks p Find text at cursor |v preserve line endsEditor Speedsetting:Editor Speedsetting:[DeFault keymapping[DeFault keymappingUndo Smit:Undo Smit:£2767£2767File backup limit:File backup limit:1010Help jHelp jPuc. 25.1. Окно настройки редактора
Options
Options
-; C++ Options
Paths and Directories
Type Library
Delphi Options
VCL Designer
-; C++ Options
Paths and Directories
Type Library
Delphi Options
VCL Designer
[Editor1 Options,
[Editor1 Options,
Source Options
Display Key Mappings
-1 Code Insight
HTML/ASP.NET Options HTML Formatting
- HTML Tidy Options New Tags
Optimized
WebSnap
CORBA
[~i Debugger Options
Event Log
Borland Debuggers
Source Options
Display Key Mappings
-1 Code Insight
HTML/ASP.NET Options HTML Formatting
- HTML Tidy Options New Tags
Optimized
WebSnap
CORBA
[~i Debugger Options
Event Log
Borland Debuggers
- Editor options - • - • -- •
. tv jnsert mode
tv Group mdo
Г" Cursor beyond EOF
Г £oubte dick line
. Г" Force cut and copy enabled
■ v Auto-complete text to Find
’ w Create backup Fites
- Editor options - • - • -- •
. tv jnsert mode
tv Group mdo
Г" Cursor beyond EOF
Г £oubte dick line
. Г" Force cut and copy enabled
■ v Auto-complete text to Find
’ w Create backup Fites
iv Undo after save
r" BRIEF regular expressions
Г" Persistent Mocks p Overwrite blocks p Find text at cursor |v preserve line ends
iv Undo after save
r" BRIEF regular expressions
Г" Persistent Mocks p Overwrite blocks p Find text at cursor |v preserve line ends
Editor Speedsetting:
Editor Speedsetting:
[DeFault keymapping
[DeFault keymapping
Undo Smit:
Undo Smit:
£2767
£2767
File backup limit:
File backup limit:
10
10
Help j
Help j
ются классы с полями и методами, что позволяет переходить к их определениям и реализациям двойным щелчком на соответствующем узле (рис. 25.2).
Редактор позволяет быстро отыскивать описания элементов программы в исходном тексте и перемещаться между ними. Если подвести курсор к элементу текста программы при нажатой клавише CTRL, появится контекстная всплывающая подсказка. Если щелкнуть на ней, откроется файл, содержащий соответствующую декларацию, или же произойдет прокрутка текущего файла.
Любую часть кода можно превратить в комментарий. Для этого ее надо выделить и нажать комбинацию клавиш CTRL+/. В начало каждой выделенной строки добавятся символы комментария / / .
Рис. 25.2. Просмотрщик структуры программыРис. 25.2. Просмотрщик структуры программыВизуализация кода
Рис. 25.2. Просмотрщик структуры программы
Рис. 25.2. Просмотрщик структуры программы
Если фрагменты кода свернуты, ориентироваться в файле помогает линейка, отображаемая в окне редактора. Она содержит номера строк программы с шагом 10, выдается также номер текущей строки, содержащей курсор. Строки, модифици-
рованные в рамках текущей сессии, помечены зеленым, а те, что были изменены после последнего сохранения файла, — желтым.
Анализ кода
Среда C++Builder автоматически анализирует код программы и может отображать полученную информацию в виде подсказок, представленных во всплывающем окне.
Подсказка в коде — это всплывающее окно, которое содержит справочную информацию о текущем идентификаторе и помогает ускоренно завершить ввод.
Набор отображаемых подсказок выбирают в окне настройки среды*. Для этого предназначена панель Automatic Features (Автоматизация) в категории Editor Options > Code Insight (Параметры редактора > Анализ кода). Доступные настройки приведены в таблице 25.1.
Options@С|
Windows Forms Designer i||[ r- C++OptionsPaths and DirectoriesType LibraryDelphi OptionsVCL Designer + Editor OptionsSource OptionsDisplay Key MappingsWindows Forms Designer i||[ r- C++OptionsPaths and DirectoriesType LibraryDelphi OptionsVCL Designer + Editor OptionsSource OptionsDisplay Key Mappings[Code Insight[Code Insight.-: HTML/ASP.NET Options HTML Formatting~; HTML Tidy Options New TagsOptimizeltWebSnap CORBA- Debugger OptionsEvent Log.-: HTML/ASP.NET Options HTML Formatting~; HTML Tidy Options New TagsOptimizeltWebSnap CORBA- Debugger OptionsEvent LogSource fib type: | C/C++ i • Automatic features ". 17 £ode completion : 17 Code parameters | P Too tip expression evaluation{ 17 Tooltip Symbol Insighti |7 Block completion' j Without new Hne ^| ! J7 Cgde completion auto parenthesis I 17 Code Template Completioni7 Auto Complete Templatesi|7 Template HintsSource fib type: | C/C++ i • Automatic features ". 17 £ode completion : 17 Code parameters | P Too tip expression evaluation{ 17 Tooltip Symbol Insighti |7 Block completion' j Without new Hne ^| ! J7 Cgde completion auto parenthesis I 17 Code Template Completioni7 Auto Complete Templatesi|7 Template Hints
Windows Forms Designer i||[ r- C++Options
Paths and Directories
Type Library
Delphi Options
VCL Designer + Editor Options
Source Options
Display Key Mappings
Windows Forms Designer i||[ r- C++Options
Paths and Directories
Type Library
Delphi Options
VCL Designer + Editor Options
Source Options
Display Key Mappings
[Code Insight
[Code Insight
.-: HTML/ASP.NET Options HTML Formatting
~; HTML Tidy Options New Tags
Optimizelt
WebSnap CORBA
- Debugger OptionsEvent Log
.-: HTML/ASP.NET Options HTML Formatting
~; HTML Tidy Options New Tags
Optimizelt
WebSnap CORBA
- Debugger OptionsEvent Log
Source fib type: | C/C++ i • Automatic features "
. 17 £ode completion : 17 Code parameters | P Too tip expression evaluation
{ 17 Tooltip Symbol Insight
i |7 Block completion
' j Without new Hne ^| ! J7 Cgde completion auto parenthesis I 17 Code Template Completion
i7 Auto Complete Templates
i|7 Template Hints
Source fib type: | C/C++ i • Automatic features "
. 17 £ode completion : 17 Code parameters | P Too tip expression evaluation
{ 17 Tooltip Symbol Insight
i |7 Block completion
' j Without new Hne ^| ! J7 Cgde completion auto parenthesis I 17 Code Template Completion
i7 Auto Complete Templates
i|7 Template Hints
Puc. 25.3. Окно настройки всплывающих подсказок
Задержка в секундах перед появлением всплывающего окна с подсказкой определяется параметром Delay (Задержка). Цвета элементов подсказки задают в категории Colors (Цвета).
Разным языкам программирования (например, C+ + или Delphi) соответствуют разные типы подсказок. Нужный язык выбирают в раскрывающемся списке Source file type (Тип исходного файла).
Таблица 25.1. Инструменты анализа кода
fИнструмент
Описание
Code completion (Завершение кода)
Выдача списка имен свойств, типов, методов или событий после имени объекта или класса с точкой
Code parameters (Параметры кода)
Выдача списка параметров процедуры или метода после имени подпрограммы с открывающей скобкой
Tooltip expression evaluation (Вычисление выражения)
Выдача текущего значения переменной или выражения в процессе отладки при наведении указателя
Tooltip Symbol Insight (Анализ идентификатора)
Выдача информации об идентификаторе при наведении указателя
Block completion (Завершение блока)
Автоматическое размещение закрывающей фигурной скобки текущего блока
Code completion auto parenthesis (Закрытие скобок)
Автоматическое закрытие скобок по завершении формирования списка параметров функции
Code Template Completion (Завершение шаблона кода)
Автоматическое добавление шаблона кода при вводе комбинации символов, соответствующих некоторому шаблону и нажатию клавиши ТАВ
Шаблоны кода
В оболочку C++Builder включен Менеджер шаблонов кода, предназначенный для ускорения ввода типовых конструкций языка.
Для запуска этого средства служит команда View > Templates (Вид > Шаблоны), доступная при открытом окне редактора.
Менеджер шаблонов кода — это окно с шаблонами типовых конструкций и операторов языка C++. Эти конструкции вводят в текущую позицию курсора щелчком мыши (рис. 25.4).
Шаблон, вставленный в исходный текст, подсвечивается, а его ключевые элементы выделяются рамкой. Для перемещения по этим элементам служит клавиша ТАВ. Комбинация SHIFT+TAB обеспечивает перемещение в обратном порядке.
Например, выберем шаблон оператора цикла (рис. 25.5).
Введем выражение инициализации переменной-счетчика i, затем, нажав клавишу ТАВ, автоматически перейдем к редактированию логического условия цикла, а потом, снова с помощью клавиши ТАВ, — к редактированию выражения приращения счетчика. Наконец, нажав клавишу ТАВ еще раз, попадем в строку, следующую за заголовком цикла, — в место ввода тела цикла. Подсветка сбрасывается, и далее редактирование продолжается в обычном режиме.
Менеджер шаблонов функционирует и в автоматическом режиме. Когда разработчик вводит в редакторе текст, соответствующий ключевому элементу шаблона, Менеджер автоматически вставляет в текущее место код шаблона целиком. Если,
Q> New QaRemove ^ Edit [^Execute ^Д^^^
Name -; Description_
- C/C++C/C++
/*multi-line comment
classclass declaration (all parts)
classcclass declaration (with constructor/desctructor)
classdclass declaration (no parts)
forfor statement
forsfor (no opening/closing braces)
Qif statement
ifeif (no opening/closing braces) else (no opening/clo
ifebif else
ifsif (no opening/closing braces)
structstructure declaration
switchswitch statement (with default)
templatetemplate class declaration
trytry catch
try ■{'$selected$$*$$end$ i:} catch ($except$) •{ - } try ■{'$selected$$*$$end$ i:} catch ($except$) •{ - } whilewhile (no opening/closing br<
try ■{'
$selected$$*$$end$ i:
} catch ($except$) •{ - }
try ■{'
$selected$$*$$end$ i:
} catch ($except$) •{ - }
whilebwhile statement
7^ Object Inspector | Templates [
Puc. 25.4. Менеджер шаблонов кода
Рис. 25.5. Использование шаблона кодаРис. 25.5. Использование шаблона кода
Рис. 25.5. Использование шаблона кода
Рис. 25.5. Использование шаблона кода
например, ввести ключевое слово for и нажать клавишу ПРОБЕЛ, мы увидим в тексте программы соответствующий шаблон3.
Набор шаблонов легко расширить собственноручно. Для этого надо выделить в исходном тексте блок, который будет служить шаблоном. Перетащите выделенный фрагмент на палитру инструментов при нажатой клавише ALT. Новый «кусочек» кода добавится в конец раздела и получит имя по первым символам. Впоследствии его можно удалить через контекстное меню.
Менеджер шаблонов позволяет выполнить тонкую настройку шаблонов. Если выбрать строку Менеджера шаблонов, в окне редактора появится детальное описание выбранного шаблона в формате XML. В дополнительном окне отображается краткое описание шаблона. В формате XML можно запрограммировать очень сложные шаблоны.
Рис. 25.6. Окно редактирования шаблона кода
Система C++Builder позволяет преобразовывать фрагменты коды в элементы внешних операторов. Например, имеется возможность быстрого создания составного оператора, включения операторов в блок t гу и так далее. Для такого преобразования следует выделить последовательность команд в окне редактора и выбрать в контекстном меню пункт Surround (Заключить). Конкретный оператор языка C++ выбирают во вложенном меню.
Автоматическое завершение описаний
В среде C++Builder используется технология автоматического завершения описаний составных операторов и классов. В частности, отслеживается парность логи-
В некоторых случаях Менеджер шаблонов автоматически добавляет описание переменной-счетчика в качестве локальной переменной текущего метода.
ческих скобок { и }. Если в корректной позиции исходного кода ввести скобку { и нажать клавишу ENTER, система автоматически добавит завершающую скобку } (при условии, что она действительно нужна, а не расположена ниже по тексту).
Средства свертывания кода
Части кода можно сворачивать и разворачивать, акцентируя тем самым внимание на наиболее важных на данный момент фрагментах. Части исходного текста сворачивают щелчками на значках + и - на линейке с номерами строк (рис. 25.7).
Рис. 25.7. Свертываем и развертываем части кода
Свернутый код представлен одной строкой, в конце которой имеется пометка, например такая, как на рисунке 25.7 (три точки в прямоугольнике).
Организация закладок
Для быстрого перехода к важным фрагментам текста их помечают закладками.
Закладка — это помеченная строка исходного текста, к которой возможен быстрый переход в окне редактора.
Среда C++Builder позволяет установить до 10 закладок в каждом окне редактора. Закладки помечаются цифрами от 0 до 9. Для установки закладки нажмите комбинацию клавиш CTRL+K и, не отпуская эти клавиши, нажмите клавишу с нужной цифрой. В дальнейшем для перехода к соответствующей закладке служат комбинации клавиш от CTRL+0 до CTRL+9.
В окне редактора закладка представлена значком на линейке. При закрытии проекта закладки сохраняются и впоследствии автоматически восстанавливаются. Для удаления закладок служит команда Clear Bookmarks (Очистить закладки) в контекстном меню.
Синхронное редактирование
Режим синхронного редактирования позволяет править одинаковые части кода одновременно. Для этого надо выделить редактируемый фрагмент текста и нажать кнопку с изображением двух карандашей. В результате выделяются все одинаковые идентификаторы в выбранном фрагменте (рис. 25.8).
Рис. 25.8. Выделяем нужный фрагмент текста и нажимаем кнопку на левой панели редактора
Рис. 25.9. Одновременно правим все вхождения идентификатора TMyForm
Курсор устанавливается на первый из выделенных идентификаторов. При редактировании одновременно модифицируются все вхождения данного идентификатора в выделенный блок. Для выхода из режима синхронного редактирования надо нажать клавишу ESC.
Ведение списка задач
Среда C++Builder позволяет вести список задач «Что надо сделать» (ToDo List). С его помощью программист расставляет по всему коду своеобразные проектные подсказки — сообщения, напоминающие о работах, которые необходимо выполнить в незаконченных модулях кода.
Проектные подсказки удобнее всего создавать в окне редактора. Для этого надо поместить курсор в строку кода, к которой необходимо добавить напоминание. Напоминание добавляется в исходный текст в виде комментария в особом формате. Выберите в контекстном меню пункт Add To-Do List Item (Добавить напоминание) — откроется диалоговое окно, представленное на рисунке 25.10.
Рис. 25.10. Окно добавления нового пункта напоминания
Текст напоминания вводят в поле Text (Текст), важность работы (от 0 до 5) задают в поле Priority (Приоритет). В поле Owner (Владелец) необходимо указать автора напоминания. Его имя вводится в произвольной форме или выбирается из уже существующих. Поле Category (Категория) определяет тематическую группу, к которой относится работа. Оно заполняется таким же способом, как поле Owner (Владелец).
После нажатия кнопки ОК в исходный текст добавится следующая строка:
/* TODO 5 -оВася -сШифрование : Доделать рекурсивный цикл */
Модифицировать ее вручную не следует, иначе среда C++Builder не сможет распознать такой комментарий как напоминание.
Когда деятельность, связанная с определенным напоминанием, завершена, соответствующий пункт списка напоминаний помечают как выполненный. Для этого открывают список напоминаний командой View > To-Do List (Вид > Список напоминаний) и в нужной строке устанавливают флажок. Строка с текстом напоминания отображается зачеркнутой.
Окно списка напоминаний содержит средства поиска, сортировки и отбора. Для сортировки надо щелкнуть на заголовке соответствующего столбца. Для
отбора следует выбрать в контекстном меню команду Filter (Фильтр списка напоминаний). В диалоговом окне задают критерии отбора (например, по категории или по автору напоминания) — рис. 25.11.
To-Do List - ProjectlTo-Do List - ProjectlAction Item□ |§| Доделать рекурсивную функцию0|ESSSSSSSSS□ 3 Здесь нужен подробный комментарий!Action Item□ |§| Доделать рекурсивную функцию0|ESSSSSSSSS□ 3 Здесь нужен подробный комментарий!J • ^Module ^J • ^Module ^] Owner j Category] Owner j Category55aa22D:\TE.„\Unitl.cppD:\TE.„\Unitl.cppD:\TE...\Unitl.cppD:\TE...\Unitl.cppШифрованиеШифрованиеТехнические писателиТехнические писателиГГ
To-Do List - Projectl
To-Do List - Projectl
Action Item
□ |§| Доделать рекурсивную функцию
0|ESSSSSSSSS
□ 3 Здесь нужен подробный комментарий!
Action Item
□ |§| Доделать рекурсивную функцию
0|ESSSSSSSSS
□ 3 Здесь нужен подробный комментарий!
J • ^Module ^
J • ^Module ^
] Owner j Category
] Owner j Category
5
5
a
a
2
2
D:\TE.„\Unitl.cpp
D:\TE.„\Unitl.cpp
D:\TE...\Unitl.cpp
D:\TE...\Unitl.cpp
Шифрование
Шифрование
Технические писатели
Технические писатели
Г
Г
3 items (0 hidden)3 items (0 hidden)2 items pending2 items pending
3 items (0 hidden)
3 items (0 hidden)
2 items pending
2 items pending
Рис. 25.11. Окно ведения списка напоминаний
Любое напоминание можно удалить — выберите его в списке и дайте с помощью контекстного меню команду Delete (Удалить). Однако удалять строки списка напоминаний не рекомендуется — полезнее полностью сохранить историю сделанной работы.
Вопросы
Зачем нужен специализированный редактор C++Builder?
Как изменить раскладку управляющих клавиш?
Как изменить настройки подсветки синтаксиса?
Как работает средство просмотра структуры?
Какую информацию выдают автоматические подсказки в коде?
В чем польза закладок?
Как работают шаблоны кода?
Как одновременно исправить несколько одинаковых частей кода?
Как организуется ведение списка задач?
ГЛАВА 26 Технологии компиляции
Создание приложения начинается с проектирования формы в Проектировщике и подготовки исходного текста программы в редакторе. Далее проект должен пройти фазу компиляции. Эту задачу берет на себя компилятор.
Компиляция — перевод записанных в текстовом виде команд и операторов языка C++, а также всех вспомогательных ресурсов проекта в машинные команды и машинный формат данных.
Компилятор — программное средство, выполняющее компиляцию исходного текста.
Пока работа над программой не завершена, разработчик совершенствует ее, активно привлекая встроенный в среду C++Builder пошаговый отладчик.
Пошаговый отладчик — программное средство, позволяющее выполнять команды программы пошагово с возможностью проверки текущего состояния переменных, полей классов, объектов.
На практике этапы редактирования, компиляции и отладки тесно совмещены. Программист создает небольшой логически законченный блок кода, вызывает компилятор, получает работоспособную программу или устраняет синтаксические ошибки. Далее он проверяет этот блок в пошаговом режиме и либо продолжает его совершенствование, если выявлены недочеты, либо переходит к следующей задаче. Производительность работы по такой схеме зависит от знания разработчиком возможностей компилятора и отладчика.
Компиляция проекта
С внешней стороны компиляция представляется единой операцией. На самом деле это группа тесно связанных между собой отдельных операций, которые, в случае необходимости, могут быть выполнены порознь.
Сборка проекта
В результате работы компилятор создает не готовый исполнимый файл .EXE, а набор промежуточных библиотек, отображающих содержимое компилируемого модуля в особое двоичное представление, определенное внутренними соглашениями C++. Такое представление называют объектным. Полученный объектный модуль совпадает по имени с модулем исходного текста, но вместо расширения
имени .СРР использует расширение .OBJ. Возможности объектного модуля описаны в его внешнем интерфейсе — перечне функций, снабженных ключевым словом extern.
Объектный модуль можно использовать в любых проектах C++Builder как готовую библиотеку. Из исходных текстов библиотеки необходимо только заголовочное описание функций модуля, оформленных как внешние.
В создании конечного приложения EXE принимает участие специальный компонент среды C++Builder, который называется сборщик. Процесс сборки приложения выполняется в любом прикладном проекте, где задействовано более одного модуля. Как правило, даже небольшая проектная заготовка нуждается в сборке, так как к ней практически всегда подключаются различные стандартные библиотеки.
Сборка приложения — это объединение всех предварительно скомпилированных (объектных) модулей в одно целое и формирование готовой исполнимой программы.
Формирование исполнимой программы
Формирование исполнимой программы происходит в следующем порядке.
Программист дает команду на запуск программы или компиляцию проекта. Запуск программы осуществляется командой Run > Run (Запуск > Выполнить) или клавишей F9. Команда запуска обязательно подразумевает предварительную компиляцию проекта. Отдельная компиляция проекта выполняется командой Project > Compile project (Проект > Компилировать проект) или комбинацией клавиш CTRL+F9.
Среда C++Builder вызывает компилятор, который транслирует исходный текст программы в промежуточный (объектный) код, проверяя отсутствие серьезных ошибок.
Если ошибки найдены, дальнейшее построение приложения прекращается. Программист должен исправить ошибки и заново сформировать приложение.
Процесс компиляции может быть визуализирован. Информация о ходе компиляции отображается в отдельном окне C++Builder, если установлен флажок Show compiler progress (Показать ход компиляции) в окне настроек среды: Tools > Options > Environment Options (Сервис > Параметры > Параметры среды).
Физически компилятор представляет собой программу командной строки1.
Это файл cpp32.exe, расположенный в папке BIN каталога установки. Список параметров автоматически вызываемого компилятора можно узнать,
-, C++ command line for “Unitl .cpp"-, C++ command line for “Unitl .cpp"Messages'В
-, C++ command line for “Unitl .cpp"
-, C++ command line for “Unitl .cpp"
-D.DEBUG -DJiTllXUUSEPACKASES;NC>^TRICT -H -Н-у^рндатГМЬ»^^
-ID: \TEMP\borland\cpp\ 1 \cpp\ 1 ;"c: \program file5\borland\bds\4.0\include"; "c: \program f iles\borl< files\borland\bds\4. O\include\vcl";“c: \program files\borland\bds\4 .O\Lib\Indy 10" -oD: \TEMP\borlai
”I^Bufld |~
Puc. 26.1. Окно параметров компилятора
включив флажок Show command line (Показать командную строку) в окне настроек среды: Tools > Options > Environment Options (Сервис > Параметры > Параметры среды). В процессе компиляции эти параметры отображаются в окне Messages (Сообщения).
Среда C++Builder вызывает сборщик и формирует готовое приложение EXE (или иное в соответствии с типом проекта, например библиотеку DLL). Сборка проекта выполняется либо в режиме быстрой компиляции (по умолчанию), либо в режиме полной перестройки проекта. В первом случае компилятор не выполняет перекомпиляцию модулей, исходные тексты которых не менялись с момента последней сборки итоговой программы. Он обновляет только модифицированные модули, а сборщик формирует готовый файл EXE как из новых, так и из старых модулей. Данный режим используется по умолчанию при нажатии клавиши F9 или при выдаче команды Project > Compile project (Проект > Компилировать проект).
В некоторых, довольно редких, случаях компилятор не может определить, какие модули нуждаются в перекомпиляции, а какие нет. Так случается, если проект составлен из множества модулей со сложно организованными внутренними взаимосвязями друг с другом. В такой ситуации сборщик может задействовать объектные файлы, содержащие старый код и не отражающие модификации, внесенные в исходные тексты. Поэтому периодически рекомендуется выполнять полную перестройку проекта — принудительную перекомпиляцию всех модулей проекта независимо от времени их модификации. Полную перестройку выполняют командой Project > Build project (Проект > Перестроить проект).
Созданное приложение запускается на выполнение непосредственно из оболочки C++Builder. Сама оболочка при запуске скомпилированной программы автоматически сворачивается или скрывает вспомогательные окна Проектировщика, Инспектора объектов и другие. Режим оболочки при запуске программы определяется состоянием флажков Minimize on run (Сворачивать при запуске) и Hide designers on run (Скрывать средства разработки при запуске) в окне настройке свойств оболочки. Его открывают командой Tools > Environment Options (Сервис > Параметры среды).
Сообщения об ошибках
№■№■Сообщения об ошибках, найденных в исходном коде, выводятся в окне Messages (Сообщения). Для перехода к строке исходного текста, содержащей недочет, служит двойной щелчок на строке с сообщением об ошибке (рис. 26.2).
№■
№■
* Projectl - Borland Developer Studio 2006 - Unitl .cpp
Fite Edit Search View Refactor Project Run Component Tools StarTeam Window Help ;jam»w
6 ef a
^ Object Inspector ? X^ Object Inspector ? X^ Welcome Page ^ Unit 1 .cpp^ Welcome Page ^ Unit 1 .cpp£q Project!.bdsproj-Pro,.. ? X£q Project!.bdsproj-Pro,.. ? X
^ Object Inspector ? X
^ Object Inspector ? X
^ Welcome Page ^ Unit 1 .cpp
^ Welcome Page ^ Unit 1 .cpp
£q Project!.bdsproj-Pro,.. ? X
£q Project!.bdsproj-Pro,.. ? X
Button! ' |Properties] EventsEllActionButton! ' |Properties] EventsEllActionTForrnl *Forml;TForrnl *Forml;» Caption Button!Enabled trueHelpCont 0 iHmt.Visible trueE Drag, Drop and Dodo'DragCurscrDragDragKind^dkDrag;DragMod»dmManualEliHelp and HintsHelpCont; 0HelpKeyt* HelpTypehtContextParentSh hueiShowMmtfalseBiInputEnabled trueBiLayaut'Align ahone'AiignWithfalseS.Anchors [akleft,akiopB'Constrair(T5izeCon$trai'Height 25'loft an'All shown» Caption Button!Enabled trueHelpCont 0 iHmt.Visible trueE Drag, Drop and Dodo'DragCurscrDragDragKind^dkDrag;DragMod»dmManualEliHelp and HintsHelpCont; 0HelpKeyt* HelpTypehtContextParentSh hueiShowMmtfalseBiInputEnabled trueBiLayaut'Align ahone'AiignWithfalseS.Anchors [akleft,akiopB'Constrair(T5izeCon$trai'Height 25'loft an'All shownfastcaLl. TForinl: : TForinl (TCoinponent ’ Owner) : TForm(Owner)fastcaLl. TForinl: : TForinl (TCoinponent ’ Owner) : TForm(Owner)^Activate ▼ i^blew fi^ReFite;§§ ProjectGroupi■- r^] Projectl.ене,+ ■ ® Projectl .cppProjectl.res+ @ Unitl.cpp^Activate ▼ i^blew fi^ReFite;§§ ProjectGroupi■- r^] Projectl.ене,+ ■ ® Projectl .cppProjectl.res+ @ Unitl.cppivoid fastcaLl TForinl: :ButtonlClick (TOb ject ’Senderivoid fastcaLl TForinl: :ButtonlClick (TOb ject ’SenderN.N." ~1 £aProject 1..., ] ^Data Expl..." ~1 £aProject 1..., ] ^Data Expl...H^ Tool PaletteH^ Tool Palette¥ X¥ X24: 12 Insert24: 12 InsertModifiedModifiedUnitl.cpp Unit!.h Design Hi{|JgUnitl.cpp Unit!.h Design Hi{|JgQ MessagesQ MessagesXX+ C++ command line for "Unitl.cpp"[C++ Error] Unitl .cpp(24): E2188 Expression syntax[C++ Warning] Umtl.cpp(25): W6004 'X1 is assigned a value that is never usedКм |+ C++ command line for "Unitl.cpp"[C++ Error] Unitl .cpp(24): E2188 Expression syntax[C++ Warning] Umtl.cpp(25): W6004 'X1 is assigned a value that is never usedКм |+ Other Hies* C++Builder Projects+ C++Builder Projects | C, ♦ Web Documents* C++Builder Projects | W- + C++Builder Projects | W... + C++Builder Projects | W™ + C++Builder Projects | In™ + C++Builder Projects | Ac™ - C++Builder Projects | XML У XML Data Binding.- r++a»iMwPmMH+« I M_+ Other Hies* C++Builder Projects+ C++Builder Projects | C, ♦ Web Documents* C++Builder Projects | W- + C++Builder Projects | W... + C++Builder Projects | W™ + C++Builder Projects | In™ + C++Builder Projects | Ac™ - C++Builder Projects | XML У XML Data Binding.- r++a»iMwPmMH+« I M_
Button! ' |Properties] Events
EllAction
Button! ' |Properties] Events
EllAction
TForrnl *Forml;
TForrnl *Forml;
» Caption Button!
Enabled true
HelpCont 0 iHmt
.Visible true
E Drag, Drop and Dodo
'DragCurscrDrag
DragKind^dkDrag
;DragMod»dmManual
EliHelp and Hints
HelpCont; 0
HelpKeyt* HelpTypehtContext
ParentSh hue
iShowMmtfalse
BiInput
Enabled true
BiLayaut
'Align ahone
'AiignWithfalse
S.Anchors [akleft,akiop
B'Constrair(T5izeCon$trai
'Height 25
'loft an'
All shown
» Caption Button!
Enabled true
HelpCont 0 iHmt
.Visible true
E Drag, Drop and Dodo
'DragCurscrDrag
DragKind^dkDrag
;DragMod»dmManual
EliHelp and Hints
HelpCont; 0
HelpKeyt* HelpTypehtContext
ParentSh hue
iShowMmtfalse
BiInput
Enabled true
BiLayaut
'Align ahone
'AiignWithfalse
S.Anchors [akleft,akiop
B'Constrair(T5izeCon$trai
'Height 25
'loft an'
All shown
fastcaLl. TForinl: : TForinl (TCoinponent ’ Owner) : TForm(Owner)
fastcaLl. TForinl: : TForinl (TCoinponent ’ Owner) : TForm(Owner)
^Activate ▼ i^blew fi^Re
Fite
;§§ ProjectGroupi
■- r^] Projectl.ене
,+ ■ ® Projectl .cpp
Projectl.res
+ @ Unitl.cpp
^Activate ▼ i^blew fi^Re
Fite
;§§ ProjectGroupi
■- r^] Projectl.ене
,+ ■ ® Projectl .cpp
Projectl.res
+ @ Unitl.cpp
ivoid fastcaLl TForinl: :ButtonlClick (TOb ject ’Sender
ivoid fastcaLl TForinl: :ButtonlClick (TOb ject ’Sender
N.
N.
" ~1 £aProject 1..., ] ^Data Expl...
" ~1 £aProject 1..., ] ^Data Expl...
H^ Tool Palette
H^ Tool Palette
¥ X
¥ X
24: 12 Insert
24: 12 Insert
Modified
Modified
Unitl.cpp Unit!.h Design Hi{|Jg
Unitl.cpp Unit!.h Design Hi{|Jg
Q Messages
Q Messages
X
X
+ C++ command line for "Unitl.cpp"
[C++ Error] Unitl .cpp(24): E2188 Expression syntax
[C++ Warning] Umtl.cpp(25): W6004 'X1 is assigned a value that is never used
Км |
+ C++ command line for "Unitl.cpp"
[C++ Error] Unitl .cpp(24): E2188 Expression syntax
[C++ Warning] Umtl.cpp(25): W6004 'X1 is assigned a value that is never used
Км |
+ Other Hies
* C++Builder Projects
+ C++Builder Projects | C, ♦ Web Documents
* C++Builder Projects | W- + C++Builder Projects | W... + C++Builder Projects | W™ + C++Builder Projects | In™ + C++Builder Projects | Ac™ - C++Builder Projects | XML У XML Data Binding
.- r++a»iMwPmMH+« I M_
+ Other Hies
* C++Builder Projects
+ C++Builder Projects | C, ♦ Web Documents
* C++Builder Projects | W- + C++Builder Projects | W... + C++Builder Projects | W™ + C++Builder Projects | In™ + C++Builder Projects | Ac™ - C++Builder Projects | XML У XML Data Binding
.- r++a»iMwPmMH+« I M_
Puc. 26.2. В исходном тексте обнаружена ошибка
Сообщения о недоработках образуют три группы: подсказки (hints), предупреждения (warnings) и ошибки (errors).
Подсказка обращает внимание на мелкий недочет в исходном коде.
Предупреждение — реакция на достаточно серьезную, явную недоработку в исходном тексте, которая, тем не менее, не препятствует компиляции и запуску программы.
Ошибка компиляции свидетельствует о серьезных недостатках исходного текста, однозначно не позволяющих создать работоспособное приложение.
Ошибки, которые выявляет компилятор, называют синтаксическими. Они связаны не с логикой работы программы, а только с неверным использованием языковых конструкций. Например, к этой категории относятся такие ошибки, как пропуск точки с запятой, использование неописанного идентификатора, неверное преобразование данных, вызов несуществующей функции и многие другие. Список отображаемых предупреждений настраивают в диалоговом окне параметров текущего проекта в разделе Warnings (Предупреждения). Открыть нужное окно позволяет команда Project > Options (Проект > Параметры).
Настройки компилятора
Язык C++ объемный и сложный, он включает множество возможностей, а среда C++Builder дополняет стандарт языка немалым числом специфических технологий. В результате разработчик получает мощный инструмент создания программ, однако эффективное его использование требует аккуратной настройки на конкретную задачу. Среда C++Builder включает большое число настроек компилятора и среды поддержки выполнения программ, комбинации которых сильно варьируются от проекта к проекту. Эти настройки задают в окне, вызываемом командой Project > Options (Проект > Настройки) — в разделе C++ Compiler (компилятор C++). Список его подразделов приведен в таблице 26.1.
Таблица 26.1. Разделы настроек компилятора
Название
Назначение
Paths and Defines
(Пути и определения)
Формирование списка каталогов, в которых расположены стандартные подключаемые файлы C++Builder, и списка глобальных макроопределений проекта
Output settings (Выходные определения)
Выбор каталога для хранения объектных файлов, создаваемых в ходе компиляции проекта
Warnings (Предупреждения)
Настройка перечня выдаваемых предупреждений
Optimization (Оптимизация)
Настройка компилятора на получение максимально быстрого или максимально компактного кода (режим оптимизации недопустим на этапе отладки программы)
Debugging (Отладка)
Базовые настройки для формирования отладочной версии приложения
Compiling (Компиляция)
Базовые настройки процесса компиляции
Instruction set (Набор инструкций)
Выбор списка машинных инструкций, поддерживаемых компилятором (в зависимости от целевого процессора)
Data alignment (Выравнивание данных)
Способ выравнивания данных программы в оперативной памяти
Calling convention
(Способ преобразования вызовов функций)
Выбор внутреннего способа вызова функций (в соответствии с одним из стандартных механизмов вызова)
Pre-compiled headers (Прекомпилированные заголовки)
Поддержка прекомпилированных заголовков
Floating point (Плавающая точка)
Поддержка операций с плавающей запятой
Language compliance
(Языковая совместимость)
Выбор одного из популярных расширений языка C++
C++ Compatibility (Совместимость версий C++)
Обеспечение совместимости между различными компиляторами и версиями C++
Exception handling
(Обработка исключений)
Настройка механизма обработки исключительных ситуаций
Output
(Выходные данные проекта)
Настройка результирующей информации, выдаваемой компилятором
Название
Назначение
Templates (Шаблоны)
Особенности реализации шаблонов
Virtual tables (Виртуальные таблицы)
Выбор схемы поддержки виртуальных таблиц
Source (Исходные тексты)
Настройки совместимости исходных текстов
Member Pointers (Указатели на члены классов)
Выбор реализации доступа к членам классов с помощью указателей
Register variables (Регистровые переменные)
Поддержка регистровых переменных
CodeGuard compile support (Поддержка системы CodeGuard)
Настройка системы CodeGuard, упрощающей отладку программы
Assembler options (Настройки ассемблера)
Использование встроенного ассемблера
Other options
(Дополнительные настройки)
Дополнительные настройки процесса компиляции
Command line (Командная строка)
Текущий перечень настроек для компилятора командной строки
Создание настроечных конфигураций
Настройки компилятора могут сильно разниться как от проекта к проекту, так и в рамках одного проекта. Например, будут отличаться настройки для сборки отладочной версии, подразумевающей пошаговое выполнение (это требует включения различной отладочной информации в объектные и исполнимые файлы) и для сборки результирующей версии (релиза), которая подразумевает компактный и эффективный код. Кроме того, результирующие версии также могут разниться — в зависимости от способа отладки, целевой платформы, версии проекта и ряда других вещей.
Среда C++Builder позволяет создавать неограниченное число настроечных конфигураций проекта с возможностью их быстрого переключения. Доступ к списку текущих настроек проекта происходит с помощью команды Project > Build Configurations (Проект > Конфигурации компиляции). Текущая конфигурация отмечена словом active (рис. 26.3).
I Output DkectoiyD:\TEMP\borland\cpp\1 \Debug_BuildD AT EM P\borland\cpp\1 \R elea$e_B uildI Output DkectoiyD:\TEMP\borland\cpp\1 \Debug_BuildD AT EM P\borland\cpp\1 \R elea$e_B uild^ Build Configurations
I Output Dkectoiy
D:\TEMP\borland\cpp\1 \Debug_Build
D AT EM P\borland\cpp\1 \R elea$e_B uild
I Output Dkectoiy
D:\TEMP\borland\cpp\1 \Debug_Build
D AT EM P\borland\cpp\1 \R elea$e_B uild
Debug Build (active]
Release Build
OK | Cancel | Help ।
Puc. 26.3. Окно настроечных конфигураций проекта
По умолчанию для каждого проекта создаются две конфигурации: Debug Build (Отладочная конфигурация) и Release Build (Результирующая конфигурация). При выборе той или иной конфигурации (двойным щелчком на соответствующем названии) автоматически меняются все настройки проекта. Для добавления новой конфигурации нажимают кнопку New.
Данное окно используется только для манипулирования списком названий конфигураций. Доступ к нужной конфигурации происходит непосредственно из окна настроек проекта. В его верхней части имеется раскрывающийся список Build configuration с названиями, а рядом с ним —- кнопка вызова окна настроечных конфигураций Configurations.
При смене настроек и нажатии на кнопку ОК текущий вариант настроек записывается в конфигурацию, выбранную в раскрывающемся списке (независимо от того, является ли она активной).
# Project Options for Project1.exe# Project Options for Project1.exe- C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations Debugging Compiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling Output Templates Virtual tables Source- C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations Debugging Compiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling Output Templates Virtual tables SourceBuild ConfiguratloniilJnWed (active)Build ConfiguratloniilJnWed (active)All ConfigurationsAll ConfigurationsД Configurations Д Find Option.. ДД Configurations Д Find Option.. Д' Language com6 UseANSI k Де'еа5е„‘ДИ,C Use Kernighan and Hitchte [K.HJ Keywords and extensions (-AK) • " Use Borland C++ keywords and extensions (also -A-) (-АТГ r Use UNIX System V keywords and extensions (-AU)' Language com6 UseANSI k Де'еа5е„‘ДИ,C Use Kernighan and Hitchte [K.HJ Keywords and extensions (-AK) • " Use Borland C++ keywords and extensions (also -A-) (-АТГ r Use UNIX System V keywords and extensions (-AU)Г" DefaultГ" DefaultCancelCancelHelpHelp
# Project Options for Project1.exe
# Project Options for Project1.exe
- C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations Debugging Compiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling Output Templates Virtual tables Source
- C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations Debugging Compiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling Output Templates Virtual tables Source
Build ConfiguratloniilJnWed (active)
Build ConfiguratloniilJnWed (active)
All Configurations
All Configurations
Д Configurations Д Find Option.. Д
Д Configurations Д Find Option.. Д
' Language com
6 UseANSI k Де'еа5е„‘ДИ,
C Use Kernighan and Hitchte [K.HJ Keywords and extensions (-AK) • " Use Borland C++ keywords and extensions (also -A-) (-АТГ r Use UNIX System V keywords and extensions (-AU)
' Language com
6 UseANSI k Де'еа5е„‘ДИ,
C Use Kernighan and Hitchte [K.HJ Keywords and extensions (-AK) • " Use Borland C++ keywords and extensions (also -A-) (-АТГ r Use UNIX System V keywords and extensions (-AU)
Г" Default
Г" Default
Cancel
Cancel
Help
Help
Puc. 26.4. Просмотр конфигураций компиляции
Настройки сборщика
Язык C++ часто используется в качестве интеграционной среды, объединяющей модули и библиотеки сторонних разработчиков. Результирующее приложение может формироваться из множества объектных и библиотечных файлов, поэтому процесс сборки сам по себе достаточно сложен и нуждается в дополнительном контроле.
Среда C++Builder предлагает программисту немало настроек процесса сборки — по аналогии с настройкой процесса компиляции. Они сосредоточены в разделе Linker (Сборщик) окна настроек проекта. Группы этого раздела приведены в таблице 26.2.
Таблица 26.2. Разделы настроек сборщика
Название
Назначение‘
Paths and Defines (Пути и определения)
Формирование списка каталогов, в которых расположены стандартные библиотеки C++Builder и дополнительные объектные файлы
Output settings (Выходные определения)
Выбор каталога для хранения объектных, библиотечных и исполнимых файлов, создаваемых в ходе компиляции проекта
Warnings (Предупреждения)
Настройка перечня выдаваемых предупреждений
Linking (Сборка)
Основные настройки процесса сборки и построения результирующего приложения
Map file (Файл раскладки)
Формирование файла раскладки, описывающего внутреннюю структуру результирующего исполнимого файла
РЕ file options (Настройка структуры РЕ-файла)
Детальные параметры внутренней структуры формируемого упакованного исполняемого файла
Advanced options (Дополнительные настройки)
Дополнительные настройки процесса сборки
Version (Версия)
Включение в файл номеров версий
Package options (Настройки пакета)
Настройка параметров формируемого пакета
Other options (Дополнительные настройки)
Дополнительные настройки процесса сборки
Command line (Командная строка)
Текущий перечень настроек для сборщика командной строки
Препроцессора обработка
Практически во всех крупных проектах применяются средства препроцессорно- го управления компиляцией исходных текстов. Они позволяют включать блоки и отдельные строки кода в транслируемые тексты или исключать их в зависимости от определенного условия. Препроцессор удобен, когда некоторые функциональные возможности желательно включать в одни версии продукта и нежелательно включать в другие4.
Например, при создании локализованных версий продукта не имеет смысла хранить в файле EXE все без исключения строковые ресурсы на всех поддерживаемых национальных языках. Правильнее добавить в приложение только текстовые константы для конкретной версии. В этом поможет препроцессор, который способен полностью исключить определенные части исходных текстов из компиляции.
Препроцессоры активно используют для адаптации программы к разным платформам. Если, например, выделить процедуры, зависящие от целевой платформы, в один модуль, можно компилировать тот или иной набор процедур в зависимости от платформы, выбранной разработчиком.
Препроцессор — программное средство предварительной компиляции, преобразующее исходный текст приложения перед передачей его основному компилятору.
Структура команд препроцессора
Препроцессорные команды записывают в следующем виде:
#директива параметр
Символ # в начале команды подсказывает препроцессору, что следующая директива подлежит обработке.
Определение препроцессорной константы
Для объявления препроцессорных констант применяется команда вида:
#define константа
Например:
#define COMPILE_FOR_RUSSTAN-VERSION
После ее выполнения препроцессор регистрирует константу с именем compile_ for_russian_version. В качестве имени можно использовать произвольный идентификатор, выбранный разработчиком. Зарегистрированная константа считается определенной. Определение константы отменяют командой:
#undef константа
Например:
#undef COMPILE_FOR_RUSSIAN_VERSION
Далее по тексту эта константа считается неопределенной.
Условная компиляция
Константы препроцессора позволяют управлять процессом компиляции. Групповая команда:
#ifdef константа
#endif
включает в процесс компиляции исходный текст на языке C++, который заключен между директивами ifdef и endif, если препроцессорная константа ранее определена командой define. В противном случае эта часть кода в компиляцию не включается (временно удаляется из исходного текста).
Например:
ttifdef COMPILE—FOR_RUSSIAN_VERSION
Labell->Caption = "Привет!";
#endif
# i fde f COMPILE-FOR-ENGLISH_VERSION
Labell->Caption = "Hello!";
#endif
Если до этих команд определена константа compile_for_russian_version, то компилятор C++ получит на вход только оператор:
Labell->Caption = "Привет!";
Если ранее определена константа compile_for_english_version, то компилятор C++ получит на входе только оператор
Labell->Caption = "Hello!";
Таким образом включают в процесс компиляции одни части исходного текста и исключают другие. При этом, конечно, не следует определять сразу обе используемые константы.
В условной директиве препроцессора if def можно использовать дополнительную команду else.
Она добавляет раздел, включаемый в компиляцию, если константа, указанная в директиве ifdef, не определена.
Например:
#ifdef COMPILE_FOR_RUSSIAN_VERSION
Labell->Caption = "Привет!";
#else
Labell->Caption = "Hello!";
#endif
Данный пример не слишком удачен, потому что он позволяет организовать поддержку лишь двух локализуемых языков: русского, если константа compile_ for_russian_version определена, и иного в противном случае. Если препроцес- сорных частей, которые должны включаться в исходный код, больше двух, для каждой из них следует определить отдельную константу.
Директивы условной компиляции можно вкладывать друг в друга:
#i f def COMPILE_FOR_RUSSIAN_VERSION
#ifdef CP1251
Labell->Caption = "Привет!";
#else
Labell->Caption = "ОПХБЕРЕ1";
#endif
#endif
В случае, если выстраивается длинная цепочка вложенных проверок:
#ifdef А
//
#else
#ifdef В
//
#else
#ifdef С
//
#else
//
#endi f
#endi f
#endif
ее можно сократить директивой el i f, объединяющей две последовательно следующие инструкции else и if и позволяющей избавиться от вложений и лишних завершающих директив endif:
#ifdef А
//
#elif В
//
#elif С
//
#else
//
#endif
Существует обобщенная версия препроцессорного условного оператора, записываемая следующим образом:
#if выражение
В рамках заданного выражения можно проверять препроцессорные константы и даже значения констант в исходном тексте программы на языке C++. Они связываются логическими операциями && (и), | | (или), а также могут использоваться с унарной операцией 1 (не). Кроме того, препроцессорная функция defined () получает в качестве своего аргумента препроцессорную константу и возвращает истинное значение, если эта константа определена.
Например:
#define MyTest
const MyConst = 5;
#if defined(MyTest) && (MyConst > 4)
// здесь размещается код, компилируемый, если
// препроцессорная константа MyTest определена,
// а значение константы MyConst больше четырех
#endif
Подстановка файла
Эта уже неоднократно применявшаяся для подключения заголовочных файлов директива подставляет в текущий модуль содержимое другого файла. Она записывается следующим образом:
#include имя-файла
Имя файла заключается в двойные кавычки, если оно располагается в каталоге проекта, или же в угловые скобки, если оно расположено в одном из системных каталогов C++Builder.
С помощью этой директивы в текст программы нередко включают файл, в свою очередь также содержащий настройки препроцессора. Они могут быть едиными для модулей проекта и задавать один и тот же набор констант препроцессора (например, настройку на конкретный язык локализации). Этот выделенный файл желательно подставить в начало каждого модуля проекта, в чем и поможет данная инструкция.
Сообщение об ошибке
Иногда бывает полезно прервать процесс компиляции и выдать пользовательское сообщение об ошибке. Для этого используется следующая директива препроцессора:
terror сообщение
Например, следующий код сформирует ошибку компиляции (Fatal error directive), если произойдет попытка сборки проекта с определенной константой compile_ FOR_ENGLISH_VERSION:
«define COMPILE_FOR_ENGLISH_VERSION
«ifdef COMPILE_FOR_RUSSIAN_VERSION
const char* STR_PRIVET = “Привет!";
ttendif
«ifdef COMPILE_FOR_ENGLISH_VERSION
«error He реализован английский вариант STR_PRIVET!
«endif
Директивы компиляции
Настройки компиляции обычно задают в окне настроек проекта, но некоторые из них можно установить и непосредственно в исходных текстах. Для этого служат текстовые директивы компиляции (или директивы компилятора), которые определяют конкретные настройки компиляции для фрагментов исходного текста.
Директивы компиляции записывают с помощью инструкции препроцессора pragma:
«pragma директива-компиляции
Инструкция pragma считается платформно-независимой — она поддерживается большинством компиляторов языка C++, поэтому в целях совместимости охватывает относительно небольшой перечень настроек компиляции. Основные элементы этого перечня приведены в таблице 26.3.
Таблица 26.3. Настройки компиляции для препроцессорной директивы pragma
Название
Значение
anon_struct on/off
Разрешить/запретить встраивание анонимных структур в классы
argsused
Блокировка предупреждения о неиспользованном аргументе в ближайшем определении функции
codeseg
Задание класса или сегмента памяти для размещения функций
comment
Запись комментария в выходной файл компиляции
startup/exit
Спецификация функций, которые будут автоматически вызываться при запуске и завершении программы
hdrfile
Определение имени файла для хранения прекомпилированных заголовков
hdrstop
Фиксация конца списка заголовочных файлов, для которых выполняется прекомпиляция
Таблица 26.3 (продолжение)
Название
Значение
inline
Все функции далее по тексту компилируются как inline
intrinsic
Все функции далее по тексту компилируются как обычно (отмена режима inline)
link
Подключение к программе внешнего объектного модуля
message
Встраивание сообщения препроцессора в исходный текст
pack
Способ выравнивания элементов в памяти
package
Настройка порядка инициализации пакетов
obsolete
Выделение идентификатора, который считается морально устаревшим в текущем проекте
resource
Подключение файлов с ресурсами
warn
Настройка перечня выдаваемых компилятором предупреждений
Примеры:
#pragma anon_struct on
frpragma argsused
void f(int x) {}
Отметим также директиву #pragma option, которая позволяет использовать настройки, специфичные для конкретного компилятора. Эти настройки обычно представляют собой параметры компилятора командной строки.
Например:
#pragma -к -01
Действие любой директивы #pragma обычно распространяется только на текущий файл (весь код до конца файла, следующий за директивой).
Макросы
С помощью препроцессорной инструкции define можно определять не только константы, но и выражения с параметрами, называемые макросы.
Макрос (или макро) — это препроцессорная инструкция, которая позволяет настраивать с помогцъю параметров выражение, подставляемое в исходный текст.
Макрос записывается следующим образом:
#define макро(список-параметров) подставляемый-текст
Параметры используются в подставляемом тексте.
Например, определим наглядные макросы для нахождения минимального из двух чисел и квадрата числа:
ttdefine minimum(x,y) х<у?х:у
#define SQR(x) х*х
С точки зрения программиста вызов minimum (а, Ь) ничем не отличается от вызова обычной функции. Однако реализуется макрос по-другому: в любой стро-
ке программы, где встречается вызов minimum (), соответствующий текст механически заменяется на исходно заданный и выполняется прямая подмена параметров. Анализ синтаксической или смысловой корректности препроцессор не выполняет. В этом таится опасность — при некорректном определении макроса могут возникнуть ошибки, понять которые бывает крайне трудно.
Пусть макрос SQR () используется в следующем операторе:
X = SQR(y-lO);
Препроцессор подставляет параметр макроса — строку у-10, в качестве аргумента х в выражение х*х, и в результате на вход компилятора подается такая команда:
х = у~10*у-10; // правильно так: (у-10)*(у-10)
Из-за того, что в макросе SQR () не учитывается возможность использования выражений в качестве параметров, результирующее выражение будет вычислено совсем по-другому, однако программист заметит это далеко не сразу. Чтобы избежать подобных накладок, все аргументы в макросе заключают в круглые скобки:
#define minimum(x,y) (х)<(у)?(х):(у)
#define SQR(x) (х)*(х)
Теперь результирующие выражения будут строиться корректно.
Макросы активно применяются в программах на языке Си, в котором отсутствуют выразительные объектные механизмы C++. Макросы хороши тем, что позволяют повысить эффективность выполнения критических точек программы за счет замены вызовов обычных функций непосредственно на исполнимый код. При использовании современных компиляторов это не актуально, так как разработчик может явно описывать функции как ini ine. К недостаткам макросов относят невозможность проверки типов аргументов (и, в общем случае, корректности синтаксиса формируемого препроцессором выражения) на этапе компиляции.
Вопросы
Какой командой выполняется сборка проекта?
Зачем нужна программа-сборщик?
В чем разница между компиляцией и перестройкой проекта?
Зачем нужен препроцессор?
Как определяется препроцессорная константа?
Как организуется условная компиляция?
Как включить содержимое файла в модуль проекта?
Каково назначение препроцессорной директивы pragma?
Как создаются макросы и в чем их недостатки?
ГЛАВА 27 Базовые технологии отладки
Процесс компиляции исходных текстов и исправления очевидных синтаксических ошибок несложен и не требует особых навыков. На основании аккуратно написанного кода программист быстро получит готовый исполнимый файл. Однако с ростом объема приложения, который может достигать сотен и тысяч операторов, увеличивается и количество ошибок. Большинство ошибок проявляется не на этапе компиляции, а в ходе работы программы. Одни ошибки вызывают явный сбой программы, другие не мешают ей корректно работать, но результаты функционирования оказываются не теми, которых ожидал разработчик. Это уже не синтаксические ошибки — правила записи исходных текстов формально соблюдены, и компилятор отработал нормально. Ошибки, связанные с несоблюдением заданной логики работы программы, называют логическими или семантическими.
Пусть, например, имеется следующая последовательность команд C++:
х = 0;
у = 5 / х ;
Ее выполнение приведет к ошибке времени выполнения в последнем операторе, связанной с попыткой деления на ноль. Работа программы аварийно завершится, однако компиляторы не способны выявить подобные непродуманности в исходном коде.
Ошибка могла быть и иной: например, вместо кода
у — 5 / х;
мог быть написан оператор:
у = х / 5;
Из-за естественной человеческой невнимательности подобные промахи случаются нередко, а программа в таком случае продолжит работу без видимых сбоев. Однако в переменную у записывается совсем не то значение, на которое рассчитывай! создатель программы. Вся дальнейшая последовательность расчетов также будет неверной, хотя программа и выдаст внешне допустимый результат.
Отладка — это процесс поиска в программе ошибок и их последующего устранения.
Процесс поиска ошибок в программе и их последующего устранения называют отладкой. Процесс этот не так прост, как может показаться, — уже более полувека лучшие кибернетики, математики и психологи безуспешно бьются над задачей организации безошибочного труда разработчиков и над технологиями создания правильных программ, не содержащих ошибки. Согласно статистике, в проектах по разработке программного обеспечения на фазу отладки приходится около 60% времени, причем чем крупнее проект, тем эта доля выше.
Отладочные настройки проекта
Основным средством выявления ошибок на этапе кодирования считается выполнение программы по шагам. В таком режиме программист заглядывает «внутрь» работающей программы, выполняя операторы исходного текста поочередно, проверяя значение переменных в промежутках между шагами и выясняя, почему программа выдает неверный результат и в каком конкретно операторе происходит ее аварийное завершение. Для организации пошагового режима в исполнимый файл программы должна быть записана отладочная информация, связывающая строки исходного текста с результирующим двоичным кодом.
Запись отладочной информации в исполнимый файл задают в диалоговом окне настройки проекта: Project > Options > C++ Compiler > Debugging (Проект > Настройки > Компилятор C++ > Отладка).
> Project Options for Project1.exe
'-. C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations'-. C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizationsguild Configuration:|Debug Build (active)guild Configuration:|Debug Build (active)■ Debugging■ DebuggingfvfvCompiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling OutputTemplates Virtual tablesSourceCompiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling OutputTemplates Virtual tablesSourceDebug line numbers (-y)Source debugging (-v)Inline function expansion [-vi]Generate CodeView4 compatible debug info (-v4)Debug line numbers (-y)Source debugging (-v)Inline function expansion [-vi]Generate CodeView4 compatible debug info (-v4)■■ . >■■ . >F DefaultF DefaultHelp jHelp j
'-. C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations
'-. C++ Compiler (bcc32) Paths and Defines Output settings Warnings Optimizations
guild Configuration:|Debug Build (active)
guild Configuration:|Debug Build (active)
■ Debugging
■ Debugging
fv
fv
Compiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling Output
Templates Virtual tables
Source
Compiling Instruction set Data alignment Calling convention Pre-compiled headers Floating point Language compliance C++ Compatibility Exception handling Output
Templates Virtual tables
Source
Debug line numbers (-y)
Source debugging (-v)
Inline function expansion [-vi]
Generate CodeView4 compatible debug info (-v4)
Debug line numbers (-y)
Source debugging (-v)
Inline function expansion [-vi]
Generate CodeView4 compatible debug info (-v4)
■■ . >
■■ . >
F Default
F Default
Help j
Help j
Puc. 27.1. Окно настройки отладочных параметров компилятора
Надо проверить, включены ли флажки Debug line numbers (Включать в отладочный вариант программы номера строк исходного текста) и Source debugging (Поддержка отладки на уровне исходных текстов). Кроме того, в разделе Optimization надо выбрать переключатель Disable all optimizations (Отключить всю оптимизацию), запрещающий работу компилятора в оптимизирующем режиме. Хотя в таком режиме полученный исполни-
мый код выполняется быстрее, во многих случаях не удается однозначно связать части отлаживаемого кода с операторами исходного текста2.
Функция Assert
Удобный метод контроля правильности промежуточных значений основан на вызове специального макроса assert (). У этого макроса один параметр — логическое выражение. Когда во время работы программы встречается вызов assert (), вычисляется логическое выражение. Если оно ложно, генерируется исключительная ситуация. Работа программы приостанавливается, и пользователю выдается текстовое сообщение об прерывании asset.
Например:
X = 0;
assert(х 1= 0);
у = 5 / х;
Если значение переменной х равно нулю (впоследствии это приведет к делению на ноль), макрос assert () прервет работу программы и выведет предупреждающее сообщение “Assertion failed: х!=0, file ххх, line nnn”. Здесь ххх — название файла с исходным текстом, где встретилась ошибка, a nnn — номер строки с ошибочным оператором в этом файле. Такая подробная информация нужна для того, чтобы получить точные сведения о сбойной ситуации, когда программа запускается не из среды C++Builder, а уже эксплуатируется в тестовом или промышленном режимах3.
В крупных проектах могут насчитываться сотни и тысячи вызовов макроса assert (). Оставлять их в коммерческой версии продукта незачем, но ручное удаление их из кода — весьма трудоемкий процесс. Однако среда C++Builder упрощает эту операцию.
Макрос assert () определен в заголовочном файле assert.fi. Он входит в состав заголовочного файла vcl.fi, автоматически включаемого в проект. Если перед этим файлом разместить определение константы ndebug, то макрокоманда assert () преобразуется препроцессором не в отладочный вызов, а в пустой оператор, не выполняющий никаких действий.
Откорректируем вышеприведенный пример, разместив определение константы ndebug в самом начале текущего файла, перед инструкцией включения заголовочного файла vcl.h:
#define NDEBUG
ttinclude <vcl.h>
Теперь в коде программ будут игнорироваться любые обращения к макрокоманде assert () независимо от значения логического выражения.
Как правило, корректные отладочные настройки по всем разделам задаются автоматически, если выбирается настроечная конфигурация среды для режима отладки Debug Build (см. главу 26).
Эффективное и грамотное использование макрокоманды assert() требует от разработчика определенного уровня профессионализма.
Пошаговое выполнение программ
При возникновении ошибки разработчик обычно примерно представляет, в каком модуле программы или методе класса происходит неверная работа. Однако обнаружить ошибку, просто рассматривая исходный текст, удается далеко не всегда*.
Удобно выполнять сомнительную часть программы по шагам — после каждого оператора работа приостанавливается, что позволяет контролировать значения переменных. Чтобы организовать режим выполнения кода по шагам, надо установить так называемую точку прерывания.
Рис. 27.2. Среда C++Builder врежиме пошагового выполнения
Точка прерывания — это помеченная команда исходного кода, по достижении которой работа программы приостанавливается и управление передается среде C++Builder.
■* Признанные специалисты по программной инженерии для профилактики ошибок настоятельно рекомендуют «метод пристального взгляда» — многократный просмотр исходного текста без его компиляции и выполнения.
После того как в ходе работы программа достигла точки прерывания, дальнейшее ее выполнение можно вести по шагам: по одной строке исходного кода за шаг. Операторы и команды, которые могут быть выполнены в пошаговом режиме, помечены синими круглыми маркерами на линейке. Эти маркеры появляются после успешной компиляции программы.
Установка точки прерывания
Познакомимся с точками прерывания на примере. Создадим пустую форму VCL и разместим на ней кнопку и надпись. Пусть в обработ чике нажатия на эту кнопку записан следующий код:
void fastcall TForml::ButtonlClick(TObjact ‘Sender)
{
int a[10] ;
int i ;
for( i = 1; i <=10; i ++ )
if ( i < 5 ) a[i] = 1;
elsea [ i ] = 0 ;
}
Данный код по какой-то причине некорректен: программист обнаружил, что в некоторых случаях после нажатия на кнопку программа завершается с ошибкой. Мы хотим выявить ее в отладчике, в пошаговом режиме. После компиляции программы операторы, допускающие установку точки прерывания, будут помечены маркерами.
Для добавления точки прерывания служат команда контекстного меню Debug > Toggle Breakpoint (Отладка > Установить точку прерывания) и клавиша F5. Строка с точкой прерывания выделяется синим цветом, а маркер становится красным.
Для отмены точки прерывания надо дать ту же самую команду еще раз. В нашем случае установим точку прерывания на заголовок оператора цикла for.
Запустим программу и нажмем кнопку на форме. Программа приостановит работу — управление будет передано среде C++Builder. В заголовке окна среды C++Builder выводится информационное сообщение Projectl - Unitl.cpp [Stopped] (Проект Projectl, модуль Unitl.cpp [Приостановлен]).
Достигнутая точка прерывания помечается зеленой галочкой, а оператор, с которого выполнение программы будет продолжено, — стрелкой. Пока что это строка, содержащая точку прерывания.
Продолжим выполнение программы по шагам. Для этого используется команда Run > Step Over (Запуск > Шаг с возвратом) или клавиша F8.
Если нажать клавишу F8, отладчик C++Builder выполнит операторы в помеченной строке и перейдет к следующей строке с исполнимым кодом C++. Строка, выполняемая на очередном шаге, выделена цветом (рис. 27.3).
В нашем случае управление дается на первый оператор тела цикла: условный оператор if, в котором проверяется условие i < 5.
Рис. 27.3. Точка прерывания и текущая выполняемая команда выделены цветомРис. 27.3. Точка прерывания и текущая выполняемая команда выделены цветомАнализ значений переменных
Рис. 27.3. Точка прерывания и текущая выполняемая команда выделены цветом
Рис. 27.3. Точка прерывания и текущая выполняемая команда выделены цветом
Если работа программы приостановлена, можно проверить текущие значения локальных и глобальных переменных. В частности, если навести указатель мыши на интересующую переменную, ее значение отображается во всплывающем окне (рис. 27.4).
Д Welcome Page ® Unitl.cpp
•fastcaUL TForml: : TFornil (TComponent* Owner)
**<<ij 1 i1ij 1 i10;0;26:26:Unitl.cpp .Unltl,h • Design History"7Unitl.cpp .Unltl,h • Design History"7,: TForm(Owner)
*
*
<
<
ij 1 i1
ij 1 i1
0;
0;
26:
26:
Unitl.cpp .Unltl,h • Design History"7
Unitl.cpp .Unltl,h • Design History"7
Puc. 27.4. Всплывакпцая подсказка текущего значения переменной
Переменные, значения которых необходимо контролировать на протяжении многих шагов, добавляют в так называемое окно слежения. Для этого надо уста
новить курсор на идентификатор переменной, подлежащей анализу, и с помощью
контекстного меню дать команду Debug > Add Watch at Cursor (Отладка > Добавить значе
ние из позиции курсора). В открывшемся окне Watch List (Список контролируемых значений)
Watch List®Watch List®| Watch Name ; Value . J| Watch Name ; Value . J- д ■- д ■в столбце Watch Name (Имя контролируемой переменной) отображается имя переменной, а в столбце Value (Значение) показано ее значение.
Watch List®
Watch List®
| Watch Name ; Value . J
| Watch Name ; Value . J
- д ■
- д ■
Для удаления переменной из списка контролируемых значений предназначена команда Delete Watch (Удалить контролируемое значение), доступная в контекстном меню.
Отладочная раскладка рабочего стола
После перехода в режим отладки в среде C++Builder используется специальная раскладка рабочего стола. В этом режиме автоматически открываются вспомогательные окна, такие как Watch List (Список контролируемых значений) и Local Variables (Локальные переменные).
Local Variables®Local Variables®Name j Value _va{1527308,512,4,4,71,21,19,1527..,I1 (0x00000001)Sender:00A05984+this:00A03A7CName j Value _va{1527308,512,4,4,71,21,19,1527..,I1 (0x00000001)Sender:00A05984+this:00A03A7CВ окне Local Variables (Локальные переменные) представлены все переменные, локальные для данного модуля (подпрограммы, метода). Их список модифицировать нельзя. На рисунке, в частности, видно, что значение переменной i (счетчика цикла) равно 1.
Local Variables®
Local Variables®
Name j Value _
va{1527308,512,4,4,71,21,19,1527..,
I1 (0x00000001)
Sender:00A05984
+this:00A03A7C
Name j Value _
va{1527308,512,4,4,71,21,19,1527..,
I1 (0x00000001)
Sender:00A05984
+this:00A03A7C
Продолжим выполнение цикла, нажав кла
вишу F8. Управление перейдет к основной ветке if условного оператора, так как значение переменной i меньше 5. После этого ветка else будет пропущена и управление вновь вернется к оператору заголовка цикла. Цикл будет повторен 10 раз, и когда счетчик примет значение 10, возникнет ошибка выхода за границу массива а. Это легко заметить и по исходному коду — в итоге следует либо увеличить длину массива, либо изменить конечное значение счетчика в заголовке цикла.
Пошаговое выполнение процедур и функций
Если в исходном тексте встречается вызов функции, по нажатию клавиши F8 она выполняется за один шаг как отдельный оператор. Но нередко требуется выяснить, как работает эта функция.
Для того чтобы перейти к выполнению тела подпрограммы, встретившейся на очередном шаге отладки, надо дать команду Run > Trace into (Запуск > Шаг с входом) или нажать клавишу F7. Редактор автоматически перейдет к тексту нужной функции или метода (если доступны их исходные тексты), а по его завершении вернется к команде, содержащей вызов подпрограммы.
Call Stack, ®: :BO4Q1BE4 TForml::E;uti:oniriiclXthi5=;(XiA03A7C, 5eridet=:0dQCall Stack, ®: :BO4Q1BE4 TForml::E;uti:oniriiclXthi5=;(XiA03A7C, 5eridet=:0dQ:52038f02 vdlOO,@Controls@TControl@Click$qqrv + Охба з::5203c9ad; C:\WINDOWS\System32\vcllOO.bpl;5 lffb93d vd 100. @Stdctrls@TButtonControl@WndProc$qqrr 17:5203cae4; C:\WINDOWS\System32\vcllOO.bpl;5203c9ad; C:\WINDOWS\System32\vdlOO.bpl:520511e2; C:\WINDOWS\5ystem32\vcllOO.bpl:5203c 163 vdl00.@Control5@TWinControl@MainWndProc$qqr:51F40bb2 rtl 100.@Classes@TDataModule@WriteHeight$qqrp 1:77d48654; C:\WINDOWS\sy5tem32\USER32.dll:77d48723; C;|WINDOW51$y$tem32\U5ER32.dll:77d49153 USER32,PostMes$ageA + Oxad:77d49196 USER32.PostMe$5ageA + OxFO:77F75da3 ntdll.KiUserCallbackDispatcher + 0x13:77d4al4d U5ER32.5endMessageW + 0x47:77d68392; C:\WINDOWS\system32\USER32.dll:77d66840; C:\WINDOWS\system32\USER32.dll^<*■:52038f02 vdlOO,@Controls@TControl@Click$qqrv + Охба з::5203c9ad; C:\WINDOWS\System32\vcllOO.bpl;5 lffb93d vd 100. @Stdctrls@TButtonControl@WndProc$qqrr 17:5203cae4; C:\WINDOWS\System32\vcllOO.bpl;5203c9ad; C:\WINDOWS\System32\vdlOO.bpl:520511e2; C:\WINDOWS\5ystem32\vcllOO.bpl:5203c 163 vdl00.@Control5@TWinControl@MainWndProc$qqr:51F40bb2 rtl 100.@Classes@TDataModule@WriteHeight$qqrp 1:77d48654; C:\WINDOWS\sy5tem32\USER32.dll:77d48723; C;|WINDOW51$y$tem32\U5ER32.dll:77d49153 USER32,PostMes$ageA + Oxad:77d49196 USER32.PostMe$5ageA + OxFO:77F75da3 ntdll.KiUserCallbackDispatcher + 0x13:77d4al4d U5ER32.5endMessageW + 0x47:77d68392; C:\WINDOWS\system32\USER32.dll:77d66840; C:\WINDOWS\system32\USER32.dll^<*■Количество вложенных друг в друга вызовов функций может быть значительным. В таком случае полезно разобраться, на какой глубине погружения находится отладчик, и просмотреть текст программы, относящейся к более высокому уровню. Чтобы увидеть последовательность вызова подпрограмм во время работы приложения, дайте команду View > Debug Windows > Call Stack (Вид > Окна отладки > Стек вызовов).
Call Stack, ®
: :BO4Q1BE4 TForml::E;uti:oniriiclXthi5=;(XiA03A7C, 5eridet=:0dQ
Call Stack, ®
: :BO4Q1BE4 TForml::E;uti:oniriiclXthi5=;(XiA03A7C, 5eridet=:0dQ
:52038f02 vdlOO,@Controls@TControl@Click$qqrv + Охба з:
:5203c9ad; C:\WINDOWS\System32\vcllOO.bpl
;5 lffb93d vd 100. @Stdctrls@TButtonControl@WndProc$qqrr 17
:5203cae4; C:\WINDOWS\System32\vcllOO.bpl
;5203c9ad; C:\WINDOWS\System32\vdlOO.bpl
:520511e2; C:\WINDOWS\5ystem32\vcllOO.bpl
:5203c 163 vdl00.@Control5@TWinControl@MainWndProc$qqr
:51F40bb2 rtl 100.@Classes@TDataModule@WriteHeight$qqrp 1
:77d48654; C:\WINDOWS\sy5tem32\USER32.dll
:77d48723; C;|WINDOW51$y$tem32\U5ER32.dll
:77d49153 USER32,PostMes$ageA + Oxad
:77d49196 USER32.PostMe$5ageA + OxFO
:77F75da3 ntdll.KiUserCallbackDispatcher + 0x13
:77d4al4d U5ER32.5endMessageW + 0x47
:77d68392; C:\WINDOWS\system32\USER32.dll
:77d66840; C:\WINDOWS\system32\USER32.dll^
<*■
:52038f02 vdlOO,@Controls@TControl@Click$qqrv + Охба з:
:5203c9ad; C:\WINDOWS\System32\vcllOO.bpl
;5 lffb93d vd 100. @Stdctrls@TButtonControl@WndProc$qqrr 17
:5203cae4; C:\WINDOWS\System32\vcllOO.bpl
;5203c9ad; C:\WINDOWS\System32\vdlOO.bpl
:520511e2; C:\WINDOWS\5ystem32\vcllOO.bpl
:5203c 163 vdl00.@Control5@TWinControl@MainWndProc$qqr
:51F40bb2 rtl 100.@Classes@TDataModule@WriteHeight$qqrp 1
:77d48654; C:\WINDOWS\sy5tem32\USER32.dll
:77d48723; C;|WINDOW51$y$tem32\U5ER32.dll
:77d49153 USER32,PostMes$ageA + Oxad
:77d49196 USER32.PostMe$5ageA + OxFO
:77F75da3 ntdll.KiUserCallbackDispatcher + 0x13
:77d4al4d U5ER32.5endMessageW + 0x47
:77d68392; C:\WINDOWS\system32\USER32.dll
:77d66840; C:\WINDOWS\system32\USER32.dll^
<*■
Текст функции, представленной в стеке вызова, можно редактировать, если, конечно, она относится к текущему проекту, а не к системным вызовам Windows. Для этого
надо дважды щелкнуть на соответствующей строке.
Разработчик может просмотреть все процессы, контролируемые отладчиком, и модули, загруженные для каждого процесса. Для этого надо открыть окно модулей приложения командой View > Debug Windows > Modules (Вид > Окна отладки > Модули). Для каждого модуля в иерархическом виде отображаются сведения о связанных с
ним классах, методах и пространствах имен.
Дополнительные возможности пошагового выполнения
Если цикл должен быть выполнен сотни и тысячи раз, выполнять его по шагам, конечно же, неудобно. Чтобы быстро передать управление на определенный оператор, надо в режиме пошагового выполнения установить па пего курсор и дать команду Run > Run to Cursor (Запуск > Выполнение до курсора) или нажать клавишу F4. Программа продолжит работу, пока не будет достигнут указанный оператор.
Если выполнение программы приостановлено, можно продолжить работу с места остановки командой Run > Run (Запуск > Выполнить) или клавишей F9. Для прерывания работы служат команда Run > Reset (Запуск > Сброс) и комбинация клавиш CTRL+F2.
В режиме выполнения программы по шагам можно выяснить не только зна
чение конкретной переменной, но и значение определенного выражения. Для этого надо выделить текст выражения и подвести к нему указатель мыши. Среда
всплывающем окне.всплывающем окне.C++Builder покажет значение выделенного выражения во Для вычисления произвольных выражений предназначено окно оценки значений. Его открывают комбинацией клавиш CTRL+F7.
всплывающем окне.
всплывающем окне.
Анализируемое выражение вводят в поле Expression (Выражение), после чего надо нажать клавишу ENTER или кнопку Evaluate (Вычислить). В поле Result (Результат) отображается текущее значение выражения. Чтобы изменить значение переменной, введите ее имя в поле Expression (Выражение) и нужное значение в поле New value (Новое значе-
ние), после чего нажмите кнопку Modify (Изменить).
Вопросы
1.
2.
3.
4.
5.
6.
7.
8.
Что такое отладка?
Как настроить проект на поддержку отладочного режима?
Зачем нужна макрокоманда assert?
Что такое точка прерывания?
Как выполнять программу в пошаговом режиме?
Как по шагам выполнить тело процедуры?
Как узнать значение переменных и выражений в процессе отладки?
Как изменить значение переменной программы во время отладки?
ГЛАВА 28 Расширенные технологии отладки
Простые средства поиска ошибок, представленные в главе 27, позволяют быстро выявить большинство типичных недостатков проекта. Однако нередки случаи, когда эти средства не дают желаемого результата или их использование сопряжено с трудностями. В подобных случаях применяют расширенные технологии отладки.
Условные точки прерывания
Поиск ошибки существенно затрудняется, если ошибка возникает в ситуации, требующей существенных усилий по ее воспроизведению. Точки прерывания не слишком полезны, если требуется прервать выполнение программы в середине длинного цикла. Крайне утомительно на протяжении сотен раз просматривать текущее значение переменной и вновь запускать приложение, дожидаясь достижения этой переменной нужного значения. Программисты в таких случаях идут па хитрость — вставляют в исходный код специальные проверки, например:
int i, к;
for! i = 10000; i <= 100000; i ++ )
if(i==12345)
k=0;
CallTest (i) ;
}
Оператор k=0 выполняется, только если значение переменной 1 равно 12345. Если поставить на него точку прерывания, программа остановится как раз в нужный момент. Такой подход плох тем, что исходный код «загрязняется» ненужными операторами, которые снижают его наглядность, а в некоторых случаях и сами служат источниками новых ошибок.
Среда C++Builder позволяет устанавливать условные точки прерывания, которые срабатывают не всегда, а лишь при выполнении заданного условия. Чтобы установить условную точку прерывания, надо дать команду Run > Add Breakpoint > Source Breakpoint (Запуск > Добавить точку прерывания > Исходная точка прерывания). На экране появится диалоговое окно Add Source Breakpoint (Добавление исходной точки прерывания).
В поле Condition (Условие) следует ввести логическое выражение. Данная точка прерывания сработает, только когда значение этого выражения будет истинно.
Число выполнений конкретной строки исходного кода до срабатывания точки прерывания задают в поле Pass count (Число проходов) — рис. 28.1.
Filename;Filename;Add Source Breakpoint
Filename;
Filename;
p;\TEMP\borland\cpp\l\Unitl .срр
Line number:
Condition;Condition;, Eass count:Group:, Eass count:Group:j i==123«j i==123«
Condition;
Condition;
, Eass count:
Group:
, Eass count:
Group:
j i==123«
j i==123«
Advanced «I
, Actions:
R* Break
1 Г" Ignore subsequent exceptionst” Handle subsequent exceptions
Log message: $
1 ; Eval expression: [
- Enable group:P~
i ' Disable group: |
Г" Log Call Stack
3.
j Cancel| Help j
Puc. 28.1. Окно настройки параметров точки прерывания
При использовании условной точки прерывания вспомогательные операторы в примере, приведенном выше, не нужны:
int i,k;
for( i = 10000; i <= 100000; i ++ ) CallTest(1) ;
На вызов функции Ca 1 ITest () надо установить условную точку прерывания и задать в качестве условия ее срабатывания выражение i==12345.
С каждой точкой прерывания можно связать определенное действие, задаваемое на дополнительной панели в диалоговом окне Add Source Breakpoint (Добавление исходной точки прерывания). Эту панель открывают кнопкой Advanced (Дополнительно).
Флажок Break (Прерывание) отвечает за прерывание работы. Если он сброшен, то достижение точки прерывания не приведет к ее срабатыванию;
Флажок Ignore subsequent exceptions (Игнорировать последующие исключительные ситуации) отключает обработку исключительных ситуаций, возникающих при попытке выполнения программой ошибочных действий;
Флажок Handle subsequent exceptions (Обработать последующие исключительные ситуации) включает обработку исключительных ситуаций в программе.
8.1.1. Прерывание по изменению значения
Точка прерывания может быть настроена не только на конкретную инструкцию, но и на изменение переменной или ячейки данных в оперативной памяти. Для этого служит команда Run > Add Breakpoints > Data Breakpoint (Запуск > Добавить точку прерывания > Точка прерывания по данным). В появившемся диалоговом окне надо ввести в поле Address (Адрес), например, имя переменной, значение которой контролируется. Установить такую точку прерывания можно только при выполнении программы, так как она привязывается к конкретным адресам оперативной памяти, в которые загружена программа и ее внутренние переменные. Работа программы приостанавливается, как только значение в заданной области памяти изменяется.
Протокол отладки
Способ отладки программы по шагам наиболее распространен, однако обойтись им удается не всегда. Этому есть две причины. Во-первых, отладка по шагам позволяет взглянуть на функционирование программы в статике: выполняется один оператор и программа «замирает», ожидая следующей команды. Сформировать глобальный взгляд на поток выполняемых операторов таким образом невозможно. Во-вторых, крупная система может дать сбой, когда она уже развернута у заказчика. В такой ситуации нет возможности приехать к нему с дистрибутивным комплектом C++Builder и исходными текстами и устроить отладку по шагам.
В подобных случаях помогает ведение протокола работы. Протокол - это текстовый файл, в который выводится промежуточная информация о значениях переменных во время работы программы. С его помощью удается выявить ошибки, которые трудно обнаружить в ходе пошаговой отладки.
Ведение протокола легко реализуется с помощью команд вывода промежуточных значений в файл. Однако при этом приходится добавлять в программу дополнительные операторы, а при подготовке окончательной версии удалять их вручную. Среда C++Builder снимает эти проблемы — в нее встроены собственные средства ведения протокола работы на основе точек прерывания.
Рассмотрим, как задействовать стандартные возможности C++Builder по ведению протокола работы приложения.
Подключение протокола работы
Добавим точку прерывания к строке кода, выполнение которой нам интересно. Для этого дадим команду Run > Add Breakpoint > Source Breakpoint (Запуск > Добавить точку прерывания > Исходная точка прерывания) — откроется диалоговое окно Add Source Breakpoint (Добавление исходной точки прерывания).
Нажмем кнопку Advanced (Дополнительно) для отображения дополнительных элементов управления.
Сбросим флажок Break (Прерывание), чтобы при протоколировании работа программы не прерывалась.
Введем в поле Log message (Сообщение для протокола) произвольное текстовое сообщение. Как только в ходе работы программы встретится эта точка пре-
рывания, во внутренний протокол, поддерживаемый средой C++Builder, добавится данное сообщение. При этом сама программа не остановится.
Настройка протокола работы
В дополнение к информационному тестовому сообщению в протокол можно записать значение определенного выражения. Это выражение вводят в поле Eval expression (Вычислить выражение). Чтобы занести вычисленное значение в протокол, установите флажок Log result (Записать результат в протокол).
Протокол отладки может содержать информацию о текущем стеке вызовов. Для этого надо открыть диалоговое окно настройки точки прерывания командой Breakpoint Properties (Свойства) из контекстного меню. Установите флажок Log Call Stack (Регистрировать стек вызовов) и выберите значение Entire Stack (Весь стек) или Partial Stack (Часть стека). Количество протоколируемых элементов стека задают в поле Number of frames (Число элементов).
Просмотр протокола
После того как программа отработала или прервала работу, достигнув точки прерывания, сформированный протокол посматривают командой View > Debug Windows > Event Log (Вид > Окна отладки > Протокол событий).
Содержимое этого окна нередко весьма объемно. Его фильтруют и гибко настраивают на вывод нужной информации. Окно настройки протокола открывают командой Properties (Свойства) из контекстного меню. Можно настроить вывод следующих сведений.
Сообщения о встретившихся точках прерывания и сопутствующая информация относятся к категории Breakpoint messages (Сообщения точек прерывания). Если установлен флажок Display process info with event (Отображать сведения о процессе), то дополнительно выводится информация о системном процессе, который сгенерировал соответствующее сообщение.
Сообщения об обращении отлаживаемого приложения к системным процессам и библиотекам Windows относятся к категории Process messages (Сообщения процессов).
Сообщения, выводимые в точках прерывания, зависят от содержимого полей Log message (Сообщение для протокола) и Eval expression (Вычислить выражение) и входят в категорию Output messages (Сообщения вывода).
Получаемые программой сообщения Windows входят в категорию Windows messages (Сообщения Windows). Таких сообщений обычно очень много, поэтому злоупотреблять данной возможностью не стоит.
Разные типы сообщений выделены цветом. Для настройки цвета служит категория Use Event Log Colors (Использовать цвета сообщений протокола).
Отладка машинного кода
Чтобы выяснить причину ошибки, программистам иногда приходится заглядывать в сгенерированный компилятором машинный код. Обычно такая потребность возникает, если не ясны нюансы работы стандартной функции или требуется выяснить, в какие ассемблерные команды транслируются конкретные последовательности операторов языка C++ для их последующей оптимизации.
Окно машинного кода открывают в тот момент, когда программа приостановлена на точке прерывания. Для этого служит команда View > Debug Windows > CPU (Вид > Окна отладки > Процессор).
Чтобы разобраться в содержимом этого окна, надо знать структуру оперативной памяти компьютера и понимать язык ассемблера для процессоров Intel и AMD. В этом окне визуализируется смешанный код: исходные операторы C++ и ассемблерный код конкретного процессора. Для их совместного отображения надо включить флажок Mixed Source Code (Добавить исходный код) в контекстном меню окна процессора (рис. 28.2).
♦ Project 1 - Borland Developer Studio 2006 - UniM.cpp [Stopped] [Built: 20,75 secs]♦ Project 1 - Borland Developer Studio 2006 - UniM.cpp [Stopped] [Built: 20,75 secs]File Edit Search View Refactor Project Run Component TodsFile Edit Search View Refactor Project Run Component TodsStar Team Window Help ; joebug LayoutStar Team Window Help ; joebug LayoutLLP W iD 'L Дм ^ @ Q La 1 S Welcome Page ^Unitl.cpp ^CPU[$0012F4A0] = $00174CBC Thread #3 02 8LLP W iD 'L Дм ^ @ Q La 1 S Welcome Page ^Unitl.cpp ^CPU[$0012F4A0] = $00174CBC Thread #3 02 8® a в® a в♦ e♦ eUnit!. cpp .27Unit!. cpp .27for( ifor( i10000; i10000; i100000; i100000; i00401BEE C74 5C.C10370000 mov [ebp-$34) ,$0000271000401BEE C74 5C.C10370000 mov [ebp-$34) ,$00002710Unitl.cpp.28i {Unitl.cpp.28i {00401BF5 00401BFB 00401C00 00401C03 00401C08 O0401COB00401BF5 00401BFB 00401C00 00401C03 00401C08 O0401COB66C745E818O0 BA3C324OOO 8P45FC E8480B0000 FF45F466C745E80C0Q66C745E818O0 BA3C324OOO 8P45FC E8480B0000 FF45F466C745E80C0QAnsiString A -AnsiString A -mov word ptr [ebp-$18],$0018mov edx,50040323clea eax,[ebp-$04]call System::AnsiStrlng::AnsiString(consmov word ptr [ebp-$18],$0018mov edx,50040323clea eax,[ebp-$04]call System::AnsiStrlng::AnsiString(consUnitl.opp.29: i±(i=12345)Unitl.opp.29: i±(i=12345)dword ptr [ebp-$0c] word ptr [ebp-518],SOOOcdword ptr [ebp-$0c] word ptr [ebp-518],SOOOc00401C11 00401C16 00401C19 00401C1B00401C11 00401C16 00401C19 00401C1BВ A39300000 8955CC 85027405В A39300000 8955CC 85027405edx,§00003039 [ebp~$34],edxedx,§00003039 [ebp~$34],edxUnitl.cpp.30: k-0; 00401C1D 33COUnitl.cpp.30: k-0; 00401C1D 33COteat edx,edx jz $00401c22teat edx,edx jz $00401c22xor eax,eaxxor eax,eax00410000 M00 0000000000004100080000010048IB5P0041001000000000ОС000100410018503131E28862331ЛП41ПП2ЛП14ПП1nn2ЯЯДAQ00410000 M00 0000000000004100080000010048IB5P0041001000000000ОС000100410018503131E28862331ЛП41ПП2ЛП14ПП1nn2ЯЯДAQtCPU'tCPU'^ Thread Status^ Thread Status00 ВС 00 E2 El00 ВС 00 E2 El....H.]j....H.]jР11в€ЬЭе .0.. f ©6Р11в€ЬЭе .0.. f ©6Thread Id4b 3028Thread Id4b 3028' StatedStopped' StatedStopped' Status .Breakpoint' Status .BreakpointLocation._ . _ _ . _ D:\TEMP\borland\cpp\l\Unitl,cpp(27)Location._ . _ _ . _ D:\TEMP\borland\cpp\l\Unitl,cpp(27)ЕВХ 00105984.^PF1ЕСХ51FFBB80AF0ЕРХ0DA05984ZF0ESI51FFBA70SF0EPI0012F674TF0EBP0012F4P4IF1ESP0012F474PF0EIP00401BEEOF0EFL00000207100CS0O1BNF0PS0023RF0ss0023VH0ES0023 #AC0ЕВХ 00105984.^PF1ЕСХ51FFBB80AF0ЕРХ0DA05984ZF0ESI51FFBA70SF0EPI0012F674TF0EBP0012F4P4IF1ESP0012F474PF0EIP00401BEEOF0EFL00000207100CS0O1BNF0PS0023RF0ss0023VH0ES0023 #AC000000000 0D12F4A8 01010053 77D66C28 00000005 00000001 0000000100000000 0D12F4A8 01010053 77D66C28 00000005 00000001 000000010012F49C 0012F498 0012F494 Q012F490 D012F48C 0012F468 0012F4840012F49C 0012F498 0012F494 Q012F490 D012F48C 0012F468 0012F484**wlwl**XX
♦ Project 1 - Borland Developer Studio 2006 - UniM.cpp [Stopped] [Built: 20,75 secs]
♦ Project 1 - Borland Developer Studio 2006 - UniM.cpp [Stopped] [Built: 20,75 secs]
File Edit Search View Refactor Project Run Component Tods
File Edit Search View Refactor Project Run Component Tods
Star Team Window Help ; joebug Layout
Star Team Window Help ; joebug Layout
LLP W iD 'L Дм ^ @ Q La 1 S Welcome Page ^Unitl.cpp ^CPU
[$0012F4A0] = $00174CBC Thread #3 02 8
LLP W iD 'L Дм ^ @ Q La 1 S Welcome Page ^Unitl.cpp ^CPU
[$0012F4A0] = $00174CBC Thread #3 02 8
® a в
® a в
♦ e
♦ e
Unit!. cpp .27
Unit!. cpp .27
for( i
for( i
10000; i
10000; i
100000; i
100000; i
00401BEE C74 5C.C10370000 mov [ebp-$34) ,$00002710
00401BEE C74 5C.C10370000 mov [ebp-$34) ,$00002710
Unitl.cpp.28i {
Unitl.cpp.28i {
00401BF5 00401BFB 00401C00 00401C03 00401C08 O0401COB
00401BF5 00401BFB 00401C00 00401C03 00401C08 O0401COB
66C745E818O0 BA3C324OOO 8P45FC E8480B0000 FF45F4
66C745E80C0Q
66C745E818O0 BA3C324OOO 8P45FC E8480B0000 FF45F4
66C745E80C0Q
AnsiString A -
AnsiString A -
mov word ptr [ebp-$18],$0018
mov edx,50040323c
lea eax,[ebp-$04]
call System::AnsiStrlng::AnsiString(cons
mov word ptr [ebp-$18],$0018
mov edx,50040323c
lea eax,[ebp-$04]
call System::AnsiStrlng::AnsiString(cons
Unitl.opp.29: i±(i=12345)
Unitl.opp.29: i±(i=12345)
dword ptr [ebp-$0c] word ptr [ebp-518],SOOOc
dword ptr [ebp-$0c] word ptr [ebp-518],SOOOc
00401C11 00401C16 00401C19 00401C1B
00401C11 00401C16 00401C19 00401C1B
В A39300000 8955CC 8502
7405
В A39300000 8955CC 8502
7405
edx,§00003039 [ebp~$34],edx
edx,§00003039 [ebp~$34],edx
Unitl.cpp.30: k-0; 00401C1D 33CO
Unitl.cpp.30: k-0; 00401C1D 33CO
teat edx,edx jz $00401c22
teat edx,edx jz $00401c22
xor eax,eax
xor eax,eax
00410000 M
00 00
00
00
00
00
00410008
00
00
01
00
48
IB
5P
00410010
00
00
00
00
ОС
00
01
00410018
50
31
31
E2
88
62
33
1ЛП41ПП2Л
П1
4П
П1
nn
2Я
ЯД
AQ
00410000 M
00 00
00
00
00
00
00410008
00
00
01
00
48
IB
5P
00410010
00
00
00
00
ОС
00
01
00410018
50
31
31
E2
88
62
33
1ЛП41ПП2Л
П1
4П
П1
nn
2Я
ЯД
AQ
tCPU'
tCPU'
^ Thread Status
^ Thread Status
00 ВС 00 E2 El
00 ВС 00 E2 El
....H.]j
....H.]j
Р11в€ЬЭе .0.. f ©6
Р11в€ЬЭе .0.. f ©6
Thread Id
4b 3028
Thread Id
4b 3028
' StatedStopped
' StatedStopped
' Status .
Breakpoint
' Status .
Breakpoint
Location._ . _ _ . _
D:\TEMP\borland\cpp\l\Unitl,cpp(27)
Location._ . _ _ . _
D:\TEMP\borland\cpp\l\Unitl,cpp(27)
ЕВХ 00105984.^
PF
1
ЕСХ
51FFBB80
AF
0
ЕРХ
0DA05984
ZF
0
ESI
51FFBA70
SF
0
EPI
0012F674
TF
0
EBP
0012F4P4
IF
1
ESP
0012F474
PF
0
EIP
00401BEE
OF
0
EFL
00000207
10
0
CS
0O1B
NF
0
PS
0023
RF
0
ss
0023
VH
0
ES
0023 #
AC
0
ЕВХ 00105984.^
PF
1
ЕСХ
51FFBB80
AF
0
ЕРХ
0DA05984
ZF
0
ESI
51FFBA70
SF
0
EPI
0012F674
TF
0
EBP
0012F4P4
IF
1
ESP
0012F474
PF
0
EIP
00401BEE
OF
0
EFL
00000207
10
0
CS
0O1B
NF
0
PS
0023
RF
0
ss
0023
VH
0
ES
0023 #
AC
0
00000000 0D12F4A8 01010053 77D66C28 00000005 00000001 00000001
00000000 0D12F4A8 01010053 77D66C28 00000005 00000001 00000001
0012F49C 0012F498 0012F494 Q012F490 D012F48C 0012F468 0012F484
0012F49C 0012F498 0012F494 Q012F490 D012F48C 0012F468 0012F484
*
*
wl
wl
*
*
X
X
^Eventlog ^BreakpointList |^ThreadStatus[
Puc. 28.2. Окно представления, исходного кода в виде команд ассемблера
В окне машинного кода после каждой строки исходного текста располагается соответствующий ей код, сгенерированный компилятором. На отдельной панели
показано состояние регистров процессора. Значения регистров, измененные на последнем шаге, подсвечены. На дополнительных панелях отображаются дамп и стек программы.
Дамп — это содержимое определенной области оперативной памяти в конкретный .момент работы программы, ее моментальный «снимок».
Стек — это область памяти, в которой хранится текущая последовательность вызовов подпрограмм.
При желании содержимое ячеек оперативной памяти можно изменить непосредственно из среды C + + Builder, выделив нужную ячейку и выбрав в контекстном меню пункт Change (Изменить).
При работе с машинным кодом полезна команда Run > Trace to Next Source Line (Запуск > Выполнить доследующей строки исходного кода). При этом отладчик C++Builder выполнит все инструкции машинного кода до ближайшей строки исходного текста па языке C++2.
Окно регистров математического сопроцессора открывают командой View > Debug Windows > FPU (Просмотр > Окна отладки > Сопроцессор).
Инспектор отладки
В систему C++Builder входит Инспектор отладки. Его запускают командой Run > Inspect (Запуск > Инспектор) или комбинацией клавиш ALT+F5. Если курсор в исходном тексте программы находится на идентификаторе, то Инспектор сразу покажет его структуру.
Инспектор отладки — это средство системы C++Builder, предназначенное для детального просмотра и изменения значений простых переменных и сложно организованных структур (массивов, объектов).
В верхней части окна Инспектора отладки показаны имя и тип анализируемого объекта, а также его физический адрес. Доступ к прочим данным осуществляется при помощи вкладок:вкладка Data (Данные) отображает поля объекта, определенные пользователем;вкладка Methods (Методы) содержит методы класса с указанием класса-родителя, модуля, в котором он описан, и физического адреса начала подпрограммы в машинном коде;на вкладке Properties (Свойства) приведены свойства класса и их текущие значения или адреса обработчиков.Любое из этих значений можно изменить, выделив нужную строку в Инспекторе отладки и выбрав в контекстном меню пункт Change (Изменить). ЕслиВ верхней части окна Инспектора отладки показаны имя и тип анализируемого объекта, а также его физический адрес. Доступ к прочим данным осуществляется при помощи вкладок:вкладка Data (Данные) отображает поля объекта, определенные пользователем;вкладка Methods (Методы) содержит методы класса с указанием класса-родителя, модуля, в котором он описан, и физического адреса начала подпрограммы в машинном коде;на вкладке Properties (Свойства) приведены свойства класса и их текущие значения или адреса обработчиков.Любое из этих значений можно изменить, выделив нужную строку в Инспекторе отладки и выбрав в контекстном меню пункт Change (Изменить). ЕслиDebug InspectorIxlDebug InspectorIxlpFormli TForml :00A03A7C№1 Methods I PropertiespFormli TForml :00A03A7C№1 Methods I PropertiesButton!Button!I Buttons Label!; ■' FActrveControl. FFocusedControlFBorderkons. FBorderStyle F5izeChanging. FWIndowState FShowAcbon FKeyPreview FActive FFormStyle FPosition FDeFaultMonrtor FTIIeModo FDropTarget FOIdCreateOrder FPrlntScalejStdctrfs; .-TButton *I Buttons Label!; ■' FActrveControl. FFocusedControlFBorderkons. FBorderStyle F5izeChanging. FWIndowState FShowAcbon FKeyPreview FActive FFormStyle FPosition FDeFaultMonrtor FTIIeModo FDropTarget FOIdCreateOrder FPrlntScalejStdctrfs; .-TButton *|:00A05984SIS;:00A05E70Я:00A05518В:00A05984:00A05984^V 10/* Forms::bK :2 /* FormsubsSizeable’0 /* Forms: iwsNormal ,0 /* Formsusalgnore'0 /* Forms; ifsNormal • '0 /* Forms: :poDeslgne,3 /* FotmscdmActivef |Q /* Forms: itbHorizoni1 /* FormsupoProport^^|:00A05984SIS;:00A05E70Я:00A05518В:00A05984:00A05984^V 10/* Forms::bK :2 /* FormsubsSizeable’0 /* Forms: iwsNormal ,0 /* Formsusalgnore'0 /* Forms; ifsNormal • '0 /* Forms: :poDeslgne,3 /* FotmscdmActivef |Q /* Forms: itbHorizoni1 /* FormsupoProport^^
В верхней части окна Инспектора отладки показаны имя и тип анализируемого объекта, а также его физический адрес. Доступ к прочим данным осуществляется при помощи вкладок:
вкладка Data (Данные) отображает поля объекта, определенные пользователем;
вкладка Methods (Методы) содержит методы класса с указанием класса-родителя, модуля, в котором он описан, и физического адреса начала подпрограммы в машинном коде;
на вкладке Properties (Свойства) приведены свойства класса и их текущие значения или адреса обработчиков.
Любое из этих значений можно изменить, выделив нужную строку в Инспекторе отладки и выбрав в контекстном меню пункт Change (Изменить). Если
В верхней части окна Инспектора отладки показаны имя и тип анализируемого объекта, а также его физический адрес. Доступ к прочим данным осуществляется при помощи вкладок:
вкладка Data (Данные) отображает поля объекта, определенные пользователем;
вкладка Methods (Методы) содержит методы класса с указанием класса-родителя, модуля, в котором он описан, и физического адреса начала подпрограммы в машинном коде;
на вкладке Properties (Свойства) приведены свойства класса и их текущие значения или адреса обработчиков.
Любое из этих значений можно изменить, выделив нужную строку в Инспекторе отладки и выбрав в контекстном меню пункт Change (Изменить). Если
Debug InspectorIxl
Debug InspectorIxl
pFormli TForml :00A03A7C
№1 Methods I Properties
pFormli TForml :00A03A7C
№1 Methods I Properties
Button!
Button!
I Buttons Label!
; ■' FActrveControl
. FFocusedControlFBorderkons
. FBorderStyle F5izeChanging
. FWIndowState FShowAcbon FKeyPreview FActive FFormStyle FPosition FDeFaultMonrtor FTIIeModo FDropTarget FOIdCreateOrder FPrlntScale
jStdctrfs; .-TButton *
I Buttons Label!
; ■' FActrveControl
. FFocusedControlFBorderkons
. FBorderStyle F5izeChanging
. FWIndowState FShowAcbon FKeyPreview FActive FFormStyle FPosition FDeFaultMonrtor FTIIeModo FDropTarget FOIdCreateOrder FPrlntScale
jStdctrfs; .-TButton *
|:00A05984SIS
;:00A05E70Я
:00A05518В
:00A05984
:00A05984
^V 10/* Forms::bK :2 /* FormsubsSizeable
’0 /* Forms: iwsNormal ,0 /* Formsusalgnore'
0 /* Forms; ifsNormal • '0 /* Forms: :poDeslgne
,3 /* FotmscdmActivef |Q /* Forms: itbHorizoni
1 /* FormsupoProport^^
|:00A05984SIS
;:00A05E70Я
:00A05518В
:00A05984
:00A05984
^V 10/* Forms::bK :2 /* FormsubsSizeable
’0 /* Forms: iwsNormal ,0 /* Formsusalgnore'
0 /* Forms; ifsNormal • '0 /* Forms: :poDeslgne
,3 /* FotmscdmActivef |Q /* Forms: itbHorizoni
1 /* FormsupoProport^^
поле имеет сложную структуру (например, представляет собой экземпляр класса), то для него можно вызвать отдельный Инспектор отладки, выделив это поле и выбрав в контекстном меню пункт Inspect (Инспектор).
Система CodeGuard
В версию C++Builder 2006 включена система CodeGuard, которая выявляет ошибки, ранее пропускавшиеся компилятором и средой поддержки выполнения и приводившие к краху системы. К подобным ошибкам относится прежде всего неконтролируемый выход индекса за границы массива, например:
int А[100] ;
А[100] = 1;
К массиву а можно обращаться только с индексом, укладывающимся в диапазон от 0 до 99. Однако данный код не вызовет ошибку компиляции и, скорее всего, сразу не приведет к ошибке времени выполнения. Но она вполне может возникнуть в неподходящих условиях, в процессе промышленной эксплуатации программы и так далее.
Система CodeGuard выявляет ошибки двух основных классов: связанные с неверным использованием оперативной памяти (некорректное применение указателей) и других ресурсов (запись данных в буфер малого размера) и связанные с передачей некорректных параметров в функции. Чтобы включить поддержку в
<# Projectl - Borland Developer Studio 2006 - Unitl .cpp [Stop... [• |[B~||
File Edit Search View Refactor Project Run Component Tools Star Team , |Dej &# $ tj -& - © ■ ® S £ ^ > - II ® _ 5 ^ - ❖Д Welcome Page ^Unitl.cpp ^SJCPU' • •
-L L }A
• devoid fastcall TForml::ButtonlClick(TObject *Sende
| int a[10];
^ Ф й 26: 1 Insert Modified Unit) .cpp Unitl.h Desjgri+J#i
i'iSTM*™!!** X
22.09.2006 17:03:01 started a CodeGuard(tm) process: Projectl.exe(3536)*
-' Access overrun in process; Project Lexe(3536) - D:\TEMP\borland\cpp\l\Unitl.cpp#26
Я Attempt to access 4 byte(s) at 0x0012F47L+40, that is at offset 0+40 in local block 0x0
0X00401EFC - D:\TEMP\borland\cpp\l\Unltl ,cpp#26
0X52038F02
0X5203C9AO
>
^t Build | CodeGuard |
проекте режима CodeGuard, надо включить флажок All Codeguard options on в разделе CodeGuard compile support проектных настроек компилятора C++ Compiler.
После установки флажка проект надо принудительно перестроить и запустить. Теперь возникновение практически любой ошибки, которая ранее не выявлялась, приведет к остановке проекта и переключению в редактор C++Builder, где будет подсвечена строка с ошибочным оператором.
В окне Messages появится подробное описание места, причины и последствий ошибки.
Система CodeGuard также может генерировать предупреждения по поводу подозрительных операций.
Вопросы
Что такое точка прерывания?
Как устанавливаются условные точки прерывания?
Как организуется ведение протокола отладки?
Как просмотреть машинный код?
Какое средство C++Builder предназначено для детального изучения структуры объектов?
Как использовать систему CodeGuard?
ГЛАВА 29 Технологии визуального проектирования приложений
Процесс создания приложения в среде C++Builder основан на использовании компонентов, к которым в первую очередь относятся визуальные элементы управления приложением. Для добавления компонентов на форму используют палитру инструментов. В этой главе рассмотрены важнейшие визуальные компоненты, их основные свойства, методы и события. Напомним, что ряд свойств, присущих большинству визуальных компонентов, рассмотрен в главе 24.
Компоненты группы Standard
В данной группе представлены наиболее востребованные элементы управления, применяемые практически в каждом приложении.
Компонент Надпись
Компонент Надпись (TLabel) предназначен для вывода на экран текстовой строки в позиции размещения компонента. Выводимая строка задается свойством Caption.
Компонент Текстовое поле
Компонент Текстовое поле (TEdit) предназначен для организации взаимодействия с пользователем, а именно для приема от него и передачи в программу текстовой строки. Вводимая строка представлена свойством Text. В это же свойство записывается вводимое пользователем значение. Параметры вводимой строки определяются свойствами Maxlength (максимально допустимая длина вводимой строки) и Charcase (регистр вводимых символов). По умолчанию свойство Charcase имеет значение ecNormal, которое означает ввод символов без преобразования. Если выбрано значение eclowerCase, вводимые символы автоматически преобразуются к нижнему регистру. Значение ecUpperCase описывает ввод с преобразованием к верхнему регистру.
Событие OnChange позволяет отреагировать на модификацию строки, представленной свойством Text.
Компонент Меню
Компонент Меню (TMainMenu) невизуальный — он представляет конструктор меню, вызываемый двойным щелчком на экземпляре класса TMainMenu, размещенном на форме.
Выделенный элемент показывает местоположение очередного создаваемо
го или модифицируемого пункта в меню. Текст пункта (свойство Caption) задают
в окне Инспектора объектов, который автоматически настраивается на текущий
пункт меню*.
Если надо создать вложенное меню для данного пункта, надо выбрать в его контекстном меню пункт Create Submenu (Создать подменю).
Каждый пункт меню имеет имя (свойство Name) для обращения к нему в исходном тексте программы (тип пункта меню — TMenultem). Для каждого пункта меню
необходим обработчик, активируемый при его выборе. Для этого надо обратиться к событию OnClick на закладке Events в окне Инспектора объектов или дважды щел
кнуть на пункте меню в окне Проектировщика.
Каждый пункт меню имеет свойство Checked, позволяющее при работе программы помечать этот пункт флажком. В свойство Bitmap загружают картинку, отобра
жаемую вместе с текстом пункта.
Компонент Контекстное меню
Компонент Контекстное меню (TPopupMenu) позволяет расширить пользовательский интерфейс, снабдив элементы управления контекстным меню. При работе приложения контекстное меню вызывается правой кнопкой мыши.
Компонент TPopupMenu так же, как и TMainMenu, невизуальный и проектируется схожим способом. Однако он не присваивается всей форме, а привязывается к конкретному элементу управления через его свойство PopupMenu. Значением этого свойства должно быть контекстное меню, имя одного из экземпляров класса TPopupMenu, представленных на форме.
Компонент Текстовая область
Компонент Текстовая область (ТМето) расширяет возможности компонента TEdit, позволяя вводить и редактировать не одну строку, а несколько. Главное свойство данного компонента, Lines, имеет тип TStrings (список строк). В нем хранится список строк, введенных пользователем.
При выделении фрагмента текста в текстовой области в свойство SelStart записывается позиция первого выделяемого символа, а в свойство SelLength — число выделенных символов. Сам выделенный текст хранится в свойстве SelText (тип string). Для выделения всего текста применяется метод SelectAII, для удаления выделенного текста — метод ClearSelection.
Выделенный текст помещают в буфер обмена Windows вызовом метода CopyToClipboard. Вставить текст из буфера в компонент можно вызовом метода PasteFromClipboard.
Чтобы очистить содержимое текстовой области, надо обратиться к методу Clear, а чтобы отменить последние внесенные в него изменения, надо вызвать метод Undo.
Когда текст в текстовой области изменяется, генерируется событие OnChange.
Компонент Флажок
Компонент Флажок (TCheckBox) может находиться в одном из двух состояний: включенном или выключенном. Подпись к этому элементу управления задается свойством Caption, а ее расположение определяется значением свойства Alignment. Так, значение taRightJustify означает расположение подписи справа от значка флажка, а значение taLeftJustify — слева.
Главное свойство флажка называется Checked. Оно принимает значение True, если флажок включен, и значение False, если флажок выключен. Флажки могут находиться в промежуточном, третьем состоянии, хотя используется оно редко.
В таком состоянии признак установки флажка (галочка) отображается приглушенным цветом. Такая возможность обычно используется, чтобы сообщить пользователю о неполном соответствии определенного признака нужному статусу (например, если выбрана лишь часть возможностей из предложенных). Если для свойства AllowGrayed задано значение True, то флажок при последовательных щелчках на нем будет поочередно принимать значения «сброшен», «установлен частично», «установлен». Текущее состояние (из трех возможных) определяется свойством State, это же свойство позволяет изменить состояние флажка программно.
Событие OnClick позволяет реагировать на изменение состояния флажка.
Компонент Переключатель
Компонент Переключатель (TRadioButton) предназначен для выбора одного значения из ряда возможных. Переключатели всегда используются группами. В группе переключателей всегда либо выбран только один из них, либо не выбрано ни одного. Поэтому на форме всегда располагают несколько компонентов TRadioButton.
Свойство Alignment для переключателя (как и в случае флажка) определяет положение подписи справа или слева от значка переключателя. Сама подпись задается свойством Caption. Свойство Checked определяет состояние переключателя — оно имеет значение True, если переключатель включен.
Компонент Группа переключателей
Если на форме имеется несколько переключателей TRadioButton, все они считаются принадлежащими к одной группе. При выборе одного из них ранее установленный переключатель отключится. Поэтому с помощью компонента TRadioButton сложно организовать на форме несколько отдельных групп переключателей. Для создания подобной самостоятельной группы предназначен компонент Группа переключателей (TRadioGroup).
Для создания переключателей, входящих в данный компонент, необходимо задать их имена. Список имен вводится в свойство Items (список строк, тип TStrings).
Для каждого имени автоматически создается отдельный переключатель.
Компонент TRadioGroup представляет одно целое со своим содержимым, поэтому его используют не так, как компонент TRadioButton. Свойство Caption задает имя всей группы, которое показано на рамке, окружающей переключатели. Свойство Columns задает число столбцов (по умолчанию один) для размещения переключателей. Свойство Itemindex содержит номер выбранного переключателя. Нумерация начинается с нуля, а значение -1 показывает, что ни один из переключателей не выбран. Данное свойство разрешается менять программно, что приводит к изменению состояния группы переключателей.
Обработчик события OnClick реагирует в группе на выбор нового переключателя.
Компонент Список
Компонент Список (TListBox) отображает в окне список строк и позволяет выбрать (выделить в списке) одну или несколько из них. Содержимое списка хранится в свойстве Items (список строк, тип TStrings). Строки можно ввести в данный компонент на этапе проектирования формы с помощью текстового редактора, вызываемого при обращении к свойству Items. Можно заполнить список и во время работы программы вызовом метода Add класса TStrings. Для отображения элементов списка в отсортированном порядке в свойство Sorted надо занести значение True.
Метод Clear удаляет все строки списка. Удаление одной строки выполняет метод DeleteString, номер удаляемой строки передается в качестве параметра метода.
Номер выделенного элемента списка хранится в свойстве Itemindex (нумерация начинается с нуля). Значение -1 показывает, что ни один элемент списка не выбран.
Выбирать элементы в списке можно по одному или по несколько сразу. Множественный выбор разрешает значение True в свойстве MultiSelect. Свойство SelCount, доступное только для чтения, содержит число выделенных строк. Чтобы определить, выделен ли конкретный элемент в списке, надо обратиться к свойству списка Selected (массиву) с номером интересующего элемента. Это свойство хранит логические значения, поэтому следующее обращение вернет значение True, если выделен шестой элемент списка (нумерация начинается с нуля):
ListBoxl->Selected[5]
Списки столь часто используют в пользовательских интерфейсах, что для них введен особый стиль отображения (свойство Style). По умолчанию он равен значению IbStandard, но его можно сменить на значения IbOwnerDrawFixed или IbOwnerDrawVariable (каждый элемент списка имеет, соответственно, фиксированную или переменную высоту). При этом программист должен самостоятельно запрограммировать отображение на экране каждой строки списка. Для этого обрабатывают событие OnDrawltem. Параметры обработчика определяют номер рисуемого элемента (Index), координаты прямоугольника, в котором надо уместить рисуемый элемент (Reel), и состояние (State) элемента (выделен/не выделен).
Компонент Поле со списком
Компонент Поле со списком (TComboBox) представляет собой вариант списка с дополнительным присоединенным полем, в котором отображается выбранный элемент.
Это поле может использоваться для ввода новых элементов в список, для быстрого поиска элементов и так далее. Компонент TComboBox работает в одном из трех состояний, в зависимости от значения свойства Style.
csDropDown. В присоединенное поле можно вводить значения, отсутствующие в списке. Текст, введенный пользователем, доступен через свойство Text.
csDropDownlist. Выбор элемента в списке происходит только на основе имеющегося списка значений. Список представлен как раскрывающийся.
csSimple. Аналогично csDropDownlist, но список представлен полностью, без сворачивания.
Элементы списка хранятся в свойстве Items, максимальное число отображаемых в видимой части списка элементов задается свойством DropDownCount. Чтобы раскрыть список программно, надо присвоить свойству DroppedDown значение True.
Понятия выделенной строки в раскрывающемся списке нет. Вместо него введено понятие текущей строки, номер которой хранится в свойстве Itemindex.
Если текст строки списка изменен пользователем, генерируется событие OnChange.
Компонент Полоса прокрутки
Полосы прокрутки (компонент TScrollBar) обычно используются как вспомогательные инструменты в других элементах управления: списках, текстовых и графических областях и прочих. Однако их можно применять и как самостоятельные элементы, например в качестве движка для приблизительной установки значении или для прокрутки областей с нестандартным содержимым.
Диапазон значений, охватываемых полосой прокрутки, вводится в свойства Mir (минимальное значение) и Мах (максимальное значение). Текущая позиция ползунка определяется свойством Position. Его значение должно лежать в диапазоне от Min до Мах. При перемещении ползунка значение этого свойства меняется автома тически, но можно модифицировать его и программно. Тогда ползунок сам сме стится в нужную позицию. При изменении значения свойства Position всегда гене рируется событие OnChange.
Если прокрутка выполняется с помощью курсорных клавиш или щелчкам! на конечных кнопках полосы прокрутки, значение свойства Position меняется (г любую сторону) на величину, указанную в свойстве SmallChange. Если щелкнут] на свободной области полосы прокрутки или нажать клавишу PAGE UP или PAGE DOWN (листание страниц), то значение свойства Position изменится на величину указанную в свойстве LargeChange.
Вид полосы прокрутки задается свойством Kind. Оно принимает значеню sbHorizontal (горизонтальная полоса прокрутки) или sbVertical (вертикальная поло са прокрутки).
Компоненты группы Win32
В этой группе доступны компоненты, специфичные для интерфейса 32-разряд- ных версий Windows. Отметим в этой группе компонент TPageControl (набор страниц), который часто применяется в современных программах. По такому принципу работает большинство диалоговых окон настройки. Этот компонент удобен, так как позволяет экономить экранное пространство, неограниченно увеличивая его «в глубину».
Компонент Набор страниц
Компонент Набор страниц представляет собой набор совмещенных страниц с элементами управления. Доступ к определенной странице осуществляется через так называемые корешки.
Корешок — это содержащий короткое название выступ над страницей.
Первоначально компонент TPageControl, помещенный на форму, пуст и не содержит ни одной страницы. Новая страница добавляется командой New Page (Создать страницу) его контекстного меню. При этом на форме в рамках набора страниц появится новый объект — экземпляр класса TTabSheet, представляющий отдельную страницу. Текст корешка страницы вводят в свойство Caption нужного экземпляра TTabSheet (но не всего компонента TPageControl!).
На каждой из страниц размещают свой набор элементов управления, который отображается при выборе данной страницы. Смену страниц компонента можно выполнять непосредственно в окне Проектировщика. Для программного открытия той или иной страницы надо ввести порядковый номер страницы (нумерация начинается с нуля) в свойство ActivePagelndex.
?9.3. Компоненты группы System
В группу System вошли компоненты, стыкующие программу с системными механизмами Windows. Среди них наиболее важен компонент TTimer — он позволяет программировать процессы, развивающиеся во времени.
Компонент Таймер
Таймер (компонент TTimer) задействуется, когда в программе выполняется временной процесс: моделирование физического объекта, формирование анимированного изображения, контроль состояния ресурса (например, подключения к Интернету) или иной. Следить за временем необходимо так, чтобы не отвлекать ресурсы процессора на непроизводительный временной контроль. Оптимальный способ решения этой проблемы — использование стандартного таймера системы Windows.
Таймер — это системная функция Windows, которая после активизации с заданной периодичностью посылает приложению системное сообщение WMTIMER.
Сообщения от таймера имеют минимальный приоритет, поэтому они принимаются и обрабатываются, только если нет других, более важных сообщений.
Обработку сообщения WM_TIMER выполняет компонент TTimer. Он невизуальный и имеет два свойства, полезных для прикладного разработчика.
Enabled. Если свойство Enabled имеет значение True, таймер включен и обрабатывает сообщения WM_TIMER, передавая их прикладной программе.
Interval. Свойство Interval задает интервал срабатывания (периодичность генерации сообщения WM_TIMER) в миллисекундах.
Обрабатывать надо единственное событие данного компонента OnTimer. Оно возникает при каждом срабатывании таймера, то есть через одинаковые промежутки времени, указанные в свойстве Interval. Между срабатываниями таймера ресурсы процессора не используются.
ПРИМЕР 29.1
•Компьютерные часы
Рассмотрим простейший вариант компьютерных часов, основанный на компоненте TTimer.
Разместим на форме компонент TTimer и надпись TLabel.
Установим периодичность срабатывания таймера: введем в свойство Interval компонента TTimer значение 1000 (одна секунда).
Активизируем таймер, для чего введем значение True в свойство таймера Enabled.
Создадим обработчик события OnTimer для таймера: дважды щелкнем на экземпляре класса TTimer на форме.
Функцией обработчика будет запись в надпись Label 1 текущего времени. Оно определяется стандартной функцией Time() и преобразуется в текстовый вид стандартной функцией TimeToStr().
void fastcall TForml:zTimerlTimer(TObject ‘Sender)
{
TDateTime T = Timet);
Labell->Caption = TimeToStr(T);
}
Запустим программу. В ее окне раз в секунду будет выводиться текущее время. В результате возникает эффект идущих часов.
Обработка щелчка мыши
В программах нередко используется щелчок мыши в произвольной области формы (в дополнение к стандартным действиям по использованию элементов управления). Подобные щелчки востребованы, например, при создании оригинального интерфейса управления. Так, щелчок на картинке может имитировать нажатие кнопки. При этом требуется проанализировать координаты точки щелчка и проверить, произведен ли щелчок в заданной области окна.
Событие OnClick (щелчок) по умолчанию предназначено для выполнения стандартного действия. Так, щелчок на флажке приводит к смене его состояния, на пункте меню — к его выбору, на кнопке — к ее нажатию. Среда C++Builder позволяет
отслеживать еще два события: OnMouseDown (нажатие кнопки мыши) и OnMouseUp (отпускание кнопки мыши). Списки параметров обработчиков этих событий одинаковы:
void fastcall TForml::FormMouseDown(TObject ‘Sender, TMouseButton Button, TShiftState Shift, int X, int Y) ;
Среди них:
параметр Button, показывающий, какая кнопка мыши нажата (mbLeft — левая, mbRight — правая, mbMiddle — средняя);
параметр Shift, позволяющий определить, были ли дополнительно нажаты управляющие клавиши;
параметры X и Y — координаты точки щелчка в клиентской области формы.
Вопросы
Как использовать в программе компонент Надпись?
Как использовать в программе компонент Текстовое поле?
Как использовать в программе компонент Текстовая область?
Как в программе создается меню?
Как в программе создается контекстное меню?
Как использовать в программе компонент Флажок?
Как использовать в программе компонент Переключатель?
Как добавить на форму несколько групп переключателей?
Как использовать в программе компонент Список?
Как использовать в программе компонент Поле со списком?
И. Как использовать в программе компонент Полоса прокрутки?
Как использовать в программе компонент Набор страниц?
Как создать приложение, выполняющее определенные действия с заданной периодичностью?
Как узнать координаты точки щелчка мыши?
ГЛАВА 30
Технологии работы с графикой и мультимедиа
Графический интерфейс современных программ часто предполагает, что изображение, представленное на экране, строится самой программой. Система C++Builder содержит набор средств, обеспечивающих генерацию и вывод на экран графической и другой мультимедийной информации.
Работа с графикой
Работа с графикой подразумевает подготовку и вывод на экран нужных изображений. В среде C++Builder процесс рисования обычно представлен как применение инструментов рисования в рамках специального объекта — холста.
Понятие холста
Технология вывода графической информации на экран основывается в системе C++Builder на понятии холста (класс TCanvas).
Холст — прямоугольная область для рисования изображений, представленная на форме приложения.
Изображения рисуются как поточечно, обращением к отдельным точкам холста, так и с помощью простых функций рисования (методов класса TCanvas), предназначенных для создания линий, кругов, прямоугольников и других графических примитивов.
Формы проектов VCL и элементы управления (например, списки) имеют свойство Canvas (экземпляр класса TCanvas), которое позволяет формировать графические изображения. По умолчанию область холста для формы совпадает с ее клиентской областью.
Клиентская область формы (элемента управления) — это часть формы (элемента управления), не занятая заголовком, меню, строкой состояния и границами окна.
Работа с холстом подразумевает использование графического курсора.
Графический курсор — это условная, виртуальная точка, определяющая место начала очередной графической операции (например, исходную позицию, из которой будет рисоваться линия).
Принцип отрисовки изображений в C++Builder
Нарисованное на холсте изображение отображается на нем до тех пор, пока область холста не закрыта другим приложением (возможно, частично) или пока окно не свернуто. При новом появлении области холста на экране изображение, ранее нарисованное на нем, само по себе не восстанавливается.
Чтобы нарисованное на холсте изображение сохранялось и восстанавливалось при любых обстоятельствах, следует создать в программе обработчик системного сообщения WM_PAINT. Это сообщение автоматически посылается программе операционной системой и содержит указание на то, какие области окна требуется перерисовать. Сообщение передается непосредственно объекту, который нуждается в перерисовке. В системе C++Builder соответствующее событие называется OnPaint.
Все действия по отрисовке содержимого холста желательно поместить в данный обработчик. Он автоматически вызывается, когда возникает необходимость обновить содержимое холста.
Технология графического вывода
Изображение на холсте формируется в три шага.
Создается обработчик события OnPaint для формы или элемента управления, холст которого задействуется.
Выделим на форме объект с холстом.
Перейдем на закладку Events в окне Инспектора объектов.
Дважды щелкнем на строке OnPaint.
Выполняется настройка инструментов, необходимых для создания изображения.
В код обработчика добавляются обращения к методам рисования графических примитивов. С их помощью рисуются линии, эллипсы, прямоугольники и другие фигуры.
Основные графические операции
При рисовании последовательность действий и набор выполняемых операций не зависит от того, какое именно изображение создается. Различия состоят лишь в конкретном наборе операторов, выполняющих рисование.
Подготовка холста
Любая форма содержит холст (свойство Canvas), однако удобнее пользоваться компонентом TPaintBox (область рисования) из группы System палитры инструментов. Он предназначен для того, чтобы играть роль холста. Этот компонент размещают в области формы, отводимой разработчиком под вывод изображения.
В отсутствие компонента TPaintBox потребуется вручную рассчитывать на форме координаты области холста, задействованной для формирования изображения. Для добавленного компонента TPaintBox необходимо подготовить обработчик события OnPaint.
Использование инструментов рисования
К основным инструментам, применяемым для создания изображения, относятся:
Карандаш (свойство Реп холста, класс ТРеп), предназначенный для рисования линий и точек;
Кисть (свойство Brush холста, класс TBrush), предназначена для заполнения (закрашивания) однородных областей холста определенным цветом или рисунком по заданному шаблону;
Шрифт (свойство Font холста, класс TFont), предназначенный для настройки операций вывода текста.
Основными характеристиками Карандаша являются цвет, которым выводятся точки и линии (свойство Color класса ТРеп), и ширина рисуемой точки или линии в пикселах (свойство Width). Например:
void fastcall TForml::PaintBoxlPaint
(TObject ‘Sender)
PaintBoxl->Canvas->Pen->Color = clGreen;
PaintBoxl->Canvas->Pen->Width = 3;
PaintBoxl->Canvas->Brush->Color = clRed;
}
Свойство Color (Цвет) имеется у многих элементов управления.
Оно имеет тип TColor. Стандартные библиотеки C++Builder предлагают набор предопределенных цветовых констант, имена которых начинаются с префикса cl. Так, зеленый цвет запишется как clGreen. Для программного формирования цветового оттенка служит функция RGB, возвращающая значение типа TColor. В нее следует передать три параметра: интенсивности красного, зеленого и синего компонентов цвета, заданные числом от 0 до 255. Например, получить чистый зеленый цвет позволяет обращение RGB(0,255,0).
Координаты объектов задают с помощью стандартного типа TRect (прямоугольник). В нем имеются свойства, определяющие правую (свойство Right), левую (Left), верхнюю (Тор) и нижнюю (Bottom) границы области, в которой изображается объект.
Отображение графического прямоугольника на холст выполняется вызовом метода холста Rectangle. Параметром этого метода выступает прямоугольник (представитель типа TRect). Например:
void fastcall TForml::PaintBoxlPaint (TObject ‘Sender)
TRect rc;
// задаем цвет, которым
// будет рисоваться прямоугольник:
PaintBoxl->Canvas->Pen->Color = clGreen;
PaintBoxl->Canvas->Pen->Width = 3;
PaintBoxl->Canvas->Brush->Color = clRed;
// области вывода прямоугольника:
re.Left = 5;
rc.Right = 100;
гс.Тор = 0 ;
rc.Bottom = 50;
// рисуем прямоугольник:
PaintBoxl->Canvas->Rectangle(rc);
Если теперь запустить программу, то она нарисует красный прямоугольник с толстой зеленой каймой. При этом окно можно закрывать и открывать, сворачивать и разворачивать — изображение всегда отображается корректно.
Заполнение прямоугольника определяется параметрами кисти. Для закраски можно использовать и заранее подготовленное точечное изображение. Его следует присвоить свойству Bitmap Кисти.
Вывод текста
Для вывода текста предназначен метод холста TextOut(). В качестве его параметров передают координаты (левый верхний угол) области вывода текста и саму отображаемую строку:
PaintBoxl->Canvas->TextOut(20,30,"Привет!");
Настройки используемого шрифта (цвет Color, размер Size, набор символов Charset, стиль Style и другие) задают в подсвойствах свойства холста Font. Например:
' PaintBoxl->Canvas->Font->Color = clBlue;
PaintBoxl->Canvas->Font->Size = 14;
PaintBoxl->Canvas->Font->Style =
TFontStyles!) << fsBold;
PaintBoxl->Canvas->TextOut(20,30,"Привет!");
Рассмотрим более подробно оператор из этого текста, задающий стиль шрифта:
PaintBoxl->Canvas->Font->Style = TFontStyles() << fsBold;
Подсвойство Style объекта Font (Шрифт) представляет собой множество — неупорядоченный набор объектов типа TFontStyle, каждый из которых определяет ту или иную характеристику шрифта. В C++ нет явной поддержки множеств, они реализуются с помощью шаблонов (см. главу 20). Операция включения элемента в множество « может использоваться несколько раз подряд — делаем шрифт полужирным курсивным:
PaintBoxl->Canvas->Font->Style =
TFontStyles() << fsBold << fsltalic;
Значения заносятся не напрямую в свойство Style, а во временный объект (он конструируется командой TFontStyles()). При попытке занести значения настроек в Style будет автоматически создан временный объект, который уничтожится по окончании операции, и прежнее значение свойства Style не изменится.
Точечные графические изображения
Работа с точечными графическими изображениями выполняется в системе C++Builder с помощью класса TBitmap (определен в модуле Graphics). В экземпляр этого класса можно загружать растровое изображение из заранее подготовленного файла (с расширением имени .BMP). Подготовленное изображение можно вывести в нужную позицию холста. В классе TBitmap имеется собственное свойство Canvas, позволяющее рисовать в пределах точечного изображения. Это позволяет строить изображение в невидимом для пользователя режиме, выполняя графические операции на внутреннем холсте точечного объекта, который еще не показан на экране.
Подготовку точечного изображения удобнее всего выполнять с помощью компонента Tlmage из группы Additional палитры компонентов. В нем имеется свойство Picture, представитель класса TPicture, предназначенного, в свою очередь, для хранения изображений произвольных типов, не только точечных. У свойства Picture есть свойство Bitmap (экземпляр класса TBitmap).
При обращении к свойству Picture компонента Tlmage в Инспекторе объектов вызывается Загрузчик изображений, который позволяет выбрать графический файл и загрузить выбранное изображение с помощью кнопки Load (Загрузить). Загруженное изображение отображается в компоненте Tlmage, размещенном на форме2.
Свойство AutoSize включает автоматическую подстройку размеров компонента под размер изображения.
Принципы компьютерной анимации
На основе экземпляров класса TBitmap можно создавать статические графические программы произвольной сложности и даже формировать простые анимационные последовательности. Для этого применяется технология, характерная для систем двумерной компьютерной мультипликации.
Анимация — это последовательность кадров (изображений), динамически воспроизводимых через короткие интервалы времени.
Отдельный кадр анимации формируется следующим образом:
Очищаем холст, на котором воспроизводится анимация.
Выводим на холст неподвижный фоновый рисунок (статическое изображение).
Поверх статического изображения рисуем отдельные движущиеся части изображения. Если они должны перемещаться по экрану, то позиции вывода этих фрагментов меняются.
При быстром выполнении такой последовательности и смены кадров проявится полноценный эффект мультипликации. В современных графических приложениях используется дополнительный внутренний буфер — второй, невидимый холст, представляющий собой копию основного холста, на которой производит-
С помощью компонента Tlmage удобно, например, создавать на форме фоновые рисунки.
Элементы управления размещают непосредственно на поверхности экземпляра Tlmage.
ся рисование нового кадра. Когда изображение во внутреннем буфере построено, содержимое буфера целиком выводится на экран. Если внутренний буфер не задействован, этапы очистки кадра и его поэлементной перерисовки заметны на глаз, что создает некомфортное ощущение «рваной» анимации.
Спрайты
При двумерной анимации динамические элементы принято называть спрайтами. Спрайт — это логически целый, движущийся на экране элемент изображения.
Спрайт — это частично прозрачное изображение или набор изображений, который может накладываться и перемещаться по фону или по другому спрайту без необходимости перерисовывать все основное окно.
Отдельные динамические элементы (спрайты) отображаются прозрачным образом, а не просто накладываются поверх фона, закрывая его. Рисуются лишь те точки спрайта, которые считаются «непрозрачными», что и позволяет добиться эффекта прозрачности и показывать объекты произвольной формы (не только прямоугольной). Простейший способ создания спрайтов, содержащих прозрачный, не отображаемый на холсте цвет, — это использование так называемых битовых масок.
Битовая маска — это копия рисуемого изображения, в которой его прозрачные и непрозрачные точки помечены разными цветами. В качестве битовой маски часто выступает черно-белое изображение. Белыми точками помечены рисуемые области, черными — прозрачные.
Битовая маска строится на базе исходного изображения — в ней разделяются все видимые и все прозрачные точки. Для этого на каждую точку достаточно отвести один бит, признак прозрачности. Кроме черно-белых применяются битовые маски, в которых нет однозначного разделения на видимые и невидимые пикселы. Вместо этого введено понятие альфа-канала — степени прозрачности каждой точки. Результирующие пикселы изображения не просто отображаются в режиме «виден — не виден», а выводятся с учетом индивидуальной прозрачности, которая плавно колеблется в диапазоне от 0% (не виден совсем) до 100% (виден полностью и закрывает фоновый пиксел). Для промежуточных значений альфа-канала цвет фонового и спрайтового изображений комбинируется по одному из типовых алгоритмов.
Затем эта маска накладывается на фоновое изображение, стирая на нем лишь те точки, которые считаются непрозрачными. Далее поверх такой промежуточной растровой структуры выводится основной рисунок — в специальном режиме комбинирования точек, когда меняются (перерисовываются) лишь те точки, которые были предварительно затерты битовой маской.
ПРИМЕР 30.1
■ ■ - • • ' - * = - Создание спрайтовой анимации
Рассмотрим технологию создания анимации на простом примере. Данный пример чисто демонстрационный. Для создания профессиональных и быстро работающих графических приложений служат графические библиотеки DirectX, OpenGL и им подобные. Анимация, созданная описываемым здесь способом, работает слишком медленно.
Пусть имеется фоновое изображение, записанное в файл fon.bmp (например, вид леса) и анимационный элемент (колобок), записанный в файл kolobok.bmp. Изображение «колобка» состоит в свою очередь из двух «склеенных» кадров равной ширины, которые должны последовательно сменять друг друга (например, колобок шагает). Мы хотим показать анимированного колобка (изображение с поочередно сменяющими друг друга кадрами) на фоне статического изображения леса.
Подготовка формы.
Создадим новое приложение, разместим на форме компоненты, отвечающие за отображение графики, и подготовим элементы программы, используемые при отображении спрайтов.
Создадим пустое приложение командой File > New > VCL Forms Application - C++Builder (Файл > Создать > Приложение VCL Forms).
Изменим размер формы таким образом, чтобы он соответствовал размеру выбранного фонового изображения.
Добавим в класс формы две переменные Ы и Ь2 (класс TBitmap) для хранения, соответственно, фонового изображения и изображения с двумя кадрами колобка.
class TForml : public TForm
{
public:
fastcall TForml(TComponent* Owner);
GraphicsTBitmap *bl, *b2 ;
) ;
Сформируем обработчик создания формы — дважды щелкнем на свободном пространстве формы.
В этом обработчике создадим графические объекты (экземпляры класса TBitmap), которые будут храниться в переменных Ы и Ь2. Загрузим в них изображения из файлов с помощью метода LoadFromFile.
void fastcall TForml::FormCreate(TObject ‘Sender)
Ы = new Graphics;:TBitmap();
bl->LoadFromFile("fon.bmp");
b2 = new Graphics::TBitmap();
b2->LoadFromFile("kolobok.bmp");
}
Вывод фона.
Фоновое изображение остается неизменным, однако его необходимо восстанавливать при сворачивании и разворачивании окна.
Создадим обработчик события OnPaint для формы. Выберем форму в Инспекторе объектов, перейдем на закладку Events и дважды щелкнем на строке OnPaint. В этом обработчике мы реализуем весь процесс вывода анимационной композиции.
Выведем фоновое изображение в нужную точку холста формы с помощью метода Draw() холста. Этот метод имеет три параметра: две координаты выводимого рисунка на форме и ссылку на графический объект, содержащий изображение.
void fastcall TForml::FormPaint(TObject ‘Sender) {
Canvas->Draw(10,10,Ы);
Вывод спрайта.
На статическом фоне надо изобразить колобок. Он должен выглядеть как кружок, наложенный на фоновое изображение, и не закрывать фон в тех местах, где колобка нет. Простым копированием растрового изображения на холст добиться этого невозможно — все изображения (экземпляры классов TBitmap и TPicture) имеют прямоугольную форму и при наложении закрывают точки фона по всему пространству прямоугольника. С их помощью не удастся вывести круглое изображение колобка.
Определим пространство вывода спрайта. Пусть изображение с колобком представляет собой квадрат размером 50x50 точек. Координаты области вывода для удобства зададим константами — они понадобятся неоднократно. Пусть эти координаты соответствуют координатам вывода фонового изображения — то есть, колобок должен быть показан в левом верхнем углу фонового рисунка.
Для холста также надо задать способ взаимодействия точек исходного и целевого изображений. В соответствующее свойство холста CopyMode заносится значение cmSrcCopy — все точки целевого изображения (холста формы) заменяются на точки исходного изображения Ы.
const rcLeft = 10;
const rcTop = 10;
Canvas->CopyMode = cmSrcCopy;
Canvas->Draw(rcLeft,rcTop,Ы) ;
Определим в обработчике две переменные класса TRect для хранения координат вывода изображений с исходного холста (внутреннего холста объекта Ь2, на котором записаны два кадра с колобком) на целевой холст формы.
const rcLeft = 10;
const rcTop = 10;
TRect rcdst, rcsrc;
Canvas->CopyMode = cmSrcCopy;
Canvas->Draw(rcLeft,rcTop,Ы);
rcdst.Left = rcLeft; rcdst.Top = rcTop;
rcdst.Right = rcLeft+50; rcdst.Bottom = rcTop+50;
rcsrc.Left = 0; rcsrc.Top = 0;
rcsrc.Right = 49; rcsrc.Bottom = 49;
Переменная rcdst задает прямоугольную область для выводимого рисунка на форме. Переменная rcsrc определяет прямоугольник на исходном изображении колобка, хранящемся в объекте Ь2, который следует скопировать в позицию, заданную прямоугольником rcdst. Исходно выбран первый кадр, левая половина двухкадрового изображения шириной 100 точек.
Декларируем в классе TForml переменную MaskBitmap для битовой маски:
Graphics::TBitmap *Ы, *Ь2;
Graphics::TBitmap ‘MaskBitmap;
Битовая маска будет динамически создаваться на базе исходного изображения Ь2. Для этого создадим экземпляр класса TBitmap и загрузим в него текущее содержимое переменной Ь2. Это выполняется методом Assign() класса битовой маски. Сформируем битовую маску в момент создания формы:
void fastcall TForml::FormCreate(TObject ‘Sender) {
bl = new Graphics::TBitmap();
bl->LoadFromFile("fon.bmp");
b2 = new Graphics::TBitmap();
b2->LoadFromFile("kolobok.bmp");
MaskBitmap = new Graphics::TBitmap();
MaskBitmap->Assign(b2);
}
Зададим в маске прозрачный цвет исходного изображения. Для этого вызовем метод битовой маски Mask(). В качестве параметра необходимо указать цвет, считающийся в исходном изображении прозрачным. Чтобы маска сформировалась правильно, в исходном изображении все прозрачные точки должны быть черными.
MaskBitmap = new Graphics::TBitmap();
MaskBitmap->Assign(Ь2);
MaskBitmap->Mask(clBlack);
Определим в функции перерисовки изображения FormPaint() способ комбинирования рисуемой маски с поверхностью холста. В данном случае нужна логическая операция And (логическое И). Ее обозначение вводят в свойство целевого холста CopyMode.
Canvas->CopyMode = cmSrcAnd;
На холст формы надо скопировать прямоугольную часть битовой маски. Для этого используем метод CopyRect() холста формы. В качестве первого параметра укажем прямоугольник вывода на целевом холсте формы, вторым параметром - прямоугольник, копируемый с битовой маски.
Canvas->CopyRect(rcdst,MaskBitmap->Canvas,rcsrc);
Зададим метод комбинирования цветов исходного и целевого холстов при выводе основного изображения спрайта.
Canvas->CopyMode = cmSrcPaint;
Выполним вывод (копирование) основного изображения спрайта поверх выведенной на холст битовой маски.
Canvas->CopyRect(rcdst,Ь2->Canvas,rcsrc);
Полученный рисунок будет выглядеть так, как задумывалось. Круглый колобок не будет закрывать элементы фона в пределах прямоугольной области вывода за границей круга.
Временная анимация.
Пока что выведенное изображение статично. Требуется, чтобы оно менялось с течением времени.
Добавим на форму компонент Таймер.
Введем в свойство Interval значение 500 — таймер будет срабатывать каждые полсекунды.
Создадим обработчик таймера двойным щелчком на экземпляре компонента TTimer на форме.
Запишем операторы обработчика таймера. В нем необходимо переключать внутренний флажок KolF, определяющий, какое из двух изображений колобка надо показать. Этот флажок инициализируем в момент создания формы.
class TForml : public TForm {
published:
TTimer‘Timerl;
void fastcallFormCreate(TObject ‘Sender);
void fastcallFormPaint(TObject ‘Sender);
void fastcallTimerlTimer(TObject ‘Sender);
public:
fastcall TForml(TComponent* Owner);
Graphics::TBitmap *bl, *b2;
Graphics::TBitmap ‘MaskBitmap;
bool KolF; // флажок для таймера
void fastcall TForml::TimerlTimer(TObject ‘Sender) { // меняем состояние флажка KolF = ! KolF;
// явно вызываем метод // перерисовки изображения FormPaint(Sender);
Модифицируем в обработчике события OnPaint выбор координат изображения спрайта (выбора кадра с колобком) в зависимости от текущего значения перемененной KolF:
if( KolF ) { rcsrc.Left = 0; rcsrc.Top = 0; rcsrc.Right = 49; rcsrc.Bottom = 49;
} else { rcsrc.Left = 50; rcsrc.Top = 0; rcsrc.Right = 50+49; rcsrc.Bottom = 49;
Запустим приложение — изображение колобка будет изменяться два раза в секунДУ-
Подобным же образом реализуется и более сложная анимация. Ключевым в этом процессе остается изменение координат вывода спрайтов для имитации процесса их движения по фону.
Воспроизведение музыкальных и видеоклипов
Для воспроизведения музыкальных и видеофайлов предназначен компонент Мультимедийный проигрыватель (TMediaPleyer) из группы System палитры инстру-
ментов. Он представляет собой программную оболочку, надстройку над стандартным мультимедийным проигрывателем Windows Media Player, и использует его функции для воспроизведения мультимедийной информации через стандартный интерфейс MCI (Media Control Interface).
Управление процессом воспроизведения осуществляется пользователем программы с помощью набора кнопок с типовыми обозначениями, встроенных в данный компонент.
Pro jet: 11 - Borland Developer Studio 2006 - Unitl.cpp [Built: 10.64 secs]
File Edit Search View Refactor Project Run Component Tools StarTeam Window Help |Test Layout
в e* ^ v ^ ' . 4§^,.H§£ Tool Palette■—- ;^’-SystemФ Ulmer ^ TPaintBoxв e* ^ v ^ ' . 4§^,.H§£ Tool Palette■—- ;^’-SystemФ Ulmer ^ TPaintBoxД Object Inspector V X Welcome Page ^Unitl.cpp! MediaPlayer 1 | Properties] Events'Д Object Inspector V X Welcome Page ^Unitl.cpp! MediaPlayer 1 | Properties] Events'* X* X000000ColoredBi[btPlay,btPauf*' DevIceTydtAutoSelect EnabledBi[btPlay,btPau;FileNameMame MediaPlayerlShareabk falseTag :0VisIbleBut [btPlay,btP3Ut;ColoredBi[btPlay,btPauf*' DevIceTydtAutoSelect EnabledBi[btPlay,btPau;FileNameMame MediaPlayerlShareabk falseTag :0VisIbleBut [btPlay,btP3Ut;B|WsuaiCursor crDefaultEnabled ФгиеAll shownB|WsuaiCursor crDefaultEnabled ФгиеAll shown*<i TOleCortalner<$ TCOMAdmlnCatalogЙ TDdeCllentConv% TDdeClientltem@ TDdeServerConv % TDdeServerltem j|*<i TOleCortalner<$ TCOMAdmlnCatalogЙ TDdeCllentConv% TDdeClientltem@ TDdeServerConv % TDdeServerltem j|
в e* ^ v ^ ' . 4§^
,.H§£ Tool Palette
■—- ;^’
-System
Ф Ulmer ^ TPaintBox
в e* ^ v ^ ' . 4§^
,.H§£ Tool Palette
■—- ;^’
-System
Ф Ulmer ^ TPaintBox
Д Object Inspector V X Welcome Page ^Unitl.cpp
! MediaPlayer 1 | Properties] Events'
Д Object Inspector V X Welcome Page ^Unitl.cpp
! MediaPlayer 1 | Properties] Events'
* X
* X
0
0
0
0
0
0
ColoredBi[btPlay,btPauf*' DevIceTydtAutoSelect EnabledBi[btPlay,btPau;
FileName
Mame MediaPlayerl
Shareabk false
Tag :0
VisIbleBut [btPlay,btP3Ut;
ColoredBi[btPlay,btPauf*' DevIceTydtAutoSelect EnabledBi[btPlay,btPau;
FileName
Mame MediaPlayerl
Shareabk false
Tag :0
VisIbleBut [btPlay,btP3Ut;
B|Wsuai
Cursor crDefault
Enabled Фгие
All shown
B|Wsuai
Cursor crDefault
Enabled Фгие
All shown
*<i TOleCortalner
<$ TCOMAdmlnCatalog
Й TDdeCllentConv
% TDdeClientltem
@ TDdeServerConv % TDdeServerltem j|
*<i TOleCortalner
<$ TCOMAdmlnCatalog
Й TDdeCllentConv
% TDdeClientltem
@ TDdeServerConv % TDdeServerltem j|
Puc. 30.1. Размещаем на форме мультимедийный проигрыватель
Видимые кнопки данного компонента выбирают настройкой составного свойства VisibleButtons. Чтобы сделать невидимым весь компонент, введите в свойство Visible значение False. После этого управлять работой проигрывателя можно только программно, через его свойства и методы.
Имеется два способа выбора типа воспроизводимого мультимедийного файла. Его можно явно задать свойством DeviceType (тип устройства) или определить автоматически, указав внешний мультимедийный файл. Путь поиска для внешнего файла задают свойством FileName. В этом случае значением типа устройства должна быть константа dtAutoSelect (автоматическое определение типа в зависимости от расширения файла). После того как файл загружен в проигрыватель, его воспроизведение начинают вызовом метода Play(). Например:
MediaPlayer.l->FileName = "song.mp3",-
MediaPlayerl->Play () ;
Если во время воспроизведения файла работа программы продолжается, говорят, что воспроизведение идет в фоновом режиме. Фоновый режим воспроизведения используется, если свойство Wait (ожидание) проигрывателя имеет значение false.
MediaPlayerl->FileName = "song.mp3";
MediaPlayerl->Wait: = false;
MediaPlayerl->Play();
x = 1; // продолжаем вычисления уже на фоне музыки
Фоновое воспроизведение прерывают обращением к методу Stop().
// Закончить воспроизведение
// мультимедийного файла:
MediaPlayerl->Stop();
Если свойство Wait имеет значение true, программа после выполнения команды проигрывателя Play приостанавливает работу и ожидает завершения воспроизведения.
MediaPlayerl->FileName = "song.mp3";
MediaPlayerl->Wait = true;
MediaPlayerl->Р1ау(); // ждем окончания воспроизведения...
// продолжаем вычисления только после завершения музыки:
X = 1;
Точки мультимедийного клипа, в которых начинается и заканчивается воспроизведение, определяются свойствами StartPos и EndPos. Условные временные единицы для клипа заданы свойством TimeFormat. Значение tfMilliseconds определяет в качестве такой единицы миллисекунды, а значение tfFrames — кадры. Длина клипа в выбранных единицах задана свойством Length, текущая позиция — свойством Position. Для продвижения по клипу вперед и назад используется метод Step(), который смещает текущую позицию в клипе на число единиц, указанных в свойстве Frames.
Если проигрыватель должен воспроизводить видеоинформацию, необходимо указать область формы, в которой должен демонстрироваться видеоклип. Удобнее всего задействовать для этого компонент TPanel (Панель). Ссылку на такой вспомогательный компонент задают в свойстве Display проигрывателя. После этого указывают файл с видеозаписью (например, с расширением .AVI) в свойстве FileName и вызывают метод Р1ау().
Вопросы
Что такое холст?
Какое событие надо обрабатывать в программе для корректной поддержки процесса отрисовки изображений?
Как настроить для рисования карандаш и кисть?
Как на холст выводится текстовая информация?
Из каких шагов складывается процесс компьютерной анимации?
Какой компонент предназначен для воспроизведения мультимедийных файлов?
Как программно организовать воспроизведение видеофайла?
ГЛАВА 31 Технологии информационной безопасности
Любая компьютерная система уязвима. Она всегда содержит определенные недоработки, которые либо позволяют несанкционированно вмешиваться в ее работу, либо приводят к отказу от функционирования под влиянием внешних факторов. С одной стороны, пользователям надо обеспечить максимально широкий доступ к возможностям системы в рамках их полномочий. С другой стороны, наилучшей защитой системы будет полное отключение от нее всех пользователей, хотя и в такой ситуации остается вероятность сбоя оборудования и порчи информации.
Компьютерная безопасность (нередко используется более общий термин информационная безопасность) — это наука, которая изучает риски, связанные с использованием компьютеров.
Так как добиться абсолютной защиты системы невозможно, необходимо каким- то образом оценивать текущий уровень ее безопасности. Попытки выразить этот уровень количественно, например в процентах, успехом не увенчались, так как безопасность системы составляется из множества плохо сравнимых друг с другом элементов, что не позволяет представить ее в виде единой интегральной оценки. Поэтому в мировой практике принято оценивать соответствие системы одному из предопределенных классов безопасности — по набору предопределенных критериев. Соответствующих стандартов немало — это, например, общие критерии информационной безопасности и методы защиты ISO 15408; способы управления информационной безопасностью и анализ рисков ISO 17799; порядок создания защищенных автоматизированных систем ГОСТ 51583-00 и ГОСТ Р 51624-00. Они подразумевают проведение по определенным правилам аудита безопасности, который выполняют независимые сертифицированные структуры, и выработку по его результатам рекомендаций по улучшению системы информационной защиты.
Имеются отечественные стандарты Государственной технической комиссии РФ, которые ориентированы на сертификацию безопасности конкретных программных продуктов и оборудования, а вот стандарты ISO оценивают всю систему целиком, включая программы, аппаратную составляющую, а также средства и каналы связи.
При сертификации по каждому из подобных стандартов необходимо проверить все механизмы защиты, выполнить множество тестов, детально проанализировать исходные тексты программ с привлечением квалифицированных экспертов. Поэтому сертифицированные системы компьютерной защиты относятся к разряду самых сложных и дорогостоящих в производстве и доводке до рынка. Базовые
принципы их построения хорошо известны, весьма просты и не представляют особого труда в плане программирования. Сложность заключается в их эффективной реализации, ведь требуется удержать общую производительность защищаемой операционной системы и приложений. Проблемы с защитой нередко возникают и из-за наличия в системе так называемых скрытых каналов (covert channels), когда несколько процессов взаимодействуют способом, нарушающим политику безопасности, например, в заголовках сетевых пакетов из-за исходной незащищенности протокола можно размещать стороннюю информацию. Продуманно учесть все скрытые каналы и составить их полный перечень в каждом конкретном случае крайне сложно.
Модели защиты
Решения для защиты компьютерных сетей не всегда основываются на математических и кибернетических научных исследованиях. Однако подходы к компьютерной безопасности традиционно вызывали и вызывают интерес у ученых, поэтому имеется немало теоретических наработок в этой сфере, на базе которых создавались успешные системы информационной защиты. Построение такой системы обычно начинается с выбора или выработки модели безопасности, которая систематизирует и структурирует представление о возможных рисках и угрозах и позволяет заранее изучить и оценить последствия тех или иных решений. Недостаток модельного подхода заключается в ограниченности взгляда на компьютерную систему, который связан с ограниченностью любой модели.
Одна из старейших моделей безопасности информационной системы — модель Белла-Лападулы — была предложена в 1973 году и предназначалась для развития подходов, принятых в Министерстве обороны США. Она задает набор правил доступа к данным, и обеспечивает конфиденциальность информации. Последняя разделяется на четыре группы: не оцениваемая, конфиденциальная, секретная и сверхсекретная.
Модель Биба ориентирована не на конфиденциальность, а на обеспечение целостности данных, и основана на простом принципе: не модифицировать данные вышестоящего уровня (для этого нужен более высокий приоритет) и не считывать данные нижестоящего уровня (на текущем уровне они не актуальны). Модель Грэхэма-Деннинга задает правила модификации объектов и правила модификации прав доступа; модель Бревера и Нэша «Китайская стена» запрещает передачу информации между двумя объектами, если в результате возникает конфликт интересов; модель Take-Grant представляет систему в виде графа, а пользователи получают права над узлами этого графа и могут передавать эти права по определенным правилам; модели Гогена-Мезиджера и Сазерленда представляют систему в виде набора предопределенных состояний и отслеживают переходы в разрешенные состояния; модель Discretionary access control (DAC) контролирует доступ к элементам файловой системы (файлам и каталогам) и выполняет избирательное управление доступом на уровне пользователь/группа. Модель Mandatory access control (МАС) подразумевает управление полномочиям доступа: каждый объект содержит сведения о своем уровне секретности, и при попытке доступа к нему проверяются права пользователя. Эта модель реализова-
на в системе SELinux, созданной Агентством национальной безопасности США на базе стандартного ядра Linux (Red Hat Enterprise Linux 4).
Модель Кларка-Уильсона стала одной из основных моделей, которая используется практически во всех современных СУБД. В ней впервые было введено понятие транзакции. Безопасность в этой модели организуется посредством внешнего монитора безопасности, контролирующего выполнение транзакций и разделяющего права доступа пользователей.
Модели многоуровневой защиты
Наиболее перспективной группой современных моделей информационной безопасности считаются модели многоуровневой защиты (Multi Level Security, MLS). Они объединяют наиболее полезные элементы других моделей и показывают на практике наиболее высокую эффективность. История их возникновения приводится далее.
В1964 году сотрудник Массачусетского технологического института Фернандо Корбато (Fernando Corbato), автор идеи разделения времени в операционных системах, совместно с инженерами компаний General Electric и Bell Labs приступил к созданию коммерческой многопользовательской системы Multics — прообраза Unix. Коллектив намеревался воплотить в ней множество передовых технологических идей: иерархическую файловую систему, расширяемые ресурсы, командный процессор и так далее. Multics стала первой операционной системой, в которую технологии безопасности закладывались с самого начала проектирования и разработки: идея многоуровневой защиты предполагала разделение виртуальной памяти на сегменты кода и данных и взаимодействие между сегментами с учетом уровня их секретности.
Современные принципы построения высоконадежных систем защиты, во многом опирающиеся на концепции, реализованные в Multics, сформулировал в 1981 году Джон Рашби (John Rushby), ученый Стэнфордского университета. Он предложил идею монитора, который следит за информационным взаимодействием между виртуальными разделами системы и вмешивается в данный процесс лишь в исключительных случаях. Впоследствии идеи MLS развились в так называемую концепцию NEAT (Non-bypassable, Evaluatable, Always invoked, Tamperproof). Она объединяет четыре требования:
функции защиты нельзя обойти или отключить;
эти функции невелики и математически выверены;
функции защиты вызываются постоянно и регулярно при любых действиях прикладных и системных программ;
эти функции не могут быть ни повреждены, ни модифицированы плохим или зловредным кодом защищаемого приложения.
Недостатки MLS хорошо известны пользователям массовых систем наподобие Windows. Обеспечение их безопасности происходит по схеме «найден сбой — выпущена заплата», что допустимо для настольных и отчасти корпоративных приложений, однако совершенно не подходит при обеспечении работы критически важных и встраиваемых систем. Традиционный подход создания службы безопасности, отделенной от адресного пространства прикладной программы
и выполняющейся при поддержке автономного ядра защиты, приводит к тому, что, во-первых, становится сложно организовать защищенное сетевое взаимодействие процессов из разных систем, а во-вторых, крайне затруднительна сертификация прикладных проектов на базе таких систем. Ведь «смесь» в одном проекте независимых механизмов системной безопасности и внутренних модулей защиты прикладных приложений весьма трудна для оценки и понимания их суммарной надежности. При этом не удается гарантировать безопасную работу готового решения, даже если все его составные части сертифицированы. Оказывает влияние на общую трудоемкость и так называемый принцип распространения секретности — работа секретного приложения с несекретными данными делает результат его работы тоже секретным. Во всяком случае, создателям общеизвестных операционных систем пока еще не удавалось получить для них сертификаты безопасности более-менее высокого уровня.
Модель MILS как наиболее передовое решение
Самой передовой на сегодняшний день компьютерной моделью безопасности считается Multiple Independent Levels of Security (MILS, http://mils.ois.com), базирующаяся на концепции NEAT. Она серьезно развивает идеи MLS, решает множество вышеперечисленных проблем и представляет собой архитектуру развертывания систем MLS, включая и сетевые решения, что позволяет в итоге получать полноценное решение класса NEAT.
Архитектура МILS реализуется на базе компактных компонентов, которые разрабатываются с помощью систем формального моделирования, а их исходный код математически верифицирован, что гарантирует отсутствие логических ошибок и точное соответствие заданной функциональности. С помощью этих компонентов конкретные прикладные приложения строят свою защиту индивидуальным способом, с помощью собственных оригинальных политик безопасности, — таким образом удается отойти от концепции выделенного ядра защиты, действующего фактически на одном уровне безопасности с прикладными приложениями. Более того, важная особенность такой модели заключается в том, что любые модули проекта, использующие компоненты MILS, можно сертифицировать по отдельности и затем повторно использовать в других проектах, сразу получая сертифицированный продукт.
Архитектура MILS сама строится на ядре безопасности, синхронизирующем выполнение компонентов MILS. Это ядро, в отличие от решений с объемной автономной системой защиты, обеспечивает только функции NEAT. Создано оно, как и компоненты MILS, с применением математических методов проверки корректности, а занимает всего около четырех тысяч строк кода на одном из универсальных языков программирования. Практика показала, что в прикладных проектах накладные расходы на выполнение такого ядра составляют 5% процессорных ресурсов.
Ядро MILS занимается исключительно следующими задачами.
Разделение адресного пространства.
Изоляция разделов, в которых работают приложения (сбой в одном разделе никогда не приведет к сбоям в других).
Разделение интервалов работы процессора. Ядро при переключении от обслуживания одного раздела к другому удаляет всю информацию (из кэша, временных настроек и так далее), прямо или косвенно связанную с обработкой предыдущего раздела.
Защищенная и авторизованная связь между разделами MILS — каждое приложение, выполняющееся в собственном пространстве памяти, может взаимодействовать с разделами только своего или более низких уровней защиты, используя для этого специальные сокеты MILS.
С помощью этих режимов обеспечивается полноценная изолируемость данных, защита от использования скрытых каналов процессора и системных служб, возможность контроля за потоками информации между разделами и внешними источниками, а также существенно снижается объем и сложность самих средств обеспечения безопасности (при требовании их абсолютного и гарантированного качества). За счет авторизации потоков информации можно настраивать в конкретных прикладных приложениях собственные контролируемые уровни безопасности.
В архитектуру MILS включено программное обеспечение промежуточного слоя. Оно разделяет ядро MILS, сетевые драйверы и средства поддержки файловой системы, реализуя также спецификацию POSIX* и интерфейс к различным компонентным технологиям (в частности, CORBA). В его функции также входят внешняя авторизация разделов MILS и фильтрация сетевого трафика между ними, обычно на базе системы идентификации разделов с помощью специальных меток. В частности, приложение MILS Console Protection Profile способно обработать сведения разных уровней секретности в нескольких окнах одновременно в рамках текущей сессии коммерческой операционной системы на одном компьютере.
В сети взаимодействие между системами MILS осуществляется по технологии, напоминающей обычные пиринговые сети. Специальное решение Partitioning Communications System обеспечивает те же принципы работы, что и автономная версия MILS: контроль за информационными потоками, изоляцию данных, разделение процессорного времени и независимость от сбоев. Однако они поддерживаются уже в сетевом режиме «точка - точка» и дополнены технологиями криптографии, идентификации и им подобными. В результате создание крупных систем с поддержкой многоуровневой безопасности существенно упрощается: их можно разбивать на отдельные части и автономно сертифицировать, а составные элементы MILS повторно использовать в новых решениях. На такой концепции, в частности, базируется проект глобальной информационной матрицы Министерства обороны США Global Information Grid, которая связывает все элементы современной армии — от людей и роботов до программных интеллектуальных модулей в единую сеть по протоколу IPv6.
Спецификация POS1X — это набор стандартных интерфейсов к операционной системе (на практике — прежде всего к Unix). Программа, выполненная в соответствии с этой спецификацией, должна без модификации работать в любой системе, поддерживающей POSIX.
Технология обеспечения информационной безопасности
Вышеперечисленные модели используются, когда компания уже приняла решение об обеспечении собственной компьютерной безопасности и выбирает конкретную систему. Однако этому решению обычно предшествует ряд действий по общей оценке уровня угроз. Кроме того, внедрение какой-либо передовой модели оправдано в ситуациях, когда необходимо обеспечить очень высокий уровень защиты, который нужен далеко не всегда, а его организация может быть очень затратной. В организациях, где не ведется работа с секретными данными и не создаются критически важные системы технологического управления, можно обойтись типовыми процедурами сертификации.
Комплексный подход к информационной безопасности в любом случае должен обеспечивать доступность нужных сведений всем заинтересованным пользователям, в противном случае он теряет смысл. Этот подход подразумевает обеспечение безопасности данных, повышение общей надежности и мониторинг угроз.
Обеспечение безопасности данных
Обеспечение безопасности данных строится на следующих принципах:
Целостность данных подразумевает недопустимость модификации информации неавторизованными пользователями или ее порчи вследствие сбоев или вирусных атак. Для этого применяется шифрование данных и резервное копирование.
Конфиденциальность данных подразумевает разграничение прав доступа пользователей к информации в зависимости от степени ее секретности. Для этого в системе вводится система ролей (каждому пользователю назначается свое имя в системе и пароль и допустимый набор действий). Она основана на технологиях авторизации (проверка, имеет ли право конкретный пользователь на выполнение той или иной операции) и аутентификации (является ли пользователь тем, за кого себя выдает). Конфиденциальность также обеспечивается предварительным анализом корректности загружаемых в систему данных и проверкой полномочий пользователей, поставляющих информацию в систему (например, с помощью электронно-цифровой подписи, которая с лета 2006 года официально применима для идентификации человека наряду с личной подписью).
Электронно-цифровая подпись — это реквизит электронного документа, предназначенный для загциты этого документа от подделки. Такая подпись формируется с помощью криптографических технологий.
Повышение общей надежности
Для повышения надежности системы обычно выполняются следующие действия:
Все данные шифруются и регулярно выполняется резервное копирование;
Программы в ряде случаев функционируют одновременно, дублируя работу друг друга;
На аппаратном уровне также задействуется несколько компьютеров или отдельных элементов, дублирующих процесс решения задачи.
Борьба с угрозами
По отношению к угрозам безопасности периодически выполняется следующий цикл действий.
Предупреждение — профилактические мероприятия с целью не допустить возникновение различных угроз.
Выявление — исследование системы с целью определить потенциальные угрозы.
Обнаружение — нахождение реально возникших угроз.
Локализация — поиск конкретных причин и источников угроз, нахождение возможных злоумышленников.
Ликвидация — устранение выявленных угроз.
Стандартная технология повышения уровня информационной безопасности
Стандартная технология проверки и повышения уровня безопасности системы в общем случае такова.
В системе выявляются уязвимые места. Они определяются, исходя из спи- |ска известных угроз (вирусы, сбои оборудования, несанкционированный
Iдоступ извне и изнутри системы и так далее). Такой список обычно предла
гается конкретными методиками сертификации.
2. Определяется допустимый уровень незащищенности системы. Для этого
।по каждой из выявленных уязвимостей вырабатываются рекомендации по
।устранению (от частичного до полного) и дается оценка возможных расхо
дов.
Заказчик обследования, исходя из особенностей конкретной организации и доступных ресурсов, выбирает оптимальный вариант, после чего исполнитель выполняет первоочередные меры по защите, при необходимости внедряет дополнительное программное обеспечение, готовит правила соблю- |дения безопасности и обновляет должностные инструкции. На этом этапе
обычно улучшаются способы разделения доступа пользователей к системе, устанавливаются антивирусные продукты, средства резервного копирования
и восстановления, мониторы анализа состояния сети и контроля доступа в
реальном времени. Подобному контролю, в частности, подвергаются серверы, рабочие станции (пользовательские места), каналы связи и узлы сети.
Подходы к обеспечению безопасности программного
। продукта
При создании программы надо обеспечить базовый уровень ее безопасности — защищенности от использования с целями, отличными от запланированных.
Практически все нарушения информационной безопасности, связанные с программным обеспечением, возникают вследствие ошибок в этом обеспечении. В подавляющем большинстве случаев они связаны либо с отсутствием контроля над размещением данных в оперативной памяти, либо с отсутствием проверок некоторых действий пользователя. В первом случае злоумышленник получает возможность выйти за границы блока памяти, отведенного для хранения определенного значения. Например, при вводе излишне длинной строки в адресной части браузера окончание этой строки накладывается на исполнимый код приложения и в некоторых случаях трактуется как машинные команды. Во втором случае, например, ввод нецифровых данных в числовое поле программы может привести к ее неработоспособности.
При создании программ рекомендуется придерживаться следующих правил: не использовать недокументированные возможности операционной системы, не размещать данные динамически, явно очищать (перезаписывать) блоки памяти, если в них хранились пароли, шифровать любую важную информацию, при работе с файлами захватывать их в монопольную обработку, закрывать файлы сразу по окончании работы с ними, следить за свободным местом на жестком диске, не забывать стирать временные данные на диске (по возможности многократно перезаписывая данные в кластерах, использованных для хранения важных сведений), регистрировать все действия пользователей, вести протокол их работы, — и всегда как можно больше времени уделять тестированию.
Приемы использования программных технологий защиты
Мы познакомились с теоретическими и организационными подходами к информационной защите. Теперь изучим конкретные приемы, реализуемые с помощью системы C++Builder. В операционной системе Windows отсутствует более-менее серьезный набор технологий для информационной защиты, поэтому многие необходимые элементы защиты приходится программировать с нуля.
Наименее подвержен информационным угрозам локальный компьютер. Единственный источник проблем с безопасностью для него — нелицензионное программное обеспечение, зараженное вирусами и «троянскими конями».
Вирус — это программа, способная к патоматическому массовому распространению. Выполняемые ею действия обычно скрыты от пользователя. Многие компьютерные вирусы содержат дополнительную нагрузку, предназначенную для выполнения тех или иных потенциально опасных действий на зараженном компьютере.
Троянская программа — это, обычно, скрытая коммуникационная программа, выполняющая сетевые операции в интересах злоумышленника (своего владельца). Троянские программы способны похищать информацию с компьютера, использоваться для доставки незаконных материалов (непример, для рассылки спама) или для захвата компьютера под удаленное управление («зомбирования»).
Основной источник информационных угроз — это компьютерная сеть. Чем большее число компью теров в ней сосредоточено и чем активнее они взаимодействуют друг с другом, тем больше вероятность загрузить на свой компьютер опасный код или подвергнуться атаке злоумышленника. Так как наибольшее число компьютеров подключено к глобальной сети (Интернету), то и риски подвергнуться информационному нападению наиболее велики при работе во Всемирной сети.
По этим причинам сложнее всего обеспечить безопасность системы, рассчитанной на работу в Интернете. Немного проще организовать безопасность программы для локальной сети, и наименьшие трудности встречаются при создании однопользовательского приложения.
Рассмотрим некоторые из приемов организации информационной защиты системы более подробно.
Средства защиты приложений для сети
Чтобы выполнить какие-либо действия по защите сетевого приложения, надо, во-первых, удостовериться, подключено ли оно вообще к какой-либо IP-сети, а
Toot PaletteToot Palette■+ Indy Clients ** Indy Servers♦ Indy I/O Handlers it: Indy Intercepts i™ Indy Mise '18 TIdSocksInfo1'5 TIdSchedulerOfTh...^ TIdSchedulerOfTh...Ж TIdThreadCompo... I Ю TIdConnectThrou...■ TldCompressorZLi... S■+ Indy Clients ** Indy Servers♦ Indy I/O Handlers it: Indy Intercepts i™ Indy Mise '18 TIdSocksInfo1'5 TIdSchedulerOfTh...^ TIdSchedulerOfTh...Ж TIdThreadCompo... I Ю TIdConnectThrou...■ TldCompressorZLi... Sво-вторых, при необходимости суметь отключиться от сети, переведя компьютер в локальный режим работы.
Toot Palette
Toot Palette
■+ Indy Clients *
* Indy Servers
♦ Indy I/O Handlers it: Indy Intercepts i™ Indy Mise '
18 TIdSocksInfo
1'5 TIdSchedulerOfTh...
^ TIdSchedulerOfTh...
Ж TIdThreadCompo... I Ю TIdConnectThrou...
■ TldCompressorZLi... S
■+ Indy Clients *
* Indy Servers
♦ Indy I/O Handlers it: Indy Intercepts i™ Indy Mise '
18 TIdSocksInfo
1'5 TIdSchedulerOfTh...
^ TIdSchedulerOfTh...
Ж TIdThreadCompo... I Ю TIdConnectThrou...
■ TldCompressorZLi... S
Проще всего узнать, подключен ли компьютер к сети, с помощью компонентов набора Indy.
Компонент TldlPWatch из раздела Indy Mise предназначен для определения текущего IP-адреса машины. Если он равен 127.0.0.1, то компьютер работает в локальном режиме, в противном случае он подключен к IP-сети (возможно, Интернету). Немедленное определение IP-адреса выполняет функция Forcecheck (), а сам адрес в виде строки заносится в свойство Current ip. Кроме того, свойство IsOnline, доступное только для чтения, принимает значение true, если компьютер работает в сети.
Пример определения собственного 1Р-адреса:
IdIPWatchl->Active = true;
IdIPWatchl->ForceCheck();
if ( IdIPWatchl->IsOnline )
ShowMessage(ldIPWatchl->CurrentIP);
else ShowMessage."Компьютер не в сети.");
Для отключения от сети используется стандартная библиотека Windows Rasapi32.dll. Функция RasEnumConnections () формирует список текущих соединений, а функция RasHangUp () разрывает выбранное соединение.
Чтобы проверить, насколько работоспособно текущее соединение, можно попробовать подключиться к сайту в Интернете. Для этого задействуется компонент TidHTTP из группы Indy Clients. Его метод Get () получает в качестве параметра адрес сайта (серверный запрос HTTP) и возвращает текстовое содержимое от
удаленного сервера (как правило, исходный код запрошенной страницы HTML). Например:
Labell->Caption =
IdHTTPl->Get("http://my-site.ru");
Служба безопасности обычно осуществляет контроль за активностью пользователей и программ в сети, анализирует сетевой трафик и сообщает системному администратору о возникновении подозрительных ситуаций. Задача перехвата сетевого трафика достаточно сложная и зависит от конкретной операционной системы. Обычно либо создается промежуточный прокси-сервер, через который пропускается сетевой поток (наиболее эффективное решение, но не всегда возможное из-за организационных сложностей), либо подменяются системные библиотеки Windows (при этом возможна нестабильная работа системы), либо на время работы программы-перехватчика подменяются системные вызовы функций, ответственных за организацию сетевой связи.
Существует немало готовых компонентов сторонней разработки для среды Borland Developer Studio, которые автоматизируют работу по перехвату трафика. В стандартную поставку Indy 10 также входит компонент TidHTTPProxyServer (HTTP-прокси), выполняющий функции прозрачного слежения за трафиком. Достаточно настроить его свойства DefaultPort (порт прокси-сервера) и Bindings (список контролируемых сокетов) и создать обработчик события OnHTTPDocument. Это событие формируется, когда сервер отследил передачу текстового блока, который можно вывести, например, в поле формы:
void fastcall
TForml::IdHTTPProxyServerlHTTPDocument (TidHTTPProxyServer *ASender, const AnsiString ADocument, TStream *&VStream, const TldHeaderList *AHeaders)
(
Memol->Lines->Add(ADocument);
}
Для изучения доступных сетевых ресурсов (например, получения списка компьютеров, пользователей, периферийного оборудования) используют стандартный набор функций Windows Networking (WNet). Функция WNetOpenEnumQ подготавливает процесс поиска нужных ресурсов, функция WNetEnumResourcef) выполняет их поиск и перебор, а функция WNetCloseEnum() завершает работу. Для получения имени пользователя, которому принадлежит локальный диск, подключенный к сети, применяется функция WNetGetUser().
Защита Интернет-каналов
В Интернет-системах используется, как правило, стандартная технология шифрования передаваемых по глобальной сети данных Secure Sockets Layer (SSL, защищенное расширение протокола HTTP). Она была разработана в первые годы развития Сети компанией Netscape Communications, а затем стала международным стандартом. Технология SSL реализована в виде ассиметричного криптоалгорит-
ма с ключом длиной 128 бит. Чтобы обратиться к веб-ресурсу по протоколу SSL, надо в адресной строке браузера вместо префикса http ввести префикс https. Работа по протоколу SSL должна поддерживаться и браузером, и на стороне веб-сервера, который распознает запросы HTTPS и организует защищенное взаимодействие с клиентским приложением. Технология SSL массово используется в электронной коммерции — абсолютное большинство интернет-магазинов и платежных систем поддерживает запросы HTTPS. О встраивании этой технологии в свое приложение рассказывается в главе 44.
В последние годы все большую популярность набирают так называемые виртуальные частные сети. Виртуальная частная сеть — это организации связи между локальными сетями через каналы общего пользования. Технология VPN включает контроль неизменности данных, шифрование, а также специальные меры по идентификации адресата и отправителя. Пользователь, работающий через VPN, получает персональный доступ к защищенным ресурсам, при котором исключены подслушивание или подмена корреспондента. Защита соединения обеспечивается созданием туннеля передачи данных между клиентом и сервером.
С помощью VPN возможно, например, создать одну виртуальную сеть на основе множества разнородных сетей и организовать в ее рамках защищенную передачу данных. Информация, передаваемая по каналам VPN, шифруется, а клиентским машинам присваиваются условные IP-адреса, отличающиеся от реальных, что существенно повышает защищенность виртуальной частной сети.
C++Builder позволяет создавать сети VPN с помощью компонентов TldTunnelMaster и TldTunnelSlave. Компонент TldTunnelMaster фактически работает как прокси-сервер, позволяя дополнительно выполнять проверки IP-пакетов, проводить авторизацию пользователей, управлять IP-каналами, оптимизировать трафик в сети, собирать всевозможную статистику и так далее. Компонент TldTunnelSlave, который перераспределяет обработанные клиентские IP-потоки, обеспечивая дополнительные сервисы, например виртуальные 1Р-каналы.
Получение сведений о системе
Одной из наиболее передовых считается технология информационной защиты, в которой выполняется непрерывное наблюдение за состоянием системы. Как только в ней выявляются подозрительные действия, немедленно оповещается системный администратор и служба безопасности, а соответствующий ресурс блокируется. Основная сложность в создании подобных мониторов реального времени заключается в нечеткости понятия «подозрительное действие». Существуют два подхода к этой проблеме:
Выработка множества типовых правил и шаблонов допустимых действий пользователей в сети, нарушение которых считается подозрительным и блокируется. Данный подход трудоемок, негибок и требует больших усилий по подготовке такого набора правил. Однако он легко реализуем технически.
Создание системы искусственного интеллекта, которая в режиме самообучения фиксирует типовые шаблоны активности в системе и в дальнейшем самостоятельно распознает подозрительные случаи. Как правило, в подоб-
ных задачах применяются алгоритмы нейронных сетей, которые показывают высокую эффективность в выявлении реальных несанкционированных действий (95-99%). Но они достаточно дороги и сложны в развертывании и сопровождении.
Рассмотрим основные и наиболее простые способы программного получения сведений о состоянии системы. Доступ к названию текущего компьютера осуществляет функция GetComputerName(). Ее первый параметр — указатель на буфер символов, а второй параметр — указатель на целочисленное значение, задающее максимальную длину этого буфера:
char buf [1024] ;
unsigned long Sz=1024;
GetComputerName(buf, &Sz);
Схожим образом организуется доступ к имени текущего пользователя:
GetUserName(buf, &Sz);
Информация о версии используемой операционной системы формируется функцией GetVersionEx(). В качестве своего параметра она получает указатель на экземпляр структуры osversioninfo:
OSVERSIONINFO os;
GetVersionEx(&os);
Например, для системы Windows ХР поле dwMajorVersion примет значение 5, а поле dwMinorVersion — значение 1.
Наименование каталога, в который установлена операционная система, и имя системной папки Windows получают вызовом функций GetWindowsDirectory() и GetSystemDirectory(). Их первый параметр — указатель на текстовый буфер, а второй параметр — максимальная длина этого буфера.
Общие сведения о системе доступны в формате структуры SYSTEMJNFO, которая заполняется функцией GetSystemlnfo():
SYSTEM_INFO si;
GetSystemlnfо(&si);
В частности, свойство dwNumberOfProcessors определяет число процессоров в используемом компьютере, а свойство wProcessorlevel — архитектуру процессора (Intel, IA64, MIPS и другие).
Сведения об оперативной памяти и ее распределении выдает функция GlobalMemoryStatus():
MEMORYSTATUS ms ;
GlobalMemoryStatus(&ms);
Общий объем физической оперативной памяти в байтах записывается в свойство dwTotalPhys, текущий доступный объем — в свойство dwAvailPhys. Соответствующие объемы виртуальной памяти хранятся в свойствах dwTotalVirtu- al и dwAvailVirtual.
Сведения о текущих настройках экрана хранятся в глобальной переменной Screen (тип TScreen). Она позволяет, в частности, узнать размеры экрана (свойства Width и Height). Детальные настройки экранного драйвера доступны через системную функцию GetDeviceCaps().
Всевозможные дополнительные характеристики операционной системы получают вызовом функции SystemParameterslnfo(). Ее первый параметр определяет конкретную характеристику. Например, следующий вызов заносит в переменную buf местонахождение файла с изображением, который используется в качестве рисунка рабочего стола:
char buf[ 1024];
SystemParametersInfо
(SPI_GETDESKWALLPAPER,1024,buf,0);
Криптографические средства шифрования данных
Технологии шифрования подразделяются на две большие группы: симметричное шифрование с закрытым ключом, который используется и для кодирования, и для декодирования, и ассиметричное шифрование с двумя ключами (закрытым и открытым).
При ассиметричном шифровании закрытый и открытый ключи дополняют друг друга — то, что зашифровано одним, может быть расшифровано другим. Открытый ключ предоставляется в публичный доступ, а закрытый, который невозможно установить по открытому, остается в распоряжении пользователя. Технология ассиметричного шифрования работает медленно, поэтому на практике обычно применяются средства симметричного шифрования, в которых сам ключ (относительно короткое значение) дополнительно шифруется методами ассиметричного шифрования.
В систему Windows входит стандартный набор функций (библиотека crypt32.dll), поддерживающий различные технологии шифрования. Список реализаций этих технологий, установленных в конкретной версии Windows, можно получить следующим кодом:
DWORD dwProvType, cbName, i;
char name[1024];
for( i = 0;
CryptEnumProviders
(i, 0, 0, SdwProvType, 0, &cbName);
i + + ) {
CryptEnumProviders
(i, 0, 0, &dwProvType, name, ScbName);
Memol->Lines->Add(name);
Функция CryptEnumProviders() формирует сведения об i-м провайдере (номер вводится первым параметром) и записывает их в четвертый параметр (числовой код типа провайдера). Кроме того, название провайдера заносится в пятый параметр (текстовый буфер).
Пример 31.1
'I-.'-Организация симметричного шифрования с помощью C++Builder
Рассмотрим на примере, как реализуется механизм симметричного шифрования. В данном примере мы будем использовать алгоритм шифрования RSA, который обычно применяется для ассиметричных схем кодирования, подразумевающих использование разных ключей для шифрования и расшифровки данных. Он плохо подходит для задач симметричного шифрования, так как функционирует медленно. В данном учебном примере алгоритм RSA задействуется для шифрования и расшифровки данных с помощью одного пароля.
Создадим пустое приложение VCL Forms.
Подготовим переменные, которые понадобятся для процесса шифрования:
// Провайдер шифрования: HCRYPTPROV hProv;
// Хеш-объект:
HCRYPTHASH hash;
// Ключ шифрования: HCRYPTKEY key;
// Пароль шифрования: char psw[80] = "пароль";
// Промежуточный буфер: char buf[1024];
// Вспомогательная переменная: DWORD len;
Настраиваем провайдер шифрования.
Для выбора и настройки провайдера шифрования применяется функция CryptAcquireContext():
СryptAcquiгeContext
(ShProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
Ее четвертый параметр PROV_RSA_FULL задает систему шифрования (RSA). Указатель на объект, реализующий RSA, заносится в переменную hProv. Последний параметр CRYPT_VERIFYCONTEXT позволяет выполнять нужные функции, не формируя пары открытых и закрытых ключей и выполняя лишь симметричное шифрование (приложение не получает прямой доступ к системному контейнеру ключей).
Готовим пространство хеширования.
Если для кодирования и декодирования данных используется конкретный пароль, то формируемый на основе пароля ключ всегда должен быть одним и тем же. Для этого выполняется предварительное хеширование пароля (превращение его в некий уникальный идентификатор), по которому невозможно восстановить исходное значение (сам пароль):
CryptCreateHash(hProv, CALG_SHA, 0, 0, &hash);
Последним параметром вводится переменная hash типа HCRYPTHASH (хеш-объект). Второй параметр определяет тип алгоритма хеширования (SHA — один из стандартных алгоритмов, поддерживаемых в продуктах Microsoft).
Формирование хеша на основе пароля (переменная psw) выполняется так:
CryptHashData(hash, psw, strlen(psw), 0) ;
В результате хеш-объект hash подготовлен к процедуре создания ключа.
Получаем ключ шифрования.
В схеме шифрования на основе пароля будем использовать так называемые сессионные ключи — ключи шифрования, которые используются однократно и затем теряются. Так как используется симметричный механизм и предполагается, что пользователь знает пароль (фактически выполняющий функцию закрытого ключа), потребность в хранении сессионных ключей отсутствует.
Генерируем ключ:
CryptDeriveKey(hProv, CALG_RC4, hash, 0, &key);
Результат заносится в последний параметр. Второй параметр определяет потоковый алгоритм генерации ключа RC4 с длиной 40 битов.
Сразу по окончании процедуры генерации удалим хеш-объект:
CryptDestroyHash(hash) ;
Шифруем данные.
Занесем шифруемую строку в промежуточный буфер и вызовем функцию CryptEncrypt() с ключом key, которая поместит в этот же буфер зашифрованный результат. Длина буфера и ссылка на переменную, хранящую длину результата, заносятся в седьмой и шестой параметры:
strcpy(buf,"шифруемая строка");
len=strlen(buf) ;
CryptEncrypt(key, 0, 1, 0, buf, &len, len) ;
Третий параметр принимает значения 1 или 0 (истина или ложь) в зависимости оттого, является ли очередной блок данных завершающим в общем шифруемом потоке.
Результат можно вывести на экран:
Memol->Lines->Add(buf) ;
Если, например, шифруется строка «шифруемая строка», то результат может быть таким (не все символы имеют печатный вид):
КЬсПДМАДцеОмбУзт
Освобождаем ресурсы провайдера шифрования.
Завершаем работу модуля шифрования:
CryptReleaseContext(hProv, 0);
Расшифровываем данные.
Вышеприведенный код полностью подходит и для выполнения обратной операции — раскодирования данных. Для этого в переменную buf в п. 6 записывается зашифрованная строка:
strcpy (buf , "КЬсПДМАДцеОмбУзт" ) ;
и вызывается функция расшифровки CryptDecrypt():
CryptDecrypt(key, 0, 1, 0, buf, &len );
В результате в переменной buf окажется значение «шифруемая строка».
Вышеописанная последовательность имеет существенный недостаток: пароль хранится в исходном коде «как есть», и в таком случае мощность алгоритма шифрования теряет смысл —достаточно изучить структуру программы и выявить явно заданный пароль. Этого можно избежать, исключив хранение пароля в коде (пользователь должен ввести его в соответствующее поле), а в ситуациях, когда выполняются полностью автоматизированные действия по шифрованию, использовать ассиметричные алгоритмы. В них для шифрования данных применяется закрытый ключ, а для расшифровки — открытый. Но так как подобные алгоритмы ресурсоемки, используются промежуточные решения: например, симметричные ключи перед передачей шифруются ассиметричными криптоалгоритмами.
Вопросы
Что такое информационная (компьютерная) безопасность?
Расскажите об основных моделях компьютерной безопасности.
Как устроены модели многоуровневой защиты?
Какова архитектура модели MILS?
Из каких шагов складывается процесс обеспечения информационной безопасности?
Какими приемами повысить надежность и защищенность программного продукта?
Расскажите о криптографических технологиях.
Как алгоритмы шифрования реализуются с помощью C++Builder?
41) РАЗДЕЛ
Работа с файлами и базами данных
Технологии работы с файлами
Базы данных и работа с ними
Разработка файл-серверных приложений
Разработка клиент-серверных приложений
Разработка многоуровневых приложений
Возможности языка запросов SQL
ГЛАВА 32 Введение в системы управления базами данных
Любая современная среда программирования обязательно включает технологии хранения и обработки внешней информации. В самом простом варианте разработчику доступны средства обработки данных в файлах. В более развитых системах задействуются так называемые базы данных и программы, ими управляющие.
База данных — это совокупность данных, которые определенным образом организованы и связаны друг с другом. Способ этой организации и связи называется моделью базы данных.
Одной из наиболее распространенных моделей баз данных является реляционная модель. Она рассматривается далее. Данные в этой модели хранятся в базе в виде таблиц.
Таблица базы данных - это набор записей фиксированной структуры, где каждая запись описывает определенный информационный объект. Таблица состоит из строк, представляющих записи, и столбцов, представляющих именованные поля — элементы структуры записей таблицы.
Система управления базами данных (СУБД) — это технологическая программная система, предназначенная для обработки данных и предоставляющая пользователю средства просмотра и редактирования этих данных.
Среда C++Builder включает многочисленные технологии и компоненты для создания программ, работающих с базами данных.
$2.1. Недостатки файлового подхода
Выделим ряд наиболее серьезных недостатков, связанных с использованием файлов для хранения данных.
Данные в файлах неупорядочены. Отдельный файл не несет информации о своем содержании (так называемой метаинформаций). В результате не существует универсального способа для быстрого поиска нужных сведений в наборах файлов. Частично помогают условные соглашения, например договоренности об использовании расширения имени файла в качестве формальной характеристики его назначения.
Однако, вообще говоря, структура файлов не стандартизирована. Например, при создании сборника сведений о сотрудниках организации разработчику придется продумать внутреннюю структуру файла, содержащего такой справочник, и вручную реализовать запись и чтение сведений
по выработанному шаблону. Если формат сборника потребуется изменить, всю эту работу придется проделать заново.
Файловые методы доступа организованы на низком уровне. Системные средства обеспечивают лишь ввод и вывод заданного числа байтов и не предоставляют более-менее развитой системы организации логического, смыслового поиска и отбора информации. Разработчик должен запрограммировать доступ программы к файлу как к набору организованных по шаблону данных, ввести средства перемещения от одной логической записи к другой и так далее. Для каждого проекта все это приходится разрабатывать и реализовывать заново, с нуля.
Например, программисту, создавшему сборник, хранимый в файле, придется вручную разрабатывать средства отбора нужных данных. Так, чтобы выбрать сотрудников, имеющих заданный уровень заработной платы, необходимо детально запрограммировать соответствующие действия. При изменении кода эксплуатируемой программы ее придется переустанавливать.
Сложно организовать промежуточное хранение сведений, отобранных из файла. Данные, которые программа отобрала из файла для обработки, надо каким-то образом сохранить. Если их много, придется создать временное хранилище на диске, причем опять-таки в виде файла.
Сложно вносить изменения в файл, содержащий структурированную информацию. При модификации наборов логически упорядоченных данных требуется, в частности, точно рассчитывать местоположение нужной записи. При этом необходимо обеспечить надежность используемой схемы добавления, модификации и удаления записей. Если файл велик, сбой в процессе работы с ним может привести к потере или порче данных.
Обращение к записи в файле большого объема — длительная операция. Если нужный элемент данных записан в конце файла, то для его поиска может потребоваться просмотр всего файла от начала до конца.
Организация многопользовательской работы с файлом трудоемка. Если в организации имеется сборник сведений о сотрудниках, желательно, чтобы с ним могли одновременно работать несколько человек. Одним пользователям разрешено просматривать информацию, другим — модифицировать определенные поля, третьим — изменять любые элементы сборника. Файловая система не поддерживает столь тонкие аспекты разделения доступа пользователей к данным, поэтому программировать многопользовательский режим придется вручную.
Файлы не содержат средств связи между собой. Крупные системы содержат не один, а множество информационных файлов. В дополнение к сведениям о сотрудниках нужны сведения об отделах, товарах, заказчиках и так далее. Доступные сведения тесно связаны друг с другом. Например, запись о сотруднике может содержать ссылку на запись, определяющую отдел, в котором он работает. Запись о клиенте может хранить ссылки на приобретаемые клиентом товары. Такая организация взаимосвязанных таблиц позволяет избежать хранения избыточной информации и ограничиться коротким полем-указателем на запись в другом файле. Однако организация связей между файлами — непростая и трудоемкая задача.
Недостатки файлового подхода к хранению данных сняты в современных СУБД. Работа с данными происходит в них не на физическом уровне байтов и позиций указателя в файле, а на абстрактном логическом уровне полей, упорядоченных записей и взаимосвязанных таблиц, для которых не существен конкретный механизм реализации системы.
СУБД поддерживают ссылки между записями таблиц - это их ключевая отличительная особенность от файлов. На физическом уровне базы данных почти всегда хранятся в виде набора файлов, но пользователь взаимодействует с ними лишь в рамках понятий полей, записей и таблиц. В частности, СУБД часто развертываются в смешанных сетях и работают под управлением разных операционных систем, но к ним возможен универсальный доступ с разных клиентских платформ.
Современные СУБД представляют собой довольно сложные программные системы. В отличие от файловых систем они включают готовые средства создания запросов к таблицам и построения отчетов, обеспечивают целостность данных, поддерживают многопользовательскую работу и предлагают разработчику много других возможностей. При этом некоторые СУБД поддерживают лишь базовый набор функций, например только запросы, как многие из файл-серверных СУБД. Другие предлагают развитые средства визуального проектирования таблиц, усовершенствованные диалекты языка запросов SQL, мощные средства создания отчетов. Передовые СУБД обычно способны работать на множестве платформ и в Сети, но стоят они достаточно дорого.
История развития СУБД
До середины 1960-х годов данные хранились в обычных файлах при всех вышеописанных недостатках такого подхода. Известный компьютерный специалист Чарльз Бахман (Charles Bachman) из компании General Electric занимался автоматизацией крупных архивов с помощью популярного в те годы языка программирования КОБОЛ, рассчитанного на решение экономических задач. Он постоянно выслушивал нарекания пользователей на несовместимость форматов данных в его программах и, в конце концов, разработал концепцию сетевого, свободного перемещения между логическими записями с помощью так называемых перекрестных ссылок (подобие гиперссылок в современном Интернете), а также придумал универсальный язык описания иерархически организованной информации.
Бахман основал рабочую группу по стандартизации работы с данными Database Task Group, которая вошла во влиятельную организацию КОДАC/LT(CODASYL), занимавшуюся стандартизацией КОБОЛа. В 1971 году группа выпустила первый стандарт КОД АС ИЛ на базы данных, который быстро завоевал популярность и был подержан немалым числом разработчиков СУБД. Бахман за свои работы впоследствии получил престижную премию Тьюринга.
В основу первого стандарта КОДАСИЛ была заложена концепция перекрестных ссылок между наборами данных. Она была ориентирована на возможности тогдашних компьютеров и объясняла, как компактно хранить большое число таблиц с множеством взаимосвязей. К сожалению, спецификации КОДАСИЛ не затрагивали вопросы эффективного поиска информации в таблицах, так как поиск
и отбор данных считался тогда слишком трудоемкой задачей. Программистам приходилось «выжимать» из компьютеров все ресурсы, и о стандартах на поиск данных речи тогда идти не могло. Поэтому первый стандарт КОДАСИЛ считается описанием так называемой сетевой модели базы данных. Каждый набор записей в такой модели может быть связан с множеством других наборов, причем без особых ограничений. Однако сетевая модель не позволяла добиться высокой эффективности поиска и отбора нужных сведений, а интерес к таким задачам со стороны потребителей систем обработки данных быстро рос.
Реляционная модель данных
Первая официальная СУБД IMS, выпущенная корпорацией IBM в 1968 году для американского проекта «Аполлон» (полеты на Луну), работала на машинах System/360, аналогом которых в СССР была серия ЕС ЭВМ. Правда, первоначально была реализована не сетевая, а более строгая иерархическая модель базы данных. В ней таблицы не могли связываться друг с другом произвольно. Такие связи организовывались в виде «деревьев». К тому времени Бахман основал собственную компанию Cullinet, которая одно время даже была крупнейшей программной компанией в мире.
Эдгар Кодд (Edgar Codd), работавший в 1960-е годы в корпорации IBM, активно занимался разработкой приложений, использующих базы данных, и постоянно сетовал на ограничения стандарта КОДАСИЛ. Он, в частности, неоднократно вступал в дискуссии с Бахманом. Стандарт КОДАСИЛ, как уже говорилось, не позволял создавать производительные системы с удобными функциями поиска. Когда же появились первые дисковые накопители с быстрым доступом к данным, существенно снизилась актуальность компактного представления таблиц, до этого обычно хранимых на магнитных лентах. Востребованность же гибких средств организации запросов к СУБД росла. В 1970 году Кодд выдвинул принципиально новую, реляционную модель базы данных, которая с незначительными изменениями используется и по сей день. Знаменитые 12 правил Кодда, описывающие эту модель, впервые были опубликованы в издании IBM Research Report в августе 1969 года.
Двенадцать правил Кодда, описывающих реляционную модель базы данных
Информационное правило', вся информация в реляционной базе данных представляется исключительно на логическом уровне — в виде значений в таблицах. К этой информации относятся также имена таблиц и их полей. Фактически данное правило является основой для определения СУБД.
Правило гарантированного доступа', любой элемент в базе данных гарантированно доступен по имени таблицы, первичному ключу и имени поля. Первичный ключ — это специальное значение, сформированное на основе данных записи таким образом, что для каждой записи оно является уни-
кальным и фактически способно однозначно идентифицировать любую запись.
Правило систематической обработки нулевых значений; не заданные значения полей, представленные, например, пустыми строками или нулями, должны корректно обрабатываться СУБД как «неопределенные значения». Это правило учитывает типичные ситуации отсутствия элементов в случае неполноты информации, загружаемой в базу.
Правило ведения динамического каталога, основанного на реляционной модели; описание базы данных представляется на логическом уровне так же, как и обычные данные. Пользователь может использовать язык реляционных запросов к базе для манипуляции как с данными, так и с именами таблиц и полей. Для этого в базу включаются дополнительные системные таблицы, хранящие описания структуры пользовательских таблиц.
Правило всеобъемлющего подмножества языка работы с данными; СУБД должна поддерживать хотя бы один формальный язык с вычисляемыми выражениями и определенным синтаксисом. Этот язык должен включать средства описания данных, представления данных в виде наборов записей, средства манипуляции с данными (в том числе интерактивные), а также механизмы обеспечения целостности данных и поддержки транзакций (выполнения группы операций как единого действия).
Правило обновления представлений; если теоретически возможно сформировать в базе определенное представление (так называемую виртуальную таблицу, которая реально не существует в базе, а формируется динамически), то СУБД должна обеспечивать полноценный доступ к нему.
Правило высокоуровневой вставки, обновления и удаления; СУБД должна поддерживать действия по вставке, обновлению и удалению групп записей как единую операцию. Это правило задает концепцию работы не с отдельной записью, а с множеством записей.
Правило физической независимости данных, способы доступа к данным и способы их хранения в СУБД не влияют на логику их обработки.
Правило логической независимости данных; модификация структуры таблиц не влияет на логику их обработки (если сохранены поля, выбранные для такой обработки, а их типы изменены в разумных пределах).
Правило независимой целостности; правила целостности данных должны определяться с помощью определенного подмножества языка обработки данных (см. правило 5). Эти правила хранятся в динамическом каталоге базы данных наряду с описаниями таблиц. Как минимум, должны соблюдаться два правила целостности:
сущностная целостность; в базе не должно быть первичных ключей с нулевыми значениями, то есть записей с неопределенным идентификатором;
ссылочная целостность; для каждого внешнего ключа в базе должна существовать запись с соответствующим первичным ключом, то есть все ссылки должны указывать на реально существующие записи.
Правило независимого распространения; работа с реляционной СУБД не должна зависеть от способа развертывания базы данных и программного сервера.
Правило отсутствия побочных эффектов: если в СУБД реализован низкоуровневый язык работы с данными, обрабатывающий отдельные записи, то он не может быть использован для обхода механизмов обеспечения целостности системы.
Вывод, который можно сделать из перечисленных правил, нередко называют нулевым правилом; любая система, которая названа реляционной системой обработки данных, должна управлять данными исключительно за счет реляционных механизмов.
Первой коммерческой СУБД, в которой были воплощены эти двенадцать правил, была система Oracle с языком запросов DBMS Language. Массово распространенный сегодня язык SQL, предназначенный для описания запросов к реляционным базам, был создан в компании IBM в 1974 году. Он основывался на исследованиях Кодда и первоначально назывался Structured English Query Language (SEQUEL).
Кодд первым предложил концепцию таблиц (наборов записей с фиксированными полями) вместо ранее массово применявшихся списков. Списки допускали сложные взаимосвязи между разрозненными блоками данных, которые считались одинаковыми по структуре и «смыслу», но могли храниться в далеко разнесенных позициях на магнитной ленте с медленным последовательным доступом. В реляционной модели доступ к каждой записи выполняется быстро (нередко одной дисковой операцией) за счет системы индексов. Явная взаимосвязь между индексами и записями в таблицах может быть задана и эффективно реализована с помощью ключевых полей.
Первые реляционные СУБД
Реляционная схема имеет и существенные недостатки. Так, в прежней сетевой модели, в рамках одного программного цикла можно было отобрать весь набор тематических данных — каждая запись прямо указывала на следующую. В реляционной модели иногда приходилось перебирать всю таблицу, чтобы найти единственную запись. Поэтому Кодд выдвинул идею специального языка запросов и обработки реляционных баз данных, который базировался бы не на классической, императивной программной модели, а на математической теории множеств. Он предложил принципы быстрой и эффективной обработки базы с помощью языка, который оперировал бы не отдельными полями и записями, а наборами данных как одним целым, формируя их одной операцией.
Первой системой, основанной на концепциях Кодда, стала System R, которую создавали специалисты IBM. Она появилась в 1975 году, а ее авторы принялись расширять эту СУБД поддержкой множества пользователей. В результате очередная версия была выпущена в 1978 году и дополнилась уже стандартизованным к тому времени языком запросов к базам данных SQL, который был создан в ее рамках в первоначальной версии SEQUEL. Впоследствии эта система получила название SQL/DS, а с 1983 года известна как СУБД IBM DB2.
С 1973 года начались разработки реляционной СУБД INGRES. Ее авторы, Юджин Вонг (Eugene Wong) и Майкл Стоунбрейкер (Michael Stonebraker) из университета Беркли, привлекли к работам студентов, используя бюджет, выде-
ленный на создание базы географических данных. Уже в 1974 году появилась первая версия этой СУБД, которая потом интенсивно развивалась до 1979 года. В ней использовался оригинальный язык запросов QUEL, опиравшийся на идеи Кодда.
В коллективе INGRES работало много замечательных специалистов, и некоторые их них в 1980-х годах основали собственные компании и затеяли оригинальные проекты по созданию собственных СУБД. На основе платформы INGRES впоследствии выросли такие общеизвестные продукты, как Sybase, Informix, NonStop SQL. Даже ведущая СУБД корпорации Microsoft, MS SQL Server, представляла собой переделанный вариант сервера Sybase, который в свою очередь базировался на INGRES. Одним из немногих исключений в этом ряду стала, пожалуй, СУБД Oracle, которая была основана на идеях, опубликованных в открытой документации IBM по ее СУБД System R.
Стоунбрейкер также не удержался от соблазна создать оригинальный продукт. Его объектно-реляционная версия получила название PostgreSQL и сегодня массово применяется во многих проектах.
По мере роста рынка СУБД и реализованных на их основе проектов проявились очередные ограничения реляционной модели. Достаточно высокое быстродействие работы с базой данных обеспечивалось с помощью языка SQL. Но классическая модель Кодда, если ее требования строго соблюдались, накладывала и ряд более тонких ограничений на принципы обработки таблиц.
Вопросы
В чем заключаются основные недостатки хранения информации в виде файлов?
Какой основной принцип был заложен в первый стандарт баз данных?
В чем отличие сетевой модели данных от иерархической?
Расскажите о недостатках стандарта КОДАСИЛ.
Сформулируйте нулевое правило Кодда.
Перечислите 12 правил Кодда и поясните, зачем каждое из них вводилось.
ГЛАВА 33
Принципы проектирования баз данных
Современные информационные системы характеризуются большими объемами хранимых данных, их сложной организацией, а также высокими требованиями к скорости и эффективности обработки этих данных. Для успешного функционирования базы данных важна правильная организация данных в ней.
Технология нормализации
Модель Кодда позволяет добиться непротиворечивости хранимой информации, элементы которой связаны между собой, а также избежать ее дублирования. Это обеспечивается с помощью так называемой нормализации.
Нормализация базы данных — это формальная технология, позволяющая преобразовать базу данных к одной из нормальных схем (или нормальных форм, сокращенно записываемых как NF).
Нормальная схема — это совокупность требований к организации взаимосвязанных реляционных таблиц, обеспечивающая, минимальную избыточность информации в базе данных.
Первичный ключ — это элемент данных, уникальный для каждой записи и однозначно ее идентифицирующий.
Первая нормальная форма (1NF) предполагает требование, согласно которому в записях данных должны быть выделены первичные ключи и исключено дублирование. Это достигается распределением информации по разным таблицам.
Вторая нормальная форма (2NF) дополнительно предполагает требование, согласно которому все без исключения вторичные значения в записях должны полностью зависеть от первичного ключа.
Третья нормальная форма (3NF) подразумевает, что и сами вторичные значения взаимно независимы.
Технология нормализации представляет из себя последовательное разбиение существующих таблиц на несколько новых. В идеале, как рекомендовал Кодд, этот процесс повторяется вплоть до получения простейших неделимых элементов, «атомов». В современных СУБД обычно удается поддерживать форму 3NF и, тем самым, обеспечивать эффективный доступ к нормализованным таблицам с помощью языка запросов SQL1.
Нормальные формы разработаны до шестой формы (6NF), в которой введено понятие временного измерения. Однако поддержка нормальных форм более высокого уровня, нежели третья, сопряжена со значительными технологическими сложностями.
Основные недостатки технологии нормализации связаны со снижением производительности в обработке запросов к полностью нормализованным таблицам. Так, чтобы найти контракт клиента А, требуется сначала отыскать этого клиента в соответствующей таблице, получить первичный ключ для его записи, затем выполнить поиск в другой таблице, хранящей контракты, чтобы отобрать по значению ключа соответствующие записи. Поэтому были предприняты попытки организовать работу с так называемыми денормализованными данными. База данных пополнялась дополнительной информацией (индексами), или же нормализация не выполнялась вообще. При этом, однако, возникала опасность несоблюдения принципа полноты и непротиворечивости информации, а сложность обработки и реализации ненормализованных данных «съедала» все потенциальные преимущества в быстродействии.
От реляционных СУБД к объектным
Некоторые из денормализующих подходов оказались достаточно успешными. Так, концепция многомерных СУБД подразумевает условное хранение всех возможных комбинаций данных из связанных таблиц в виде многомерного куба, доступ к каждому элементу которого осуществляется по указателю. Например, для вышеприведенного примера достаточно найти указатель, хранящий адрес физической позиции на диске, где находится нужный контракт (по параметру «клиент А»), Тогда считывание нужного значения произойдет за одну физическую операцию обращения к диску. В качестве примера успешной реализации многомерной концепции можно привести СУБД Pich.
В начале 1980-х годов большую популярность на рынке завоевала СУБД Oracle, которая вступила в многолетнюю конкурентную гонку с СУБД IBM DB2, продолжающуюся и по сей день. В конце 1990-х и начале 2000-х годов на третье место на рынке крупных СУБД (следом за Oracle и IBM), вышла корпорация Microsoft со своим решением MS SQL Server, созданным на базе SQL Server компании Sybase (который в свою очередь являлся версией СУБД INGRES). Впрочем, современная версия MS SQL Server практически полностью переработана и уже давно не похожа на своего прародителя. Она, в частности, привлекается в качестве основы для файловой системы WinFS, анонсированной компанией Microsoft для новых версий Windows. Данные в WinFS будут храниться не в «плоских» файлах и случайных цепочках кластеров на жестком диске, а в таблицах СУБД, что существенно повысит надежность и эффективность файловой системы.
С развитием Интернета большую популярность приобрели свободно распространяемые реляционные СУБД PostgreSQL и MySQL. Последняя хорошо известна разработчикам веб-приложений.
Следующим шагом в развитии СУБД стала концепция объектно-ориентированных хранилищ, данных. Массовость компьютеров росла, и СУБД обрабатывали данные все более и более сложных структур. В 1990-е годы получили развитие объектные СУБД2, которые хранили не табличные данные, а наборы взаимосвязанных объектов (объектная модель базы данных ближе к сетевой и многомерной
2 К популярным объектно-ориентированным СУБД относятся, в частности, продукты ObjectDB и Cache.
моделям, нежели к реляционной). Например, объект «клиент А» может содержать в качестве значения одного из своих полей прямую ссылку на объект с контрактом. Кроме того, вместе со значениями полей в объектной базе сохраняются также и методы (двоичный исполнимый код). Эти методы можно запускать на выполнение на сервере (сервере баз данных или специально выделенном промежуточном сервере приложений).
В среде Borland Developer Studio имеются средства объектно-реляционной раскладки, позволяющие получать на основе реляционной структуры объектную схему и наоборот. Каждой таблице ставится в соответствие свой объект (класс), связанный с другими классами отношениями, заданными в модели базы данных.
К недостаткам объектных решений относятся: сложность эксплуатации, отсутствие общепризнанных стандартов, не всегда высокая эффективность языков запросов к данным, организованным сложным образом.
Требования к современным СУБД
Наиболее популярными по сей день остаются классические реляционные системы. Все другие технологии излишне сложны в эксплуатации и сопровождении, а нехватка квалифицированных кадров подталкивает к массовому внедрению пусть не самых производительных, зато стандартных решений. Дополнительную популярность реляционным системам придает развитый набор функциональных возможностей и удобные средства администрирования, которые в значительной степени компенсируют недостатки реляционного подхода.
К типичным и наиболее востребованным возможностям современных СУБД относятся:
работа нескольких серверов в сети с управлением из единой административной консоли;
поддержка баз данных, физически размещенных на нескольких компьютерах;
устойчивая круглосуточная работа, выполнение административных работ в «горячем» режиме без отключения системы;
встроенные средства обеспечения целостности хранимой информации, ее архивирования и восстановления;
представление логической структуры базы в виде визуальных диаграмм и возможность манипулирования структурами таблиц в визуальных проектировщиках;
возможность публикации данных в разных сетевых форматах (например, ХМЬиНТМЬ);
наличие средств преобразования, импорта и экспорта данных в произвольных форматах, в частности в форматах прикладных программ и других СУБД;
поддержка хранимых процедур3;
3 Хранимая процедура — это программа, код которой хранится в базе данных. Чаще всегс хранимые процедуры представлены в виде сценариев SQL, которые хранятся в базе вме сте с таблицами и выполняются сервером при наступлении определенных событий илг вызываются напрямую из клиентских приложений.
возможность определения пользовательских типов данных для полей таблиц;
работа с большими двоичными объектами (мультимедийной информацией), геоинформационными, картографическими и другими нестандартными типами данных;
безопасность при работе с базами данных. В базах часто хранится важная личная, корпоративная, а то и секретная информация, поэтому в коммерческих продуктах доступ к содержимому таблиц ограничивают. Каждый пользователь получает идентификатор для подключения к СУБД. По этому идентификатору определяется список таблиц или даже отдельных полей, доступных для просмотра или редактирования. Хорошие СУБД также обеспечивают шифрование хранимых данных, причем при любых операциях обработки таблиц. Администратор системы может производить любые манипуляции с наборами данных, однако все они представлены в зашифрованном виде, и опасность хищения базы значительно снижается.
Проектирование баз данных
Любая корпоративная или административная информационная система подразумевает активное использование баз данных. Поэтому в современных СУБД их проектированию уделяется все возрастающее внимание.
Проектирование базы данных заключается в описании ее логических и физических элементов, в создании структуры таблиц и организации взаимосвязей между ними, а также в создании запросов, предназначенных для отбора и редактирования данных в наборах базовых таблиц.
Разработка программного кода, обрабатывающего содержимое таблиц и воспроизводящего информацию в прикладных пользовательских формах, требует «линейного» мышления в стиле классических пошаговых императивных алгоритмов. А вот проектирование базы данных, продумывание структуры таблиц и взаимосвязей между ними, формирование запросов SQL подразумевает уже не линейную логику, а «двумерное» оперирование понятиями теории множеств (комбинирование массивов разной внутренней структуры). В средних и крупных прикладных системах, использующих базы данных, задействованы сотни, тысячи и десятки тысяч таблиц, между которыми сформировано примерно такое же число взаимосвязей. Поэтому даже небольшие неаккуратности в их построении могут привести к резкому ухудшению производительности системы из-за уже упоминавшихся недостатков реляционного подхода. Например, один запрос SQL, применяемый к базам данных с одинаковой логической структурой, но с разной внутренней организацией таблиц и их связей, может выполняться и секунды, и десятки часов.
В ходе проектирования выполняются действия, имеющие существенное отношение к рассмотренному выше процессу нормализации. Ведь он ориентирован на исключение противоречивости и дублирования данных — основных причин снижения производительности СУБД.
Общая идея проектирования подразумевает разделение (расщепление) таблиц таким образом, чтобы автономная в смысловом плане информация хранилась в отдельной таблице (обычно называемой справочником). Если, например, имеется набор данных по заказам, каждая запись которого содержит наборы полей по клиенту и по исполнителю, то, очевидно, большой объем сведений дублируется. Ведь сведения как об одном клиенте, так и об одном исполнителе могут многократно повторяться в разных комбинациях. Если потребуется модифицировать описание клиента, придется найти и откорректировать все записи, в которых он упомянут.
КЛИЕНТЫКЛИЕНТЫИСПОЛНИТЕЛИИСПОЛНИТЕЛИ001001002002003003Иванов :Иванов :Петров iПетров iКозлов :Козлов :ПЕРВИЧНЫЙ КЛЮЧПЕРВИЧНЫЙ КЛЮЧСтепановСтепанов001001Кузнецов iКузнецов i002002003003СидоровСидоровПЕРВИЧНЫЙ КЛЮЧПЕРВИЧНЫЙ КЛЮЧ001Шарики : :'002Ролики : :003Шпунтики; : '001Шарики : :'002Ролики : :003Шпунтики; : 'ИЗДЕЛИЯИЗДЕЛИЯПЕРВИЧНЫЙ КЛЮЧПЕРВИЧНЫЙ КЛЮЧ
КЛИЕНТЫ
КЛИЕНТЫ
ИСПОЛНИТЕЛИ
ИСПОЛНИТЕЛИ
001
001
002
002
003
003
Иванов :
Иванов :
Петров i
Петров i
Козлов :
Козлов :
ПЕРВИЧНЫЙ КЛЮЧ
ПЕРВИЧНЫЙ КЛЮЧ
Степанов
Степанов
001
001
Кузнецов i
Кузнецов i
002
002
003
003
Сидоров
Сидоров
ПЕРВИЧНЫЙ КЛЮЧ
ПЕРВИЧНЫЙ КЛЮЧ
001
Шарики : :'
002
Ролики : :
003
Шпунтики; :
'
001
Шарики : :'
002
Ролики : :
003
Шпунтики; :
'
ИЗДЕЛИЯ
ИЗДЕЛИЯ
ПЕРВИЧНЫЙ КЛЮЧ
ПЕРВИЧНЫЙ КЛЮЧ
КЛИЕНТЫ
ИСПОЛНИТЕЛИ
ИЗДЕЛИЯ
Иванов : :
Степанов ; :
Шарики : :
Иванов : :
Кузнецов : i
Ролики ; :
Петров : :
Кузнецов : :
Шарики : :
Петров :
Сидоров : i
Ролики : :
ИСХОДНАЯ ТАБЛИЦА
Поля описания клиентов
Поля описания исполнителей
Поля описания заказов
Клиенты
Исполнители
Изделия
001
001
001
001
002
002
002
002
001
002
003
002
ОСНОВНАЯ ТАБЛИЦА
Рис. 33.1. Расщепление исходной таблицы на три справочника и таблицу связей
Для устранения избыточности следует создать два новых справочника: один по клиентам, другой по исполнителям. Описание заказов ведется в отдельной
таблице. В нее помимо прямой информации о заказе (дата, сумма, товар) включаются первичные ключи (уникальные идентификаторы) соответствующих записей о клиенте и исполнителе. При этом любая модификация их описаний подразумевает внесение изменений лишь в один справочник — таблица заказов останется неизменной. Когда же потребуется подробная информация о заказе, детальные сведения о клиенте и исполнителе будут динамически извлечены из соответствующих справочников на основании их ключей.
Появление слишком большого числа таблиц, однако, тоже может стать проблемой. В этом случае создание и сопровождение системы усложняется, а ее производительность снижается. При выполнении объемных запросов SQL серверу придется обследовать множество таблиц, физически расположенных в разных местах жесткого диске. Поэтому при расщеплении наборов данных на новые таблицы обычно придерживаются формального процесса нормализации и прерывают его на второй или третьей нормальной форме.
Этапы нормализации
Подготовка исходных данных
Исходный набор данных, предназначенный для нормализации и последующего представления в реляционной базе данных, может представлять собой обычный текстовый файл или даже комплект бумажных документов, описывающих исходную информацию в виде, понятном и привычном для человека.
Снижение размерности — первая нормальная форма
Обычно в каждой отдельной записи исходных данных содержится несколько однотипных величин: номеров банковских счетов или телефонов, имен и фамилий контактных лиц. Такую информацию можно считать многомерной — в отдельном поле базы данных хранится не одно, а несколько значений.
Задача проектировщика — преобразовать записи с несколькими значениями в одном поле в набор однородных записей, различающихся лишь значением этого поля. В итоге размерность данных снижается, но их избыточность повышается — ведь в набор входят всевозможные комбинации исходных данных.В результате проектировщик получает таблицу в первой нормальной форме.Задача проектировщика — преобразовать записи с несколькими значениями в одном поле в набор однородных записей, различающихся лишь значением этого поля. В итоге размерность данных снижается, но их избыточность повышается — ведь в набор входят всевозможные комбинации исходных данных.В результате проектировщик получает таблицу в первой нормальной форме.ШарикиСтальные, крепкие, : пластмассовые, мягкие •РоликиСтальные, крепкие,:деревянные, легкие■ШарикиСтальные, крепкие, : пластмассовые, мягкие •РоликиСтальные, крепкие,:деревянные, легкие■ШарикиСтальные, крепкиеШарикиПластмассовые, мягкиеРоликиСтальные, крепкиеРоликиДеревянные, легкиеШарикиСтальные, крепкиеШарикиПластмассовые, мягкиеРоликиСтальные, крепкиеРоликиДеревянные, легкие
Задача проектировщика — преобразовать записи с несколькими значениями в одном поле в набор однородных записей, различающихся лишь значением этого поля. В итоге размерность данных снижается, но их избыточность повышается — ведь в набор входят всевозможные комбинации исходных данных.
В результате проектировщик получает таблицу в первой нормальной форме.
Задача проектировщика — преобразовать записи с несколькими значениями в одном поле в набор однородных записей, различающихся лишь значением этого поля. В итоге размерность данных снижается, но их избыточность повышается — ведь в набор входят всевозможные комбинации исходных данных.
В результате проектировщик получает таблицу в первой нормальной форме.
Шарики
Стальные, крепкие, : пластмассовые, мягкие •
Ролики
Стальные, крепкие,:
деревянные, легкие■
Шарики
Стальные, крепкие, : пластмассовые, мягкие •
Ролики
Стальные, крепкие,:
деревянные, легкие■
Шарики
Стальные, крепкие
Шарики
Пластмассовые, мягкие
Ролики
Стальные, крепкие
Ролики
Деревянные, легкие
Шарики
Стальные, крепкие
Шарики
Пластмассовые, мягкие
Ролики
Стальные, крепкие
Ролики
Деревянные, легкие
Выделение ключевых полей — вторая нормальная форма
В нормализованной таблице выделяются ключевые поля, к которым привязываются все остальные поля. Эта работа подразумевает интуитивное понимание про-
ектировщиком предметной области, для которой строится база данных. В качестве первичного ключа обычно выбирается специально формируемое поле таблицы с уникальными, неповторяющимися значениями.
Остальные поля таблицы должны быть объединены по смыслу. Например, группа полей образующая отдельную таблицу с собственным первичным ключом может содержать поля, полностью и однозначно идентифицирующие клиента, исполнителя и заказ. В результате получается база данных, приведенная ко второй нормальной форме (см. рис. 33.1).
Выделение вторичных связанных признаков — третья нормальная форма
В системах, где каждая таблица насчитывает несколько десятков полей, вторая нормальная форма обеспечивает удовлетворительную производительность. Однако если объем информации велик, то внутри понятия, представленного схемой таблицы, обычно существуют дополнительные взаимосвязи. Если, например, полное имя клиента состоит из трех полей: Имя, Фамилия и Отчество, — их можно, в свою очередь, вынести в отдельную таблицу. В исходной таблице вместо трех текстовых полей потребуется ввести одно, хранящее значение первичного ключа из таблицы с именами. При полном разделении таких вторичных полей получается третья нормальная форма. Этот процесс также во многом интуитивен и подразумевает выделение смысловых элементов во вторичных данных.
ИСХОДНАЯ ТАБЛИЦА
Шарики Шарики Ролики РоликиШарики Шарики Ролики РоликиСтальные, крепкие Пластмассовые, мягкие Стальные, крепкие Деревянные, легкие
Шарики Шарики Ролики Ролики
Шарики Шарики Ролики Ролики
ОСНОВНАЯ ТАБЛИЦАОСНОВНАЯ ТАБЛИЦАШарики Шарики Ролики РоликиШарики Шарики Ролики Ролики001002001003001002001003001002003001002003Рис. 33.2. Приведение к третьей нормальной формеРис. 33.2. Приведение к третьей нормальной форме
ОСНОВНАЯ ТАБЛИЦА
ОСНОВНАЯ ТАБЛИЦА
Шарики Шарики Ролики Ролики
Шарики Шарики Ролики Ролики
001
002
001
003
001
002
001
003
001
002
003
001
002
003
Рис. 33.2. Приведение к третьей нормальной форме
Рис. 33.2. Приведение к третьей нормальной форме
ПОДЧИНЕННАЯ ТАБЛИЦА
Стальные, крепкие Пластмассовые, мягкие Деревянные, легкие
Метод проектирования «сущность-связь»
Помимо классической технологии нормализации существует также метод «сущность-связь» (Entity-Relationship, ER), предложенный в 1976 г. Питером Ченом (Peter Chen), ученым из Массачусетского технологического института. Этот метод расширил принципы нормализации и послужил основой создания систем визуального проектирования баз данных (Computer-Aided Software Engineering, системы CASE).
В рамках ER-подхода в системе, подлежащей реляционному представлению, предварительно выделяются смысловые сущности (клиент, исполнитель, заказ, товар) и между ними задаются связи. Такие схемы отношений удобно визуализировать в виде диаграмм ER.
Проектировщик при их создании использует привычное представление, отвлекаясь от низкоуровневых понятий и нюансов построения баз данных. Готовая диаграмма ER может быть автоматически переведена в полноценную нормализованную базу данных (набор физических таблиц и взаимосвязей между ними).
Основным отличительным элементом диаграмм ER считается так называемая степень связи. Она определяет, в каком количественном отношении связи находятся две сущности. Например, каждый клиент может иметь только один заказ. Тогда в таблицу клиентов достаточно добавить одно поле, хранящее первичный ключ записи в таблице заказов. В таком случае отношения между двумя сущностями (таблицей клиента и таблицей заказа) называется отношением один к одному — каждому значению одной таблицы соответствует ровно одно значение другой таблицы (рис. 33.3).
На практике гораздо чаще встречается отношение один ко многим,. Например, один клиент может иметь много заказов, но каждый заказ сделан только одним клиентом. Тогда приходится создавать минимум две таблицы. Одна содержит описания заказов и включает поле для значения первичного ключа клиента (значения в этом поле могут повторяться), а вторая хранит описания клиентов.
Если подразумевается партнерство, в котором у клиента может быть несколько исполнителей его требований, а у исполнителя — несколько клиентов, придется создавать три таблицы. Две из них представляют собой справочники по клиентам и исполнителям, а третья хранит пары ключей «клиент — исполнитель». При этом в ней допускается повторение значений в любом из столбцов. Такое соотношение называется многие ко многим,.
Проектирование баз данных как искусство
При создании крупных систем сложно соблюсти баланс между производительностью с одной стороны и прозрачностью обшей структуры базы данных, разумным числом таблиц, защитой от ввода дублирующихся и противоречивых данных с другой стороны. Готовые алгоритмы здесь не помогают, поэтому успеха чаще добиваются проектировщики с большим опытом исполнения реальных проектов.
001
001
001
002
002
001
002
002
001Иванов ■002Петров001Иванов ■002ПетровСВЯЗЬ «МНОГИЕ ко многим»СВЯЗЬ «МНОГИЕ ко многим»001Винтики002Шпунтики001Винтики002Шпунтики
001
Иванов
■
002
Петров
001
Иванов
■
002
Петров
СВЯЗЬ «МНОГИЕ ко многим»
СВЯЗЬ «МНОГИЕ ко многим»
001
Винтики
002
Шпунтики
001
Винтики
002
Шпунтики
Рис. 333. Три формы степени связи
Ведь процесс построения базы данных подразумевает не только проектирование отношений между сущностями в дизайнерах диаграмм UML или ER. Нужна еще периодическая трансляция логической схемы в физическую базу данных, заполнение ее тестовыми значениями, прогон запросов SQL, выявление узких мест (современные СУБД содержат развитые средства мониторинга производительности) и последующую корректировку исходной логической схемы. Такие операции повторяют до тех пор, пока не будут достигнуты заданные характеристики производительности системы. В общем случае по мере роста проектируемой системы на первый план выходят задачи оптимального управления сложным проектом.
Вопросы
Что такое нормализация данных?
Как устроены объектные СУБД?
3.
4.
5.
6.
Какими требованиями характеризуются современные СУБД?
В чем важность этапа проектирования базы данных?
Каковы практические этапы нормализации до третьей нормальной формы?
Какие формы отношений между сущностями поддерживаются в подходе «сущность-связь»?
ГЛАВА 34
Технологии работы с файлами
Файловая форма считается одной из самых распространенных схем хранения данных на внешних носителях. Почти все другие схемы хранения информации в конечном итоге базируются на файлах. Так, системы управления базами данных физически хранят свои таблицы с наборами записей именно в файлах.
Файл — это совокупность данных на внешнем носителе, идентифицируемая именем и дополнительными атрибутами, набор которых определяется операционной системой.
Прикладная программа работает с содержимым файла, которое может быть организовано достаточно сложно. Но физически файл всегда представляет собой линейную последовательность байтов, записанных на определенный носитель, например в цепочку кластеров жесткого диска. Файловая система автоматически связывает такую цепочку с именем файла.
Типизированные и нетипизированные файлы
Файлы, обрабатываемые программой, могут быть типизированными и нетипизи- рованными.
Типизированный файл содержит последовательность значений данных, относящихся к определенному типу.
Значение определенного типа, хранящееся в файле, называется записью. Каждая из записей файла имеет фиксированный размер в байтах. Размер этот одинаков для всех записей, что упрощает обработку файла. Всегда известно, в какой позиции относительно начала файла располагается определенная запись.
Нетипизированный файл — это линейная последовательность байтов, смысл которой определяет только программист. Структура этой последовательности задается исключительно логикой функционирования приложения, предназначенного для работы с данным файлом.
Если нетипизированный файл должен хранить двоичные наборы данных, размеры которых заранее неизвестны, то структура такого файла может быть организована как последовательность записей переменной длины в формате «длина следующего блока данных — блок данных указанной длины».
Пусть, например, разработчик решил, что первые 100 000 байтов в файле размером 450 000 байтов отводятся на хранение музыкальной записи. Следующие 50 000 байтов выделены для хранения текстового документа, очередные 300 000 байтов отведены для записи в них содержимого внутреннего массива программы. Таким образом, в файле выделены три записи разного размера. Но физически данный файл представляет собой последовательность из 450 000 байтов, которая формально никак не упорядочена.
ГЫГЫ
2567321
Звукозапись длиной 2567321 байт
51
Название композиции, 51 байт
Рис. 34.1. Возможная схема организации нетипизированного файла
В языке C++ нет стандартных средств, явно различающих работу с типизированными и нетипизированными файлами. Однако основные стандартные функции можно использовать для организации работы с файлом в любом из нужных форматов.
Файловые операции
Программа, написанная на языке C++, взаимодействует с файлами через системные функции Windows. Поэтому специфика стандартных возможностей языка по работе с файлами напрямую связана с механизмами поддержки файловой системы Windows. В общем случае технология работы с любым файлом сводится к трем стандартным шагам: открытию файла, его последующей обработке и закрытию.
Открытие файла
Перед началом работы файл должен быть открыт. Программа, открывая файл, сообщает операционной системе, что на некоторое время монопольно захватывает его для обработки.
После открытия файла программа может считывать данные, записывать данные в файл или выполнять обе эти операции произвольном порядке. Тип файловой операции задается при открытии файла — предусмотрены три варианта открытия файла.
На чтение. Данные из файла разрешено только считывать, его содержимое не изменится.
На запись. Старое содержимое файла уничтожается и полностью заменяется новым. Возможно и открытие файла на дозапись — тогда новая информация заносится в конец файла, а существующая остается.
На чтение-запись. Допускаются оба вида операций, причем порядок их выполнения произволен.
Если файл открывается на запись или на чтение-запись, то, пока программа работает с ним, другие приложения не смогут обращаться к этому файлу.
В некоторых случаях разрешена модификация одного файла разными программами. Современные версии системы Windows допускают создание так называемых share-файлов.
Share-файл — общедоступный файл, с которым могут работать несколько программ одновременно.
Хранимые данные можно как считывать, так и изменять одновременно сразу несколькими программами. Share-файлы полезны при создании крупных систем, состоящих из множества одновременно выполняемых компонентов. Если требуется протоколировать их работу, создается общий share-файл протокола. Каждый компонент в произвольные моменты времени записывает в него текстовый блок, определяющий текущее состояние компонента.
Обработка файла
В ходе обработки выполняются операции считывания из файла определенного количества информации или записи блока информации в файл.
Закрытие файла
Когда файл закрыт, программа уже не может изменять его содержимое или считывать из него данные. Файл переходит в обычное состояние и становится доступным для других приложений системы.
В современных операционных системах ввод-вывод данных часто выполняется в режиме буферизации. Данные, которые были записаны в файл, не сразу попадают на жесткий диск. Они временно размещаются системой Windows в оперативной памяти, в специальной области, называемой буфер ввода-вывода. Такие буферы предназначены для накопления в них больших блоков данных, которые затем с помощью одной физической операции записи переносятся на жесткий диск. Такая процедура значительно ускоряет файловый обмен — ведь обращение к накопителям на жестких дисках в сотни и тысячи раз медленнее, нежели обращение к оперативной памяти.
Режим буферизации при работе с файлом подразумевает промежуточное накопление данных, предназначенных для записи в файл, в специальной области оперативной памяти (буфере ввода-вывода). Режим буферизации существенно ускоряет работу с файлом.
Но буферизация ввода-вывода таит в себе определенную опасность. Если в системе произойдет сбой или программа по каким-то причинам не закроет файл, данные, накопленные в буфере, пропадут, а структура файла на жестком диске может
быть испорчена. Поэтому при закрытии файла, вся информация из системного буфера Windows в обязательном порядке записывается на жесткий диск, вне зависимости от ее объема.
Процесс принудительно переноса содержимого буфера в физический файл называется вталкивание (flush). Во многих стандартных библиотеках существуют функции вталкивания, которые переносят содержимое буфера в файл, не дожидаясь, пока операционная система выполнит эту операцию.
Общая технология работы с файлами в системе C++Builder
Мы рассмотрели общую технологию работы с файлами. При создании программ на языке C++, которые работают с файлами, надо придерживаться более детального подхода. Дополнительные шаги вызваны необходимостью учета особенностей файловой системы Windows.
Для работы с файлом создается переменная, которая хранит системное значение, однозначно идентифицирующее файл.
Эта переменная связывается с полным именем файла, записанным с учетом действующих системных соглашений.
Файл открывают обращением к стандартной процедуре или функции, которая заносит значение системного идентификатора файла в файловую переменную.
Обработку содержимого файла ведут с помощью файловой переменной.
Файл закрывают.
Файловая переменная — это переменная файлового типа. Она хранит значение, позволяющее однозначно определить нужный файл.
Технология работы с типизированными файлами
Типизированные файлы содержат информацию, внутренняя структура которой организована единым способом. Поддержка такой структуры далеко не всегда реализуется на уровне стандартных функций ввода-вывода. Нередко единообразную организацию файла поддерживает программист в рамках своей прикладной программы. Для этого достаточно использовать функции обмена для нетипизи- рованного формата, но записывать и считывать все данные по одному общему шаблону.
Как правило, для обработки файлов используют стандартную библиотеку ввода-вывода stdio.h, поддерживаемую практически всеми компиляторами C++.
Объявление файловой переменной
Файловая переменная описывается с помощью типа данных FILE. В файловую переменную записывается указатель на структуру FILE, хранящую подробную информацию об открытом файле. Например:
FILE* fi;
Открытие файла
В момент открытия файла необходимо определить способ его открытия (на запись или чтение), задать местонахождение файла и связать с ним файловую переменную. Эти действия выполняет стандартная функция f open ():
FILE *fopen(const char *filename, const char ‘mode);
Первый параметр — строка, определяющая местонахождение файла (полный или относительный путь).
Второй параметр в виде строки определяет способ открытия файла и принимает одно или допустимую комбинацию значений, приведенных в таблице.
Таблица 34.1. Режимы открытия файла
Значение
Описание
г
Открыть файл на чтение
W
Открыть файл на запись. Если файл существует, его содержимое удаляется. Если файл не существует, он создается
а
Открыть файл на дозапись. Данные в нем сохраняются, а вводимые величины помещаются в конец файла
г+
Открыть существующий файл для модификации (чтения и записи)
W+
Открыть или создать файл для модификации (чтения и записи). Если файл существует, его содержимое удаляется
t
Файл открывается на обмен в текстовом формате (символы перевода и новой строки трактуются как окончание строки). Этот режим подразумевается по умолчанию
b
Файл открывается на обмен в двоичном режиме
Функция возвращает указатель на структуру FILE, описывающую открытый файл. Если открыть файл не удалось, возвращаемое значение равно нулю.
Например:
FILE* fi;
fi = fopen("data.txt" , "rt");
if ( fi == 0 ) // обработка ошибки ...
Обмен данными с файлом
Для побайтной обработки нетипизированных файлов или организации обмена крупными блоками в типизированных файлах используются функции ввода fread() и вывода fwrite (). Они имеют одинаковый список параметров и отличаются лишь именами:
size_t fread(void *ptr, size_t size, size_t n, FILE ‘stream);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE ‘stream);
Тип данных size_t представляет собой беззнаковое целое, используемое для определения результата операции sizeof () (размер объекта в байтах).
Первый параметр каждой функции — указатель на буфер программы, из которого данные считываются для записи в файл или помещаются при считывании из файла. Второй параметр — размер такого блока данных, третий параметр — число подобных блоков данных. Функция возвращает число введенных или записанных блоков данных или 0 в случае ошибки или невозможности выполнить операцию обмена.
Нередко двоичные файлы требуется обрабатывать посимвольно или побайтно (как двоичный поток). В таком случае в функции обмена в качестве размера блока используют значение 1 (один байт).
Закрытие файла
По завершении работы с файлом его закрывают командой:
fclose(имя-файловой-переменной) ;
Проверка завершения файла
При работе с файлом его полная длина может быть неизвестна. При попытке прочитать из файла данные, которых в нем нет, обнаруживается завершение файла, которое обрабатывается как исключительная ситуация.
Завершение файла — ситуация, когда при попытке считать очередную порцию данных: встретится конец файла (файл закончился).
Система C++Builder позволяет проверить наличие данных в файле без возникновения исключительной ситуации. Для этого надо воспользоваться функцией:
feof(имя-файловой-переменной);
Эта функция возвращает ненулевое значение, если после выполнения предыдущей операции ввода был достигнут конец файла.
ПРИМЕР 34.1
Запись ста целых чисел в файл
В данном примере выполняется последовательная запись в файл ста целых чисел.
// декларация файловой переменной:
FILE* Fil;
int i ;
// открытие файла на запись:
Fil = fopen ("OUT. DAT" , "wb" ) ;
// цикл на 100 повторений for ( i = 1; i < = 100; i ++ ) { // записываем текущее значение // переменной i в файл:
fwrite(&i, sizeof(i), 1, Fil);
}
// закрываем файл:
£ close (Fil.) ;
Операция fwrite() выполняет многократную запись целого значения в формате компилятора C++ - таким образом формируется структура типизированного файла, состоящего из значений типа int.
ПРИМЕР 34.2
Считывание содержимого файла с целыми значениями
В данном примере ранее созданный типизированный файл считывается программой поэлементно, и каждое значение анализируется отдельно.
// декларация файловой переменной:
FILE* Fil;
int i ;
// открытие файла на чтение:
Fil = fopen("OUT.DAT","rb");
// пока не встретился конец файла...
while( Ifeof(Fil) )
{
// считываем из файла очередное значение
/ / в переменную i:
fread(&i, sizeof(i), 1, Fil);
// закрываем файл: fclose(Fil);
В общем случае тип переменной i в этих примерах может быть произвольным (например, представлять собой структуру). Никаких дополнительных модификаций в данный код вносить не требуется: в файл будет записано и затем считано нужное количество байтов, заданное функцией sizeof() (размер объекта в байтах).
Технология работы с нетипизированными файлами
Технология работы с нетипизированными файлами схожа с подходом, описанным в вышеприведенных примерах. Можно считать, что в них фактически и ведется работа с нетипизированным файлом, содержащим набор значений одного типа int.
Нетипизированными, например, считают исполнимые файлы EXE или музыкальные файлы WAV, структура которых воспринимается ограниченным набором приложений. Содержимое нетипизированных файлов обычно представляет собой блоки данных разной длины.
Работа с файловым указателем
Когда программа работает с файлом, механизмы операционной системы контролируют текущую позицию в файле — место, начиная с которого будет выполняться очередная операция чтения или записи. Указатель текущей позиции принято называть файловым указателем.
Файловый указатель определяет, с какого байта внутри файла произойдет очередное считывание или запись данных. Файловый указатель поддерживается системными механизмами Windows.
Когда записи последовательно считываются из файла или записываются в файл, файловый указатель автоматически смещается на нужное число байтов. При обработке нетипизированного файла нередко возникает необходимость считать данные, начиная с определенной позиции. Иногда требуется модифицировать несколько байтов внутри файла, а остальное его содержимое меняться не должно. В таких операциях файловый указатель задействуется явно.
Функция f seek () устанавливает файловый указатель в нужную позицию:
int fseek(FILE *stream, long offset, int mode);
После ее выполнения указатель в файле stream перемещается в позицию, заданную параметром of f set, который содержит порядковый номер байта (отсчет начинается с нуля). После того как файловый указатель установлен в новую позицию, выполняют нужное действие по модификации соответствующего байта. Остальное содержимое файла, не затрагивается.
Последний параметр определяет начало отсчета для параметра offset. Если значение mode равно seek_set, отсчет начинается с начала файла (с нуля), если оно равно seek_end, отсчет ведется от конца файла, если оно равно seek_cur, отсчет ведется от текущей позиции файлового указателя.
ПРИМЕР 34.3
Модификация двоичного файла
Пусть имеется файл password.dat, и в нем надо изменить двенадцатый байт на новое шестнадцатеричное значение 0x43 (символ ‘С’). Опишем файловую переменную Fil и переменную Ь, хранящую значение нужного байта:
FILE* Fil;
byte b = 0x43;
Откроем файл в режиме двоичной модификации с сохранением текущего содержимого:
Fil = fopen("OUT.DAT","r+b" ) ;
После открытия файла необходимо перевести указатель к двенадцатому байту. В качестве смещения в функции fseek() надо задать значение 11, так как отсчет байтов начинается с нуля (первый байт имеет нулевое смещение).
fseek(Fil, 11, SEEK_SET);
Теперь запишем в текущую позицию файла новое значение и закроем файл. Остальное содержимое файла не изменится.
fwrite(&b, 1,1,Fil);
fclose(Fil);
Форматированный ввод-вывод
Нередки ситуации, когда файлы используются для хранения настроечных параметров программы, которые желательно модифицировать вручную. Такой подход позволяет быстро перенастраивать различные элементы программы без внесения изменений в ее исходные тексты и перекомпиляции. Настройки в наглядном текстовом виде хранятся во многих программах, например, операционной системе Windows (файлы WIN.INI, SYSTEM.INI и другие). Но для этого программисту, во-первых, необходимо ввести в приложение функции считывания и записи значений в подобные файлы и их последующего учета в работе, а во-вторых, обеспечить текстовое представление в файле данных разных типов (целых, дробных, символьных значений и так далее).
В библиотеке stdio.h имеются две функции fscanf () и fprintf (), предназначенные для форматированного ввода и вывода:
int fscanf(FILE ‘stream, const char *format, список-аргументов);
int fprintf(FILE ‘stream, const char ‘format, список-аргументов);
Форматированный ввод и вывод значений — это процесс выделения данных разных типов из строки и преобразования данных разных типов в текстовый вид.
Вторым параметром в этих функциях следует строка, которая определяет формат ввода-вывода. За ней вводится список аргументов (этот список можно полностью опускать), которые по этому формату либо вводятся (выделяются из строки), либо выводятся (преобразуются в текстовый вид).
Структура формата ввода-вывода
Формат задается текстовой строкой, в которой различают элементы двух типов: собственно элементы формата ввода-вывода (так называемые спецификаторы формата), определяющие процесс преобразования, и все остальные символы. В случае форматированного вывода они записываются в выходной поток без какой- либо модификации, в случае форматированного ввода они не учитываются.
Спецификаторы формата ввода-вывода выделяются префиксом %. Обычно спецификатор формата записывается в виде
%-w.ds
Знак «-» требует выровнять результат по левому краю. Число w определяет минимальный размер результирующего поля: если итоговое значение меньше этого поля, то оставшиеся позиции заполняются пробелами. Число d используется как показатель точности для дробных значений (число знаков после запятой) или как показатель минимального числа цифр в результате для целых значений. Все элементы спецификатора можно опускать (должно присутствовать значение w или d). В конце спецификатора (позиция s) ставится его название, определяющее тип форматируемого значения. Список наиболее часто используемых названий спецификаторов приведен в таблице 34.2.
Например:
% - 5 . 3 f — вещественный результат должен занимать 5 позиций, быть выровненным по левому полю, для дробной части отводится три позиции;
%4d— целый результат должен занимать 4 позиции;
% 2 0 s — строка умещается в 20 символов;
%. 2 f — вещественный результат произвольной длины с двумя цифрами после запятой.
Таблица 34.2. Основные спецификаторы формата ввода-вывода
Спецификатор
Назначение,
d, i
Десятичное целое число
0
Восьмеричное целое число
X
Шестнадцатеричное целое число
u
Десятичное беззнаковое целое
с
Отдельный символ
s
Строка (должна завершаться нулем)
f
Вещественное (подразумевает задание размера дробной части)
e
Вещественное с десятичной экспонентой
P
Указатель
%
Сам символ %
Форматированный вывод
Значения, которые преобразовываются по заданному формату и попадают в выходной текстовый файл, вводят в конце списка аргументов функции, перечисляя через запятую.
Например:
FILE* Fil;
Fil = fopen("format.txt","wt") ;
int n = 5;
double x = 3.14;
char* s = "строка";
fprintf(Fi1,"n= %d; x=%-6.3f; s=%s;",n,x,s);
fclose(Fil);
В результате в файл format.txt запишется следующая строка:
п= 5; х=3.140; з=строка;
В строку формата можно вводить различные управляющие символы, например, символ перехода на новую строку \п, символ конца строки \0, символ табуляции \ t, а также обратный слэш \ \.
Форматированный ввод
Содержимое текстовой строки, разбитое на элементы пробелами, можно поэлементно считать функцией f scanf (). В строке формата учитываются только спецификаторы с префиксом %, все остальное содержимое игнорируется. Очередному спецификатору функция ставит в соответствие очередной элемент входной строки, отделенный от предыдущего либо пробелом, либо символом новой строки. В списке аргументов вводят не имена переменных, а их адреса.
Пример:
FILE* Fil;
Fil = fopen("format.txtrt");
int n ;
float x;
char s[1024];
fscanf(Fil,"%d %f %s",&n,&x,s);
fclose(Fil) ;
Пусть в файле format.txt записана строка:
15 3.14 строка
Тогда после выполнения данного кода переменная п получит значение 15, переменная х — 3,14, переменная s — значение «строка».
Форматирование строк
Функции форматированного ввода-вывода имеют несколько вариантов, которые используются не только для обработки файлов. Так, функции sprint f () г sscanf () работают схоже с функциями fprintf () и fscanf (), однако их первыг аргумент — не файловая переменная, а символьный массив. В него и заносится результат преобразования по формату, либо из него считывается содержимое г затем поэлементно анализируется.
Например:
char buf[1024] ;
sprintf(buf,"%d; %3.1f; это %s",25, 2.81,"текст");
В результате выполнения этого кода в переменную buf будет занесена строка
"25; 2.8; это текст".
Существуют также версии printf () и scanf (), которые предназначены дл; консольных приложений. Они, соответственно, выводят данные на экран в фор матированном виде либо считывают по формату значения, введенные пользовате лем с клавиатуры.
Вопросы
Что такое файл?
Что такое типизированный файл?
Что такое нетипизированный файл?
Опишите последовательность работы с файлом.
Что такое файловый указатель?
Как работают с типизированными файлами?
Как работают с нетипизированными файлами?
Как организуется форматированный ввод-вывод?
ГЛАВА 35 Объектные технологии работы с файлами
Рассмотренные в предыдущей главе средства работы с файлами существуют в языке C++ (в виде стандартной библиотеки stdio.h) еще со времен самых первых компиляторов. С одной стороны, это удобно, так как позволяет использовать один и тот же программный код в разных проектах. С другой стороны, стандартные средства для работы с файлами создавались много лет назад и не могут учитывать возможности современных операционных систем. Их прямое отображение в системные вызовы нередко синтаксически невозможно. Как следствие, авторы компиляторов изобретают обходные пути для реализации классических возможностей ввода-вывода. Расплачиваться же за это приходится производительностью получаемых программ.
Удобнее всего пользоваться объектными механизмами ввода-вывода, отвечающими современным технологическим концепциям. Компании Borland реализовала принципы объектного программирования в 1990 году в версии Turbo C++ 1.0. Она, в частности, поддерживала стандартную библиотеку stream.h. Входившие в эту библиотеку функции, во-первых, позволяли абстрагироваться от понятия «файл», файловых переменных и идентификаторов, а во-вторых, предоставляли типовые способы обмена данными произвольных типов, в том числе пользовательских (объектных).
Новые средства обмена дают возможность взаимодействовать в рамках единого объектного подхода как с файловой системой, так и с любыми другими источниками данных: с сетевыми устройствами, с внешними хранилищами информации и так далее. Конкретный способ физического хранения данных перестал играть значимую роль. Обмен данных между программой и внешней средой теперь можно выполнять в рамках объектной модели и понятия потока. Любые хранилища данных представляются как поток байтов, который может быть считан из виртуального устройства или записан в него. При этом преобразования типов данных обмениваемых элементов осуществляются автоматически.
Поток ввода-вывода — это объект, который организует побайтовый обмен данными между программой и внешними устройствами хранения.
Потоки, как правило, используют временные буферы в оперативной памяти, в которых накапливаются данные для ввода и вывода. Буферы нужны, чтобы минимизировать объем взаимодействия с физическим устройством ввода-вывода (например, жестким диском), которое работает значительно медленнее, чем оперативная память.
Стандартная библиотека stream.h
Средства, включенные в стандартную библиотеку stream.h, основаны на простой и эффективной модели преобразования данных произвольных типов в символьный вид и обратно. Для вывода данных в поток используется оператор «, для считывания данных из потока используется оператор ». В левой его части размещается имя потока, в правой — либо данные, которые должны быть в него записаны, либо имя переменной, в которую должны быть введены данные из потока. В правой части операторов можно указывать цепочку выводимых данных, разделенных символами « или », например:
cout << X « " ; " << Y ;
В поток вывода cout (стандартный поток для консольных программ, соответствует выводу на экран) помещается значение переменной х, строка " ; " и значение переменной Y.
сin » П;
В переменную п помещается вводимое пользователем значение (в случае консольной программы оно вводится с клавиатуры).
Потоковый обмен в разных системах и компиляторах реализован по-разному. Чаще всего он основан на стандартной библиотеке шаблонов Standard Temlpate Library (STL C++), включающей множество готовых шаблонов для упрощения множества типовых действий (вводу-выводу, работе с коллекциями, списками, множествами, строками и так далее) по обработке данных произвольных типов. Она была стандартизована комитетом ANSI/ISO в качестве одной из библиотек, входящей в поставку любого компилятора C++.
Эта библиотека, в свою очередь, имеет множество конкретных реализаций, привязанных к определенной вычислительной платформе (например, Windows или .NET) и среде разработки. В системе C++Builder имеется реализация STL C++, которая называется Dinkam C++ Library.
35.1.1. Файловый потоковый обмен
Чтобы использовать операторы >> и « при работе с файлами, надо подключить заголовочный файл f stream, ориентированный на взаимодействие с файлами:
#include <fstream>
В нем определены классы if stream и of stream, предназначенные, соответственно, для ввода и вывода данных.
Пусть имеется файл info.txt с следующим содержимым:
15 3.14 строка
Определим в программе экземпляр класса i fstream: ifstream fi;
Открытие потока выполняют вызовом метода open (), параметром которого выступает физический путь к файлу (полный или относительный):
fi.open("infо.txt");
Теперь файл открыт, и для считывания из него данных можно воспользоваться оператором >>. В файле хранятся три значения, и их можно загрузить в программу одной командой:
char s [ 10 2 4 ] ;
int п ;
double х;
f i > > n > > X > > S ;
В результате переменная п получит значение 15, переменная х — значение 3,14, а переменная s — строку "строка".
В заключение файл закрывают вызовом метода close о :
fi.close();
Для вывода данных в файл применяется схожая схема, только используется класс файла для вывода of stream:
ofstream fo;
fо.open("info.txt");
char *s = "привет";
int n = 2 0;
double x = 2.81;
fo << n << " " << x << " " << s;
fО.close();
Данные выводятся в поток «подряд», поэтому для их отделения друг от друга необходимо явно вставлять пробелы. В результирующем файле info.txt появится строка:
20 2.81 привет
Объектная технология потокового обмена VCL
Корпорация Borland реализовала в своих системах программирования оригинальную объектную технологию ввода-вывода. Она поддерживается библиотекой VCL. Объектный обмен данных между программой и внешней средой выполняется с помощью наследников базового класса TStream (поток). В таком потоке допустимы операции установки указателя в заданную позицию и выполнения операций обмена, начиная с этой позиции.
Базовый класс для работы с файлами
Для работы с файлами предназначен класс TFileStream — наследник класса THandleStream, который предоставляет средства работы с источником информации через его идентификатор. Вместе с тем схема объектного процесса работы с файлом остается прежней: сначала файл надо открыть, потом обработать, а затем закрыть.
Если необходимо создать временный файл, хранящий промежуточные значения, можно воспользоваться классом TMemoryStream. Его экземпляры создаются с помощью конструктора без параметров, а остальная функциональность этого класса аналогична возможностям класса TFileStream. Физически временные данные хранятся в оперативной памяти компьютера.
В рамках объектной модели для открытия файла создают экземпляр класса TFileStream, связанный с файлом. Параметры операции указывают в конструкторе объекта. В общем случае необходимо задать путь доступа к файлу и настроить способ открытия. Параметры доступа к файлу обозначаются так же, как в обычных, необъектных процедурах открытия.
Конструктор класса TFileStream имеет два параметра: физический путь к файлу и режим открытия. В следующем примере существующий файл myfile.txt открывается для чтения (такой режим задается константой fmOpenRead):
TFileStream * fi =
new TFileStream("myfile.txt",Sysutils::fmOpenRead);
В результате создается объект, управляющий доступом к нему (экземпляр класса TFileStream). Ссылка на него помещается в переменную f i. Далее к ней можно обращаться, используя свойства и методы класса TFileStream.
Считывание данных из файла происходит при вызове виртуального метода Read() класса TFileStream:
virtual int Read(void * Buffer, int Count);
Его параметр Buffer задает буфер, данные из которого будут записаны в файл, а параметр Count — число байтов, считываемых в этот буфер. Возвращает метод число реально принятых байтов. В прикладном коде используется версия этого метода с именем ReadBuf fer () и аналогичными параметрами.
Запись данных в файл выполняется с помощью виртуального метода write () класса TFileStream:
virtual int Write(const void * Buffer, int Count);
Он возвращает число реально записанных в файл байтов. В прикладном коде используется версия этого метода с именем WriteBuf fer ().
В методах ReadBuf fer и WriteBuf fer удобно использовать следующую форму записи параметров:
(переменная, sizeof(переменная))
В качестве первого параметра указывается переменная, значение которой записывается в файл или считывается из файла. Второй параметр должен содержать число передаваемых байтов, и здесь помогает стандартная функция sizeof О, возвращающая объем значения-параметра в байтах. Например:
TRect гс;
fi->ReadBuffer(&rc,sizeof(rc)) ;
Для позиционирования файлового указателя используется метод seek () класса TFileStream. Он позволяет установить указатель в файле в заданную позицию — смещение указателя определяется параметром offset:
virtual int Seek(int Offset, unsigned short Origin);
Второй параметр Origin показывает, относительно какой позиции задано смещение: от начала файла (soFromBeginning), от текущей позиции указателя (soFromCurrent) или от конца файла (soFromEnd).
Текущее местоположение указателя доступно через свойство Position класса TFileStream, а имя файла, связанного с данным потоком, — через свойство Filename. Свойство size задает размер потока (файла) в байтах.
Для закрытия файла следует обратиться к стандартному методу Free () класса TFileStream, который разрушает объект и удаляет его из памяти, выполняя при этом все действия по корректному освобождению системного идентификатора файла.
ПРИМЕР 35.1
Объектная работа с текстовым файлом
В данном примере информация считывается из середины текстового файла и выводится в надпись на форме:
// открываем файл:
TFileStream * fi =
new TFileStream("my.txt", Sysutils::fmdpenRead);
char buf[1024];
// устанавливаем указатель на середину файла:
fi->Seek(fi->Size/2, soFromBeginning);
И считываем данные:
fi->ReadBuffer(buf,80);
// показываем данные на форме:
Labell->Caption = buf;
// закрываем файл: fi->Free();
Ввод и вывод компонентов
В классе TFileStream имеется важная пара методов WriteComponent() и ReadComponent(). Они позволяют записать в файл и считать из него полное содержимое объекта.
В качестве параметра достаточно задать ссылку на нужный объект.
TComponent * Readcomponent(TComponent * Instance);
void WriteComponent (TComponent * Instance);
Например, если форма содержит объект сложной структуры, все его содержимое можно сохранить в файле:
// создаем файл для сохранения
TFileStream * fi = new TFileStream
("listl.dat",
Classes::fmCreate I Sysutils : :fmOpenWrite) ;
// записываем содержимое списка ListBoxl в файл fi->WriteComponent(ListBoxl);
f i->Free() ;
Для обратного чтения можно использовать следующий код:
TFileStream * fi =
new TFileStream("listl.dat", Sysutils::fmOpenRead);
fi->ReadComponent(ListBoxl);
fi->Free ( ) ;
При этом полностью восстанавливается содержимое списка со всеми значениями свойств.
Копирование файлов
Мощный метод CopyFrom() класса TFileStream позволяет одной командой скопировать содержимое одного файла в другой. Первый параметр задает поток ввода- вывода, содержимое которого копируется во владельца метода CopyFrom(), а второй — число принимаемых байтов. Например:
// задаем исходный файл:
TFileStream * Streaml = new TFileStream ("myfile.dat", Sysutils::fmOpenRead ); // задаем конечный файл:
TFileStream * Stream2 = new TFileStream
( "result.dat",
Classes::fmCreate I Sysutils::fmOpenWrite);
try { // пытаемся скопировать файл:
Stream2->CopyFrom(Streaml, Streaml->Size);
}
finally {
M в любом случае закрываем файл-приемник: Stream2->Free();
// закрываем исходный файл: Streaml->Free();
Классы для типизированного файлового обмена
Неудобство стандартных методов класса TFileStream заключается в том, что они обеспечивают работу с неформатированным потоком, линейной последовательностью байтов. В библиотеке VOL имеется ряд дополнительных классов, которые ассоциируются с потоком и предоставляют развитые средства форматированной записи и считывания данных. К ним относятся, прежде всего, классы TReader и TWriter. В их конструкторе задается родительский поток (экземпляр класса TFileStream) и размер внутреннего буфера в байтах. Его можно задать с запасом, в расчете на несколько копий считываемых или записываемых в поток объектов.
Обмен с родительским потоком можно выполнять с помощью только этих классов. Они содержат удобные методы считывания и записи информации предопределенных типов. Так, для ввода используются методы Readlnteger(), ReadString(),
ReadBoolean(), ReadDate(), ReadCurrency() и другие. Для вывода задействуются их аналоги, только вместо префикса Read ставится префикс Write: Writelnteger(), WriteString() и так далее. В качестве параметра достаточно указать переменную соответствующего типа, что значительно повышает наглядность кода. Например, запрограммируем запись строки в файл myfile.dat:
// открываем файл на запись:
TFileStream * Streaml = new TFileStream ("myfile.dat",
Classes::fmCreate I SysutiIs::fmOpenWrite);
// создаем дополнительный управляющий класс:
TWriter * Writeri = new TWriter(Streaml, 2048);
// записываем строку соответствующим методом:
Writerl->WriteString(Labell->Caption);
// закрываем файлы:
Writerl->Free();
Streaml->Free () ;
Сохранение списков в файлах
Когда в потоке сохраняется последовательность Элементов списка, число которых заранее неизвестно, такую последовательность выделяют специальными маркерами начала и конца списка. Класс TWriter предоставляет специальные методы WriteListBegin() и WriteListEnd() (оба без параметров), которые записывают в поток внутренние коды-маркеры. При считывании списка наличие признака завершения (соответствующего маркера) проверяют методом EndOfList() класса TReader. Например:
// открываем файл на запись:
TFileStream * Streaml = new TFileStream ("mylist.dat", Sysutils::fmOpenRead); // создаем дополнительный управляющий класс: TReader * Readerl = new TReader(Streaml, 2048); // записываем строку соответствующим методом: try { // обрабатываем признак начала списка: Readerl->ReadListBegin(); // выполняем цикл, пока не встретится // признак конца списка: while( ! Readerl->EndOfList() ) // чтение очередной строки:
ListBoxl->Items->Add(Readerl->ReadString());
// обрабатываем найденный признак окончания списка: Readerl->ReadListEnd();
}
finally {
// закрываем файлы:
Readerl->Free();
}
Streaml->Free();
Технология работы с файлами .INI
Одна из технологий хранения текущих настроек программ опирается на использование текстовых файлов специальной структуры. По стандартному расширению имени такие файлы настроек принято называть файлами .INI.
Файл .INI — это текстовый файл, хранящий информацию (как правило, настройки программы) в виде пар «имя = значение», разбитых на разделы.
Первый элемент (имя) в каждой паре называется ключ.
В настоящее время поставщики операционных систем и средств разработки считают идею использования файлов .INI устаревшей. Однако она все еще популярна у программистов всего мира, во многом потому, что файлы .INI, хранящие данные в текстовом виде, легко просматривать и редактировать. Впрочем, именно по этой причине, снижающей общую безопасность и надежность системы, файлы .INI признаны не лучшим решением для хранения настроек.
В библиотеке VCL имеется класс TlniFile (библиотека IniFiles.hpp), который обладает набором методов для гибкой обработки файлов .INI. Свойство у этого класса одно — FileName (имя файла .INI). Его обычно задают как параметр конструктора класса. Если соответствующий файл существует, то он задействуется в качестве источника информации. Если такого файла нет, он создается.
Для считывания данных из файла .INI применяется метод, имя которого начинается с префикса Read. Далее указывается тип значения, например: Readlnteger() (целое значение), ReadString() (строка), ReadBool() (логическое значение).
Параметры метода содержат раздел файла и ключ этого раздела. Можно указать и третий, дополнительный параметр. Он подставляется как возвращаемое методом значение по умолчанию, если заданный раздел или ключ не обнаружены.
Запись пары «имя = значение» в раздел файла .INI выполняет метод WriteString() с тремя текстовыми параметрами: имя раздела, ключ и значение. Но чаще применяют вариант данного метода, напоминающий группу методов типизированного считывания с префиксом Read. При записи данных используется префикс Write.
ПРИМЕР 35.2
Использование файла .INI для хранения настроек приложения
Пусть имеется файл настроек myprog.ini. В нем имеется раздел Window, содержащий набор ключей, описывающих положение окна на экране. Среди них, например, имеется ключ Left, определяющий координату левой границы окна на экране в пикселах (целое число). Для считывания его значения задействуется функция Readlnteger(). Координаты левого края окна загружаются из файла настроек при создании формы:
void fastcall TForml-.: FormCreate (TObject ‘Sender) {
// связываемся с файлом настроек:
TlniFile * MylniFile = new TIniFile("myprog.ini");
// вводим значение ключа Left и на его основе
// устанавливаем форму в заданное место:
Left = MyIniFile->ReadInteger("Window", "Left", 100);
// закрываем файл .INI,
// если он не будет далее использоваться: MyIniFile->Free();
}
Если раздел Window или ключ Left не обнаружены, функция Readlnteger() вернет значение 100, заданное третьим параметром по умолчанию.
Значение в данный ключ можно занести в момент закрытия формы, в обработчике события OnClose:
void fastcall TForml::FormClose
(TObject ‘Sender, TCloseAction SAction) { TIniFile * MylniFile = new TIniFile("myprog.ini"); // записываем в поле Left раздела Window // текущее положение окна: MyIniFile->WriteInteger("Window", "Left", Left); MyIniFile->Free(); )
Вопросы
Как организована объектная работа с файлами?
Что такое поток ввода-вывода?
В чем заключаются особенности потокового обмена, реализованного в библиотеке VCL?
Каковы основные методы класса TFileStream?
Как организуется типизированная объектная работа с файлами?
Как организуется работа с файлами .INI?
ГЛАВА 36
Файл-серверные технологии
Реляционная СУБД представляет собой программный комплекс, обрабатывающий наборы записей фиксированной структуры — таблицы, хранимые в базах данных. Каждая запись представляет собой набор полей, в которых могут храниться значения произвольных типов данных, поддерживаемых конкретной СУБД. Среда C++Builder ориентирована, прежде всего, на работу с такими реляционными системами.
Рассмотрим простейшую, так называемую файл-серверную, архитектуру приложения, взаимодействующего с СУБД.
Файл-серверная архитектура приложения — это архитектура, в которой вся деятельность по выполнению прикладной логики и обработке баз данных происходит на компьютерах пользователей. Сама же база данных может храниться в виде физического набора файлов как на этих же компьютерах, так и на файловом сервере в сети.
Технология работы с файл-серверной СУБД
Файл-серверное приложение выполняет любую операцию за несколько шагов. Ход операции состоит из следующих этапов.
Пользователь обращается к набору данных через элементы интерфейса клиентского приложения.
Приложение формирует запрос к СУБД. Этот запрос чаще всего заранее готовится на языке SQL, который в своей основе не зависит от конкретного типа СУБД, что делает приложение достаточно независимым. Этот запрос должен сформировать набор данных (например, содержимое таблицы базы данных или связанные записи нескольких таблиц, отобранные по определенному условию), содержимое которого будет отображено на форме программы.
Запрос отправляется из приложения к программе-посреднику. Как правило, это системный драйвер конкретной СУБД, который связан с программой, сформировавшей запрос. В ряде случаев драйвер анализирует и проверяет структуру запроса, после чего передает его СУБД.
СУБД обрабатывает запрос: просматривает нужные таблицы, отбирает и компонует записи, при необходимости вносит в них заданные модификации и, если требуется, создает результирующий набор данных. Этот набор
передается программе-посреднику. В файл-серверной архитектуре функции СУБД по обработке запросов SQL обычно берет на себя драйвер.
Драйвер переводит полученный от СУБД набор данных в вид, понятный приложению. Он также контролирует корректное завершение всего процесса, сохранность и целостность передаваемой информации.
В крупных корпоративных системах на первый план выходят задачи сохранности данных, надежности работы всего программного комплекса. Ведь если при выполнении описанной цепочки произойдет сбой (например, откажет прикладное приложение, драйвер или СУБД), процесс модификации информации может прерваться, не завершившись. Это приведет к накоплению в базе данных обрывочных, неполных, противоречивых сведений и в итоге сделает работу с системой невозможной. Поэтому файл-серверная технология считается решением среднего уровня надежности.
Тем не менее, существует немало задач, для которых файл-серверная архитектура приемлема. Как правило, такие программы рассчитаны на ведение индивидуальных баз данных или работу с небольшим числом пользователей. Главное преимущество этой архитектуры состоит в том, что при развертывании файл-сер- верного приложения не требуется устанавливать, настраивать и запускать выделенный сервер баз данных (что само по себе может быть нетривиальной задачей). Вся логика работы реализована в автономной программе. При этом разработчик использует богатый набор основных возможностей среды C++Builder по поддержке баз данных, их проектированию, обработке, организации запросов и так далее.
Технология Borland Data Engine
Технология файл-серверных СУБД реализована в среде C++Builder с помощью специализированного набора компонентов Borland Data Engine (BDE). Он доступен в рамках библиотеки VCL.
Набор компонентов BDE —- это программная прослойка между создаваемым приложением и файл-серверными драйверами. Поддерживается взаимодействие с драйверами для файл-серверных: СУБД (dBase, FoxPro, MS Access, Paradox) и с драйверами технологии ODBC, через которую в Windows поддерживается стандартизованный доступ к базам данных.
При переключении программы с одной СУБД на другую не требуется вносить в проект никаких изменений, кроме модификации значения единственного свойства, задающего драйвер доступа.
Набор данных
Набор данных считается базовым понятием технологии BDE, как, впрочем, и многих других технологий работы с базами данных в C++Builder. Он реализован в библиотеке VCL в виде базового класса DB.TDataSet. На логическом уровне набор данных представляет классическую таблицу базы данных — набор записей одинаковой структуры. Физически набор данных может быть копией реальной таблицы базы данных или результатом работы запроса SQL.
Набор данных — это набор записей фиксированной структуры, составленных из одинакового набора полей.
Прикладные классы, реализующие концепцию набора данных, наследуют свойства и методы абстрактного класса TDataSet. Они ориентированы на конкретные средства доступа к базам данных разных архитектур. Среди наследников класса TDataSet отметим класс TBDEDataSet. Этот набор данных напрямую ориентирован на средства доступа BDE. Однако он, как и класс TDataSet, абстрактный и напрямую в разработке не используется.
Все наследники этого класса поддерживают концепцию указателя набора данных. По отношению к указателю выполняются операции перехода в наборе на запись вперед и назад, редактирование и удаление записи и так далее.
Указатель набора данных — системный элемент, который отслеживает местонахождение текугцей записи таблицы базы данных.
Реляционная таблица
В прикладных проектах для доступа к таблице базы данных применяется компонент ТТаЫе (реляционная таблица), наследник класса TBDEDataSet. Он является базовым элементом файл-серверных приложений.
Компонент ТТаЫе самодостаточный, с ним можно работать, не используя других компонентов связи с базами данных. Однако файл-серверные СУБД обыч
но поставляются в рамках пакета с ограниченными возможностями. Многие задачи проектирования баз данных, которые в клиент-серверных СУБД реализуются в удобных графических средах, в файл-серверной архитектуре приходится решать вручную, с помощью команд SQL, вводимых с текстовой консоли. Для упрощения такой деятельности среда C++Builder предлагает специальную визуальную утилиту Database Desktop (Работа с настольной СУБД). Она вызывается обращением к исполнимому файлу dbd32.exe (по умолчанию он расположен в папке C:\Program Files\Common Files\Borland Shared\Database Desktop\) и предназначена для проектирования структуры файл-серверных баз данных1.
Технология взаимодействия с базой данных в проекте C++Builder
Организация взаимодействия с файл-серверной базой данных осуществляется в несколько этапов по следующей технологии.
Создается база данных и таблицы в ней, формируется внутренняя структура таблиц (набор полей), задаются перекрестные ссылки между ними.
База данных регистрируется в службе BDE.
Принципы работы с утилитой Database Desktop аналогичны принципам использования встроенных средств для построения таблиц в большинстве современных СУБД.
В проекте создается модуль данных, в который включаются компоненты, ответственные за взаимодействие с СУБД.
Организуется доступ к полям таблиц в программном коде приложения.
На форму добавляются компоненты, ответственные за визуализацию данных из таблиц базы.
Организуется работа с содержимым базы данных с помощью команд языка C++.
Технология создания базы данных и таблиц
В файл-серверных СУБД работа ведется с файлами, хранящими конкретные таблицы и наборы индексов к ним. Например, в настольной СУБД Microsoft Access таблицы базы данных вместе с вспомогательной информацией хранятся в одном файле. Речь о создании базы данных как таковой в подобных СУБД обычно не идет.
Создадим таблицу базы данных. Для этого воспользуемся утилитой Database Desktop и дадим команду File > New > Table (Файл > Создать > Таблица) Откроется диалоговое окно со списком форматов доступных СУБД.
Выберем формат создаваемой таблицы. Наиболее популярны у разработчиков форматы Paradox 7 и dBase. Нажмем кнопку ОК — откроется диалоговое окно. Оно предназначено для формирования структуры создаваемо? таблицы.
Рис. 36.1. Окно проектирования таблицы
Последовательно опишем каждое поле таблицы. Описание поля включа ет: порядковый номер, имя (столбец Field Name), тип хранимых в поле дан ных (столбец Туре), длина поля в символах для текстовых данных (столбе) Size). Для перехода между столбцами описания полей используют курсор ные клавиши или мышь.
Порядковый номер поля формируется автоматически. В качестве имени следует задать уникальный идентификатор, содержащий до 8 символов (латинских букв и цифр) и начинающийся с буквы. Последовательное описание набора полей по данной схеме определяет структуру записи, а фактически — всей таблицы.
Сохраним структуру таблицы. После того как структура таблицы сформирована, ее надо сохранить командой File > Save As (Файл > Сохранить как). В открывшемся диалоговом окне выберите каталог и задайте имя таблицы (например, TestTable). При использовании формата СУБД Paradox обычно выбирается принятый по умолчанию каталог WorkDir.
Впоследствии таблицу можно загрузить командой File > Open > Table (Файл > Открыть > Таблица). По умолчанию она открывается в режиме ввода значений. Вернуться в режим проектирования позволяет команда Table > Restructure (Таблица > Реструктурировать).
Тип поля
В столбце Туре (Тип) задается тип значения, хранимого в поле. Для выбора типа следует открыть контекстное меню или нажать клавишу ПРОБЕЛ. Для ключевого поля (см. далее) можно выбрать тип Autoincrement (Автоприращение). В этом случае в данном поле для каждой записи автоматически формируется уникальное значение (поле представляет собой счетчик, значение которого для каждой следующей записи увеличивается на единицу).
Чаще всего используются следующие типы данных.
String, иногда используется название Alpha. Текстовая строка, для которой требуется задать максимально допустимую длину (число символов) в столбце Size (Размер).
Integer, Number. Целое число.
Boolean. Логическое значение.
Date. Дата.
Time. Время.
Binary. Двоичный объект. Этот тип особенно распространен в современных версиях СУБД, где позволяет хранить мультимедийные данные: музыку, видеозаписи, программный код.
В окне создания таблицы имеется еще несколько столбцов, уточняющих характеристики текущего поля. Так, флажок Required Field (Обязательное поле) требует обязательного заполнения данного поля значением. Если этот флажок не установлен, допускается отсутствие значения в поле. Это не означает, что в таком поле хранится ноль или пустая строка, — в соответствии с третьим правилом Кодда считается, что содержимое поля не определено.
В текстовых полях Minimum value (Минимальное значение) и Maximum value (Максимальное значение) задают допустимый диапазон значений поля. Если для поля задано автоприращение, таким способом определяют значение, с которого начинается отсчет индексов.
Ключевое поле
Особое внимание надо обратить на столбец Key (Ключ): он определяет, является ли данное поле ключевым. Если является, то в столбце Key отображается звездочка.
Ключевым полем таблицы называется поле, в котором хранятся первичные ключи записей этой таблицы.
Крайне желательно, чтобы ключевое поле имелось в каждой таблице базы данных. Оно позволяет отличать одну запись от другой по уникальному идентификатору, хранимому в ключевом поле. Обычно такой идентификатор представляет собой числовое значение. Например, в таблице оператора телефонной связи ключевым может быть поле, в котором хранятся неповторяющиеся номера телефонов. Однако лучше выбирать условное значение, не использующее реальные элементы хранимых данных, чтобы изменения в последних не повлияли на уникальность ключевого идентификатора.
Ключевые поля выполняют две функции. Во-первых, повышается эффективность работы СУБД: можно быстро обращаться к конкретным записям по их идентификаторам без перебора всей таблицы. Во-вторых, появляется возможность формировать так называемые перекрестные ссылки между таблицами.
Перекрестная ссылка между таблицами — это логическая взаимосвязь между записями разных таблиц. В такой связи поле одной записи содержит значение первичного ключа связанной с ней записи другой таблицы.
Например, таблица номеров телефонов может быть связана с таблицей клиентов. В этом случае в каждой записи таблицы клиентов хранится идентификационный ключ (перекрестная ссылка), соответствующий записи в таблице номеров телефонов.
Ключевые поля позволяют минимизировать объем модификаций, вносимых в базу данных. Если меняется поле, связанное с другой таблицей (например, по требованию национальной телефонной службы изменены коды доступа к телефонным номерам), то изменения в эти таблицы вносить не потребуется. Ведь в них хранятся только первичные ключи, а реальные значения (измененные номера) меняются только в поле таблицы номеров телефонов, которое не является ключевым. Взаимосвязь между таблицами останется целостной и неизменной.
Значения в ключевых полях должны быть уникальными и неповторимыми. Рекомендуется всегда создавать ключевые поля — это повышает производительность и масштабируемость системы.
Масштабируемость — это способность системы работать без снижения эксплуатационных характеристик, несмотря на увеличение количества пользователей.
Индексация
С ключевыми полями тесно связан механизм индексации таблиц. Как уже говорилось, с помощью ключевого поля можно существенно ускорить доступ к конкретным записям. Для этого СУБД формирует так называемые индексы.
Индекс записи базы данных связывает значение ключевого поля записи с ее физическим местонахождением в базе.
Благодаря системе индексов, содержащей дополнительную информацию об организации таблиц, доступ к элементам таблицы ускоряется. Определив точное местоположение записи на диске по значению ее ключевого поля, можно считать или перезаписать это значение одной дисковой операцией. В противном случае потребовался бы просмотр всего набора данных.
Ключевое поле, как правило, индексируется автоматически.
Первичный индекс — это индекс, соответствующий ключевому полю.
Первичный индекс всегда один. В то же время в таблице допускается неограниченное число индексов для других полей.
Вторичный индекс — это индекс, созданный для неключевого поля.
Вторичные индексы создают следующим образом.
Выберем пункт Secondary Indexes (Вторичные индексы) в раскрывающемся списке Table Properties (Свойства таблицы), имеющемся в окне проектирования таблицы.
Нажмем кнопку Define (Определить), появившуюся в окне, — откроется диалоговое окно Define Secondary Index (Определить вторичный индекс).
Выберем в списке Fields (Поля) те поля, которые требуется проиндексировать. Поместим их в список Indexed fields (Индексируемые поля) кнопкой Вправо.
Если значения полей для выбранного индекса должны быть уникальными, установим флажок Unique (Уникальный).
Нажмем кнопку ОК — откроется диалоговое окно. Введем в этом окне имя файла индекса, которое не должно совпадать с именами полей.
Рис. 36.2. Окно выбора вторичного индекса
ПРИМЕР 36.1
.>»« Создание перекрестных ссылок между таблицами
Между таблицами часто существуют логические взаимосвязи, например, поля одной таблицы хранят первичные ключи записей другой таблицы. Такие взаимосвязи желательно явно описать при построении базы данных — эффективность взаимодействия с полностью определенной системой существенно возрастает.
Познакомимся с технологией формирования перекрестных ссылок на примере.
Создание исходных таблиц.
Создадим две таблицы.
Первая таблица содержит три поля; ID (автоприращение, первичный ключ), Name (текстовое длиной 50 символов, имя клиента) и Num (целочисленное значение). Назовем таблицу TestTable.
Рис. 36.3. Создание структуры таблицы TestTable
Вторая таблица содержит три поля: ID (автоприращение, первичный ключ), Address (текстовое поле, адрес клиента) и Link (целое). Назовем таблицу LinkTable. Поле Link будет выступать в качестве связи — перекрестной ссылки на записи таблицы TestTable. Это поле будет хранить значения первичного ключа (значения поля ID) таблицы TestTable.
Создание перекрестной ссылки.
Выберем пункт Referential Integrity (Ссылочная целостность) в раскрывающемся списке Table Properties (Свойства таблицы).
Нажмем появившуюся кнопку Define (Определить) — откроется диалоговое окно Referential Index (Индекс ссылки), служащее для настройки связи между таблицами.
В списке Fields (Поля) приведены поля текущей таблицы. Выберем поле связи, содержащее значения первичных индексов. В нашем случае это поле Link.
Нажмем кнопку Вправо — поле Link будет включено в список Child fields (Подчиненные поля).
Обратим внимание на список таблиц, с которыми можно установить перекрестную связь. Выберем таблицу TestTable.db.
Нажмем кнопку Влево. При этом поле первичного ключа таблицы TestTable автоматически выбирается и заносится в список Parent’s key (Ключевое поле родительской таблицы).
Рис. 36.4. Формирование логических связей между таблицами
Если поля связи выбраны правильно и их типы совместимы, активизируется кнопка ОК. Нажмем ее — текущее окно закроется и откроется диалоговое окно для ввода имени перекрестной ссылки.
Введем имя для перекрестной ссылки и нажмем кнопку ОК.
В окне проектирования таблицы новая перекрестная связь будет представлена в раскрывающемся списке. Загрузим в утилиту Database Desktop таблицу TestTable и выберем в раскрывающемся списке Table Properties (Свойства таблицы) пункт Dependent Tables (Зависящие таблицы). В окне будет представлен список таблиц, связанных с текущей и ссылающихся на ее первичный ключ. В данном случае список содержит только таблицу LinkTable.
Вопросы
В чем состоят особенности файл-серверной архитектуры?
Для чего предназначена технология BDE?
Каким классом BDE представлен набор данных?
Каким классом BDE представлена реляционная таблица?
Какова последовательность создания таблицы в утилите Database Desktop?
Как создается ключевое поле?
Как создается индекс для заданного поля?
Перечислите основные типы полей в файл-серверных СУБД.
Как создаются вторичные индексы?
Как организуются перекрестные ссылки между таблицами?
ГЛАВА 37 Технологии создания файл-серверных приложений
Создание приложения, работающего с файл-серверной СУБД, выполняется в семь этапов.
Формируется пустая заготовка приложения VCL.
Создается модуль данных, в котором группируются невизуальные компоненты взаимодействия с СУБД.
Организуется доступ к таблице выбранной базы данных.
Настраивается список полей таблицы, доступных в приложении.
Выполняются действия по абстрагированию пользовательского интерфейса программы от конкретной СУБД.
Формируется пользовательский интерфейс. Для этого задействуются компоненты, ответственные за визуализацию содержимого таблиц базы данных.
Программируется детальная обработка набора данных.
Первые шесть этапов практически не требуют ручного программирования. Рассмотрим все эти этапы подробнее.
Создание модуля данных
Для создания модуля данных следует дать команду главного меню File > New > Other (Файл > Создать > Другое). Выберите значок Data Module (Модуль данных) в категории C++Builder Files (Файлы C++Builder).
В проекте выделяется особая составная часть, позволяющая централизованно управлять работой объектов, связанных с базами данных. Опа отделяет логику их функционирования от логики, ответственной за вычисления и пользовательский интерфейс.
Доступ к таблице базы данных
Доступ из программы к таблице базы данных выполняется следующим образом.
Выберем компонент ТТаЫе в категории BDE палитры инструментов и щелкнем мышью на пространстве модуля данных. Экземпляр выбранного компонента разместится в модуле данных.
*‘ New Items*‘ New ItemsItem Categories:Item Categories:Г"’ C++Builder Projects£’■ ActiveXf“j С+4-Builder Ftes' Г": Inheritable Items Intraweb Multitier WebBrokerГ'"; WebServicesWebSnap. XMLQ 1 Other Files'"' Web DocumentsГ"’ C++Builder Projects£’■ ActiveXf“j С+4-Builder Ftes' Г": Inheritable Items Intraweb Multitier WebBrokerГ'"; WebServicesWebSnap. XMLQ 1 Other Files'"' Web DocumentsAbout boxAbout boxControl Panel ModuleControl Panel ModuleCORBA IDL FileCORBA IDL FileDialog withHelp (Vertical)Dialog withHelp (Vertical)CPP FileCPP FileDual list boxDual list boxComponentComponentDialog with Help (Ho..,Dialog with Help (Ho..,FrameFrame•l•lHeader FileHeader FilePassword DialogPassword Dialog
*‘ New Items
*‘ New Items
Item Categories:
Item Categories:
Г"’ C++Builder Projects
£’■ ActiveX
f“j С+4-Builder Ftes
' Г": Inheritable Items Intraweb Multitier WebBroker
Г'"; WebServicesWebSnap
. XML
Q 1 Other Files
'"' Web Documents
Г"’ C++Builder Projects
£’■ ActiveX
f“j С+4-Builder Ftes
' Г": Inheritable Items Intraweb Multitier WebBroker
Г'"; WebServicesWebSnap
. XML
Q 1 Other Files
'"' Web Documents
About box
About box
Control Panel Module
Control Panel Module
CORBA IDL File
CORBA IDL File
Dialog withHelp (Vertical)
Dialog withHelp (Vertical)
CPP File
CPP File
Dual list box
Dual list box
Component
Component
Dialog with Help (Ho..,
Dialog with Help (Ho..,
Frame
Frame
•l
•l
Header File
Header File
Password Dialog
Password Dialog
ServiceService| Help| HelpReconcileError DialogReconcileError Dialog| OK | Cancel
Service
Service
| Help
| Help
Reconcile
Error Dialog
Reconcile
Error Dialog
Puc. 37.1. Выбираем заготовку модуля данных
Для доступа к СУБД, доступной на локальном компьютере или в локальной сети, необходимо настроить свойство DatabaseName компонента ТТаЫе. Выберем нужное имя СУБД в раскрывающемся списке, например, название DBDEMOS.
Нужную таблицу базы данных описывает свойство TableName. Занесем в это свойство имя таблицы, например, доступную в DBDEMOS таблицу animals.dbf.
Занесем в свойство Active значение True. Таблица станет активной — доступной в проектируемом приложении.
Создание запросов
В дополнение к компоненту ТТаЫе, предоставляющему доступ к физической таблице базы данных, в среде C++Builder имеется компонент TQuery (запрос), позволяющий обращаться из программы к базе данных с запросом SQL2.
Запрос получает от СУБД не содержимое реально существующей таблицы, а временный набор записей — результат работы выражения SQL. Полученный набор записей может содержать подмножество записей реально существующей таблицы (например, из таблицы пользователей отобраны только клиенты-долж-
2 Версия компонента TQuery для запросов SQL к СУБД существует и в других технологиях, реализованных в среде C++Builder. Так, в наборе компонентов dbExpress, предназначенных для работы с серверами SQL, имеются компонент-таблица TSQLTable и компонент-запрос TSQLQuery. В наборе Interbase (доступ к СУБД Interbase) имеется компонент-таблица TlBTable и компонент-запрос TIBQuery. В наборе dbGo (доступ к данным по технологии ADO) существует компонент-таблица TADOTable и компонент-запрос TADOQuery и так далее.
ники) или представлять собой результат объединения таблиц, связанных перекрестными ссылками. Структура результирующей записи (набор виртуальных полей) в таком случае составляется из полей разных таблиц.
Свойство SQL компонента TQuery содержит текст команды SQL. Отладка кода SQL выполняется в окне SQL Window (Окно SQL). Чтобы открыть это окно, следует выбрать конкретное подключение к СУБД в окне просмотра данных (Data Explorer) и дать команду SQL Window (Окно SQL) с помощью контекстного меню. О способах построения команд SQL рассказывается в главах 40 и 41.
Поля набора данных
Поля таблиц баз данных, используемые в программе на языке C++, бывают динамическими (по умолчанию) и постоянными.
Динамическое поле — это поле, точно и однозначно соответствующее в программе физическому полю базы данных.
Постоянное поле — это поле, которое может соответствовать физическому полю таблицы базы данных или существовать виртуально (например, вычисляться на основе сторонних величин).
Постоянные поля (их также называют псевдополями) удобны тем, что даже после изменения физической структуры таблицы их набор в программе останется прежним и модифицировать ее код не потребуется. Псевдополя добавляются в модуль данных в виде отдельных полей класса этого модуля. Имя псевдополя состоит из имени таблицы и имени соответствующего поля. Например, поле NAME таблицы animals.dbf может быть представлено в виде поля AnimalName (тип TStringField) в классе текущего модуля данных.
Тип псевдополя выбирается автоматически на основе типа соответствующего поля таблицы базы данных. Имя типа начинается с буквы Т, за которой следует тип данных и, в заключение, слово Field. Например, тип TStringField описывает текстовое поле, тип TlntegerField — поле с
Structure®Structure®□ Formiл|^ Buttonlй§ Default {Session}।и »3 OBDEMOS {Alias}IВ *^ animals, dbf {Table 1}I^ Constraints'-‘ FieldDefs4ЛЗВЯИ.fa ChildDefs”’+4 1 ■ SIZEI»4 2- WEIGHTГ; 4 3 - AREA't4 4 - BMP"IIi^IndexDefsIi-i t& Fields■«4 0 - NAME {TablelNAME}IЛ 1 - SIZE {TablelSIZE}*$. 2- WEIGHT {TablelWEIGHT}*®□ Formiл|^ Buttonlй§ Default {Session}।и »3 OBDEMOS {Alias}IВ *^ animals, dbf {Table 1}I^ Constraints'-‘ FieldDefs4ЛЗВЯИ.fa ChildDefs”’+4 1 ■ SIZEI»4 2- WEIGHTГ; 4 3 - AREA't4 4 - BMP"IIi^IndexDefsIi-i t& Fields■«4 0 - NAME {TablelNAME}IЛ 1 - SIZE {TablelSIZE}*$. 2- WEIGHT {TablelWEIGHT}*®целым значением, TBooleanField — поле с логическим значением. Все эти типы являются наследниками класса TField.
Structure®
Structure®
□ Formiл
|^ Buttonl
й§ Default {Session}
।и »3 OBDEMOS {Alias}
IВ *^ animals, dbf {Table 1}
I^ Constraints
'-‘ FieldDefs
4ЛЗВЯИ
.fa ChildDefs”
’+4 1 ■ SIZE
I»4 2- WEIGHT
Г; 4 3 - AREA
't4 4 - BMP"I
Ii^IndexDefs
Ii-i t& Fields
■«4 0 - NAME {TablelNAME}
IЛ 1 - SIZE {TablelSIZE}
*$. 2- WEIGHT {TablelWEIGHT}*®
□ Formiл
|^ Buttonl
й§ Default {Session}
।и »3 OBDEMOS {Alias}
IВ *^ animals, dbf {Table 1}
I^ Constraints
'-‘ FieldDefs
4ЛЗВЯИ
.fa ChildDefs”
’+4 1 ■ SIZE
I»4 2- WEIGHT
Г; 4 3 - AREA
't4 4 - BMP"I
Ii^IndexDefs
Ii-i t& Fields
■«4 0 - NAME {TablelNAME}
IЛ 1 - SIZE {TablelSIZE}
*$. 2- WEIGHT {TablelWEIGHT}*®
Структуру приложения, в котором задействованы поля и псевдополя, можно визуально изучить с помощью средства просмотра структуры, которое открывают командой View > Structure (Вид > Структура). Иерархия таблиц, динамические и постоянные поля и индексы, а также источники данных представлены в виде дерева.
Добавление псевдополей в текущий проект
Добавление полей в текущий проект выполняют следующим образом.
Выберем в Проектировщике нужную таблицу или запрос (экземпляр класса ТТаЫе или TQuery).
Вызовом редактор коллекции полей с помощью команды контекстного меню таблицы Fields Editor (Редактор полей).
Для добавления отдельного поля выберем в контекстном меню редактора коллекции пункт Add Field (Добавить поле). Для добавления всех физических полей таблицы служит команда Add All Fields (Добавить все поля).
Выберем в редакторе коллекции нужное поле — в Инспекторе объектов появится список его свойств. При желании можно изменить идентификатор конкретного поля (свойстве Name), используемый для обращения к полю из программного кода.
Источники данных
Среда C++Builder позволяет полностью отделить базу данных от прикладной программы. В роли промежуточного объекта, обеспечивающего независимость приложения от конкретной СУБД, выступает источник данных.
Источник данных — компонент системы C++Builder, предназначенный для организации взаимодействия между прикладной программой и конкретной базой данных. Источник данных делает приложение независимым от конкретной СУБД и предоставляет стандартный интерфейс их взаимодействия.
Концепция источника данных реализована в среде C+ rBuilder с помощью компонента TDataSource (источник данных) из категории палитры инструментов Data Access (Доступ к данным). С его помощью приложение настраивается на взаимодействие с конкретной таблицей или запросом к базе данных, на перечень и типы ее полей (с помощью псевдополей). Если в дальнейшем потребуется сменить СУБД, достаточно модифицировать лишь настройки элемента ТТаЫе или даже просто ввести вместо этого компонента иной, ориентированный на взаимодействие с клиент-серверными СУБД или вообще на получение данных из файлов или Сети.
В настройки прикладного интерфейса при этом не требуется вносить модификаций: уровень интерфейса изолирован от уровня взаимодействия с СУБД. Это достигается с помощью компонента TDataSource — пользовательский интерфейс получает данные только через источник данных и поэтому вообще не подвергается модификации.
Компонент TDataSource должен существовать в виде отдельного экземпляра для каждой таблицы и каждого запроса (компонентов ТТаЫе и TQuery). Он связывается с таблицей данных через свойство DataSet (исходный набор данных). Далее источник данных используется уже на уровне элементов пользовательского интерфейса.
Создание пользовательского интерфейса
В приложениях, взаимодействующих с базами данных, пользовательский интерфейс формируют с помощью компонентов, расположенных в категории палитры
инструментов Data Controls (Элементы управления для данных). Они, с одной стороны, схожи с компонентами категории Standard (Стандартные), а с другой, обладают специфическими возможностями обмена информацией с источниками данных и ее отображения.
Если в проекте создан отдельный модуль данных, в котором хранятся компоненты TTable, TDataSource и другие, то его сначала надо сделать доступным в модуле формы. Для этого следует дать команду File > Use Unit (Файл > Подключить модуль) и выбрать модуль данных проекта в открывшемся диалоговом окне.
Пользовательская таблица данных
Tool Palette■ail. - t> t - Data ControlsLSLSSSL^—_^ TDBNavigatorЧу TDBText У TDBEdit^| TDBMemo й TDBImage ^ TDBListBox eg TDBComboBox %- TDBCheckBox Р» TDBRadioGroupTool Palette■ail. - t> t - Data ControlsLSLSSSL^—_^ TDBNavigatorЧу TDBText У TDBEdit^| TDBMemo й TDBImage ^ TDBListBox eg TDBComboBox %- TDBCheckBox Р» TDBRadioGroupКомпонент TDBGrid (пользовательская таблица данных) — один из наиболее часто используемых компонентов пользовательского интерфейса. Он требует минимальных усилий по ручной настройке. Чтобы задействовать этот компонент, его надо разместить на форме и задать в свойстве DataSource ссылку на источник данных TDataSource. Отображение содержимого исходной таблицы базы данных в компоненте TDBGrid произойдет автоматически еще на этапе проектирования (в окне Проектировщика).
Tool Palette
■a
il. - t> t - Data Controls
LSLSSSL^—_
^ TDBNavigator
Чу TDBText У TDBEdit
^| TDBMemo й TDBImage ^ TDBListBox eg TDBComboBox %- TDBCheckBox Р» TDBRadioGroup
Tool Palette
■a
il. - t> t - Data Controls
LSLSSSL^—_
^ TDBNavigator
Чу TDBText У TDBEdit
^| TDBMemo й TDBImage ^ TDBListBox eg TDBComboBox %- TDBCheckBox Р» TDBRadioGroup
4 Project 1 - Borland Developer Studio 2006 - Umt1 .cpp
в©ев©еFile Edfc Search View Refactor Project Run Component Tools StarTeam Window Неф Fest Layout
в©е
в©е
/| Object Inspector/| Object InspectorDBGridl|Properties] EventsBjAdion[Enabled true[HelpContext 0I Hint[Visible-trueBiDatabase[Columns.(TDBGrldColumns)'DataSource DataSource 1□brag. Drop and DockingiDragCursor crDrag[DragKind dkDragiDragMode dmManualBiHelp and HintsHelpContext 0IHelpKeyword(HelpType htContext;Parent5howH[trueishowHintfalseВ(InputPnabilaHhvtio'All shownDBGridl|Properties] EventsBjAdion[Enabled true[HelpContext 0I Hint[Visible-trueBiDatabase[Columns.(TDBGrldColumns)'DataSource DataSource 1□brag. Drop and DockingiDragCursor crDrag[DragKind dkDragiDragMode dmManualBiHelp and HintsHelpContext 0IHelpKeyword(HelpType htContext;Parent5howH[trueishowHintfalseВ(InputPnabilaHhvtio'All shownК 5*щ Projectl.bdsproj-Pro... ? X
/| Object Inspector
/| Object Inspector
DBGridl
|Properties] Events
BjAdion
[Enabled true
[HelpContext 0
I Hint
[Visible-true
BiDatabase
[Columns.(TDBGrldColumns)
'DataSource DataSource 1
□brag. Drop and Docking
iDragCursor crDrag
[DragKind dkDrag
iDragMode dmManual
BiHelp and Hints
HelpContext 0
IHelpKeyword
(HelpType htContext
;Parent5howH[true
ishowHintfalse
В(Input
PnabilaHhvtio
'All shown
DBGridl
|Properties] Events
BjAdion
[Enabled true
[HelpContext 0
I Hint
[Visible-true
BiDatabase
[Columns.(TDBGrldColumns)
'DataSource DataSource 1
□brag. Drop and Docking
iDragCursor crDrag
[DragKind dkDrag
iDragMode dmManual
BiHelp and Hints
HelpContext 0
IHelpKeyword
(HelpType htContext
;Parent5howH[true
ishowHintfalse
В(Input
PnabilaHhvtio
'All shown
V X « Welcome Page ^Unitl,cpp ■V X « Welcome Page ^Unitl,cpp ■-□} ^ ^ Ж ; Й: 31 Insert [[ У^АЬ Design Hid^S-□} ^ ^ Ж ; Й: 31 Insert [[ У^АЬ Design Hid^S^Activate ▼ ^gjew ^t«.
V X « Welcome Page ^Unitl,cpp ■
V X « Welcome Page ^Unitl,cpp ■
-□
} ^ ^ Ж ; Й: 31 Insert [[ У^АЬ Design Hid^S
-□
} ^ ^ Ж ; Й: 31 Insert [[ У^АЬ Design Hid^S
[ §^ ProjectGroupl*
f^ Projectl.exe
; iЩ Projectl.cpp
i® Projectiles
-[^ProjectL^J^OataExplT
И6Г Tool PaletteV X
4. - ®V
+ Data Access*
Data Controls
■ TDBGrid '■
^j TDBNavigator
Д, TDBText
У TOBEdit
1^) TDBMemo TDBImage
9a tobusibox<
Рис. 3..2. Отображение содержимого базы данных в Проектировщике
Компонент TDBGrid предлагает базовый перечень функций по работе с набором данных. Можно просматривать содержимое набора, а также редактировать
отдельные поля. Достаточно дважды щелкнуть на нужном поле таблицы, чтобы оно перешло в режим редактирования3. Когда фокус из этого поля будет перемещен, обновление физической таблицы в базе данных произойдет автоматически. Запрет редактирования данных обеспечивает значение True для свойства Readonly.
По умолчанию в пользовательской таблице TDBGrid представлены все столбцы исходной таблицы базы данных, в том числе ключевое поле, редактировать которое, конечно же, нежелательно. Список отображаемых полей настраивают через свойство FieldName. Конкретное поле можно сделать недоступным для редактирования, если задать значение True для его свойства Readonly.
Другие элементы интерфейса
Обойтись компонентом TDBGrid удается далеко не всегда. Он хорош, если требуется просмотреть и быстро отредактировать небольшие и не очень сложные таблицы. Для обработки объемных таблиц обычно задействуется дополнительный элемент управления TDBNavigator (навигатор). Он связывается с источником данных через свойство DataSource, а синхронизация его работы с таблицей TDBGrid выполняется автоматически. Навигатор отображает на форме кнопки перемещения и редактирования — он обеспечивает переход к следующей и предыдущей записи, к началу и концу таблицы, позволяет приступить к редактированию, создать или удалить запись, отменить модификацию и так далее4.
Для точного отображения содержимого отдельных полей таблицы базы данных можно воспользоваться другими компонентами панели Data Controls (Элементы управления для данных). Так, текстовое значение поля хорошо визуализируется с помощью компонента TDBText, а возможность редактирования значения предоставляет компонент TDBEdit. Имя обрабатываемого поля набора данных задается свойством DataField. Все эти компоненты связываются с источником данных через свойство DataSource.
Логические значения удобно отображать с помощью компонента-флажка TDBCheckBox, двоичные данные — с помощью многострочного поля TDBMemo или компонента-изображения TDBImage, а значения из ограниченного набора — с помощью компонента-переключателя TDBRadioGroup. В последнем случае возможные значения поля таблицы базы данных задаются в свойстве Values, а соответствующие им имена элементов переключателя — в свойстве Items.
Технологии программной работы с таблицами
До сих пор мы рассматривали чисто визуальные возможности среды C++Builder по построению программ, работающих с базами данных. Теперь познакомимся
При работе с таблицей TDBGrid все операции выполняются с текущей записью, помеченной звездочкой или стрелочкой. Текущую запись можно удалить как целое, а новые пустые записи вставляются сразу после текущей.
Если в таблице TDBGrid модифицировалось свойство Columns и некоторые поля были защищены от изменений, то и в режиме добавления или редактирования записи с помощью Навигатора соответствующие поля недоступны для модификации.
с основными подходами к использованию компонентов непосредственно в программном коде.
Открытие и закрытие набора данных
Перед началом модификации записей набор данных (экземпляра класса ТТаЫе) надо открыть. Открытие выполняет метод Ореп() без параметров. Состояние набора данных проверяют обращением к его свойству Active: если он возвращает значение True, то набор открыт.
Table!->Ореп(};
if ( I Tablel->Active )
// сообщить о невозможности открытия
По окончании работы с набором данных его надо закрыть с помощью метода Close(): Tablel->Close();
Модификация записей в наборе данных
Данные в открытый набор данных добавляют с помощью метода lnsert() без параметров. Он формирует новую пустую запись, которая автоматически становится текущей.
Для модификации записи вызывают метод Edit() без параметров. Он переводит текущую запись в режим редактирования. Изменения в поля текущей записи вносят с помощью обычных операторов присваивания языка C++. Поля текущей записи хранятся в свойстве набора данных Fields. Оно имеет тип TFields и представляет собой список элементов ranaTField, описывающих конкретные поля. Нумерация элементов в свойстве Fields начинается с нуля. Например, для таблицы из трех полей ID, Name и Num поле Id считается нулевым, поле Name — первым, поле Num — вторым.
Класс TField имеет набор свойств, через которые можно обращаться к содержимому поля в соответствии с его реальным типом в базе данных5. Например, свойство AsString предоставляет доступ к содержимому поля (как на чтение, так и на запись) в виде текстовой строки, а свойство Aslnteger — в виде целого значения:
Editl->Text = Tablel->Fields->Fields[1]->AsString;
Tablel->Edit(); // перевод в режим редактирования Tablel->Fields->Fields[0]->AsInteger = 10;
Метод набора данных Delete(), вызываемый без параметров, удаляет текущую запись.
Tablel->Delete();
Фиксация изменений и отказ от изменений
Любые изменения, внесенные в набор данных (добавление, модификация или удаление записей), должны быть зафиксированы в физической таблице. Для этого надо вызвать метод Post() набора данных.
Можно получить доступ к «сырому» содержимому поля напрямую, через его свойство Value, которое имеет тип Variant.
Такая схема двухшаговой модификации набора данных позволяет избежать ошибок при записи в поля неверных значений. До тех пор пока метод Post() не вызван явно, все действия программы (с момента последнего вызова этого метода), менявшие содержимое набора данных, не сказываются на физической базе данных. Например:
// выполняем модификацию значений:
Tablel->Edit();
Tablel->Fields->Fields[0]->AsInteger = 10;
М в базу данных эти изменения будут внесены
.4 топько после выполнения следующей команды:
Tablel->Post();
Можно отказаться от изменений, вызвав вместо метода набора данных Post() ме тод Сапсе1():
/ / выполняем модификацию значений:
Tab!el->Edit();
Tablel->Fields->Fields[0]->AsInteger = 10;
// отказываемся от этих изменений:
Tablel->Cancel();
В результате первое и второе поля текущей записи не модифицируются.
Организацию последовательности модификаций набора данных с отложенной фиксацией выполненных изменений называют транзакцией.
Дополнительные возможности
При работе с полями базы данных разработчик располагает рядом дополнительных возможностей. Эти возможности позволяют управлять представлением данных в программе.
Индексы
В файл-серверных СУБД (а на самом деле — ив других типах СУБД) организована система индексации записей, значительно повышающая скорость доступа к данным. Индекс для любого поля таблицы можно создать программно — вызовом метода набора данных Addlndex(). Первый параметр метода — имя индекса, а второй — текстовая строка, содержащая список имен полей, на которых базируется индекс. Отдельные поля в списке отделяются точкой с запятой.
Вычисляемые поля
Список полей таблицы можно пополнять собственными, так называемыми вычисляемыми полями.
Вычисляемое поле — это поле набора данных, которое реально в нем отсутствует. Значение поля формируется (вычисляется) программно.
Вычисляемое поле добавляют в компонент ТТаЫе следующим образом.
Выберем в окне Проектировщика компонент ТТаЫе.
Вызовем редактор полей командой контекстного меню Fields Editor (Редактор полей).
Добавим новое поле командой контекстного меню New field (Создать поле). Откроется диалоговое окно настройки свойств нового поля.
Установим в этом окне на панели Field type (Тип поля) значение Calculated (Вычисляемое поле).
Вернемся в окно Проектировщика и создадим для таблицы обработчик события OnCalcFields. В этом обработчике программируется расчет вычисляемого поля.
Закладки
В крупных проектах нередки ситуации, когда набор данных насчитывает сотни тысяч и миллионы записей. При обработке такого набора часто используется переход между записями. Этот процесс можно существенно ускорить, задействовав так называемые закладки.
Закладка — это механизм ускоренного доступа к конкретной записи набора данных.
Текущая закладка в таблице представлена в свойстве Bookmark класса TDataSet и любых его наследников (например, компонента ТТаЫе). Для создания закладки в позиции текущей записи вызывают метод GetBookmark(), а для перехода к нужной закладке — метод GotoBookmark(). Переход к определенной записи большого набора с помощью закладок значительно эффективнее, нежели другие методы.
Фильтры
Для того чтобы выделить в существующей таблице определенное подмножество записей и работать только с ним, надо воспользоваться фильтрами.
Фильтр — это условие (обычно записываемое на языке SQL), по которому в наборе данных отбираются конкретные записи.
Условие отбора заносится в свойство Filter набора данных. При обращении к соответствующему компоненту будут представлены только записи, отобранные по этому условию.
Поиск
Метод набора данных для поиска записей FindKey() быстро находит нужный элемент по указанному ключу поиска (значению проиндексированного поля). Метод FindNearest() отыскивает «похожие» записи (близкие к указанному значению). Этот метод может работать медленно. Универсальные методы поиска Locate() и Lookup() получают в качестве параметров списки полей и соответствующих им искомых значений.
В наборе данных также имеются стандартные методы, предназначенные для навигации по таблице. Они позволяют переместить указатель в наборе данных на первую (метод First), последнюю (Last), следующую (Next) или предыдущую (Prior) запись, а также сдвинуть указатель на заданное число строк (MoveBy). При работе с отфильтрованным набором следует использовать подобные же методы с префиксом Find (FindFirst, FindLast и так далее).
Вопросы
Как создается файл-серверное приложение?
Зачем нужен модуль данных?
Как организуется доступ к таблице базы данных?
Как создаются запросы к базе данных?
Чем отличаются динамические и постоянные поля набора данных?
Что такое источник данных?
Как связываются элементы пользовательского интерфейса с таблицами базы данных?
Как работает компонент TDBGrid?
Каковы основные элементы организации пользовательского интерфейса, связываемые с источниками данных?
Как программно открывают и закрывают набор данных?
Как программно добавляют в набор новую запись?
Как программно модифицируют запись?
Как программно удаляют запись?
Как организуется фиксация выполненных изменений?
Каковы дополнительные программные возможности набора данных?
ГЛАВА 38
Клиент-серверные технологии
В этой главе изучается клиент-серверная технология — дальнейшее развитие файл-серверного подхода. Процесс формирования клиент-серверного приложения в среде C++Builder отличается от процесса создания файл-серверной системы, но эти отличия не очень значительны.
Недостатки файл-серверной архитектуры
Необходимость отказа от файл-серверной архитектуры связана с тем, что эта архитектура обладает рядом недостатков. Эти недостатки хорошо известны.
Вся работа с базами данных возлагается на драйвер. Он вынужден работать на одном компьютере с прикладным приложением и при этом выполнять множество специфичных системных задач по обработке данных. В результате возникает излишняя нагрузка на процессор локального компьютера, например, при сортировке больших таблиц данных или обработке сложных запросов SQL.
Число пользователей файл-серверного приложения ограничено производительностью файловой системы и пропускной характеристикой сети. Между сервером, на котором хранятся файлы базы данных, и клиентскими приложениями ведется интенсивный обмен копиями таблиц.
Плохо развиты средства разделения прав пользователей на доступ к данным.
Надежность итогового решения невысока: сбой в процессе модификации базы данных нередко приводит к порче файлов или, в лучшем случае, к необходимости перестройки индексной системы перекрестных ссылок.
Блокировка в файл-серверной системе осуществляется обычно на уровне таблицы. Если какая-то программа уже обратилась к таблице, другим приложениям придется ожидать ее полного освобождения.
Уровень блокировки базы данных соответствует логическому элементу базы данных, с которым в многопользовательском режиме разрешено работать только одному пользователю.
Блокировка на уровне таблицы означает, что когда с таблицей работает один поль- । зователь (одна клиентская программа), другие должны ждать, пока таблица не освободится. Файл-серверные системы нередко обеспечивают блокировку лишь на уровне всей базы: если в ней несколько таблиц, стоит обратиться к одной из них, как другие тоже становятся временно недоступными. Современные клиент-
серверные СУБД поддерживают блокировку на уровне записи (несколько программ могут одновременно редактировать разные записи одной таблицы) и даже па уровне отдельного поля конкретной записи.
Понятие о клиент-серверной архитектуре
Следующим логическим шагом в развитии архитектур баз данных после файл- серверной стала клиент-серверная архитектура.
Клиент-серверная архитектура — это программная архитектура, в которой логика обработки базы данных явно отделена от прикладной логики (взаимодействие с пользователем, решение прикладных задач).
В клиент-серверной системе имеется выделенный сервер обработки баз данных, который работает на собственной машине в сети и независим от местонахождения клиентских компьютеров. Этот сервер следит за сохранностью базы данных, обрабатывает запросы SQL, обеспечивает транзакционную работу, реализует блокировку на уровне записи или даже ее отдельного поля и устойчиво функционирует при наличии тысяч клиентских обращений. Он снимает с пользовательских машин нагрузку, связанную с обработкой баз данных, разграничивает доступ пользователей и программ к данным, скрывая отдельные таблицы и их отдельные поля и ограничивая возможность их модификации, и так далее. Драйвер СУБД выполняет в этом случае лишь функции связи прикладной программы с сервером баз данных. Клиентское приложение реагирует на действия пользователя, выполняет необходимую счетную работу, но наборы данных полностью получает от СУБД, которая обрабатывает запросы SQL на удаленном компьютере.
Принципы создания клиент-серверных приложений в среде C++Builder
Процессы создания файл-серверных и клиент-серверных приложений в среде C++Builder не сильно разнятся друг от друга. И в том и в другом случае можно использовать компонент TTable, который одинаково настраивается на доступные драйверы к СУБД разных архитектур. Но имеются и определенные отличия.
Сервер СУБД практически всегда работает не на одном компьютере с клиентским приложением, а на выделенной серверной машине, доступной в локальной сети. Соответственно, резко возрастают требования к надежности соединения с ним, проверке прав соединяющейся с сервером программы и так далее. Учет этих особенностей автоматизируется с помощью упоминавшегося ранее компонента TDataSource (источник данных), расположенного в категории палитры инструментов Data Access (Доступ к данным). Он формирует дополнительную прослойку между элементами пользовательского интерфейса, компонентами наподобие TTable и СУБД'.
В качестве элемента доступа к базе данных при использовании компонента TDataSource может выступать не только компонент TTable, но и любой другой программный объект (наследник класса TDataSet), способный генерировать информацию в виде реляционных наборов.
Доступ к содержимому компонента ТТаЫе происходит через промежуточный источник данных. Такой механизм позволяет абстрагироваться от физической СУБД и в дальнейшем быстро переключаться между разными базами данных, модифицируя значения одного-двух свойств прикладных компонентов. Главное, чтобы таблицы из разных баз данных имели одну и ту же схему построения полей. Доступ к ним за счет прослойки TDataSource будет организован универсальным способом.'
Мы уже рассматривали работу с компонентом TDataSource. В ряде случаев для доступа к серверу СУБД можно обойтись им и набором компонентов панели BDE. Однако среда C++Builder предлагает специальные, более эффективные и производительные наборы компонентов, специально рассчитанные на клиент-серверные архитектуры.
В общем случае технология создания клиент-серверной системы в среде C++Builder выглядит следующим образом.
Приложение настраивают на связь с конкретной СУБД с помощью одного из специализированных наборов компонентов.
Приложение дополняют компонентами доступа к конкретным наборам данных (например, таблицам) выбранной базы данных.
В проект добавляется компонент TDataSource, изолирующий прикладную логику работы от средств связи с СУБД.
Создается пользовательский интерфейс для работы с набором данных, который функционирует через компонент TDataSource.
Технология создания клиент-серверного приложения с использованием СУБД Interbase
Набор компонентов InterBase палитры инструментов предназначен для построения клиент-серверного приложения, взаимодействующего с СУБД Interbase. Она разрабатывается корпорацией Borland как коммерческий продукт, а некоторые ее версии распространяются бесплатно и доступны в исходных текстах. СУБД Interbase входит в поставку системы C++Builder.
Технология создания клиент-серверного приложения при использовании СУБД InterBase следующая.
Устанавливается связь приложения с сервером Interbase и конкретной базой данных.
Организуется поддержка транзакций в рамках этой связи.
Формируется обращение к конкретной таблице базы.
Настраивается список доступных в программе полей таблицы.
Добавляется компонент TDataSource, обеспечивающий независимость приложения от марки СУБД.
Проектируется интерфейс пользователя, организуется работа с базой данных через промежуточный источник данных.
При необходимости программируются специфические действия по обработке таблиц.
ПРИМЕР 38.1
Визуальное построение клиент-серверного приложения с использованием СУБД Interbase
Рассмотрим процесс создания клиент-серверного приложения в соответствии с описанной выше технологией.
Создание заготовки приложения.
Tool PaletteTool Palette- InterBase' '' ag'Щ TlBTableTIBQuery^ TIBStoredProc."% TIBDatabase jв® TlBTransaction^ TIBUpdateSQL^ TIBDataSetgf TIBSQL© TIBDatabaselnfo- InterBase' '' ag'Щ TlBTableTIBQuery^ TIBStoredProc."% TIBDatabase jв® TlBTransaction^ TIBUpdateSQL^ TIBDataSetgf TIBSQL© TIBDatabaselnfoСоздадим пустую заготовку приложения командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL).
Tool Palette
Tool Palette
- InterBase' '' ag'
Щ TlBTable
TIBQuery
^ TIBStoredProc.
"% TIBDatabase j
в® TlBTransaction
^ TIBUpdateSQL
^ TIBDataSet
gf TIBSQL
© TIBDatabaselnfo
- InterBase' '' ag'
Щ TlBTable
TIBQuery
^ TIBStoredProc.
"% TIBDatabase j
в® TlBTransaction
^ TIBUpdateSQL
^ TIBDataSet
gf TIBSQL
© TIBDatabaselnfo
Настройка связи с СУБД Interbase.
Связь приложения с СУБД Interbase обеспечивается компонентом TIBDatabase из категории палитры инструментов Interbase.
Разместим этот компонент на форме.
Введем в свойство DatabaseName полный путь доступа к файлу базы данных InterBase.
Активизируем связь с базой данных — присвоим свойству Connected значение True.
Когда выполняется подключение к базе данных Interbase, сервер запрашивает имя пользователя и пароль в отдельном всплывающем окне. Чтобы скрыть этот запрос при подключении к СУБД Interbase, свойство LoginPrompt должно иметь значение False. Тогда параметры регистрации (имя пользователя и пароль) должны быть представлены в свойстве Params (тип TStrings) в виде двух строк, например:
user_name=SYSDBA
password=masterkey
Значения имени пользователя (SYSDBA) и пароля (masterkey) следует изменить на заданные администратором СУБД. При запуске программы окно запроса параметров подключения к СУБД появляться не будет.
Организация транзакционной работы.
Работа с СУБД Interbase обязательно должна вестись с поддержкой режима транзакций. Его обеспечивает компонент TlBTransaction.
Добавим этот компонент на форму.
В его свойстве DefaultDatabase выберем имя экземпляра класса TIBDatabase (объект, ответственный за подключение к Interbase), доступного в текущем проекте.
Доступ к таблице базы данных.
Доступ к конкретной таблице базы осуществляется с помощью компонента TlBTable (наследника класса TDataSet), напоминающего уже изученный компонент ТТаЫе.
Добавим этот компонент на форму.
Свяжем его с нужной базой данных — выберем в свойстве Database имя доступного компонента TIBDatabase.
В свойстве Transaction выберем имя транзакционного объекта (компонент TlBTransaction).
Зададим название нужной таблицы базы в свойстве TableName. Нужное название выбирается из полного списка таблиц.
Настройка списка полей.
Список полей, которые будут доступны в клиентском приложении, формируется с помощью визуального редактора полей компонента TlBTable.
Вызовем редактор полей командой контекстного меню Fields Editor (Редактор полей).
Зададим в редакторе список полей, который может и не совпадать с реальным набором полей таблицы базы данных.
Пользовательский интерфейс.
Подготовим пользовательский интерфейс клиентской программы.
Разместим на форме компонент TDataSourse, выступающий в качестве независимого поставщика наборов данных элементам пользовательского интерфейса.
Выберем в его свойстве DataSet имя экземпляра компонента TlBTable, доступного в текущем проекте.
Задействуем стандартные компоненты для визуализации содержимого таблиц данных. Они уже рассматривались ранее и располагаются в категории палитры компонентов Data Controls (Элементы управления для данных). Например, можно добавить на форму компонент TDBGrid и связать его с источником данных (компонент TDataSource) через свойство DataSource, после чего на форме сразу появится содержимое выбранной таблицы.
Таким образом, мы создали законченное приложение, взаимодействующее с СУБД Interbase, которая, возможно, запущена на другом компьютере. При этом мы не написали ни одной строчки кода вручную.
Программная работа с наборами данных Interbase
Рассмотрим теперь, как модифицировать содержимое базы данных с помощью прикладного программного кода. Любые действия по модификации или считыванию содержимого базы данных должны выполняться в рамках транзакции. Пока транзакция не завершена, состояние базы всегда можно вернуть (говорят: «откатить») к исходному состоянию, существовавшему до начала внесения изменений. Если транзакция была явно завершена, то откат выполнить уже невозможно.
Начало (говорят еще, «старт») транзакции фиксируется вызовом метода StartTransaction() компонента TlBTransaction. Успешное завершение фиксируется методом Commit(), отказ от изменений (откат) — методом Rollback().
ПРИМЕР 38.2 Программная модификация таблицы базы данных Interbase
Пусть на форме имеются объекты TIBDatabase и TlBTransaction, настроенные на демонстрационную базу employee.gdb. Компонент TlBTable обеспечивает связь таблицы базы данных COUNTRY с приложением. В этом компоненте определены два поля, связанные с полями COUNTRY и CURRENCY таблицы COUNTRY. Доступ к этим полям (для текущей записи) осуществляется обращением к индексному свойству Fields таблицы (компонент TlBTable). Нумерация полей начинается с нуля.
Требуется по нажатию кнопки Добавить программно создать в таблице COUNTRY новую запись. Данные в поля таблицы должны быть занесены из двух полей формы, представленных компонентами TEdit. Работа программного обработчика нажатия на кнопку объяснена в комментариях.
void fastcall TForm2::ButtonlClick(TObject *Sender) { // Если транзакционный режим не установлен... if( ! IBTransactionl->Active )
// ...то явно стартовать транзакцию: IBTransactionl->StartTransaction();
// Последующие действия выполняются // в рамках текущей транзакции. // Они либо полностью выполняются, // либо целиком отменяются: try (
// Если искомая таблица базы данных неактивна, то., if( ! IBTablel->Active ) // открыть эту таблицу IBTablel->Ореп();
// С помощью метода Insert добавляем // в таблицу новую пустую запись: IBTablel->Insert() ;
/* Эта запись считается текущей.
Теперь надо занести в поля новой текущей записи значения из полей формы. При записи в поле данные форматируются с помощью свойства AsString. Оно представляет поле как хранилище текстовых данных. В зависимости от типа конкретного поля можно воспользоваться свойствами Aslnteger, AsBoolean и другими. */ IBTablel->Fields->Fields[0]->AsString = Editl->Text; IBTablel->Fields->Fields[1]->AsString = Edit2->Text; // Метод таблицы Post сохраняет // обновленную запись в базе данных: IBTablel->Post();
/‘До сего момента в рамках одной транзакции были выполнены две операции с таблицей: добавление новой записи (Insert) и обновление таблицы (Post) . Теперь можно завершить текущую транзакцию. Изменения в базе данных станут доступны другим пользователям только после успешного завершения транзакции. До этого момента любые формируемые этим прикладным кодом изменения таблицы в базе данных не видны. */ IBTransactionl->Commit();
catch(...) {
// При возникновении сбоя или исключительной ситуации
// вышеприведенный процесс взаимодействия с базой данных // надо полностью "откатить" - сделать содержимое таблицы // в точности таким, каким оно было до начала транзакции.
// Откат выполняется методом Rollback: IBTransactionl->Rollback();
// Затем вызываем стандартный обработчик исключительной
// ситуации. Он выдаст обычное сообщение об ошибке.
throw;
I Tool Palette
XT' dbExpress
х^ TSQLConnection gf TSQLDataSet ® TSQLQuery
^ TSQLStoredProc
A
;$ TSOLTM
^ TSQLMonitor
^|l TSimpleDataSet
+ DataSnap
M
38.5. Технология работы с серверами SQL
Специализированный набор компонентов для работы с СУБД Interbase создан потому, что эта СУБД была разработана самой корпорацией Borland и выпускается в виде коммерческого продукта. В системе C++Builder имеется еще один богатый набор компонентов — dbExpress. Он применяется при создании клиент-серверных приложений, взаимодействующих с другими популярными СУБД: Microsoft SQL Server, Oracle, IBM DB2 и другими.
Компоненты dbExpress
Компоненты dbExpress унифицированным способом взаимодействуют с поддерживаемыми СУБД через единый стандартизованный интерфейс, поддерживаемый набором драйверов.
Универсальность компонентов dbExpress имеет и негативную сторону — она подчас порождает определенные ограничения на взаимодействие с СУБД. В частности, компоненты dbExpress работают только с так называемыми однонаправленными наборами данных.
Однонаправленный набор данных — это набор данных в базе, который можно просматривать только в одном направлении: от начала к концу или от конца к началу. Перемещение от записи к записи сначала вперед, а потом назад в таких наборах запрещено.
Однонаправленные наборы данных обрабатываются очень быстро и почти не расходуют системные ресурсы. Но разработчик может запрограммировать просмотр
таких наборов (таблиц) лишь в одном направлении, только вперед или только назад.
При использовании компонентов dbExpress существенно ограничен набор интерфейсных компонентов, доступных в категории палитры инструментов Data Controls (Элементы управления для данных). Из него удалены все компоненты, позволяющие редактировать визуализируемое значение. Это плата за высокую скорость взаимодействия с компонентами dbExpress.
Технология создания клиент-серверного приложения dbExpress
Технология построения клиент-серверного приложения dbExpress такова.
Устанавливаем связь приложения с сервером SQL и конкретной базой данных.
Формируем обращение к конкретному набору данных (таблице, запросу или расширенному варианту команды SQL).
Добавляем компонент TDataSource, обеспечивающий независимость приложения от типа СУБД.
Проектируем интерфейс пользователя, организуем работу с базой данных через промежуточный источник данных. При этом доступен существенно ограниченный набор компонентов визуализации данных.
Рассмотрим эту технологию на примере.
ПРИМЕР 38.3
— Создание клиент-серверного приложения dbExpress, взаимодействующего с СУБД Microsoft SQL Server
В примере создается клиент-серверное приложение, в котором организован доступ к СУБД Microsoft SQL Server с помощью компонентов dbExpress.
Создание заготовки приложения.
Создадим пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL).
Настройка связи с СУБД.
Свяжем приложение с СУБД.
Добавим на форму компонент TSQLConnection из категории палитры инструментов dbExpress.
Укажем в его свойстве ConnectionName одно из текущих настроенных соединений с СУБД. Пусть это одно из стандартных соединений MSSQLConnection, обеспечивающих взаимодействие с сервером MS SQL Server 2005. При этом автоматически задаются свойства DriverName, GetDriverFunction и LibraryName компонента TSQLConnection, ответственные за настройку текущего соединения с СУБД через драйверы Microsoft.
Выберем соединение с демонстрационной базой pubs, входящей в стандартную поставку MS SQL Server 2005.
Доступ к таблице базы данных.
Организуем доступ приложения к подходящему набору данных. В наборе dbExpress имеется компонент TSQLDataSet, который представляет собой обобщенный вариант
компонентов TSQLTable и TSQLQuery. Он позволяет задавать команду SQL, а также способен работать как таблица базы. Форма работы данного компонента выбирается в его свойстве CommandType. Значение ctTable означает работу в виде таблицы, значение ctQuery — в виде запроса. Если нужна конкретная таблица, задействуют компонент TSQLTable; если нужен набор, динамически формируемый запросом SQL, выбирают компонент TSQLQuery (текст запроса SQL вводится в его свойство SQL).
Разместим на форме компонент TSQLTable.
В качестве соединения с СУБД выберем в свойстве SQLConnection ссылку на компонент TSQLConnection.
Конкретную таблицу базы данных выберем в свойстве TableName (например, jobs).
Добавим на форму компонент TDataSource.
Выберем в его свойстве DataSet ссылку на настроенный компонент TSQLTable.
Создание пользовательского интерфейса.
Разместим на форме элементы визуализации содержимого набора данных. Они, как обычно, связываются с этим набором посредством промежуточного компонента TDataSource. При этом придется учитывать, что введен запрет на такие компоненты визуализации категории Data Controls (Элементы управления для данных), как TDBGrid (таблица данных)6 или TDBEdit (поле данных).
Таблица TDBGrid предполагает одновременное представление в окне множества записей. Но набор данных в нашем проекте (компонент TSQLTable) однонаправленный, и в нем не реализована возможность перехода от текущей записи то к следующей, то к предыдущей.
Компоненты dbExpress не позволяют одновременно визуализировать содержимое нескольких записей, так как они не ведут локальную копию всего набора. По этой причине недопустимо использовать и компонент TDBEdit, предполагающий редактирование значения поля текущей записи. Ведь для «легких» наборов dbExpress не создаются буферы редактирования и не хранятся временные, модифицированные значения записей7. Поэтому задействованный компонент TDBEdit будет работать в режиме «Только чтение», независимо от значения свойства Readonly.
Среди немногих доступных компонентов панели Data Controls (Элементы управления для данных) отметим TDBText (надпись) и TDBNavigator (навигатор). В свойстве DataField для надписи задается отображаемое поле набора данных.
Навигатор предлагает функции перемещения по набору данных. При этом доступны все кнопки перемещения, в том числе и кнопки перехода к следующей и предыдущей записи. Однако попытка их одновременного использования приведет к ошибке времени выполнения.
(’ Если соединить компонент TDBGrid или подобный ему с источником данных, настроенным на компонент dbExpress, Проектировщик C++Builder сообщит об ошибке: Operation not allowed on as unidirectional dataset (Операция недопустима для однонаправленных наборов данных).
Проблемы с буферизацией компонентов группы dbExpress можно частично обойти. В этом поможет компонент TSimpleDataSet из группы dbExpress. Он представляет собой упрощенную реализацию буферизированного набора данных и, с одной стороны, обеспечивает быстрый доступ к серверам SQL, а с другой стороны, работает как обычный набор данных (наследник класса TDataSet), допускающий модификацию записей.
Вопросы
В чем преимущества клиент-серверной архитектуры?
Как визуально создается приложение, работающее с СУБД Interbase?
Как программируется взаимодействие с наборами данных Interbase?
Что такое однонаправленные и двунаправленные наборы данных?
Как организуется работа с серверами SQL с помощью компонентов группы dbExpress?
Как можно обойти ограничения компонентов группы dbExpress?
ГЛАВА 39
Технология многоуровневой работы с базами данных
Ранее рассмотренные технологии работы с базами данных использовали два звена: компьютер пользователя и сервер, на котором хранится сама база данных. В крупных системах схема работы с базой данных может содержать дополнительные звенья. Такие системы называют многоуровневыми.
Понятие многоуровневой архитектуры
На крупных предприятиях обычно действует немало разрозненных систем, функционируют операционные системы и СУБД разных марок и постоянно возникает необходимость в стыковке приложений и баз данных. Многие современные промышленные системы состоят из большого числа компонентов, обслуживающих сотни и тысячи пользователей и обращающихся к десяткам баз данных. Практически невозможно организовать одновременную и устойчивую работу таких систем на одном компьютере. Поэтому специально для крупных проектов была разработана многоуровневая архитектура.
Веб-архитектура - развитие клиент-серверной архитектуры
С помощью файл-серверных и клиент-серверных систем формируют и развертывают как локальные решения, работающие с базами данных, так и масштабные корпоративные системы. При этом соблюдаются все требования к целостности и сохранности данных, надежности и устойчивости работы, поддержке множества пользователей. Следующей, более развитой архитектурой управления базами данных считается распределенная или веб-архитектура.
Веб-архитектура (распределенная архитектура) — это программная архитектура, в которой элементы прикладной логики перенесены из клиентской программы на веб-сервер или серверные модули, распределенные по сети.
В такой архитектуре пользователь обращается к базе данных из браузера через Интернет. Подобный подход позволяет задействовать в качестве клиентских множество разнообразных цифровых устройств — от обычных персональных компьютеров до мобильных аппаратов — при условии, что они поддерживают работу с Интернетом. В такой архитектуре возникает дополнительное промежуточное звено между клиентской программой (браузером) и СУБД, роль которого играет
веб-сервер. Модуль, исполняемый на веб-сервере, анализирует запросы браузера, затем обращается к СУБД, выполняет расчетные действия над полученным от нее набором данных и передает результат в браузер в подходящем формате.
Создание приложений для веб-архитектуры рассматривается в пятой части книги.
Особенности многоуровневой архитектуры
Главные отличия многоуровневой архитектуры от клиент-серверной архитектуры следующие.
Работа с базами данных разных СУБД. Эти СУБД могут функционировать в разных сетях и под управлением разных операционных систем.
Наличие промежуточных серверных приложений, которые берут на себя задачи доступа к базе данных. Промежуточные приложения выполняются па специально выделенном сетевом компьютере, сервере приложений. Клиентские программы взаимодействуют с СУБД не напрямую, а через серверные модули.
Многоуровневая, многозвенная или многослойная (multitier) архитектура — это программная архитектура, в которой разные функции системы выделены в обособленные уровни.
Программы клиентского уровня выполняются на компьютере конечного пользователя и ответственны только за взаимодействие с человеком. Программы промежуточных серверных, уровней реализуют вычислительную логику, принимают от клиентских программ запросы и передают им результаты расчетов. Они также при необходимости обращаются к СУБД, а с человеком-пользователем напрямую не контактируют.
Программы, выполняемые на промежуточных серверных уровнях, называются программами уровня middleware.
Программы уровня баз данных — это конкретные СУБД, для которых серверные модули являются «клиентами», запрашивающими наборы данных.
Сервер приложений это системная программная среда, обеспечиваюш,ая устойчивую работу прикладных программ уровня middleware. Такой сервер функционирует на выделенном компьютере, который тоже нередко называют сервером приложений.
Многоуровневая архитектура позволяет существенно снизить нагрузку на сервер баз данных и клиентские программы и перенести значительную часть логики обработки таблиц из клиентских программ и СУБД на серверные звенья.
Если промежуточное серверное звено одно, то архитектуру «клиентское приложение — серверный модуль — СУБД» называют трехуровневой. Такая архитектура наиболее распространена.
Серверы приложений сложны в развертывании и сопровождении и весьма дороги. Среда C++Builder включает в себя готовые средства построения многоуровневых систем и не требует привлечения дополнительных дорогостоящих продуктов.
Рис. 39.1. Схема взаимодействия приложений в многозвенной архитектуреРис. 39.1. Схема взаимодействия приложений в многозвенной архитектуре
Рис. 39.1. Схема взаимодействия приложений в многозвенной архитектуре
Рис. 39.1. Схема взаимодействия приложений в многозвенной архитектуре
Создание многоуровневого приложения с помощью
технологии DataSnapКак правило, в прикладных проектах требуется обеспечить взаимодействие клиентов с сервером с помощью разных технологий: Windows DCOM, .NET, Web, CORBA и других. Система C++Builder включает набор компонентов DataSnap, на базе которых можно развернуть многоуровневое приложение с поддержкой всевозможных сетевых технологий.Рассмотрим пример создания сервера приложений DataSnap для платформ Windows 2000, 2003 и ХР.технологии DataSnapКак правило, в прикладных проектах требуется обеспечить взаимодействие клиентов с сервером с помощью разных технологий: Windows DCOM, .NET, Web, CORBA и других. Система C++Builder включает набор компонентов DataSnap, на базе которых можно развернуть многоуровневое приложение с поддержкой всевозможных сетевых технологий.Рассмотрим пример создания сервера приложений DataSnap для платформ Windows 2000, 2003 и ХР.Tool PaletteTool Palette«I • I' ?'* (fcExpress*- DataSnap^ TSocketConnection 7§Л TSImpteObjectBroker TWebConnection^| TConnectionBrokerBa TSharedConriection©•0 TLocalConnection+ BDEу«I • I' ?'* (fcExpress*- DataSnap^ TSocketConnection 7§Л TSImpteObjectBroker TWebConnection^| TConnectionBrokerBa TSharedConriection©•0 TLocalConnection+ BDEу
технологии DataSnap
Как правило, в прикладных проектах требуется обеспечить взаимодействие клиентов с сервером с помощью разных технологий: Windows DCOM, .NET, Web, CORBA и других. Система C++Builder включает набор компонентов DataSnap, на базе которых можно развернуть многоуровневое приложение с поддержкой всевозможных сетевых технологий.
Рассмотрим пример создания сервера приложений DataSnap для платформ Windows 2000, 2003 и ХР.
технологии DataSnap
Как правило, в прикладных проектах требуется обеспечить взаимодействие клиентов с сервером с помощью разных технологий: Windows DCOM, .NET, Web, CORBA и других. Система C++Builder включает набор компонентов DataSnap, на базе которых можно развернуть многоуровневое приложение с поддержкой всевозможных сетевых технологий.
Рассмотрим пример создания сервера приложений DataSnap для платформ Windows 2000, 2003 и ХР.
Tool Palette
Tool Palette
«I • I' ?
'* (fcExpress*
- DataSnap
^ TSocketConnection 7
§Л TSImpteObjectBroker TWebConnection
^| TConnectionBroker
Ba TSharedConriection
©•0 TLocalConnection
+ BDEу
«I • I' ?
'* (fcExpress*
- DataSnap
^ TSocketConnection 7
§Л TSImpteObjectBroker TWebConnection
^| TConnectionBroker
Ba TSharedConriection
©•0 TLocalConnection
+ BDEу
ПРИМЕР 39.1
Создание сервера приложений
Рассмотрим универсальный способ использования компонентов набора DataSnap, которые поддерживают разнообразные технологии взаимодействия сервера с клиентами.
Создание заготовки приложения.
Создадим пустую заготовку сервера командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Добавление модуля дистанционной связи.
Расширим сервер модулем дистанционной связи с СУБД.
Дадим команду File > New > Other (Файл > Создать > Другое). Выберем значок Remote Data Module (Модуль дистанционной связи с базой данных) в категории C++Builder Projects > Multitier (Проекты C++Builder > Многоуровневые).
В диалоговом окне Мастера построения модуля имеется поле CoClass Name для указания интерфейса модуля, дистанционно доступного клиентским приложениям. Введем в это поле, например, строку CB2006Test.
Нажмем кнопку ОК — к проекту добавится модуль, обеспечивающий дистанционную связь сервера с СУБД.
Настройка связи с СУБД.
Свяжем сервер приложений с одной из доступных СУБД. Задействуем для этого любые изученные ранее компоненты, например из категории dbExpress.
Добавим к модулю дистанционной связи компонент TSQLConnection.
В свойстве ConnectionName выберем, например, значение IBConnection (связь с СУБД Interbase).
Добавим к проекту компонент TSQLTable.
Свяжем его с СУБД — выберем имя компонента связи (экземпляр компонента TSQLConnection) в свойстве SQL Connection.
Выберем имя нужной таблицы базы данных в раскрывающемся списке свойства TableName.
Обработка клиентских запросов.
Настроим сервер на обработку множества клиентских запросов.
Добавим к проекту компонент TDataSetProvider из категории палитры инструментов Data Access (Доступ к данным). Он берет на себя рутинную деятельность сервера приложений по обслуживанию клиентских запросов.
Выберем в его свойстве DataSet ссылку на экземпляр компонента TSQLTable (объект SQLTablel).
Зададим значение True для свойства Active объекта SQLTablel — сервер будет активизирован.
Регистрация на серверном компьютере.
Зарегистрируем сервер в операционной системе серверного компьютера. Чтобы информация о созданном сервере приложений стала доступной в сети, его надо подготовить для постоянной работы на серверном компьютере. Для этого сервер однократно запускается с параметром /regserver. На этапе отладки сервер можно запустить непосредственно из среды C++Builder, поэтому зарегистрируем его в текущей версии Windows.
Откроем диалоговое окно параметров запуска среды C++Builder командой Run > Parameters (Запуск > Параметры).
Введем параметр /regserver в поле Parameters (Параметры).
Запустим сервер командой Run > Run (Запуск > Выполнить). Сервер автоматически зарегистрируется в текущей версии Windows и сразу завершит работу.
Удалим строку /regserver из поля Parameters (Параметры)1.
ПРИМЕР 39.2
• Создание клиентской программы
Назначение клиентской программы стандартно: доступ к СУБД через посредство сервера приложений и предоставление пользователю сведений из базы данных.
Создание заготовки приложения.
Создадим пустую заготовку клиентской программы командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
' Если ранее зарегистрированный сервер надо исключить из реестра системы, его запускают с параметром /unregserver.
Настройка связи с сервером.
Свяжем клиентскую программу с сервером приложений.
Добавим на форму компонент TDCOMConnection из категории палитры инструментов DataSnap.
Обратимся к его свойству ServerName — раскрывающийся список покажет все серверы DCOM, доступные в текущей версии Windows. Выберем значение Projectl. CB2006Test — имя сервера, созданного в предыдущем примере.
Присвоим свойству Connected значение True — дистанционный сервис станет доступен разработчику уже на этапе проектирования.
Настройка связи с удаленным набором данных. ■
Настроим клиентскую программу на связь с удаленным набором данных.
Добавим на форму компонент TCIientDataSet (клиентский, локальный набор данных) из категории палитры инструментов Data Access (Доступ к данным).
Выберем в его свойстве RemoteServer (дистанционная связь с сервером) имя доступного соединения DCOMConnectionl.
StarTeam Window HelpStarTeam Window HelpВыберем один из доступных на сервере провайдеров данных — серверный экземпляр компонента TDataSetProvider (серверное приложение может содержать несколько таких поставщиков). Нужный провайдер заносится в свойство ProviderName. Если выполняется тестовый пример, то будет доступен провайдер DataSetProviderl.
StarTeam Window Help
StarTeam Window Help
^ Projectl - Borland Developer Studio 2006 - Unit2.cpp
File Edit Search View Refactor Project Run Component Tools
J С ^^*0©^1£^ ► - 0 S 8* .> ; [^
Я Object InspectorV X $ Welcome Page ,^^Unit2,cpp
Species No (category*Species No (category*90020900309005090070900809002090030900509007090080Triggerfish Snapper Wrasse Angelfish CodTriggerfish Snapper Wrasse Angelfish Cod(common J4ame ]^Clown Triggerfish—Red EmperorGiant Maori WrasseBlue AngelfishLunartail Rockcod(common J4ame ]^Clown Triggerfish—Red EmperorGiant Maori WrasseBlue AngelfishLunartail Rockcodpp»■ 9 ®13: 20 Insert. Unlt2Wti»■ 9 ®13: 20 Insert. Unlt2WtiDCOMConnectionl 'vj '
Species No (category*
Species No (category*
90020
90030
90050
90070
90080
90020
90030
90050
90070
90080
Triggerfish Snapper Wrasse Angelfish Cod
Triggerfish Snapper Wrasse Angelfish Cod
(common J4ame ]^
Clown Triggerfish—
Red Emperor
Giant Maori Wrasse
Blue Angelfish
Lunartail Rockcod
(common J4ame ]^
Clown Triggerfish—
Red Emperor
Giant Maori Wrasse
Blue Angelfish
Lunartail Rockcod
p
p
»■ 9 ®13: 20 Insert. Unlt2Wti
»■ 9 ®13: 20 Insert. Unlt2Wti
| Properties | Events 1
El Linkage
ObjectBroken
El Midas
ComputerNar
*5erverGUID {3C361C7A-EAD0-4501 ;
ServerName Projectl.CB2006Test
[^Miscellaneous;
i Connected true.
; LoginPrompt .false,
»|Name IDCOMConnectionl'
(Tag ;0i
Ав shown
Puc. 39.2. Визуализация содержимого базы данных посредством технологии DataSnap
Настройка на таблицу базы данных.
Свяжем клиентский набор данных с конкретной серверной таблицей. Структура клиентского набора данных должна соответствовать структуре набора, дистанционно получаемого от сервера (рис. 39.2).
Вызовем редактор полей клиентского набора данных (компонент TCIientDataSet) командой контекстного меню Fields Editor (Редактор полей).
Выберем в редакторе список полей, соответствующих полям серверного набора данных.
Пользовательский интерфейс.
Подготовим пользовательский интерфейс клиентской программы.
Добавим на форму компонент TDataSource.
Выберем вего свойстве DataSet ссылку натекущий экземпляр класса TCIientDataSet (например, значение ClientDataSetl).
Разместим на форме таблицу TDBGrid и другие элементы визуализации данных из категории палитры инструментов Data Controls (Элементы управления для данных).
Выберем в ее свойстве DataSource ссылку на доступный компонент TDataSource.
Активизируем при необходимости клиентский набор данных (переведем в True значение его свойства Active) — полученные с сервера данные будут показаны в окне Проектировщика. Теперь клиентское приложение можно скомпилировать и запустить.
Дополнительные компоненты DataSnap
Разработчику доступен обширный набор компонентов категории DataSnap. С их помощью можно установить связь с сервером по разным сетевым протоколам, а также через внутренние программные интерфейсы сервера. При этом один сервер приложений может поддерживать самые разные формы взаимодействия с клиентскими модулями на базе технологии DataSnap большинство задач оптимизации обращений решается автоматически.
Связь с сервером приложений через сокеты
Компонент TSocketConnection предназначен для доступа к серверу приложений через сокеты (socket). Сокет представляет собой виртуальное окончание сетевого соединения с другим компьютером по протоколу TCP/IP, сформированное конкретной прикладной программой. Другими словами, сокет — это аналог файлового потока, который надо открыть (установить соединение). Затем в него можно записывать данные (посылать их на другой компьютер) или считывать их (принимать от другого компьютера). По окончании работы сокет надо закрыть3.
В Windows работа с сокетами обеспечивается технологией Winsock, реализованной в виде системной библиотеки winsock.dll, и утилитой ScktSrvr.exe, входящей в поставку C++Builder.
При использовании компонента TSocketConnection настраивают его свойство Address — IP-адрес удаленного компьютера, на котором запущен сервер приложений. Главный параметр настройки связи через сокеты — свойство ServerGUID, хранящее идентификационный номер GUID нужного сервера приложений. Данный номер генерируется автоматически при создании экземпляра компонента TDCOMConnection и записан в его свойство ServerGUID. Это значение копируют в аналогичное свойство компонента TSocketConnection. Сам сервер приложений доступен через свойство ServerName.
Связь с сервером приложений по протоколу HTTP
Вышеописанные способы связи с помощью DCOM-связи или сокетов подразумевают наличие прямой сетевой связи между сервером приложений и клиентскими программами. В ряде случаев проще использовать Интернет в качестве готовой среды передачи данных. В этом поможет компонент TWebConnection, обеспечивающий связь по протоколу передачи гипертекстовых данных HTTP.
Данный компонент настраивают также, как и компонент TSocketConnection — через свойства ServerGUID и SevrerName. Чтобы клиентское приложение могло с его помощью обращаться к серверу приложений, последний должен быть запущен в связке с веб-сервером.
В свойстве URL компонента TWebConnection вводят строку подобную “http://my-site.ru/cgi-bin/httpsrvr.dll”, где httpsrvr.dll — специальная библиотека C++Builder, предназначенная для вызова сервера приложений из веб-сервера. Клиентский набор данных настраивают на удаленный набор данных через свойство RemoteServer.
Другие возможности связи с сервером приложений
Если в приложении реализовано несколько соединений с сервером приложений, каждое из них будет функционировать самостоятельно и требовать отдельный пул ресурсов. Группу DCOM-соединений можно выделить в одно соединение и уже в его рамках переключаться от одного дистанционного поставщика к другому. Для этого применяют компонент TConnectionBroker, который выступает промежуточным звеном для клиентского набора данных.
Еще один способ оптимизации подключений заключается в создании промежуточной прослойки для каждого набора данных с помощью компонента TSharedConnection. Он предоставляет доступ к удаленным наборам данных в виде их списка (свойство ChildName) в рамках единственного серверного подключения.
Компонент TLocalConnection используют, если и поставщики данных, и клиентские наборы расположены в одном приложении. Чтобы не перегружать его связями с самим собой, достаточно выбрать в свойстве RemoteServer клиентского набора экземпляр этого компонента, а затем с помощью метода GetServer() получить доступ к внутреннему программному интерфейсу lAppServer, обеспечивающему взаимодействие с серверными наборами данных.
Вопросы
Что такое многоуровневая архитектура?
Зачем нужен сервер приложений?
Как создать сервер приложений с помощью технологии DataSnap?
Как создать клиентское приложение для многоуровневой архитектуры с помощью технологии DataSnap?
Расскажите о дополнительных компонентах технологии DataSnap.
ГЛАВА 40
Формирование запросов к базам данных на языке SQL
Язык структурированных запросов к базам данных Structured. Query Language (SQL) предназначен для обработки содержимого реляционных баз данных. Он должен поддерживаться любой реляционной СУБД в соответствии с пятым правилом Кодда. Выражения SQL формируют наборы данных, модифицируют значения записей, создают и удаляют элементы наборов и целые таблицы.
Язык SQL позволяет одной командой отобрать подмножества записей из одной или нескольких таблиц по заданному критерию отбора, сортировать содержимое таблиц и выполнять другие действия по фильтрации и модификации содержимого таблиц.
Язык SQL был разработан в корпорации IBM в середине 1970-х годов в рамках проекта по созданию СУБД System/R. Американский национальный институт стандартизации ANSI принял официальный стандарт языка SQL в 1986 году. Через год язык SQL был также стандартизирован международной организацией по стандартизации ISO. Последней считается версия SQL3, принятая в 1999 году. В 2003 году выпущен вариант SQL, расширенный поддержкой языка разметки XML. '
Напомним основные понятия, которые необходимо знать при использовании языка SQL.
Реляционная база данных — это набор двумерных таблиц, состоящих из столбцов (называемых также полями) и строк (называемых также записями). По столбцам (полям), хранящим значения определенных типов, организуется единая структура всех записей в одной таблице.
Между таблицами можно формировать логические взаимосвязи. СУБД обеспечивает целостность связанных данных, запрещая некорректные смысловые модификации связанных записей.
В каждой таблице должно быть поле, по которому можно однозначно идентифицировать каждую запись. Оно введено для повышения эффективности работы с таблицами. Такое поле называют первичным ключом. Значение первичного ключа в каждой записи должно быть уникальным. Нередко разработчики задействуют дополнительные поля в качестве первичных ключей, если доподлинно известно, что значения в таких полях будут неповторяющимися. Подчас в качестве первичного ключа используется обычный числовой счетчик. Формирование первичного ключа обычно возлагается на СУБД, и прикладной проектировщик базы данных не задается проблемой его создания.
Штатное средство для работы с командами SQL
В систему C++Builder входит средство, позволяющее в интерактивном режиме вводить команды языка SQL, запускать их на исполнение и тут же получать результат работы. Окно SQL Window (Окно SQL) открывают с помощью средства просмотра данных. В нем следует выбрать соединение с базой данных и дать команду SQL Window (Окно SQL) с помощью контекстного меню.
При этом откроется окно, предназначенное для ввода команды SQL. Для выполнения команды следует нажать кнопку Execute SQL (Выполнить команду SQL). Результат работы команды — временный набор данных — представляется в этом же окне*.
Запись команд SQL
Язык SQL обычно используют не для создания законченных программ, а для записи отдельных выражений, формирующих конкретный набор данных. Эти выражения задействуются программами на разных языках (в частности, C++) для запросов к СУБД и получения от них наборов данных.
Среди выражений SQL имеются команды и сообщения.
Команда, или оператор SQL — это инструкция для СУБД на выполнение заданных действий в базе данных.
Сообщение SQL информирует СУБД об определенном событии в клиентской программе.
Выражения SQL записываются с помощью букв латинского алфавита, цифр и специальных знаков. Они состоят из ключевых слов (аналога ключевых слов языка C++), пользовательских идентификаторов и констант2.
Команды почти всегда начинаются с ключевого слова и по нему именуются, например команда SELECT.
Стандарт ANSI требует, чтобы при записи команд SQL использовались только прописные буквы. Однако это правило соблюдается редко, и практически все реализации SQL позволяют записывать команды и идентификаторы в любом регистре. Например, следующие команды идентичны:
SELECT select Select
Команды SQL состоят из нескольких логически законченных частей. Например, команда отбора записей может содержать часть, задающую таблицу и набор полей для анализа, и часть, определяющую условие отбора записей в результирующий набор. Такие элементы команд SQL называют предложениями SQL.
Разные формы запроса SQL, выдающие одинаковый результат, могут иметь существенно разную производительность. При подготовке выражений SQL следует учитывать реальные размеры таблиц и промежуточных наборов данных и постоянно проверять эффективность разных вариантов на практике.
Строки в выражениях SQL заключают в одинарные кавычки.
Предложение, как и команду SQL, обычно называют по ключевому слову, с которого оно начинается. Например, предложение WHERE.
Логические выражения SQL
Язык SQL допускает использование в предложениях логических выражений — выражений, которые вычисляют логическое значение.
В логическом выражении SQL можно использовать операции сравнения (<, >, <=, >=, =. <>), круглые скобки и операции and (логическое И), or (логическое ИЛИ) и not (логическое отрицание). Схема построения таких выражений аналогична схеме построения логических выражений языка C++.
Язык SQL позволяет сравнивать числовые, символьные и строковые значения. В последнем случае алгоритм выявления отношения между символами надо выяснять из документации к конкретной СУБД или проверять опытным путем. В зависимости от используемой кодировки (особенно русскоязычной), сравнение одних и тех же символов может давать разные результаты.
Отбор записей с помощью команды SELECT
Наиболее популярной и наиболее часто используемой командой языка SQL считается команда SELECT. Она предназначена для отбора группы записей из одной или нескольких таблиц и позволяет задать условие отбора.
В общем случае эта команда записывается следующим образом;
SELECT описок-полей FROM имя-таблицы;
Список полей представляет собой набор приведенных через запятую полей указанной таблицы, значения которых включаются в результирующий набор. Пусть, например, база данных содержит таблицу Customer, содержащую тысячу записей. Каждая запись состоит из полей ID, Name, Address, Phone. Команда:
SELECT Name, Phone FROM Customer;
сформирует набор из тысячи записей, каждая из которых содержит два поля: Name и Phone3.
Часто необходимо отобрать записи таблицы целиком, не отбрасывая никаких полей. Тогда вместо списка полей в выражении SQL задают символ *, обозначающий выбор всех нолей:
SELECT * FROM Customer;
Отказ от дублирования
Если в список нолей, добавляемых в результирующий набор, не входит ключевое поле, то в полученном наборе могут встретиться одинаковые записи. Чтобы избежать дублирования, перед списком полей надо поставить ключевое слово distinct.-
Последовательность полей в запросе SQL и, соответственно, в результирующем наборе может не соответствовать порядку полей в исходной таблице. Разработчик сам определяет порядок полей в результирующем наборе.
SELECT DISTINCT список-полей FROM имя-таблицы;
Команда с предикатом di stinct выдаст результат, из которого удалены дублированные значения.
Фильтрация записей
На значения полей в результирующем наборе данных можно накладывать ограничения. Для этого после имени таблицы надо поставить ключевое слово where, за которым следует логическое выражение SQL. В результате в итоговый набор попадут лишь те записи, для которых это выражение истинно.
Фильтрация записей — это отбор записей из таблицы по определенному условию.
Команды фильтрации записывается следующим образом:
SELECT список-полей FROM имя-таблицы WHERE условие;
Пусть, например, требуется отобрать список всех пользователей, идентификационный номер которых больше значения 12345, а телефонный номер отличается от строки 000-00-00. Для этого надо выполнить следующую команду SQL:
SELECT Name FROM Customer WHERE (ID > 12345) AND (Phone <> '00000-00');
Специальные условия отбора
Язык SQL, в дополнение к обычным операциям сравнения, позволяет проводить специальные проверки. В частности, можно проверить, входит ли определенное значение в явно заданное множество величин. Для этого используется запись: значение IN (список-значений)
Список значений представляет собой последовательность элементов, разделенных запятыми. Если значение в левой части выражения совпадает с одним из этих элементов, выражение считается истинным. Например:
Customer IN ('Вася', 'Петя')
Если значение поля Customer равно 'Петя' или 'Вася', выражение считается истинным.
Для числовых величин удобно задать начальную и конечную границы диапазона, в который должно попасть проверяемое значение.
Для этого используется ключевое слово between: значение BETWEEN нижняя-граница AND верхняя-граница Например:
X BETWEEN 2.87 AND 3.14
Значение поля х должно лежать в диапазоне от 2,87 до 3,14.
Границы диапазона входят в него. Если значение поля х в точности равно 3,14, результат приведенного выше сравнения будет истинным.
Для анализа строк предназначена операция like . Она проверяет, имеется ли в анализируемом значении указанная подстрока:
значение-строки LIKE шаблон
Значение строки проверяется на соответствие шаблону. В шаблоне используют обычные символы, а также два специальных: знак подчеркивания _ и знак процента %. Знак подчеркивания означает, что в данном месте может находиться любой отдельный символ. Знак процента заменяет строку произвольной, в том числе нулевой, длины. Например, следующие выражения истинны:
'Петя' LIKE 'Пе_я'
'Петя' LIKE 'П%я'
А вот выражения в следующем примере ложны:
'Пея' LIKE 'Пе_я'
'Петя' LIKE '%П%а'
Если надо проверить наличие в строке самих знаков _ или %, перед ними указывают символ наклонной черты /. Следующее выражение истинно:
'П_я%' LIKE 'П/_я/%'
Символ наклонной черты в шаблоне необходимо повторить дважды, как, например, в следующем истинном выражении:
'/' LIKE '//'
Сортировка набора
Записи в результирующем наборе могут быть упорядочены в нужной последовательности. Порядок записей изменяют с помощью сортировки.
Сортировка набора — это упорядочение записей набора по значению определенного поля, в порядке возрастания или убывания.
Если итоговый набор требуется отсортировать по значению определенного поля, следует воспользоваться предложением order by. После этих ключевых слов указывается поле, по которому выполняется сортировка.
По умолчанию сортировка выполняется по возрастанию. Если требуется выполнить сортировку по убыванию, после имени поля надо добавить ключевое СЛОВО DESC.
Например:
SELECT Name, Address FROM Customer
WHERE (ID > 12345) AND (Phone <> '000-00-00') ORDER BY Name DESC;
В отобранном наборе значений записи будут представлены в алфавитном порядке имен пользователей.
Если для упорядочения задано несколько полей, выполняется многоуровневая сортировка. Записи сначала сортируются по первому указанному полю, при совпадении значений выполняется сортировка во следующему полю и так далее.
Например:
ORDER BY Name, Address DESC;
Результирующий набор будет упорядочен по полю Name (в возрастающем порядке). Записи с одинаковыми значениями в поле Name дополнительно сортируются по значению поля Address в порядке убывания.
Проверка неопределенных полей
В таблицах нередко встречаются поля с неопределенными значениями. Их содержимое нельзя явно сравнивать с помощью вышеописанных операций. Единственное допустимое в отношении таких полей действие — проверка на неопределенность. Она выполняется командой is null. Например:
SELECT Name FROM Customer WHERE Phone IS NULL;
В результирующий набор попадут записи, у которых значение поля Phone не определено.
Агрегирование и группировка
Функции агрегирования обрабатывают все значения столбца (поля) таблицы базы данных. Они позволяют получить обобщенные, итоговые результаты.
Агрегирование — это класс операций, выполняемых над всеми записями набора данных.
Пусть, например, требуется вычислить общий бюджет всех отделов компании. Список отделов хранится в таблице Department, а бюджет каждого отдела записан в поле BUDGET.
Функция зим определяет сумму всех значений поля budget. Имя нужного поля указывается в качестве параметра:
SELECT SUM(Budget) FROM Department;
Такая команда вернет не набор записей, а значение агрегированной функции sum для всего набора. В данном примере оно равно 15410000.
К часто применяемым функциям агрегирования относятся функции avg (вычисление среднего значения), min и мах (поиск минимального и максимального значений), а также count. Последняя функция не обрабатывает значения поля, а просто считает количество значений данного поля в таблице, не учитывая нулевые и неопределенные. Например:
SELECT COUNT(Budget) FROM Department;
В таблице будет обнаружено 21 значение.
Уточнение работы функций
Работу функции count можно уточнять с помощью дополнительных предложений. Пусть надо подсчитать число уникальных значений в определенном столбце (без учета повторяющихся значений). Перед параметром функции (именем поля) в круглых скобках дополнительно ставится ключевое слово distinct:
SELECT COUNT(DISTINCT Budget) FROM Department;
С учетом повторений в таблице обнаруживается 14 значений.
Предикат distinct можно применять и в других функциях агрегирования, например в функции зим. Нередко требуется, чтобы функция работала, учитывая все поля, как с повторяющимися, так и с неповторяющимися значениями. Это важно, например, при подсчете бюджета — у разных отделов могут быть одинаковые бюджеты. В такой ситуации вместо distinct используют ключевое слово all, прямо противоположное DISTINCT по смыслу7.
Если требуется узнать общее число записей в таблице, вместо имени поля можно поставить символ *. Тогда запрос:
SELECT COUNT(*) FROM Department;
вернет общее число записей в таблице (в данном примере — 21).
Скалярные аргументы
Функции агрегирования принимают в качестве параметров не только имена полей, но и выражения. Так, сумма всех продаж компании (стоимость проданного товара хранится в поле total_value таблицы sales) вычисляется следующим образом:
SELECT SUM (TOTAL_VALUE) FROM SALES;
Составит она 2250591,03 долл.
Сумма скидок (величина скидки представлена как вещественное число в поле discount) рассчитывается следующим образом:
SELECT SUM(TOTAL_VALUE * DISCOUNT) FROM SALES;
В качестве результата будет показана сумма в 182994,9 долл.
Видно, что в качестве аргумента можно задавать допустимые выражения над полями подходящих типов. Они корректно рассчитываются на основе всех значений таблицы, автоматически подставляемых из каждой записи.
Группировка результатов
Предложение группировки результатов group by расширяет концепцию агрегирования. Оно позволяет сочетать в одном выражении функции и поля. Дополняя запрос именами нолей, мы получим в качестве результата уже не одно значение, а набор строк. Функция агрегирования применяется к подмножествам значений, выделяемых для каждого уникального значения поля группировки.
7 Не всегда известно, как определенная функция выполняется в конкретной СУБД. Хотя стандарт языка определяет работу функций и команд SQL однозначно и недвусмысленно, в сомнительных ситуациях лучше проверить форму работы на примерах.
ПРИМЕР 40.1
.; f • ,Л&тХУЙЙ^ЙКЙ®^^Использование средств группировки
Пусть на основе таблицы SALES требуется узнать, какие сотрудники (поле CUST_NO) оформили заказы на максимальное количество единиц продукции (поле QTY_ORDERED). В этой таблице сотруднику с уникальным идентификатором (значением CUST_NO) может соответствовать несколько записей, описывающих разные контракты с разным количеством проданных единиц товара. По каждому из сотрудников (по каждому подмножеству записей для каждого сотрудника), мы хотим выполнить операцию МАХ. Она вернет максимальное значение поля QTY_ORDERED для конкретного сотрудника (конкретного значения CUST_NO). Так как сотрудников (уникальных значений CUST_NO) в таблице несколько, выдается результирующий набор пар CUST_NO — MAX(QTY_ORDERED). Чтобы операция МАХ применялась к каждому подмножеству повторяющихся значений поля CUST_NO, результат должен быть сгруппирован по этому полю.
Соответствующий запрос записывается следующим образом:
SELECT CUST_NO, MAX(QTY_ORDERED) FROM SALES GROUP BY CUST_NO;
Borland Developer Studio 2006{. ){П|^|
•1?ЖЙЙ:SELECT CUST NO,|MAX(QTY_ORDERED)iFROM SALES GROUP BY CUST„NO;!•1?ЖЙЙ:SELECT CUST NO,|MAX(QTY_ORDERED)iFROM SALES GROUP BY CUST„NO;!Data Explorer pageData Explorer page0e ДО Storch Sew Refactar Eroject gun Component Joe i |8 “^ ^4 ^f *j i^ » W i® ^ . • аб ► - О И a • * , * - * j.* Welcome Page SQL Window 2. ■. U ^ Data Explorer? X
•1?ЖЙЙ:
SELECT CUST NO,|
MAX(QTY_ORDERED)i
FROM SALES GROUP BY CUST„NO;!
•1?ЖЙЙ:
SELECT CUST NO,|
MAX(QTY_ORDERED)i
FROM SALES GROUP BY CUST„NO;!
Data Explorer page
Data Explorer page
- йГTables^
- й BOOK ♦ Й COUNTRY T CUSTOMER I* qg DEPARTMENT +' й ECOMODELROOT S- ECOJD
s tg ECO_ORMAPPING S ng ECO.TABLES S ECO_TYPE s ng EMPLOYEE
S eg EMPLOYEE-PROJECT HI, а «9 job
® 19 NEW_USERS ~ Si PROJECT
ffi Й PHOJ.DEPT.BUDGET
Щ 1)9 SALARY-HISTORY
a n9 SALES'
'S Й STUDENT
.$ 1)9 USERS
*. •** У I M *1
$53 Project.,,Model ..~^^DataE... f
Puc. 40.1. Результат запроса SQL
^ vt 'fl1*4th&-«r^T^ *6 44 1"
Фильтрация групп записей
Предложение group by не позволяет использовать для отбора нужных записей предложение where, которое подразумевает проверку условия для отдельной записи. Средство group by ориентировано не на одну запись, а на группу. В то
же время обойтись без предложения where в прикладных задачах сложно — отбор записей по определенному критерию требуется постоянно. Для того чтобы отобрать семейство сгруппированных записей, используют предложение having.
Пусть в вышеприведенном примере надо отобрать только сотрудников, сумевших продать за один раз не менее 100 единиц товара. В этом случае надо воспользоваться следующим запросом SQL:
SELECT CUST_NO, MAX(QTY_ORDERED) FROM SALES
GROUP BY CUST_NO
HAVING MAX(QTY_ORDERED) >= 100;
Применение предложения having допустимо только по отношению к полям, которые задействованы в операции группировки. Например, задать в предложении having условие для поля city нельзя, так как оно не выбрано для группировки.
Стандарт ANSI SQL позволяет располагать фильтр where до операции группировки. Но этот фильтр не может применяться после функций агрегирования — в тексте запроса он должен стоять до ключевых слов group by:
SELECT CUST_NO, MAX(QTY_ORDERED) FROM SALES
WHERE QTY_ORDERED > 100
GROUP BY CUST_NO;
Результирующие наборы значений можно дополнительно сортировать с помощью предложения order by, которое всегда располагается в конце команды группировки. В нем надо указать одно из полей, заданных после ключевого слова SELECT.
Вопросы
Зачем нужен язык SQL?
Как выполняется отбор записей из таблицы?
Как избежать дублирования записей в результирующем наборе?
Как выполняется фильтрация записей?
Как сортируются наборы данных?
Как работают функции агрегирования?
Как работают функции группировки?
Как организуется фильтрация сгруппированных результатов?
ГЛАВА 41 Язык запросов SQL: дополнительные возможности
В главе 40 рассмотрены основные возможности команды select по формированию результирующего набора записей. Однако язык SQL позволяет создавать и более сложные запросы, используя заложенные в него дополнительные возможности.
Вычисляемые поля
Запрос SQL может выдавать в результирующий набор не только реально существующие и отфильтрованные поля исходного набора, но и вычисленные «на лету» (столбцы с такими полями называют выходными столбцами)'.
Вычисляемое поле — это поле результирующего набора записей, значение которого не берется из таблиц базы данных, а вычисляется в соответствии с выражением, заданным в запросе.
Рассмотрим, например, следующий запрос:
SELECT CUST_NO, QTY_ORDERED / 100 FROM SALES
Второй, выходной столбец результирующей таблицы не соответствует никакому полю исходной таблицы. Он будет содержать значение поля qty_ordered, деленное на 100 (рис. 41.1).
В дополнение к вычисляемым полям в выражении SQL можно использовать текстовые константы, которые берутся в одинарные кавычки. Они просто вставляются в каждую запись как отдельное поле, например:
SELECT 'Номер пользователя: ', CUST_NO, 'Крупные заказы: ', QTY_ ORDERED/100 FROM SALES
Соединение таблиц
В одном запросе допускается использование полей из разных таблиц базы данных. Возможность соединения таблиц в одном выражении SQL существенно повышает потенциальную мощь запросов SQL.
В операции соединения таблиц в одном запросе объединяются указанные поля из разных таблиц.
Иногда необходимо упорядочить результирующий набор по одному из вычисляемых столбцов. Так как имен у них нет, вместо них задают числа — порядковые номера столбцов. Нумерация полей в списке SELECT начинается с единицы.
•* Borland Developer Studio 2006
File Edit Search yew Refactfir Project gun Component Tools StarTeam Window Help
"3 ф
^ > *A
» .
4»l
i*
* Welcome page BDPJBConnl: CUSTOMER SQL Windowl? IBConnl ^Mtoi~
SELECT 'Номер пользователя:', Cust_No, 'Крупные заказы: ', Qty_Ordered /100 FROM Sales
^ Data Explorer
¥ X
3
Columnl
Номер пользователя.
Номер пользователя: J Номер пользователя:
Номер пользователя. Номер пользователя: Номер пользователя: Номер пользователя: । Номер пользователя: Номер пользователя:
; Номер пользователя: : Номер пользователя:
Номер пользователя:
CUST-NO
1004
1004
1010
1010
1010
1012
1012
1012
1012
1005
1005
1001
Column2
Крупные заказы. Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы: Крупные заказы.
Column3
О О о о о о о о о о о 10
.Data Explorer page
, 5QL®Rt
+ ^ DB2
- 9 Interbase
- @Ц IBConnl
- q§ Tables
a
- в
«а
♦ a
BOOK COUNTRY CUSTOMER DEPARTMENT ECOMODELROOT ECOJD ECO.ORMAPPING ECO_TABLES ECO_TYPE EMPLOYEE EMPLOYEE-PROJECT JOB PROJECT
-^J ^Project Ма... ^Model View |^DataExplorer f
JjfcLjtieLflalatts.
Puc. 41.1. Результат запроса SQL с вычисляемыми полями
В команде select перед каждым полем указывается префикс: имя таблицы с точкой. В списке таблиц from приводятся все задействованные в запросе таблицы.
Пусть требуется узнать всех сотрудников (таблица Employee), новая зарплата которых установлена на уровне более 100 тысяч долларов в год. Информация
> Borland Developer Studio 2006
ЕЖ®
01e ДО Search yew Refactor project gun Component Took StarTeam Window Help
, # # »/ ti^’tf e a e * > < л Welcome Page BDPrBConnl: CUSTOMER BDPrlBCr
s ss » . Ф - Ф - ^
^ Data Explorerф X
SELECT Employee.Last_Name, Salary_Hi$tory.New_SalaryA
FROM Employee, Salary_History.
WHERE Sa!ary_History.New_Salary > 100000;v
LAST_NAME
►1 Baldwin
I Baldwin : Baldwin : Bender
Bender
_ ! Bennet Bennet Bennet
_ Bishop 'Bishop
Brown; Brown
. 1NEW-SALARY 105900~ 102750 116100 105900 102750 105900 102750 116100 105900 102750 105900 102750
»1 Й
■ti Й
S 1^
CUSTOMER DEPARTMENT ECOMODELROOT ECOJD ECO_ORMAPPING ECO-TABLES ECO-TYPE EMPLOYEE
Indices
^ EMP_NO ^ FIRST_NAME 6g LAST_NAME % PHONE-EXT ^ HIRE_DATE ^] DEPT_NO ^j JOB_CODE 6gJOB_GRADE
'Data Explorer page
^qProject^ ^ Model Vi... |^DataEx...f
о зарплатах хранится в таблице Salary_History, новый оклад записан в поле new. salary. Тогда соответствующий запрос должен выглядеть следующим образом:
SELECT Employee.Last_Name, Salary„History.New_Salary
FROM Employee, Salary„History
WHERE Salary„History.New„Salary > 100000;
Никаких принципиальных отличий от прежних вариантов команды select здесь нет, добавились только префиксы таблиц. В результирующий набор попадают все возможные комбинации соединения двух таблиц, удовлетворяющие заданному условию (рис. 41.2). При этом используется связь между таблицами Salary_History и Employee по ключевому полю emp_no, что позволяет определить сотрудника, однозначно соответствующего записи о зарплате.
Эквисоединение
Как правило, в запросах к нескольким таблицам используются так называемые эквисоединения. Термин « эквисоединение >> заимствован из реляционной алгебры.
Эквисоединение — это соединение между таблицами, в котором проверяется равенство значения поля одной таблицы значению поля другой таблицы. Если, например, требуется сформировать список сотрудников с названиями отделов, в которых они работают, можно задействовать следующую команду SQL:
SELECT Employee.First_Name, Employee.Last„Name, Department. Department
FROM Employee, Department
WHERE Employee.Dept_No = Department.Dept_No;
При эквисоединении в данном выражении выполняется проверка равенства значений полей Dept_No в таблицах Employee и Department.
$ Borland Developer Studio 2006$ Borland Developer Studio 2006(kJ©®(kJ©®вв0е Ed* Search yew Refacty inject Sun Component look agrTeem Wndow Це!р fl [ййДЕ0е Ed* Search yew Refacty inject Sun Component look agrTeem Wndow Це!р fl [ййДЕ-*»»._t^-• »a-*# , #• ’ у a *-*»»._t^-• »a-*# , #• ’ у a *’([SELECT Employee.First..Name,Employee.La$t_Name..(■ Department Departmentt) FROM Employee. Department'! WHERE Employee Dept_No = Department.Dept_No,’([SELECT Employee.First..Name,Employee.La$t_Name..(■ Department Departmentt) FROM Employee. Department'! WHERE Employee Dept_No = Department.Dept_No,^ Data Explorer~+ Й BOOK"* @ COUNTRYл Й CUSTOMERЙ DEPARTMENT* Я ndices^ Data Explorer~+ Й BOOK"* @ COUNTRYл Й CUSTOMERЙ DEPARTMENT* Я ndiceseV XeV XS'S'(HRST.NAME' _ Oliver H MaryS Michael’ Robert. '(Kelly ■Stewart j Walter(HRST.NAME' _ Oliver H MaryS Michael’ Robert. '(Kelly ■Stewart j WalterLAST.NAMELAST.NAMEMacDonaldYanowski NelsonBrownHallSteadmanJohnson NordstromMacDonaldYanowski NelsonBrownHallSteadmanJohnson NordstromDEPARTMENTCorporate Headquarters Corporate Headquarters Sales and Marketing Sales and Marketing Engineering Engineering FinanceFinance Marketing MarketingDEPARTMENTCorporate Headquarters Corporate Headquarters Sales and Marketing Sales and Marketing Engineering Engineering FinanceFinance Marketing Marketing■Data Explorer page■Data Explorer page^Project... ^ Model Vi^Project... ^ Model Vit #t #^ DEPT.NO 4 department ^j HEAD_DEPT ^ MNGR.NO ^ BUDGETLOCATIONPHONE_NO ECOMODELROOT ECOJD ECO_ORMAPPING ECO_TABLES ECO_TYPE^ DEPT.NO 4 department ^j HEAD_DEPT ^ MNGR.NO ^ BUDGETLOCATIONPHONE_NO ECOMODELROOT ECOJD ECO_ORMAPPING ECO_TABLES ECO_TYPE»»
$ Borland Developer Studio 2006
$ Borland Developer Studio 2006
(kJ©®
(kJ©®
в
в
0е Ed* Search yew Refacty inject Sun Component look agrTeem Wndow Це!р fl [ййДЕ
0е Ed* Search yew Refacty inject Sun Component look agrTeem Wndow Це!р fl [ййДЕ
-*»»._t^-• »a-*# , #• ’ у a *
-*»»._t^-• »a-*# , #• ’ у a *
’([SELECT Employee.First..Name,Employee.La$t_Name.
.(■ Department Department
t) FROM Employee. Department
'! WHERE Employee Dept_No = Department.Dept_No,
’([SELECT Employee.First..Name,Employee.La$t_Name.
.(■ Department Department
t) FROM Employee. Department
'! WHERE Employee Dept_No = Department.Dept_No,
^ Data Explorer
~+ Й BOOK
"* @ COUNTRY
л Й CUSTOMER
Й DEPARTMENT
* Я ndices
^ Data Explorer
~+ Й BOOK
"* @ COUNTRY
л Й CUSTOMER
Й DEPARTMENT
* Я ndices
e
V X
e
V X
S'
S'
(HRST.NAME
' _ Oliver H MaryS Michael
’ Robert
. '(Kelly ■Stewart j Walter
(HRST.NAME
' _ Oliver H MaryS Michael
’ Robert
. '(Kelly ■Stewart j Walter
LAST.NAME
LAST.NAME
MacDonald
Yanowski Nelson
Brown
Hall
Steadman
Johnson Nordstrom
MacDonald
Yanowski Nelson
Brown
Hall
Steadman
Johnson Nordstrom
DEPARTMENT
Corporate Headquarters Corporate Headquarters Sales and Marketing Sales and Marketing Engineering Engineering Finance
Finance Marketing Marketing
DEPARTMENT
Corporate Headquarters Corporate Headquarters Sales and Marketing Sales and Marketing Engineering Engineering Finance
Finance Marketing Marketing
■Data Explorer page
■Data Explorer page
^Project... ^ Model Vi
^Project... ^ Model Vi
t #
t #
^ DEPT.NO 4 department ^j HEAD_DEPT ^ MNGR.NO ^ BUDGET
LOCATION
PHONE_NO ECOMODELROOT ECOJD ECO_ORMAPPING ECO_TABLES ECO_TYPE
^ DEPT.NO 4 department ^j HEAD_DEPT ^ MNGR.NO ^ BUDGET
LOCATION
PHONE_NO ECOMODELROOT ECOJD ECO_ORMAPPING ECO_TABLES ECO_TYPE
»
»
Puc. 413. Результат запроса SQL с эквисоединением
Работа с копиями таблицы
Команду select можно применять к нескольким копиям одной и той же таблицы. При этом после ключевого слова from имя таблицы записывается несколько раз, причем сопровождается произвольным идентификатором. Он используется для различения копий таблицы в запросе. В качестве префикса полей используют уже не имя таблицы, а эти идентификаторы (так называемые переменные корреляции).
Использование объединения таблицы с нею самой полезно, если в ней надо отыскать связанные элементы. Пусть требуется найти всех сотрудников с одинаковыми именами (поле first_name). Для этого надо сравнить все записи таблицы — каждую с каждой. В языке C++ для решения такой задачи пришлось бы использовать вложенный цикл, а язык SQL подобные манипуляции над данными (множествами) выполняет автоматически:
SELECT aaa.Full_Name, bbb.Full_Name
FROM Employee aaa, Employee bbb
WHERE (aaa.First_Name = bbb.First_Name) AND (aaa.Full_Name <> bbb.Full_Name);
*■ Borland Developer Studio 2006
gie fiat Search Sew Refactfir groiect Bun Component tools StyTeem yglndow
^ Data Explorer9 X^ Data Explorer9 X'®"й BOOK§Й S COUNTRY № Й CUSTOMER frig DEPARTMENTй И ndices DEPT_NO DEPARTMENT HEAD_DEPTfig MNGR-NO BUDGET LOCATION PHONE_NO Я Л ECOMODELROOT а й ECO.ID ft в ECO_ORMAPPING t в ECO_TABLES ft # E CO.TYPE*.^Project... ^Model Vi... MSI'®"й BOOK§Й S COUNTRY № Й CUSTOMER frig DEPARTMENTй И ndices DEPT_NO DEPARTMENT HEAD_DEPTfig MNGR-NO BUDGET LOCATION PHONE_NO Я Л ECOMODELROOT а й ECO.ID ft в ECO_ORMAPPING t в ECO_TABLES ft # E CO.TYPE*.^Project... ^Model Vi... MSI^^sl.iXj^’* eg «x. ► и в « ..•^ ^fWekomePagejBDPilBConpbCUSTOMERtBDPilBConnl.EMP ' ’ 2 ,
^ Data Explorer9 X
^ Data Explorer9 X
'®"й BOOK§
Й S COUNTRY № Й CUSTOMER frig DEPARTMENT
й И ndices DEPT_NO DEPARTMENT HEAD_DEPT
fig MNGR-NO BUDGET LOCATION PHONE_NO Я Л ECOMODELROOT а й ECO.ID ft в ECO_ORMAPPING t в ECO_TABLES ft # E CO.TYPE*.
^Project... ^Model Vi... MSI
'®"й BOOK§
Й S COUNTRY № Й CUSTOMER frig DEPARTMENT
й И ndices DEPT_NO DEPARTMENT HEAD_DEPT
fig MNGR-NO BUDGET LOCATION PHONE_NO Я Л ECOMODELROOT а й ECO.ID ft в ECO_ORMAPPING t в ECO_TABLES ft # E CO.TYPE*.
^Project... ^Model Vi... MSI
I ' SELECT aaa.Full_Name, bbb.Full_Name- i
J j FROM Employee aaa. Employee bbb' |
д WHERE (aaa.Fir$t_Name = bbb.Fir$t_Name) AND (aaa.Full_Name <>j
.(| bbb.Full_Name);
Рис. 41.4. Результат запроса SQL с копиями одной таблицы
Переменные корреляции особо важны, если требуется сформировать не просто список результатов, а набор комбинаций, удовлетворяющих заданному условию. Например, для определенной работы требуется подобрать пару сотрудников (таблица Employee) с уровнем квалификации 4-го разряда (поле job_grade).
В этом поможет следующее выражение:
SELECT aaa.Full—Name, bbb.Full_Name
FROM Employee aaa, Employee bbb
4) AND (bbb.Job_Grade4) AND (bbb.Job_Grade4) AND (aaa.Emp_No4) AND (aaa.Emp_NoWHERE (aaa.Job_Grade bbb.Emp_No);
4) AND (bbb.Job_Grade
4) AND (bbb.Job_Grade
4) AND (aaa.Emp_No
4) AND (aaa.Emp_No
*- Borland Developer Studio 2006*- Borland Developer Studio 2006BillBill
*- Borland Developer Studio 2006
*- Borland Developer Studio 2006
Bill
Bill
Efe E& Search yew Refect^ Project gun Component Tools Star Team Window Help
.,.*-» ^!Ш’.* MW ^ > * - 031.,.*-» ^!Ш’.* MW ^ > * - 031Page;SDPTBConr.i: CUSTOMER1 BD₽:IBConnl: SALARY-HISTORY'#ё1#ЙЙ11SELECT aaa.Full_Name, bbb.Full„NameFROM Employee aaa. Employee bbbWHERE (aaa Job_Grade = 4]AND (bbb.Job_Grade = 4] AND (aaaEmp_No<> bbb.Emp_No);Page;SDPTBConr.i: CUSTOMER1 BD₽:IBConnl: SALARY-HISTORY'#ё1#ЙЙ11SELECT aaa.Full_Name, bbb.Full„NameFROM Employee aaa. Employee bbbWHERE (aaa Job_Grade = 4]AND (bbb.Job_Grade = 4] AND (aaaEmp_No<> bbb.Emp_No);^ Data Explorer^ Data Explorer* X* X1Я1Яi FULL_NAME (Weston, К J iWeston, K. J. j Westen, K. J IWeston, К J 'Weston, K. J iWeston. K. J ;Weston, K. J Weston, K. J ’Weston, K. J. ’Weston, K. J. iWeston, K. Ji FULL_NAME (Weston, К J iWeston, K. J. j Westen, K. J IWeston, К J 'Weston, K. J iWeston. K. J ;Weston, K. J Weston, K. J ’Weston, K. J. ’Weston, K. J. iWeston, K. J{Data Explorer page{Data Explorer pageWW$$®?$®?$$$«9«9I FULL_NAME1Lee, Tani Stansbury, Willie Phong, Leslie Nordstrom. Carol Leung, Luke Sutherland Claudia Williams, Randy Page, Mary Yamamoto. T akashi Ferrari. Roberto Yanowski, MichaelI FULL_NAME1Lee, Tani Stansbury, Willie Phong, Leslie Nordstrom. Carol Leung, Luke Sutherland Claudia Williams, Randy Page, Mary Yamamoto. T akashi Ferrari. Roberto Yanowski, MichaelBOOKCOUNTRY CUSTOMER DEPARTMENT ECOMODELROOT ECOJDEC0_0RMAPPING ECO_TABLES ECO_TYPE EMPLOYEE EMPLOYEEJWJECT JOB PROJECTPROJ.DEPT.BUDGET SALARY.HISTORYBOOKCOUNTRY CUSTOMER DEPARTMENT ECOMODELROOT ECOJDEC0_0RMAPPING ECO_TABLES ECO_TYPE EMPLOYEE EMPLOYEEJWJECT JOB PROJECTPROJ.DEPT.BUDGET SALARY.HISTORY»»•■ ^ Indices§3 EMP_N0^•■ ^ Indices§3 EMP_N0^^Project ... ^Model Vi.,, IM^Project ... ^Model Vi.,, IM
.,.*-» ^!Ш’.* MW ^ > * - 031
.,.*-» ^!Ш’.* MW ^ > * - 031
Page;SDPTBConr.i: CUSTOMER1 BD₽:IBConnl: SALARY-HISTORY
'#ё1#ЙЙ11
SELECT aaa.Full_Name, bbb.Full„Name
FROM Employee aaa. Employee bbb
WHERE (aaa Job_Grade = 4]AND (bbb.Job_Grade = 4] AND (aaaEmp_No
<> bbb.Emp_No);
Page;SDPTBConr.i: CUSTOMER1 BD₽:IBConnl: SALARY-HISTORY
'#ё1#ЙЙ11
SELECT aaa.Full_Name, bbb.Full„Name
FROM Employee aaa. Employee bbb
WHERE (aaa Job_Grade = 4]AND (bbb.Job_Grade = 4] AND (aaaEmp_No
<> bbb.Emp_No);
^ Data Explorer
^ Data Explorer
* X
* X
1Я
1Я
i FULL_NAME (Weston, К J iWeston, K. J. j Westen, K. J IWeston, К J 'Weston, K. J iWeston. K. J ;Weston, K. J Weston, K. J ’Weston, K. J. ’Weston, K. J. iWeston, K. J
i FULL_NAME (Weston, К J iWeston, K. J. j Westen, K. J IWeston, К J 'Weston, K. J iWeston. K. J ;Weston, K. J Weston, K. J ’Weston, K. J. ’Weston, K. J. iWeston, K. J
{Data Explorer page
{Data Explorer page
W
W
$
$
®?
$
®?
$
$
$
«9
«9
I FULL_NAME1
Lee, Tani Stansbury, Willie Phong, Leslie Nordstrom. Carol Leung, Luke Sutherland Claudia Williams, Randy Page, Mary Yamamoto. T akashi Ferrari. Roberto Yanowski, Michael
I FULL_NAME1
Lee, Tani Stansbury, Willie Phong, Leslie Nordstrom. Carol Leung, Luke Sutherland Claudia Williams, Randy Page, Mary Yamamoto. T akashi Ferrari. Roberto Yanowski, Michael
BOOK
COUNTRY CUSTOMER DEPARTMENT ECOMODELROOT ECOJD
EC0_0RMAPPING ECO_TABLES ECO_TYPE EMPLOYEE EMPLOYEEJWJECT JOB PROJECT
PROJ.DEPT.BUDGET SALARY.HISTORY
BOOK
COUNTRY CUSTOMER DEPARTMENT ECOMODELROOT ECOJD
EC0_0RMAPPING ECO_TABLES ECO_TYPE EMPLOYEE EMPLOYEEJWJECT JOB PROJECT
PROJ.DEPT.BUDGET SALARY.HISTORY
»
»
•■ ^ Indices
§3 EMP_N0^
•■ ^ Indices
§3 EMP_N0^
^Project ... ^Model Vi.,, IM
^Project ... ^Model Vi.,, IM
Puc. 415. Использование переменных корреляции в запросе SQL
Вложенные запросы
Запросы разрешено вкладывать друг в друга.
В основной запрос передаются результаты предварительных запросов, которые фильтруют значения в таблицах. Выполняется подобная операция одним выражением SQL.
Вложенный запрос (подзапрос) — это запрос, включенный в другой запрос. Результат его работы передается для дальнейшей обработки родительскому запросу.
ПРИМЕР 41.1
-„-,- Использование вложенных запросов
Пусть требуется узнать, какой отдел возглавляет сотрудник с фамилией Bender (таблица Employee).
Определим ключ (значение поля EMP_NO) записи, в которой хранится сотрудник с фамилией Bender. Это сделает, например, следующая команда SQL:
SELECT EMP_NO FROM EMPLOYEE WHERE LAST_NAME = 'Bender';
Этот запрос выдаст значение поля EMP_NO (в данном примере — значение 105), которое далее надо использовать в запросе к таблице Department. Следующее выражение SQL отыщет запись таблицы Department, в которой значение поля MNGR_NO (идентификатор сотрудника — начальника отдела) равно 105:
SELECT DEPARTMENT
FROM DEPARTMENT
WHERE MNGR_NO = 105;
Если организовать выдачу запросов из прикладной программы, потребуется два раза соединяться с сервером, два раза отправлять ему команды SQL и два раза дожидаться результата. Однако эти два запроса можно объединить в один. Для этого вместо значения 105 в последнем выражении надо подставить полный текст первого запроса, заключив его в круглые скобки:
SELECT DEPARTMENT FROM DEPARTMENT
WHERE MNGR_NO =
(SELECT EMP_NO FROM EMPLOYEE
WHERE LAST_NAME = 'Bender');
Сначала рассчитывается значение 105, формируемое первым запросом, взятым в скобки, а затем оно передается в «вышестоящий» основной запрос.
Соединение таблиц во вложенном запросе
Во вложенных запросах, как и в отдельных запросах (см. выше), допускаются соединения таблиц.
Пусть, например, требуется отобрать всех пользователей из таблицы Customer, которые оформили заказы на 1000 единиц товара (поле qty_ordered таблицы Sales). Соответствующий запрос записывается следующим образом:
SELECT * FROM CUSTOMER tabi
WHERE 1000 IN
(SELECT QTY_ORDERED FROM SALES tab2
WHERE tabl.CUST_NO = tab2.CUST_NO);
Здесь задействованы переменные корреляции tabi и tab2, связывающие две таблицы через поле cust_no (ключ пользователя). Как правило, в подобных запросах используют команду in для проверки присутствия определенного значения в списке значений, выдаваемых вложенным запросом.
Проверка пустого запроса
Предложение exists проверяет, выдает ли подзапрос хотя бы одну запись. Если выдаваемый им набор непустой, предложение exists выдает значение «истина». Фактически это предложение определяет, пуст ли заданный набор.
Чаще всего предложение exists применяется в запросах, где требуется сформировать список записей, связанных с другими таблицами. Например, пусть требуется показать всех пользователей, которые оформили хотя бы один заказ. Для этого лучше всего подходит предложение exists, которое проверит, имеется ли для пользователя непустой список заказов. Если список не пуст, информация о пользователе добавляется в результирующий набор:
SELECT * FROM CUSTOMER tabi
WHERE EXISTS
(SELECT * FROM SALES tab2
WHERE tabl.CUST_NO = tab2.CUST_NO);
Проверка набора на наличие значений
Предложение any проверяет, имеется ли в наборе, выданном подзапросом, хотя бы одно из заданных значений. Она применяется только во вложенных запросах. Например, требуется отобрать всех сотрудников (таблица Employee), работающих в одной стране с заказчиком (таблица Customer). Для этого надо проверить, какие записи таблицы Employee совпадают по значению поля job_country (страна работы) со значением поля country (страна) таблицы Customer:
SELECT tabi.FULL_NAME, tabi.JOB_COUNTRY, tab2.CUSTOMER
FROM EMPLOYEE tabi, CUSTOMER tab2
WHERE JOB_COUNTRY = ANY
(SELECT COUNTRY FROM CUSTOMER);
Предложение all противоположно по смыслу предложению any. Оно проверяет, равно ли заданное значение не одному, а каждому из значений тестируемого набора записей.
Однако работает эта команда не совсем обычно. Выражение наподобие:
ALL (SELECT COUNTRY FROM CUSTOMER)
выдаст значение «истина» только в случае, если все значения в столбце country одинаковы, а такое маловероятно. Поэтому команда all обычно применяется не для проверки равенства, а для проверки неравенства.
Пусть требуется найти все фирмы-заказчики (таблица Customer) в странах, где нет ни одного сотрудника — представителя подрядчика (таблица Employee). Соответствующий запрос можно записать так:
SELECT tab2.COUNTRY, tabi.JOB_COUNTRY, tab2.CUSTOMER
FROM EMPLOYEE tabi, CUSTOMER tab2
WHERE tab2.COUNTRY <> ALL
(SELECT JOB_COUNTRY FROM EMPLOYEE);
Выражение:
tab2 .COUNTRY <> ALL
(SELECT JOB_COUNTRY FROM EMPLOYEE);
истинно, если страна пользователя (поле country таблицы Customer) не соответствует ни одной из стран (job_country) таблицы сотрудников.
Обновление записей
Команды SQL позволяют не только отбирать и фильтровать содержимое таблиц, но и модифицировать их содержимое. Для этого предназначена группа так называемых команд обновления.
Команда обновления — это команда SQL, модифицирующая значение в базе данных.
Добавление новой записи
Команда insert добавляет в таблицу новую запись4:
INSERT INTO имя-таблицы
VALUES (значение-первого-поля, значение-второго-поля, . . . ) ;
В списке значений представлены значения полей новой записи, следующие в порядке полей. Пусть, например, имеется таблица USERS с двумя текстовыми полями Name (первичный ключ) и Address. Добавляют новую запись следующим образом:
INSERT INTO USERS
VALUES ( 'Petrov', 'Novosibirsk' );
Иногда для определенных полей желательно оставить значения по умолчанию (они формируются СУБД). Тогда после имени таблицы в круглых скобках явно указывают имена полей, которым соответствуют значения по умолчанию.
Удаление записей
Команда delete удаляет записи из таблицы. Она имеет две формы. При использовании первой формы:
DELETE FROM имя-таблицы;
из заданной таблицы исключатся все записи.
При использовании второй формы:
DELETE FROM имя-таблицы WHERE условие;
из заданной таблицы исключаются записи, удовлетворяющие условию. Например:
DELETE FROM USERS WHERE Name='Petrov';
Модификация значений полей
Команда update модифицирует отдельные поля по всей таблице. Она записывается следующим образом:
UPDATE имя-таблицы SET поле = значение;
Команда INSERT срабатывает не всегда. Обычно в базе данных сформировано множество взаимосвязей между таблицами. Добавление новой записи без учета этих связей (попытка ввода неверного первичного ключа) может привести к нарушению ссылочной целостности.
Записи в таблице модифицируются — в указанное поле каждой записи заносится заданное значение.
Команда update может быть расширена ключевым словом where. В нем задается условие, показывающее, какие записи необходимо модифицировать. Например, следующее выражение SQL изменит сведения о местоположении отделов (поле location таблицы Department), связанные с переводом их из Сан-Франциско в Торонто:
UPDATE DEPARTMENT SET LOCATION='Toronto '
WHERE LOCATION ='San Francisco';
После ключевого слова set можно указать несколько пар «поле=значение»:
UPDATE DEPARTMENT
SET LOCATION ='Toronto', BUDGET = 2000000
WHERE LOCATION ='San Francisco';
В качестве значений в разделе set разрешено использовать допустимые выражения SQL.
Команды обновления и команда SELECT
Команды обновления могут стыковаться с запросами select. Это позволяет создавать новые наборы на базе запросов и гибко модифицировать и удалять записи способами, которые далеко не всегда удается реализовать с помощью условия
WHERE.
Добавление записи в таблицу из другой таблицы такой же структуры обеспечивает команда insert в следующей форме:
INSERT INTO таблица
команда-SELECT
Например:
INSERT INTO NEW_USERS
SELECT * FROM USERS
WHERE CONTRACT_DATE > 01.01.2006;
В таблицу NEW USERS добавятся записи из таблицы USERS о пользователях, заключение контракта с которыми состоялось (поле contract_date) после 1 января 2006 года.
Обработка таблиц
Язык SQL позволяет динамически менять структуру базы данных, создавая новые таблицы и изменяя структуру существующих.
Создание таблицы
Команда create table создает новую таблицу в базе данных:
CREATE TABLE имя-таблицы (список-описаний-полей);
Список описаний полей представляет собой набор пар:
имя-поля тип-поля. Каждая пара отделяется запятой
При необходимости тип поля может дополняться спецификатором длины (для текстовых полей).
Например, следующая команда создаст таблицу NEW_USERS с тремя полями: целочисленным полем id и двумя текстовыми полями name и address, каждое из которых может содержать строку длиной до 10 символов.
CREATE TABLE NEW_USERS
(ID INTEGER, NAME CHAR(10), ADDRESS CHAR(10));
После типа поля допустима конструкция not null, которая запрещает хранение в поле неопределенных значений.
CREATE TABLE NEW_USERS
(ID INTEGER NOT NULL, NAME CHAR(10), ADDRESS CHAR(10));
По умолчанию неопределенное значение поля допустимо. Такое состояние поля в языке SQL представляет константа null (неопределенное значение).
Уникальные поля и первичные ключи
Ключевое слово unique, которое ставится после типа поля, накладывает на это поле требование уникальности значений.
Уникальное поле — это поле, в котором все значения в таблице должны быть неповторяющимися.
Обычно уникальные поля играют роль первичного ключа. Первичный ключ можно определить в таблице явно с помощью конструкции primary key. Эта конструкция, как и unique, требует неповторяемости значений поля. Однако она может быть задана только для одного поля, выбранного в качестве первичного ключа.
Ключевое слово unique может использоваться в команде создания таблицы неоднократно.
Ограничения на значения полей
На значения полей при модификации можно наложить ограничения. Проверку соответствия значения заданному ограничению выполняет сервер базы данных. Ограничение задается ключевым словом check, за которым в круглых скобках следует условие ограничения.
Если при обновлении таблицы возникает некорректная попытка модифицировать такое поле (записать в него значение, нарушающее ограничение), подобное изменение блокируется.
Например, следующая команда создаст таблицу и одновременно запретит ввод в ноле id нулевого или отрицательного значения:
CREATE TABLE NEW_USERS
(ID INTEGER CHECK(ID > 0), NAME CHAR(10), ADDRESS CHAR(10));
Иногда желательно вводить в поле значения только из заранее сформированного списка. Для этого следом за ключевым словом check в круглых скобках вводится следующая языковая конструкция:
имя-поля IN (спискок-значений)
Например, разрешить ввод в поле name только значения 'Иван', 'Петр' или 'Сергей' можно следующим образом:
CREATE TABLE NEW_USERS
(ID INTEGER, NAME CHAR(10) CHECK (NAME IN ('Иван', 'Петр', 'Сергей')), ADDRESS CHAR(10));
Значения полей по умолчанию
В поле создаваемой таблицы можно сразу ввести значение по умолчанию. Для этого в конце описания поля ставится конструкция:
DEFAULT = значение
Например:
CREATE TABLE NEW_USERS (ID INTEGER,
NAME CHAR(10) DEFAULT = 'Сергей', ADDRESS CHAR(10));
Создание индексов
Для создания индексов5 для конкретных полей таблицы служит команда create index:
CREATE INDEX имя-индекса ON имя-таблицы (имя-поля)
Имя индекса нужно для того, чтобы впоследствии его можно было уничтожить. Существующий индекс удаляют командой:
DELETE INDEX имя-индекса
Модификация структуры таблицы
Структуру существующей таблицы можно модифицировать, например добавить новые поля. Для этого предназначена команда alter table:
ALTER TABLE имя-таблицы ADD имя-поля тип-поля;
Например, можно пополнить ранее созданную таблицу NEW_USERS полем telephone:
ALTER TABLE NEW_USERS
ADD TELEPHONE CHAR(7);
Новое поле добавляется в конец записи. Исходно его значение для существующих записей считается неопределенным.
3 Индексы создают для повышения скорости выборки элементов из таблицы. Однако индексирование замедляет операции добавления, модификации и удаления записей.
Удаление таблицы
Пустую таблицу можно уничтожить. Команда:
DROP TABLE имя-таблицы
удаляет заданную таблицу из базы данных при условии, что в ней нет ни одной записи.
Представления
В дополнение к таблицам современные СУБД активно используют так называемые представления.
Представление — это именованный набор данных, который физически не существует в таблице, а формируется динамически при выполнении запроса SQL. Текст запроса хранится в базе данных, а не в клиентской программе, поэтому с точки зрения пользователя базы данных представление по структуре ничем не отличается от таблицы.
Содержимое представления формируется динамически, выполнением запроса в момент обращения к представлению по имени. Результат работы запроса и становится содержимым представления, которое передается клиентскому приложению.
Представление создают командой create view:
CREATE VIEW имя-представления AS команда
В качестве команды выступает, как правило, команда select, а ее результирующий набор считается содержимым представления. Например, команда
CREATE view my_view as
SELECT * FROM CUSTOMER;
Data ExplorerData Explorerя ^ InletbaseАт 9°i IBConnT+:Tables-я <% MY.VIEWgg CUST.NO6g CUSTOMER§ CONTACT-FIRST6 CONTACT-LAST6g PHONE.NO§ ADDRESSJJNE1 ADDRESS.LINE2% CITY6g STATE-PROVINCEgg COUNTRY POSTAL-CODE ON.HOLD- ©a PHONE_LIST% EMP_NO _^Project” ^ Model View | ^Data Exp... Pя ^ InletbaseАт 9°i IBConnT+:Tables-я <% MY.VIEWgg CUST.NO6g CUSTOMER§ CONTACT-FIRST6 CONTACT-LAST6g PHONE.NO§ ADDRESSJJNE1 ADDRESS.LINE2% CITY6g STATE-PROVINCEgg COUNTRY POSTAL-CODE ON.HOLD- ©a PHONE_LIST% EMP_NO _^Project” ^ Model View | ^Data Exp... Pсоздаст в базе данных преставление, обращение к которому сформирует точное содержимое таблицы Customer. Однако физически в базе данных хранится лишь текст этого запроса SQL, хотя название представления доступно наряду с именами таблиц.
Data Explorer
Data Explorer
я ^ InletbaseА
т 9°i IBConnT
+:Tables
-
я <% MY.VIEW
gg CUST.NO
6g CUSTOMER
§ CONTACT-FIRST
6 CONTACT-LAST
6g PHONE.NO
§ ADDRESSJJNE1 ADDRESS.LINE2
% CITY
6g STATE-PROVINCE
gg COUNTRY POSTAL-CODE ON.HOLD
- ©a PHONE_LIST
% EMP_NO _
^Project” ^ Model View | ^Data Exp... P
я ^ InletbaseА
т 9°i IBConnT
+:Tables
-
я <% MY.VIEW
gg CUST.NO
6g CUSTOMER
§ CONTACT-FIRST
6 CONTACT-LAST
6g PHONE.NO
§ ADDRESSJJNE1 ADDRESS.LINE2
% CITY
6g STATE-PROVINCE
gg COUNTRY POSTAL-CODE ON.HOLD
- ©a PHONE_LIST
% EMP_NO _
^Project” ^ Model View | ^Data Exp... P
Представления, доступные в базе данных можно увидеть с помощью средства просмотра данных C++Builder, в разделе Views (Представления).
При обращении к СУБД через любые компоненты C++Builder, предназначенные для взаимодействия с базами данных, все представления доступны наравне с таблицами — по именам.
С точки зрения прикладного разработчика никаких различий между представлениями и таблицами нет.
Вопросы
Что такое вычисляемые поля?
Как объединить в одном запросе несколько таблиц?
Что такое эквисоединения?
Как организуются вложенные запросы?
Как набор данных проверяется на наличие или отсутствие в нем записей?
Как добавить запись в таблицу?
Как модифицировать значения полей записи в таблице?
Как удалить запись из таблицы?
Как создать новую таблицу?
Как удалить таблицу?
Что такое представления и как их создают?
^) РАЗДЕЛ
Технологии
создания веб-приложений
Принципы современных веб-технологий
Технологии Indy
Разработка веб-серверов и веб-клиентов
Представление документов и наборов данных с помощью
языка XML
Применение технологии IntraWeb
Базы данных и интернет
ГЛАВА 42
История развития веб-технологий
Веб-технологии — это в настоящее время наиболее широко используемый в сети Интернет способ обмена и предоставления информации. Время показало, что правильное применение этих технологий позволяет повысить эффективность труда программистов и сократить время разработки информационных систем.
Ключевые технологии Интернета
Интернет, как его понимают сотни миллионов пользователей во всем мире, — это, прежде всего, множество сайтов, связанных друг с другом гиперссылками. Для просмотра содержимого сайтов служат стандартные клиентские программы, известные как браузеры. Браузер обращается к заданному пользователем сайту, получает от него по сети файлы для просмотра и показывает их в своем окне.
Браузер — это программа, предназначенная для просмотра содержимого сайтов в Интернете.
Полученный браузером файл почти всегда представляет собой обычный текстовый файл, содержимое которого размечено с помощью так называемых тегов форматирования.
Тег форматирования — это последовательность текстовых символов, характеризующая фрагмент текста и информирующая браузер о том, каким образом его следует представить на экране.
Язык гипертекстовой разметки HTML — это язык разметки содержимого веб-страницы. Он стандартизирует назначение тегов форматирования и правила их использования.
Теги позволяют выделить определенную часть текста, например, для отображения ее на экране полужирным шрифтом или для выравнивания по центру экрана. Браузер интерпретирует теги и соответствующим образом отображает элементы полученного файла. Теги также задают ссылки на дополнительные объекты: изображения, видеоклипы, архивы и другие ресурсы. Самый важный из тегов форматирования —- это тег, описывающий гиперссылку.
Гиперссылка — это ссылка из текущего документа на файл или иной объект (возможно, размещенный в Интернете).
Именно тег гиперссылки организует переход между сайтами (между страницами разных сайтов) и связывает все сайты в одно цельное информационное пространство.
К ключевым технологическим решениям, на которых базируется современная глобальная Сеть, относятся следующие.
Универсальный идентификатор ресурсов URI (Uniform Resource Identifier). Он задает стандартным образом адрес требуемого ресурса Сети (например, страницы HTML).
Протокол передачи гипертекстовой информации HTTP (HyperText Transfer Protocol). С его помощью серверы обмениваются информацией с клиентскими программами (браузерами).
Язык разметки документов XML и его реализация в виде языка гипертекстовой разметки HTML.
Каждый сетевой ресурс физически расположен на серверном компьютере в локальной или глобальной сети. Клиентские программы отсылают серверу запросы по протоколу HTTP и получают ответ, как правило, в виде документа в формате XML или HTML. Разработчику прикладных Интернет-решений важнее всего изучить средства обработки гипертекстовой информации и построения распределенных серверных приложений.
Возникновение гипертекста
Много тысяч лет лучшие умы человечества задумывались над возможностью организации альтернативных, нелинейных способов представления текстов. Документальные тексты, справочные материалы, исторические труды и словари, с которыми удобнее работать, быстро перемещаясь от одного понятия к другому, гораздо эффективнее в электронном виде, дополненном средствами поиска нужной информации. Всемирная сеть вообще немыслима без браузеров, позволяющих переходить от документа к документу щелчками мыши. Практически все современные комфортные системы компьютерной навигации основаны на концепции гипертекста, позволяющего быстро переходить между связанными по смыслу частями одного или нескольких документов.
История ссылок в материалах восходит к папирусам и глиняным табличкам (3000 лет до н. э.), на которых были обнаружены перекрестные указания на другие работы. Сотрудники Александрийской библиотеки (300 лет до н. э.) уже целенаправленно занимались подготовкой аннотаций и ссылок. Первые системы, напоминавшие современный гипертекст, возникли в начале второго тысячелетия н. э. Они представляли собой наборы пометок в печатных изданиях Библии, отсылающих человека к схожим по тематике разделам.
Современная история гипертекста начинается с июля 1945 года. Доктор Ванневар Буш (Vannevar Bush), ранее служивший научным советником президента США Ф. Д. Рузвельта, упомянул в своей статье «As We May Think» идею машины «Метех». Машина предназначалась для хранения научных материалов таким образом, что разделы одного материала можно было связать с любыми разделами других документов.
Спустя 15 лет нью-йоркский издатель Стэнли Райс (Stanley Rice) решил разработать универсальный гипертекстовый каталог, хранящий дписания книг. Его поддержал Норман Шарф (Norman Scharpf), директор Ассоциации графических коммуникаций (Graphic Communications Associations, GCA). Ассоциация запу-
стила проект GenCode, в рамках которого придумывались способы формального описания структуры и способа представления иерархически организованных документов. При этом структура и способ представления были независимы друг от друга.
В 1963 году легендарный компьютерный деятель Дуглас Энгельбарт (Douglas Engelbart), впоследствии изобретший манипулятор мышь, а тогда возглавлявший центр развития человеческих способностей в Стэнфордском исследовательском институте, выдвинул идею интерактивного взаимодействия человека и машины. Правда, реализовать ее на практике он не сумел из-за ограниченности существовавших тогда интерфейсов. В те же годы бакалавр философии Теодор Нельсон (Theodor Nelson) из Гарвардского университета увлекся идеей программы подготовки текстов, которая позволяла бы пересматривать сделанную работу, сравнивать элементы материала и отменять внесенные изменения. Нельсон назвал ее «Xanadu». Он первым в мире предложил термины гипертекст (hypertext) и гипермедиа (hypermedia) в 1965 году на конференции Ассоциации компьютерной машинерии (Association of Computing Machinery, ACM). «Гипертекст, — заявил Теодор, — означает непоследовательное письмо, текст, который ветвится и предоставляет читателю выбор наилучшего варианта чтения на интерактивном экране».
Первым полноценным гипертекстовым приложением стала система Hypertext Editing System для мэйнфреймов IBM/360, которую в 1967 году совместно с Теодором Нельсоном закончил Эндрис Ван Дам (Andries van Dam). Он возглавлял в университете Брауна исследовательский коллектив, работающий при финансовой поддержке IBM. В ходе проекта Ван Дам выдвинул идею обратного возврата по ссылке (в современных браузерах ее реализует обязательная кнопка Назад). Год спустя он создал первую коммерческую гипертекстовую систему FRESS (File Retrieval and Editing System). Она эксплуатировалась компанией Philips на протяжении 20 лет.
К тому времени появились практические результаты деятельности Дугласа Энгельбарта. В 1968 году на осенней компьютерной конференции в СанФранциско он продемонстрировал систему NFS (oN Line System), способную обрабатывать сотни тысяч документов (она существует и по сей день в виде коммерческого продукта Augment). В процессе ее создания Энгельбарт предложил идеи систем поддержки коллективной работы в масштабе реального времени, телеконференций, электронной почты, онлайновых подсказок и многооконного интерфейса.
В 1969 году сотрудник IBM Чарльз Голдфарб (Charles Goldfarb) возглавил проектирование компьютерной системы обслуживания юридических контор. Под его руководством был создан первый язык разметки документов Generalized Markup Language (GML), в котором была реализована концепция типа документа (формально определенного шаблона, описывающего схему внутреннего построения схожих документов) и вложенных друг в друга структур. GML не зависел ни от марки компьютеров, ни от операционной системы, и IBM удалось перевести 90% своей документации в этот формат.
К 1970 году завершился проект GenCode Ассоциации GCA. Стало ясно, что в разных задачах будут востребованы разные способы описания документов, поэто-
му возникла потребность в расширяемой метасхеме, представляющей все виды описаний. Чтобы можно было включать одни документы в другие, авторы проекта решили ориентироваться на гипертекстовый способ организации данных.
Через два года Дональд Мак-Кракен (Donald McCracken) и Роберт Акскин (Robert Akscyn), сотрудники Университета Карнеги-Меллона, запрограммировали первую гипермедиа-систему ZOG. Она представляла собой базу из так называемых кадров: информационных элементов, связанных с ними описаний их структуры и набора меню для перехода к другим кадрам. Сначала система ZOG работала только на текстовых терминалах IBM, но вскоре была перенесена на сеть из 28 графических рабочих станций PERQядepнoгo авианосца Carl Vinson.
К 1978 году комитет по обработке информации Американского национального института стандартов ANSI всерьез заинтересовался языками подготовки гипертекстовых данных. Чарльз Голдфарб стал в этом комитете руководителем нового направления, связанного с формированием стандарта на мощный язык разметки документов, который получил название Standard Generalized Markup Language (SGML). В его основу был заложен язык GML. Первый рабочий вариант спецификации появился в 1980 году. Через три года Ассоциация GCA приняла шестую рабочую версию SGML в качестве промышленного стандарта (GCA 1011983), поддержанного Министерством обороны и Налоговым управлением США. Спустя два года сформировалась международная группа пользователей SGML. Начался расцвет гипертекстовых технологий.
В 1986 года международная организация по стандартизации ISO одобрила стандарт SGML ISO-8879. Он был основан на языке GML, позволил отказаться от конкретных способов представления информации и сосредоточить усилия на продумывании структуры документов — с помощью правил определения собственных тегов форматирования, их атрибутов и синтаксиса использования. Для создания конкретных прикладных наборов тегов было введено понятие «приложение SGML». Так, популярный сегодня язык разметки гипертекста HTML формально является приложением SGML.
Язык SGML оказался очень мощным и универсальным. В рамках SGML была изобретена концепция определения типа документа DTD (Document Туре Definition). Она связала конкретные синтаксические правила разбора с заданными способами организации структуры документов.
Язык HTML и развитие браузеров
1989 год известен появлением идеи Всемирной Сети1. Ее предложил (реализована она была спустя два года) Тим Бернерс-Ли (Tim Berners-Lee), программист Европейского центра ядерных исследований CERN, расположенного в Женеве. Тогда же он создал новое приложение SGML, известное сегодня как язык разметки HTML. Сам язык SGML расширился стандартизованной ISO таблицей стилей DSSSL (ISO 10179), позволившей дополнять описания структуры документа SGML способами его представления на экране, независимыми от платформы.
В декабре 1990 года в СССР при Министерстве связи был открыт Научно-технический центр гиперинформационных технологий, известный как ГНТЦ «Гинтех».
Через год Бернерс-Ли написал на языке Objective-C первый браузер World Wide Web для компьютера NeXT. Браузер также включал в себя первый редактор документов HTML. Соответственно, для работы браузера требовался сервер, поставляющий документы HTML. Бернерс-Ли запрограммировал и его, создав, фактически, первый веб-сервер.
В феврале 1993 года в Национальном центре суперкомпьютерных приложений США под руководством Марка Андриссена (Marc Andreessen) был создан браузер Mosaic для Unix, первый браузер с графическим интерфейсом. Летом Марк подготовил версии для платформ Macintosh и Windows. На следующий год группа разработчиков Mosaic, возглавляемая Джеймсом Кларком (James Clark), создала собственную корпорацию Netscape Communications, которая вскоре потрясла Уолл-стрит самыми быстрыми в мире темпами роста. Современное название Netscape Navigator 2.0 этот браузер получил со второй версии, когда в действовавший стандарт HTML корпорация Netscape добавила множество новых тегов.
Ведущие информационные корпорации, недовольные неразберихой с тегами HTML, сформировали в декабре 1994 года консорциум WIUW( W3C), быстро взявший под свой контроль работу над практически всеми стандартами важнейших технологий Сети. Надо отметить, что формально консорциум W3C выпускает только рекомендации и некоторые компании их игнорируют, но в целом эти рекомендации признаются всем рынком в качестве стандартов.
Роберт МакКул (Robert McCool), сотрудник Национального центра суперкомпьютерных приложений США, создал в 1994 году веб-сервер NCSA HTTPd. Вскоре он уволился из Центра, а его детище продолжали дорабатывать другие программисты, расширяя его множеством исправлений и «заплат». Со временем небольшая группа энтузиастов выделила его в отдельный проект под названием Apache (от английского «А patchy server», постоянно исправляемый сервер). В 1995 году появилась версия NCSA HTTPd 1.3, которая была официально переименована в сервер Apache, а причиной выбора такого названия впоследствии была признана идея благодарности индейцам племени апачей. Этот веб-сервер сегодня используется в 70% Интернет-проектов и распространяется свободно, бесплатно и в исходных текстах.
В 1995 году была утверждена версия языка HTML 2.0. Такую ответственность взяла на себя Международная комиссия по стандартам в Интернете Internet Engineering Task Force, разрабатывавшая сетевые протоколы. Но язык HTML 2.0 по-прежнему оставался приложением SGML, ориентированным на построение структуры документа, а не на задачи форматирования текста, более востребованные в практических веб-проектах.
В феврале 1996 года Билл Гейтс (Bill Gates), президент корпорации Microsoft, провозгласил: «HTML стал нашим типом данных». В этом году Microsoft резко активизировала разработку решений для Интернета. Джеймс Кларк не догадался выкупить исключительные права на браузер Mosaic, и его исходные тексты через фирму Spyglass приобрела Microsoft, выпустив на его основе браузер Internet Explorer. В результате появившиеся летом 1996 года обновленные браузе-
ры Netscape Navigator 3.0 и Internet Explorer 3.0 оказались основанными на одном ядре, причем в последнем пакете была реализована поддержка всех тегов конкурента. Вдобавок Microsoft решила распространять Internet Explorer бесплатно, и рыночная доля Netscape Navigator стремительно пошла вниз. Интерес к решениям Microsoft увеличился после выхода ее нового веб-сервера Internet Information Services (IIS) 3.0. Он включил первую версию одной из самых популярных на сегодня технологий построения веб-приложений Active Server Pages (ASP, активные серверные страницы). Она представляет собой систему разработки и поддержки серверного выполнения сценариев на простых языках программирования.
В те годы над версией HTML 3.0 (известной также как HTML+) интенсивно трудился Дэйв Рэггетт (Dave Raggett), специалист консорциума W3C. Эта спецификация из-за конкуренции между производителями веб-приложений, оказывавших давление на W3C, так и не была закончена. Консорциум сосредоточил усилия не на технологической, а на политической стороне вопроса, стараясь примирить враждовавших разработчиков браузеров. В результате была достигнута договоренность о выпуске в январе 1997 года стандарта HTML 3.2, объединившего наборы тегов, поддерживаемые браузерами Netscape Navigator и Internet Explorer3.
На скорейшем выходе HTML 3.2 настаивали также менеджеры компаний IBM, Novell и Sun.
Корпорация Netscape, все еще веря в собственное влияние на рынок, не отнеслась с должным вниманием к новой технологии таблиц каскадных стилей Cascading Style Sheets (CSS, аналог таблиц стилей DSSSL, только для языка HTML). Таблицы CSS содержат наборы стилей визуализации элементов текста на экране и позволяют красиво оформлять содержимое документов HTML. Вместе с тем файлы таблиц CSS отделяются от файлов страниц HTML, что делает эту технологию независимой. Специалисты Netscape реализовали поддержку CSS в виде не очень удобной технологии JASS (JavaScript Accessible Style Sheets), требовавшей знания программирования. Microsoft же, наоборот, специально выделила возможность создания сценариев в документах HTML, предложив пользователям сразу два языка сценариев: JScript и VBScript. Эта корпорация также начала финансирование подразделения W3C, ответственного за разработку новых рекомендаций HTML, фактически взяв под свой контроль процесс стандартизации браузеров.
Технология CSS мгновенно завоевала популярность. Она позволила отделить логическую структуру документов HTML от способов их форматирования и отображения на экране. Технология CSS оказалась весьма гибкой и дала возможность создавать вложенные (каскадные) стили с иерархической системой подчинения и наследования возможностей. Симпатии пользователей окончательно переместились к браузеру Internet Explorer. А компания Netscape была куплена сетевым гигантом America Online за 8,98 миллиардов долларов.
Продолжалась подготовка нового стандарта HTML 4.0. Его рабочая версия стала доступной 8 июня 1997 года, а в декабре появилась и официальная рекоменда-
В эти же годы была разработана первая версия языка разметки XML, о котором рассказывается в главе 45.
ция W3C. Война браузеров и наборов тегов закончилась, поэтому язык HTML 4.0 оказался па удивление стабильным. Он действует до сего дня, только в 1999 году вышла небольшая промежуточная версия 4.01 с исправленными ошибками спецификации. Интересно, что язык HTML 4 был снова признан W3C языком логической разметки, хотя всего несколько лет назад казалось, что главное назначение этого языка — форматирование документов. Но теперь для этих целей существует технология CSS, по поводу которой эксперты W3C высказались однозначно: все, что связано с визуальными аспектами отображения документа HTML, разработчики должны выносить в таблицы CSS.
26 января 2000 года вышла спецификация нового языка разметки XHTML 1.0, в которую консорциум W3C предложил переносить существующие документы HTML. Она названа «переформулировка HTML 4.0 в виде приложения XML 1.0» (хотя ранее язык HTML считался приложением SGML). Этот шаг означал окончательный отказ от наследия SGML и переход к технологии XML. Разработчики официально получили в распоряжение новый класс документов DTD, предназначенный для описания типов, определяемых требованиями HTML 4.0. Теперь можно обрабатывать файлы XHTML с помощью практически любых анализаторов документов XML. Важной оказалась возможность создания в приложении XHTML собственных тегов (ведь язык XHTML основывается на языке XML, где такая возможность ключевая). Консорциум W3C назвал XHTML технологией, предназначенной для мягкого перехода с технологии HTML на технологию XML.
ГЛАВА 43
Современные веб-технологии
1995 год стал одним из наиболее памятных в истории Всемирной Сети. Появился десятимиллионный сайт и десятитысячная новостная группа, а ежедневное число сообщений в этих группах достигло 250 тысяч. Заработали поисковые механизмы Yahoo и Lycos, к декабрю поисковая машина Altavista компании DEC проиндексировала 15 миллионов страниц Интернета. Компании Sun удалось отладить первую программу Java, работавшую без переделки и перекомпиляции на разных компьютерных платформах. Стремительный взлет акций Netscape, обусловленный популярностью одноименного браузера, поразил дельцов Уолл-стрит. Начинался звездный час так называемых «доткомов» (от английского dot com, обозначающего расширение сайтов .сот).
На этом фоне стали заметны недостатки существующих сетевых технологий, ограничивавшие текущие и потенциальные возможности разработчиков. Немало нареканий, в частности, вызывал набиравший популярность язык HTML, хотя в том же знаменательном 1995 году вышла вторая версия спецификации HTML 2.0. В ней появились новые теги, позволившие, к примеру, центрировать текст в окне и задавать размеры используемых шрифтов. Были улучшены и старые теги, предназначенные для создания списков, разделительных полос и отображения графических элементов.
Однако язык HTML вполне устраивал зарождавшийся электронный бизнес, перспективы которого выглядели крайне заманчиво. Ведь этот язык предназначался, прежде всего, для презентационных целей и позволял более-менее красиво форматировать веб-страницы сайтов с помощью фиксированного набора тегов, но не более того. Разработчик мог перемешивать теги в документе в произвольном порядке — средств проверки корректности записи и соответствия стандарту в языке HTML не существовало.
Между тем разработчики сетевых приложений нуждались не только в средствах описания внешнего вида данных, но и в механизмах определения смысла частей сетевых документов, организованных как линейно, так и иерархически. На сайтах хранились статические тексты HTML, процесс их интерпретации проходил целиком на компьютерах пользователей — в браузерах. Заказчики корпоративных Интернет-систем в то же время желали создавать содержимое веб-страниц динамически, например, путем вставки в них информации из баз данных, расположенных на сервере. Технология Java получила к тому времени определенное развитие, но фиксированный набор тегов HTML ограничивал и ее возможности, не позволяя гибко генерировать содержимое веб-страницы.
Актуальной выглядела задача стыковки и автоматического обмена данными, хранимыми в самых разных форматах. Требовался универсальный и гибкий формат представления структур данных. Для этого в рамках проекта SGML, как уже говорилось, была придумана технология DTD для описания пользовательских тегов и их предназначения в документах.
Технология XML
Консорциум W3C одно время настоятельно рекомендовал создателям браузеров реализовывать поддержку языка SGML. Но на его членов оказывалось все возрастающее давление со стороны как ведущих компьютерных корпораций, так и независимых сообществ разработчиков, не желавших прикладывать усилия к изучению и сопровождению этого языка и утверждавших, что SGML слишком сложен.
По этой и другим причинам консорциум в конце концов признал необходимость выработки ограниченного варианта SGML и выделил средства на деятельность рабочей группы, в которую вошло несколько экспертов по SGML. Они собрались вместе в августе 1996 года в Сиэтле, куда их пригласила Ассоциация графических коммуникаций GCA. Эксперты, во-первых, выявили классы программных задач, плохо решаемых с помощью языка HTML, а во-вторых, определили направления модификации технологии SGML с целью эффективного представления структурированной информации в Сети.
Эта группа получила название SGML Editorial Review Board. Ее возглавил Джон Боусек (Jon Bosak), ранее работавший ведущим инженером в корпорации Sun, а также управлявший в рамках W3C небольшой исследовательской командой специалистов по SGML. До этого Боусек перенес в Сеть документацию по системе NetWare компании Novell в виде данных SGML. Проект продолжался с 1990 по 1994 годы и охватил 150 тысяч страниц технических сведений.
Группа приступила к интенсивной работе и определила важнейшие критерии, которым должно отвечать их детище, получившее предварительное название Extensible Markup Language. От нового языка, известного сегодня как XML, требовалась пригодность к решению прикладных Интернет-задач, поддержка самых разных типов приложений, совместимость с SGML, удобство программной обработки, отсутствие неоднозначных особенностей эксплуатации (чтобы не возникали диалекты языка), понятность и простота для пользователя, легкость проектирования и подготовки данных, возможность краткого и формального представления грамматики языка и максимальная компактность.
К середине ноября на бостонской конференции SGML-96 группа представила предварительный вариант нового языка расширяемой разметки на тридцати двух страницах (спецификация SGML занимает 500 страниц). Рабочий вариант, в котором было учтено множество деталей, пожеланий и нюансов, авторы закончили в августе 1997 года. В частности, на конференции WWW, которая прошла в апреле 1997 года в Санта-Кларе, был определен механизм организации гиперссылок в новом языке, для чего группа придумала еще один специальный язык XLL (Extensible Link Language). На этой же конференции компания Microsoft совместно с фирмой Marimba внесла на рассмотрение консорциума W3C первое приложение XML — технологию OSD (Open Software Description), позволяющую опи-
сывать программные компоненты (версии, поддерживаемые платформы, способы связи с другими компонентами).
8 декабря на вашингтонской конференции SGML-97 была анонсирована окончательная версия языка разметки XML 1.0. Она вышла 10 февраля 1998 года в виде рекомендации W3C. Интересно, что по поводу названия нового языка мнения членов группы разделились, а окончательное решение было принято только перед выходом самой рекомендации. Название «Extensible Markup Language» предложил Джеймс Кларк, известный специалист по языкам SGML и DSSSL. Однако в результате опечатки заглавной оказалась вторая буква, и язык был записан в документах как extensible Markup Language. Такой вариант Кларку очень понравился, и поэтому вместо аббревиатуры EML мы сегодня пользуемся аббревиатурой XML.
В версии XML 1.0 были учтены требования стандартов Unicode (UTF-8 и UTF-16) и ISO/IEC 10646 к формату и кодировке символов; Internet RFC 1766 к тегам идентификации национальных языков; ISO 639 к кодам названий национальных языков; ISO 3166 к кодировке названий стран. Тогда же официально заработала группа W3C XML Working Group по сопровождению XML. Был выпущен свободно распространяемый разборщик документов XML — важнейший элемент приложений, обрабатывающих данные в формате XML. С помощью подобных разборщиков программисты обрабатывают данные XML как иерархически организованные документы. При этом не тратятся усилия на выделение и анализ отдельных тегов — программа сразу получает доступ к структуре документа, представленной в виде дерева (узлы-родители и узлы-потомки). По официальной версии консорциума W3C, первым браузером, поддерживающим XML 1.0, стал Microsoft Internet Explorer 5.0.
В 2000 году появилась первая версия технологии Microsoft ASP для платформы .NET (ASP.NET). Вторая версия ASP.NET 2.0 стала доступна разработчикам в ноябре 2005 года.
В 2001 году усилиями консорциума WWW увидела свет концепция схем XML (XML Schema), которую в свое время предложила Microsoft, чтобы, видимо, полностью избавиться от наследия SGML. Концепция предназначалась для борьбы с главным недостатком языка XML — отсутствием в документе XML жесткой привязки к определению типа документа (средство разметки DTD, ключевая концепция SGML).
Язык XML позволял задавать документ DTD, однако такая возможность была необязательной. Однако такие документы нельзя было корректно интерпретировать без предварительного определения значений тегов и их взаимосвязи. Поэтому на смену определениям типов документов DTD была призвана идея схем XML. Они описывают структуру документа XML и дополняют технологию DTD средствами задания типов данных для разных тегов, что решает проблему использования одинаковых тегов в разных целях.
Технологии веб-служб
К концу 1990-х годов Всемирная сеть представляла собой множество сайтов, работавших на базе разных, подчас экзотических, технологий. Разнообразие техноло-
гических подходов приводило к несовместимостям при визуализации страниц HTML в разных браузерах. При этом все большее число разработчиков крупных корпоративных и государственных систем активно использовали Интернет как среду взаимодействия. Их деятельность сильно затруднялась отсутствием стандартных подходов к организации взаимодействия программ.
Одним из наиболее популярных на сегодня решений проблемы несовместимости стала технология веб-служб (или веб-сервисов). Этот термин был официально использован рабочими группами консорциума WWW в 1999 году.
Согласно современной спецификации консорциума WWW, веб-служба — это система, поддерживающая взаимодействие компьютеров в сети.
Программный интерфейс веб-службы описывается с помощью языка определения интерфейсов WSDL (Web Services Description Language). Процесс общения компьютеров происходит по высокоуровневому протоколу SOAP (Simple Object Access Protocol), основанному на языке XML.
Веб-службы обычно организовываются в каталоги UDDI (Universal Description Discovery Interface), которые свободно доступны в Интернете.
С точки зрения прикладного разработчика веб-служба — это программный модуль, выполняемый на сервере Сети с известным IP-адресом. Клиентская программа обращается к веб-службе и получает от нее описание предоставляемых ею функций (программный интерфейс) в формате WSDL. Далее нужная функция вызывается в известном формате. Она выполняется на сервере и возвращает в клиентскую программу результат работы по протоколу SOAP.
Сегодня инструменты для создания веб-служб предлагает множество компаний. Корпорация Microsoft выпускает набор разработчика, позволяющий создавать веб-службы на базе технологий DCOM и .NET. Компания Sun, автор плат- формно-независимой технологии Java, производит сервер Sun Java System Web Server, который предлагает, в частности, технологию JSP (Java Server Pages) и собственную реализацию технологии ASP под названием Java System Active Server Pages. Сильная сторона решений Sun — независимость от операционной системы и возможность работы на платформах Windows, Linux, Unix и других. Собственные инструменты создания веб-служб выпускает и корпорация IBM.
Семантическая сеть
На международной конференции XML-2000, прошедшей в Вашингтоне, Тим Бернерс-Ли, автор идеи Интернета и председатель консорциума WWW, выдвинул новую концепцию Семантической сети. Сеть будущего не предназначена для замены нынешнего Интернета. Она представляет собой набор развивающих его новых технологий, структурирующих и классифицирующих хранимые в Сети данные на основе их смысла. Все элементы Семантической сети дополнены формальным описанием своего содержания и могут иметь произвольное число осмысленных связей с другими элементами. Это позволит быстро и точно находить нужные сведения, причем не обязательно по ключевым словам — достаточно описать нужный результат на естественном языке. Программы научатся самостоятельно понимать, к какой теме или к какому классу знаний в универсальном иерархическом классификаторе Сети относится тот или иной материал или данные на сайте. В результате будет развернута глобальная система автономных агентов, выполняющих множество рутинных, но требующих определенного интеллекта задач по обработке информации без участия человека. Компьютеры «оживут» и станут в ходе решения логических задач общаться друг с другом на высоком семантическом уровне. Пользователь же сможет быстро получать точные и недвусмысленные результаты запросов к Сети на естественном языке.
Веб 2.0
В 2004 году родилась новая концепция: Веб 2.0. Для нее пока не существует законченного формального определения, хотя сетевая энциклопедия Wiki (wikipedia.org/ wiki/Web_2) относит к этому понятию второе поколение Интернет-сервисов.
Понятие Веб 2.0 охватывает технологии, упрощающие построение так называемых социальных компьютерных сетей — средств онлайнового взаимодействия, ориентированных на максимально удобный процесс общения и коллективного труда с помощью передовых ИТ. Такие сети, например, могут предоставлять интегрированные наборы услуг (как порталы Google и Yahoo), обеспечивать новые способы взаимодействия (как служба VoIP-телефонии Skype), упрощать процесс обсуждения проблем и представлять результаты в наиболее наглядном виде (как wiki-энциклопедпи и блоги). Особенностью Веб 2.0 считают фолксономию (механизм коллективной классификации понятий), которая задействуется для описания смысла сетевого контента и в перспективе существенно облегчит задачу автоматического поиска нужных сведений в наборах ранжированной информации.
Несмотря на нечеткость и относительность, концепцию Веб 2.0 мгновенно подхватили практически все ведущие участники ИТ-рынка, связанные с Интернет- разработками. Она появилась в подходящее время, когда рынок истосковался по модным стратегическим находкам, способным привлечь инвесторов, и в результате нынешний ажиотаж вокруг Веб 2.0 сильно напоминает страсти по дот-комам.
Термин «Web 2.0» был придуман коллективными усилиями специалистов компании O’Reilly Media (www.oreilly.com). Она специализируется на издательском биз-
иесе (в 1992 г. выпустила первую в мире книгу про зарождающемуся Интернету), онлайновых проектах (в ее активе, например, сеть сайтов WindowsDevCenter.com, LinuxDevCenter.com, SysadminDevCenter.com), а также на организации ИТ-конференций, в числе которых в последние годы появилось мероприятие, целиком посвященное Web 2.0 (www.web2con.com). Глава издательства Том О’Релли (Tom O’Reilly) известен в мире ИТ как активный пропагандист открытого кода и свободно распространяемого ПО.
Хотя участники проектов нового поколения не сошлись во мнениях по поводу определения Веб 2.0, они единодушны в отношении технологий, на которых он основан. Это толстые клиенты, веб-сервисы и повсеместное использование XML. Для Веб 2.0 характерен уход от «тяжелых» серверных технологий к мощным и комфортным клиентским местам и предоставление пользователям большой свободы в подготовке и обработке контента. Отметим следующие особенности такой архитектуры.
Взаимодействие сервера с клиентскими приложениями осуществляется по различным протоколам (прежде всего SOAP) на базе XML и с помощью языка описания веб-сервисов WSDL. В частности, востребована спецификация WS- Routing, разработанная несколько лет назад корпорациями IBM и Microsoft. Она позволяет маршрутизировать сообщения SOAP по разным транспортным средам, а также упрощает процесс шифрования передаваемых данных. Становится популярной и новая технология WS-Addressing, обеспечивающая защищенное соединение для веб-служб.
Разбор файла XML (так называемая адресация частей документа) выполняется с помощью языка XPath, который призван заменить язык запросов SQL в сетевых приложениях. Его сильная сторона — способность работать с иерархически организованными данными. Для распространения новостной информацией применяют технологию RSS или ее аналоги. RSS представляет собой приложение XML, ориентированное на оперативную поставку «горячих» данных (новостей, анонсов, заголовков) и прозрачно поддерживается современными браузерами. К технологиям, схожим с RSS, относят Atom (популярное средство агрегирования XML-данных), OPML (универсальный язык, уступающий RSS по массовости), GData (версия RSS, предложенная Google), RDF (формат описания метаданных для Семантической сети — надстройки над Интернетом, дополненной средствами описания смысла данных) и другие.
Для создания толстых клиентов в Вебе 2.0 активнее всего применяется новая технология AJAX (асинхронный язык сценариев JavaScript + XML), появившаяся в начале 2005 года. Она позволяет организовывать динамическое взаимодействие между браузером и сервером без перезагрузки текущей веб-страницы. Таким образом создается удобный пользовательский интерфейс и организуется оперативная обработка контента на клиентской машине.
В рамках Веб 2.0 намечено возвращение к технологиям наподобие ActiveX, от которых вследствие их низкой защищенности одно время отказались многие производители браузеров. Но сегодня на первый план вышли решения, обеспечивающий удобный графический интерфейс и развитые мультимедийные возможности, и недаром программы, написанные на языках Flash и Java, не требующие установки и запускаемые из браузера, завоевали у пользователей очень большую популяр-
ность. В результате разработчикам пришлось смириться с высокой уязвимостью этих технологий и потенциальной способности сетевых приложений к любым разрушительным действиям в клиентской системе.
Онлайновые программы нового поколения объединяют термином «Насыщенные приложения для Интернета» (Rich Internet Applications, RIA). В дальнейшем тенденция к созданию клиентов RIA сохранится, и в системе пользователя останется все меньше и меньше различий между онлайновыми и оффлайновыми приложениями. Интересной тенденцией в этом русле являются гаджеты (или виджеты) для рабочего стола, поддержка которых, в частности, организована в Windows Vista на системном уровне. Гаджеты представляют собой компоненты, которые размещаются в небольших окнах непосредственно на рабочем столе, например, в виде часов, календарей, справочников погоды и валют, датчиков состояния системных и прикладных ресурсов, трансляторов радио и телепрограмм, работающих в реальном времени. Чаще всего они получают информацию по Сети от веб-служб в обход браузера. Появляются версии таких гаджетов и для мобильных устройств, однако пока Веб 2.0 ограничивается ПК и ноутбуками.
В любом случае время браузеров постепенно уходит в прошлое. На их долю остается поддержка обратной совместимости с сайтами первого поколения и посредничество в запуске толстых RIA-клиентов. В выигрыше оказываются конечные пользователи, которые получают удобные механизмы сетевой работы. И хотя новые технологии несут новые риски, вместе с ними открываются и принципиально новые возможности.
ГЛАВА 44 Технологии INDY
Система C++Builder включает множество технологий работы с Интернетом. На базе готовых компонентов для IP-сетей можно также создавать и развертывать внутрикорпоративные (интранет-) приложения, базирующиеся на проверенных многофункциональных сетевых технологиях. Крупные системы, работающие с базами данных в Сети, создают с помощью специализированных наборов компонентов и дополняющих их Мастеров. Разработчики прикладных приложений, системных утилит и модулей расширения существующих приложений могут использовать наборы компонентов, поддерживающие разные сетевые стандарты.
Компоненты Indy 10
Широкий диапазон возможностей по созданию сетевых приложений предлагает набор компонентов Indy1.
Он объединяет несколько категорий палитры инструментов: Indy Clients (для создания клиентских приложений), Indy Servers (для создания серверных приложений), Indy I/O Handlers (прослойка для повышения эффективности работы компонентов Indy с помощью буферизации операций ввода-вывода), Indy Intercepts (для протоколирования работы приложений Indy) и Indy Mise (для поддержки дополнительных сетевых технологий, не представленных в других категориях).
Принципы создания серверных приложений Indy
Рассмотрим, как с помощью компонентов Indy создать серверную программу, взаимодействующую с клиентскими приложениями по протоколу TCP. Этот протокол низкоуровневый, поэтому программист должен запрограммировать не только процесс согласованного обмена данными, но и всю прикладную логику подобного взаимодействия (например, способ и формат обмена данными). Однако доступ к низкоуровневой технологии обмена потенциально позволяет создавать высокопроизводительные сетевые системы.
В данной главе рассматривается версия библиотеки Indy 10, существенно отличающаяся от предыдущих версий.
ПРИМЕР 44.1
Создание сервера Indy
Серверное приложение ожидает поступления запросов от клиентских программ. Сетевому серверу должен быть назначен номер порта, который этот сервер «прослушивает». Как только появится запрос TCP от клиентского приложения, сервер сформирует ему ответ.
Создание заготовки приложения.
Создадим пустой проект командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Подготовка сервера TCP.
Сформируем сервер TCP на основе стандартных компонентов Indy.
Добавим на форму невизуальный серверный компонент TldTCPServer из категории палитры инструментов Indy Servers (Серверы Indy). Он обеспечивает практически всю функциональность сервера TCP.
Прослушиваемый сервером порт задается в свойстве DefaultPort. Введем для примера значение 12345.
Когда в порт поступает запрос TCP от клиентской программы, компонент TldTCPServer генерирует событие OnExecute. Создадим обработчик этого события, формирующий осмысленный отклик на клиентское обращение. У такого обработчика всего один параметр AContext (тип TldContext).
void fastcall TForml::IdTCPServerlExecute (TldContext *AContext);
Взаимодействие с клиентской программой.
Параметр обработчика AContext задает текущий контекст вызова. Его свойство Connection обеспечивает доступа к подробной информации о клиентском соединении.
Тип свойства Connection — класс TldTCPConnection. Он содержит свойства и методы, позволяющие, в частности, принимать данные по установленному соединению и отсылать их клиентскому приложению. Обращение к этим свойствам и методам происходит через вложенное свойство lOHandler (класс TldServerlOHandier). Оно задает доступ к соответствующему программному потоку на сервере, прослушивающем данный порт. В простейшем случае достаточно обратиться к методу ReadLn() класса TldServerlOHandier, чтобы считать из порта строку байтов, переданную клиентским соединением.
Добавим на форму компонент ТМето.
В обработчике события OnExecute в этот компонент будем выводить все поступающие на сервер запросы:
void fastcall TForml::IdTCPServerlExecute (TldContext *AContext)
{
AnsiString s = AContext-->Connection->IOHandler->ReadLn();
Memol->Lines-->Add(s) }
Активизация и отключение компонента связи может происходить, например, при нажатии кнопки. При этом мы модифицируем значение свойства Active: void fastcall TForml::ButtonlClick(TObject ‘Sender) {
IdTCPServerl-vActive = ! IdTCPServer1->Act1ve;
Скомпилируем сервер и запустим его. Запуск следует выполнить не из оболочки C++Builder, а стандартными средствами Windows, например, из Проводника. Среда C++Builder будет далее задействована для разработки клиентского приложения.
ПРИМЕР 44.2
■Создание клиентского приложения Indy
Клиентское приложение взаимодействует с сервером Indy с помощью компонента TldTCPCIient, который обменивается данными через заданный порт.
Создание заготовки приложения.
Сформируем пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Настройка серверной связи.
Клиентский компонент должен содержать информацию об адресе сервера, с которым он взаимодействует.
Разместим на форме компонент TldTCPCIient из категории палитры инструментов Indy Clients (Клиентские приложения Indy).
В свойство Host (адрес компьютера с запущенным сервером) введем IP-адрес сервера.
В свойство Port введем номер рабочего порта.
Взаимодействие с сервером.
Пусть при нажатии кнопки надо отправить на сервер строку, введенную в текстовое поле Editl, имеющееся на форме.
Добавим на форму текстовое поле TEdit и кнопку TButton.
Сформируем обработчик нажатия на кнопку.
Запишем в обработчике следующий код:
void fastcall TForm2::ButtonlClick(TObject *Sender)
IdTCPClientl->Connect();
IdTCPClientl->IOHandler->WriteLn(Editl->Text); IdTCPClientl->Disconnect();
Объект IndyClient — это экземпляр компонента TldTCPCIient. Его метод Connect() организует линию связи с сервером — он посылает запрос на указанный порт и проверяет, имеется ли на другом конце линии связи «слушающий» сервер. Далее метод WriteLn() записывает в порт строку, то есть отсылает серверу содержимое поля Editl. В заключение выполняется отсоединение от сервера (метод Disconnect), чтобы не перегружать последний «висячими» сетевыми соединениями. Теперь можно запустить несколько копий клиентской программы и отсылать серверу текстовые сообщения. Он будет выводить их в поле ТМето своего окна в порядке поступления.
Технология создания сервера HTTP
В данном разделе рассматривается технология создания собственного сервера HTTP, работающего как обычный веб-сервер (IIS, Apache и так далее).
Сервер HTTP — это сервер, который принимает стандартные запросы браузеров по протоколу HTTP, самостоятельно их обрабатывает и возвращает содержимое страницы HTML для интерпретации в клиентской программе.
Поддержка серверной технологии HTTP в системе C++Builder позволяет развертывать в рамках одной программы сложную логику сетевого взаимодействия, в которой выполняется, например, активное обращение к базам данных. При этом почти готовая функциональность веб-сервера реализуется на базе компонента TldHTTPServer. С его помощью удается быстро формировать многофункциональные серверные приложения для Интернета по схеме, типичной для создания однопользовательских, локальных приложений, что существенно упрощает процесс разработки.
Сформируем пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Разместим на форме компонент TldHTTPServer из категории палитры инструментов Indy Servers (Серверы Indy).
Основная настройка сервера Indy заключается в добавлении в свойство (коллекцию) Bindings нового объекта — сокета.
Сокет (сетевое гнездо) — это виртуальное -начало или окончание сетевого соединения, с другим компьютером. С точки зрения прикладного разработчика, сокет — это аналог виртуального файла, позволяющий записывать данные, отправляемые другому компьютеру и считывать данные, принимаемые от другого компьютера.
Сокет отвечает за соединение с клиентскими программами (браузерами). Добавление нового объекта выполняется в стандартном редакторе коллекций C++Builder, который вызывается обращением к свойству Bindings.
Каждый элемент коллекции сокетов (экземпляр класса TldSocketHandle) настраивается через свойства IP (адрес текущего сервера) и Port (порт).
В свойстве сокета IPVersion при необходимости задается версия протокола JP. Сегодня используется массово распространенный протокол IPv4 (значение ld_IPv4). В будущем предполагается использование протокола IPv6 (значение ld_IPv6), охватывающего 3,4х1038 IP-адресов.
Значение свойства активности сервера Active можно сразу перевести в значение True.
Свойство SessionTimeOut задает время ожидания сервером отклика клиентской программы в секундах. Не всегда следует долго поддерживать соединение: возможен обрыв связи или завершение работы пользователя с браузером.
В свойстве ServerSoftware при желании задается произвольное имя сервера. Оно отображается в ответ на стандартный запрос версии сервера.
.3.1. Обработка базового события OnCommandGet
Функциональность создаваемого веб-сервера можно уместить фактически в одной строке. Она размещается в обработчике события OnCommandGet. Это событие возникает, когда к серверу поступает запрос от клиентского приложения (например, обычного браузера).
void fastcall TForml::IdHTTPServerlCommandGet
(TIdContext *AContext,
TIdHTTPRequestInfо *ARequestInfо,.
TldHTTPResponselnfo *AResponseInfо);
В число параметров обработчика входит объект AResponselnfo. Он хранит ссылку на экземпляр класса TldHTTPResponselnfo, описывающего структуру ответа сервера HTTP. Среди его методов имеется, в частности, метод ServeFile. В нем задается локальный файл, содержимое которого отправляется в качестве ответа клиентскому приложению, сформировавшему данный запрос. Файл должен содержать информацию в формате HTML или XML. Она автоматически интерпретируется клиентом HTTP (браузером).
Например, мы хотим в ответ на обращения к нашему серверу (виртуальному сайту) выдавать одну и ту же начальную страницу, код которой хранится на диске С: в каталоге \MyWeb в файле index.html. Обработчик события OnCommandGet может быть следующим:
void fastcall TForml::IdHTTPServerlCommandGet
(TIdContext ‘AContext,
TIdHTTPRequestInfo *ARequestInfо,
TldHTTPResponselnfo ‘AResponselnfo)
AResponselnfo->ServeFile
(AContext, "C:\\MyWeb\\index.html");
}
Файл index.html может иметь следующее содержание:
<html>
<head>
<title>
Мой любимый сервер
</1i tle>
</head>
cbody bgColor=white>
<hlxcenter>
Приветствую на моем сервере!
< /centerx /Ы>
</body>
</html>
При запуске сервера2 и обращении к нему из браузера окно браузера будет выглядеть следующим образом (рис. 44.1).
Перед запуском серверной программы на компьютере надо отключить ранее запущенные веб-серверы (например, стандартную службу IIS или общедоступный сервер Apache). В противном случае они будут конфликтовать при доступе к сетевым ресурсам с оригинальным сервером HTTP, созданным с помощью C++Builder.
Рис. 44.1. Доступ к собственному веб-серверу из браузера
.3.2. Анализ пользовательского запроса
Выдавать на любой запрос один и тот же ответ неразумно. Желательно встроить в программу обработчик пользовательского запроса и анализировать, к какому файлу происходит обращение. При этом вовсе не обязательно запрашивать реально существующие файлы на серверном компьютере. В целях безопасности лучше, чтобы имена запрашиваемых файлов проходили обработку. Реальное имя должно вычисляться по «виртуальному», причем соответствие имен должно быть скрыто от удаленного пользователя.
Информация о запрошенном файле хранится в параметре ARequestlnfo обработчика события OnCommandGet. В частности, свойство Document содержит часть исходного запроса, в которую включены как имя файла, так и имена промежуточных каталогов, но не включены название протокола и цифровой или буквенный адрес сервера3.
Так, если в обращении указан адрес:
http://127.0.0.1/dat/show.html
то значением свойства ARequestlnfo. Document в обработчике будет строка “/dat/show. html”.
Если мы соблюдаем однозначное соответствие между запрошенным в браузере файлом и файлом на локальном диске нашего сервера, то достаточно записать обработчик следующим образом:
void fastcall TForml::IdHTTPServerlCommandGet (TIdContext ‘AContext, TIdHTTPRequestInfо *ARequestInfо, TIdHTTPResponseInfo *AResponseInfо)
(
AResponselnfo->ServeFile
(AContext, "C:\\MyWeb"+ARequestInfо->Document);
}
В поле Document параметра ARequestlnfo включается часть запроса, следующая сразу за адресом (URL) сайта, начиная с символа наклонной черты.
При желании в обработчике можно анализировать строку запроса дополнительно. В простейшем случае можно имитировать известный ответ сервера («ошибка 404»), означающий, что файл не найден. Для этого надо проверить, имеется ли на сервере запрашиваемый файл. Если он отсутствует, в качестве ответа достаточно передать стандартный файл HTML с текстом описания ошибки. Его мы должны подготовить сами. Именно таким способом формируются сообщения наподобие 404 File not found об отсутствии на сервере запрашиваемого файла.
Технология создания почтовых программ
Одной из наиболее активно используемых служб современного Интернета является электронная почта. Программу для приема и отправки сообщений электронной почты также можно создать на основе компонентов набора Indy.
Прием электронной почты
Компонент TldPOP3 из категории Indy Clients (Клиентские приложения Indy) используется для подключения к почтовому серверу по стандартному протоколу POP3 (прием почты). В свойстве Host задается имя почтового сервера, а в свойствах Username и Password — имя пользователя и пароль, соответственно. Для подключения компонента к серверу вызывают метод Connect(), для отключения — метод Disconnect!).
Электронные сообщения, поступившие адресату, физически хранятся на почтовом сервере. Узнать их количество позволяет метод CheckMessages. При приеме сообщения можно получить его целиком или принять только заголовок.
Для приема заголовка служит метод RetrieveHeader ( ):
bool fastcall RetrieveHeader
(const Int MsgNum, Idmessage::TIdMessage* AMsg);
Класс TIdMessage описывает анализируемое сообщение. В нем имеется свойство Headers, в котором хранится список строк с содержимым в виде «имя = значение». Так описываются стандартные характеристики заголовка письма.
Принять сообщение целиком позволяет метод Retrieve () с такими же параметрами. Принятые сообщения физически остаются на сервере. Чтобы удалить сообщение с сервера, его сначала необходимо пометить как подлежащее удалению. Это выполняет метод Delete ():
bool fastcall Delete(const Int MsgNum);
Однако физическое удаление помеченных сообщений выполняется только после отключения клиентского приложения от сервера с помощью метода Disconnect () без параметров.
Отправка почтового сообщения
Отправку сообщения по стандартному протоколу SMTP выполняет компонент TldSMTP. Имя сервера SMTP задается в свойстве Host, имя пользователя и пароль
для авторизации - в свойствах Username и Password. Соединение с сервером обеспечивает метод Connect (), после чего можно отправлять сообщения, используя метод Send ():
virtual void fastcall Send
(Idmessage::TIdMeseage* AMsg);
Отправляемое сообщение записывается в параметр AMsg. Текстовое содержимое послания заносится в свойство Body экземпляра класса TldMessage с помощью метода Acid (). Свойство Body представляет собой набор текстовых строк (наследник класса TStrings).
Технологии кодирования почтовых вложений
При передаче информации с использованием популярных сетевых протоколов часто возникает потребность в пересылке двоичных данных: мультимедийных файлов, присоединяемых к письмам документов, текста, написанного кириллицей, и других. Многие сетевые протоколы и по сей день поддерживают обработку только текстовой информации, причем используют лишь первые 128 символов кодировочных таблиц. Так происходит из-за того, что для хранения каждого символа протоколы отводят лишь 7 из 8 разрядов каждого байта, а один бит задействуется под служебные задачи.
Для обхода этой проблемы придумано немало технологий, которые кодируют двоичные данные с помощью обычных латинских символов. При этом каждый исходный знак считается двоичным, так что для его представления требуется более одного символа. Обычно величина результирующего блока данных больше исходного в 1,5—2 раза.
Кодирование двоичных данных часто выполняют почтовые программы. Они самостоятельно преобразуют исходные письма. Обычно необходимость перекодировки задается в настройках почтовых средств. Подобные механизмы кодировки двоичных данных широко востребованы в разнообразных системах сетевого обмена информацией.
Среда C ++Builder предлагает готовые компоненты кодирования из набора Indy. Они являются наследниками двух классов: TldEncoder (кодировщик) и TldDecoder (декодировщик). Чаще всего востребованы их наследники, реализующие популярные технологии кодирования: Base64 MIME и UUE.
Компоненты TldEncoderMIME и TldDecoderMIME реализуют алгоритм преобразования между двоичными кодами и латинскими символами, который называется Base64 MIME. В нем задействовано 64 текстовых символа и знак равенства =. Этот алгоритм считается неформальным стандартом кодирования в почтовых системах и новостных конференциях.
Компоненты TldEncoderUUE и TldDecoderUUE используют алгоритм UUE, реализующий требования приложений UUCP (Unix-to-Unix Copy Program). Данный формат разрабатывался для операционной системы Unix и применяется при кодировании присоединенных к электронным письмам файлов и передаче больших блоков данных, разбиваемых на множество частей.
ПРИМЕР 44.3
-- • Кодирование и декодирование двоичных данных
Рассмотрим пример использования компонентов кодирования.
Создание заготовки приложения.
Сформируем пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Настройка пользовательского интерфейса.
Интерфейс программы позволит нам контролировать результаты кодирования и декодирования.
Разместим на форме два многострочных поля для хранения исходного и перекодированного сообщений.
Добавим две кнопки, предназначенные для преобразования текста в кодированный вид и обратно.
Программирование процесса кодирования.
Организуем выполнение требуемых операций.
Процесс кодирования реализуется компонентами IdEncoderMIME и IdDecoderMIME. Добавим их на форму.
Создадим обработчик нажатия кнопки кодирования текста, введенного в поле Мето1, с выводом результата преобразования в поле Мето2. Кодирование выполняется обращением к функции Encode(), которая возвращает преобразованный текст.
void fastcall TForml::ButConlClick(TObject ‘Sender)
Memo2->Text = idEncoderMIMEl->Encode(Memol->Text) ; }
Обратное преобразование из кодированного вида в обычный выполняет функция DecodeString() компонента IdDecoderMIME:
void fastca.il TForml::Button2Click(TObject ‘Sender) {
Memol->Tezt = IdDecoderMIMEl->DecodeString(Memo2->Text); }
Использование протокола SSL
В главе 31 упоминалась технология SSL предназначенная для шифрования данных, передаваемых по протоколу HTTP. Она поддерживается в Indy несколькими компонентами, хотя реализация самой технологии SSL в приложениях требует платного сертификата.
Основной компонент, обеспечивающий взаимодействие с установленной в системе средой поддержки SSL, называется TIdSSLIOHandlerSocketBase. Если, например, используются компоненты TldHTTPServer (для серверной части) и TldHTTP (для клиентской части), то в их свойствах lOHandler (перехватчик передаваемого трафика) достаточно задать ссылку на экземпляры компонента TldSSLIOHandlerSocket
Base. Их настройка на конкретную реализацию SSL выполняется через свойство SSLOptions.
Вопросы
Как создается сетевое клиент-серверное приложение Indy?
Как с помощью компонентов Indy создать сервер HTTP?
Как организовать прием и отправку электронной почты с помощью компонентов Indy?
Как с помощью компонентов Indy организуется кодирование и декодирование двоичных данных?
ГЛАВА 45 Технологии XML
Язык XML предназначен для создания новых языков разметки типа HTML. Он используется в прикладных проектах для интеграции стандартов хранения и представления данных и упрощения процессов обмена информацией. Язык XML’, в отличие от своего предшественника HTML, ориентирован не на форматирование, а на описание данных. Он позволяет определять смысл используемых в документе тегов и порядок их применения, задавая тем самым структуру и значение конкретных данных.
Язык XML упрощает подходы к хранению данных произвольной организации и произвольного формата, причем независимо от платформы и используемых программ. Он также позволяет проверять на корректность как сами данные, так и способы их организации и взаимосвязи и, что важно, целостность всего документа. Эти три отличия: создание новых тегов, поддержка произвольных структур данных и возможность проверки корректности документа (описание грамматики) — сделали язык XML и все основанные на нем технологии очень популярными. Они задействованы в большинстве современных Интериет-приложений.
Многие сильные стороны языка XML можно имитировать путем встраивания исполняемых сценариев в документы HTML. Однако для этого на клиентской или серверной стороне должен работать интерпретатор сценарных языков. А язык XML не привязан к дополнительным системам программирования, что делает его независимым от конкретных фирм-разработчиков.
Язык XML помогает стыковать приложения на корпоративном уровне. На предприятиях нередко работает множество систем, созданных разными производителями в разные годы, и синхронизировать такую деятельность очень трудно. Для решения этой задачи задействуются средства автоматической интеграции на базе документов формата XML. Такие средства разворачиваются на промежуточных серверах и берут на себя всю нагрузку по преобразованию и обмену данными между программами.
В роли посредника технология XML хорошо подходит для любых задач, где востребована независимость от конкретных программных продуктов. Независимости от СУБД можно легко добиться представлением запросов к базе данных на языке XML Query (своеобразном аналоге языка реляционных запросов SQL). Это важно, в частности, для проектов, где клиентское приложение должно обращаться к нескольким СУБД разных производителей, которые могут быть и не реляционными.
Об истории создания языка XML рассказано в главе 43.
Язык XML не связан с типом хранимых данных, поэтому он получил широкое распространение в областях, где ранее не существовало универсального способа отображения и обмена специфической информацией. В научной сфере известно немало приложений XML — например, языки записи химических и математических формул Chemical Markup Language и MathML.
Незаменим язык XML и в веб-проектах. На его основе организуется обмен сервера с клиентскими программами целостными копиями веб-страниц сайта со всеми входящими в них элементами, точными настройками шрифтов, проверенными взаимосвязями и сохраненными гиперссылками. Язык XML позволяет по-разному представлять одни и те же данные в разных типах браузеров, настраивая формы представления информации.
Язык XML задумывался, в частности, как своеобразная замена HTML (в силу чего появился язык XHTML). По этой же причине в свое время был предложен язык стилей XSL — по аналогии с таблицами стилей CSS для языка HTML, только с гораздо большими возможностями. Язык XSL упрощает способы представления документов XML на экранах программ и устройств: от домашних компьютеров до интеллектуальных телефонов и диагностического оборудования.
Использование языка XML для представления наборов данных
Одно из важных применений XML — представление наборов данных при пересылке по сети (например, от сервера баз данных клиентскому приложению). Содержимое наборов представляется не в закрытом двоичном формате, а в простом текстовом виде. Язык XML хорош тем, что в силу своей семантики позволяет описывать не только реляционные, но и иерархические структуры данных. С его помощью можно, например, расширить существующее приложение механизмами работы в Интернете (документы XML понимаются всеми современными браузерами), или состыковать разнородные системы, организовав обмен данными между ними.
Штатное средство XML Mapper
Процесс преобразования наборов данных разных СУБД в формат XML и обратно реализован в среде C++Builder в виде утилиты XML Mapper (xmlmapper.exe). Опа вызывается командой Tools > XML Mapper (Сервис > XML Mapper). Эта утилита реализует функции преобразования наборов данных и документов XML в визуальном режиме, а также автоматически генерирует правила преобразования, которые можно сохранить в виде файла.
ПРИМЕР 45.1
Преобразование набора данных сервера СОМ в формат XML
Утилита XML Mapper работает следующим образом. Сначала в нее необходимо загрузить набор данных. Он может быть взят из текстового формата XML или получен по сети. В последнем случае надо организовать связь с сервером СОМ, зарегистрированным в операционной системе Windows. Затем для этого набора определяются правила преобразо-
ваний в результирующий вид. Эти правила сохраняются в файле для использования при последующих сходных преобразованиях.
Загрузка набора данных в утилиту.
В качестве источника набора данных, подлежащего преобразованию в формат XML, задействуем сервер CB2006Test, который был подготовлен как пример многоуровневого приложения (см. главу 39).
В окне утилиты XML Mapper щелкнем правой кнопкой мыши на панели Datapacket (Пакет данных) и выберем в контекстном меню пункт Connect То Remote Server (Подключение к удаленному серверу). Для загрузки набора данных из файла предназначена команда Open Datapacket (Открыть пакет данных).
В открывшемся диалоговом окне выберем нужный сервер СОМ из раскрывающегося списка. Если выбранный сервер не активен, он запустится автоматически. В этом же окне выбирается один из внутренних провайдеров (поставщиков данных) сервера.
Нажмем кнопку Get Datapacket (Получить пакет данных). Диалоговое окно закроется, и в окне XML Mapper отобразится иерархическая структура указанного набора. В ней представлены поля набора, их типы и другие параметры.
& XML Mapping Tool
Efe Create Help 5Й X
Document; <norDocument; <norTransformation; <noneiHecteid^^;Transformation; <noneiHecteid^^;Project!,CB2006Test:DataSetProvid er 1Project!,CB2006Test:DataSetProvid er 1PJ I" I J ' ■ Create Datapacket As: , <• talent Dataset ' Г ^jery Parameters jPJ I" I J ' ■ Create Datapacket As: , <• talent Dataset ' Г ^jery Parameters jГ - •Document ViewSchema ViewГ - •Document ViewSchema ViewTransform Direction- $Datapacket to XMl|Transform Direction- $Datapacket to XMl|Node Properties । 4Node Properties । 4IciientdatasetIciientdataset” 5pecies_No♦ fieldname="Species No"♦ DataT ype="Float"♦ InPrimaryKey-'True"” 5pecies_No♦ fieldname="Species No"♦ DataT ype="Float"♦ InPrimaryKey-'True"S Category♦ DataType="5tring"♦ Maxlength="15“-1 Common_Name♦ DataType="5tring"♦ MaxLength="30"-T ^ Species_Name♦ fieldname=‘'Species Name"♦ DataType="5tring"♦ MaxLength="40"Ct1 Ши Length_cm_it’ ® Length_InА Ш5 Notes<+ GraphicS Category♦ DataType="5tring"♦ Maxlength="15“-1 Common_Name♦ DataType="5tring"♦ MaxLength="30"-T ^ Species_Name♦ fieldname=‘'Species Name"♦ DataType="5tring"♦ MaxLength="40"Ct1 Ши Length_cm_it’ ® Length_InА Ш5 Notes<+ Graphic;i Datapacket: Projec tl.CB2006T ей: DataSet Provider 1;i Datapacket: Projec tl.CB2006T ей: DataSet Provider 1Field View [Datapacket View |Field View [Datapacket View |
Document; <nor
Document; <nor
Transformation; <nonei
Hecteid^^;
Transformation; <nonei
Hecteid^^;
Project!,CB2006Test:DataSetProvid er 1
Project!,CB2006Test:DataSetProvid er 1
PJ I" I J ' ■ Create Datapacket As: , <• talent Dataset ' Г ^jery Parameters j
PJ I" I J ' ■ Create Datapacket As: , <• talent Dataset ' Г ^jery Parameters j
Г - •
Document ViewSchema View
Г - •
Document ViewSchema View
Transform Direction- $
Datapacket to XMl|
Transform Direction- $
Datapacket to XMl|
Node Properties । 4
Node Properties । 4
Iciientdataset
Iciientdataset
” 5pecies_No
♦ fieldname="Species No"
♦ DataT ype="Float"
♦ InPrimaryKey-'True"
” 5pecies_No
♦ fieldname="Species No"
♦ DataT ype="Float"
♦ InPrimaryKey-'True"
S Category
♦ DataType="5tring"
♦ Maxlength="15“
-1 Common_Name
♦ DataType="5tring"
♦ MaxLength="30"
-T ^ Species_Name
♦ fieldname=‘'Species Name"
♦ DataType="5tring"
♦ MaxLength="40"
Ct1 Ши Length_cm_
it’ ® Length_In
А Ш5 Notes
<+ Graphic
S Category
♦ DataType="5tring"
♦ Maxlength="15“
-1 Common_Name
♦ DataType="5tring"
♦ MaxLength="30"
-T ^ Species_Name
♦ fieldname=‘'Species Name"
♦ DataType="5tring"
♦ MaxLength="40"
Ct1 Ши Length_cm_
it’ ® Length_In
А Ш5 Notes
<+ Graphic
;i Datapacket: Projec tl.CB2006T ей: DataSet Provider 1
;i Datapacket: Projec tl.CB2006T ей: DataSet Provider 1
Field View [Datapacket View |
Field View [Datapacket View |
Puc. 45.1. Полученная структура таблицы от удаленного сервера СОМ
Определение правила преобразования.
Определим поля исходного набора, содержимое которых требуется перенести в формат XML.
Дважды щелкнем на имени каждого поля, значение которого будет перенесено в формат XML. Эти поля будут представлены в столбце Selected Fields (Выбранные
поля) на панели Transformation (Преобразование). На этой панели фиксируются правила преобразования содержимого полей.
Дадим команду Create > XML from Datapacket (Создать > Документ XML из пакета данных). Утилита запросит подтверждение преобразования — нажмите кнопку Create (Создать) в открывшемся диалоговом окне.
На панели Document (Документ) появится дерево структуры в формате XML, содержащей описание набора данных на основе выбранных полей. На закладке Node Properties (Свойства узла) можно уточнить имя и тип каждого перемещаемого поля.
^ XML Mapping Tool
Fite Create tjelpi£H X
Document: <generated>i4 Щ Document- И ROW[*1(Я Species_No5^ 5pecies_Name ® GraphicDocument: <generated>i4 Щ Document- И ROW[*1(Я Species_No5^ 5pecies_Name ® GraphicTransformation: <none>В Nodes ^""selected Fiejds"1 [ROW[*]V \ROWDATA\ROW[*ROW[*]\ \ROWDATA\ROW['Transformation: <none>В Nodes ^""selected Fiejds"1 [ROW[*]V \ROWDATA\ROW[*ROW[*]\ \ROWDATA\ROW['Projectl ,CB2OO6Test:DataSetProviderl ~~^l Clientdataset ' ^'~Projectl ,CB2OO6Test:DataSetProviderl ~~^l Clientdataset ' ^'~ADocumerit’i^ADocumerit’i^i\ROWDATA\ROW[‘i\ROWDATA\ROW[‘• Create Datapacket As• Create Datapacket As- Category♦ DataT ype="5tring"♦ MaxLength="15"CT Common_Name♦ DataType="5tring"♦ MaxLength="30"+ ^ 5pecie$_Name♦ Lengthcm_+ ^ Lengthjn+ ^П Notes+ Graphic- Category♦ DataT ype="5tring"♦ MaxLength="15"CT Common_Name♦ DataType="5tring"♦ MaxLength="30"+ ^ 5pecie$_Name♦ Lengthcm_+ ^ Lengthjn+ ^П Notes+ Graphic
Document: <generated>
i4 Щ Document
- И ROW[*1
(Я Species_No
5^ 5pecies_Name ® Graphic
Document: <generated>
i4 Щ Document
- И ROW[*1
(Я Species_No
5^ 5pecies_Name ® Graphic
Transformation: <none>
В Nodes ^""selected Fiejds"1 [
ROW[*]V \ROWDATA\ROW[*
ROW[*]\ \ROWDATA\ROW['
Transformation: <none>
В Nodes ^""selected Fiejds"1 [
ROW[*]V \ROWDATA\ROW[*
ROW[*]\ \ROWDATA\ROW['
Projectl ,CB2OO6Test:DataSetProviderl ~~^l Clientdataset ' ^'~
Projectl ,CB2OO6Test:DataSetProviderl ~~^l Clientdataset ' ^'~
ADocumerit’i^
ADocumerit’i^
i\ROWDATA\ROW[‘
i\ROWDATA\ROW[‘
• Create Datapacket As
• Create Datapacket As
- Category
♦ DataT ype="5tring"
♦ MaxLength="15"
CT Common_Name
♦ DataType="5tring"
♦ MaxLength="30"
+ ^ 5pecie$_Name
♦ Lengthcm_
+ ^ Lengthjn
+ ^П Notes
+ Graphic
- Category
♦ DataT ype="5tring"
♦ MaxLength="15"
CT Common_Name
♦ DataType="5tring"
♦ MaxLength="30"
+ ^ 5pecie$_Name
♦ Lengthcm_
+ ^ Lengthjn
+ ^П Notes
+ Graphic
■ Transform Direction-
Create and Test Transformation iCreate and Test Transformation iГ XML to D|Г XML to D|Field View [Datapacket ViewJField View [Datapacket ViewJ^ Datapacket to XML
Create and Test Transformation i
Create and Test Transformation i
Г XML to D|
Г XML to D|
Field View [Datapacket ViewJ
Field View [Datapacket ViewJ
Г Data View
Document View iSchemaView? Node Properties MmpingJ
' Datapacket: Proj6ctl.CB2006Test:Data5etProvtderl
Puc. 45.2. Настройка схемы преобразования
Сохраним подготовленное правило (схему) преобразования. Эта схема представ лена на закладке Schema View (Просмотр схемы). Дополнительные вкладки DT[ (общее описание типа документа XML), XML-Schema (схема XML, формат XSD дополняющий описание DTD) и XDR (уточненная форма представления данны: XML) служат для выбора формата схемы. Для сохранения схемы преобразование в выбранном формате служит команда главного меню File > Save > Transformatioi (Файл > Сохранить > Преобразование).
Сохраним текущие правила преобразования в файле с расширением .XTR.
Выполнение преобразования.
Направление преобразования задается на закладке Mapping. В данном примере необ ходимо преобразовать информацию из набора базы данных в формат XML.
Установим переключатель Transform Direction (Направление преобразования) в положение Datapacked to XML (Пакет данных в документ XML).
Нажмем кнопку Create and Test Transformation (Выполнить и проверить преобразование).
^ Test transformation Datapacket... |* }j'O|^^
5pecies_No="90220"
^ 5pecie$_Name="Haemulon flavollneatum"
® Graph!C="AQAAAd6XAABCTd6XAAAAAAAA^ HflROW[*]
® 5pede$_No="90230"
5pecies_Name="Lutjanus jocu"
H Graphк="AQAAAd6XAABCTd6XAAAAAAAA^
И ROW!*]
^5 5pedes_No="90240"
5pedes_Name="Epinephelus striatus1'
И Graphic="AQAAAd6XAABCTd6XAAAAAAAAA
Ч И ROW[*]" "
И Species_No="90250"
DgJ 5pedes_Name="Thalassoma biFasdatum"
§5 Graphic="AQAAAd6XAABCTd6XAAAAAAAAX;^:
< 'j J> "
Datapacket: Projectl,CB2006Te5t:Data5etProviderl ;
Puc. 45.3. Подробный отчет no каждой преобразованной записи
Нажмем кнопку Save (Сохранить) и сохраним набор данных в формате XML. Имя файла выберем произвольно.
На этом процесс преобразования набора данных в формат XML с помощью утилиты XML Mapper закончен.
Обратное преобразование.
Обратный процесс преобразования из файла XML в набор данных выполняется по схожей технологии.
Сбросим все настройки утилиты командой File > Clear АП (Файл > Очистить все).
Выберем схему и данные для преобразования командой главного меню File > Open (Файл > Открыть) и выбором файла с расширением .XTR (тип файла Transformation files в диалоговом окне выбора файлов)3.
Откроем ранее подготовленный файл .XTR с описанием процесса преобразования.
При необходимости загрузим данные в формате XML из файла, сформированного на шаге 3.3. Для этого надо дать команду Open XML Document (Открыть документ XML) с помощью контекстного меню закладки Document View (Просмотр документа). Данные будут показаны, если включен флажок Data View.
Отберем поля, содержимое которых надо перенести в результирующий пакет. Для этого надо дважды щелкнуть на нужных узлах дерева структуры данных.
Установим переключатель Transform Direction (Направление преобразования) в положение XML to Datapacket (Документ XML в пакет данных).
Дадим команду главного меню Create > Datapacket from XML (Создать > Пакет данных из документа XML). На панели Datapacket (Пакет данных) появится структура преобразованного набора данных.
Нажмем кнопку Create and Test Transformation (Выполнить и проверить преобразование). В промежуточном диалоговом окне будет отображено содержимое транслируемого документа XML, которое поступит в целевой набор данных.
Программная работа с данными в формате XML
Утилита XML Mapper предназначена для ручного преобразования данных. Среда C++Builder предлагает набор технологий, с помощью которых такая работа автоматизируется.
Технология программного преобразования данных в формат XML
Функциональные возможности утилиты XML Mapper реализованы в среде C++Builder в виде компонента TXMLTransform (преобразование XML). Он расположен в категории палитры компонентов Data Access (Доступ к данным). Этот компонент позволяет создавать программы, в которых происходит преобразование наборов данных, представленных как во внутреннем формате, так и в формате XML. Такая возможность полезна, например, если требуется состыковать действующее приложение с внешними системами, которые сегодня практически все способны обрабатывать данные XML. Полезна возможность преобразования данных в формат XML и при создании Интернет-приложений.
ПРИМЕР 45.2
Программное преобразование данных XML в клиентский набор данных
Рассмотрим пример, в котором данные в формате XML и правила преобразования в формате XDR переводятся во внутреннее представление клиентского набора данных (класс TCIientDataSet).
Создание заготовки приложения.
Сформируем пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Настройка компонентов.
Набор данных представляется в программе с помощью обычных компонентов, используемых для работы с базами данных (см. главу 37).
Разместим на форме таблицу TDBGrid, источник данных TDataSource и клиентский набор данных TCIientDataSet.
Свяжем эти компоненты друг с другом, но пока не будем стыковать набор данных с поставщиком информации.
Добавим на форму компонент TXMLTransform и кнопку. Создадим обработчик ее нажатия.
Программирование преобразования.
Задействуем в обработчике компонент TXMLTranstorm для преобразования подготовленных заранее (например, с помощью утилиты XML Mapper) данных: файла .XML с исходной информацией и файла .XDR, содержащего схему преобразования:
void fastcall TForml::ButtonlClick(TObject *Sender) { // источник XML-данных:
XMLTransforml->SourceXmlFile = "d:\\1.xml";
// правила преобразования:
XMLTransforml->TransformationFile = "d:\\ToDp.xtr";
// выполнить преобразование:
ClientDataESetl->XMLData = XMLTransforml->Data; ClientDataSetl->Active = true;
}
Преобразование выполняется при работе оператора присваивания — в момент обращения к свойству Data компонента TXMLTranstorm.
Технология двусторонних преобразований наборов данных
Специально для задач преобразования наборов баз данных в формат XML и обратно система C++Builder предлагает в категории Data Access (Доступ к данным) компонент TXMLTransformProvider (поставщик данных XML). Его свойства TransformRead (входная информация для преобразования) и TransformWrite (выходная информация для преобразования) представляют собой экземпляры компонента TXMLTranstorm. Поставщик данных XML позволяет избежать ручного кодирования, использованного в предыдущем разделе.
ПРИМЕР 45.3
Двустороннее преобразование данных формата XML
При использовании поставщика данных XML двустороннее преобразование данных полностью обеспечивается визуальной настройкой.
Сформируем пустое приложение командой File > New > VOL Forms Application (Файл > Создать > Приложение VCL Forms).
Добавим на форму компонент TXMLTransformProvider.
В его свойстве XMLDataFile зададим файл .XML с исходными данными в формате XML. При этом данный компонент настраивается на прием информации из файла.
В подсвойстве TransformationFile свойства TransformRead зададим схему входного преобразования (файл с расширением .XTR, определяющий процесс преобразования данных из формата XML в формат набора данных). Теперь в проекте доступен источник (поставщик) данных в стандартизованном формате C++Builder, который поддерживается компонентами C++Builder, связанными с наборами данных.
Поместим на форму клиентский набор данных. Добавим компоненты TCIientDataSet, TDataSource и TDBGrid и свяжем их друг с другом.
В свойстве набора данных ProviderName выберем доступный настроенный экземпляр компонента TXMLTransformProvider и активизируем набор данных. Таблица TDBGrid сразу отобразит информацию от данного поставщика.
Технология использования удаленных поставщиков DCOM
Поставщик данных XML получает информацию из файла .XML или записывает ее в такой файл. В крупных проектах в качестве источников данных удобнее использовать внешние серверы (например, объекты DCOM). Взаимодействие программы с такими поставщиками и преобразование получаемых по сети наборов данных в формат XML выполняет компонент TXMLTransformClient (клиент преобразования XML), представленный в категории палитры инструментов Data Access (Доступ к данным).
ПРИМЕР 45.4
Преобразование наборов данных серверов DCOM в формат XML
В данном примере программа выполняет преобразование данных, поступающих от сервера DCOM.
Сформируем пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Поместим на форму компонент TDCOMConnection и настроим его на связь с одним из доступных удаленных серверов DCOM.
Добавим на форму компонент TXMLTransformClient и свяжем его с сервером DCOM через свойство RemoteServer, значением которого должна быть ссылка на доступные в проекте компоненты класса TDCOMConnection.
Выберем нужный поставщик данных сервера DCOM в свойстве ProviderName компонента TXMLTransformClient.
Разместим на форме кнопку и создадим ее обработчик. В нем происходит обращение к данным XML, то есть к набору данных, преобразованному в формат XML. Такое обращение обеспечивает метод компонента TXMLTransformClient:
virtual AnsiString
GetDataAsXml(AnsiString PublishTransformFile);
Единственный параметр метода определяет правила преобразования из формата XML в формат набора данных, записанные в файл .XTR.
Переведем данные от сервера DCOM в текстовый формат:
void fastcall TForml::ButtonlClick(TObject *Sender) {
AnsiString s = XMLTransformClientl-> GetDataAsXml ( "d: WToDp.xtr" ) ;
Полученные данные XML записаны в текстовую переменную s. Передадим ее содержимое компоненту TCIientDataSet — вручную закодируем загрузку текстового представления в клиентский набор данных:
ClientDataSetl->XMLData = s;
ClientDataSetl->Active = true;
Технология анализа документов XML
Рассмотренные компоненты предназначены для преобразования данных в формат XML, однако внутренняя структура документов XML остается скрытой от разработчика. Однако в большом числе приложений желательно организовать доступ к содержимому пакета данных XML непосредственно из программы, что позволяет обработать эти данные. Для этого предназначен компонент TXMLDocument (документ XML), представленный в категории палитры инструментов Internet (Интернет). Он предоставляет доступ к структуре информации, хранимой в формате XML. Обычно эта структура организована иерархически.
Данный компонент невизуальный и получает исходные данные XML либо из файла (свойство FileName), либо из текстовой строки (свойство XML). Эти свойства взаимоисключающие: если одно свойство задано, другое должно быть пустым.
При настройке компонента TXMLDocument особое внимание следует обратить на свойство DOMVendor. В нем задана одна из зарегистрированных в операционной системе моделей DOM.
Для платформы Windows в дополнение к стандартной модели DOM версии Microsoft (MSXML) обычно доступны две модели: Open XML (формат офисных документов новых версий пакета Microsoft Office) и Xerces XML (вариант, созданный корпорацией IBM и затем переданный сообществу разработчиков открытого кода).
ПРИМЕР 45.5
Разбор иерархически организованного документа XML
Рассмотрим в качестве примера схему работы с файлом employee.xml, расположенном в папке Demos\Delphi.Net\DBWeb\XMLCaching\ в каталоге установки системы Borland Developer Studio.
Предварительную схему файла employee.xml можно выяснить, открыв его в любом современном браузере — все они поддерживают формат XML. Так можно узнать, что документ XML состоит из набора записей EMPLOYEE (сотрудник), в каждой из которых, в частности, имеется поле FULL_NAME (полное имя сотрудника). Покажем, как организовать программный доступ к этому полю.
Создание заготовки приложения.
Сформируем пустое приложение командой File > New > VCL Forms Application (Файл > Создать > Приложение VCL Forms).
Настройка компонентов.
Добавим компоненты, которые обеспечат доступ к файлу XML.
Разместим на форме кнопку и компонент TXMLDocument.
Зададим ссылку на файл employee.xml в свойстве FileName компонента TXMLDocument.
Присвоим свойству Active значение True.
Программное обращение к элементам документа XML.
Корневой узел всей структуры документаХМЕопределяется в компоненте TXMLDocument через свойство DocumentElement (элемент документа). Список наследников корневого узла хранится в свойстве ChildNodes, представляющем собой список объектов, реализующих интерфейс IXMLNode. Каждый объект хранит значение поля или, в свою очередь, ссылается на вложенное дерево объектов. Подобной схемой взаимосвязей задается иерархия элементов документа XML.
Доступ к конкретным элементам текущего уровня документа происходит через свойство Nodes элемента IXMLNode, но обращаться к нему не обязательно. Свойство ChildNodes индексируется, причем не только с помощью числовых индексов (нумерация начинается с нуля), но и с помощью текстовых констант, описывающих имена полей документа XML.
Доступ к содержимому (значению) выбранного элемента в документе осуществляется обращением к свойству Text текущего элемента списка ChildNodes. Например, переберем значения всех полей первой записи EMPLOYEE: forfint i=0; i <= 10; i++) ListBoxl->Items->Add(
XMLDocumentl->DocumentElement-> ChildNodes->Nodes[0]-> ChildNodes->Nodes[i]->Text);
Префикс XMLDocument 1 ->DocumentElement обращается к корневому элементу документа XML. Далее в этом корневом элементе выделяется первый элемент из списка связанных с ним элементов — это часть ChildNodes->Nodes[0]. У первого элемента поочередно выделяется свойство, обозначающее текущего наследника — часть ChildNodes->Nodes[i]. Свойство Text текущего наследника содержит нужное значение.
При переборе всех записей документа надо менять индекс элемента, следующего за корневым:
XMLDocumentl->DocumentElement->ChildNodes->Nodes[ N ]
Обращение к конкретным полям записи допускается не только по номеру, но и по имени соответствующего поля:
XMLDocument 1 - >Document El ement - >
ChildNodes->Nodes[ 3 ] — >
ChildNodes->Nodes["HIRE_DATE"]->Text
Здесь HIRE_DATE — имя одного из полей в структуре документа XML.
Для динамического определения типа узла, например для проверки, является ли он узлом с конечными данными или же представляет собой ссылку на список дочерних узлов, используют метод GetNodeType().
!|Мт|ЖМ11еме^Я9И111№№ЯМ111№8Яае^^
Вопросы
Зачем нужен язык XML?
Как использовать утилиту XML Mapper для преобразования наборов данных в формат XML и обратно?
Как организовывается программное преобразование наборов данных в формат XML и обратно?
Как данные XML стыкуются с клиентскими наборами данных в программе на языке C++?
Как получать данные в формате XML от удаленных поставщиков данных DCOM?
Как программно анализировать содержимое документа XML?
ГЛАВА 46 Технология IntraWeb
Набор компонентов IntraWeb развивается в системах разработки Borland уже много лет. С помощью технологии IntraWeb процесс создания веб-приложений практически неотличим от процесса создания обычного приложения Windows. Подготовка веб-программы выполняется в визуальном проектировщике с помощью компонентов, внешне идентичных компонентам панели Standard.
Обзор технологии IntraWeb
В качестве прикладной формы в проекте IntraWeb выступает не форма Windows, а окно браузера. Но при его проектировании не требуется специальных знаний языка разметки HTML и сетевых протоколов.
Компоненты IntraWeb отличаются от их аналогов для Windows по префиксу IW. Например, компонент-кнопка (TButton) в технологии IntraWeb называется TIWButton. Компоненты IntraWeb доступны в палитре инструментов после создания пустой заготовки проекта IntraWeb.
Вызов экземпляра приложения IntraWeb происходит не совсем обычно. В браузере необходимо ввести сетевой адрес (URL), по которому установлен модуль IntraWeb. Однако этот модуль не является внутренним приложением веб-сервера — он включает в себя готовую реализацию как прикладной логики, так и системной функциональности веб-сервера. При обращении к исполняемому объекту Intra Web на серверном компьютере автоматически создается копия этого объекта, которая поддерживает взаимодействие с конкретным пользователем (клиентской программой) на протяжении всего процесса общения (сессии). Данная технология отличается весьма высокой производительностью, несмотря на то, что для каждого соединения создается свой экземпляр объекта IntraWeb. О способах повышения масштабируемости этой технологии говорится далее.
По результатам многочисленных испытаний доказано, что в плане эффективности IntraWeb как минимум не уступает таким стандартным веб-технологиям, как ASP, CGI и ISAPI.
ПРИМЕР 46.1Создание простого приложения IntraWebПРИМЕР 46.1Создание простого приложения IntraWeb
ПРИМЕР 46.1
Создание простого приложения IntraWeb
ПРИМЕР 46.1
Создание простого приложения IntraWeb
Рассмотрим возможности IntraWeb на простом примере. Допустим, надо создать приложение, складывающее два введенных пользователем числа и выдающее их сумму. Эта схема должны быть реализована в веб-архитектуре. Пользователь обращается из браузера по
заданному сетевому адресу, заполняет поля в открытой странице HTML, нажимает кнопку веб-формы, после чего числа отсылаются серверному модулю IntraWeb. Он выполняет заданные действия (складывает числа), а результат передает клиентскому браузеру в виде страницы HTML.
Создание заготовки приложения.
Создание заготовки приложения IntraWeb требует выполнения дополнительных настроек.
Дадим команду File > New > Other (Файл > Создать > Другое). В категории C++Builder Projects > IntraWeb (Проекты C++Builder > IntraWeb) выберем значок IntraWeb Application Wizard (Мастер создания приложения IntraWeb) и нажмем кнопку ОК.
Рис. 46.1. Выбираем заготовку проекта Intra WebРис. 46.1. Выбираем заготовку проекта Intra Web
Рис. 46.1. Выбираем заготовку проекта Intra Web
Рис. 46.1. Выбираем заготовку проекта Intra Web
Tool Palette ■ '®Tool Palette ■ '®6 ’ fe Т : - IW Standard А* HWAppletHWCbeckBox а TIWCcmboBox ь₽ TMEdt“5 TIWHe“ TIWFlash• Tiwm.ulea TIWImege TIWImageFde"jj TIWImageButtonH? TIWLti6 ’ fe Т : - IW Standard А* HWAppletHWCbeckBox а TIWCcmboBox ь₽ TMEdt“5 TIWHe“ TIWFlash• Tiwm.ulea TIWImege TIWImageFde"jj TIWImageButtonH? TIWLtigaPr... <Qd... I*^To...[gaPr... <Qd... I*^To...[
Tool Palette ■ '®
Tool Palette ■ '®
6 ’ fe Т : - IW Standard А
* HWApplet
HWCbeckBox а TIWCcmboBox ь₽ TMEdt“
5 TIWHe
“ TIWFlash
• Tiwm.ule
a TIWImege TIWImageFde
"jj TIWImageButton
H? TIWLti
6 ’ fe Т : - IW Standard А
* HWApplet
HWCbeckBox а TIWCcmboBox ь₽ TMEdt“
5 TIWHe
“ TIWFlash
• Tiwm.ule
a TIWImege TIWImageFde
"jj TIWImageButton
H? TIWLti
gaPr... <Qd... I*^To...[
gaPr... <Qd... I*^To...[
Откроется диалоговое окно. Структуру создаваемого серверного модуля выбирают в разделе Application Туре (Тип приложения). Выберем вариант StandAlone Application (Автономное приложение), который представляет целевой веб-модуль в виде самостоятельного исполняемого файла.
Укажем каталог проекта в поле Project Directory (Каталог проекта), а имя проекта — в поле Project Name (Имя проекта). В раскрывающемся списке Main Form (Главная форма) настраивают вид пользовательской формы на конкретное устройство.
Возможные варианты — классический браузер (Standard 4.0), карманное персональное устройство (PDA 3.2) или смартфон (WAP).
Закроем диалоговое окно и сохраним создаваемый проект IntraWeb. Среда C++Builder сгенерирует пустую заготовку будущего приложения. Проектируемая веб-форма приложе-
ния доступна в окне Проектировщика. Она представляет собой вариант страницы, отображаемой в браузере пользователя.
Подготовка пользовательского интерфейса.
Для проектирования пользовательского интерфейса используются компоненты из категории палитры инструментов IW Standard.
С точки зрения прикладного программиста, создание приложения IntraWeb почти не отличается от создания обычного приложения. Точно так же используется Проектировщик и программируется логика обработки действий пользователя. Эта логика может быть перенесена в автономное приложение или, наоборот, извлечена из программы Windows. Например, можно просто скопировать код обработчиков нажатий кнопок.
Разместим на форме кнопку TIWButton с панели IW Standard.
В ее свойстве Caption зададим подпись Сложить.
I-J&!®I-J&!®Window Help - fFest LayoutWindow Help - fFest LayoutДобавим два текстовых поля (компонент IWEdit) и надпись IWLabel.
I-J&!®
I-J&!®
Window Help - fFest Layout
Window Help - fFest Layout
* Projectl - Borland Developer Studio 2006 - Unit 1 .cpp
File Edit Search View Refactor Project Run Component Tools StarTeam
я Object Inspector ¥ X Д Welcome Page ^Unttl.cpp 1 ^UserSessя Object Inspector ¥ X Д Welcome Page ^Unttl.cpp 1 ^UserSessIWButtonlIWButtonlAA|propertie"s] Events:j■Cursor crAuto ^|j 5 jDoSubmitVali^truejiExtraTa^ar^TStringLis!0|pont (TIWFonlJ(FnendlyName IWButtor)IhotKey ;i[Name ;IWButtonl;j RenderSize [true)jScrrptEvents (TlWScrit'|propertie"s] Events:j■Cursor crAuto ^|j 5 jDoSubmitVali^truejiExtraTa^ar^TStringLis!0|pont (TIWFonlJ(FnendlyName IWButtor)IhotKey ;i[Name ;IWButtonl;j RenderSize [true)jScrrptEvents (TlWScrit'. [iwEdlti. [iwEdlti] : : |iwedit2] : : |iwedit2• Сложить• СложитьIWLabell- • ■IWLabell- • ■iStatusText|iagjZIndexВ ^Visual[All showniStatusText|iagjZIndexВ ^Visual[All shownооа1Ылпаа1Ылпаде.де.J6J6InsertInsertf<? Tool Palette$ X-IWstandard " ‘ в* TIWApfletf<? Tool Palette$ X-IWstandard " ‘ в* TIWApflet■#TlwCheckBox“fв HWComboBoxiПГ TIWEdlt:^ TIWFffe;*TIWFIash.*TIWHRule:4 TZWImage■'Л UWImageFiteШ■#TlwCheckBox“fв HWComboBoxiПГ TIWEdlt:^ TIWFffe;*TIWFIash.*TIWHRule:4 TZWImage■'Л UWImageFiteШ
я Object Inspector ¥ X Д Welcome Page ^Unttl.cpp 1 ^UserSess
я Object Inspector ¥ X Д Welcome Page ^Unttl.cpp 1 ^UserSess
IWButtonl
IWButtonl
A
A
|propertie"s] Events:j
■Cursor crAuto ^|j 5 jDoSubmitVali^truej
iExtraTa^ar^TStringLis!
0|pont (TIWFonlJ
(FnendlyName IWButtor)
IhotKey ;i
[Name ;IWButtonl;
j RenderSize [true)
jScrrptEvents (TlWScrit'
|propertie"s] Events:j
■Cursor crAuto ^|j 5 jDoSubmitVali^truej
iExtraTa^ar^TStringLis!
0|pont (TIWFonlJ
(FnendlyName IWButtor)
IhotKey ;i
[Name ;IWButtonl;
j RenderSize [true)
jScrrptEvents (TlWScrit'
. [iwEdlti
. [iwEdlti
] : : |iwedit2
] : : |iwedit2
• Сложить
• Сложить
IWLabell- • ■
IWLabell- • ■
iStatusText
|iag
jZIndex
В ^Visual
[All shown
iStatusText
|iag
jZIndex
В ^Visual
[All shown
о
о
а1Ылпа
а1Ылпа
де.
де.
J6
J6
Insert
Insert
f<? Tool Palette$ X
-IWstandard " ‘ в
* TIWApflet
f<? Tool Palette$ X
-IWstandard " ‘ в
* TIWApflet
■#TlwCheckBox“
fв HWComboBox
iПГ TIWEdlt
:^ TIWFffe
;*TIWFIash
.*TIWHRule
:4 TZWImage
■'Л UWImageFiteШ
■#TlwCheckBox“
fв HWComboBox
iПГ TIWEdlt
:^ TIWFffe
;*TIWFIash
.*TIWHRule
:4 TZWImage
■'Л UWImageFiteШ
Puc. 46.2. Проектирование веб-формы приложения Intra Web
Программирование логики.
Программа должна обрабатывать только одно событие — нажатие кнопки Сложить.
Создадим обработчик нажатия кнопки Сложить, дважды щелкнув на ее образе в окне Проектировщика.
Запрограммируем процесс расчета результата стандартным способом:
void fastcall TIWForml::IWButtonlClick(TObject ‘Sender)
int x, у, z;
x = StrToInt(IWEdit1->Text);
у = StrToInt(IWEdit2->Text);
z = x+y;
IWLabell->Caption = IntToStr(z);
Работа с приложением.
Откомпилируем и запустим приложение. Оно выполнено в веб-архитектуре, поэтому может быть развернуто на типовом веб-сервере или запущено с помощью собственного сервера веб-приложений IntraWeb Application Server, входящего в поставку C++Builder.
Рис. 46.3. Настраиваем встроенный веб-сервер Intra WebРис. 46.3. Настраиваем встроенный веб-сервер Intra WebСервер IntraWeb автоматически стартует при запуске созданной программы из среды C++Builder. Его можно настроить на моделирование работы определенного клиентского устройства (микрокомпьютера, мобильного телефона с встроенным браузером) с помощью раскрывающегося списка Device simulation (Имитация устройства). Если задать в списке Simulated connection speed (Имитация скорости соединения) примерную скорость связи с устройством, можно оценить объем передаваемой информации и качество визуального представления.
Рис. 46.3. Настраиваем встроенный веб-сервер Intra Web
Рис. 46.3. Настраиваем встроенный веб-сервер Intra Web
Когда этот сервер работает, для запуска программы доступна команда Run > Execute
(Запуск > Выполнить) в окне сервера. Откроется браузер, в окне которого представлена спроектированная форма. С помощью кнопок поработаем с виртуальным калькулятором, который физически выполняет расчеты на сервере.
Получившееся приложение легко развертывается на существующих веб-серверах или запускается вообще без внешнего веб-сервера (только с помощью собственного сервера IntraWeb). Приложение IntraWeb автоматически распознает тип браузера пользователя (поддерживаются браузеры Internet Explorer, Netscape, Mozilla и ряд микробраузеров для мобильных устройств) и динамически подстраивает свой интерфейс.
ПРИМЕР 46.2
' Использование технологии IntraWeb для работы с базой данных
Создадим приложение IntraWeb, работающее с базой данных. Допустим, требуется организовать взаимодействие такого приложения с базой данных employee.gdb, входящей в поставку СУБД Interbase. Содержимое записей должно отображаться для просмотра в браузере клиента.
Создание заготовки приложения.
Создадим пустую заготовку приложения. Дадим команду File > New > Other (Файл > Создать > Другое). В категории C++Builder Projects > IntraWeb (Проекты C++Builder > IntraWeb) выберем значок IntraWeb Application Wizard (Мастер создания приложения IntraWeb) и нажмем кнопку ОК. Выполним последующие начальные настройки.
Настройка связи с СУБД.
Для связи приложения с СУБД воспользуемся технологией dbExpress.
Добавим к проекту компонент TSQLConnection.
Выберем значение IBConnection в его свойстве ConnectionName.
Присвоим свойству Connected значение True.
Добавим на форму набор данных (компонент TSQLDataSet).
Свяжем набор данных с базой данных через его свойство SQLConnection. Выберем в нем ссылку на экземпляр компонента TSQLConnection.
Сформируем в свойстве CommandText запрос SQL с помощью встроенного редактора:
select * from CUSTOMER
Переведем набор данных в активное состояние — введем в свойство Active значение True.
Добавим к проекту источник данных (компонент TDataSource), необходимый для стыковки набора данных с пользовательским интерфейсом.
Свяжем источник данных с набором данных, выбрав последний в свойстве источника DataSet.
Пользовательский интерфейс.
На форме необходимо разместить два поля: одно предназначено для редактирования значения поля базы данных, а другое — для его визуализации. Однако для этого нельзя применять компоненты IWText и IWLabel из категории палитры инструментов IW Standard — ведь они не рассчитаны на взаимодействие с базами данных. Поэтому мы воспользуемся компонентами категории IW Data.
Разместим на проектируемой форме компонент IWDBEdit, который позволит редактировать значение поля таблицы базы данных.
Добавим компонент IWDBLabel, который будет отображать значение поля таблицы базы данных.
Разместим на форме средство навигации по набору данных — компонент IWDBNavigator.
Для каждого из компонентов IWDBEdit, IWDBLabel и IWDBNavigator выберем в свойстве DataSource ссылку на источник данных (экземпляр компонента TDataSource).
Конкретное редактируемое или отображаемое поле таблицы для компонентов IWDBEdit и IWDBLabel выбирается в свойстве DataField (укажем, например, поля COUNTRY и CUSTOMER).
Приложение готово. Можно запу
стить его и просмотреть содержимое базы данных. Обратите внимание, как красиво выполнен элемент-навигатор для перемещения по таблице базы данных и ее
обновления
Множественные формы проекта IntraWeb
В проекте IntraWeb допускается одновременное использование нескольких форм. Такой режим работы напоминает обычные многооконные формы Windows. В приложениях IntraWeb невозможно создавать модальные окна — они не могут «всплывать» в браузере поверх других окон и полностью захватывать управление.
Новую форму к проекту IntraWeb добавляют командой File > New > Other (Файл > Создать > Другое). В категории C++Builder Projects > IntraWeb следует выбрать значок New Form (Новая форма IntraWeb). В проекте появится новая форма. В прикладные модули проекта надо вручную включать ссылку на нее. По умолчанию главной формой проекта IntraWeb считается самая первая форма.
Ее можно сменить — текущую форму модуля делают основной с помощью метода SetAsMainForm(), вызываемого в главном файле проекта Projectl .cpp:
#include <vcl.h>
#pragma hdrstop
// Unit2.h подключен вручную:
#include "Unit2.h"
USEFORM("Unit1.cpp", IWForml); /* TIWAppForm: File Type */
USEFORM("Servercontroller.cpp", IWServerController); /* TIWServerControllerBase: File Type */
USEFORM("Unit2.cpp", IWForm2); /* TIWAppForm: File Type */
#include <IWMain.hpp>
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try {
Forms::Application->Initialize() ;
Forms::Application->CreateForm
(classid(TFormlWMain), &FormIWMain);
// делаем активной вторую форму:
TIWForm2::SetAsMainForm(classid(TIWForm2));
Forms::Application->Run();
}
catch (Exception Sexception) { }
return 0;
}
Для отображения формы приложения IntraWeb всегда применяется метод show. Соответствующая команда записывается, например в обработчике нажатия кнопки, следующим образом:
void fastcall TIWForm2::IWButtonlClick(TObject *Sender)
{
TTWForml *fl = new TIWForml((TComponent*)Sender); fl->Show();
}
Здесь из второй, текущей формы TiwForm2, которая сделана главной методом SetAsMainForm (), вызывается (визуализируется в браузере) первая форма TIWForml. Если требуется вернуться из вызванной формы обратно к вызвавшей, надо использовать метод Release ().
Класс TiWForm не может вызывать методы самоуничтожения (наподобие методов Free () или Destroy () класса TOb j ect), поэтому открытая форма может обращаться лишь к методу Release (). Он активизирует форму, которая была активна
до текущей. В то же время метод Release () функционирует и как метод разрушения формы — промежуточные значения и временные объекты, связанные с текущей формой, удаляются из памяти. После вызова этого метода и последующего обращения к другому классу формы с помощью метода Show() все процедуры инициализации новой формы (если они предусмотрены) необходимо выполнить заново.
Масштабируемость системы IntraWeb
Для повышения масштабируемости приложения IntraWeb и снижения нагрузки на процессор при большом числе пользователей (клиентских подключений) применяется ряд специальных подходов.
Управление состояниями
Данный подход полезен не только при создании проектов IntraWeb, но и для любых других сетевых систем. Он анализирует состояние пользовательской программы и обрабатывает каждый клиентский запрос не специально создаваемым серверным потоком (новым экземпляром приложения IntraWeb), а любым потоком, свободным в данный момент. В реальной работе процесс взаимодействия пользователя с сервером непродолжителен — все остальное время пользователь занят разглядыванием информации в окне браузера и изучением результата. Поэтому неразумно выделять для каждого подключения отдельный серверный поток, который 99% времени находится в состоянии ожидания. Достаточно определить текущее состояние браузера пользователя и продолжить с ним работу с того момента, когда она была приостановлена.
На прикладном уровне управление состояниями реализуется с помощью сессий. По умолчанию режим сессий поддерживается для каждого приложения IntraWeb, и разработчику достаточно организовать запоминание состояний клиентского приложения3.
Это можно сделать разными способами. Однако применять глобальные переменные, описанные вне класса главного приложения TIWForm, недопустимо — все сведения следует хранить внутри экземпляра этого класса.
Организация множественных серверов
На серверной стороне можно запустить несколько модулей IntraWeb с разными IP-адресами для независимого обращения. Такой прием переносит задачу управления нагрузкой на стандартные средства операционной системы, обычно хорошо развитые.
3 После создания заготовки приложения IntraWeb Мастер автоматически формирует в проекте дополнительный модуль ServerController.cpp, доступный в Менеджере проекта. Он позволяет контролировать технические характеристики работы приложения (настройку на конкретный порт, типы браузеров и их внутренние характеристики, например поддержку файлов cookies и сценариев JavaScript). Кроме того, по умолчанию предлагается включить в проект поддержку сессионного взаимодействия (флажок в окне Мастера Create User Session).
Серверы транзакций
Для балансировки нагрузки приложений IntraWeb удобно задействовать серверы приложений и транзакций. Они обеспечивают автоматическое распределение запросов по нескольким машинам, на каждой из которых может быть установлена отдельная копия приложения IntraWeb4.
Дополнительные возможности технологии IntraWeb
Технология IntraWeb ориентирована на разработку веб-приложений и взаимодействие со стандартными тонкими клиентами (браузерами). Поэтому в нее включен обширный набор компонентов, поддерживающих массово распространенные технологии создания красивых и удобных пользовательских интерфейсов. Так, модуль Java встраивают в пользовательский интерфейс с помощью компонента TIWApplet (имя файла с кодом Java задается в свойстве ClassName), а флэш-ролик добавляют с помощью компонента TIWFIash (ссылку на локальный флэш-файл записывают в свойство Filename, а сетевой адрес — в свойство URL).
Отметим еще несколько полезных компонентов, предназначенных для встраивания в страницу HTML специальных объектов.
Компонент TIWActiveX позволяет использовать элементы ActiveX. Этот компонент весьма удобен при организации взаимодействия с альтернативными браузерами, (например, с браузерами Firefox и Opera).
Компонент TIWMPEG обеспечивает воспроизведение видеороликов в формате MPEG.
Компонент TIWQuickTime обеспечивает воспроизведение видеороликов в формате QuickTime.
Вопросы
В чем отличия технологии Intra Web от других технологий создания Web- приложений, доступных в системе C++Builder?
Как организуется взаимодействие с пользователем в приложении IntraWeb?
Как приложение Intra Web работает с базами данных?
Как организовать поддержку нескольких форм в приложении IntraWeb?
Каковы основные подходы к масштабированию приложений IntraWeb?
Оптимизация работы множественных копий программ IntraWeb почти всегда выгодна. Достоинство приложений IntraWeb в том, что они хорошо поддерживают многопоточный режим, позволяя активно задействовать всевозможные технологии балансировки.
^У) РАЗДЕЛ
Технологии управления жизненным циклом приложений
История и принципы программной инженерии
Контроль версий и организация групповой работы
Управление требованиями к проекту
Локализация приложений
ГЛАВА 47 Технологии проектирования программных систем
Многие современные программные проекты (до 60-80%) из-за технических сложностей не укладываются в сроки и бюджет и практически всегда содержат невы- явленные ошибки. Они подчас приводят к полному краху системы, а версии продуктов для Интернета быстро взламываются хакерами.
Вместе с тем, в мире не одну сотню лет успешно реализуются крупные инженерные проекты: возводятся гигантские мосты, строятся огромные небоскребы, запускаются космические станции, создаются самолеты, корабли и подводные лодки. В начале XX века, например, созданием крупных крейсеров управляли один-два инженера, а остальной состав состоял из техников и рабочих. Но процент неудач, тем более критических ошибок, в классических инженерных проектах (весьма и весьма сложных), всегда был мал.
Одно из основных различий между классическими и программными проектами заключается в том, что вероятность возникновения ошибки при написании программы гораздо выше, чем при создании физического объекта. Кодировщик может случайно отвлечься, сделать описку, не проверить редко используемую логическую ветку программы. Ошибка, внесенная в код, может остаться невыяв- ленной и в результирующем коде. Она сохранится в нем, фактически, в виде мины замедленного действия, которая может сработать и через много лет — как только сложатся какие-то экзотические условия эксплуатации, не предусмотренные на этапе тестирования.
Качество работы сотрудника в классических инженерных проектах проверить гораздо проще. Отдельные детали проходят проверку качества и на них дается гарантия, а эффективность их сборки легко поддается контролю. В таком случае на первый план выходит мастерство проектировщика, главного конструктора, который создает из набора доступных элементов сложную и устойчиво работающую систему. В программировании готового и ограниченного стандартами перечня строительных элементов нет — все модули проекта приходится каждый раз создавать с нуля, а на стандартные компоненты приходится лишь малый процент задуманных функциональных возможностей.
Кроме того, число сложных проектов реального мира невелико. Такие проекты встречаются, в основном, в строительстве, машиностроении и электронике. В каждой из этих отраслей наработан многолетний опыт проектирования. Современные конструкторы в основном лишь развивают этот коллективный опыт на базе новых технологий, задействуют материалы новых типов, более эффективные детали, более совершенные источники питания и так далее.
В программировании же ситуация противоположная. Практически в каждом новом проекте создание системы ведется заново. Подчас даже небольшие различия во второстепенных, с точки зрения заказчика, требованиях могут привести к полной переделке ранее спроектированной схожей системы. Поэтому невозможно свести процесс создания программной системы только к фазе проектирования.
Сложность процесса разработки
Важная особенность процесса разработки программного обеспечения заключается в том, что этому процессу практически всегда присуща сложность. Причем эта сложность растет тем стремительнее, чем больше объем проекта (количество операторов в исходном тексте). Согласно Гради Бучу (Grady Booch), одному из ведущих специалистов по программной инженерии, сложность программного проекта вызвана следующими причинами.
Сложна и плохо формализуема исходная предметная область, подлежащая автоматизации.
Сложно описать поведение будущей компьютерной системы с надлежащей точностью.
Требования к нечетко определенному проекту часто меняются в ходе его реализации. В результате архитектура приложения с самого начала должна проектироваться как максимально гибкая и допускающая возможность внесения быстрых модификаций и перестроек. Однако создание гибкой архитектуры — процесс, сложный сам по себе.
Трудоемко управление масштабным проектом, в котором объединены сот- гги программистов. Современные приложения насчитывают миллионы строк исходного кода, на создание которых требуются десятки человеко- лет, и эффективная организация этого процесса крайне сложна.
Снижение проектной сложности
Об истории программной инженерии и методологиях эффективной разработки программ рассказывается в главе 49. Здесь мы более подробно коснемся вопроса снижения сложности программных систем. Существуют достаточно эффективные подходы, приближающие процесс разработки программ к процессу разработки физических комплексов. Все они основаны на концепции повторного использования наборов стандартных компонентов. В системе C++Builder эта концепция воплощена в виде библиотеки компонентов VCL и технологий объектного программирования. Однако такие подходы слабо решают проблему минимизации ошибок, вносимых рядовыми кодировщиками. В любом проекте найдется немало задач, которые возможно решить лишь объемным ручным кодированием.
Программирование-в-малом (programming in small) — область программной инженерии, в которой изучаются вопросы создания надежных, эффективных и безошибочных исходных текстов программ.
В рамках программирования-в-малом разработаны, например, шаблоны проектирования — средства, стандартизующие проектирование и построение разных эле-
ментов системы. Однако в каждом конкретном проекте все равно находится оригинальная логика, скрытая внутри прикладных элементов таких шаблонов, — ее необходимо кодировать вручную.
Сегодня не существует высокоэффективных методологий, которые позволяли бы свести к минимуму (в идеале — вообще исключить) ошибки кодировщиков. В результате удаление ошибок на последних этапах проекта, в ходе тестовой эксплуатации, обходится обычно крайне дорого, на порядки дороже, нежели на этапах проектирования и программирования. Ведь в конце проекта необходимо каждый раз сворачивать и заново разворачивать систему на компьютерах заказчика, обеспечивать доступ разработчиков к исходному коду эксплуатируемых приложений для обнаружения неверных операторов, имитировать ситуацию, в ходе которой ошибка возникла, и так далее. Нередко подобное вообще невозможно.
В большинстве современных масштабных проектов массово распространены подходы на базе структурного и, особенно, объектного проектирования. Они обеспечивают достаточно высокий уровень абстрагирования спроектированной модели системы от реализующего ее программного кода. В одной среде разработки объединены технологии модульной сборки и ведения иерархий понятий, поддержки объектного хранилища, визуального построения моделей и автоматической генерации на их основе исходного кода. Этот код впоследствии может модифицироваться разработчиками или сразу транслироваться в исполнимый вид.
Технология объектного проектирования
Способы объектного проектирования программных систем отличаются от способов, принятых в структурном, модульном подходе. Базовым элементом на этапе проектирования становится объект (достаточно абстрактное понятие), а не отдельный оператор (простой или составной, как принято в структурном подходе). В ходе объектного проектирования создается модель программной системы, которая описывается двумя способами:
статически, в виде иерархии классов (формализуемых сущностей реального мира) и взаимосвязей между ними;
динамически, с помощью визуальных диаграмм, описывающих поведение системы, последовательность перехода ее элементов из состояния в состояние.
Основной акцент в структурном проектировании делается на алгоритмическом моделировании, в котором базовым элементом системы считается модуль с кодом. Его статическая структура представляется в виде дерева взаимосвязей между модулями.
Проектировщикам, использующим инструменты создания объектных моделей, доступны значительно более выразительные средства, чем при структурном подходе. Модели хорошо представляются в виде разнообразных графических диаграмм, в результате чего удается учесть тонкие нюансы организации структуры и функционирования приложения. Визуальные диаграммы упрощают осмысленный диалог проектировщика с заказчиком, который обычно не владеет программистской терминологией и слабо понимает проблемы разработчиков.
Переход от императивного программирования к объектному является, как отмечает Гради Буч, эволюционным. По-прежнему используются лучшие из существующих наработки, идеи и технологии. Только теперь они перенесены на уровень программирования-в-малом и тесно объединены в рамках более гибкой и понятной проектировщикам объектной парадигмы. В результате же упрощается процесс управления программистскими коллективами, что позволяет увеличить размер программ, а главное — еще выше поднять границы потенциально достижимых проектных объемов, примерно до десятков миллионов строк кода.
На базе объектного подхода был выработан ряд методологий разработки программного обеспечения. Они отличаются, как правило, нацеленностью на прикладные проекты разного размера. Чем больше создаваемый проект, тем сложнее создавать его объектную структуру и тем ближе этот процесс к искусству.
Проекты малого и среднего размера создаются сегодня с помощью сред объектной разработки достаточно быстро и легко. При этом обычно придерживаются следующей технологии, последовательности этапов проектирования.
Создание словаря предметной области. Выявляются понятия и термины, на основе которых в дальнейшем будет формироваться иерархия классов и взаимосвязей между ними. Понятия и термины охватывают предметную область задачи в объеме, минимально необходимом для построения действующего продукта.
Как правило, выявляются сущности, имеющие отношение кролям пользователей, например: Начальник отдела, Сотрудник в проекте автоматизации, Игрок в компьютерной игре. Выделяются также сущности, соответствующие материальным объектам, используемым этими пользователями в моделируемой деятельности, например: Документ в документообороте, Пулемет в игре жанра action. Этим сущностям, очевидно, присуще определенное поведение, которое также заносится в словарь. Начальник может Подписывать Документы, Нанимать и Увольнять Сотрудников, Игрок — Стрелять из Пулемета и так далее. Это поведение задается соответствующим сущностям в виде их функциональных особенностей (методов). Изучаются также возможные сценарии взаимодействия будущих пользователей с системой.
Наполнение элементов словаря смыслом. На этом шаге создаются конкретные классы, определяются их внутренние структуры и формируются самые важные взаимосвязи между классами. Пока реализация классов не выполняется. Проектирование ведется с помощью визуальных средств, на уровне модели UML. Поддерживающий ее исходный код генерируется автоматически и незаметно для проектировщика. В результате он не отвлекается на программное формирование описаний классов и их минимальную реализацию. Ведущие специалисты по программной инженерии рекомендуют на этом этапе также формировать диаграммы состояний на базе конечных автоматов: они описывают динамическое поведение системы.
На первый взгляд, на данном этапе не происходит серьезных изменений по сравнению с первым шагом. Тем не менее, требование точного описания структур всех классов выявляет недоговоренности и незаконченности в проектных требованиях и позволяет избежать множества проектных ошибок.
Особое внимание уделяется функциональным требованиям. Они представляют собой сценарии взаимодействия пользователя с системой, а объектов — между собой. Работа над детализацией сценариев по мере их завершения постепенно передается от старших проектировщиков младшим3.
Классы формируются преимущественно в изолированном виде. Между ними задаются только самые необходимые связи, и без детализации взаимных отношений. Активно применяются на данном шаге шаблоны проектирования.
Формирование связей между классами. Этот проектный процесс выделен особо. Дело в том, что методы выявления взаимосвязей существенно отличаются от методов построения классов и наполнения их смыслом. Успешность процесса формирования связей и задания отношений между классами в значительной степени зависит от понимания проектировщиком их предназначения и особенностей. Для этого требуется полностью завершить второй шаг проектирования.
Выстроенная на этом шаге иерархия классов наглядно демонстрирует основные категории проектных сущностей. Сами классы при этом естественно распределяются по независимым модулям.
Реализация классов. Критерием эффективности предыдущего шага становится простота реализации построенной к данному моменту модели (уже почти завершенной). Если отобразить ее в код, получится первый прототип результирующей системы. Он работоспособен и может быть передан пользователю на изучение и даже опытную эксплуатацию.
В первую очередь реализуются самые важные и необходимые функциональные возможности. Это достигается за счет того, что каждому требованию к системе задается приоритет. В результате уже на первых итерациях удается получить готовый продукт с наиболее востребованными возможностями.
Если удается выполнять реализацию модели прямым программированием методов классов, то можно считать, что спроектированная модель удачна. Но предусмотреть все нюансы взаимосвязей между классами, объектами и пользователями удается далеко не всегда. Приходится вводить дополнительные понятия, глобальные переменные, новые связи и так далее. Требования к системе также нередко меняются уже в ходе проекта. Поэтому в процессе проектирования приходится возвращаться к третьему и даже ко второму шагу, дополняя и расширяя модель новыми сущностями и взаимосвязями.
Новые подходы к проектированию
Процесс проектирования всегда тесно связан с конкретным языком программирования, на котором создается система. Требования постепенно переводятся в модель, состоящую из компактных функциональных элементов (классов) и вза-
3 Хороший критерий необходимости дальнейшей детализации некоторого шага функционального требования — невозможность уложить описание этого шага в 5-7 слов.
имосвязей между ними. Модель автоматически транслируется в исходный код (описания классов), а программисты расширяют проект реализованными методами. Соответственно, выбранный язык программирования (в общем случае — выбранная среда разработки) должен поддерживать все шаги такого проектирования. В современных объектно-ориентированных языках (в частности, в языке C++) имеются развитые средства построения иерархии классов, организации видимости и защиты кода на уровне классов и модулей. Это позволяет, комбинируя объектный и структурный подходы, добиваться высокой автономности элементов программной системы.
Приложение, созданное с помощью подобных подходов, обычно отличается одним из двух недостатков.
Оно малоэффективно и работает медленно. При этом внутренняя структура приложения проста для понимания.
Оно высокопроизводительно, но разобраться в его структуре и коде весьма непросто.
Как правило, элегантные и понятные алгоритмы, записываемые в несколько строк, малоэффективны. Они требуют дополнительной оперативной памяти или агрессивно используют ресурсы процессора. И наоборот, быстрая и компактная реализация алгоритма требует от разработчика дополнительных ухищрений. В результате существенно усложняется код, появляются дополнительные взаимосвязи с другими модулями, становится все сложнее и сложнее расширять модель.
Современные методики программирования позволяют преодолеть эти недостатки. Удобнее всего их использовать, конечно, с языками, целенаправленно созданными под такие методики. Но и массовые объектно-ориентированные языки (C++, Delphi, С#, Java) позволяют достаточно успешно реализовывать новые подходы.
К современным методологическим новшествам относится, прежде всего, так называемый аспектно-ориентированный подход. Он предлагает в дополнение к понятию «объект» новое понятие «аспект». Под объектом в аспектно-ориентированном подходе понимается более широкое понятие компонента. Оно включает в себя понятие объекта, интерфейса, процедуры и модуля из императивных и объектных языков — всего того, что в процессе проектирования легко выделяется в автономную сущность.
В понятие аспекта включается все то, что не может быть достаточно просто выделено, инкапсулировано в программный элемент с прозрачным и удобным интерфейсом. При этом аспект все же подлежит программной реализации в рамках проекта. Аспекты обычно представляют собой смысловые структуры, которые должны быть задействованы в большинстве модулей и объектов программы. В то же время выделить их классическим способом в отдельные объекты не представляется возможным.
Типичным аспектом считается, например, локализация приложения4.
1 Большое число задач, связанных с оптимизацией и производительностью программных систем, относятся к аспектным.
Во многих модулях программы имеются элементы, нуждающиеся в адаптации к разным национальным языкам. Однако эти элементы рассредоточены по всему проекту. Среди них имеются свойства элементов управления и формы, значения которых хранятся в ресурсах, текстовые строки, записанные непосредственно в коде, форматы отображения времени и валюты и многое другое.
Другой пример аспекта — организация синхронной работы множества объектов. Так бывает, например, при создании модели автомобиля или самолета. В модель входят тысячи управляющих и одновременно функционирующих элементов, а процесс их синхронного управления нетривиален и плохо формализуем.
Аспект обычно реализуется стандартным способом - в виде модуля или объекта. Он содержит средства контроля и управления поведением существующих объектов и модулей программы, действия которых связаны друг с другом. Аспекты нередко создаются с помощью ранее рассмотренных шаблонов проектирования.
Окончательное приложение, включающее все аспекты и все прикладные модули, формируется, однако, не прямой сборкой со всеми существующими взаимосвязями в готовый продукт. В него вводится дополнительная управляющая структура, так называемый интегратор аспектов (aspect weaver). Он нередко реализуется в виде дополнительного модуля, компилируемого наравне с остальными. Другая возможная реализация интегратора аспектов — специальный препроцессор, который автоматически дополняет управляющими вызовами исходный код (подпрограммы или методы, взаимодействующие с внешними модулями), организуя тем самым контроль над взаимосвязями.
С помощью аспектов удобно расширить действующую систему, например, встроенным режимом самотестирования. Такой режим, с одной стороны, должен охватить все модули и классы системы, а с другой, должен обеспечить централизованный контроль и сбор данных. Для этого в систему добавляется модуль, реализующий аспект тестирования.
В нем содержится класс, ответственный за анализ накапливаемой информации и предоставляющий методы тестирования и сбора статистики. Но вызовы этого класса расставляет по исходному коду уже не программист, а препроцессор-интегратор. Ведь человек может ошибиться, задать неверный параметр тестирования или пропустить методы, нуждающиеся в контроле. Препроцессор же автоматически просканирует весь исходный текст и расширит его корректными вызовами аспектного класса.
Пока что среда Borland Developer Studio не содержит средств поддержки аспектно-ориентированного программирования. Но разработчику достаточно понять, что при проектировании сложной программной системы всегда присутствуют моменты функционирования, реализация которых затруднена или неясна. Какие- то из элементов системы, конечно, выделить довольно просто.
Решить проблему слабой формализации помогают, в частности, шаблоны проектирования, фактически представляющие собой типичные аспекты и способы решения плохо формализуемых задач. Главное, проектировщик должен максимально осознанно относиться к наличию в проекте таких «темных пятен». Надо стараться выделять их в отдельные модули с прозрачной реализацией с самого начала проектирования.
ГЛАВА 48 Архитектура, управляемая моделью
В последующих главах рассматриваются технологии проектирования программных систем, построения визуальных моделей и формирования на их основе готовых приложений. Они используются на разных этапах так называемого жизненного цикла программного продукта.
Жизненный цикл программного продукта — это период от возникновения замысла до завершения эксплуатации программной системы. Он складывается из последовательности этапов: исходный замысел, анализ и формализация требований, проектирование, кодирование, тестирование, внедрение, сопровождение, вывод из эксплуатации.
Продукты Borland полностью охватывают все этапы жизненного цикла приложения. Для этого корпорация Borland разработала концепцию Application Lifecycle Management (ALM) — управление жизненным циклом приложения — и положила ее в основу стратегии создания и совершенствования всех своих продуктов. В частности, построение моделей и проектирование архитектуры системы выполняется с помощью продукта Borland Together, который встроен в среду Borland Developer Studio в 2006 году и является ее неотъемлемым элементом.
Концепция архитектуры, управляемой моделью
Каждому этапу ALM соответствует своя концепция организации информационного труда. Такие конкретные концепции и методологии не столь масштабны, как модель ALM в целом, однако сами по себе достаточно эффективны и успешны. Так. тесно связанные этапы проектирования и разработки корпорация Borland рекомендует осуществлять в рамках технологии Model Driven Architecture (MDA) — архитектура, управляемая моделью. Концепция MDA, в отличие от ALM, была разработана не в компании Borland, а в независимой некоммерческой организации Object Management Group (OMG) — консорциум объектного управления Она объединяет сотни компаний — производителей программного и аппаратного обеспечения. В технологии MDA основным элементом проектирования считается модель.
Модель — это описание реальной системы, учитывающее определенньи характеристики или аспекты моделируемого объекта, процесса или явления.
В конкретном проекте модель MDA представляет собой законченное и формализованное представление создаваемого программного продукта или его смысловой части. Высокая точность и непротиворечивость описания необходима, чтобы автоматизировать процесс перевода модели в программный (обычно исходный) код.
Понятие «управление моделью» подразумевает прямое использование модели при любых действиях по проектированию, разработке и развертыванию системы. При внесении изменений в архитектуру приложения модель считается первичной. Пусть, например, требуется пополнить описание класса новым полем или методом. В классическом (не модельном) подходе к разработке модификация описания класса выполнялась бы в исходном коде, ручным кодированием.
В технологии MDA происходит модификация визуальной диаграммы, на которой класс представлен в виде графического элемента. На базе такой диаграммы исходный код с измененным описанием класса генерируется автоматически1.
Под архитектурой, управляемой моделью, понимается полная и законченная архитектура приложения, целиком формируемая с помощью модели.
В жизненный цикл программы входит этап формализации и анализа требований заказчика. Идея ведения этого этапа с помощью визуальных моделей развивается уже много лет. Она реализована в виде визуальных высокоуровневых средств, понятных людям, слабо знакомым с технологиями программирования. Такие средства задают целостную внутреннюю архитектуру сложной информационной системы. Лучшие подобные решения поддерживают прямую и двустороннюю связь модели и программного кода. Из модели можно автоматически получить исходный код, а из исходного кода — визуальную модель. В результате удается плавно состыковать этап выработки и согласования требований с этапом кодирования и формирования исполнимого приложения.
В крупных проектах с моделью обычно работает не программист, а системный аналитик. Он отвлекается от мелких деталей реализации (программирования-в- малом) и перестает мыслить в терминах отдельных операторов языка программирования. Хороший проектировщик способен охватить и продумать структуру больших функциональных логических блоков и основные взаимосвязи между ними. Далее подобная модель детализируется и транслируется в исходный код на выбранном языке.
Архитектура (приложения), управляемая моделью, позволяет быстро создавать объемные программные системы разной функциональной направленности. У заказчика появляется возможность инвестировать основные средства не в конкретные технические решения, а, прежде всего, в реализацию требуемой логики приложения на модельном уровне, которая потом может почти автоматически развертываться на разных платформах. При этом достигается высокий уровень сохранности инвестиций. Ведь систему, созданную с помощью MDA, при появлении новых технологий можно адаптировать к ним с минимальными усилиями по модификации модели и на высоком абстрактном уровне.
Важной характеристикой готового приложения MDA считается хранение модели внутри него самого. Таким образом, в ходе выполнения приложение по-прежнему (как и на этапе проектирования) управляется моделью.
Технология MDA ориентирована на создание приложений, которые независимы от платформы, операционной системы и языков программирования. Она позволяет строить масштабируемые приложения из компонентов, которые могут использоваться повторно и многократно. Сам процесс разработки выполняется, как явствует из названия, под управлением модели.
Модель приложения — это взаимосвязанный набор визуальных диаграмм, наглядно описывающих внутреннюю структуру системы и принципы ее функционирования. Модель приложения не привязана к конкретному языку или конкретной среде программирования.
Визуальные диаграммы чаще всего строятся с помощью унифицированного языка моделирования UML (Unified Modelling Language). Его стандарт разработан группой OMG для задач объектно-ориентированного проектирования.
Модельные точки зрения и модели MDA
Когда разработчик взаимодействует с создаваемой системой в рамках подхода MDA, ему доступны три модельные точки зрения на систему.
Точка зрения, независимая от вычислительных особенностей, Computation Independent Viewpoint (CIV) нацелена на анализ системной среды и системных требований. Логика работы пока что не рассматривается — возможно, она еще не определена. В CIV учитываются программные, аппаратные и другие технологические ограничения, которые не связаны напрямую с внутренней архитектурой разрабатываемой программы.
Точка зрения, независимая от программно-аппаратной платформы, Platform Independent Viewpoint (PIV) задает конкретные функциональные элементы системы, не зависящие от платформы. В ее рамках используются языки моделирования наподобие UML.
Точка зрения, зависящая от платформы, Platform Specific Viewpoint (PSV) задает детали реализации, зависящие от используемых платформ и языков программирования. Она сочетает платформно-независимую логику с дополнительными особенностями, вызванными использованием конкретной платформы или системы.
Каждая из этих точек зрения предлагает разные средства построения соответствующих моделей. Бизнес-аналитики, не знакомые с особенностями программной разработки, вырабатывают свои требования к системе на уровне CIV — с помощью так называемых вычислительно-независимых моделей (Computation Independent Model, CIM). Модели CIM задают основные требования к проекту и включают словари предметной области. Никакие технические характеристики в этих моделях не фиксируются.
Модели, описывающие конкретные структуры и поведение системы, создаются с помощью языка UML. Они также не связаны с конкретной системой разработки и выполнения и называются в терминологии MDA платформно-независимые модели (Platform Independent Model, PIM).
Концепция MDA не ограничивается построением абстрактных моделей — она позволяет связать моделирование с конкретными средами разработки. Для этого в MDA введено понятие платформно-специфичных моделей (Platform Specific Model, PSM). Они определяют способ работы системы и ее элементов на конкретной программной платформе. Модели PSM обычно дополняют модели PIM, уточняя детали их реализации. На их основе удается автоматически генерировать значительный объем итоговых исходных текстов проекта (а нередко — и все 100%), реализующих структуру и логику поведения модели, описанной на языке UML. Модели PSM также нередко используются как готовые элементы сопроводительной документации.
Проектирование прикладной системы во многих случаях сводится к формированию и постепенному уточнению иерархии классов и описанию последовательности смены внутренних состояний объектов, их реализующих. Средства MDA позволяют визуально представлять структуру пространства объектов, хранить его в базе данных и привязывать процедуры ее модификации к пользовательскому интерфейсу.
В рамках подхода MDA желательно моделировать и мелкие детали системы (на уровне программирования-в-малом). В частности, можно задавать логику работы отдельных методов классов с помощью визуальных диаграмм (блок-схем). Однако такая деятельность требует определенного опыта и специфических навыков и пока слабо распространена.
Мощные возможности моделирования на наших глазах становятся доступными в массовых продуктах программирования, в частности в версии Borland Developer Studio 2006, которая предлагает средства генерации кода на основе моделей UML. При этом она поддерживает и обратный процесс (реинжениринг) построения моделей на основе исходных текстов проекта и существующих структур баз данных.
Продукты, поддерживающие технологии MDA, на рынке существуют, однако отличаются, как правило, весьма высокими ценами в сравнении со средой Borland Developer Studio.
Вопросы
Расскажите о технологии MDA.
Что такое модель?
Что такое реинженеринг?
ГЛАВА 49
История программной инженерии
Понятие жизненного цикла программного продукта рассматривалось в главе 47. Вопросы, связанные с организацией такого цикла и повышением эффективности его этапов, изучает область знаний, которая называется программная инженерия. Она объединяет технологии самых разных сфер знаний: компьютерных, кибернетических, проектных, инженерных и других.
Зарождение программной инженерии
Термин «программная инженерия» использовался от случая к случаю в статьях на тему кибернетики в конце 1950-х годов, когда первые ЭВМ получили относительно широкое распространение. В то время разработчики использовали для создания программ преимущественно ассемблер (символически записываемые команды машинного языка) и простые языки программирования наподобие Фортрана.
Процесс общения программиста с компьютером начинался с подготовки текста программы на бумажных перфокартах. Набор перфокарт выполнялся в так называемом пакетном режиме. Оператор загружал в ЭВМ множество таких пакетов. Компьютер поочередно запускал их на выполнение и выводил результат работы каждой программы на общий принтер. В результате ожидание результата единственного исполнения программы могло затягиваться на часы, а то и дни, а время работы ЭВМ стоило очень дорого.
Вопросы повышения эффективности труда первых программистов решались в основном путем создания макроассемблеров (усовершенствованных компиляторов). Они позволяли объединять группы машинных команд в именованные макрокоманды с параметрами. Наряду с макроассемблерами постепенно входили в обиход интерпретирующие системы. Они организовывали интерактивный процесс общения разработчика с ЭВМ в реальном времени и существенно снижали продолжительность цикла «ввод текста программы — получение результатов ее работы». Однако главным препятствием на пути интерактивных систем оставалась дороговизна машинного времени.
По мере развития компьютерной отрасли и массового распространении дешевеющих компьютеров отчетливо проявились сложности в организации эффективного труда программистов. Деятельность по созданию программ поначалу казалась весьма необычной: чем сильнее росла сложность приложений, тем интенсивнее увеличивалось в них число ошибок. Возникла острая потребность в
доступных методах разработки сложных программных систем, обеспечивающих одновременно удовлетворительные проектные сроки и хорошее качество итогового продукта.
На первых порах процесс создания программного продукта часто приравнивался к процессам создания физических систем. И в том и другом случае имеются такие общие проектные критерии, как стоимость работ, сроки выполнения, измеряемая производительность труда и качество результата. В результате к процессу разработки программного обеспечения применимы классические проектные подходы. Объем создаваемого приложения удается точно измерить. Чаще всего он выражается в строках (или операторах) исходного кода. Достаточно несложно рассчитать и качество продукта, например по числу выявленных ошибок.
Потребность в хороших методах разработки крупных программных систем была более-менее успешно удовлетворена к концу 1960-х годов. Именно тогда в официальный обиход вошел термин «программная инженерия». Он был предложен в 1968 году немецким профессором математики Фридрихом Бауэром (Friedrich Bauer), придумавшем идею программного стека. Профессор в те годы активно участвовал в создании языка программирования Алгол 60, прародителя множества современных универсальных языков, в том числе Паскаля и Си. С 1969 года он возглавлял Мюнхенский технологический университет.
Впервые Бауэр использовал термин «программная инженерия» на конференции НАТО по программированию, прошедшей в 1968 году в немецком городе Гармиш. К тому времени уже появились известные и по сей день языки программирования (Алгол, Фортран). Завоевывали популярность оптимизирующие трансляторы, развивалась теория компиляции, автоматизировались технические аспекты создания программ. Все более массово обсуждались вопросы управления программными проектами. К концу 1960-х годов в крупнейших проектах уже объединялось до тысячи программистов. Бюджеты таких работ достигали десятков миллионов долларов — объем, внушительный и по современным меркам. Однако к тому времени не существовало технологий надежного проектирования, кодирования и тестирования сложных программных систем. Внимание в основном уделялось средствам коллективной работы в сети. Процесс программирования, как и двадцать лет назад, представлял собой простую последовательность шагов «написал код — исправил ошибку».
Модель «водопад»
Первой более-менее продуманной и эффективной моделью разработки программного обеспечения считается модель «водопад». Эта модель представляет собой набор последовательных этапов анализа требований заказчика к продукту, проектирования системы, кодирования, сборки, тестирования, развертывания и сопровождения. К следующему этапу разработчики в этой модели приступают, только когда полностью закончен предыдущий.
«Водопад» был предложен в 1970 году компьютерным автором Уинстоном Ройсом (Winston Royce). Он опубликовал идею «водопада» в статье «Управление созданием больших программных систем», опубликованной в рамках крупной выставки электронного оборудования IEEE WESCON. Ройс впоследствии сфор-
мулировал и концепцию жизненного цикла программного продукта. Интересно, что Ройс не был особым приверженцем модели «водопада». Он отмечал, например, что последовательность шагов этой модели надо применять минимум два раза. Фактически Ройс выдвинул идею более универсальной и популярной сегодня итеративной модели разработки программ. Однако в историю он вошел именно как автор морально устаревшего «водопада».
Сильной стороной «водопада» считается формально провозглашенная важность явных этапов проектирования, тестирования и других. А слабой стороной — независимость всех этапов. В реальных проектах требования заказчика в ходе работы часто меняются, но модель «водопада» не позволяет учесть такие изменения. Она эффективна, только если все пожелания к функциональным возможностям сформулированы заранее.
Переход к гибким методологиям
В 1980-х годах появились первые персональные компьютеры. Профессия программиста быстро превращалась из элитной в массовую. Тогда же появились серьезные методологические разработки в сфере программной инженерии. Наиболее известная из них — модель зрелости программных процессов Capability Maturity Model (СММ). Она была разработана институтом программной инженерии при университете Карнеги-Меллона при активном спонсорстве министерства обороны США, информационные проекты которого и по сей день остаются крупнейшими в мире. Модель СММ детально охватывает все этапы и шаги процесса создания крупных программных систем. Но внедрение этой методологии требует длительной многолетней подготовки и подразумевает дорогостоящую сертификацию. Кроме того, в модели СММ имеются явно выделенные этапы работ (анализ требований, проектирование и так далее). Тем самым она напоминает модель «водопада» со всеми ее минусами.
В 1990-е годы популярность компьютеров стремительно росла, а цена снижалась. Быстро увеличивалось число крупных программных проектов, которые разворачивались на персональных компьютерах. Классические методологии разработки, требующие длительного внедрения, не могли удовлетворить потребность малых и средних фирм в подходах к групповой разработке программного обеспечения. Возникла массовая потребность в методологических решениях, которые не требуют долгого внедрения и при этом хорошо адаптируются к сложившейся корпоративной культуре. Такие подходы также должны были ориентироваться на создание систем, требования к которым быстро и постоянно меняются уже в ходе программирования. Ситуациям, когда заказчики обычно плохо представляют конечный вариант продукта и не способны сформулировать все требования заранее, отвечают так называемые гибкие {agile) методологии. Из них наиболее известны экстремальное программирование и SCRUM.
В гибких методологиях нет разделения всего проекта на явные этапы, как в «водопаде». Работа ведется небольшими итерациями продолжительностью в одну- две недели. Каждая из итераций, в свою очередь, включает полный цикл «анализ требований — проектирование — кодирование — отладка», только в миниатюре. На выходе итерации гибкого процесса получается работоспособный релиз. В нем
воплощен некий набор функциональных возможностей, которые заказчик может сразу использовать в работе.
До возникновения гибких методологий существовали промежуточные модели — так называемая спиральная разработка. Она фактически и подразумевалась Уинстоном Ройсом, когда он говорил о «водопаде». В спиральных моделях цикл от проектирования до отладки повторяется несколько раз. Однако в них не существует тесного слияния проектной деятельности исполнителя и заказчика, как при гибких подходах. В последних же разработано множество приемов, целенаправленно ориентированных на непрерывное взаимодействие всех участников проекта.
Существенное влияние на развитие гибких методологий оказали идеи объектно-ориентированного программирования. Они упростили деятельность по проектированию и модификации крупных систем. Еще одним фактором, стимулировавшим появление гибких подходов к созданию программ, стало развитие Интернета и мобильных технологий. Возможность работы над международными проектами в виртуальном сетевом окружении и реальном времени снимает последние грани между заказчиком и исполнителем. Но при этом особую важность приобретают средства организации в Сети коллективного труда над программным кодом. Пока они развиты слабо.
Современное состояние программной инженерии
Согласно стандартному определению института инженеров по электротехнике и радиоэлектронике IEEE 610.12 (глоссарий терминов программной инженерии), программная инженерия изучает применение инженерных (систематических, упорядоченных, количественно измеряемых) подходов к разработке, управлению и сопровождению программного обеспечения.
Программная инженерия имеет ряд отличий от большинства классических областей технических знаний. В ней одновременно объединяется множество подходов из разных сфер: от математики до психологии. Так, область программирования неразрывно связана с кибернетической теорией алгоритмов. Для создания эффективных оптимизирующих компиляторов также нужные серьезные математические наработки. Неоднократно предпринимались попытки разработать методику построения безошибочных программ, смысловая корректность которых может быть формально доказана. Пока, правда, подобные решения в силу высокой сложности позволяют создавать надежное и безошибочное программное обеспечение относительно небольшого объема (до десяти тысяч операторов).
Сетевые продукты, повышающие эффективность работы программ, взаимодействующих друг с другом, используют научные подходы для оптимизации сложных систем. Создание системного обеспечения для разных компьютерных архитектур требует изучения физических принципов функционирования оборудования. Принципы сертификации программных продуктов, ведение проектной документации и комментирование исходного кода относятся к классической инженернотехнической сфере. Выбор оптимальной последовательности шагов при создании программы, сквозной контроль качества, вопросы эффективности труда разработчиков, этапы анализа, разработки и тестирования тесно связаны с аналогич-
ными темами, исследуемыми в современной промышленности и на производстве. Ведение проекта, в котором занято от десятков до тысяч программистов, подразумевает хорошие управленческие навыки, знание современных правил проектного менеджмента. Высокая интеллектуальная нагрузка на разработчиков подразумевает проведение психологических исследований и изучение опыта подготовки артистов, музыкантов, спортсменов. Процесс проектирования сложной архитектуры, кодирования запутанных алгоритмов и разработки удобного, интуитивно понятного пользовательского интерфейса можно отнести к искусству, где основную роль играют вдохновение, опыт и талант.
Сегодня программная инженерия развивается, прежде всего, как инженерная область деятельности. По всему миру вводится множество программ сертификации, внедряются типовые технологические инструменты и системы стандартизации процесса разработки. Совершенствуется программная инженерия и как сфера проектного менеджмента. В ближайших версиях популярных сред разработки Borland и Microsoft ожидается появление развитых средств управления проектами. Ведутся работы над прикладными системами, поддерживающими формальные методы доказательства правильности программ. Для этого, в частности, активно задействуются достижения теории автоматов — последняя версия среды Borland Developer Studio поддерживает автоматные диаграммы машин состояний. В ближайшие годы ожидаются прорывы по направлениям когнитивной психологии, изучающей механизмы человеческого познания. Они приведут к появлению простых и эффективных пользовательских интерфейсов, упростят работу по визуальному проектированию, кодированию, модификации исходных текстов и навигации по ним.
Структура жизненного цикла программного обеспечения в среде Borland
Жизненный цикл приложения, как уже говорилось, состоит из нескольких этапов.
Сначала формируются требования к системе. Они обычно вырабатываются в ходе общения с заказчиком или с потенциальными покупателями.
Затем эти требования анализируются и систематизируются.
Далее начинается этап проектирования. Готовится модель будущего продукта, например в виде наборов диаграмм UML. При необходимости формируется структура базы данных.
На следующем этапе выполняется кодирование и другие шаги непосредственной разработки программы.
Далее, когда приложение запрограммировано, его требуется протестировать в разных условиях эксплуатации. На этом этапе устраняются выявленные ошибки, улучшаются функциональные характеристики.
Затем следует этап развертывания приложения. Эта задача сама по себе может быть достаточно трудоемкой, особенно если приложение взаимодействует с базами данных, эксплуатируется на веб-сервере или реализовано в многоуровневой архитектуре.
Завершается жизненный цикл обычно этапом сопровождения. В его рамках организуется техническое обслуживание, устраняются в ходе эксплуатации выявленные недочеты, оказываются консультации и проводится обучение.
Нередко необходим и этап вывода программного продукта из эксплуатации. Если, например, на смену морально устаревшей системе приходит другой продукт, надо выполнить переход максимально незаметно для конечных пользователей и при этом аккуратно перенести все исходные данные в новое приложение.
Все этапы жизненного цикла программного обеспечения охватывает сквозное управление работой сотрудников. Специфика их деятельности в рамках разных этапов сильно различается. Тем не менее, многие типичные приемы управления персоналом остаются неизменными.
Продукты Borland полностью охватывают все вышеописанные части жизненного цикла программного обеспечения. Для этого корпорация разработала концепцию Application Lifecycle Management (ALM, управление жизненным циклом приложения). Руководители Borland используют ее в качестве стратегического принципа при создании и совершенствовании всех своих продуктов.
Обработка требований организована с помощью линейки продуктов Borland Caliber. Эти продукты фиксируют требования смысловой направленности, контролируют изменения в требованиях и взаимосвязи между ними, отслеживают историю версий. Такая деятельность выполняется на базе централизованного репозитория требований, к которому обеспечивается коллективный доступ.
К положительной особенности систем Caliber следует отнести способность хранения в репозитории любых сопроводительных документов, связанных с базой требований. Использование продуктов Caliber существенно упрощает синхронизацию версий проектных документов. В этих продуктах имеются также развитые средства анализа требований и генерации всевозможных отчетов.
За построение моделей и проектирование архитектуры системы отвечает продукт Borland Together. Он встроен в среду Borland Developer Studio в 2006 году.
В качестве средств разработки программистам наряду с системой C++Builder доступны следующие компоненты.
Среда программирования BorlandJBuilder для языка Java.
Среда программирования Borland Delphi для языка Delphi.
Среда программирования Borland C#Builder на языке C# для платформы .NET. Эта среда, как и Delphi, также встроена в Borland Developer Studio.
Сквозное управление коллективной работой и контроль версий и изменений исходных текстов и других файлов проекта (так называемое конфигурационное управление') обеспечивается системой Borland StarTeam. Ее клиентская часть теперь тесно интегрирована со средой Borland Developer Studio. Контролю поддаются не только исходные файлы с кодом, но и целые проекты и группы проектов. В контекстном меню пунктов проектов и групп проектов Менеджера проектов имеется раздел StarTeam.
В нем сосредоточены функции версионного контроля. С системой StarTeam состыкован Менеджер истории файлов C++Builder. Он позволяет использовать возможности StarTeam для ведения как коллективной, так и локальной истории файлов.
Семейство серверных решений Borland Optimize.lt занимается тестированием и оптимизацией созданных приложений. В оболочку Borland Developer Studio также встроены популярные системы тестирования DUnit (для платформы Win32) и NUnit (для пратформы .NET). Они автоматизируют процесс формирования проекта на базе тестов. Для этого используется ряд подходов из методологии экстремального программирования, подразумевающей гибкое, адаптивное создание программы на основе тестов, которые выдвигает заказчик.
За хранение данных отвечают СУБД Borland InterBase и мобильная переносимая СУБД JDataStore, написанная на языке Java. Сервер приложений Borland Enterprise Server решает задачи развертывания и организации надежной и масштабируемой работы многоуровневых приложений. Набор технологий Janeva упрощает стыковку приложений, написанных для платформ .NET, J2EE и CORBA.
Все продукты Borland открыты и совместимы с продуктами сторонних разработчиков и даже прямых конкурентов. В частности, выпущена версия Borland Together для системы программирования Microsoft Visual Studio .NET. Средства Borland ориентированы на самые разные платформы: Windows, .NET, Linux и другие. Проекты, создаваемые на базе решений Borland, легко расширяются новыми функциональными возможностями и хорошо масштабируются под растущие требования заказчика.
Вопросы
Чем занимается программная инженерия?
Как устроена модель разработки «водопад»?
Как устроена спиральная модель разработки?
Чем характерны гибкие модели разработки?
Дайте стандартное определение программной инженерии по версии IEEE.
Какие продукты Borland охватывают разные этапы жизненного цикла программного обеспечения?
ГЛАВА 50 Технологии контроля версий и организации групповой работы
При коллективной работе над проектами важную роль играет согласование действий отдельных разработчиков. Средства контроля версий и организации групповой работы позволяют повысить как качество сотрудничества, так и производительность труда группы.
Диспетчер версий
Удобная и полезная возможность сравнения разных версий файлов реализована в среде C++Builder с помощью Диспетчера версий (History Manager). Он хранит историю локальных изменений текстовых файлов и их множественные версии. Диспетчер версий отслеживает изменения в файлах, содержащих исходные тексты не только на языках программирования C++, Delphi и С#, но и на языках разметки HTML и других. Он визуально представляет различия между версиями файлов*.
Диспетчер версий начинает работать, если в среде C++Builder включен режим создания копий исправленных файлов. Для этого надо обратиться к настройкам редактора C++Builder командой Tools > Options > Editor Options (Сервис > Параметры > Настройки редактора). В диалоговом окне следует установить флажок Create backup files (Создавать резервные копии файлов).
Содержимое журнала доступно на закладке History (Журнал) в окне редактора C++Builder. Для возврата к редактированию исходного текста программы следует выбрать закладку Code (Исходный текст) — рис. 50.1.
В журнале в списке Revision content (Содержание модификаций) представлены версии текущего файла. В столбце Rev. (Версия) хранится номер версии. Автоматическая последовательная нумерация начинается с единицы. В столбце Label (Категория) показан способ хранения версии файла-, используются значения Local backup (Локальная резервная копия) и Local file (Текущий файл). В столбце Data (Дата) показаны дата и время последней модификации. В столбце Author (Автор) указан пользователь, создавший данную версию файла (файл может модифицироваться разными пользователями, хотя его одновременное редактирование разными людьми запрещено). При выборе любой строки списка Revision content (Содержание модификаций) содержимое соответствующей версии файла отображается на отдельной панели.
Диспетчер версий — это персональное средство контроля версий файлов. Он не рассчитан на отслеживание изменений, которые внесены в один файл разными разработчика
ми.
StyTeam jHStyTeam jH► - It 5)► - It 5)i* • фi* • фД Welcome Page ® Unitl.cpp© ^ ^ w+ 6^ ; * funiu.cppД Welcome Page ® Unitl.cpp© ^ ^ w+ 6^ ; * funiu.cpp7J7JRevision contentRevision contenta © © ©10a © © ©10Fife~9~~7~~6~Fife~9~~7~~6~Label ’’ Local fileLocal backup Local backup Local backup Local backupLabel ’’ Local fileLocal backup Local backup Local backup Local backup21.10.200610:57:5420.10.2006 11:55:4620.10.2006 9:40:2420.10.2006 9:40:0020.10.2006 9:39:3221.10.200610:57:5420.10.2006 11:55:4620.10.2006 9:40:2420.10.2006 9:40:0020.10.2006 9:39:32AuthorAuthor•f astral 1 TForml: : ButtonlClick (TObject•f astral 1 TForml: : ButtonlClick (TObject■.Send^;■.Send^;
StyTeam jH
StyTeam jH
► - It 5)
► - It 5)
i* • ф
i* • ф
Д Welcome Page ® Unitl.cpp
© ^ ^ w+ 6^ ; * funiu.cpp
Д Welcome Page ® Unitl.cpp
© ^ ^ w+ 6^ ; * funiu.cpp
7J
7J
Revision content
Revision content
a © © ©
10
a © © ©
10
Fife
~9~
~7~
~6~
Fife
~9~
~7~
~6~
Label ’’ Local file
Local backup Local backup Local backup Local backup
Label ’’ Local file
Local backup Local backup Local backup Local backup
21.10.200610:57:54
20.10.2006 11:55:46
20.10.2006 9:40:24
20.10.2006 9:40:00
20.10.2006 9:39:32
21.10.200610:57:54
20.10.2006 11:55:46
20.10.2006 9:40:24
20.10.2006 9:40:00
20.10.2006 9:39:32
Author
Author
•f astral 1 TForml: : ButtonlClick (TObject
•f astral 1 TForml: : ButtonlClick (TObject
■.Send^;
■.Send^;
# Projectl - Borland Developer Studio 2006 • Unitl.cpp
fife Edit Search yew Refactor Project &un Component look
ListBoxl->Items->Add(
XHLDocumentl->DocumentElement->
ChildNodes-> Nodes[3] ->GetNodeType() );
<21 Октябрь 2006 г. 10:57:54 Unitl.cpp ~~| Contents | Info Diff<21 Октябрь 2006 г. 10:57:54 Unitl.cpp ~~| Contents | Info DiffAA» e Ш» e Ш0; 0 Read only0; 0 Read onlyi Unitl.h Design HistoryMHi Unitl.h Design HistoryMH
<
21 Октябрь 2006 г. 10:57:54 Unitl.cpp ~~| Contents | Info Diff
<
21 Октябрь 2006 г. 10:57:54 Unitl.cpp ~~| Contents | Info Diff
A
A
» e Ш
» e Ш
0; 0 Read only
0; 0 Read only
i Unitl.h Design HistoryMH
i Unitl.h Design HistoryMH
Puc. 50.1. Режим просмотра версий текущего файлас исходным текстом
Диспетчер версий позволяет сделать любую из доступных версий файла текущей. Для этого в контекстном меню строки списка Revision content (Содержание модификаций) надо выбрать команду Revert (Откат) и подтвердить операцию. Выбранная версия файла загружается в редактор уже в качестве основной. Все изменения, сделанные после создания файла, используемого для отката, безвозвратно теряются.
Физически копии файлов хранятся в скрытой папке каталога проекта с именем history. У файлов версий имеется дополнительное расширение имени, совпадающее с номером в столбце Rev. (Версия). Например, самая первая версия может храниться в файле с именем WinForm.cpp.~1~.
Представление информации на панели журнала определяется выбранной закладкой. Просмотр списка версий выполняется на закладке Contents (Содержание). Закладка Info (Информация) позволяет просмотреть краткие сведения о каждой версии файла в отдельном окне.
0.1.1. Визуальное сравнение содержимого файлов
Важная закладка журнала Diff (Различия) поможет наглядно увидеть различия между версиями файлов.
При выборе закладки Diff (Различия) панель журнала разделяется на три части. В списке Differences From (Отличия между) показаны файлы, выбранные в качестве исходных. В списке То (И) представлены файлы, с которыми возможно сравнение.
•^ Projectl Borland Developer Studio 2006 -Unitl .cpp .. l2fclBl
gle Edit Search View Refactgr Project Run Component loots StgrTeam 1 ®
^- Welcome Page Щ Unitl .cpp
13 i-Ж ^ ^-' Muniti.cpp
Differences From;_ To:
f"j Rev, * DateT Г Rev. ! Date wj
0~14~21,10.2006 10:22:30jO"<4"'21.10.2006 10:22 30
1$~13~20.10.2006 12:07:38"|Ф~13~20.10,2006 12:07 3В/
1ф~12~20.10.2006 12:06:02tO^12-"20.10.200612:06:02
]$~11~20.10.2006 11:59:04^j®~I1~20.10.2006 11:59:04^
■ void fastcal1 TForml::ButtonlClick(TObject *Send^|:
4"
ФLi3tBoxl->Items->Add(
-ФXMLDocumeniil-->Docuj№ntEleK№nt->
5» ChildNodes~> Nodes[3] ->GetNodeType () );
ксточтск XNL-хвжямк;•
w XMLTransfortol->Sourde5imlFile a "d:\M.jttnl0;
** // upwere арво6р49о&акилг . ‘, r" v
, 3 differences found Diff from File to ~13~ Unitl.cpp
Contents 1 Info" I Diff [/ ф ^'26: 1 Read onlyContents 1 Info" I Diff [/ ф ^'26: 1 Read only; ■ Unitl.h, Design AHIstorytiN ,; ■ Unitl.h, Design AHIstorytiN ,
Contents 1 Info" I Diff [
/ ф ^'26: 1 Read only
Contents 1 Info" I Diff [
/ ф ^'26: 1 Read only
; ■ Unitl.h, Design AHIstorytiN ,
; ■ Unitl.h, Design AHIstorytiN ,
Puc. 50.2. Анализ различий между разными версиями файла
В списке То (И) может быть представлена и текущая версия открытого файла. Она обозначена словом Buffer (Буфер) и помечена значком в виде глаза. Эта строка представляет текущее состояние файла в редакторе, пока что не сохраненное на жесткий диск.
Для сравнения файлов следует выбрать нужные строки в списках Differences From (Отличия между) и То (И). Различия между выбранными файлами отображаются на отдельной панели. Строки первого файла, отсутствующие во втором, помечены знаком +. Оригинальные строки второго файла помечены знаком -.
Дополнительное внимание к отличиям привлекает цветовая подсветка. Так можно быстро проверить все изменения и сравнить редакции файлов. Если файлы велики по объему и почти одинаковы, удобно использовать кнопки Go to next difference (Переход к следующему отличию) и Go to previous difference (Переход к предыдущему отличию).
Взаимодействие с системами управления версиями исходных текстов
Идея совместной сетевой работы над программными проектами возникла еще в ходе первых экспериментов по организации связи между компьютерами. Они были проведены в Массачусетском технологическом институте в конце 1950-х годов. Концепция групповой работы получила массовое признание с появлением
недорогих модемов для настольных ПК. Развитие электронной почты и глобальной сетевой инфраструктуры дало возможность людям из разных городов и стран объединяться для совместной работы над приложениями произвольной сложности2. Важной составляющей успешного группового проекта считается использование системы контроля версий проектных файлов.
Система контроля версий проектных файлов — это сетевая программная система, которая регистрирует версии файлов проекта в единой базе, отслеживает изменения, которые вносят в эти файлы участники проекта, и позволяет корректно согласовывать версии файла, модифицированного разными пользователями.
На компьютере каждого разработчика, подключенного к системе контроля версий, хранится автономная копия файлов проекта, которые он может модифицировать. Над крупным проектом обычно одновременно трудится множество сотрудников, поэтому постоянно возникает необходимость согласования локальных версий файлов с их основной копией в центральной базе. Наработки, сделанные конкретным программистом, надо быстро передать всем другим участникам проекта, а в локальной копии файла, соответственно, требуется учесть все сторонние изменения. Классическим решением этой проблемы считается организация централизованного сервера. На нем хранится эталонный вариант проекта, а программисты периодически к нему обращаются, синхронизируя локальные копии.
Служба уведомлений следит за вносимыми в проект модификациями и автоматически сообщает разработчикам о необходимости обновить определенный файл. Различия в версиях одного файла у конечного пользователя обычно представлены в редакторе системы контроля версий. Неодинаковые строки и более мелкие фрагменты текста подсвечиваются. Развитые продукты контроля версий автоматически выявляют синтаксические противоречия в вариантах кода и стараются разрешить их без участия человека, уточняя лишь неоднозначные моменты. В будущем в таких системах появятся средства визуализации логической структуры приложений и взаимосвязей между классами.
Массовое распространение широкополосного доступа к Интернету стимулировало работу над программными проектами в реальном времени. Сегодня уже не редкость одновременная деятельность групп программистов в виртуальном офисе. Востребованными оказались Интернет-системы коллективной проектной работы, ранее применявшиеся только в локальном сетевом окружении. С их помощью разработчики могут одновременно редактировать, например, один и тот же файл, если в нем сосредоточены описания большого количества функций. Запрет одновременного доступа нескольких человек к одному файлу считается сегодня плохим решением. Вместо блокировки доступа хорошая проектная среда предложит развитые средства раздельного редактирования и слияния элементов одного текста с учетом всех затронутых взаимосвязей.
На сайте SourceForge.Net, самом популярном у разработчиков общедоступных программ в открытых текстах, в октябре 2006 года велось 132 тысячи проектов, объединяющих программистов из разных стран. Только зарегистрированных членов сообщества SourceForge.Net насчитывается 1,42 миллиона человек.
Сегодня самой распространенной общедоступной системой контроля версий считается среда CVS (Concurrent Versions System). На ее основе, в частности, построена вся структура сайта SourceForge.Net. Система эта довольно аскетична по возможностям и интерфейсу, хотя существует ее графическое расширение WinCVS. Тем не менее, среда CVS завоевала у разработчиков очень большую популярность. Некоторые провайдеры даже предлагают виртуальный хостинг проектов с поддержкой CVS.
Контроль версий в системе C++Builder
Рассмотренный выше Диспетчер версий C++Builder полезен, когда программист работает в одиночку. Однако любой крупный проект подразумевает групповую, коллективную работу программистов. Каждый из них нередко использует собственную, локальную копию всех файлов проекта. В такой ситуации важно обеспечить согласование всех файлов проекта — всех их версий, имеющихся у разных разработчиков. Эту работу часто берут на себя самостоятельные продукты, например системы CVS, Rational ClearCase или Microsoft Visual SourceSafe. Их несложно настроить на проекты, создаваемые средой C++Builder.
В основу систем контроля версий заложен репозиторий проектов, к которому подключены клиентские приложения. Каждый проект сохраняется не на локальном диске разработчика, а в общем репозитории. В нем хранятся не только последние версии файлов, но и их предыдущие варианты. Это позволяет при необходимости возвращаться назад, к ранним состояниям проекта. Программист со своего рабочего места, чаще всего непосредственно из среды разработки, может в любой момент обратиться к репозиторию службы контроля версий. Он может отправить в репозиторий локальную версию файла проекта или, наоборот, запросить из репозитория текущие файлы на свой компьютер. И в том, и в другом случае выполняется согласование содержимого файлов. Если один и тот же модуль проекта изменялся несколькими людьми, необходимо синхронизировать его содержимое, учесть все модификации, которые могут оказаться противоречивыми.
При работе со средой C++Builder задачи контроля версий удобнее всего решать с помощью продукта StarTeam. Эта система контроля версий Borland тесно интегрирована со средой C++Builder. Она работает по вышеописанной схеме. Копии всех файлов проекта C++Builder и всех их модификаций записаны в централизованное хранилище. Каждый из программистов работает с локальной копией проекта, загруженной в его версию системы C++Builder. Время от времени программисты могут отправлять файлы на сервер или, наоборот, загружать к себе новые версии. При этом могут возникать конфликты. Такое бывает, если, например, один файл (его локальные копии) был модифицирован разными людьми. Все выявленные различия необходимо аккуратно согласовать.
Работа с системой StarTeam
Хранение файлов проекта и контроль версий обеспечивает сервер StarTeam. Он входит в состав среды C++Builder. Для работы с системой сервер должен быть запущен заранее.
Взаимодействие с системой StarTeam. Открытый в среде C++Builder проект первоначально помещают в репозиторий StarTeam командой StarTeam > Place Project (StarTeam > Разместить проект). В открывшемся диалоговом окне выбирают доступный сервер StarTeam. Здесь же вводят имя проекта и рабочий каталог для хранения версий файлов. Впоследствии проект, сохраненный в репозитории StarTeam, можно загрузить в среду C++Builder командой StarTeam > Pull (StarTeam > Принять проект).
Если соединение с сервером StarTeam установлено, перенастроить рабочие каталоги проекта можно в любой момент. Чтобы система StarTeam зарегистрировала изменение структуры каталогов, следует дать команду StarTeam > Manage Associations (StarTeam > Управление связями) и нажать кнопку Edit (Редактировать).
Настройку каталогов выполняют в открывшемся диалоговом окне. Это же окно позволяет отключиться от сервера StarTeam кнопкой Disconnect (Разорвать соединение).
Быстрое обновление проекта. Для быстрой загрузки в среду C++Builder обновленных файлов проекта из репозитория StarTeam служит команда StarTeam > Update Project (StarTeam > Обновить проект). Обратная процедура — перенос локальных файлов проекта C++Builder на сервер StarTeam — осуществляется командой StarTeam > Commit Project (StarTeam > Передать проект). При этом в репозитории StarTeam создается новый набор файлов проекта.
Контроль над процессом загрузки. На практике загрузку файлов проекта из репозитория StarTeam следует выполнять под строгим контролем, так как локальный и загружаемый файл могут иметь нежелательные отличия. Команда StarTeam > Check Out (StarTeam > Проверка и прием) загрузит из репозитория в текущий проект файлы, в которых обнаружены изменения. Команда StarTeam > Check In (StarTeam > Проверка и передача) передаст в репозиторий локальные файлы текущего проекта, подвергшиеся модификации. При этом пользователь увидит список обновленных файлов проекта в окне StarTeam среды C++Builder. Взаимосвязи между ними представляют в виде списка — с помощью кнопки List Display (Показать список) — или в виде дерева — с помощью кнопки Tree Display (Показать дерево). В обоих случаях файл, который не следует обновлять, можно исключить из процесса обмена, сняв флажок с соответствующего элемента списка.
Загрузить из репозитория можно и более раннюю версию файла. Для этого надо дать команду StarTeam > Revert (StarTeam > Откат). После загрузки прежней версии последние изменения, внесенные в текущий файл проекта, будут утрачены.
Система StarTeam позволяет заблокировать файл — запретить внесение в него изменений другими пользователями. Такой режим эксклюзивной работы с файлом задается командой StarTeam > Lock/Unlock (StarTeam > Установить/Снять блокировку).
Любой из файлов проекта можно сравнить с редакцией, которая хранится в репозитории. Для этого в окне StarTeam надо нажать кнопку Compare Contents (Сравнить содержимое). В результате откроется окно визуального сравнения Visual Diff (Визуальное представление отличий). В нем показаны все изменения в коде3.
Имеется также возможность получить сводный отчет об этих изменениях.
В окне Visual Diff (Визуальное представление отличий) локальный файл, открытый в редакторе C++Builder, сравнивается с копией, хранимой в репозитории.
V,u.dl MM
! aS 1 У А т ==
Projectl.cpp(Revision: 1.1)
19^' У include < IdTCPConnect ion. hpp>
22
23
Г 29j
;#include ^include ^include ^include ^include ^include :#include
#Include ^include
<DBClient.hpp> <DBGrids.hpp> <Grids.hpp> <xmldom.hpp> <Xmlxform.hpp> <MConnect.hpp> <msxmldom.hpp> <XHLDoc.hpp> <XMLIntf.hpp>
Differences
‘k x
у Visual Diff Chart
120
105
90
75
60
1 ss TForml : public TForm
class TFocml : public TForrn
14
35 '<
37
^
39
4П
41;
42
pub 1ished:
// IDE-managed Component 33;
TButton *Buttonl;
TXMLDocument *XMLDocumentl;
TListBox *ListBoxl;
published:
”4 ■
35 ; 36g
// IDE-managed Compone
fastcall ButtonlClick(TOk 37;
private://User declarations
public://User declarations
fastcall TForml (TComponenf
38; 39 40;
4L 42
private: public:
TButton *Buttonl;
TXHLDocument ^XHLDocumentl;
TListBox *ListBoxl;
void fastcall ButtonlClick(T
;// User declarations
// User declarations fastcall TForml(TComponent*
For Help, press Fl
^LLJ
'encoding: 51 (ANSI-кириллица)/1251 (ANSI- кириллица)
Рис. 50.3. Анализ различий между файлами разных разработчиков
* Project 1 • Borland Developer Studio 2006 - Projectl. cpp
File Ed* Search View Refactor Project Run Component Took StarTeam Window Н^з ^ |ИВИЯИ8Я!1
. ^ ^ T, ^ ’ W i
^ Structure9 x
'C Ump'WandXnC ^j ® т1 О
6 a
j@$ WelcomePage . gjUmtl.cpp tS^ol*1! ^ UKZ.cpp StarTeam
°'” ^ V? ^S s4 /^ !^ <AII Files By Status’ +i
$щ Projectl.bdsproj-ProjectMan... ? X
Contents of Project! \
♦ I Includes
+ '| Functions
£*64bvats ^- r^tfew
^ Object Inspector
9 X
j --Status: Current (8 items]
.Projectl.res
Umtl.cpp Umtl.dfm
Untl.h
UnitZ.cpp
Unit3.o»
Status Loe. Vault Br_ Content... Fite Time Stomp.. Size *! 1
Current
Current Current Current Current Cui rent
Г went
j Change Request; Requsement: Task 1 Topic j
; Property
«Status
■ Locked By .Vault Blanch Content Rev File Time St
Projectl.cpp Curient
02.08 2006 23 10 1 6
Detail ' History I Label j Link j Reference
StarTeam
Checked out version 2 of File "C.\tmp\borland\i З^Новая nanxa\Unitl .cpp"
Checked out version 1 of file "C:\tmp\borlandU 3\Но₽ая nanKa\Unltl .dfm"
Checked out version 1 of file "C:\tmp\bor)and\131>loBdB nanKalProjectLcpcf
Checked out version 1 of fie "C:Ump\boriand\13\Hoeas лапка\ИгЛ2 h".
Pull Project operation ended successfully.
'^bu'ti | StarTeam Г
Template'- ]|
02 08 2006 23-03. 127
04 08.2006 2212
04 08 2006 22 02
02 08.2006 23 03
П4 ПЯ 2006 22 02
file
ProjectGroupl
— @3 Projectl.exe
® BESSES
Si Projectl res
Щ Umtl.cpp Ji! Unitl.dfm
t! j -[E^PrajecL^-P^SD^aEx^-^Tool
—J H^s Tool Palette
*il -Otherfiles '
? X
* C++Builder Projects
♦ C+4BuUder Projects | C++6uflder ■♦ Web Documents
’ C++Builder Projects j WebSnap ♦ C++Builder Projects | WebService < C++Bttider Projects | Webteoker
* C++Builder Projects | Intraweb ♦ C++Budder Projects | ActiveX 4 C++euttderProjects | XML
9 X
Множество гибких средств работы с версиями доступны из клиентской программы StarTeam, пользовательского элемента серверного комплекса StarTeam. Она входит в поставку системы C++Builder и запускается командой StarTeam > View Client (StarTeam > Запустить клиентское приложение). После запуска клиент системы StarTeam встраивается в среду С ++Builder и доступен как одно из ее окон.
Слияние файлов проекта
В процессе групповой разработки приложений нередки ситуации, когда с одним и тем же файлом одновременно работают несколько человек. При этом каждый разработчик вносит в код собственные изменения. В результате возникает необходимость выполнить так называемое слияние файлов.
Слияние файлов — это корректное смысловое объединение всех модификаций файла в единый итоговый вариант.
Результирующий файл записывается в репозиторий системы контроля версий в качестве образца.
ПРИМЕР 50.1
' - «» Использование системы StarTeam для слияния файлов
Пусть имеется проект Projectl, с которым одновременно работает несколько программистов. Он хранится в репозитории StarTeam. В проект, в частности, входит файл Unitl.cpp. С этим файлом работают два программиста.
У каждого из них на компьютере установлена среда C++Builder и имеется собственный экземпляр проекта. Будем считать, что каждый из разработчиков загрузил с сервера файлы проекта и редактирует экземпляр файла Unitl .срр по своему усмотрению. В результате получится три разных варианта файла Unitl .срр: по одному в локальной среде C++Builder на каждом компьютере и еще один в репозитории. Процесс слияния начнется, когда один из разработчиков попытается отправить свой экземпляр файла Unitl.cpp в репозиторий StarTeam, например даст команду StarTeam > Check In (StarTeam > Проверка и передача).
Если система StarTeam выявляет наличие расхождений в версиях файлов, она автоматически запускает утилиту Visual Merge (Визуальное слияние). Она действует по так называемому трехдорожечному принципу: имеется базовый вариант файла и две модифицированные версии. Все изменения в них надо согласовать.
Утилита Visual Merge сравнивает содержимое модифицированных файлов между собой и с оригиналом, представленным в репозитории. В окне утилиты представлены как обе модифицированные версии файла, используемые при слиянии, так и результат слияния, который должен быть записан в репозиторий. Разработчик может вручную вносить исправления в окончательный вариант.
Отличия между файлами помечены цветом. Утилита использует разные цвета для выделения добавленных и удаленных строк, а также тех, что представлены в модифицированных файлах по-разному.
Противоречия в тексте версий файлов могут быть простыми, разрешаемыми автоматически, и конфликтными. В последнем случае снять противоречия необходимо вручную. Утилита Visual Merge позволяет переходить к следующему или предыдущему противоречию нужного типа. Для этого предназначены кнопки Next/Previous Difference (Следующее/ Предыдущее отличие) и Next/Previous Conflict (Следующий/Предыдущий конфликт).
Рис. 50.5. Работа с утилитой Visual Merge: корректное согласование различий в файлах
Чтобы устранить выявленное рассогласование (простое или конфликтное), необходимо поместить курсор в соответствующую строку. Для выбора варианта слияния используются команды контекстного меню.
Apply Change (Принять изменение). В итоговый файл записывается вариант согласования, автоматически предложенный утилитой Visual Merge.
Apply Both Changes (Принять оба изменения). В итоговый файл переносятся оба варианта текста из модифицированных файлов.
Remove Change (Отказаться от изменения). Несогласованные изменения не принимаются во внимание. Соответствующий фрагмент итогового файла не меняется.
Вопросы
Как восстановить предыдущую версию редактируемого файла с помощью Диспетчера версий?
Как устроены системы контроля версий?
Для чего предназначена система StarTeam?
Как разместить проект C++Builder в репозитории StarTeam?
Как загрузить проект C++Builder из репозитория StarTeam?
Как сравнить локальный файл проекта C++Builder с его копией в Репозитории StarTeam?
Как организован в StarTeam механизм визуального слияния версий файлов?
ГЛАВА 51
Технология управления проектными требованиями с помощью системы
Caliber
Управление требованиями в жизненном цикле разработки приложений по праву считается задачей с максимальным приоритетом. Успех всей работы определяется качеством проектных требований, их точностью и согласованностью с заказчиком. Согласно статистике, абсолютное большинство неудач в разработке программного обеспечения связано, прежде всего, с непродуманными требованиями. В одних случаях их противоречивость и неоднозначность приводят впоследствии к масштабным переделкам в архитектуре системы. В результате стоимость ее перестройки превосходит все мыслимые пределы. В других случаях уже в ходе работ заказчик многократно уточняет список требований и дополняет его. Такие доделки вносят в систему множество мелких противоречий, которые, накапливаясь, существенно замедляют общий ход проекта.
В общем случае реализация требования, не учтенного первоначально и введенного, когда программирование уже началось, обходится в десятки и сотни раз дороже, чем в случае его учета уже на предварительном этапе. Поэтому при создании системы желательно учитывать потенциальную возможность появления новых требований и не проектировать систему слишком «жестко».
Управление требованиями и срез проекта
Все современные методологии разработки программного обеспечения в той или иной форме включают этап сбора требований к будущей системе и последующее управление ими. Сам по себе этот этап обычно выполняется по спирали, итеративно. Он складывается из следующих этапов.
Определяются самые общие, первичные требования к системе.
На основе первичных требований строится общий образ системы. Заказчик и исполнитель согласованно определяют, что система должна делать, и исключают нереальные требования.
Проводится обследование нужд пользователей, которым придется работать с системой. Чаще всего их требования формализуются с помощью диаграмм прецедентов UML.
Начинается детальный анализ требований и их проверка на непротиворечивость. На этой стадии уточнения вырабатываются функциональные требования к системе, формируется понимание того, как будет работать будущая программа. Эти уточненные требования становятся техническим заданием для программистов.
Процесс сопровождения требований обычно строится на понятии среза проекта.
Срез (слепок, базовая линия, Baseline) проекта — текугций набор проектных требований, зафиксированный в определенный момент времени.
По мере поступления от заказчика новых требований или модификации существующих аналитики исполнителя проверяют эти требования и добавляют их в действующий проектный набор. Однако программисты используют не текущую и постоянно меняющуюся версию набора требований, а его срез: промежуточную, «замороженную», базовую на данный момент редакцию этого набора.
Разработчики реализуют только те требования, которые определены в текущем срезе. Новые модификации, вносимые в набор требований, программистам не видны. Они станут доступны, когда специалисты, ответственные за ведение требований, подготовят очередную редакцию набора требований и опубликуют ее в виде нового среза.
Система Borland Caliber
В линейке продуктов Borland имеется система, специально созданная для управления требованиями. Она включает средства анализа требований, выполняемого перед проектированием программного продукта. Эта система называется Caliber1 и тесно интегрирована с системой C++Builder.
Система Caliber используется в России и мире не только для проектов информационной индустрии. Она задействована в проектах многих других областей, где имеется потребность в хорошо организованном процессе управления большим числом проектных задач2.
Система управления требованиями Caliber выполнена в клиент-серверной архитектуре. Это означает, что для доступа к ней необходим действующий сервер Caliber, запущенный на одном из компьютеров сети. В среду C++Builder встроен клиент Caliber. Он обеспечивает доступ к набору проектных требований, хранимых на сервере, и ведение программирования в точном соответствии с этими требованиями.
Система Caliber хорошо масштабируется. Она способна одновременно поддерживать до 100 проектов, каждый из которых содержит до 1000 требований.
Работа с клиентом Caliber
В общем случае технология использования системы управления требованиями Borland Caliber состоит из трех основных действий.
Структурируется информация по требованиям.
В данной книге рассматривается версия Caliber RM 2005.
- Компания Butler Group в конце 2005 года опубликовала отчет по рынку систем управления требованиями с развитой функциональностью. Она исследовала системы, охватывающие не только прямые задачи управления требованиями, но и круг смежных. Лидерами на этом рынке, согласно оценкам Butler Group, сегодня являются корпорации Borland и IBM. К концу десятилетия к ним прибавятся еще три-четыре конкурента.
И е^,И е^,^ CaliberRM Requirements^ CaliberRM Requirements- Д Automated Teller Machine- Ф I. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1 ф 2.1 Replenishment Alert - ф 2.2 Tamper Alert - WHY3 ф 3 24x7 - WHY4 ф 4 Verify Balance - WHY5 ф 5 Accessibility - WHY6 2^ 6 Receipt - WHY271- Д Automated Teller Machine- Ф I. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1 ф 2.1 Replenishment Alert - ф 2.2 Tamper Alert - WHY3 ф 3 24x7 - WHY4 ф 4 Verify Balance - WHY5 ф 5 Accessibility - WHY6 2^ 6 Receipt - WHY2719 X ^Welcome Page ^CaliberRM ^UrritLcpp'д , Project: [Automated Teller Machine " ^[ Baseline: [current Baseline"! ^? ^ ^ 0* 0*№ Д JS <* '9 X ^Welcome Page ^CaliberRM ^UrritLcpp'д , Project: [Automated Teller Machine " ^[ Baseline: [current Baseline"! ^? ^ ^ 0* 0*№ Д JS <* '$3 Projectl6,bdsproj-... 9 X$3 Projectl6,bdsproj-... 9 XWHY2WHY2ф 7 Standalone Operation - WHY8- ф 8 Foreign Cards - WHY9ф 8.1 Cirrus - WHY10ф 8.2 Plus+ - WHYllф 9 Capacity-WHY12- ф 10 Deposit Transaction -WHY13 ф 10.1 Fee-WHY14ф 11 Transfer Transactions - WHY15.+ ф 12 Withdrawal Transactions - WHYlt.- e 2. User RequirementsЖ4> 1 Accessibility • USER284ф 2 Transaction Notification - USER285-фЗ.Functional RequirementsiS,’+ Л 1 ATM Marhina . WHAT 19^Structure | ^ CaHberRM Requirements |ф 7 Standalone Operation - WHY8- ф 8 Foreign Cards - WHY9ф 8.1 Cirrus - WHY10ф 8.2 Plus+ - WHYllф 9 Capacity-WHY12- ф 10 Deposit Transaction -WHY13 ф 10.1 Fee-WHY14ф 11 Transfer Transactions - WHY15.+ ф 12 Withdrawal Transactions - WHYlt.- e 2. User RequirementsЖ4> 1 Accessibility • USER284ф 2 Transaction Notification - USER285-фЗ.Functional RequirementsiS,’+ Л 1 ATM Marhina . WHAT 19^Structure | ^ CaHberRM Requirements |XS History | ^, ResponsibSties i W User Attributes j ^ Approvals {: i^f* Details Й^ Traceabifity | ^ Validation j 2^ Discussion | ® ReferencesXS History | ^, ResponsibSties i W User Attributes j ^ Approvals {: i^f* Details Й^ Traceabifity | ^ Validation j 2^ Discussion | ® References^; §5 ProjectGroupl^; §5 ProjectGroupl, | Traces From<> 24x7, | Traces From<> 24x7j StatusSUSPECTj StatusSUSPECT-, (§3 Projectl.exeЩ Proiectl.cppЩ Projectl.res* Щ Umtl.cpp-, (§3 Projectl.exeЩ Proiectl.cppЩ Projectl.res* Щ Umtl.cpp>■>■'"IbSsProject 1..,. [^DataEZ"•< Tool Palette¥ X'"IbSsProject 1..,. [^DataEZ"•< Tool Palette¥ X' 1 Traces Toф ATM Machine: ф Paper Depletion Rate ф Receipt Printing' 1 Traces Toф ATM Machine: ф Paper Depletion Rate ф Receipt Printingll9SZ®_WHAT19WHAT26WHAT30ll9SZ®_WHAT19WHAT26WHAT30| Status| Statusi Projei Proje^ t^ tт Other FHe«:+ C++Buflder Projects tC++Builder Projects I C++.* Web Documents* C++Buflder Projects | Web.; + C++Builder Projects | Web..т Other FHe«:+ C++Buflder Projects tC++Builder Projects I C++.* Web Documents* C++Buflder Projects | Web.; + C++Builder Projects | Web..
И е^,
И е^,
^ CaliberRM Requirements
^ CaliberRM Requirements
- Д Automated Teller Machine
- Ф I. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1 ф 2.1 Replenishment Alert - ф 2.2 Tamper Alert - WHY3 ф 3 24x7 - WHY4 ф 4 Verify Balance - WHY5 ф 5 Accessibility - WHY6 2^ 6 Receipt - WHY271
- Д Automated Teller Machine
- Ф I. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1 ф 2.1 Replenishment Alert - ф 2.2 Tamper Alert - WHY3 ф 3 24x7 - WHY4 ф 4 Verify Balance - WHY5 ф 5 Accessibility - WHY6 2^ 6 Receipt - WHY271
9 X ^Welcome Page ^CaliberRM ^UrritLcpp'
д , Project: [Automated Teller Machine " ^[ Baseline: [current Baseline
"! ^? ^ ^ 0* 0*№ Д JS <* '
9 X ^Welcome Page ^CaliberRM ^UrritLcpp'
д , Project: [Automated Teller Machine " ^[ Baseline: [current Baseline
"! ^? ^ ^ 0* 0*№ Д JS <* '
$3 Projectl6,bdsproj-... 9 X
$3 Projectl6,bdsproj-... 9 X
WHY2
WHY2
ф 7 Standalone Operation - WHY8
- ф 8 Foreign Cards - WHY9ф 8.1 Cirrus - WHY10ф 8.2 Plus+ - WHYllф 9 Capacity-WHY12
- ф 10 Deposit Transaction -WHY13 ф 10.1 Fee-WHY14
ф 11 Transfer Transactions - WHY15
.+ ф 12 Withdrawal Transactions - WHYlt.
- e 2. User RequirementsЖ
4> 1 Accessibility • USER284
ф 2 Transaction Notification - USER285
-фЗ.Functional RequirementsiS,
’+ Л 1 ATM Marhina . WHAT 19
^Structure | ^ CaHberRM Requirements |
ф 7 Standalone Operation - WHY8
- ф 8 Foreign Cards - WHY9ф 8.1 Cirrus - WHY10ф 8.2 Plus+ - WHYllф 9 Capacity-WHY12
- ф 10 Deposit Transaction -WHY13 ф 10.1 Fee-WHY14
ф 11 Transfer Transactions - WHY15
.+ ф 12 Withdrawal Transactions - WHYlt.
- e 2. User RequirementsЖ
4> 1 Accessibility • USER284
ф 2 Transaction Notification - USER285
-фЗ.Functional RequirementsiS,
’+ Л 1 ATM Marhina . WHAT 19
^Structure | ^ CaHberRM Requirements |
XS History | ^, ResponsibSties i W User Attributes j ^ Approvals {: i^f* Details Й^ Traceabifity | ^ Validation j 2^ Discussion | ® References
XS History | ^, ResponsibSties i W User Attributes j ^ Approvals {: i^f* Details Й^ Traceabifity | ^ Validation j 2^ Discussion | ® References
^; §5 ProjectGroupl
^; §5 ProjectGroupl
, | Traces From
<> 24x7
, | Traces From
<> 24x7
j Status
SUSPECT
j Status
SUSPECT
-, (§3 Projectl.exe
Щ Proiectl.cpp
Щ Projectl.res
* Щ Umtl.cpp
-, (§3 Projectl.exe
Щ Proiectl.cpp
Щ Projectl.res
* Щ Umtl.cpp
>■
>■
'"IbSsProject 1..,. [^DataEZ"
•< Tool Palette¥ X
'"IbSsProject 1..,. [^DataEZ"
•< Tool Palette¥ X
' 1 Traces To
ф ATM Machine
: ф Paper Depletion Rate ф Receipt Printing
' 1 Traces To
ф ATM Machine
: ф Paper Depletion Rate ф Receipt Printing
ll9SZ®_
WHAT19
WHAT26
WHAT30
ll9SZ®_
WHAT19
WHAT26
WHAT30
| Status
| Status
i Proje
i Proje
^ t
^ t
т Other FHe«:
+ C++Buflder Projects tC++Builder Projects I C++.
* Web Documents
* C++Buflder Projects | Web.; + C++Builder Projects | Web..
т Other FHe«:
+ C++Buflder Projects tC++Builder Projects I C++.
* Web Documents
* C++Buflder Projects | Web.; + C++Builder Projects | Web..
Выполняется связывание требований с внешними информационными источниками и первичными документами. Достигается это прямым копированием данных в репозиторий требований или же заданием ссылки на них.
По ходу проекта и по мере реализации требований формируются отчеты и графики и автоматически генерируется проектная документация. Реализация требований распределяется между сотрудниками с помощью сформированной для текущего проекта системы ролей.
Клиент Caliber вызывается командой View > CaliberRM Requirements (Вид > Требования в системе CaliberRM). Чтобы клиент Caliber заработал, надо, чтобы в сети был запущен и доступен сервер Caliber. При подключении к нему требуется авторизация. Имя пользователя и пароль задаются администратором системы Caliber.
Окно клиента управления требованиями Caliber отображается в рабочем пространстве среды C++Builder. Для подключения к нужному проекту следует выбрать имя проекта в раскрывающемся списке Project (Проект). Текущий срез выбирают в списке Baseline (Срез). Последняя, действующая версия среза обычно называется Current Baseline (Текущий срез). Среда Caliber позволяет возвращать проект к любому из более ранних срезов.
Клиент Caliber позволяет находить отличия между текущим срезом и одним из предыдущих. Команда Tools > Compare baselines (Сервис > Сравнить срезы) открывает диалоговое окно, в котором надо выбрать сравниваемые срезы. В результате формируется отчет в формате HTML, который представит одинаковые и разные элементы срезов в наглядном иерархическом виде.
< Project! - Borland Developer Studio 2006 - Unit! .cpp
File Edit Search View Refactor Project Run Component Tools StarTeam Window Help ^ , [Default Layout
Э Compare Baselines - Microsoft Internet ExplorerЭ Compare Baselines - Microsoft Internet ExplorerILI@!ILI@!Файл Правка ВидФайл Правка ВидИзбранное Сервис СправкаИзбранное Сервис Справка> 1 Поиск 1 ' Избранное ^> 1 Поиск 1 ' Избранное ^[^ C:\tmp\CaiiberRM\CompareBaselines2.html[^ C:\tmp\CaiiberRM\CompareBaselines2.htmlv Щ Переходv Щ Переход
Э Compare Baselines - Microsoft Internet Explorer
Э Compare Baselines - Microsoft Internet Explorer
ILI@!
ILI@!
Файл Правка Вид
Файл Правка Вид
Избранное Сервис Справка
Избранное Сервис Справка
> 1 Поиск 1 ' Избранное ^
> 1 Поиск 1 ' Избранное ^
[^ C:\tmp\CaiiberRM\CompareBaselines2.html
[^ C:\tmp\CaiiberRM\CompareBaselines2.html
v Щ Переход
v Щ Переход
0 Show Grid
[show Only Baseline Differences Grouped By Difference Type
&M Current Baseline а Я Version 1.0 - Unlocked t- - ® Ф Missing Requirements &ф Missing Requirements- It MoYed-Req.uirement_st Moved Requirements&M Current Baseline а Я Version 1.0 - Unlocked t- - ® Ф Missing Requirements &ф Missing Requirements- It MoYed-Req.uirement_st Moved RequirementsMoved and Versioned^Requirementsj_JVersioned RequirementsMoved and Versioned^Requirementsj_JVersioned RequirementsMoved and Versioned tL Requirements й v Versioned ReauirementsMoved and Versioned tL Requirements й v Versioned Reauirements..._^..._^Develop / Produce Products - 76 V2.0Develop / Produce Products - 76 V2.0_ ♦ Trade Shows - 87 V2.0ф Sell Products - 89 VV7_ ♦ Trade Shows - 87 V2.0ф Sell Products - 89 VV7ф Support Products - 96 VI .2ф Support Products - 96 VI .2Customer places order - 994>V1 9 Customer places order - 994>V1 9 Ф Logs On -100 V1.5ф Check Credit - W1 V1.4"~ ф Enter Order'- 102 V1.5ф Create Invoice - 103 V1.4Ф Logs On -100 V1.5ф Check Credit - W1 V1.4"~ ф Enter Order'- 102 V1.5ф Create Invoice - 103 V1.4Customer cancels order -Customer cancels order -ф Trade Shows - 87 V1.1ф Trade Shows - 87 V1.1_ Ф Sell Products - 89 V1.6;: ф Support Products - 96 VI 1;_ Ф Sell Products - 89 V1.6;: ф Support Products - 96 VI 1;Customer places order - 99Customer places order - 99ф Logs On - 100 V1.2 ф Check Credit -101 V1.1ф Logs On - 100 V1.2 ф Check Credit -101 V1.1■ ф Ent er Order 102 V1.2jф Create Invoice -103 V1 1j Customer cancels order - |■ ф Ent er Order 102 V1.2jф Create Invoice -103 V1 1j Customer cancels order - |^104 \C2.3 . 4>104 V1 2jф Logs On • 105 V2.3ф Logs On - 105V2.0_>ф Cancel Order - 106 V_15 ф Cancel Order - 106 V1 -2_j^104 \C2.3 . 4>104 V1 2jф Logs On • 105 V2.3ф Logs On - 105V2.0_>ф Cancel Order - 106 V_15 ф Cancel Order - 106 V1 -2_jШШ
&M Current Baseline а Я Version 1.0 - Unlocked t- - ® Ф Missing Requirements &ф Missing Requirements- It MoYed-Req.uirement_st Moved Requirements
&M Current Baseline а Я Version 1.0 - Unlocked t- - ® Ф Missing Requirements &ф Missing Requirements- It MoYed-Req.uirement_st Moved Requirements
Moved and Versioned^Requirementsj_JVersioned Requirements
Moved and Versioned^Requirementsj_JVersioned Requirements
Moved and Versioned tL Requirements й v Versioned Reauirements
Moved and Versioned tL Requirements й v Versioned Reauirements
..._^
..._^
Develop / Produce Products - 76 V2.0
Develop / Produce Products - 76 V2.0
_ ♦ Trade Shows - 87 V2.0ф Sell Products - 89 VV7
_ ♦ Trade Shows - 87 V2.0ф Sell Products - 89 VV7
ф Support Products - 96 VI .2
ф Support Products - 96 VI .2
Customer places order - 99
4>V1 9
Customer places order - 99
4>V1 9
Ф Logs On -100 V1.5
ф Check Credit - W1 V1.4"
~ ф Enter Order'- 102 V1.5
ф Create Invoice - 103 V1.4
Ф Logs On -100 V1.5
ф Check Credit - W1 V1.4"
~ ф Enter Order'- 102 V1.5
ф Create Invoice - 103 V1.4
Customer cancels order -
Customer cancels order -
ф Trade Shows - 87 V1.1
ф Trade Shows - 87 V1.1
_ Ф Sell Products - 89 V1.6;
: ф Support Products - 96 VI 1;
_ Ф Sell Products - 89 V1.6;
: ф Support Products - 96 VI 1;
Customer places order - 99
Customer places order - 99
ф Logs On - 100 V1.2 ф Check Credit -101 V1.1
ф Logs On - 100 V1.2 ф Check Credit -101 V1.1
■ ф Ent er Order 102 V1.2j
ф Create Invoice -103 V1 1j Customer cancels order - |
■ ф Ent er Order 102 V1.2j
ф Create Invoice -103 V1 1j Customer cancels order - |
^104 \C2.3 . 4>104 V1 2j
ф Logs On • 105 V2.3ф Logs On - 105V2.0_>
ф Cancel Order - 106 V_15 ф Cancel Order - 106 V1 -2_j
^104 \C2.3 . 4>104 V1 2j
ф Logs On • 105 V2.3ф Logs On - 105V2.0_>
ф Cancel Order - 106 V_15 ф Cancel Order - 106 V1 -2_j
Ш
Ш
®Готово®Готово^ Мой компьютер
®Готово
®Готово
Puc. 51.2. Просмотр в браузере отчета о различияхв проектных требованиях
Дерево требований
Работа с клиентом Caliber базируется на так называемом дереве требований, которое представляет все требования к проекту в иерархическом виде. В общем случае требования к создаваемой системе обычно делят на три большие группы:
бизнес-требования, на основе которых формируется общее видение проекта;
пользовательские требования, определяющие интерфейсы системы;
функциональные требования, на основе которых определяются конкретные технические спецификации.
В дополнение к этим базовым наборам, обязательным для любого проекта, доступны еще шесть групп требований, детализируемых в дереве требований системы Caliber. К ним относятся: требования к дизайну проекта, проектные задачи, тестовые сценарии, ограничения на проект, требования к человеческим ресурсам и специфические требования, связанные с информационными технологиями.
Представление требования в системе Caliber
Каждое из проектных требований представлено в системе Caliber в виде конечного «лепестка» в дереве требований, представленном в окне клиента Caliber. Подробное описание выбранного требования отображается на специальной панели. Эта панель содержит ряд закладок, на каждой из которых описываются определенные характеристики этого требования. Словесная, текстовая процедура проверки требования представлена на закладке Validation (Проверка). Обсуждение требования в форме виртуальных дискуссий ведется на закладке Discussion (Обсуждение). Тематические ссылки на электронные документы и веб-страницы, связанные с данным требованием, коллекционируются на закладке References (Ссылки) — рис. 51.3.
Q°JBQ°JBFile Etfit Search View Refactor Project Run Component Tools StarTeam Window HelpFile Etfit Search View Refactor Project Run Component Tools StarTeam Window Help^ [Default Layout "~3 ^5 ^*^ [Default Layout "~3 ^5 ^*ф CaHberRM Requirementsф CaHberRM Requirements9 X ^ Welcome Page ^CaHberRM [&! Unitl.cpp9 X ^ Welcome Page ^CaHberRM [&! Unitl.cpp- Я Automated Teller Machine-A- ^ 1. Business Requirements*0 1 Welcome - WHY293ф 2 ATM Alerts-WHY1ф 2.1 Replenishment Alert - WHY2ф 2.2 Tamper Alert - WHY3ф 3 24x7 - WHY4.ф 4 Verify Balance - WHYSф 5 Accessibility - WHY6^ф 6 Receipt - WHY271ф 7 Standalone Operation - WHY©ф 8 Foreign Cards - WHY9 ф 8.1 Cirrus - WHY10 ф 8.2Plus+ - WHY11ф 9 Capacity -WHY12- ф 10 Deposit Transaction - WHY13ф 10.1 Fee- WHY14ф 11 Transfer Transactions - WHY15 .+ ф 12 Withdrawal Transactions - WHY 162. User Requirementsф 1 Accessibility - U5ER284ф 2 Transaction Notification - U5ER28SFunctional Requirements+ A 1 ATM Machine-WHAT 19■'- Я Automated Teller Machine-A- ^ 1. Business Requirements*0 1 Welcome - WHY293ф 2 ATM Alerts-WHY1ф 2.1 Replenishment Alert - WHY2ф 2.2 Tamper Alert - WHY3ф 3 24x7 - WHY4.ф 4 Verify Balance - WHYSф 5 Accessibility - WHY6^ф 6 Receipt - WHY271ф 7 Standalone Operation - WHY©ф 8 Foreign Cards - WHY9 ф 8.1 Cirrus - WHY10 ф 8.2Plus+ - WHY11ф 9 Capacity -WHY12- ф 10 Deposit Transaction - WHY13ф 10.1 Fee- WHY14ф 11 Transfer Transactions - WHY15 .+ ф 12 Withdrawal Transactions - WHY 162. User Requirementsф 1 Accessibility - U5ER284ф 2 Transaction Notification - U5ER28SFunctional Requirements+ A 1 ATM Machine-WHAT 19■'Project: (Automated Teller Machine ♦] Baseline: {Current Baseline^1;ЛЗ£ A.^..^ x у- * > <• ^ lt№is> - мГ~21ZJ. 9, Saj£f History [^ Responsibilities !<|l User Attrfoutes | IS?" Approvals |^ Details ; frt Traceability j C$ Validation |^ Discussion W ReferencesNew lext j New Web j Remove |j File; Description 1"!~JC:\Program Files\8orland\CaltberRM\Samples\ATM Flowchart,vsdProject: (Automated Teller Machine ♦] Baseline: {Current Baseline^1;ЛЗ£ A.^..^ x у- * > <• ^ lt№is> - мГ~21ZJ. 9, Saj£f History [^ Responsibilities !<|l User Attrfoutes | IS?" Approvals |^ Details ; frt Traceability j C$ Validation |^ Discussion W ReferencesNew lext j New Web j Remove |j File; Description 1"!~JC:\Program Files\8orland\CaltberRM\Samples\ATM Flowchart,vsd' lC:\Progra№ Fitest8orland\Cahbe<rRMtSamples’,ATM Machine, bmp' lC:\Progra№ Fitest8orland\Cahbe<rRMtSamples’,ATM Machine, bmpCaltberRMCaltberRM^.Structure | ^S CaHberRM Requirements f^.Structure | ^S CaHberRM Requirements f* Projectl Borland Developer Studio 2006 - Unitl.cpp
Q°JB
Q°JB
File Etfit Search View Refactor Project Run Component Tools StarTeam Window Help
File Etfit Search View Refactor Project Run Component Tools StarTeam Window Help
^ [Default Layout "~3 ^5 ^*
^ [Default Layout "~3 ^5 ^*
ф CaHberRM Requirements
ф CaHberRM Requirements
9 X ^ Welcome Page ^CaHberRM [&! Unitl.cpp
9 X ^ Welcome Page ^CaHberRM [&! Unitl.cpp
- Я Automated Teller Machine-A
- ^ 1. Business Requirements*
0 1 Welcome - WHY293
ф 2 ATM Alerts-WHY1
ф 2.1 Replenishment Alert - WHY2
ф 2.2 Tamper Alert - WHY3
ф 3 24x7 - WHY4.
ф 4 Verify Balance - WHYS
ф 5 Accessibility - WHY6
^ф 6 Receipt - WHY271
ф 7 Standalone Operation - WHY©
ф 8 Foreign Cards - WHY9 ф 8.1 Cirrus - WHY10 ф 8.2Plus+ - WHY11
ф 9 Capacity -WHY12
- ф 10 Deposit Transaction - WHY13
ф 10.1 Fee- WHY14
ф 11 Transfer Transactions - WHY15 .
+ ф 12 Withdrawal Transactions - WHY 16
2. User Requirements
ф 1 Accessibility - U5ER284
ф 2 Transaction Notification - U5ER28S
Functional Requirements
+ A 1 ATM Machine-WHAT 19■'
- Я Automated Teller Machine-A
- ^ 1. Business Requirements*
0 1 Welcome - WHY293
ф 2 ATM Alerts-WHY1
ф 2.1 Replenishment Alert - WHY2
ф 2.2 Tamper Alert - WHY3
ф 3 24x7 - WHY4.
ф 4 Verify Balance - WHYS
ф 5 Accessibility - WHY6
^ф 6 Receipt - WHY271
ф 7 Standalone Operation - WHY©
ф 8 Foreign Cards - WHY9 ф 8.1 Cirrus - WHY10 ф 8.2Plus+ - WHY11
ф 9 Capacity -WHY12
- ф 10 Deposit Transaction - WHY13
ф 10.1 Fee- WHY14
ф 11 Transfer Transactions - WHY15 .
+ ф 12 Withdrawal Transactions - WHY 16
2. User Requirements
ф 1 Accessibility - U5ER284
ф 2 Transaction Notification - U5ER28S
Functional Requirements
+ A 1 ATM Machine-WHAT 19■'
Project: (Automated Teller Machine ♦] Baseline: {Current Baseline^1
;ЛЗ£ A.^..^ x у- * > <• ^ lt№is> - м
Г~21ZJ. 9, Sa
j£f History [^ Responsibilities !<|l User Attrfoutes | IS?" Approvals |
^ Details ; frt Traceability j C$ Validation |^ Discussion W References
New lext j New Web j Remove |
j File; Description 1"
!~JC:\Program Files\8orland\CaltberRM\Samples\ATM Flowchart,vsd
Project: (Automated Teller Machine ♦] Baseline: {Current Baseline^1
;ЛЗ£ A.^..^ x у- * > <• ^ lt№is> - м
Г~21ZJ. 9, Sa
j£f History [^ Responsibilities !<|l User Attrfoutes | IS?" Approvals |
^ Details ; frt Traceability j C$ Validation |^ Discussion W References
New lext j New Web j Remove |
j File; Description 1"
!~JC:\Program Files\8orland\CaltberRM\Samples\ATM Flowchart,vsd
' lC:\Progra№ Fitest8orland\Cahbe<rRMtSamples’,ATM Machine, bmp
' lC:\Progra№ Fitest8orland\Cahbe<rRMtSamples’,ATM Machine, bmp
CaltberRM
CaltberRM
^.Structure | ^S CaHberRM Requirements f
^.Structure | ^S CaHberRM Requirements f
Puc. 51.3. Работа с деревом требований в клиенте CaHberRM
Отслеживание версий конкретного требования выполняется с помощью поля Requirement Version (Версия требования) на закладке Details (Подробности). Подробная история изменений требования от версии к версии показана на закладке History (Журнал). Запись содержит информацию об авторе изменения, его дату и время, а также описание модификации. Среда Caliber позволяет создавать собственные, оригинальные атрибуты требований. Полномочия на модификацию значения пользовательского атрибута гибко настраивают с помощью специального Мастера в момент создания этого атрибута.
Функциональные требования
Для программиста важнее всего раздел функциональных требований (Functional Requirements), наиболее тесно связанный с процессом разработки. В сложном
проекте функциональные требования удобнее всего представлять в виде дерева. Каждая из ветвей дерева может отвечать, например, определенной пользовательской роли.
4 Projectl - Borland Developer Studio 2006 - Unitl.cpp
File Edit Search View Refactor Project Run Component Tods StarTeam Window Help ^ ! ^Default Layout ^ ( ^ ^
■ Si _# В ; .5 ® • 8 • s' & ь ►■ Si _# В ; .5 ® • 8 • s' & ь ►25 И : * в’ e ■ * - 4, ^25 И : * в’ e ■ * - 4, ^^ CaliberRM Requirements^ CaliberRM Requirements;: Я Automated Teller Machine♦ 1. Business Requirements 0 1 welcome - WHY293-; ф 2 ATM Alerts - WHY1;: Я Automated Teller Machine♦ 1. Business Requirements 0 1 welcome - WHY293-; ф 2 ATM Alerts - WHY1V X -^ Welcome Page ^CaliberRM $1 Unitl.cpp5б Project: Automated Teller Machine v| Baseline: (Current Baseline• ^ < 4* ^^. •_* a 4»< ОЙ> - *V X -^ Welcome Page ^CaliberRM $1 Unitl.cpp5б Project: Automated Teller Machine v| Baseline: (Current Baseline• ^ < 4* ^^. •_* a 4»< ОЙ> - *0 2,1 Replenishment Alert- WHY2 " : '0 2,1 Replenishment Alert- WHY2 " : 'ф 2.2 Tamper Alert - WHY30 3 24x7 - WHY44^ 4 Verify Balance - WHY5■ф 5 Accessibility - WHY6jS§4> 6 Receipt-WHY271’ф 7 Standalone Operation - WHYB ;- ф В Foreign Cards - WHY9;ф 8.1 Cirrus - WHY10iф 8.2 PIU5+- WHY11?ф 9 Capacity - WHY12'S 4^ 10 Deposit Transaction - WHY13ф 10.1 Fee-WHV144^ 11 Transfer Transactions - WHY15:+ 4^ 12 Withdrawal Transactions - WHY 16♦ 2. User Requirementsф 1 Accessibility - USER2B44^ 2 Transaction Notification - USER285 - ФЭ Functional Requirements''+' A 1 ATM Machine - WHAT 19^ Structure | ^CaliberRM Requirements ]ф 2.2 Tamper Alert - WHY30 3 24x7 - WHY44^ 4 Verify Balance - WHY5■ф 5 Accessibility - WHY6jS§4> 6 Receipt-WHY271’ф 7 Standalone Operation - WHYB ;- ф В Foreign Cards - WHY9;ф 8.1 Cirrus - WHY10iф 8.2 PIU5+- WHY11?ф 9 Capacity - WHY12'S 4^ 10 Deposit Transaction - WHY13ф 10.1 Fee-WHV144^ 11 Transfer Transactions - WHY15:+ 4^ 12 Withdrawal Transactions - WHY 16♦ 2. User Requirementsф 1 Accessibility - USER2B44^ 2 Transaction Notification - USER285 - ФЭ Functional Requirements''+' A 1 ATM Machine - WHAT 19^ Structure | ^CaliberRM Requirements ]^ Details ! ^ Traceability J £0 Validation ; £^ Discussion { # References^ History |^ Responsibilities j W User Attributes |^ Approvals^ Details ! ^ Traceability J £0 Validation ; £^ Discussion { # References^ History |^ Responsibilities j W User Attributes |^ ApprovalsRev # I Date/TIme """ I Chanqed'by I Comment1.019.06.200317:51:43adminCreated1.119.0B.2003 17:51:56adminRev # I Date/TIme """ I Chanqed'by I Comment1.019.06.200317:51:43adminCreated1.119.0B.2003 17:51:56admin19.08.2003 17:51:5627.08.2003 0:14:3619.08.2003 17:51:5627.08.2003 0:14:36admin adminadmin adminglglAttributeAttributeCaliberRM.CaliberRM.j Changed from } Changed toj Changed from } Changed toTrace from Requirement 14 to Requirement. -.Trace from Requirement 14 to Requirement. -.
■ Si _# В ; .5 ® • 8 • s' & ь ►
■ Si _# В ; .5 ® • 8 • s' & ь ►
25 И : * в’ e ■ * - 4, ^
25 И : * в’ e ■ * - 4, ^
^ CaliberRM Requirements
^ CaliberRM Requirements
;: Я Automated Teller Machine
♦ 1. Business Requirements 0 1 welcome - WHY293
-; ф 2 ATM Alerts - WHY1
;: Я Automated Teller Machine
♦ 1. Business Requirements 0 1 welcome - WHY293
-; ф 2 ATM Alerts - WHY1
V X -^ Welcome Page ^CaliberRM $1 Unitl.cpp
5б Project: Automated Teller Machine v| Baseline: (Current Baseline
• ^ < 4* ^^. •_* a 4»< ОЙ> - *
V X -^ Welcome Page ^CaliberRM $1 Unitl.cpp
5б Project: Automated Teller Machine v| Baseline: (Current Baseline
• ^ < 4* ^^. •_* a 4»< ОЙ> - *
0 2,1 Replenishment Alert- WHY2 " : '
0 2,1 Replenishment Alert- WHY2 " : '
ф 2.2 Tamper Alert - WHY3
0 3 24x7 - WHY4
4^ 4 Verify Balance - WHY5■
ф 5 Accessibility - WHY6j
S§4> 6 Receipt-WHY271’
ф 7 Standalone Operation - WHYB ;
- ф В Foreign Cards - WHY9;
ф 8.1 Cirrus - WHY10i
ф 8.2 PIU5+- WHY11?
ф 9 Capacity - WHY12'
S 4^ 10 Deposit Transaction - WHY13
ф 10.1 Fee-WHV14
4^ 11 Transfer Transactions - WHY15
:+ 4^ 12 Withdrawal Transactions - WHY 16
♦ 2. User Requirements
ф 1 Accessibility - USER2B4
4^ 2 Transaction Notification - USER285 - ФЭ Functional Requirements'
'+' A 1 ATM Machine - WHAT 19
^ Structure | ^CaliberRM Requirements ]
ф 2.2 Tamper Alert - WHY3
0 3 24x7 - WHY4
4^ 4 Verify Balance - WHY5■
ф 5 Accessibility - WHY6j
S§4> 6 Receipt-WHY271’
ф 7 Standalone Operation - WHYB ;
- ф В Foreign Cards - WHY9;
ф 8.1 Cirrus - WHY10i
ф 8.2 PIU5+- WHY11?
ф 9 Capacity - WHY12'
S 4^ 10 Deposit Transaction - WHY13
ф 10.1 Fee-WHV14
4^ 11 Transfer Transactions - WHY15
:+ 4^ 12 Withdrawal Transactions - WHY 16
♦ 2. User Requirements
ф 1 Accessibility - USER2B4
4^ 2 Transaction Notification - USER285 - ФЭ Functional Requirements'
'+' A 1 ATM Machine - WHAT 19
^ Structure | ^CaliberRM Requirements ]
^ Details ! ^ Traceability J £0 Validation ; £^ Discussion { # References^ History |^ Responsibilities j W User Attributes |^ Approvals
^ Details ! ^ Traceability J £0 Validation ; £^ Discussion { # References^ History |^ Responsibilities j W User Attributes |^ Approvals
Rev # I Date/TIme """ I Chanqed'by I Comment
1.019.06.200317:51:43adminCreated
1.119.0B.2003 17:51:56admin
Rev # I Date/TIme """ I Chanqed'by I Comment
1.019.06.200317:51:43adminCreated
1.119.0B.2003 17:51:56admin
19.08.2003 17:51:56
27.08.2003 0:14:36
19.08.2003 17:51:56
27.08.2003 0:14:36
admin admin
admin admin
gl
gl
Attribute
Attribute
CaliberRM.
CaliberRM.
j Changed from } Changed to
j Changed from } Changed to
Trace from Requirement 14 to Requirement. -.
Trace from Requirement 14 to Requirement. -.
Рис. 51.4. Просмотр истории изменения конкретного требования
Requirement Type Creation Wizard - Step 6 of 7
gelow is a fist of afi attributes which are available to reqiAements rf this type. Specif y under which circumstances you would like users to be able to modify these attrflautes:gelow is a fist of afi attributes which are available to reqiAements rf this type. Specif y under which circumstances you would like users to be able to modify these attrflautes:Attribute:♦ Requirement Document Reference♦ Requirement Groupф Requirement TraceAttribute:♦ Requirement Document Reference♦ Requirement Groupф Requirement TracejNone Towned|Any™[jNone Towned|Any™[Requirement DiscussionRequirement DiscussionRequirement OwnerTested by Whomф Reviewed Byф Requirement NameSponsor NameRequirement OwnerTested by Whomф Reviewed Byф Requirement NameSponsor NameООООоо
gelow is a fist of afi attributes which are available to reqiAements rf this type. Specif y under which circumstances you would like users to be able to modify these attrflautes:
gelow is a fist of afi attributes which are available to reqiAements rf this type. Specif y under which circumstances you would like users to be able to modify these attrflautes:
Attribute:
♦ Requirement Document Reference
♦ Requirement Group
ф Requirement Trace
Attribute:
♦ Requirement Document Reference
♦ Requirement Group
ф Requirement Trace
jNone Towned|Any™[
jNone Towned|Any™[
Requirement Discussion
Requirement Discussion
Requirement Owner
Tested by Whom
ф Reviewed By
ф Requirement Name
Sponsor Name
Requirement Owner
Tested by Whom
ф Reviewed By
ф Requirement Name
Sponsor Name
О
О
О
О
о
о
tone: Never allow users to modify attribute
Owned: If the requirement containing this attribute is owned by the current user
Any: Allow anyone to modify attribute
< Назад | Далее > | Отмена j
Puc. 51.5. Окно настройки характеристик нового требования, создаваемогоПроектировщиком
Для описания характеристик функционального требования предусмотрены три дополнительные закладки.
На закладке User Attributes (Атрибуты разработчика) указывается дата окончательной реализации данного требования в поле Due Date (Срок), версия продукта, в которой данное требование надлежит реализовать, в поле Target Version (Целевая версия) и важность требования для конечного пользователя в поле User Group Rating (Пользовательский рейтинг).
На закладке Ambiguity Review (Анализ неоднозначностей) формируют список участников проекта, выполняющих анализ данного требования на предмет его непротиворечивости.
На закладке Estimate (Оценка) выполняется оценка трудоемкости реализации требования. Верхнюю, нижнюю и наиболее вероятную оценку трудоемкости задают в полях High Effort (Максимальные усилия), Low Effort (Минимальные усилия) и Expected Effort (Ожидаемые усилия).
Бизнес-требования
Для бизнес-требований (категория Business Requirements) специфична закладка User Attributes (Атрибуты пользователя). Она описывает приоритет данного требования с точки зрения деловых интересов заказчика. Поле Source (Источник) задает источник требования, поле Sponsor Name (Имя спонсора) — источник финансирования работ по реализации требования. Факт финансирования помечается флажком Funded (Профинансировано). Дата окончательной реализации требования задается в календарном поле Due Date (Срок).
Организация ролевой работы
Одна из важнейших особенностей современных систем управления требованиями состоит в том, что они позволяют не только визуализировать набор требований, но и организовать ролевую работу с ними. В проекте Caliber формируются роли — модели рабочих мест, на которых ведется модификация требований и управление ими. Полномочия каждого участника можно изменить непосредственно в ходе работ. Эти полномочия формируются в модуле администратора Caliber Framework Administrator: каждый сотрудник получает в проекте свою сферу компетенции и модификации требований.
Список лиц, ответственных за реализацию требования, формируется па его закладке Responsibilities (Ответственность). Этот список готовится заранее с помощью модуля Caliber Framework Administrator при создании нового проекта (рис. 51.6).
Отслеживание взаимосвязей между требованиями
В системе Caliber имеются средства отслеживания взаимосвязей между требованиями. Для любого требования эти взаимосвязи доступны на закладке Traceability (Трассируемость). В столбце Traces From (Зависит от) представлены требования, от которых зависит текущее требование. В столбце Traces То (Влияет на) показаны требования, которые, в свою очередь, зависят от исходно выбранного. Для навигации
•$ Piojectl - Borland Developer Studio 2006 - Unitl .cpp
File Edit Search View Refactor Project Run Component Tools StarTeam Window He^) Э .!|o=faultLayout 2 I 4} ^
• CaliberRM Requirements• CaliberRM Requirements9 X Д Welcome Page ^CaliberRM ^Unrtl.cpp9 X Д Welcome Page ^CaliberRM ^Unrtl.cppв Automated Teller Machine♦ 1. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1в Automated Teller Machine♦ 1. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1AAф 2.1 Replenishment Alert - WHY2ф 2.2 Tamper Alert - WHY3A 3 24x7 - WHY4ф 4 Verify Balance - WHY5ф 5 Accessibility - WHY6£&❖ 6 Receipt -WHY271Д;ф 7 Standalone Operation - WHY8ф 8 Foreign Cards - WHY9ф 8.1 Cirrus - WHY10ф 8.2Plus+- WHYHф 9 Capacity - WHY12- ф 10 Deposit Transaction-WHY13^ 10.1 Fee-WHY14ф 11 Transfer Transactions - WHY15'+ ф 12 Withdrawal Transactions -WHYlt ■ - ♦ 2. User Requirements*^sф 1 Accessibility • USER284^ 2 Transaction Notification - U5ER285 ;<»- Functional Requirements:+ A 1 ATM Machine-WHAT 19^.Structure | ^CdiberRM Requirements |ф 2.1 Replenishment Alert - WHY2ф 2.2 Tamper Alert - WHY3A 3 24x7 - WHY4ф 4 Verify Balance - WHY5ф 5 Accessibility - WHY6£&❖ 6 Receipt -WHY271Д;ф 7 Standalone Operation - WHY8ф 8 Foreign Cards - WHY9ф 8.1 Cirrus - WHY10ф 8.2Plus+- WHYHф 9 Capacity - WHY12- ф 10 Deposit Transaction-WHY13^ 10.1 Fee-WHY14ф 11 Transfer Transactions - WHY15'+ ф 12 Withdrawal Transactions -WHYlt ■ - ♦ 2. User Requirements*^sф 1 Accessibility • USER284^ 2 Transaction Notification - U5ER285 ;<»- Functional Requirements:+ A 1 ATM Machine-WHAT 19^.Structure | ^CdiberRM Requirements |Project: f Automated Teller Machine▼) Baseline: ) Current Baseline' I* v % * < x q b a ».a .KH^ - **L__=3Z~3; « t к th^# Details | ^ Traceabffity ! C5 Variation | ^ Discussion | 0 References j S§ History $S Responsibilities | ф User Attributes |^ ApprovalsProject: f Automated Teller Machine▼) Baseline: ) Current Baseline' I* v % * < x q b a ».a .KH^ - **L__=3Z~3; « t к th^# Details | ^ Traceabffity ! C5 Variation | ^ Discussion | 0 References j S§ History $S Responsibilities | ф User Attributes |^ ApprovalsProject Groups and Members;Project Groups and Members;Q Automated Teller Machine'ti EBES Administrators0^ Accounting0SSSB88fpatton @22 DevelopmentQ Automated Teller Machine'ti EBES Administrators0^ Accounting0SSSB88fpatton @22 Developmentt D|t D|F 0!- □!F 0!- □!i CaliberRMi CaliberRMGuestsDesign MarketingQuality AssuranceGuestsDesign MarketingQuality Assurance
• CaliberRM Requirements
• CaliberRM Requirements
9 X Д Welcome Page ^CaliberRM ^Unrtl.cpp
9 X Д Welcome Page ^CaliberRM ^Unrtl.cpp
в Automated Teller Machine
♦ 1. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1
в Automated Teller Machine
♦ 1. Business Requirements ф 1 Welcome - WHY293 - ф 2 ATM Alerts - WHY1
A
A
ф 2.1 Replenishment Alert - WHY2
ф 2.2 Tamper Alert - WHY3
A 3 24x7 - WHY4
ф 4 Verify Balance - WHY5
ф 5 Accessibility - WHY6
£&❖ 6 Receipt -WHY271Д;
ф 7 Standalone Operation - WHY8
ф 8 Foreign Cards - WHY9
ф 8.1 Cirrus - WHY10
ф 8.2Plus+- WHYH
ф 9 Capacity - WHY12
- ф 10 Deposit Transaction-WHY13
^ 10.1 Fee-WHY14
ф 11 Transfer Transactions - WHY15
'+ ф 12 Withdrawal Transactions -WHYlt ■ - ♦ 2. User Requirements*^s
ф 1 Accessibility • USER284
^ 2 Transaction Notification - U5ER285 ;<
»- Functional Requirements:
+ A 1 ATM Machine-WHAT 19
^.Structure | ^CdiberRM Requirements |
ф 2.1 Replenishment Alert - WHY2
ф 2.2 Tamper Alert - WHY3
A 3 24x7 - WHY4
ф 4 Verify Balance - WHY5
ф 5 Accessibility - WHY6
£&❖ 6 Receipt -WHY271Д;
ф 7 Standalone Operation - WHY8
ф 8 Foreign Cards - WHY9
ф 8.1 Cirrus - WHY10
ф 8.2Plus+- WHYH
ф 9 Capacity - WHY12
- ф 10 Deposit Transaction-WHY13
^ 10.1 Fee-WHY14
ф 11 Transfer Transactions - WHY15
'+ ф 12 Withdrawal Transactions -WHYlt ■ - ♦ 2. User Requirements*^s
ф 1 Accessibility • USER284
^ 2 Transaction Notification - U5ER285 ;<
»- Functional Requirements:
+ A 1 ATM Machine-WHAT 19
^.Structure | ^CdiberRM Requirements |
Project: f Automated Teller Machine▼) Baseline: ) Current Baseline' I
* v % * < x q b a ».a .KH^ - **
L__=3Z~3; « t к th^
# Details | ^ Traceabffity ! C5 Variation | ^ Discussion | 0 References j S§ History $S Responsibilities | ф User Attributes |^ Approvals
Project: f Automated Teller Machine▼) Baseline: ) Current Baseline' I
* v % * < x q b a ».a .KH^ - **
L__=3Z~3; « t к th^
# Details | ^ Traceabffity ! C5 Variation | ^ Discussion | 0 References j S§ History $S Responsibilities | ф User Attributes |^ Approvals
Project Groups and Members;
Project Groups and Members;
Q Automated Teller Machine'ti EBES Administrators0^ Accounting
0SSSB88
fpatton @22 Development
Q Automated Teller Machine'ti EBES Administrators0^ Accounting
0SSSB88
fpatton @22 Development
t D|
t D|
F 0!
- □!
F 0!
- □!
i CaliberRM
i CaliberRM
Guests
Design Marketing
Quality Assurance
Guests
Design Marketing
Quality Assurance
Puc. 51.6. Распределение проектных ролей no выбранному требованию
по цепочке таких связанных требований надо дважды щелкнуть на нужной строке списка.
Цепочка связей требований друг с другом может представлять собой сложный граф. Чтобы уверенно в нем ориентироваться, нередко приходится прибегать к дополнительным возможностям системы Caliber. Окно матрицы взаимосвязей открывают командой Tools > Traceability Martrix (Сервис > Матрица трассируемое™).
Рис. 51.7. Отслеэюивание цепочек взаимосвязей между требованиямиРис. 51.7. Отслеэюивание цепочек взаимосвязей между требованиями
Рис. 51.7. Отслеэюивание цепочек взаимосвязей между требованиями
Рис. 51.7. Отслеэюивание цепочек взаимосвязей между требованиями
В этом окне можно просмотреть и откорректировать цепочки связей между требованиями или построить диаграмму этих связей (рис. 51.8).
Traceability MatrixTraceability Matrix
Traceability Matrix
Traceability Matrix
gfe Edit View Help
.♦.♦♦:♦;♦♦ Ф Ф ф Ф ф
ЕЕ§ -8§ -8S:S:яяRRSSfIfI■55■55s■ss■s* i is"5 n S Я* i is"5 n S ЯЬЬ^Maintain Contact List ф Welcome ф Search by First Name фЗеагсЬ by Last Name ф Search by Zip Code ф Search Functions ф Define a Personal List ф Print a Personal List ф Handle 15,000 Contacts ф Create a Contact ф Search for a Contact^Maintain Contact List ф Welcome ф Search by First Name фЗеагсЬ by Last Name ф Search by Zip Code ф Search Functions ф Define a Personal List ф Print a Personal List ф Handle 15,000 Contacts ф Create a Contact ф Search for a Contactаллаллаа,л:,л:41 а41 а! Л;! Л;
Е
Е
§ -8
§ -8
S:
S:
я
я
R
R
S
S
fI
fI
■5
5
■5
5
s
■s
s
■s
* i is
"5 n S Я
* i is
"5 n S Я
Ь
Ь
^Maintain Contact List ф Welcome ф Search by First Name фЗеагсЬ by Last Name ф Search by Zip Code ф Search Functions ф Define a Personal List ф Print a Personal List ф Handle 15,000 Contacts ф Create a Contact ф Search for a Contact
^Maintain Contact List ф Welcome ф Search by First Name фЗеагсЬ by Last Name ф Search by Zip Code ф Search Functions ф Define a Personal List ф Print a Personal List ф Handle 15,000 Contacts ф Create a Contact ф Search for a Contact
алл
алл
а
а
,л:
,л:
41 а
41 а
! Л;
! Л;
ReadyReadyЖ
Ready
Ready
Puc. 51.8. Матрица взаимозависимостей между требованиями
Связь требований с исходным кодом проекта
Система Caliber позволяет отследить, как происходит реализация технических требований к создаваемой системе. Поэтому желательно, чтобы каждый важный элемент проекта был привязан к конкретному требованию. Это позволяет объединить управление требованиями с процессами разработки и кодирования.
Привязка требований системы Caliber к исходным текстам проекта среды C++Builder выполняется следующим образом.
В среде C++Builder открывается окно с клиентом Caliber, и в нем выбирается текущий проект.
В клиенте Caliber выбирается нужное требование.
Определяется область в исходном тексте программы на языке C++, которую нужно связать с текущим требованием, открытым в клиенте Caliber.
В редакторе исходных текстов C++Builder устанавливаем курсор в позицию текста, связанного с требованием, и даем команду контекстного меню Requirements > Insert (Требования > Вставить). В результате в позицию курсора в редакторе C++Builder добавится примерно такой код:
//<CaliberRMComment key="МТА1СjECjEAzCnVzZKMTcOQlNw==">
M//////1 III////////III/III/////////////////////И//////1//////// //Requirement: SMS Scripting - WBS144 //Type: 5. Project Tasks (WBS) //Project: Order Processing (Current Baseline) //Server: user
///////////////////////////////////////////////////////////////// //Certification and scripting of Order Processing System for deployment
/ /
//Version: 1.1
//Owner: vcarter
//Status: Accepted
//Priority: Useful
/ Ш IM / / / / / / / / HI / / / / / / / / II / / M / / / / / III / / / / / / / / H / II / / / / / / / / / / / /
//Comment created at 07.10.2006 20:08:18 by admin
//</CaliberRMComment>
M Projectl - Borland Developer Studio 2006 - Un1t1.cppM Projectl - Borland Developer Studio 2006 - Un1t1.cppFile Edit Search View Refactor Project Run Component ToolsFile Edit Search View Refactor Project Run Component Tools55<i><i>**^ CaliberRM Requirements^ CaliberRM RequirementsV XV X[dows NT-D5GN127dows 98-DSGN128DSGN129r Access - D5GN130 is^cessing System Upgrade Rollout - WBS 132[dows NT-D5GN127dows 98-DSGN128DSGN129r Access - D5GN130 is^cessing System Upgrade Rollout - WBS 132ЖЖ► -^ Welcome Page :#pragrl► -^ Welcome Page :#pragrltipragri#pragt!TFormjtipragri#pragt!TFormjration 1 - WBS 133|. 1 Project Startup - WBS134-й|;Requirements Capture - WB5147J .3 Analysis & Design - WBS151rtuImplementation - WB51531.1.4.1 Test Environment-WB51541.1.4.2 Order Processing Systemion Environment - WBiyl!ration 1 - WBS 133|. 1 Project Startup - WBS134-й|;Requirements Capture - WB5147J .3 Analysis & Design - WBS151rtuImplementation - WB51531.1.4.1 Test Environment-WB51541.1.4.2 Order Processing Systemion Environment - WBiyl!fastfastоIO'ioоIO'ioIntall Partner App on Order Processing SV1-1.4.2.2 Install Partner Application - WB5172Install Oracle Database - WB5173Install Caliber v4,0 on Order ProcessingsFreeze Changes to Order Processing SyslTest of New Functionality on Order Proce-Test of New Functionality - WB5177Install New Order Processing System on fTest Client Deployment - WB5179Execute Test Plan - WBS 1B0Deployment - WBS181Intall Partner App on Order Processing SV1-1.4.2.2 Install Partner Application - WB5172Install Oracle Database - WB5173Install Caliber v4,0 on Order ProcessingsFreeze Changes to Order Processing SyslTest of New Functionality on Order Proce-Test of New Functionality - WB5177Install New Order Processing System on fTest Client Deployment - WB5179Execute Test Plan - WBS 1B0Deployment - WBS181IIIILidХИ1Chi:LidХИ1Chi:rue rurnidurue rurniduAdd To-Do Item.Add To-Do Item.DebugDebugRead OnlyRead OnlyMessage ViewMessage ViewPropertiesPropertiesSurroundSurroundRefactoringRefactoringInsertInsertUpdateUpdateOwner)Owner)FoldUnfoldFoldUnfoldtoggle Bookmarks Goto Bookmarkstoggle Bookmarks Goto BookmarksTopic SearchTopic SearchComplete class at cursor 5hift+Ctrl+CComplete class at cursor 5hift+Ctrl+CToggle CommentToggle CommentShift+Ctrl+TShift+Ctrl+T(TObject *Senc(TObject *SencFindFind^, Structure | ^CatiberRM Requirements [^, Structure | ^CatiberRM Requirements [A^'A^'| <J Trace To|. ^ Trace From|-M-—^J| <J Trace To|. ^ Trace From|-M-—^J%. e%. e28: 7 Insert28: 7 InsertModifiedModified■ 1 Code Design ’ History■ 1 Code Design ’ History
M Projectl - Borland Developer Studio 2006 - Un1t1.cpp
M Projectl - Borland Developer Studio 2006 - Un1t1.cpp
File Edit Search View Refactor Project Run Component Tools
File Edit Search View Refactor Project Run Component Tools
5
5
<i>
<i>
*
*
^ CaliberRM Requirements
^ CaliberRM Requirements
V X
V X
[dows NT-D5GN127
dows 98-DSGN128
DSGN129
r Access - D5GN130 is
^cessing System Upgrade Rollout - WBS 132
[dows NT-D5GN127
dows 98-DSGN128
DSGN129
r Access - D5GN130 is
^cessing System Upgrade Rollout - WBS 132
Ж
Ж
► -
^ Welcome Page :
#pragrl
► -
^ Welcome Page :
#pragrl
tipragri
#pragt!
TFormj
tipragri
#pragt!
TFormj
ration 1 - WBS 133
|. 1 Project Startup - WBS134-й|;
Requirements Capture - WB5147
J .3 Analysis & Design - WBS151rtu
Implementation - WB5153
1.1.4.1 Test Environment-WB5154
1.1.4.2 Order Processing Systemion Environment - WBiyl!
ration 1 - WBS 133
|. 1 Project Startup - WBS134-й|;
Requirements Capture - WB5147
J .3 Analysis & Design - WBS151rtu
Implementation - WB5153
1.1.4.1 Test Environment-WB5154
1.1.4.2 Order Processing Systemion Environment - WBiyl!
fast
fast
о
IO'
io
о
IO'
io
Intall Partner App on Order Processing SV
1-1.4.2.2 Install Partner Application - WB5172
Install Oracle Database - WB5173
Install Caliber v4,0 on Order Processings
Freeze Changes to Order Processing Sysl
Test of New Functionality on Order Proce-
Test of New Functionality - WB5177
Install New Order Processing System on f
Test Client Deployment - WB5179
Execute Test Plan - WBS 1B0
Deployment - WBS181
Intall Partner App on Order Processing SV
1-1.4.2.2 Install Partner Application - WB5172
Install Oracle Database - WB5173
Install Caliber v4,0 on Order Processings
Freeze Changes to Order Processing Sysl
Test of New Functionality on Order Proce-
Test of New Functionality - WB5177
Install New Order Processing System on f
Test Client Deployment - WB5179
Execute Test Plan - WBS 1B0
Deployment - WBS181
I
I
I
I
Lid
ХИ1
Chi:
Lid
ХИ1
Chi:
rue rurnidu
rue rurnidu
Add To-Do Item.
Add To-Do Item.
Debug
Debug
Read Only
Read Only
Message View
Message View
Properties
Properties
Surround
Surround
Refactoring
Refactoring
Insert
Insert
Update
Update
Owner)
Owner)
Fold
Unfold
Fold
Unfold
toggle Bookmarks Goto Bookmarks
toggle Bookmarks Goto Bookmarks
Topic Search
Topic Search
Complete class at cursor 5hift+Ctrl+C
Complete class at cursor 5hift+Ctrl+C
Toggle Comment
Toggle Comment
Shift+Ctrl+T
Shift+Ctrl+T
(TObject *Senc
(TObject *Senc
Find
Find
^, Structure | ^CatiberRM Requirements [
^, Structure | ^CatiberRM Requirements [
A^'
A^'
| <J Trace To|
. ^ Trace From|-M-
—^J
| <J Trace To|
. ^ Trace From|-M-
—^J
%. e
%. e
28: 7 Insert
28: 7 Insert
Modified
Modified
■ 1 Code Design ’ History
■ 1 Code Design ’ History
Puc. 51.9. Связывание требования с исходным текстом программыкомандой Insert контекстного меню
В коде, сгенерированном средой C++Builder, показаны:
внутренний идентификатор требования среды Caliber (key);
описание требования (Requirement) и его типа (Туре);
имя пользователя, ответственного за требование (Owner);
приоритет требования (Priority);
вспомогательная информация, например имена среза, проекта и сервера Caliber.
Таким образом в исходный текст файла проекта C++Builder встраивается описание точки связи с требованием системы Caliber.
Привяжем текущий файл .СРР, содержащий точку связи с требованием, к этому требованию, выбранному в среде Caliber. Находясь в редакторе, дадим команду контекстного меню Requirements > Trace То (Требования > Влияние). В результате в список связей Traces То (Влияет на) текущего требования в среде Caliber добавится ссылка на файл .СРР проекта C++Builder.
# Projectl - Borland Developer Studio 2006 - Unit6.cpp
File Edit Search View Refactor Project Run Component Tools StarTeam Window Help I [Default Layout vj ^J ^1
►►• в »4• в »4• CaliberRM Requirements• CaliberRM Requirements? X? XProcessingBusiness RequirementsUser RequirementsFunctional RequirementsDesign RequirementsProject TasksProcessingBusiness RequirementsUser RequirementsFunctional RequirementsDesign RequirementsProject TasksЛЛ$ Welcome Page g) Unit6 ^ CaliberRM .Project: j Order Processing5 Baseline: | Current Baseline^ o' ». ❖* о. x и a ^ <* a .SBjtiBt - м$ Welcome Page g) Unit6 ^ CaliberRM .Project: j Order Processing5 Baseline: | Current Baseline^ o' ». ❖* о. x и a ^ <* a .SBjtiBt - м► 1 Order Processing System Upgrade Rollout - WBS 132 : ф 1.1 Iteration 1 - WBS 133:-' ф 1.1.1 Project Startup-WB5134'ф 1.1.1.1 Review Project Plan Value - WBS1ф 1.1.1.2 Database Changes - WBS 136 ф 1.1.1.3 Certification Requirement - WBS К 1 ф 1.1.1.4 Confirm Resources for Project - W ' ф 1.1,1.5 Project task-WBS 139 ф 1.1,1.6 Build Module - WBS140 ф 1.1.1.7 Order Processing System to Cert L ''Ж■ ф 1.1.1,8 ID Order Processing System Users► 1 Order Processing System Upgrade Rollout - WBS 132 : ф 1.1 Iteration 1 - WBS 133:-' ф 1.1.1 Project Startup-WB5134'ф 1.1.1.1 Review Project Plan Value - WBS1ф 1.1.1.2 Database Changes - WBS 136 ф 1.1.1.3 Certification Requirement - WBS К 1 ф 1.1.1.4 Confirm Resources for Project - W ' ф 1.1,1.5 Project task-WBS 139 ф 1.1,1.6 Build Module - WBS140 ф 1.1.1.7 Order Processing System to Cert L ''Ж■ ф 1.1.1,8 ID Order Processing System Users^ References { 5^ History : £?, Responsibilities j -3 Estimate | ^ Project Mgt# Details s& Traceability I ^ Validation | fi® Discussion^ References { 5^ History : £?, Responsibilities j -3 Estimate | ^ Project Mgt# Details s& Traceability I ^ Validation | fi® DiscussionI Traces FromI Traces FromPro ject StartupPro ject Startup1.1,1,9 Partner Арр Users - WB51431.1,1,9 Partner Арр Users - WB51434> 1.1.1.10 SMS Scripting - WBS1444> 1.1.1.10 SMS Scripting - WBS144441.1,1.11 Notification-WB51451.1,1.11 Notification-WB5145ф 1,1.1.12 Team Kickoff Meeting - WBS146 '±гф 1.1,2 Requirements Capture - WBS147 :+: ф 1.1.3 Analysis & Design - WB5151 it; ф 1.1.4 Implementation - WB5153ф 1,1.1.12 Team Kickoff Meeting - WBS146 '±гф 1.1,2 Requirements Capture - WBS147 :+: ф 1.1.3 Analysis & Design - WB5151 it; ф 1.1.4 Implementation - WB5153J Traces To® Unit6,cppJ Traces To® Unit6,cppЦтаэДоЦтаэДо^Status^StatusWBS 134WBS 134ЖЖЖ' iЖ' iI Tag/ID , ~ (statusC:\tmp\borland\64\62\Unit6.cppI Tag/ID , ~ (statusC:\tmp\borland\64\62\Unit6.cpp
►
►
• в »4
• в »4
• CaliberRM Requirements
• CaliberRM Requirements
? X
? X
Processing
Business Requirements
User Requirements
Functional Requirements
Design Requirements
Project Tasks
Processing
Business Requirements
User Requirements
Functional Requirements
Design Requirements
Project Tasks
Л
Л
$ Welcome Page g) Unit6 ^ CaliberRM .
Project: j Order Processing5 Baseline: | Current Baseline
^ o' ». ❖* о. x и a ^ <* a .SBjtiBt - м
$ Welcome Page g) Unit6 ^ CaliberRM .
Project: j Order Processing5 Baseline: | Current Baseline
^ o' ». ❖* о. x и a ^ <* a .SBjtiBt - м
► 1 Order Processing System Upgrade Rollout - WBS 132 : ф 1.1 Iteration 1 - WBS 133
:-' ф 1.1.1 Project Startup-WB5134
'ф 1.1.1.1 Review Project Plan Value - WBS1
ф 1.1.1.2 Database Changes - WBS 136 ф 1.1.1.3 Certification Requirement - WBS К 1 ф 1.1.1.4 Confirm Resources for Project - W ' ф 1.1,1.5 Project task-WBS 139 ф 1.1,1.6 Build Module - WBS140 ф 1.1.1.7 Order Processing System to Cert L ''Ж
■ ф 1.1.1,8 ID Order Processing System Users
► 1 Order Processing System Upgrade Rollout - WBS 132 : ф 1.1 Iteration 1 - WBS 133
:-' ф 1.1.1 Project Startup-WB5134
'ф 1.1.1.1 Review Project Plan Value - WBS1
ф 1.1.1.2 Database Changes - WBS 136 ф 1.1.1.3 Certification Requirement - WBS К 1 ф 1.1.1.4 Confirm Resources for Project - W ' ф 1.1,1.5 Project task-WBS 139 ф 1.1,1.6 Build Module - WBS140 ф 1.1.1.7 Order Processing System to Cert L ''Ж
■ ф 1.1.1,8 ID Order Processing System Users
^ References { 5^ History : £?, Responsibilities j -3 Estimate | ^ Project Mgt# Details s& Traceability I ^ Validation | fi® Discussion
^ References { 5^ History : £?, Responsibilities j -3 Estimate | ^ Project Mgt# Details s& Traceability I ^ Validation | fi® Discussion
I Traces From
I Traces From
Pro ject Startup
Pro ject Startup
1.1,1,9 Partner Арр Users - WB5143
1.1,1,9 Partner Арр Users - WB5143
4> 1.1.1.10 SMS Scripting - WBS144
4> 1.1.1.10 SMS Scripting - WBS144
4
4
1.1,1.11 Notification-WB5145
1.1,1.11 Notification-WB5145
ф 1,1.1.12 Team Kickoff Meeting - WBS146 '±гф 1.1,2 Requirements Capture - WBS147 :+: ф 1.1.3 Analysis & Design - WB5151 it; ф 1.1.4 Implementation - WB5153
ф 1,1.1.12 Team Kickoff Meeting - WBS146 '±гф 1.1,2 Requirements Capture - WBS147 :+: ф 1.1.3 Analysis & Design - WB5151 it; ф 1.1.4 Implementation - WB5153
J Traces To
® Unit6,cpp
J Traces To
® Unit6,cpp
ЦтаэДо
ЦтаэДо
^Status
^Status
WBS 134
WBS 134
Ж
Ж
Ж' i
Ж' i
I Tag/ID , ~ (status
C:\tmp\borland\64\62\Unit6.cpp
I Tag/ID , ~ (status
C:\tmp\borland\64\62\Unit6.cpp
55j I CaliberRMj I CaliberRM
5
5
j I CaliberRM
j I CaliberRM
$, Structure | <^>CallberRM Requirements!
Puc. 51.10. В цепочку взаимосвязей между требованиями включен файлс исходным текстом
При двойном щелчке на ссылке, задающей файл проекта C++Builder, произойдет автоматическое переключение в редактор C++Builder. Курсор
автоматически устанавливается в точку описания требования, сформированную ранее командой Requirements > Insert (Требования > Вставить). Это позволяет проследить связь между требованием Caliber и файлом проекта C++Builder.
По аналогии с вышеописанным процессом задается и обратная взаимосвязь: от файла проекта C++Builder к следующему требованию Caliber. Для этого в редакторе C++Builder задействуют команду контекстного меню Requirements > Trace From (Требования > Зависимость). Ссылка на текущий файл проекта C++Builder будет добавлена к текущему требованию, выделенному в клиенте Caliber. Она поместится в его список Trace From (Зависит от).
Оценка трудозатрат
В поставку системы CaliberRM включено приложение CaliberRM Estimate Professional для оценки трудозатрат. Оно содержит средства анализа, прогнозирования и моделирования хода развития проекта по сценариям «что, если...».
Приложение Estimate Professional включает также развитые средства отображения состояния проекта.
Q@8Q@8|0e £cfit J^ew §cope adjustments look Jgjndow tjelp PgSOJS.^.|0e £cfit J^ew §cope adjustments look Jgjndow tjelp PgSOJS.^.Р» 5<i, I M»pW4f fР» 5<i, I M»pW4f fEstimated Task» (42%) j Aflocated Tasks(58%)]Estimated Task» (42%) j Aflocated Tasks(58%)]19,619,6Planning OptionsPlanning OptionsEstimated Effort: 7 Total Effort: 17 (Staff-Months)Estimated Effort: 7 Total Effort: 17 (Staff-Months)Project planning й currently not affected byProject planning й currently not affected by18.11(±6%)18.11(±6%)47.6 Days47.6 DaysMean Time to Failute.Mean Time to Failute.47.8 Days47.8 DaysSchedule.Peak Staff.Schedule.Peak Staff.Mean Time to Failure.Mean Time to Failure.(protect not deecribed)Delated Requirements Specification Complete (FDR) -Calibration! у pe: Project Type (Business Systems)(protect not deecribed)Delated Requirements Specification Complete (FDR) -Calibration! у pe: Project Type (Business Systems)Scope (Steff-Monthe)' Expected:Sid DevMin (Sth percentile)Max (95th percentile):Scope (Steff-Monthe)' Expected:Sid DevMin (Sth percentile)Max (95th percentile):® optimal Plan(Constrarrts & priorities set by estimator - Press <F8>® optimal Plan(Constrarrts & priorities set by estimator - Press <F8>-f ‘Jl (new project) --100% (1 000)C' I 3. Functional Requirements -100% (1 088] (Service Mode •• 4% (40)Replenishment Prediction -• 7% (72) Cash Depletion Rate - 11 % (120) Paper Depletion Rate •• 4% (40) Status Query -• 5% (56) Tamper Signal - 7% (72) Receipt Printing •• 5% (56).*'1 Automatic Fee - 5% (56)Г' I Cash Dispensing - 4% 1401(£) Negative Authorization - 4% (40) (£) Foreign Card ID •• 6% (64)-""'"'I Store and Forward Mode - 20% (216) Q SF No Response •• 4% (48) M SF Comm Failure •• 4% (48) У SF Reconnect •• 2% (24) У SF Capacity •• 1% (16) У SF Forward 7% (80)У Deposit to Account -11% (120)Q Withdrawal from Account •• 6% (64) У Account Balance - 7% (72)-f ‘Jl (new project) --100% (1 000)C' I 3. Functional Requirements -100% (1 088] (Service Mode •• 4% (40)Replenishment Prediction -• 7% (72) Cash Depletion Rate - 11 % (120) Paper Depletion Rate •• 4% (40) Status Query -• 5% (56) Tamper Signal - 7% (72) Receipt Printing •• 5% (56).*'1 Automatic Fee - 5% (56)Г' I Cash Dispensing - 4% 1401(£) Negative Authorization - 4% (40) (£) Foreign Card ID •• 6% (64)-""'"'I Store and Forward Mode - 20% (216) Q SF No Response •• 4% (48) M SF Comm Failure •• 4% (48) У SF Reconnect •• 2% (24) У SF Capacity •• 1% (16) У SF Forward 7% (80)У Deposit to Account -11% (120)Q Withdrawal from Account •• 6% (64) У Account Balance - 7% (72)□ Hominal Plan (№56)(AH priorities equally weighted)Effort:Schedule:Peak Staff:□ Hominal Plan (№56)(AH priorities equally weighted)Effort:Schedule:Peak Staff:18.1 Staff-Month7.0 Months3.4 staff18.1 Staff-Month7.0 Months3.4 staff18,1 Staff-Month7.0 Months3.4 staff18,1 Staff-Month7.0 Months3.4 staffSchedule and Effort SimulationSchedule and Effort SimulationSchedule (Months!Schedule (Months!^uMptor» Scalier РЫ .^uMptor» Scalier РЫ .: Planning Options: Planning OptionsQ@®Q@®Planning OptionsPlanning OptionsSchedule (Morths)Schedule (Morths)□ Nominal Plan (50150)□ Nominal Plan (50150)<<® (new project)* • CaliberRM Estimate Professional
Q@8
Q@8
|0e £cfit J^ew §cope adjustments look Jgjndow tjelp PgSOJS.^.
|0e £cfit J^ew §cope adjustments look Jgjndow tjelp PgSOJS.^.
Р» 5<i, I M»pW4f f
Р» 5<i, I M»pW4f f
Estimated Task» (42%) j Aflocated Tasks(58%)]
Estimated Task» (42%) j Aflocated Tasks(58%)]
19,6
19,6
Planning Options
Planning Options
Estimated Effort: 7 Total Effort: 17 (Staff-Months)
Estimated Effort: 7 Total Effort: 17 (Staff-Months)
Project planning й currently not affected by
Project planning й currently not affected by
18.1
1(±6%)
18.1
1(±6%)
47.6 Days
47.6 Days
Mean Time to Failute.
Mean Time to Failute.
47.8 Days
47.8 Days
Schedule.
Peak Staff.
Schedule.
Peak Staff.
Mean Time to Failure.
Mean Time to Failure.
(protect not deecribed)
Delated Requirements Specification Complete (FDR) -
Calibration! у pe: Project Type (Business Systems)
(protect not deecribed)
Delated Requirements Specification Complete (FDR) -
Calibration! у pe: Project Type (Business Systems)
Scope (Steff-Monthe)
' Expected:
Sid Dev
Min (Sth percentile)
Max (95th percentile):
Scope (Steff-Monthe)
' Expected:
Sid Dev
Min (Sth percentile)
Max (95th percentile):
® optimal Plan
(Constrarrts & priorities set by estimator - Press <F8>
® optimal Plan
(Constrarrts & priorities set by estimator - Press <F8>
-f ‘Jl (new project) --100% (1 000)
C' I 3. Functional Requirements -100% (1 088] (Service Mode •• 4% (40)
Replenishment Prediction -• 7% (72) Cash Depletion Rate - 11 % (120) Paper Depletion Rate •• 4% (40) Status Query -• 5% (56) Tamper Signal - 7% (72) Receipt Printing •• 5% (56)
.*'1 Automatic Fee - 5% (56)
Г' I Cash Dispensing - 4% 1401
(£) Negative Authorization - 4% (40) (£) Foreign Card ID •• 6% (64)
-""'"'I Store and Forward Mode - 20% (216) Q SF No Response •• 4% (48) M SF Comm Failure •• 4% (48) У SF Reconnect •• 2% (24) У SF Capacity •• 1% (16) У SF Forward 7% (80)
У Deposit to Account -11% (120)
Q Withdrawal from Account •• 6% (64) У Account Balance - 7% (72)
-f ‘Jl (new project) --100% (1 000)
C' I 3. Functional Requirements -100% (1 088] (Service Mode •• 4% (40)
Replenishment Prediction -• 7% (72) Cash Depletion Rate - 11 % (120) Paper Depletion Rate •• 4% (40) Status Query -• 5% (56) Tamper Signal - 7% (72) Receipt Printing •• 5% (56)
.*'1 Automatic Fee - 5% (56)
Г' I Cash Dispensing - 4% 1401
(£) Negative Authorization - 4% (40) (£) Foreign Card ID •• 6% (64)
-""'"'I Store and Forward Mode - 20% (216) Q SF No Response •• 4% (48) M SF Comm Failure •• 4% (48) У SF Reconnect •• 2% (24) У SF Capacity •• 1% (16) У SF Forward 7% (80)
У Deposit to Account -11% (120)
Q Withdrawal from Account •• 6% (64) У Account Balance - 7% (72)
□ Hominal Plan (№56)
(AH priorities equally weighted)
Effort:
Schedule:
Peak Staff:
□ Hominal Plan (№56)
(AH priorities equally weighted)
Effort:
Schedule:
Peak Staff:
18.1 Staff-Month
7.0 Months
3.4 staff
18.1 Staff-Month
7.0 Months
3.4 staff
18,1 Staff-Month
7.0 Months
3.4 staff
18,1 Staff-Month
7.0 Months
3.4 staff
Schedule and Effort Simulation
Schedule and Effort Simulation
Schedule (Months!
Schedule (Months!
^uMptor» Scalier РЫ .
^uMptor» Scalier РЫ .
: Planning Options
: Planning Options
Q@®
Q@®
Planning Options
Planning Options
Schedule (Morths)
Schedule (Morths)
□ Nominal Plan (50150)
□ Nominal Plan (50150)
<
<
Estimate Quality FairEstimate Quality FairProject labor cost needs to be defined.
Estimate Quality Fair
Estimate Quality Fair
Puc. 51.11. Средство оценки проектных трудозатрат, включенныхв систему CaliberRM
Вопросы
Зачем нужен процесс управления требованиями?
Что такое срез проекта (Baseline)?
Как организована связь среды C++Builder с системой управления требованиями Caliber?
На какие группы делятся требования в системе Caliber?
Какими характеристиками описываются функциональные требования в системе Caliber?
Какими характеристиками описываются бизнес-требования в системе Caliber?
Как требования связываются с исходными текстами проекта C++Builder?
Как визуализируются взаимосвязи между требованиями в системе Caliber?
ГЛАВА 52
Технологии локализации приложений
Чем популярнее программа и чем шире у нее круг пользователей, тем актуальнее со временем становится поддержка в ней разных национальных языков и культурных особенностей. Эти задачи решаются в ходе локализации приложения.
На уровне пользовательского интерфейса важно учитывать как особенности конкретного национального языка, так и характеристики локализованных операционных систем: форму записи дробных чисел, знак валюты, обозначение даты и времени и другие. Адаптацию программы к определенным региональным особенностям называют локализацией.
Локализация — это организация поддержки в программе национальных языков и региональных настроек.
Классическая технология локализации
Задачу отображения стандартного текста формы (пунктов меню, подписей кнопок, надписей) на нескольких национальных языках решают разными способами. До появления интегрированных оболочек наподобие C++Builder программисты практиковали следующий подход: все строковые константы внутри программы, все заголовки и подписи элементов пользовательского интерфейса выделяли в отдельный модуль со списком текстовых строк. Далее в зависимости от заданных настроек препроцессора (см. главу 26) проект собирали с определенным набором текстовых данных. Для каждого национального языка готовился отдельный такой набор.
ПРИМЕР 52.1
Классический подход к локализации приложения
Предположим, нам требуется локализовать программу, окно которой содержит всего одну кнопку. При нажатии этой кнопки программа выдает информационное сообщение.
Создание исходной программы.
Сформируем программу, подлежащую локализации.
Создадим проект VCL Forms Application (Приложение VCL Forms) и подготовим исходную форму приложения, показанную на рисунке 52.1.
В проекте уже присутствуют две строки, потенциально нуждающиеся в переводе. Это заголовок формы Пример и подпись кнопки Нажать.
Star TeamStar Team**SS^ Object Inspector 9 X^ Object Inspector 9 XFormi -| Properties | Events В fAction»[caption Пример jEnabled True jHelpCont 0Formi -| Properties | Events В fAction»[caption Пример jEnabled True jHelpCont 0JJ^ Welcome Page ^ Unit 1. cpp^ Welcome Page ^ Unit 1. cpp-M Пример-M ПримерНажать JНажать JQQ00ВВ;Hint• Visible falseI Drag, Drop and Dock)Docksite falseDragKind dkDrag;Hint• Visible falseI Drag, Drop and Dock)Docksite falseDragKind dkDragDragModt dmManualIl kpDnrk^FakpWl shownDragModt dmManualIl kpDnrk^FakpWl shown□□30:30:
Star Team
Star Team
*
*
S
S
^ Object Inspector 9 X
^ Object Inspector 9 X
Formi -
| Properties | Events В fAction
»[caption Пример jEnabled True jHelpCont 0
Formi -
| Properties | Events В fAction
»[caption Пример jEnabled True jHelpCont 0
J
J
^ Welcome Page ^ Unit 1. cpp
^ Welcome Page ^ Unit 1. cpp
-M Пример
-M Пример
Нажать J
Нажать J
Q
Q
0
0
В
В
;Hint
• Visible false
I Drag, Drop and Dock)
Docksite false
DragKind dkDrag
;Hint
• Visible false
I Drag, Drop and Dock)
Docksite false
DragKind dkDrag
DragModt dmManual
Il kpDnrk^Fakp
Wl shown
DragModt dmManual
Il kpDnrk^Fakp
Wl shown
□
□
30:
30:
S Projectl - Borland Developer Studio 2006 - Unitl .cpp
File Edit Search View Refactor Project Run Component Took
Puc. 52.1. Главная форма приложения, подлежащего локализации
Создадим обработчик нажатия кнопки Нажать.
В коде обработчика присутствует текстовая константа. Она также нуждается в локализации.
void fastcall TForml::ButtonlCllck(TObject ‘Sender! {
ShowMessage("Нажато.");
Локализация констант, определенных в исходном коде программы.
Подготовим локализацию текстовых констант, используемых непосредственно в исходном коде.
Добавим к проекту новый заголовочный файл командой File > New > Other (Файл > Создать > Другое) и выбором значка Header File в разделе C++Builder Projects - C++Builder Files (Проекты C++Builder - Файлы C++Builder). Назовем его, например, Texts.h и сохраним в каталоге проекта.
Определим в новом файле препроцессорную константу, которая будет хранить нужное сообщение.
#define STR_INFO "Нажато."
Заменим в основном модуле прямое обращение к строке ссылкой на препроцессорную константу и подключим заголовочный файл.
#include "texts.h"
void fastcall TForml::ButtonlClick(TObject ‘Sender! (
ShowMessage(STR_INFO);
}
Локализация ресурсов.
Локализации и выносу в отдельный модуль должны быть подвергнуты не только строковые константы, заданные в коде программы на языке C++. Переводу подлежат и строки,
заданные с помощью Инспектора объектов. В текущем примере надо вынести в модуль локализации подпись кнопки и заголовок формы. Физически они хранятся в ресурсах формы.
Создадим обработчик события создания формы. В нем организуем инициализацию свойств элементов управления, значения которых подлежат локализации.
void fastcall TForml::FormCreate(TObject ‘Sender)
// заголовок формы: Capl ion = STR_FORMCAP;
/ / заголовок кнопки:
Bin. Lonl->Caption = STR_PRESS;
В файле Texts.h определим используемые константы.
Ildefine STR_PRESS "Нажать" lldefine STR-FORMCAP "Пример"
Добавление нового языка.
Для переноса приложения на другой язык достаточно перевести все препроцессорные константы— создать их локализованную редакцию. Набор этих констант включается в действующий код в зависимости, например, от препроцессорных настроек.
// Препроцессорная константа RUSSIAN_LANGUAGE. // Если она определена, // используется набор строк на русском языке.
#define RUSSIAN_LANGUAGE
//
Препроцессорная константа
ENGLISH_LANGUAGE.
//
Если она определена,
//
используется набор строк
на английском языке.
//
#define ENGLISH_LANGUAGE
- это определение константы
//
пока закомментировано
#lfdef RUSSIAN_LANGUAGE
#define STR_INFO "Нажато."
#define STR_PRESS "Нажать"
#define STR_FORMCAP "Пример"
#endif
#itdef ENGLISH_LANGUAGE
#define STR_INFO "The button was pressed."
#define STR_PRESS "Press"
#define STR_FORMCAP "Example"
#endif
В данном примере в исходный код, поступающий на вход компилятора, будет включен набор констант, представляющих русскоязычный вариант интерфейса.
Средства автоматической локализации
Среда Borland Developer Studio содержит средства локализации, автоматизирующие большинство этапов классического подхода. Однако эта технология локали-
зации приложения поддерживает лишь программы, создаваемые на базе библиотеки Borland VCL для программ на языке Delphi. Приложения, созданные в средах C++Builder и C#Builder, пока в ней не поддерживаются.
Дополнительные возможности локализации
При переводе программы на другой национальный язык обычно требуется внести в нее ряд дополнений. Они связаны с необходимостью учета региональных настроек пользовательской версии Windows.
Учет методов ввода текста
У многих элементов управления, допускающих ввод символов (например, текстовых полей) имеется ряд свойств, связанных с особенностями локализации.
В ряде языков поддерживается набор символов, отличающийся от стандартного набора ANSI. Чаще всего оригинальные наборы символов задействованы в азиатских и арабских версиях Windows. Для ввода букв этих языков требуется так называемый редактор метода ввода (Input Method Editor, IME).
Редакторы IME допускают ввод символов с нестандартным начертанием (например, иероглифов) с обычной клавиатуры с символами стандартного набора ANSI. Комбинации этих символов программно преобразуются к нужному представлению на экране. Редактор IME и режим приема символов задают в свойствах ImeName и ImeMode.
Свойство BiDiMode определяет направление записи текста. По умолчанию текст записывается слева направо (значение bdLeftToRight). Запись текста справа налево, характерная для арабского языка, задается значением bdRightToleft. При этом для чисел сохраняется запись слева направо.
Региональные настройки
При работе программы нередко необходим программный анализ вводимых пользователем чисел. Такие числа могут быть представлены десятичными дробями.
В локализованных версиях Windows используется разное обозначение десятичного разделителя. В США используется точка, в России принято ставить запятую, в других странах не исключено использование иных символов. По этой причине при анализе строки текста, представляющей число, нельзя рассчитывать на то, что в качестве десятичного разделителя будет использован конкретный символ, например запятая.
В качестве такого разделителя следует использовать значение глобальной переменной Decimalseparator из модуля SysUtils. В эту переменную автоматически заносится корректное значение разделителя, соответствующее настройке текущего экземпляра операционной системы Windows. Важно учитывать, что переменная Decimalseparator хранит не один символ, а строку. В некоторых регионах десятичный разделитель может быть представлен несколькими символами.
Ряд дополнительных глобальных переменных из модуля SysUtils, связанных с локализацией, приведен в таблице 52.1.
Таблица 52.1. Глобальные переменные, представляющие региональные настройки
Имя переменной
Назначение
Currencystring
Обозначение национальной валюты. В русифицированной версии Windows хранит значение “р.” (рубли)
CurrencyFormat
Код способа записи денежных сумм. В русифицированной версии Windows используется значение 1 (обозначение валюты записывается справа от числа без пробела) или 3 (обозначение валюты записывается справа от числа через пробел)
Thousandseparator
Символ, разделяющий группы разрядов в больших числах
CurrencyDecimals
Число знаков после запятой при денежных расчетах. В русифицированной версии Windows принято использовать два разряда, означающие копейки
DateSeparator
Символ, разделяющий числа, означающие день, месяц и год, в записи даты
TimeSeparator
Символ, разделяющий числа, означающие часы, минуты и секунды, в записи времени
SysLocale (тип TSysLocale)
Дополнительная информация о локализации. Задаются способы сравнения строк, порядок их сортировки, форматы значений даты и времени
Программное определение параметров локализации
Многие действия по динамической настройке приложения на региональные особенности Windows можно запрограммировать в коде приложения. Возможность программного переключения раскладки клавиатуры упрощает задачи ввода данных большого объема. При такой работе подчас сбивается регистр ввода, и его настройку важно отслеживать динамически. Для переключения текущей раскладки клавиатуры служит стандартная функция LoadKeyboardLayout (). К ней можно обратиться следующим образом.
if ( CheckBoxl->Checked )
LoadKeyboardLayout("00000419", KLF_ACTIVATE);
else LoadKeyboardLayout("00000409", KLF_ACTIVATE);
Функция LoadKeyboardLayout () выполняет переключение на клавиатурную раскладку, которая задана первым параметром строкового типа. Для русского языка используется код "00000419", для английского — "00000409". Код для других языков можно узнать в документации по программным интерфейсам Win32 API.
Язык локализации системы
Язык локализации системы, установленный по умолчанию, программно узнается С помощью функции GetKeyboardLayoutName ( ).
char sb[1024] ;
GetKeyboardLayoutName(sb);
В переменную sb будет занесена строка "00000419", если включена раскладка на русском языке, или строка "00000409", если раскладка установлена на английский язык.
Настройки экрана
Стандартный класс TScreen библиотеки VCL содержит описание текущих настроек экрана Windows. С его помощью удается подстраивать под эти настройки определенные характеристики работы программы. Класс TScreen доступен в программе через стандартную глобальную переменную Screen.
Ширина и высота экрана монитора в пикселах записаны в свойства DesktopWidth и DesktopHeight глобальной переменной Screen. Размер Рабочего стола может не совпадать с размером экрана монитора. В этом случае координаты левого верхнего угла экрана монитора на «виртуальном» Рабочем столе представлены в свойствах DesktopLeft и DesktopTop, а полный размер экранного пространства записан в свойствах Width и Height. Количество пикселов на один дюйм экрана монитора определяется свойством PixelsPerlnch.
Глобализация
Процесс локализации следует отличать от процесса глобализации приложения.
Глобализация это создание программы, не зависящей от особенностей конкретных культур, национальных языков и региональных настроек.
Глобализация ближе к проектированию программы, когда продумывается ее внутреннее устройство и выбираются общие способы обеспечения ее независимости от культурных особенностей. В ходе локализации эти способы воплощаются в конкретные технические приемы поддержки национальных языков, денежных и календарных форматов и так далее. Обычно в рамках глобализации перед началом локализации все культурно-зависимые ресурсы выделяются в проекте в отдельные файлы.
Перспективы развития систем локализации
У классического подхода к локализации имеется серьезный недостаток. Он заключается в том, что эта технология позволяет сформировать финальное приложение лишь с интерфейсом одном языке, например русскоязычное или англоязычное. Если пользователю потребуется версия программы на другом языке, ему придется полностью ее переустанавливать.
В перспективе системы локализации будут предоставлять средства для динамической смены интерфейса. В классической технологии локализации это может быть достигнуто, например, выделением модуля с описаниями переведенных строк в динамически загружаемую библиотеку.
Доступ к любой текстовой константе организуется через определенную функцию из этой библиотеки. Такая функция получает в качестве параметра идентификатор или кодовое обозначение строки и возвращает в качестве своего значения строку, соответствующую этому обозначению. В процессе работы программы имеется возможности динамически выбирать библиотеку, тем самым переключая интерфейс на другой язык.
Вопросы
Что такое локализация и глобализация приложений?
В чем заключается классический подход к локализации приложений?
Как учесть в приложении региональные настройки операционной системы?
Каковы перспективы технологий локализации?
Алфавитный указатель
ActiveX, технология 177, 468ADO.NET, технология 171ALM (Application LifestyleManagement), концепцияANSI, кодировка 36, 58, 543ASP (Active Server Pages), веб-язык268Assert, функция 306ActiveX, технология 177, 468ADO.NET, технология 171ALM (Application LifestyleManagement), концепцияANSI, кодировка 36, 58, 543ASP (Active Server Pages), веб-язык268Assert, функция 306BCPL (Basic CombinedProgramming Language), язык программмирования 45BDE (Borland Data Engine), набор компонентов 397Borland Caliber, система 528Borland Developer Studio, система 30начальное окно 31C++Builder 2006, система 260настройка 270основное окно 263технические требования 260установка 260Caliber, система 28CIM (Computation IndependentModel), модель 509CLR (Common Language Runtime), среда 170CMM (Capability Maturity Model), модель 513BCPL (Basic CombinedProgramming Language), язык программмирования 45BDE (Borland Data Engine), набор компонентов 397Borland Caliber, система 528Borland Developer Studio, система 30начальное окно 31C++Builder 2006, система 260настройка 270основное окно 263технические требования 260установка 260Caliber, система 28CIM (Computation IndependentModel), модель 509CLR (Common Language Runtime), среда 170CMM (Capability Maturity Model), модель 513CodeGuard, система 83, 319COM (Common Object Model), компонентная модель 177COOL (C-style Object OrientedLanguage), язык программирования 52CORBA (Common Object Request Broker Architecture), технология176CTS (Common Type System), система типов данных 172CTSS, операционная система 45CVS (Concurent Version System), среда 522CodeGuard, система 83, 319COM (Common Object Model), компонентная модель 177COOL (C-style Object OrientedLanguage), язык программирования 52CORBA (Common Object Request Broker Architecture), технология176CTS (Common Type System), система типов данных 172CTSS, операционная система 45CVS (Concurent Version System), среда 522DAC (Discretionary Access Control), модель защиты 342DataSnap. технология 427DCOM, технология 177DLL, динамическая библиотека 147DOM (Document Object Model), модель 488DAC (Discretionary Access Control), модель защиты 342DataSnap. технология 427DCOM, технология 177DLL, динамическая библиотека 147DOM (Document Object Model), модель 488ER (Entity-Relationship), метод 533ER (Entity-Relationship), метод 533FTP, протокол 27FTP, протокол 27GData, технология 468GUID (Global Unique Identifier), 251GData, технология 468GUID (Global Unique Identifier), 251
ActiveX, технология 177, 468
ADO.NET, технология 171
ALM (Application Lifestyle
Management), концепция
ANSI, кодировка 36, 58, 543
ASP (Active Server Pages), веб-язык
268
Assert, функция 306
ActiveX, технология 177, 468
ADO.NET, технология 171
ALM (Application Lifestyle
Management), концепция
ANSI, кодировка 36, 58, 543
ASP (Active Server Pages), веб-язык
268
Assert, функция 306
BCPL (Basic Combined
Programming Language), язык программмирования 45
BDE (Borland Data Engine), набор компонентов 397
Borland Caliber, система 528
Borland Developer Studio, система 30
начальное окно 31
C++Builder 2006, система 260
настройка 270
основное окно 263
технические требования 260
установка 260
Caliber, система 28
CIM (Computation Independent
Model), модель 509
CLR (Common Language Runtime), среда 170
CMM (Capability Maturity Model), модель 513
BCPL (Basic Combined
Programming Language), язык программмирования 45
BDE (Borland Data Engine), набор компонентов 397
Borland Caliber, система 528
Borland Developer Studio, система 30
начальное окно 31
C++Builder 2006, система 260
настройка 270
основное окно 263
технические требования 260
установка 260
Caliber, система 28
CIM (Computation Independent
Model), модель 509
CLR (Common Language Runtime), среда 170
CMM (Capability Maturity Model), модель 513
CodeGuard, система 83, 319
COM (Common Object Model), компонентная модель 177
COOL (C-style Object Oriented
Language), язык программирования 52
CORBA (Common Object Request Broker Architecture), технология176
CTS (Common Type System), система типов данных 172
CTSS, операционная система 45
CVS (Concurent Version System), среда 522
CodeGuard, система 83, 319
COM (Common Object Model), компонентная модель 177
COOL (C-style Object Oriented
Language), язык программирования 52
CORBA (Common Object Request Broker Architecture), технология176
CTS (Common Type System), система типов данных 172
CTSS, операционная система 45
CVS (Concurent Version System), среда 522
DAC (Discretionary Access Control), модель защиты 342
DataSnap. технология 427
DCOM, технология 177
DLL, динамическая библиотека 147
DOM (Document Object Model), модель 488
DAC (Discretionary Access Control), модель защиты 342
DataSnap. технология 427
DCOM, технология 177
DLL, динамическая библиотека 147
DOM (Document Object Model), модель 488
ER (Entity-Relationship), метод 533
ER (Entity-Relationship), метод 533
FTP, протокол 27
FTP, протокол 27
GData, технология 468
GUID (Global Unique Identifier), 251
GData, технология 468
GUID (Global Unique Identifier), 251
HyperCard, система 176HTML, веб-язык 268HTTP (HyperText Transfer Protocol), протокол 27HyperCard, система 176HTML, веб-язык 268HTTP (HyperText Transfer Protocol), протокол 27MSIL (Microsoft IntermediateLanguage), языкпрограммирования 172MSIL (Microsoft IntermediateLanguage), языкпрограммирования 172.NET, операционная платформа 30.NET Framework, оболочка 171.NET, операционная платформа 30.NET Framework, оболочка 171IME (Input Method Editor), редактор 543Indy, технология 470.INI, файл 394Internet Explorer, браузер 169IntraWeb, технология 491IP-адрес, 351IP-канал 351IP-поток 351IME (Input Method Editor), редактор 543Indy, технология 470.INI, файл 394Internet Explorer, браузер 169IntraWeb, технология 491IP-адрес, 351IP-канал 351IP-поток 351OLE, технология 183OSD (Open Software Description), технология 464OWL (Oblect Windows Library), библиотека 48OLE, технология 183OSD (Open Software Description), технология 464OWL (Oblect Windows Library), библиотека 48
HyperCard, система 176
HTML, веб-язык 268
HTTP (HyperText Transfer Protocol), протокол 27
HyperCard, система 176
HTML, веб-язык 268
HTTP (HyperText Transfer Protocol), протокол 27
MSIL (Microsoft IntermediateLanguage), языкпрограммирования 172
MSIL (Microsoft IntermediateLanguage), языкпрограммирования 172
.NET, операционная платформа 30
.NET Framework, оболочка 171
.NET, операционная платформа 30
.NET Framework, оболочка 171
IME (Input Method Editor), редактор 543
Indy, технология 470
.INI, файл 394
Internet Explorer, браузер 169
IntraWeb, технология 491
IP-адрес, 351
IP-канал 351
IP-поток 351
IME (Input Method Editor), редактор 543
Indy, технология 470
.INI, файл 394
Internet Explorer, браузер 169
IntraWeb, технология 491
IP-адрес, 351
IP-канал 351
IP-поток 351
OLE, технология 183
OSD (Open Software Description), технология 464
OWL (Oblect Windows Library), библиотека 48
OLE, технология 183
OSD (Open Software Description), технология 464
OWL (Oblect Windows Library), библиотека 48
POP, протокол 27
POSIX, спецификация 345
Janeva, технология 517
J ASS JavaScript Accessible Style
Sheets), технология 461
Java, язык программирования 50
JDBC, спецификация 51
JSP (Java Server Pages), технология
466
LISP, язык программирования 141
MAC (Mandatory Access Control), модель защиты 342
MCI (Media Control Interface), стандартный интерфейс 339
MDA (Model Driven Architecture), технология 177, 507
MILS (Multiple Independent Levels of Security), модель безопасности 344
архитектура 344
ядро 344
MLS (Multiple Levels Security), модель защиты 343
RSS, технология 468
RTTI (RunTime Type Identification), режим идентификации 218
share-файл 377
SmallTalk, язык
программирования 176
SMTP, протокол 27
SOAP (Simple Object Access
Protocol), протокол 466
SSL (Secure Sockets Layer), технология 350, 478
SQL (Structured Query Language), язык запросов 27
StarTeam, система 522
STL (Standard Template Library), библиотека 388
Structure View, средство просмотра 280
TCP/IP, протокол 430
Turbo C/C++, среда программирования 47
UML (Unified Modeling Language), язык моделирования 177
UNIX, операционная система 46
VCL, библиотека визуальных компонентов 33
VPN (Virtual Public Net), технология 351
WebSnap, технология 271
Win32, платформа 30
Win32 API, интерфейс 170
Winsock, технология 430
WS-Addressing, 468
XLL (Extensible Link Language), язык 464
XML (Extensible Markup Language), технология 464
автоматическое завершение описаний 285
агрегирование 438
анимация 333
спрайтовая 334
аргумент
скалярный 439
функции 129
архитектура
блочная 174
клиент-серверная 425
многозвенная 426 многослойная 426
многоуровневвая 425 распределенная 425
управляемая моделью 177
аспектно-ориентированный подход 505
ассемблер 52
виртуальный 52, 172
атрибут 178
база данных 358
иерархическая модель 361
индект записи 402
многомерная 366
нормализация 365
проектирование 368, 372
реляционная 362, 433
сетевая 361
таблица 358
уровень блокировки 415
байт-код 51
библиотека
Windows Forms 171
динамическая 161
объектных файлов 158
стандартная 159
бизнес-требования 533
битовая маска 334
битовое поле 60
блок кода 107
гарантированное завершение 244
браузер 456
буфер 377
ввод 39
формат 383
форматированный 383
веб-архитектура 425
веб-сервис 273
веб-служба 273, 466
веб-форма 268
подготовка 268
вектор 82
верификатор кода 304
версия
различия 519
версия {продолжение) список 519способ хранения 518визуальная среда быстрой разработки 176виртуальная частная сеть 351 вирус 348всплывающая подсказка 282 вывод 39формат 383форматированный 384выражение 36, 73 логическое 435 порядок вычисления 79гипермедиа 458 гиперссылка 456 гипертекст 457 глобализация 545 графический интерфейс 169 графический курсор 329 группировка 438дамп 318данныеисточник 408конфиденциальность 346 преобразование 485 тип 38типизированные 54 целостность 346декодирование 477дерево требований 530деструктор 216виртуальный 217вызов 217диаграмма ER 372дизайнер 30директива 36диспетчер версий 518драйвер 169дублирование 435версия {продолжение) список 519способ хранения 518визуальная среда быстрой разработки 176виртуальная частная сеть 351 вирус 348всплывающая подсказка 282 вывод 39формат 383форматированный 384выражение 36, 73 логическое 435 порядок вычисления 79гипермедиа 458 гиперссылка 456 гипертекст 457 глобализация 545 графический интерфейс 169 графический курсор 329 группировка 438дамп 318данныеисточник 408конфиденциальность 346 преобразование 485 тип 38типизированные 54 целостность 346декодирование 477дерево требований 530деструктор 216виртуальный 217вызов 217диаграмма ER 372дизайнер 30директива 36диспетчер версий 518драйвер 169дублирование 435
версия {продолжение) список 519
способ хранения 518
визуальная среда быстрой разработки 176
виртуальная частная сеть 351 вирус 348
всплывающая подсказка 282 вывод 39
формат 383
форматированный 384
выражение 36, 73 логическое 435 порядок вычисления 79
гипермедиа 458 гиперссылка 456 гипертекст 457 глобализация 545 графический интерфейс 169 графический курсор 329 группировка 438
дамп 318
данные
источник 408
конфиденциальность 346 преобразование 485 тип 38
типизированные 54 целостность 346
декодирование 477
дерево требований 530
деструктор 216
виртуальный 217
вызов 217
диаграмма ER 372
дизайнер 30
директива 36
диспетчер версий 518
драйвер 169
дублирование 435
версия {продолжение) список 519
способ хранения 518
визуальная среда быстрой разработки 176
виртуальная частная сеть 351 вирус 348
всплывающая подсказка 282 вывод 39
формат 383
форматированный 384
выражение 36, 73 логическое 435 порядок вычисления 79
гипермедиа 458 гиперссылка 456 гипертекст 457 глобализация 545 графический интерфейс 169 графический курсор 329 группировка 438
дамп 318
данные
источник 408
конфиденциальность 346 преобразование 485 тип 38
типизированные 54 целостность 346
декодирование 477
дерево требований 530
деструктор 216
виртуальный 217
вызов 217
диаграмма ER 372
дизайнер 30
директива 36
диспетчер версий 518
драйвер 169
дублирование 435
заголовочный файл 150
компиляция 151
пользовательский 151
стандартный 151
закладка 286, 413
запись 435
добавление 449
дублирование 435
обновление 449
отбор 435
сортировка 327
удаление 449
фильтрация 436, 440
запрос 406
вложенный 446
пользовательский 475
проверка 447
пустой 447
зарезервированное слово 36
значение, возвращаемое
функцией 127
«зомбирование» 348
идентификатор 36
GUID251
глобальный уникальный 251
длина 36
пользовательский 36
императивное программирование 43 имя
альтернативное 257
класса 182
метода 99
индекс 82, 412
вторичный 402
записи базы данных 402
первичный 402
сложного типа 224
создание 452
индексатор 52, 223
индексация 83, 401
инициализация 154
интеллектуальный порядок 154 инкапсуляция 180
Инспектор объектов 269
Инспектор отладки 318 интегрированная оболочка 30 интеллектуальный порядок инициализации 154
интернет-канал 350 защита 350
интерфейс
декларация 250 объектный 249 пользовательский 405 программный 251 реализация 250
информационная безопасность 341 уровень 347
информационная угроза 349
исключительная ситуация 239 анализ 242
«молчаливая» 245 обработка 240 перехват 242
программная генерация 242
класс 175, 178,182 абстрактный 209 атрибут 178 базовый 179 виртуальный 187 вложенный 185 дружественный 225 имя 182 интерфейс 201 исключений 244 метод 178, 200 наследование 179, 186 отложенное определение 185 параметризованный 232 поле 178,193 реализация 201 родительский 179
класс (продолжение) свойство 178 событие 179 структура 182
чисто интерфейсный 188 член 182
характеристика 182
экземпляр 179 элемент 182
класс-шаблон 233
клиент-серверная архитектура 416 ключ 394
закрытый 353
открытый 353
первичный 365, 433 шифрования 355
код
анализ 282
безопасный 52
визуализация 281 генерация 33 исполнимый 34 подсказка 282 свертывание 286 управляемый 172 шаблон 283
кодирование 477
команда 36
SQL 433
обновления 449
комментарий 37, 70 многострочный 71 однострочный 71
компилятор 30, 290
настройка 294
компиляция 26, 290
быстрая 292
директивы 301 «на лету» 52, 172 ошибки 41
условная 298
компонент палитры инструментов визуальный 267 невизуальный 267
компьютерная безопасность 341 консольное приложение 32 константа 36, 67
беззнаковая 56
длинная 56 знаковая 56 именованная 67 короткая 56 мультисимвольная 59 неименованная 67 нетипизированная 68 препроцессорная 298 строковая 68 символьная 59 типизированная 69 числовая 68
конструктор 211
копирования 213 наследование 215 перегрузка 214 порядок вызова 214 скрытый 215
конфигурационное управление 516 конфигурация 295
компиляции 296
настроечная 295
корешок 326
кросс-платформная среда разработки 168
локализация 277, 540 автоматическая 542 констант 541
параметры 544
ресурсов 541 язык 544
макрос 302
массив 81, 99
динамическое создание 105
имя 99
индексация 83
массив (продолжение) инициализация 84 многомерный 84 одномерный 82 описание 81
передача в качестве параметров 132
строк 88
удаление из памяти 106
элемент 85
Мастер приложений 273
масштабируемость 401, 497
матрица 84
Менеджер
истории файлов 517
проекта 269
шаблонов кода 283
метаинформация 358
метаописание 172
метод 178
абстрактный 209
видимость 204
вызов 202
имя 201
класса 200
наследование 204 объявление 200 перезагрузка 206 перезапись 208 переопределение 208 поиск 207
реализация 200
связывание с объектом 206
скрытие 205
статический 203
«сущность-связь» 372
многоуровневое приложение 273
модель 507
«водопад» 512
вычислительно-независимая 509
зрелости программных процессов 513
итеративная 513
платформно-независимая 510
модель {продолжение) платформно-специфичная 510 приложения 509
спиральная разработка 514
модель защиты 342
многоуровневая 343
модуль 150
данных 405
структура 153
набор данных 397
Interbase 419
закрытие 411
модификация 411
однонаправленный 421
открытие 411
поле 407
преобразование 481, 486
указатель 398
фиксация изменений 411
набор компонентов BDE 397
набор элементов
динамическое создание 104
наследование классов 179 множественное 186
нормализация 370
этапы 370
нормальная схема 365
обработчик 237
вызов 237
пользовательского действия 274 системных сообщений 248 события 274
объединение 92
объект 175, 179, 182, 189
адрес 97
видимость 108
инициализация 212
копирование 190
присваивание 190
создание 188
объектно-ориентированное
хранилище данных 366
объектный модуль 290
О-код 45
оперативная память
динамическое выделение 104
оператор 36
SQL 434
вложенный 243
выбора 114
группировки 108
доступа 111
для дробных значений 73
императивный 44
логический 75
определения отношения 77
определения размера объекта 78
перегрузка 228
перехода 124
получения информации о типе 77
поразрядный 76
порядок работы 80
прерывания цикла 122
приоритет 78
присваивания 39, 44, 71
продолжения цикла 123
пустой 107
расширенная форма 112
составной 44, 107
условный 44, 111
для целых значений 73
цикла 44,117
оператор присваивания 39, 44, 71
значение 78
составной 73
оператор цикла 117
безусловный 117
условный 120
операционная система 168
архитектура 169
ядро 168
отладка 35, 304
машинного кода 317
протокол 315
отладочная информация 305
отладчик 30
пошаговый 290
отношение
многие ко многим 372
один к одному 372
один ко многим 372
отчет 529
ошибки компиляции 41, 293
логические 304 семантические 304 синтаксические 293
переменная (продолжение) статическая 145 файловая 378
подзапрос 446
подпрограмма 44, 126
тело 126
подсказка 293
поле
вычисляемое 412, 442 динамическое 407 значение 451
модификация 449
п
пакет 165
использование 166 создание 165
палитра инструментов 265 компонент 266
параметр 126
быстрого вызова 136 неизменяемый 133 необязательный 134 неопределенный 134 фактический 129 формальный 129 функции 129
передача параметра по значению 130 по имени 131
переменная 38, 69 видимость 109 внешняя 159 глобальная 108 декларация 38, 69 инициализация 69 локальная 108 нестабильная 145 область действия 109 объявление 38, 69 описание 38, 69 регистровая 70 скрытие 109 ссылочного типа 82
неопределенное 438
постоянное 407
уникальное 451
поле класса 178, 193
видимость 194, 204
доступ 193
наследование 195
объединение 198
скрытие 195
статическое 197
структура 198
полиморфизм 179
политика безопасности 172
поток 146
ввода-вывода 387
сетевой 350
почтовое вложение 477
почтовое сообщение 476
отправка 476
пошаговое выполнение
дополнительные возможности 311
процедура 310
функция 310
пошаговый отладчик 290
правило
Кодда 361
нулевое 363
представление 453
предупреждение 293
преобразование
арифметическое 65
вещественных значений 64
преобразование {продолжение) неявное 63
перегрузка 231
с потерей значащих цифр 64
расширяющее 72
типов 231
явное 62
препроцессор 298
препроцессорная обработка 297
прерывание 239
по изменению значения 315
приложение
IntraWeb 491
архитектура 396
жизненный цикл 507
запуск 274
защита 349
клиент-серверное 416
клиентское 533
компиляция 274
локализация 505
многоуровневое 273
пустое 273
сборка 291
принцип
пошагового создания программ 36
распространения секретности 344
проект 31
добавление формы 275
компиляция 290
неисполняемый 273
обновление 523
сборка 35,290
система ролей 529
срез 528
Проектировщик 237, 267
документов HTML 268
интерактивный 268
проектировщик форм 30
программа
исполнимая часть 155
исходный текст 34
клиентского уровня 426
командной строки 291
программа {продолжение) компиляция 26
сборка 26
структура 37, 155
троянская 348
программирование императивное 43 объектно-ориентированное 174 структурное 43 экстремальное 513
программирование-в-малом 501
программная инженерия 511
прокси-сервер 350
пространство имен 252
безымянное 254
вложенное 254
доступ к элементам 255
определение 253
по умолчанию 255
протокол
отладки 315
просмотр 316
работы 315
процедура 201
пошаговое выполнение 310
хранимая 367
псевдополе 407
пункт напоминания 288
добавление 288
Рабочий стол 272
отладочная раскладка 310
региональные настройки 543
регистр процессора 318
редактор
исходных текстов 30
свойств 269
редактор среды C++Builder 280
стандартные возможности 280
репозиторий 522
режим
RTTI 218
буферизации 377
режим (продолжение)быстрой компиляции 292визуальный 26идентификации типа во время выполнения 218компиляции 153многопоточности 33направляющих линий 268перетаскиваниия 277полной перестройки проекта 292реинжениринг 510рекурсия 141репозиторий объектов 265сборка 26приложения 35проекта 35сборщик 291, 296настройка 296сведения о системе 351свойство 220категория 277объявление 225создание 221связь 372связываниевиртуальное 208динамическое 163, 207статическое 163, 207семантическая сеть 467сервермножественный 497приложений 426транзакций 498сетевое гнездо 473Симула, язык программирования 174синхронное редактирование 287система контроля версий проектныхфайлов 521система управления базамиданных (СУБД) 27слепок проекта 528режим (продолжение)быстрой компиляции 292визуальный 26идентификации типа во время выполнения 218компиляции 153многопоточности 33направляющих линий 268перетаскиваниия 277полной перестройки проекта 292реинжениринг 510рекурсия 141репозиторий объектов 265сборка 26приложения 35проекта 35сборщик 291, 296настройка 296сведения о системе 351свойство 220категория 277объявление 225создание 221связь 372связываниевиртуальное 208динамическое 163, 207статическое 163, 207семантическая сеть 467сервермножественный 497приложений 426транзакций 498сетевое гнездо 473Симула, язык программирования 174синхронное редактирование 287система контроля версий проектныхфайлов 521система управления базамиданных (СУБД) 27слепок проекта 528
режим (продолжение)
быстрой компиляции 292
визуальный 26
идентификации типа во время выполнения 218
компиляции 153
многопоточности 33
направляющих линий 268
перетаскиваниия 277
полной перестройки проекта 292
реинжениринг 510
рекурсия 141
репозиторий объектов 265
сборка 26
приложения 35
проекта 35
сборщик 291, 296
настройка 296
сведения о системе 351
свойство 220
категория 277
объявление 225
создание 221
связь 372
связывание
виртуальное 208
динамическое 163, 207
статическое 163, 207
семантическая сеть 467
сервер
множественный 497
приложений 426
транзакций 498
сетевое гнездо 473
Симула, язык программирования 174
синхронное редактирование 287
система контроля версий проектных
файлов 521
система управления базами
данных (СУБД) 27
слепок проекта 528
режим (продолжение)
быстрой компиляции 292
визуальный 26
идентификации типа во время выполнения 218
компиляции 153
многопоточности 33
направляющих линий 268
перетаскиваниия 277
полной перестройки проекта 292
реинжениринг 510
рекурсия 141
репозиторий объектов 265
сборка 26
приложения 35
проекта 35
сборщик 291, 296
настройка 296
сведения о системе 351
свойство 220
категория 277
объявление 225
создание 221
связь 372
связывание
виртуальное 208
динамическое 163, 207
статическое 163, 207
семантическая сеть 467
сервер
множественный 497
приложений 426
транзакций 498
сетевое гнездо 473
Симула, язык программирования 174
синхронное редактирование 287
система контроля версий проектных
файлов 521
система управления базами
данных (СУБД) 27
слепок проекта 528
слово
зарезервированное 36
ключевое 36
служебное 36
служба
сборки мусора 172
уведомлений 521
событие 179, 236
обработка 237
сокет 430, 473
сообщение 180
SQL 434
обмен 180
обработка 247
рассылка 249
сортировка 437
состояние 497
специальный символ 36
спецификатор 221
список
задач 299
напоминаний 289
спрайт 334
средство просмотра структуры программы 280
срез проекта 528
ссылка 99
обработка 85
перекрестная 401
стек 318
строка
с нулем в конце 87
форматирование 385
структура 89
анонимная 91
вложенная 91
доступ 102
модуля 153
описание 89
инициализация 90
указатель 102 эквивалентность 91 элемент 90
структурное программирование 43
структурированный 54
СУБД 358
фундаментальный 54
файл-серверная 396
Целое 55
сущность 372
Числовой 55
тип данных 39
т
перечислимый 61
таблица
пользовательский 61
каскадных стилей 461
преобразование 62
ключевое поле 401
производный 55
копия 445
простой 55
модификация 452
создание 93
обработка 450
точка входа 155
перекрестная ссылка 401
настройка 162
расщепление 369
точка прерывания 307
связей 369
условная 313
соединение 442
установка 308
создание 450
требования
стилей 459
взаимосвязь 534
структура 452
пользовательские 530
удаление 453
функциональные 531
таймер 326
троянская программа 348
тег форматирования 456
тестирование 35
У
технология 25
удаленное управление 348
визуального проектирования
удаленный поставщик 487
приложений 26
указатель 95, 99, 189
объектного проектирования 25
доступ 97
файл-серверная 396
значение 97
тип
на массив 100
базовый 54
набора данных 398
Валютное 60
нетипизированный 96
Вещественное 59
объявление 95
Логическое 61
на себя 203
поля 400
синоним 144
приведение 196
файловый 382, 390
производный 54
на функцию 142
простой 54
указатель-константа 103
пустой 96
управляемое расширение 168
результирующий 231
условие 112
Символьное 57
отбора 436
сложный 54
условный оператор 112
составной 54
вложенный ИЗ
ссылок 82
расширенный 112
Строковое 61,86
сокращенный 76, 112
утилита 169
системная 169
формат (продолжение) спецификатор 383
Ф
формат вызова
динамических библиотек 147
файл 375
.INI 394
версия 518
двоичный 382 завершение 380 заголовочный 150 закрытие 377 исполнимый 34 копирование 392 модификация 382, 518 нетипизированный 375 обмен данными 379 обработка 377 объектный 158 открытие 376 подкачки 169 подстановка 300 пользовательский 151 проекта 34 слияние 525 содержимое 519 стандартный 151 текущий 518 типизированный 375 файловый обмен
потоковый 388
типизированный 392 фильтр 413 фильтрация 436 форма
клиентская область 329 нормальная 365 оконная 273
проектирование 267, 274 формат
BDSPRO 34
СРР 34
DFM 34
RES 34
XML 27, 485
невозвращаемого 147 стандартный 148 функций Паскаля 148 функций Си 148
фрейм 176
функция 37,126
абстрактная 209 встраиваемая 142 вызов 127 глобальная 225 дружественная 225 заголовок 127 объявление 127,138 определение 138 основная 37 параметризованная 232 перезагрузка 139 пошаговое выполнение 310 пролог 148 прототип 138 расширение 146 тело 127 эпилог 148
функция-шаблон 232
X
холст 329
подготовка 330 хеширование 354
ц
цвет
элемента управления 278 целостность 433
ссылочная 362 сущностная 362 цикл
вложенный 119 прерывание 122 продолжение 123
цикл (продолжение)
счетчик 120
управление работой 122
шаблон 232
кода 283
тонкая настройка 285
шифрование 353
ассиметричное353
симметричное 353
технологии 353
эквисоединение 444
экземпляр 179
электронная почта 476
прием 476
электронно-цифровая подпись 346
элемент 277
выравнивание 278
данных 38
массива 82
элемент (продолжение) набор 104 предложения 36 программы 36 структуры 90
удаление из памяти 105 управления 278
элемент класса 182 видимость 183 переопределение 195 скрытие 195
язык
гипертекстовой разметки 456
запросов SQL 27, 363
локализации системы 544
язык программирования
высокого уровня 25
логический 44
объектный 44
со строгой типизацией 44
функциональный 44
Бобровский Сергей Игоревич
Технологии C++Builder
Разработка приложений для бизнесаУчебный курс
А. Кривцов Ю. Сергиенко Г. Евсеев И. СимоновичЕ. Дьяченко О. Голотвина И. СимоновичА. Кривцов Ю. Сергиенко Г. Евсеев И. СимоновичЕ. Дьяченко О. Голотвина И. СимоновичЗаведующий редакцией Ведущий редактозр Научный редактор Литературный редактор Художник обложки Корректор
А. Кривцов Ю. Сергиенко Г. Евсеев И. Симонович
Е. Дьяченко О. Голотвина И. Симонович
А. Кривцов Ю. Сергиенко Г. Евсеев И. Симонович
Е. Дьяченко О. Голотвина И. Симонович
Верстка
Подписано в печать 29.12.06. Формат 70x100/16. Усл. п. л. 45,15.
Тираж 3500. Заказ 3638.
ООО «Питер Пресс>?. 198206. Санкт-Петербург, Петергофское шоссе, д. 73. лит. А29.Налоговая льгота — общероссийский классификатор продукцииОК 005-93, том 2; 95 3005 — литература учебная.
Отпечатано по технологии С1Р в ОАО «Печатный двор» им. А. М. Горького.197110. Санкт-Петербург, Чкаловский пр., д. 15.
2 Программа командной строки — это консольная программа с текстовым пользовательским интерфейсом. Настройки для ее работы передаются через параметры командной строки, задаваемые при вызове программы.
7 void fastcaJUL TForml: : ButtoniClick (TObject *Sende:
int а[ 10] ;
1 Чтобы присвоить пункту меню «горячую клавишу», надо перед соответствующей буквой в его имени ввести символ &, например Вст&авка.