Объектно-ориентированное программирование на С++ - eDrive
Объектно-ориентированное программирование на С++ - eDrive
Объектно-ориентированное программирование на С++ - eDrive
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Федеральное агентство по образованию<br />
_________________________________________<br />
Санкт-Петербургский государственный<br />
электротехнический университет «ЛЭТИ»<br />
____________________________________________________________<br />
В. М. Водовозов Ф. В. Чмиленко<br />
<strong>Объектно</strong>-<strong>ориентированное</strong><br />
<strong>программирование</strong> <strong>на</strong> <strong>С++</strong><br />
Учебное пособие<br />
Санкт-Петербург<br />
Издательство СПбГЭТУ «ЛЭТИ»<br />
2007
ББК 32.973.26-018<br />
В62<br />
УДК 681.3.016 (018)<br />
Водовозов В. М., Чмиленко Ф. В. <strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong><br />
<strong>на</strong> <strong>С++</strong>: Учеб. пособие. СПб.: Изд-во СПбГЭТУ «ЛЭТИ», 2007.<br />
128 с.<br />
Дано описание языка <strong>С++</strong>. Представлены основные этапы процедурного,<br />
структурного, модульного и объектно-ориентированного программирования<br />
под Windows. Показаны особенности работы с библиотеками, процедурами,<br />
функциями, файлами, базами данных и справочными системами.<br />
Пред<strong>на</strong>з<strong>на</strong>чено для студентов <strong>на</strong>правления 654500 – «Электротехника,<br />
электромеханика и электротехнологии» по специальностям: 180100 – «Электромеханика»,<br />
180400 – «Электропривод и автоматизация промышленных<br />
установок и робототехнических комплексов», 180500 – «Электротехнологические<br />
установки и системы».<br />
Рецензенты: кафедра информационных технологий СПбИДПО;<br />
директор Центра профессио<strong>на</strong>льной подготовки «ИЛН Системс»<br />
О. В. Степанова<br />
Утверждено<br />
редакционно-издательским советом университета<br />
в качестве учебного пособия<br />
ISBN 5-7629-0567-5 © В. М. Водовозов, Ф. В. Чмиленко, 2007
Оглавление<br />
Предисловие............................................................................................................. 5<br />
Введение в процедурное <strong>программирование</strong> ....................................................... 7<br />
Редактор кода.................................................................................................... 7<br />
Функции............................................................................................................. 8<br />
Выражения ...................................................................................................... 10<br />
Классы форм ................................................................................................... 14<br />
Обработка событий ........................................................................................ 16<br />
Программирование графики.......................................................................... 19<br />
Исключения..................................................................................................... 20<br />
Простые типы данных и операторы ............................................................. 22<br />
Тип AnsiString ................................................................................................. 26<br />
Контрольные вопросы.................................................................................... 30<br />
Составные операторы ........................................................................................... 32<br />
Операторы перехода....................................................................................... 32<br />
Условный оператор ........................................................................................ 33<br />
Оператор выбора ............................................................................................ 34<br />
Оператор цикла............................................................................................... 35<br />
Операторы повторения .................................................................................. 36<br />
Программирование диалогов ........................................................................ 38<br />
Контрольные вопросы.................................................................................... 39<br />
Введение в структурное <strong>программирование</strong> ...................................................... 40<br />
Массивы........................................................................................................... 40<br />
Перестановки и сортировки .......................................................................... 43<br />
Списки TStringList.......................................................................................... 46<br />
Структуры и объединения............................................................................. 49<br />
Ди<strong>на</strong>мические структуры данных ................................................................ 51<br />
Стандарт<strong>на</strong>я библиотека STL........................................................................ 59<br />
Контрольные вопросы.................................................................................... 64<br />
Введение в модульное <strong>программирование</strong>......................................................... 65<br />
Работа с текстовыми файлами ...................................................................... 65<br />
Работа с графическими файлами .................................................................. 71<br />
Работа с двоичными файлами ....................................................................... 74<br />
Модули DLL и пакеты ................................................................................... 75<br />
Ресурсы............................................................................................................ 78<br />
Потоки ............................................................................................................. 79<br />
Дистрибутивы ................................................................................................. 81<br />
Контрольные вопросы.................................................................................... 83<br />
Приложения баз данных ....................................................................................... 83<br />
Технологии создания приложений баз данных........................................... 84<br />
Архитектуры приложений баз данных......................................................... 86<br />
Подключение к базам данных....................................................................... 88<br />
Доступ к записям таблиц ............................................................................... 90
Работа с запросами ......................................................................................... 92<br />
Связывание таблиц и запросов ..................................................................... 93<br />
Контрольные вопросы.................................................................................... 95<br />
<strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong>................................................ 95<br />
Инкапсуляция.................................................................................................. 95<br />
Конструкторы и деструкторы ....................................................................... 99<br />
Дружественные функции и классы.............................................................103<br />
Полиморфизм................................................................................................105<br />
Шаблоны........................................................................................................109<br />
Наследование ................................................................................................113<br />
Виртуальные функции и абстрактные классы ..........................................117<br />
Контрольные вопросы..................................................................................120<br />
Приложение. Программные документы ...........................................................122<br />
Список литературы .............................................................................................124<br />
Предметный указатель........................................................................................125
5<br />
Предисловие<br />
Напрасно обучение без мысли.<br />
Из Конфуция<br />
Программирование не чуждо человеческой природе. Тот, кто пишет и<br />
понимает программы, – не робот и не «создание не от мира сего». Программирование<br />
– это разновидность моделирования, а без модели, упрощения и<br />
обобщения нет соз<strong>на</strong>ния. Программирование естественно вытекает из нужд и<br />
потребностей современного культурного общества.<br />
Более того, если естественный язык – это в первую очередь средство<br />
общения, то искусственный – это инструмент поз<strong>на</strong>ния. В этом кроется великая<br />
польза программирования. И в этом видится смысл известного высказывания<br />
Н. Винера о том, что «вычислитель<strong>на</strong>я маши<strong>на</strong> цен<strong>на</strong> ровно <strong>на</strong>столько,<br />
<strong>на</strong>сколько ценен использующий ее человек».<br />
«Прекрасное – трудно», считал Платон. И <strong>программирование</strong> далеко не<br />
просто. Оно требует внимания и терпения. Многие студенты из<strong>на</strong>чально не<br />
обладают этими качествами. Но это не з<strong>на</strong>чит, что они не могут <strong>на</strong>учиться<br />
программированию, а оз<strong>на</strong>чает лишь, что такие черты требуется выработать<br />
в ходе учебного процесса. Для этого воспользуйтесь некоторыми приемами<br />
освоения того, что Д. Кнут <strong>на</strong>звал «искусством программирования» [6].<br />
Во-первых, постарайтесь максимально использовать мощь визуального<br />
программирования. Визуальное сродни и ребенку, и взрослому, оно уз<strong>на</strong>ваемо,<br />
в нем много ассоциаций с жизнью. Визуальные объекты проще воспринимаются,<br />
в них сложнее сделать ошибку и легче такую ошибку <strong>на</strong>йти [1][2].<br />
Сам по себе принцип визуального программирования служит прекрасным<br />
стимулом для создания хотя бы внешне неповторимых программ. Уникальный<br />
дизайн форм и элементов управления удается всем. Исходя из этого, при<br />
конструировании своей программы приложите все усилия, чтобы ваш интерфейс<br />
отличался от других. Здесь <strong>на</strong> помощь может прийти пособие [4],<br />
предшествующее данному изданию.<br />
Но красивое приложение – это еще не программа, а просто пятно <strong>на</strong> экране.<br />
Программа – как девушка: нравится за внешнось, любится за содержание.<br />
Поэтому одно из первых умений программирования – это умение алго-
6<br />
ритмизировать поведение объектов, раскладывать его <strong>на</strong> действия, совершаемые<br />
во времени. Алгоритм – предшественник логики [10]. Если вы не в<br />
состоянии последовательно описать работу будущего приложения, то, скорее<br />
всего, вы не сможете и реализовать его <strong>на</strong> языке программирования. Начните<br />
с построения простейшего линейного приближения. Затем детализируйте<br />
свой алгоритм ветвлениями. Наконец, организуйте циклы. Продумывайте<br />
каждую разработку <strong>на</strong> уровне алгоритма, так как «если не поставить себя в<br />
жесткие рамки с самого <strong>на</strong>чала, программа превратится в огромного монстра,<br />
не поддающегося отладке» [8].<br />
Позаботьтесь, также, об уникальности имен переменных, констант,<br />
функций и файлов своих проектов. Давать свои (не такие, как в учебнике или<br />
у товарища) име<strong>на</strong> трудно, потому что для этого <strong>на</strong>до иметь свое мнение, которое<br />
невозможно сформировать без понимания различий между стандартными<br />
и авторскими элементами программы. Но это хороший стимул, так как<br />
является одним из первых элементов творчества.<br />
В ходе работы <strong>на</strong>д программой полезен коллективный подход. Од<strong>на</strong>ко,<br />
принимая помощь, не выпускайте из рук мышки, и<strong>на</strong>че помощь «прошмыгнет»<br />
мимо вас и вся достанется тому, кто вам помогает («хочешь <strong>на</strong>учиться<br />
сам, <strong>на</strong>учи товарища»).<br />
«Ясные мысли выражаются ясным и понятным языком», подметил<br />
Р. Гамзатов. Красивое оформление исходного текста программы тоже требует<br />
умений. Кроме того, оно вырабатывает дисциплину программирования,<br />
проясняет мысль, учит уважать читателя текста, повышает производительность<br />
разработки приложений. Когда использовать тот или иной з<strong>на</strong>к пунктуации,<br />
где вставлять отступы, пробелы и чистые строки, как использовать<br />
строчные и прописные буквы, что такое «говорящие име<strong>на</strong>» – эти и другие<br />
вопросы требуют серьезного внимания.<br />
Давая эти рекомендации, авторы отлично понимают, что самое простое<br />
в жизни – это давать советы, а самое сложное – чужим советам следовать.<br />
Так что совсем не обязательно с <strong>на</strong>ми соглашаться.<br />
Просто примите к сведению и идите своим путем.
7<br />
Введение в процедурное <strong>программирование</strong><br />
Упрощайте сложное, и вы получите<br />
самое существенное.<br />
Г. Бокль<br />
Концепция процедурного программирования предполагает «деление<br />
проблемы <strong>на</strong> такое множество задач, чтобы, решив каждую из них, можно<br />
было прийти к решению всей проблемы» [6]. Каждой задаче приводится в<br />
соответствие отдель<strong>на</strong>я процедура. В языках С/<strong>С++</strong>, С#, Java процедуры<br />
представлены функциями, в Visual Basic и Pascal – функциями и подпрограммами.<br />
Благодаря такой процедурной декомпозиции по принципу «разделяй<br />
и властвуй» легче управлять большими и сложными программами [8].<br />
О<strong>на</strong> обеспечивает логическую сегментацию проблемы и упрощает отладку.<br />
Концепция базируется <strong>на</strong> механизме стандартных типов данных – категорий<br />
(разрядов), <strong>на</strong> которые подразделяются данные в соответствии со способами<br />
их хранения и обработки. Без типизации невозможно <strong>программирование</strong>.<br />
В отличие от Excel, Mathcad или Visual Basic, язык С/<strong>С++</strong> не «прикрывает»<br />
типизацию автоматическим присваиванием типа по умолчанию, поэтому<br />
разбираться с типами данных следует сразу, не откладывая этот вопрос<br />
«до сессии».<br />
Редактор кода<br />
Файлы программ обрабатываются <strong>на</strong> вкладках ок<strong>на</strong> редактора кода, запускаемого<br />
через меню View.Units (), View.Toggle Form/Unit<br />
(). Вкладки с текстами открываются в редакторе кода через меню File<br />
.Open. Переход между ними удобно выполнять с помощью <strong>на</strong>вигационных<br />
стрелок в правом верхнем углу ок<strong>на</strong>. В левом поле выделения ок<strong>на</strong> щелчком<br />
можно расставлять и снимать точки прерывания выполнения программы. Переключение<br />
между кодами и файлами заголовков выполняется через контекстные<br />
меню Open Source/Header File () и Open File at Cursor (). Щелчки по име<strong>на</strong>м в текстах программ при <strong>на</strong>жатой клавише <br />
превращают их в гиперссылки (hotspot), с помощью которых обычно перемещаются<br />
между связанными файлами. Другие возможности системы редактирования<br />
Code Insight приведены в табл. 1:
8<br />
Подсказки в системе редактирования Code Insight<br />
Таблица 1<br />
Технология Клавиши Наз<strong>на</strong>чение<br />
Code Completion Список членов класса после стрелки –> и точки<br />
Code Parameters – Список параметров функции после скобки (<br />
Code Templates Список шаблонов программирования<br />
Tooltip Expression<br />
Подсказка з<strong>на</strong>чения переменной под курсором в<br />
Evaluation<br />
–<br />
окне отладчика<br />
Tooltip Symbol Insight – Подсказка объявления объекта в редакторе кода<br />
Функции<br />
Исходный текст программы <strong>на</strong> языке C++ составляют выражения,<br />
сгруппированные в функции.<br />
Функции – это единственный вид процедур, используемый в C++. Будучи<br />
разновидностью процедуры, функция (function) представляет относительно<br />
автономную именованную часть программы, описывающую поведение<br />
программных объектов через действия <strong>на</strong>д данными. Программирование<br />
с использованием функций сокращает размеры исходного текста и упрощает<br />
его организацию, хотя «экономия пространства не экономит времени» [6].<br />
В отличие от простой процедуры, функция обычно имеет возвращаемое<br />
з<strong>на</strong>чение (return value), то есть результат своей деятельности в вызывающем<br />
ее выражении.<br />
Каждая функция может многократно вызываться из других функций. В<br />
момент вызова функция получает исходные данные и <strong>на</strong>чи<strong>на</strong>ет выполнять<br />
предписанные действия <strong>на</strong>д ними.<br />
Различают встроенные библиотечные и авторские функции. Первые<br />
хранятся в библиотеках C++. Для доступа к библиотечным функциям в <strong>на</strong>чале<br />
текста программы указываются име<strong>на</strong> соответствующих файлов заголовков:<br />
#include < имяФайлаЗаголовка ><br />
или<br />
#include «имяФайлаЗаголовка»<br />
Файлы заголовков – «это хранилища информации, содержащие описания<br />
констант и объявления функций» [9]. Каждый такой файл сообщает компилятору<br />
име<strong>на</strong> и характеристики функций из других файлов и библиотек,
9<br />
которые используются в программе. Файлы заголовков главных библиотек<br />
фирмы Borland приведены в табл. 2.<br />
Таблица 2<br />
Файл заголовка<br />
alloc.h<br />
bios.h<br />
conio.h<br />
ctype.h<br />
dir.h<br />
fstream.h<br />
iomanip.h<br />
iostream.h<br />
math.h<br />
process.h<br />
stdio.h<br />
string.h, stdlib.h<br />
time.h<br />
vcl.h<br />
window.h<br />
Файлы заголовков библиотек фирмы Borland<br />
Наз<strong>на</strong>чение<br />
Функции распределения памяти<br />
Функции интерфейса с BIOS<br />
Функции консольного ввода и вывода<br />
Функции классификации символов<br />
Функции доступа к файлам<br />
Функции ввода и вывода файловых потоков<br />
Функции-манипуляторы потоков<br />
Функции потокового ввода и вывода<br />
Математические функции<br />
Функции управления процессами<br />
Функции стандартного ввода и вывода<br />
Функции преобразования строк<br />
Функции времени и даты<br />
Функции библиотек VCL<br />
Функции для работы в среде Windows<br />
Выражение, <strong>на</strong>чи<strong>на</strong>ющееся символом #, <strong>на</strong>зывают директивой препроцессора<br />
(preprocessor directive). Если имя файла заголовка в директиве<br />
препроцессора заключено в угловые скобки, компилятор отыскивает его в<br />
специальной папке \include. Если же это имя заключено в кавычки, поиск будет<br />
<strong>на</strong>чат из текущей папки.<br />
В текстах программ C++Builder много директив препроцессора, служащих<br />
инструкциями для компилятора. Все они располагаются в <strong>на</strong>чале исходного<br />
текста (табл. 3).<br />
Таблица 3<br />
Директивы препроцессора<br />
Директива препроцессора<br />
#include <br />
#include «файлЗаголовка»<br />
#define имяМакроса (список<br />
параметров) текстМакроса<br />
#pragma argsused<br />
#pragma hdrstop<br />
#pragma package (smart_init)<br />
#pragma resource "*.dfm"<br />
#ifdef (#if defined)… #endif<br />
#ifndef … #define… #endif<br />
Наз<strong>на</strong>чение<br />
Заменить строку содержимым файла заголовка<br />
Создать макрос<br />
Запрет вывода сообщений о неиспользуемых параметрах<br />
Ограничение списка ранее компилированных заголовков<br />
Инициализировать пакеты в порядке их объявления<br />
В качестве файла ресурсов использовать файлы .DFM<br />
Если имя ранее определено, выполнить все до #endif<br />
Если имя еще не определено, выполнить все до #endif
10<br />
С применением авторских функций связаны три понятия: объявление<br />
функции, определение функции и вызов функции. Каждая функция только<br />
один раз определяется (declare) (описывается) в тексте. При этом ей присваивается<br />
уникальное имя и сообщается <strong>на</strong>бор параметров (parameter), то<br />
есть данных, которые должны поступить к ней прежде, чем подпрограмма<br />
приступит к их обработке. В ряде случаев определению функции предшествует<br />
ее объявление (announcement). Объявления и определения функций могут<br />
выноситься в авторские библиотеки. Каждый программист помещает<br />
нужные ему функции в файлы заголовков и при необходимости обращается к<br />
этим файлам из файлов кодов, получая доступ к созданным ранее функциям.<br />
Такое обращение <strong>на</strong>зывается вызовом (call). При вызове функции передаются<br />
аргументы (argument), то есть з<strong>на</strong>чения параметров. В процессе компиляции<br />
автоматически создается список всех функций, необходимых обрабатываемому<br />
файлу, а в ходе последующей компоновки уста<strong>на</strong>вливаются необходимые<br />
связи.<br />
Име<strong>на</strong> функциям, равно как и переменным, константам, определяемым<br />
в программах, даются программистами. Име<strong>на</strong> чувствительны к регистру,<br />
<strong>на</strong>чи<strong>на</strong>ются с буквы или символа подчеркивания, не содержат пробелов и<br />
иных символов, кроме подчеркивания, цифр и латинских букв, и отличаются<br />
от лексем, служащих име<strong>на</strong>ми встроенных функций, операторов, констант и<br />
других элементов языка. Име<strong>на</strong> функций расширяют <strong>на</strong>бор лексем языка<br />
программирования, используемый в данной программе; они включаются в<br />
выражения <strong>на</strong>ряду с лексемами языка.<br />
Обязательной частью программы является единствен<strong>на</strong>я глав<strong>на</strong>я функция<br />
(main function), обычно WinMain, расположен<strong>на</strong>я в файле кода проекта.<br />
Раздел объявлений и другие функции необязательны. Когда компьютер приступает<br />
к выполнению программы, он <strong>на</strong>чи<strong>на</strong>ет ее с первой строки главной<br />
функции, независимо от взаимного расположения функций в программе.<br />
Выражения<br />
Любое объявление является выражением, содержащим сведения об области<br />
определения и области з<strong>на</strong>чений данных. Объявления функций <strong>на</strong>зывают<br />
прототипами (prototype), т.к. они представляют собой своеобразный<br />
шаблон, образец использования функции:
11<br />
[Модификатор] [типФункции] имяФункции<br />
( типПараметра [ имяПараметра [ = З<strong>на</strong>чение ]]...);<br />
Здесь и далее используется метаязык описания языка программирования,<br />
в котором в прямоугольные скобки принято заключать необязательные<br />
части выражений, а многоточием отмечается возможность повторения элементов<br />
синтаксической формулы.<br />
Все выражения в C++, кроме директив препроцессора и комментариев,<br />
завершаются точкой с запятой. Длинные выражения можно разбивать символом<br />
\ – приз<strong>на</strong>ком продолжения <strong>на</strong> следующей строке. В круглых скобках<br />
помещаются сведения о параметрах функции. При этом части из них, расположенной<br />
в конце списка параметров, можно сразу присваивать з<strong>на</strong>чения –<br />
это <strong>на</strong>зывается инициализацией по умолчанию (default initialization). Тип<br />
функции (function type) характеризует возвращаемое з<strong>на</strong>чение, а тип параметра<br />
(parameter type) – множество входных данных, обрабатываемых этой<br />
функцией. Кроме объявления встроенных функций из стандартных библиотек,<br />
<strong>на</strong>ходящихся в файлах заголовков, авторские функции, создаваемые программистом,<br />
также могут включаться в файлы заголовков. Но часто их объявляют<br />
и затем определяют прямо в тексте программы. В тех случаях, когда<br />
функция определяется до ее вызова, объявлять функции необязательно. C++<br />
не допускает объявления и определения функций внутри других функций:<br />
Формат определения авторской функции:<br />
Заголовок {<br />
[ объявлениеДанных...]<br />
Выражение…<br />
[ return возвращаемоеЗ<strong>на</strong>чение;] }<br />
Заголовок (caption) определяемой функции является своего рода копией<br />
ее объявления, но обязательно содержит име<strong>на</strong> параметров и не завершается<br />
точкой с запятой. Внутри фигурных скобок в определении функции заключается<br />
ее тело (body). Текст программы содержит выражения, состоящие из<br />
переменных, констант, операторов, вызовов функций и заканчивающиеся<br />
точкой с запятой. Фигурными скобками текст может разбиваться <strong>на</strong> отдельные<br />
блоки.<br />
Обычно каждый блок обрабатывает свои переменные и константы, «не<br />
заботясь» о других данных программы. В этом «правиле ограниченной видимости»<br />
заключается автономность, независимость блоков и функций. Дан-
12<br />
ные, обрабатываемые внутри блока, <strong>на</strong>зывают локальными (local variable),<br />
личными или автоматическими. Память под них выделяется <strong>на</strong> время работы,<br />
а по завершении работы функции их з<strong>на</strong>чения так же автоматически стираются,<br />
освобождая место в памяти переменным других частей программы.<br />
В C++ разрешено совмещать определение коротких функций с их объявлением<br />
с помощью лексемы inline. Благодаря этому механизму, код функции<br />
автоматически встраивается в то место, откуда происходит ее вызов, и<br />
вызов происходит з<strong>на</strong>чительно быстрее.<br />
Если требуется сохранять з<strong>на</strong>чения локальных переменных между вызовами<br />
функций, их объявления с<strong>на</strong>бжают модификатором static. В момент<br />
объявления статическую переменную можно явно инициализировать. Если<br />
этого не сделать, то при первом вызове ей автоматически присваивается нулевое<br />
з<strong>на</strong>чение. При повторных вызовах выражение инициализации игнорируется.<br />
Модификатор register <strong>на</strong>правляет локальную переменную <strong>на</strong> сохранение<br />
в регистры процессора. А модификатор volatile явно указывает, что локаль<strong>на</strong>я<br />
перемен<strong>на</strong>я «изменчива», то есть может в любой момент поменять<br />
свое з<strong>на</strong>чение.<br />
Кроме локальных данных, в программах существуют глобальные переменные<br />
(global variable) и глобальные константы (global constant), доступные<br />
из разных частей программы. Они размещаются в разделе объявлений файла<br />
и помогают функциям обмениваться информацией, минуя параметры, но порой<br />
снижают <strong>на</strong>дежность программной системы и увеличивают размер памяти,<br />
занимаемой программой в ходе выполнения.<br />
Внешний модификатор extern информирует компилятор о существовании<br />
внешних данных, <strong>на</strong>ходящихся в другом файле кода программы. За<br />
внешними и глобальными данными память закрепляется постоянно <strong>на</strong> все<br />
время работы программы, а в ходе первого вызова они инициализируются<br />
нулем.<br />
Каждый вызов функции представляет собой запрос <strong>на</strong> совершение определенных<br />
действий. Он осуществляется в соответствии с объявленным<br />
прототипом и семантической организацией программы:<br />
имяФункции ([ списокАргументов ]);
13<br />
В ответ <strong>на</strong> вызов копия кода функции передается в вызывающую<br />
функцию для обработки, после чего о<strong>на</strong> удаляется из памяти. Исключением<br />
являются функции, объявленные с модификатором inline, коды которых обрабатываются<br />
непосредственно. При вызове функций параметры, инициализированные<br />
по умолчанию, могут быть опущены в списке передаваемых аргументов.<br />
В любой части программы можно помещать комментарии – тексты, обрамленные<br />
символами /* */, или фрагменты строк, <strong>на</strong>чи<strong>на</strong>ющиеся символами<br />
//, не влияющие <strong>на</strong> ход выполнения программы.<br />
Ниже приведен пример текста файла проекта <strong>на</strong> языке C++Builder:<br />
#include <br />
#pragma hdrstop<br />
// Заголовочный файл описания классов VCL<br />
// Ограничитель списка прекомпилированных<br />
// заголовочных файлов<br />
// Файлы и име<strong>на</strong> форм проекта<br />
USEFORM ("bMainForm.cpp", MainForm);<br />
USEFORM ("bDataBase.cpp", DataBase);<br />
USEFORM ("bDiagram.cpp", Diagram);<br />
USEFORM ("bAbout.cpp", About);<br />
WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int)<br />
{ try {<br />
Application–>Initialize ();<br />
// Глав<strong>на</strong>я<br />
// функция<br />
// Инициализация OLE<br />
// Создание формы<br />
Application–>CreateForm (__classid (TMainForm), &MainForm);<br />
Application–>Run ();<br />
// Запуск приложения<br />
}<br />
catch (Exception &exception)<br />
{ Application–>ShowException (&exception); }<br />
catch (...) {<br />
try { throw Exception (""); }<br />
catch (Exception &exception)<br />
{ Application–>ShowException(&exception); }<br />
}<br />
return 0;<br />
}<br />
// Обработка исключений<br />
Здесь директива препроцессора #include подключает к тексту<br />
файл заголовка, ссылающийся <strong>на</strong> описание классов VCL, а директива<br />
#pragma hdrstop ограничивает список файлов заголовка, доступных для<br />
предварительной компиляции. Директивы USEFORM сообщают о модулях,
14<br />
используемых в проекте. Выражение Application–>Initialize () инициализирует<br />
OLE-серверы автоматизации. Каждое выражение Application–>CreateForm ()<br />
создает отдельную форму, а выражение Application–>Run() запускает приложение,<br />
переводя его в состояние ожидания <strong>на</strong>ступления событий. Конструкция<br />
try…catch используется для корректного завершения приложения в случае<br />
возникновения ошибки.<br />
Классы форм<br />
Меню View.Class Explorer открывает окно обозревателя классов. В нем<br />
представле<strong>на</strong> иерархия классов с указанием типов, член-данных, членфункций<br />
и глобальных переменных, входящих во все файловые модули.<br />
Каждой форме соответствует файл заголовка с определением ее класса<br />
в формате<br />
<strong>на</strong>пример:<br />
class имяКласса: public TForm<br />
{ [[ Метка ] обявлениеЧленов...]...};<br />
class TForm1 : public TForm {<br />
_ _published:<br />
TEdit *Edit1;<br />
TEdit *Edit2;<br />
TButton *Button1;<br />
TLabel *Label1;<br />
void _ _fastcall Button1Click (TObject *Sender);<br />
private:<br />
public:<br />
_ _fastcall TForm1 (TComponent* Owner);<br />
};<br />
Закрытые член-данные и член-функции класса должны размещаться<br />
под меткой private: или в <strong>на</strong>чале описания класса без метки, защищенные<br />
члены с<strong>на</strong>бжаются меткой protected:, а открытые члены перечисляются под<br />
меткой public:. Данные, свойства которых представлены в окне Object Inspector,<br />
<strong>на</strong>ходятся под меткой _ _published:. Объекты, порождаемые от закрытых<br />
членов класса, доступны лишь объектам своего класса. Объекты, порождаемые<br />
защищенными чле<strong>на</strong>ми, открыты объектам своего класса и производных<br />
от него классов. Объекты открытых членов могут использоваться во всех<br />
операциях и взаимодействовать с переменными программы.
15<br />
Данные в определении класса объявляются со своими типами и име<strong>на</strong>ми,<br />
а у член-функций указываются, кроме того, типы данных параметров. В<br />
рассматриваемом примере присутствуют указатели <strong>на</strong> данные Edit1, Edit2,<br />
Button1, Label1 классов TEdit, TButton, TLabel. Модификатор быстрого вызова<br />
_ _fastcall организует передачу аргументов методам VCL не через стек, а<br />
через процессорные регистры, если это не вещественные числа, не структуры<br />
данных и не функции. Лексема void является указателем <strong>на</strong> то, что функция<br />
Button1Click не возвращает никаких з<strong>на</strong>чений по окончании работы.<br />
Бестиповая функция TForm1, имя которой совпадает с именем класса,<br />
<strong>на</strong>зывается конструктором (constructor). Конструкторы (а их может быть несколько)<br />
пред<strong>на</strong>з<strong>на</strong>чены для автоматической инициализации данных в ходе<br />
порождения объектов.<br />
Класс формы является производным классом, и в его заголовке через<br />
двоеточие указано имя базового класса TForm с открытым типом доступа<br />
public. Производный класс <strong>на</strong>следует все члены базового класса, за исключением<br />
конструкторов и деструктора. В производном классе у<strong>на</strong>следованные<br />
члены могут иногда изменяться, а также добавляются новые члены.<br />
Одновременно с объявлением класса в файле заголовка задается одноимен<strong>на</strong>я<br />
с формой глобаль<strong>на</strong>я перемен<strong>на</strong>я, позволяющая обращаться к ней из<br />
любого файла проекта, <strong>на</strong>пример:<br />
extern PACKAGE TForm1 *Form1;<br />
Лексема PACKAGE является именем макроса, в котором содержится код экспорта<br />
из библиотеки пакетов BPL – Borland Package Library.<br />
Члены классов, объявленные в файле заголовка, определяются в файле<br />
кода формы в форматах:<br />
[Модификатор] [Тип] имяКласса :: имяЧлен-данного [= З<strong>на</strong>чение];<br />
[Модификатор][Тип] имяКласса :: имяЧлен-функции (списокПараметров)<br />
телоЧлен-функции<br />
В частности, каждой форме в ее файле кода автоматически прописывается<br />
пустой конструктор, получающий при вызове единственный параметр Owner,<br />
указывающий объекта-владельца, создающего форму. Например:<br />
TForm1 *Form1;<br />
_ _fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner) {}
16<br />
При удалении из формы элементов управления их объявления автоматически<br />
удаляются из описания класса формы, хотя объявления членфункций<br />
сохраняются, и при необходимости программисту следует удалить<br />
их вручную. При переименовании объектов переименовываются и все ссылки<br />
<strong>на</strong> них в файле заголовка и в определениях.<br />
Обработка событий<br />
Доступ к функциям форм, обрабатывающим события элементов управления,<br />
может осуществляться через вкладку Events инспектора объектов. Эти<br />
же обработчики можно и непосредственно открывать в окне редактора кода.<br />
По умолчанию C++Builder присваивает обработчику имя, образованное соединением<br />
имени компонента с именем события. Например,<br />
void _ _fastcall TForm1::Button1Click (TObject *Sender) {}<br />
– это обработчик щелчка – события OnClick кнопки Button1 в форме Form1,<br />
являющийся функцией класса TForm1.<br />
Текст обработчика программист <strong>на</strong>полняет выражениями, описывающими<br />
поведение формы или элементов управления, изменяющими их характеристики<br />
или посылающими сообщения. Элементы управления, обрабатываемые<br />
этими выражениями, характеризуются свойствами и методами. В обработчике<br />
свойства и методы связываются стрелками (–>) с указателями <strong>на</strong><br />
элементы управления. Ниже приведены примеры обработчиков трех пунктов<br />
меню и кнопки:<br />
void __fastcall TForm1::menuNewClick (TObject *Sender) {<br />
Edit1–>Text = "2003";<br />
Label1–>Caption = "Санкт-Петербург";<br />
}<br />
void __fastcall TForm1::menuDatabaseClick (TObject *Sender) {<br />
Form2–>Show();<br />
}<br />
void __fastcall TForm1::menuAboutClick (TObject *Sender) {<br />
FormAbout–>ShowModal();<br />
}<br />
void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />
Application–>Terminate();<br />
}
17<br />
Здесь при выборе пункта меню menuNew свойству Текст поля Edit1 присваивается<br />
з<strong>на</strong>чение «2003», а <strong>на</strong>дписи Label1 передается «Санкт-Петербург».<br />
При выборе пункта menuDatabase открывается форма Form2, а при выборе<br />
пункта menuAbout открывается модально форма FormAbout. При <strong>на</strong>жатии<br />
кнопки Button1 приложение завершает свою работу.<br />
В следующем примере показаны обработчики меню открытия и сохранения<br />
файла, обслуживающие элемент RichEdit1 и строку состояния Status-<br />
Bar1:<br />
void _ _fastcall TForm1::menuOpenClick (TObject *Sender) {<br />
OpenDialog1–>Execute()<br />
RichEdit1–>Lines–>LoadFromFile (OpenDialog1–>FileName);<br />
StatusBar1–>Panels–>Items[0]–>Text = OpenDialog1–>FileName;<br />
}<br />
void _ _fastcall TForm1::menuSaveAsClick (TObject *Sender) {<br />
SaveDialog1–>Execute()<br />
RichEdit1–>Lines–>SaveToFile (SaveDialog1–>FileName);<br />
AnsiString fn = SaveDialog1–>FileName;<br />
StatusBar1–>Panels–>Items[0]–>Text = fn;<br />
}<br />
Передача данных между формами, не являющимися модальными, осуществляется<br />
либо через открытые члены их классов, либо через свойства<br />
форм. Например, чтобы внести в <strong>на</strong>дпись Label1 активной формы-владельца,<br />
<strong>на</strong> которую ссылается указатель this, з<strong>на</strong>чение заголовка формы Modeless-<br />
Form, можно воспользоваться следующим выражением:<br />
this–>Label1–>Caption = ModelessForm–>Caption;<br />
Важным достоинством обработчиков является их способность создавать<br />
новые элементы управления в ходе выполнения программы (runtime<br />
mode), а не <strong>на</strong> стадии проектирования (design time mode). Новые объекты порождаются<br />
оператором new, который выделяет необходимое количество свободной<br />
памяти и вызывает конструктор объекта. Объекты, созданные оператором<br />
new, следует удалять из памяти оператором delete, освобождающим<br />
память.<br />
В частности, формам не обязательно выделять память при запуске приложения.<br />
Это можно делать ди<strong>на</strong>мически, по мере необходимости. На стадии<br />
проектирования име<strong>на</strong> ди<strong>на</strong>мических форм <strong>на</strong>до удалить из списка Auto-
18<br />
create forms вкладки Forms (меню Project.Options) или просто вычеркнуть<br />
соответствующие строки из WinMain, а в нужном месте программы создавать<br />
их и открывать не модально методом Show или модально методом ShowModal.<br />
В следующем примере в форме MainForm по щелчку кнопки Button1 создаются<br />
три формы: не модаль<strong>на</strong>я форма ModelessForm, модаль<strong>на</strong>я форма<br />
ModalForm, объявлен<strong>на</strong>я глобальной переменной в файле заголовка, и модаль<strong>на</strong>я<br />
форма LocalForm, не связан<strong>на</strong>я с глобальной переменной:<br />
#include <br />
#include "Unit1.h"<br />
#include "Unit2.h"<br />
#include "Unit3.h"<br />
TForm1 *Form1;<br />
__fastcall TForm1::TForm1 (TComponent* Owner): TForm (Owner) {<br />
ModelessForm–>Show();<br />
ModalForm = new TModalForm (this); // при <strong>на</strong>личии<br />
ModalForm–>ShowModal();<br />
// глобального<br />
delete ModalForm;<br />
// определения<br />
TLocalForm *LocalForm = new TLocalForm (0); // вместо<br />
LocalForm–>ShowModal();<br />
// глобального<br />
delete LocalForm;<br />
// определения<br />
ModelessForm–>Close();<br />
}<br />
В программе использован аргумент this, указывающий <strong>на</strong> владельца<br />
TMainForm, и аргумент 0, не связывающий новую форму с конкретным владельцем.<br />
Еще один фрагмент программы посвящен созданию кнопки:<br />
_ _fastcall TForm1::TForm1 (TComponent* Owner): TForm (Owner) {<br />
TButton *b = new TButton (this);<br />
b–>Parent = this;<br />
b–>Name = “NewButton”;<br />
b–>Width = 100;<br />
b–>Height = 30;<br />
b–>Left = 10;<br />
b–>Top =10;<br />
b–>Caption = “Новая кнопка”;<br />
b–>Visible = true;<br />
b–>OnClick = MyClick;<br />
}<br />
Текст обработчика этой кнопки:
19<br />
void _ _fastcall TForm1::MyClick (TObject *Sender) {<br />
ShowMessage (“Привет”);<br />
}<br />
Программирование графики<br />
Графические объекты строятся <strong>на</strong> канве, являющейся свойством формы<br />
и ряда визуальных компонентов. Табл. 4 посвяще<strong>на</strong> характерным методам<br />
формирования графических образов канвы.<br />
Таблица 4<br />
Методы работы <strong>на</strong> канве<br />
Метод<br />
Наз<strong>на</strong>чение<br />
Arc<br />
Строит дугу внутри заданной области<br />
Chord Строит сектор эллипса по хорде<br />
CopyRect Копирует фрагмент изображения с другой канвы<br />
Draw Создает графический объект с заданными свойствами и коорди<strong>на</strong>тами<br />
Ellipse Строит эллипс в заданных границах<br />
FillRect Строит прямоугольную область с заданным свойством Brush<br />
FloodFill Заполняет пространство канвы заданным свойством Brush<br />
FrameRect Строит прямоугольник с границами, заданными свойством Brush<br />
LineTo Ведет линию из позиции PenPos в точку с коорди<strong>на</strong>тами X–1 и Y–1<br />
MoveTo Переносит позицию рисования в точку с коорди<strong>на</strong>тами X–1 и Y–1<br />
Pie<br />
Строит сектор эллипса внутри заданного прямоугольника<br />
Polygon Строит замкнутый контур из отрезков, проходящих через заданные точки<br />
PolyLine Строит ломаную из отрезков, проходящих через заданные точки<br />
Rectangle Строит прямоугольник через левый верхний и правый нижний углы<br />
RoundRect Строит прямоугольник с закругленными углами<br />
StretchDraw Вписывает изображение в прямоугольник, автоматически масштабируя<br />
TextHeight Возвращает высоту текстовой строки <strong>на</strong> рисунке<br />
TextOut Формирует строку текста из точки с X и Y, перенося в конец ее курсор<br />
TextWidth Возвращает ширину текстовой строки <strong>на</strong> рисунке<br />
TextRect Размещает фрагмент строки в заданной области, обрезая излишки<br />
В следующем примере показан обработчик события формы FormPaint,<br />
рисующий Андреевский флаг:<br />
void _ _fastcall TAboutBox::FormPaint (TObject *Sender) {<br />
Canvas–>Brush–>Color = clBlue;<br />
Canvas–>FillRect (Rect (0,0,ClientWidth, ClientHeight));<br />
Canvas–>Pen–>Color = clWhite;<br />
Canvas–>Pen–>Width = 20;<br />
Canvas–>MoveTo (0,0);<br />
Canvas–>LineTo (ClientWidth, ClientHeight);<br />
Canvas–>MoveTo (0,ClientHeight);
Canvas–>LineTo (ClientWidth, 0);<br />
}<br />
20<br />
А<strong>на</strong>логичную задачу можно решить в элементе управления PaintBox по событию<br />
PainBoxPaint.<br />
Во втором примере обработчик события, связанного с <strong>на</strong>жатием кнопки,<br />
формирует диалог для вставки объекта OLE:<br />
void _ _fastcall TModalForm::Button1Click (TObject *Sender) {<br />
OleContainer1–>InsertObjectDialog();<br />
}<br />
Третий пример демонстрирует использование компонента Clipboard<br />
для копирования содержимого одного объекта Image в другой:<br />
#include <br />
void _ _fastcall TForm1::BitBtn1Click (TObject *Sender) {<br />
Image1–>Picture–>LoadFromFile ("d:/val/scan.bmp");<br />
Clipboard()–>Assign (Image1–>Picture);<br />
Image2–>Picture–>Bitmap–>Assign (Clipboard ());<br />
}<br />
Компонент Image способен самостоятельно обновляться. Он является<br />
удобным средством ускоренного отображения графики в форме: достаточно<br />
создать скрытый вариант Image и копировать его в канву формы по событию<br />
OnPaint:<br />
void _ _fastcall TForm1::FormPaint (TObject *Sender) {<br />
this–>Canvas–>CopyRect (ClientRect,<br />
Image1–>Canvas, Image1–>ClientRect);<br />
}<br />
Исключения<br />
C++Builder поддерживает стандартный механизм обработки исключений<br />
(exception), то есть ошибок, которые могут возникнуть при выполнении<br />
программы (<strong>на</strong>пример, при делении <strong>на</strong> нуль или <strong>на</strong>рушении обслуживания<br />
потоков). При встрече с ненормальной ситуацией он может передать управление<br />
другой части программы, продолжить ее выполнение либо завершить<br />
работу. Обычно определяют обработчик исключения (exception handler), выполняющий<br />
необходимые действия перед завершением программы. Блоки<br />
кода, генерирующие исключения, <strong>на</strong>чи<strong>на</strong>ются словом try и заключаются в
21<br />
фигурные скобки. В случае об<strong>на</strong>ружения ошибки происходит программное<br />
прерывание. При этом управление передается блоку обработчика исключения<br />
catch. В скобках указывают спецификацию класса конкретного исключения,<br />
а для перехвата всех ошибок – многоточие (…). Не менее одного такого<br />
блока следует за блоком try. Для каждой исключительной ситуации определен<br />
свой класс. Некоторые классы исключений приведены в табл. 5.<br />
Таблица 5<br />
Класс<br />
EAccessViolation<br />
EConvertError<br />
EDatabaseError<br />
EDivByZero<br />
EInOutError<br />
EPrinterError<br />
EStackOverflow<br />
EZeroDivide<br />
EOverflow<br />
Некоторые классы исключений<br />
Описание<br />
Ошибка доступа в память<br />
Ошибка преобразования данных<br />
Ошибка доступа к базе данных<br />
Ошибка деления <strong>на</strong> нуль целого числа<br />
Ошибка файлового обме<strong>на</strong><br />
Ошибка печати<br />
Переполнение стека<br />
Ошибка деления <strong>на</strong> нуль вещественного числа<br />
Переполнение регистров вещественных чисел<br />
В приведенном ранее тексте файла проекта в блок try функции WinMain<br />
помещены выражения инициализации объектов OLE Automation, создания<br />
главной формы и запуска приложения. В блоке catch в случае исключительной<br />
ситуации выводится сообщение с указанием ее причины.<br />
Простой пример обработки исключений показывает, как при возникновении<br />
любой ошибочной ситуации, <strong>на</strong>пример при отсутствии текста в поле,<br />
можно вывести сообщение:<br />
void _ _fastcall TForm1::Button2Click (TObject *Sender) {<br />
try {<br />
Label1–>Caption = Edit1–>Text ; }<br />
catch (...) { ShowMessage ("Ошибка"); }<br />
}<br />
Другой пример обработки исключительной ситуации связан с ошибкой ввода<br />
числовых данных:<br />
try { Roubles–>Text = FloatToStrF (floor (StrToFloat (EditDollars–>Text) *<br />
StrToFloat (EditRate–>Text) + 0.5), ffFixed, 10, 2);<br />
}<br />
catch (const EConvertError &) {<br />
LabelError–>Caption = “Неверный формат числа”;<br />
}
catch (const EOverflow &) {<br />
LabelError–>Caption = “Невер<strong>на</strong>я величи<strong>на</strong> числа”;<br />
}<br />
22<br />
В блоке try можно использовать лексему throw для вызова исключительной<br />
ситуации или для указания типа исключения, которое вызывает<br />
функция. Вслед за блоком try вместо catch часто располагают блок _ _finally,<br />
выполняющийся вне зависимости от <strong>на</strong>личия исключительной ситуации.<br />
Простые типы данных и операторы<br />
Типы данных C++Builder ориентированы <strong>на</strong> 32-разрядную платформу<br />
(табл. 6).<br />
Таблица 6<br />
Типы данных C++Builder<br />
Тип данных Байт З<strong>на</strong>чения Название<br />
char 1 ±128 Байт<br />
BYTE 1 0…255 Байт без з<strong>на</strong>ка<br />
short 2 ±32767 Короткий целый<br />
unsigned short, WCHAR 2 0…65534 Короткий целый без з<strong>на</strong>ка<br />
int, long 4 ±2147483648 Целый<br />
unsigned long 4 0…4294967295 Целый без з<strong>на</strong>ка<br />
__int64 8 9,2E±18 Длинный целый<br />
bool 1 true, false Логический<br />
float 4 1,2E±38 Вещественный<br />
double 8 2,2E±308 Двойной вещественный<br />
long double 10 3,3E±4932 Длинный двойной вещественный<br />
void * 8 2,2E±308 Указатель<br />
char * – – Строковый с нулем в конце<br />
void – – Пустой<br />
TDateTime 8 – Дата и время<br />
Currency 8 – Валюта, 4 цифры после запятой<br />
Variant 16 – Универсальный (int, float, char, bool)<br />
AnsiString – – Ди<strong>на</strong>мический строковый<br />
WideString – – Строковый, 2 байта <strong>на</strong> символ<br />
Первая октава образует категорию порядковых типов данных (ordered<br />
data type). Пять последних типов являются типизированными классами (typed<br />
class) C++Builder. Из большого многообразия типов данных рекомендуется в<br />
первую очередь использовать int и __int64, double, bool, AnsiString и void. Некоторые<br />
(фундаментальные) типы введены для совместимости с языком C:<br />
char, short, long, float.
23<br />
Типы указываются во всех объявлениях переменных и констант. Объявления<br />
переменных могут совмещаться с их инициализацией:<br />
[Модификатор] [Тип] имяПеременной [=З<strong>на</strong>чение];<br />
а во множестве различных объявлений констант инициализация обязатель<strong>на</strong>:<br />
const Тип имяКонстанты = З<strong>на</strong>чение;<br />
enum [Тип] { списокЗ<strong>на</strong>чений };<br />
enum { списокЗ<strong>на</strong>чений } имяТипа;<br />
#define имяКонстанты З<strong>на</strong>чение<br />
Последний вариант объявления констант введен для совместимости с C.<br />
Типы переменных, входящих в параметры функций, указываются в<br />
круглых скобках вслед за именем функции. Если параметр отсутствует, скобки<br />
остаются пустыми. Тип возвращаемого функцией з<strong>на</strong>чения указывается<br />
при ее объявлении и определении перед именем. Функция, не возвращающая<br />
з<strong>на</strong>чений, объявляется как функция типа void. Если у функции или данных не<br />
объявлен тип, они получают тип int.<br />
В соответствии с типами, различают символьные, строковые, целые и<br />
вещественные константы. Первые заключаются в апострофы (<strong>на</strong>пример, ‘e’<br />
или ‘1’), вторые – в кавычки («привет»). Целые бывают десятичными (<strong>на</strong>пример<br />
56), восьмеричными (056) и шест<strong>на</strong>дцатеричными (0хАС), а вещественные<br />
– десятичными (6.23) и экспоненциальными (5Е-12). Специальную<br />
группу символьных констант образуют управляющие последовательности<br />
(табл. 7).<br />
Таблица 7<br />
Управляющие последовательности<br />
'\0' Ноль '\t' Горизонталь<strong>на</strong>я табуляция<br />
'\a' Сиг<strong>на</strong>л '\v' Вертикаль<strong>на</strong>я табуляция<br />
'\b' Возврат <strong>на</strong> символ '\"' Кавычки<br />
'\f' Новая страница '\'' Апостроф<br />
'\n' Новая строка '\\' Обрат<strong>на</strong>я косая черта<br />
'\r' Начало строки – –<br />
Данные, которыми манипулирует программа, хранятся в ячейках памяти<br />
компьютера, и именованные ячейки памяти мы <strong>на</strong>звали переменными и<br />
константами. Hо память пред<strong>на</strong>з<strong>на</strong>че<strong>на</strong> не только для хранения з<strong>на</strong>чений, обрабатываемых<br />
в ходе выполнения программы. Часть ячеек памяти выделяется<br />
для указания <strong>на</strong> другие области, и эту часть <strong>на</strong>зывают указателями и ссыл-
24<br />
ками. «Введение связей с другими элементами данных через указатели –<br />
чрезвычайно важ<strong>на</strong>я идея в программировании; это ключ к представлению<br />
сложных структур» [6].<br />
Указатель (pointer) – это специальный тип данных, описывающий не<br />
з<strong>на</strong>чение, а размещение переменной, константы или возвращаемого з<strong>на</strong>чения<br />
функции, «одно из хитроумных понятий C» [9]. Его объявляют как Тип<br />
*имяУказателя. Оператор разыменования * (dereference operator) служит<br />
средством доступа к данным, а имяУказателя может быть другим указателем<br />
или адресом конкретной области памяти. В последнем случае указатель <strong>на</strong>зывают<br />
ссылкой (reference) и объявляют как Тип &имяСсылки. & – это оператор<br />
взятия адреса, а имяСсылки – з<strong>на</strong>чение хранящихся по этому адресу данных.<br />
Между операторами * и & установлено следующее соотношение: если<br />
имяУказателя – адpес данных, то *имяУказателя – их з<strong>на</strong>чение (т.е. указатель);<br />
если имяСсылки – з<strong>на</strong>чение данных, то &имяСсылки – их адpес (т.е.<br />
ссылка).<br />
Од<strong>на</strong>жды инициализировав ссылку, ей нельзя присвоить другое з<strong>на</strong>чение.<br />
И, в отличие от указателей, которые могут быть объявлены без инициализации<br />
или установлены в нуль, ссылки всегда указывают <strong>на</strong> конкретные<br />
данные.<br />
Использование указателей и ссылок играет важную роль в работе с<br />
функциями.<br />
Обычно аргументы, передаваемые в функцию при ее вызове, обрабатываются<br />
функцией как копии данных, и потому сами данные не меняются в<br />
ходе выполнения функции. В вызывающем модуле функция может изменить<br />
только одну переменную – ту, которую о<strong>на</strong> возвращает как результат своей<br />
работы. Если же требуется, чтобы функция изменяла более одной переменной,<br />
ей в качестве аргументов следует передавать не з<strong>на</strong>чения, а ссылки <strong>на</strong><br />
данные, и в первую очередь – адpеса переменных.<br />
А<strong>на</strong>логичное правило действует и при передаче функции в другую<br />
функцию в качестве аргумента.<br />
Для выполнения операций <strong>на</strong>д типизированными данными в C++ используется<br />
более 50 простых операторов. В табл. 8 они расположены по убыванию<br />
приоритета.
25<br />
Простые операторы C++<br />
Таблица 8<br />
Приоритет<br />
к данным тические ские<br />
Доступ Арифме-<br />
Логиче-<br />
Категория<br />
Двоичные<br />
10 Первичные<br />
() [] {} /* */ // ‘’<br />
“” :: ; # -> .<br />
Нет Нет Нет<br />
9 У<strong>на</strong>рные<br />
& * – + (тип)<br />
new delete sizeof<br />
++ –– ! ~<br />
8 Мультипликативные Нет * / % Нет >> =<br />
Нет<br />
== !=<br />
5 И Нет Нет && &<br />
4 Исключающее ИЛИ Нет Нет ^<br />
3 ИЛИ Нет Нет || |<br />
2 Условие Нет Нет : Нет<br />
1 Присваивание =<br />
*= /= %=<br />
=<br />
Нет<br />
+= –=<br />
&= |= ^=<br />
В круглые скобки заключаются списки аргументов, в квадратные – индексы<br />
массивов, в фигурные – блоки данных, в косые – комментарии. Кроме<br />
того, в круглые скобки заключают временно заменяемый тип данных. Кавычками<br />
обрамляются константы. Двойное двоеточие (::) образует оператор<br />
области видимости данных. Точка с запятой (;) завершает выражения. З<strong>на</strong>к<br />
номера (#) <strong>на</strong>чи<strong>на</strong>ет директивы препроцессора. Стрелка ( –> ) и точка (.) отделяют<br />
свойства и методы классов. Амперсанд (&) и звездочка (*) помечают<br />
адреса, ссылки и указатели. Оператором new создаются, а оператором delete<br />
удаляются объекты.<br />
У<strong>на</strong>рные операторы обрабатывают один операнд, а би<strong>на</strong>рные мультипликативные,<br />
аддитивные и т. п. – два операнда. В частности, у<strong>на</strong>рные операторы<br />
сложения и вычитания (+, –) меняют з<strong>на</strong>ки операндов. Оператор sizeof<br />
определяет размер данных. Операторы автоинкремента (++) и автодекремента<br />
(– –) могут действовать до операции или после ее. Так, действие j = i++<br />
а<strong>на</strong>логично выражениям j = i; i = i+1. А выражение j = – –i можно представить<br />
как i = i–1; j = i. То же можно сказать и о таких операторах, как *=, /= и им<br />
подобным. Восклицательный з<strong>на</strong>к (!) служит для отрицания, а тильда (~) –<br />
для инверсии.<br />
Кроме того, звездочка (*) является з<strong>на</strong>ком умножения, косая черта (/) –<br />
деления, а процент (%) – остатка от деления <strong>на</strong>цело. Двойными стрелками
26<br />
(>>,
Метод<br />
IsDelimiter (AnsiString& delimiters,<br />
int index)<br />
IsEmpty()<br />
LastDelimiter (AnsiString& delimit)<br />
Length()<br />
LoadStr (int ident)<br />
operator != (AnsiString& rhs)<br />
operator < (AnsiString& rhs)<br />
operator (AnsiString& rhs)<br />
operator >= (AnsiString& rhs)<br />
operator == (AnsiString& rhs)<br />
operator [] (int idx)<br />
operator = (AnsiString& rhs)<br />
operator + (AnsiString& rhs)<br />
operator += (AnsiString& rhs)<br />
Pos (AnsiString& subStr)<br />
printf (char* format, ...)<br />
sprintf (char* format, ...)<br />
SetLength (int newLength)<br />
StringOfChar (char ch, int count)<br />
SubString (int index, int count)<br />
ToDouble (), ToInt ()<br />
ToIntDef (int defaultVal)<br />
Trim (),TrimLeft (),TrimRight ()<br />
UpperCase ()<br />
27<br />
Окончание табл. 9<br />
Наз<strong>на</strong>чение<br />
Подтверждает <strong>на</strong>личие определенного символа (delimiter)<br />
в заданной позиции (index)<br />
Подтверждает, что строка пуста<br />
Последняя позиция в строке определенного символа<br />
(delimiter)<br />
Дли<strong>на</strong> строки в байтах<br />
Загружает строку из файла<br />
Сравнение с другой строкой rhs<br />
Символ под указанным индексом idx<br />
Инициализация строки другой строкой rhs<br />
Соединение с другой строкой rhs<br />
Позиция <strong>на</strong>чала подстроки subStr в строке<br />
Задают формат строки в стандарте языка С, возвращая<br />
длину или ссылку <strong>на</strong> строку<br />
Задает длину (newLength) строки<br />
Строка заданного числа (count) символов (ch)<br />
Подстрока длиной count в позиции index<br />
Преобразуют строку в число, в т.ч. – в число по<br />
умолчанию в случае ошибки преобразования<br />
Удаляют пробелы<br />
Преобразует в прописные буквы<br />
Отдельные символы строк класса AnsiString индексируются, <strong>на</strong>чи<strong>на</strong>я с 1.<br />
В следующем примере приведен текст конструктора формы Form1, в<br />
котором вычисляется округленное з<strong>на</strong>чение гипотенузы и результат <strong>на</strong>правляется<br />
в <strong>на</strong>дпись Label1. Затем вычисляется средняя дли<strong>на</strong> двух окружностей,<br />
и округленный результат <strong>на</strong>правляется в <strong>на</strong>дпись Label2. Тексты <strong>на</strong>дписей<br />
имеют тип данных AnsiString, поэтому используются функции преобразования<br />
типов FloatToStr и FloatToStrF.<br />
#include <br />
#include <br />
#pragma hdrstop<br />
#pragma resource "*.dfm"<br />
TForm1 *Form1;<br />
__fastcall TForm1::TForm1 (TComponent* Owner) : TForm(Owner) {<br />
double a = 5, b = 7;
Form1–>Label1–>Caption = FloatToStr (floor (hypot (a, b) + 0.5));<br />
double R1 = 5, R2 = 7;<br />
Form1–>Label2–>Caption = FloatToStrF (M_PI * (R1 + R2), ffFixed, 10, 2);<br />
}<br />
28<br />
В примере задействова<strong>на</strong> математическая библиотека Borland C , подключаемая<br />
файлом заголовка . Функции этой библиотеки описаны в<br />
табл. 10:<br />
Таблица 10<br />
Функции<br />
abs, fabs<br />
acos, asin, atan<br />
cos, sin, tan<br />
exp<br />
floor<br />
hypot<br />
pow, pow10<br />
seal<br />
sqrt<br />
Математические функции Borland C<br />
Наз<strong>на</strong>чение<br />
Абсолютное з<strong>на</strong>чение<br />
Обратные тригонометрические функции<br />
Тригонометрические функции<br />
Экспонента<br />
Ближайшее меньшее целое<br />
Гипотенуза<br />
Степень<br />
Ближайшее большее целое<br />
Квадратный корень<br />
Второй пример посвящен использованию типа AnsiString, а также типов<br />
TDateTime, Currency и Variant.<br />
void __fastcall TForm1::vAnsiStringClick (TObject *Sender) {<br />
AnsiString as = Edit0–>Text, as1 = as;<br />
Label0–>Caption = as.StringOfChar ('>', as.Length ())<br />
+ as.ToIntDef (0) + as.StringOfChar ('
29<br />
Variant v = Edit3–>Text;<br />
Label3–>Caption = (v.Type () == varString) v : BoolToStr (v.operator bool ());<br />
}<br />
В третьем примере да<strong>на</strong> форма с полями ввода Edit1 и Edit2, кнопкой<br />
Button1 и <strong>на</strong>дписью Label1:<br />
class TForm1 : public TForm {<br />
_ _published:<br />
TEdit *Edit1;<br />
TEdit *Edit2;<br />
TLabel *Label1;<br />
TButton *Button1;<br />
void _ _fastcall Button1Click (TObject *Sender);<br />
public:<br />
AnsiString CalcF (double, double);<br />
AnsiString CalcSF (AnsiString, double);<br />
AnsiString CalcS (AnsiString, AnsiString);<br />
};<br />
При <strong>на</strong>жатии кнопки данные из полей формы преобразуются к нужному<br />
формату, обрабатываются в соответствии с условием конкретной задачи, и<br />
результат возвращается в <strong>на</strong>дпись:<br />
void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />
AnsiString c = "";<br />
try {<br />
c = CalcF (StrToFloat (Form1–>Edit1–>Text),<br />
StrToFloat (Form1–>Edit2–>Text));<br />
} catch (Exception &EConvertError) {<br />
try {<br />
c = CalcSF (Form1–>Edit1–>Text, StrToFloat(Form1–>Edit2–>Text));<br />
} catch (Exception &EConvertError) {<br />
try {<br />
c = CalcS (Form1–>Edit1–>Text, Form1–>Edit2–>Text);<br />
} catch (...) { c = "Ошибка данных"; }<br />
}<br />
}<br />
Form1–>Label1–>Caption = c;<br />
}<br />
Далее следуют обработчики кнопки Button1, которые выводят в <strong>на</strong>дпись<br />
Label1 решение трех задач:
AnsiString TForm1::CalcF (double a, double b) { return a / b; }<br />
AnsiString TForm1::CalcSF (AnsiString a, double b) {<br />
return a + FloatToStr (++b);<br />
}<br />
AnsiString TForm1::CalcS (AnsiString a, AnsiString b) {<br />
return “Имя: “ + a + “\nФамилия: “ + b;<br />
}<br />
30<br />
Еще один пример посвящен передачи данных из модальной формы в<br />
обычную. Необходимые данные должны быть получены до того, как модаль<strong>на</strong>я<br />
форма будет уничтоже<strong>на</strong>. Это удобно делать, в частности, через дополнительный<br />
параметр конструктора. В рассматриваемом примере таким параметром<br />
является указатель *arg, который инициализируется в конструкторе.<br />
При <strong>на</strong>жатии кнопки Button1 запомненный по указанному адресу заголовок<br />
формы переносится в <strong>на</strong>дпись Label1 не модальной формы:<br />
class TLocalForm : public TForm {<br />
__published: TLabel *Label1;<br />
public:<br />
__fastcall TLocalForm (TComponent* Owner);<br />
__fastcall TLocalForm (String *arg, TComponent* Owner);<br />
};<br />
__fastcall TLocalForm::TLocalForm (String *arg, TComponent*Owner):<br />
TForm (Owner) { *arg = this–>Caption; }<br />
void __fastcall TForm1::Button1Click (TObject *Sender) {<br />
String s;<br />
TLocalForm *LocalForm = new TLocalForm (&s, 0);<br />
LocalForm–>ShowModal(); delete LocalForm;<br />
this–>Label1–>Caption = s;<br />
}<br />
Контрольные вопросы<br />
1. В чем заключается разница между объявлением, определением и вызовом<br />
функций<br />
2. Как обмениваются данными функции<br />
3. В чем состоит разница между локальными и глобальными данными<br />
4. В чем разница между параметром функции и ее аргументом<br />
5. Существует ли разница в терми<strong>на</strong>х функция, член-функция и метод
31<br />
6. Как передаются данные при вызове функции и при возврате результата в<br />
вызывающую функцию.<br />
7. В чем разница между переменными и константами<br />
8. Для чего члены класса делятся <strong>на</strong> закрытые и открытые<br />
9. В чем заключается разница между классом формы и объектом формы<br />
10. В чем разница между визуальным компонентом и элементом управления<br />
11. Для чего служит файл заголовка<br />
12. Для чего пред<strong>на</strong>з<strong>на</strong>чен конструктор формы<br />
13. Для чего служат и как используются события<br />
14. Чем обработчик события отличается от других функций<br />
15. Как формы обмениваются данными между собой<br />
16. Для чего служат операторы New и delete<br />
17. Каковы главные особенности графических объектов<br />
18. Какую роль играет событие FormPaint при работе с графическими объектами<br />
19. Какую роль играют исключения и их обработчики<br />
20. Как выполняется отладка программ в режиме обработки исключений<br />
21. Нужно ли указывать типы данных при объявлении и инициализации переменных<br />
22. Чем различаются типы данных char, char* и AnsiString<br />
23. Чем различаются типы данных int, float и double<br />
24. Для чего функция имеет тип<br />
25. Что такое указатель<br />
26. Для чего используются параметры-указатели<br />
27. Для чего служат операторы доступа к данным<br />
28. Для чего служат логические операторы<br />
29. Для чего служат двоичные операторы<br />
30. Чем различаются операторы || и |, && и &<br />
31. Можно ли применять операторы отношения к данным типов char, char* и<br />
AnsiString<br />
32. Можно ли применять оператор присваивания к разнотипным данным<br />
33. Для чего служат и как используются методы класса AnsiString<br />
34. Для чего операторы ранжируются по приоритетам<br />
35. Что такое типизированные классы
32<br />
Составные операторы<br />
Длин<strong>на</strong> та дорога, где нет поворота<br />
Английская пословица<br />
Использование составных операторов – первый шаг к структурному<br />
программированию. До сих пор <strong>на</strong>ши программы были линейными, они реализовали<br />
простейший алгоритм следования. Линейные задачи самые простые<br />
и кажутся более понятными, как более понятной бывает любая упрощен<strong>на</strong>я<br />
модель.<br />
Ветвящиеся алгоритмы <strong>на</strong>много сложнее, но ближе к действительности,<br />
ибо «каждая точка <strong>на</strong>шей жизни есть точка выбора» (А. Даниэль). Такие<br />
алгоритмы реализуются с использованием составных операторов перехода,<br />
условия, выбора, цикла и повторения. Ветвления в программах часто оказываются<br />
опасными и скользкими. Только практика позволяет освоить технику<br />
выбора необходимых управляющих структур, грамотного выхода из составных<br />
операторов и обработки ошибочных ситуаций.<br />
Операторы перехода<br />
Ветвящиеся и циклические алгоритмы управления информационными<br />
процессами реализуются с помощью составных операторов. Такие операторы<br />
часто входят друг в друга, образуя управляющих структуры.<br />
Традиционным средством организации нелинейных процессов являются<br />
операторы перехода, служащие для изменения хода вычислительных процессов.<br />
К таким операторам относятся: break, continue, return, goto Метка.<br />
Первый из операторов перехода прекращает выполнение самого внутреннего<br />
из составных операторов, передавая управление следующему за прерываемым<br />
оператором. Второй прерывает выполнение составного оператора,<br />
передавая управление в его конец. Третий завершает выполнение функции.<br />
Оператор goto передает управление <strong>на</strong> метку внутри текущей функции,<br />
состоящую из имени и двоеточия. Применение его не поощряется в современном<br />
программировании, так как <strong>на</strong>рушает принцип процедурного подхода.<br />
«Goto - неизбежное зло, ибо программы с безусловными переходами<br />
трудно понять» [3]; «любая программа может обойтись без оператора goto»<br />
[5].
Условный оператор<br />
33<br />
С помощью условного оператора ход вычислительного процесса организуется<br />
в зависимости от з<strong>на</strong>чения логического выражения:<br />
if (истинноеЛогическоеВыражение) Выражение…<br />
[else Выражение… ]<br />
Логическое выражение строится <strong>на</strong> базе логических операторов и переменных.<br />
Пример:<br />
void Ifs1 () {<br />
double x, y, r;<br />
x=StrToFloat (MyForm–>Edit1–>Text);<br />
y=StrToFloat (MyForm–>Edit2–>Text);<br />
r=StrToFloat (MyForm–>Edit3–>Text);<br />
if (x * x + y * y < r * r) MyForm–>Label1–>Caption = "Точка внутри круга";<br />
else MyForm–>Label1–>Caption = "Точка вне круга";<br />
}<br />
Другой пример посвящен сравнению двух слов:<br />
void Ifs2 () {<br />
AnsiString a, b;<br />
a= Edit1–>Text;<br />
b= Edit2–>Text;<br />
if (a > b) Label1–>Caption = "a”; else Label1–>Caption = "b”<br />
}<br />
Следующий пример демонстрирует, как можно с помощью условного<br />
оператора организовать построение произвольных линий, подобно тому, как<br />
это делается в редакторе Paint. Рисование <strong>на</strong>чи<strong>на</strong>ется в момент <strong>на</strong>жатия<br />
кнопки мыши и заканчивается в момент ее отпускания. Флаг <strong>на</strong>жатия drawing<br />
должен быть предварительно внесен в список открытых членов класса формы.<br />
void __fastcall TForm1::FormMouseDown (TObject *Sender,<br />
TMouseButton Button, TShiftState Shift, int X, int Y) {<br />
drawing=true;<br />
Canvas–>TextOut (X, Y, "Старт!"); Canvas–>MoveTo (X, Y);<br />
}<br />
void __fastcall TForm1::FormMouseUp (TObject *Sender,<br />
TMouseButton Button, TShiftState Shift, int X, int Y) {<br />
drawing = false; Canvas–>TextOut (X, Y, "Стоп!");<br />
}
void _ _fastcall TForm1::FormMouseMove (TObject *Sender,<br />
TShiftState Shift, int X, int Y) {<br />
if (drawing) Canvas–>LineTo (X, Y);<br />
}<br />
34<br />
Еще од<strong>на</strong> функция показывает работу методов Delete (удаление букв) и<br />
Length (дли<strong>на</strong> строки) класса AnsiString в операции сцепления первой буквы<br />
слова a, точки и слова b:<br />
void Ifs3 () {<br />
AnsiString a = MyForm–>Edit1–>Text;<br />
AnsiString b = MyForm–>Edit2–>Text;<br />
AnsiString c = a.Delete (2, a.Length () – 1);<br />
if (a > b) MyForm–>Label1–>Caption = c + '.' + b + '\n' + a;<br />
else MyForm–>Label1–>Caption = a + '\n' + c + '.' + b;<br />
}<br />
Составные операторы могут иметь любую глубину вложенности. Но<br />
«вложенный <strong>на</strong>бор операторов if...else иногда <strong>на</strong>поми<strong>на</strong>ет водопровод старого<br />
дома: система работает, но трудно понять, какая труба куда ведет» [9].<br />
Оператор выбора<br />
Оператор выбора служит для организации хода вычислительного процесса<br />
в зависимости от з<strong>на</strong>чения выражения или переменной:<br />
switch (порядковаяПеремен<strong>на</strong>я)<br />
{ case Константа : Выражение…<br />
...<br />
[default : Выражение…] }<br />
После выполнения каждого выражения действие передается следующему<br />
выражению. Для прерывания этой последовательности обработку каждой<br />
константы в операторе выбора завершают оператором break. Выражения,<br />
открываемые лексемой default, рассчитаны <strong>на</strong> обработку незапланированных<br />
з<strong>на</strong>чений логического выражения. Пример:<br />
void Cases () {<br />
AnsiString day;<br />
int n = StrToInt (MyForm–>Edit1–>Text);<br />
switch (n) {<br />
case 1: day = "Понедельник"; break;<br />
case 2: day = "Вторник"; break;<br />
case 3: day = "Среда"; break;
}<br />
35<br />
case 4: day = "Четверг"; break;<br />
case 5: day = "Пятница"; break;<br />
case 6: day = "Суббота"; break;<br />
case 7: day = "Воскресенье"; break;<br />
default: day = "Ошибка ввода";<br />
} MyForm–>Label1–>Caption = day;<br />
Следующий пример посвящен работе с формой, в классе которой есть<br />
параметр. Для передачи форме з<strong>на</strong>чения параметра newArg класс формы дополнен<br />
вторым конструктором:<br />
class TLocalForm : public TForm {<br />
__published: TLabel *Label1;<br />
public:<br />
__fastcall TLocalForm (TComponent* Owner);<br />
__fastcall TLocalForm (int newArg, TComponent* Owner);<br />
};<br />
__fastcall TLocalForm::TLocalForm (int newArg, TComponent* Owner):<br />
TForm (Owner) {<br />
switch (newArg) {<br />
case 1: Label1–>Caption = "Обыч<strong>на</strong>я форма"; break;<br />
case 2: Label1–>Caption = "Форма с параметром"; break;<br />
}<br />
}<br />
Оператор цикла<br />
Оператор цикла организует повторяющийся вычислительный процесс в<br />
заданном диапазоне изменения переменной-счетчика цикла:<br />
for ([ выражениеИнициализации ];<br />
[ выражениеУсловия ];<br />
[ выражениеИтерации ]) Выражение…<br />
Выражения цикла, условия и итерации строятся с использованием переменной-счетчика<br />
цикла, который относится к одному из порядковых типов.<br />
Выражение итерации может быть как увеличивающимся, так и уменьшающимся,<br />
<strong>на</strong>пример, a++, b–=2 или c– –. По завершении цикла з<strong>на</strong>чение переменной-счетчика<br />
остается неопределенным.<br />
Далее приведен простой пример с оператором цикла – вывод алфавита<br />
в заголовок формы:
void Fors1 () {<br />
char ch;<br />
// ch – перемен<strong>на</strong>я-счетчик цикла<br />
AnsiString alphabet;<br />
for (ch='A'; chLabel1–>Caption = alphabet;<br />
}<br />
36<br />
Во втором примере для извлечения символов в операциях сравнения<br />
строк и формирования строки символов алфавита служит метод operator []<br />
класса AnsiString. Этот метод возвращает символ строки, соответствующий<br />
указанному в качестве аргумента порядковому номеру.<br />
void Fors2 () {<br />
AnsiString chars="", aa, bb;<br />
aa = MyForm–>Edit2–>Text;<br />
bb = MyForm–>Edit3–>Text;<br />
char a=aa.operator [] (1);<br />
char b=bb.operator [] (1);<br />
for (char k=a; kLabel1–>Caption = chars;<br />
}<br />
Для досрочного завершения цикла в его тело включают операторы перехода.<br />
При отсутствии выражения условия или итерации образуются «вечные<br />
циклы», <strong>на</strong>пример for (;;).<br />
Операторы повторения<br />
Операторы повторения пред<strong>на</strong>з<strong>на</strong>чены для организации вычислительных<br />
процессов, повторяющихся заранее неизвестное число раз. Различают<br />
операторы повторения с предусловием и с постусловием:<br />
while (истинноеЛогическоеВыражение ) Выражение…<br />
do Выражение… while (истинноеЛогическоеВыражение );<br />
З<strong>на</strong>чение логического выражения обычно меняется в теле оператора в<br />
ходе выполнения выражений. В противном случае образуются «вечные циклы»<br />
(endless repetition) типа while (1) или do while (!0). До <strong>на</strong>чала работы оператора<br />
с предусловием должно быть сформировано истинное логическое выражение.<br />
Для досрочного завершения выполнения операторов повторения в<br />
выражениях размещаются операторы перехода. Примеры:
37<br />
void Whiles1 () {<br />
int temp = 10, count = 1;<br />
AnsiString result = MyForm–>Label1–>Caption;<br />
while (2 * temp > 3 * count++) result = result + IntToStr (temp––) + ' '; // 10 9 8 7<br />
MyForm–>Label1–>Caption = result;<br />
}<br />
Оператор повторения с постусловием относится к числу <strong>на</strong>именее <strong>на</strong>дежных<br />
управляющих структур, так как условие его завершения проверяется<br />
только после <strong>на</strong>чала работы.<br />
void Whiles2 () {<br />
int x = 9;<br />
do<br />
MyForm–>Label1–>Caption = MyForm–>Label1–>Caption +<br />
IntToStr (x––) + " в квадрате равно " + IntToStr (x * x) + '\n';<br />
while (x);<br />
}<br />
А вот пример «вечного цикла», выход из которого возможен только с<br />
помощью оператора перехода:<br />
void Whiles3 () {<br />
int leapYear, year;<br />
TLabel *lb = MyForm–>Label1;<br />
try { year = StrToInt (MyForm–>Edit1–>Text);<br />
} catch (...) {<br />
lb–>Caption = "Вводите годы целыми числами"; return;<br />
}<br />
while (1) {<br />
if (year > 0) {<br />
if ((year % 100) == 0) leapYear = ((year % 400) == 0);<br />
else leapYear = ((year % 4) == 0);<br />
if (leapYear) lb –>Caption = " – високосный";<br />
else lb –>Caption = " – не високосный";<br />
break;<br />
}<br />
else { lb –>Caption = " – неверный";<br />
break;<br />
}<br />
} lb –>Caption = IntToStr (year) + lb –>Caption + " год";<br />
}
Программирование диалогов<br />
38<br />
Составные операторы помогают организовать диалог с пользователем с<br />
помощью специально пред<strong>на</strong>з<strong>на</strong>ченных для этого функций (табл. 11), что<br />
особенно удобно <strong>на</strong> этапе подготовки и отладки приложений.<br />
Таблица 11<br />
Функции сообщений<br />
Функция<br />
int MessageDlg (AnsiString text, TMsgDlgType type,<br />
TMsgDlgButtons buttons, int helpContext)<br />
int MessageDlgPos (AnsiString text, TMsgDlgType type,<br />
TMsgDlgButtons buttons, int helpContext, int X, int Y)<br />
void ShowMessage (AnsiString text)<br />
bool SelectDirectory (AnsiString &dir, TSelectDirOpts options,<br />
int helpContext)<br />
Наз<strong>на</strong>чение<br />
Сообщение в центре экра<strong>на</strong> с<br />
возвратом результата<br />
Сообщение в позиции X,Y с<br />
возвратом результата<br />
Сообщение с кнопкой ОК<br />
Окно диалога для выбора<br />
папки<br />
Большинство функций диалога возвращает определенное з<strong>на</strong>чение, которое<br />
можно обрабатывать в составных операторах.<br />
Ниже дан пример обработчика, в котором <strong>на</strong>жатие кнопки открывает<br />
возможность выбора или создания новой папки, после чего ее адрес отображается<br />
в <strong>на</strong>дписи.<br />
#include <br />
#include <br />
#include <br />
#pragma hdrstop<br />
#pragma resource "*.dfm"<br />
TForm1 *Form1;<br />
void __fastcall TForm1::Button1Click (TObject *Sender) {<br />
if (MessageDlg ("Создать/выбрать папку", mtConfirmation,<br />
TmsgDlgButtons ()
39<br />
В следующем примере последовательность окон сообщений информирует<br />
о владельце, объектах владения и дочерних компонентах некоторой<br />
формы, созданной в ходе выполнения программы.<br />
void OwnersAndParents () {<br />
AnsiString info;<br />
int i;<br />
ShowMessage ("Этой формой владеет " + MyForm–>Owner–>Name);<br />
for (i=0; iComponentCount; ++i)<br />
info = info + '\n' + MyForm–>Components[i]–>Name;<br />
ShowMessage ("Объекты владения формы " + info);<br />
info.Delete (1,info.Length ());<br />
for (i=0; iControlCount; ++i)<br />
info = info + '\n' + MyForm–>Controls[i]–>Name;<br />
ShowMessage ("Дочерние объекты формы " + info);<br />
info.Delete (1, info.Length ());<br />
for (i=0; iToolBar1–>ControlCount; ++i)<br />
info = info + '\n' + MyForm–>ToolBar1–>Controls[i]–>Name;<br />
ShowMessage ("Дочерние объекты панели инструментов " + info);<br />
}<br />
Контрольные вопросы<br />
1. Для чего служат и как различаются между собой операторы перехода<br />
2. Как определить глубину вложенности составных операторов<br />
3. В чем состоит разница между условным оператором и оператором выбора<br />
4. Может ли оператор выбора использоваться без оператора break<br />
5. Может ли оператор выбора использоваться с операторами continue и goto<br />
6. Можно ли совмещать операторы условия и выбора<br />
7. Чем различаются операторы цикла и повторения и что у них общего<br />
8. Для чего применяется «вечный цикл»<br />
9. Для чего применяются цикл без счетчика, цикл без инициализации, цикл<br />
без итерации<br />
10. Какие ок<strong>на</strong> диалога используются в Windows<br />
11. Что <strong>на</strong>зывают возвращаемым з<strong>на</strong>чением ок<strong>на</strong> диалога<br />
12. Как используются возвращаемые з<strong>на</strong>чения окон диалога в составных операторах
40<br />
Введение в структурное <strong>программирование</strong><br />
Подобное к подобному влечется<br />
Из Плато<strong>на</strong><br />
Концепция структурного программирования предполагает ранжирование<br />
алгоритма решения задачи по своеобразным «уровням абстрактных машин»<br />
[6]. При этом производится пошаговое уточнение алгоритма за счет<br />
расчленения задачи <strong>на</strong> подзадачи до тех пор, пока мельчайшую из подзадач<br />
не удастся реализовать средствами языка программирования. Массивы,<br />
структуры, объединения, списки, стеки, деки, деревья, векторы и т. п. являются<br />
такими готовыми элементами языка, которые <strong>на</strong>до <strong>на</strong>учиться использовать.<br />
Массивы<br />
Для группового представления и обработки однотипных данных применяются<br />
составные типы – массивы (array). В объявлениях массива используются<br />
прямоугольные скобки:<br />
Тип имяМассива [ [Размер] ]…[={списокЗ<strong>на</strong>чений} ];<br />
Массивы инициализируются одним из двух способов:<br />
имяМассива [ Индекс ]…=З<strong>на</strong>чение;<br />
имяМассива [ [Размер] ]…= { списокЗ<strong>на</strong>чений };<br />
Размер и индекс относятся к целому типу. Они указываются в квадратных<br />
скобках вслед за именем массива. При <strong>на</strong>личии списка з<strong>на</strong>чений один из<br />
размеров можно опускать: он рассчитывается автоматически.<br />
Массивы индексируются с нуля. Число размеров определяет размерность<br />
массива, а число членов (элементов) массива равно произведению всех<br />
размеров. Пример:<br />
void Arrays1 () {<br />
AnsiString students[] = {"Иванов", "Петров", "Сидоров"};<br />
MyForm–>Edit1–>Text = students[0];<br />
MyForm–>Edit2–>Text = students[1];<br />
MyForm–>Edit3–>Text = students[2];<br />
}
41<br />
Число байтов, выделяемое массиву при инициализации, определяется<br />
как произведение размера типа и числа членов. Для резервирования и освобождения<br />
этой памяти целесообразно использовать выражения<br />
Пример:<br />
имяМассива = new Тип [ Размер ]…;<br />
delete [ ] имяМассива;<br />
void Arrays2 () {<br />
AnsiString *whos;<br />
whos = new AnsiString[4];<br />
whos[0] = MyForm–>Edit1–>Text;<br />
whos[1] = "Mr. " + whos[0];<br />
whos[2] = "Dr. " + whos[0];<br />
whos[3] = *(whos + 1);<br />
MyForm–>RichEdit1–>Text =<br />
whos[0] + '\n' + whos[1] +'\n' + whos[2] + '\n' + whos[3];<br />
delete [] whos;<br />
}<br />
Имя каждого чле<strong>на</strong> массива состоит из имени массива и индексов в<br />
квадратных скобках. Программа обрабатывает многомерные массивы, <strong>на</strong>чи<strong>на</strong>я<br />
с членов, отмеченных крайними справа индексами. Имя массива указывает<br />
<strong>на</strong> адрес его нулевого чле<strong>на</strong>. Так как <strong>на</strong>чальный адрес массива уста<strong>на</strong>вливается<br />
автоматически в момент инициализации, он не может быть переопределен<br />
пользователем и, следовательно, име<strong>на</strong> массивов нельзя присваивать<br />
друг другу. Копирование массивов выполняется почленно, в том числе с помощью<br />
специальных функций.<br />
Массив передается в функцию только по ссылке <strong>на</strong> свой адрес. При передаче<br />
массива в функцию в качестве аргумента указывается имя массива, а<br />
при определении функции с многомерным массивом параметром задается<br />
имя массива и все размеры, кроме первого:<br />
void F1 () { AnsiString x[3]; F2 (x); ShowMessage (x[0] + ‘ ‘ + x[1]); }<br />
void F2 (AnsiString y[3]) { y[0] = “Привет”; *(y+1) = “Салют”; }<br />
void F3 () {<br />
int x[2][2]; F4 (x); ShowMessage (IntToStr (x[0][0]) + ‘ ‘ + IntToStr (x[0][1]));<br />
}<br />
void F4 (int y[][2]) { y[0][0] = 1; y[0][1] = 2; }
42<br />
Примером массива является свойство Cells компонента StringGrid, которым<br />
задается содержание ячейки, адресуемой индексами. Массивами описывают<br />
также отдельные панели строки состояния и строки списков.<br />
void Arrays4 () {<br />
MyForm–>StringGrid1–>Cols[1]–>Add (“0-ая ячейка 1-го столбца”);<br />
MyForm–>StringGrid1–>Cols[1]–>Add (“1-ая ячейка 1-го столбца”);<br />
MyForm–>StringGrid1–>Cols[1]–>Append (“2-ая ячейка 1-го столбца”);<br />
MyForm–>StringGrid1–>Cells[2][3] = “Ячейка 2-го столбца 3-ей строки”;<br />
MyForm–>StatusBar1–>Panels–>Items[0]–>Text = "Левая панель";<br />
MyForm–>ListBox1–>Items–>Strings[0] = "Нулевая строка списка";<br />
}<br />
Массивы эффективно используются в графике, <strong>на</strong>пример, для рисования<br />
геометрических фигур по канве формы:<br />
void __fastcall TForm1::FormPaint (TObject *Sender) {<br />
TPoint v[5];<br />
int cw = ClientWidth, ch = ClientHeight;<br />
v[0] = Point (0, cw/10);<br />
v[1] = Point (cw/4, cw/10);<br />
v[2] = Point (cw/2, cw/4);<br />
v[3] = Point (cw/4, cw/4);<br />
v[4] = Point (0, cw/10);<br />
Canvas–>Polyline (v, 4);<br />
v[0] = Point (cw/2, ch/2);<br />
v[1] = Point (cw/2, ch);<br />
v[2] = Point (cw,ch);<br />
Canvas–>Polygon (v,2);<br />
Canvas–>Rectangle (0, ch/2, cw/2, ch);<br />
Canvas–>RoundRect (0, ch/2, cw/2, ch, cw/10, ch/10);<br />
Canvas–>Ellipse (0, ch/2, cw/2, ch);<br />
}<br />
Свойство Pixels типа TColor у компонентов Canvas можно использовать<br />
как двухмерный массив цвета точки, коорди<strong>на</strong>ты которой отсчитываются<br />
от левого верхнего угла изображения.<br />
void _ _fastcall TForm1::FormMouseDown (TObject *Sender,<br />
TMouseButton Button, TShiftState Shift, int X, int Y) {<br />
Label1–>Caption = X; Label2–>Caption = Y;<br />
Label3–>Caption = Canvas–>Pixels[X][Y];<br />
Canvas–>Pixels[X][Y] = clWhite;<br />
}
Перестановки и сортировки<br />
43<br />
Простым алгоритмом описывается широко распространен<strong>на</strong>я в программировании<br />
операция перестановки (swap) данных местами. Пример перестановки<br />
пары переменных выглядит так:<br />
void Swap (int &a, int &b) { int x = a; a = b; b = x; }<br />
void SwapVar () {<br />
int x = 2, y = 1;<br />
Swap (x, y);<br />
ShowMessage (x >y “x > y” : “x < y”);<br />
}<br />
Следующий пример иллюстрирует перестановку членов массива:<br />
void Swap (AnsiString a[], int n) {<br />
int i, j;<br />
for (i=0, j=n–1; i
44<br />
Сортировка объединением предусматривает получение одной последовательности<br />
данных, построенной по определенному правилу, из двух и более<br />
последовательностей, построенных по тому же правилу.<br />
Простейший из алгоритмов сортировки – алгоритм упорядочения методом<br />
«пузырька» (bubble sorting). В нем последовательно проверяются все<br />
пары данных сортируемого массива, и там, где первый член больше второго,<br />
выполняется перестановка.<br />
Сортировка методом выборки минимального или максимального чле<strong>на</strong><br />
(exchange sorting) заключается в последовательной выборке <strong>на</strong>именьшего из<br />
оставшихся членов списка. При этом перебор списка проводится более медленно,<br />
чем сравнение предыдущим методом. Еще медленнее работает метод<br />
вставки (insertion sorting), когда члены просматриваются по одному, и каждый<br />
новый член вставляется в подходящее место среди ранее упорядоченных<br />
данных. Этот алгоритм эффективен при малых массивах (до 50 – 100 членов),<br />
особенно, если они частично отсортированы. По методу Шелла (Shell sorting),<br />
в отличие от «пузырька», переставляются не соседние, а далеко отстоящие<br />
члены, что иногда повышает скорость в 1,5 раза.<br />
Примерно втрое большую скорость, чем сортировка методом «пузырька»,<br />
обеспечивает «быстрая сортировка» (quick sorting). При сортировке<br />
этим методом выбирается произвольный член, а остальной список сортируемых<br />
членов делится пополам, и каждый член одной половины сравнивается с<br />
чле<strong>на</strong>ми другой. Затем список вновь делится пополам и выполняется перестановка<br />
и т.д. Этот алгоритм положен в основу ряда библиотечных функций.<br />
void Swap (int &x, int &y) { int temp = x; x = y; y = temp; }<br />
void BubbleSort (int array[], int N) {<br />
for (int i=0; ii; ––j)<br />
if (array[j] < array[j–1]) Swap (array[j], array[j–1]);<br />
}<br />
void ExchangeSort (int array[], int N) {<br />
for (int i=0; i
}<br />
45<br />
for (int i=1; i=0 + 1; – –j)<br />
if (array[j–1] > array[j]) Swap (array[j], array[j–1]);<br />
void ShellSort (int array[], int N) {<br />
int delta = 1, sw, offset = (N–1+delta) / 2;<br />
while (offset > 0) {<br />
int limit = N–1+delta–offset;<br />
do {<br />
sw = 0;<br />
for (int i=1; iarray[i+offset–delta]) {<br />
Swap (array[i–delta], array[i+offset–delta]); sw = i;<br />
}<br />
limit = sw – offset;<br />
} while (sw);<br />
offset /= 2;<br />
}<br />
}<br />
void QuickSort (int array[], int left, int right) {<br />
int i = left, j = right, temp, test = array [(left+right) / 2];<br />
do {<br />
while (array[i] < test) ++i;<br />
while (test < array[j]) ––j;<br />
if (i RadioGroup1–>ItemIndex) {<br />
case 0: BubbleSort (array, N); break;<br />
case 1: ExchangeSort (array, N); break;<br />
case 2: InsertionSort (array, N); break;<br />
case 3: ShellSort (array, N); break;<br />
case 4: QuickSort (array, 0, N–1); break;
46<br />
}<br />
for (i=0; i 0, если elem1 больше, чем elem2:<br />
int Sort_function (const void *a, const void *b);<br />
void Qsorting () {<br />
char list[5] = {'c', 'r', 'a', 'p', 'n'};<br />
int i;<br />
AnsiString result;<br />
qsort ((void*) &list, 5, sizeof (list[0]), Sort_function);<br />
for (i = 0; i < 5; i++) result += AnsiString (list[i]) + ' ';<br />
ShowMessage (result);<br />
}<br />
int Sort_function (const void *a, const void *b) {<br />
return strcmp ((char*) a, (char*) b);<br />
}<br />
Списки TStringList<br />
Разновидностью массивов являются последовательности символьных<br />
строк – списки.<br />
Библиотека VCL поддерживает общий интерфейс со списками через<br />
класс TString и производный от него класс TStringList. Это один из самых популярных<br />
классов, представляющий удобный контейнер для хранения строк<br />
и связанных с ними объектов. Методы класса позволяют загружать и сохра-
47<br />
нять, создавать списки, манипулировать строками, сортировать, вставлять,<br />
добавлять и удалять их.<br />
Временные списки и списки длительного пользования проектируют поразному.<br />
Первые обычно создаются и разрушаются в пределах одной функции:<br />
void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />
TStringList *TempList = new TStringList;<br />
try { Label1–>Caption = “Временный список используется”; }<br />
_ _finally { delete TempList;}<br />
}<br />
Вторые с<strong>на</strong>чала объявляются в файле заголовка формы как указатели<br />
<strong>на</strong> объекты класса TString. Объявленные объекты создаются в конструкторе<br />
формы и освобождают память при ее уничтожении. В приведенном ниже<br />
примере в файле заголовка формы был объявлен открытый указатель<br />
TStringList *ConstList:<br />
__fastcall TForm1::TForm1 (TComponent* Owner): TForm (Owner) {<br />
ConstList = new TStringList;<br />
}<br />
void __fastcall TForm1::FormClose (TObject *Sender, TCloseAction &Action) {<br />
delete ConstList;<br />
}<br />
void __fastcall TForm1::FormMouseDown (TObject *Sender,<br />
TMouseButton Button, TShiftState Shift, int X, int Y) {<br />
TVarRec v [] = {X,Y};<br />
ConstList–>Add (Format ("Курсор в (%d,%d)", v, ARRAYSIZE (v) – 1));<br />
}<br />
Новая строка в конец списка добавляется методом Add. Количество<br />
строк в списке вычисляется методом Count, а свойство Strings содержит весь<br />
массив строк, доступных по их индексам, <strong>на</strong>чи<strong>на</strong>я с 0. Метод IndexOf определяет<br />
индекс строки, содержащей указанный текст, <strong>на</strong>пример:<br />
void StringLists2 () {<br />
MyForm–>ConstList–>Add ("Конец");<br />
MyForm–>ConstList–>Strings [ConstList–>Count–1] =<br />
MyForm–>ConstList–>IndexOf ("Конец");<br />
}<br />
Чтобы добавить i-ю строку в список, используется метод Insert. Для<br />
добавления строк из другого списка служит методы Assign и AddStrings.<br />
Перемещение строк внутри списка выполняется методом Move. Удалением
48<br />
ремещение строк внутри списка выполняется методом Move. Удалением<br />
строки занимается метод Delete, а Clear очищает список:<br />
void StringLists3 () {<br />
TStringList *tsl = new TStringList;<br />
tsl–>Add (“Раз”);<br />
tsl–>Add (“Два”);<br />
ShowMessage (tsl–>Count);<br />
ShowStr (tsl);<br />
// Раз Два<br />
tsl–>Insert (1, “Полтора”); ShowStr (tsl); // Раз Полтора Два<br />
tsl–>Move (0, 2); ShowStr (tsl); // Полтора Два Раз<br />
tsl–>Exchange (0, 1); ShowStr (tsl); // Два Полтора Раз<br />
tsl–>Delete (1); ShowStr (tsl);<br />
// Два Раз<br />
tsl–>Sort (); ShowStr (tsl);<br />
// Два Раз<br />
int index; if (tsl–>Find (“Раз”, index)) ShowMessage (index); // 0<br />
tsl–>Clear (); ShowStr (tsl);<br />
TStringList *temp = new TStringList;<br />
temp–>Assign (tsl); ShowStr (temp); // Два Раз<br />
delete temp;<br />
Memo1–>Lines–>Assign (tsl);<br />
RichEdit1–>Lines–>Assign (tsl);<br />
tsl–>Assign (Memo1–>Lines);<br />
TStrings *ml = Memo1–>Lines;<br />
ml–>Add (“Три”);<br />
ShowMessage (ml–>Count);<br />
ml–>Move (0, 1);<br />
ml–>Insert (1, “Четыре”);<br />
ml–>Delete (1);<br />
ml–>Clear ();<br />
ComboBox1–>Items–>AddStrings (tsl);<br />
TStrings *lb = ListBox1–>Items;<br />
lb–>AddStrings (tsl);<br />
lb–>Add (“Три”);<br />
lb–>Append (“Четыре”);<br />
ShowMessage (lb–>Count);<br />
lb–>Insert (2, “Пять”);<br />
lb–>Move (0, 1);<br />
lb–>Exchange (0, 1);<br />
lb–>Delete (1);<br />
lb–>SaveToFile (“1.txt”);<br />
lb–>Clear ();
lb–>LoadFromFile (“1.txt”);<br />
ShowMessage (lb–>Text);<br />
ShowMessage (lb–>Strings[0]);<br />
ShowMessage (ListBox1–>ItemIndex); // номер выделенной строки<br />
if (ListBox1–>Selected[1] == true)<br />
ShowMessage (lb–>Strings[ListBox1–>ItemIndex]);<br />
ListBox1–>Sorted = true;<br />
ListBox1–>ItemIndex = 1;<br />
}<br />
void ShowStr (TStringList *z) {<br />
AnsiString s;<br />
for (int i=0; iCount; ++i) s+= z–>Strings[i] + ‘\n’;<br />
ShowMessage (s);<br />
}<br />
49<br />
Структуры и объединения<br />
Упростить представление и групповую обработку разнотипных данных<br />
помогают структуры (structure). Их объявления:<br />
struct имяТипаСтруктуры { объявлениеПолей };<br />
struct имяСтруктуры { объявлениеПолей } [=списокЗ<strong>на</strong>чений ];<br />
struct имяТипаСтруктуры имяСтруктуры [={ списокЗ<strong>на</strong>чений }];<br />
где объявление полей имеет формат<br />
Тип имяПоля;…<br />
Как и любой тип, структура представляет абстрактную категорию, характеризующую<br />
размер памяти и способ обработки данных. Для работы с<br />
данными создаются переменные:<br />
имяТипаСтруктуры списокИменСтруктур;<br />
Структуры инициализируют, подобно классам:<br />
имяСтруктуры.имяПоля = З<strong>на</strong>чение;<br />
указательНаСтруктуру–>имяПоля = З<strong>на</strong>чение;<br />
Име<strong>на</strong> полей уникальны в пределах структуры. Поля поддерживают все операции,<br />
характерные для их типов. Структуры и объединения могут вкладываться<br />
друг в друга. Примеры:<br />
void Structures1 () {<br />
struct Student {<br />
AnsiString firstName;<br />
int birthYear;
double scholarship;<br />
};<br />
Student example;<br />
example.firstName = MyForm–>Edit1–>Text ;<br />
example.birthYear = StrToInt (MyForm–>Edit2–>Text);<br />
example.scholarship = StrToFloat (MyForm–>Edit3–>Text) ;<br />
MyForm–>StringGrid1–>Cells[1][1] = example.firstName;<br />
MyForm–>StringGrid1–>Cells[2][1] = example.birthYear;<br />
MyForm–>StringGrid1–>Cells[3][1] = example.scholarship;<br />
}<br />
void Structures2 () {<br />
TStringGrid *sg = StringGrid1;<br />
int rows = sg–>RowCount;<br />
struct s { int n; AnsiString nm; };<br />
s group;<br />
try {<br />
for (int i=0; iCells[0][i]);<br />
group.nm = sg–>Cells[1][i];<br />
ShowMessage (IntToStr (group.n) + ' ' + group.nm);<br />
}<br />
} catch (...) { ShowMessage ("Заполнить ячейки"); }<br />
}<br />
50<br />
Следующий пример посвящен пузырьковой сортировке строк String-<br />
Grid с использованием структуры:<br />
void Structures3 () {<br />
TStringGrid *sg = StringGrid1;<br />
int rows = sg–>RowCount;<br />
struct s { AnsiString name; int year; };<br />
s temp;<br />
for (int i=1; ii; – –j)<br />
if (sg–>Cells[0][j] < sg–>Cells[0][j–1]) {<br />
temp.name = sg–>Cells[0][j];<br />
sg–>Cells[0][j] = sg–>Cells[0][j–1];<br />
sg–>Cells[0][j–1] = temp.name;<br />
temp.year = StrToInt (sg–>Cells[1][j]);<br />
sg–>Cells[1][j] = sg–>Cells[1][j–1];<br />
sg–>Cells[1][j–1] = temp.year;<br />
}<br />
}
51<br />
При передаче в функцию в качестве аргументов могут использоваться<br />
как име<strong>на</strong> полей, так и указатели <strong>на</strong> адреса. Применение указателя в качестве<br />
з<strong>на</strong>чения аргумента избавляет от необходимости копирования в функцию<br />
всех данных, но в этом случае они остаются беззащитными перед изменениями<br />
в теле функции. Для устранения этой проблемы перед указателем<br />
можно помещать лексему const. Пример передачи данных структуры:<br />
struct x { int size, price; };<br />
void Change1 (x y) { y.size = y.price; }<br />
void Change2 (x *y) { y–>size = y–>price; }<br />
void Structures4 () {<br />
x y = {1, 2};<br />
Change1 (y);<br />
MyForm–>Edit1–>Text = y.size – y.price;<br />
Change2 (&y);<br />
MyForm–>Edit2–>Text = y.size – y.price;<br />
}<br />
Близкие к структурам по смыслу объединения (union) позволяют хранить<br />
в одной и той же области памяти з<strong>на</strong>чения переменных разных типов,<br />
поочередно заменяя одно другим. Размер памяти, занимаемой данными объединения,<br />
<strong>на</strong>з<strong>на</strong>чается в соответствии с тем из типов, который требует больше<br />
памяти. Их объявление и пример:<br />
union имяТипаОбъединения {объявлениеПолей} имяОбъединения<br />
[={списокЗ<strong>на</strong>чений}];<br />
void Unions () {<br />
union intDbl {int i; double x;} id = {70};<br />
MyForm–>Edit1–>Text = "Масса в граммах" + IntToStr (id.i);<br />
id.x = 0.07;<br />
MyForm–>Edit2–>Text = "Масса в килограммах" + FloatToStr (id.x);<br />
}<br />
Ди<strong>на</strong>мические структуры данных<br />
В отличие от составных типов, ди<strong>на</strong>мические структуры данных (dynamic<br />
data structure), или связные списки (linked list), изменяются в размере по<br />
мере заполнения и освобождения их от данных и обычно не требуют последовательного<br />
размещения элементов в памяти компьютера. Ди<strong>на</strong>мические<br />
структуры не входят в состав типов данных традиционных языков програм-
52<br />
мирования и присутствуют в них как классы. Они, как правило, разрабатываются<br />
программистами или берутся из библиотек – контейнеров. Каждая<br />
структура характеризуется порядком заполнения (allocation) и порядком обхода<br />
(traversal).<br />
Некоторые характеристики классов ди<strong>на</strong>мических структур C++Builder<br />
приведены в табл. 12:<br />
Таблица 12<br />
Свойства и методы контейнерных классов C++Builder<br />
Имя Член Классы Наз<strong>на</strong>чение<br />
Push Метод TStack, TQueue Добавляет член в вершину<br />
Pop Метод TStack, TQueue Удаляет члены из вершины<br />
Peek Метод TStack, TQueue Указатель <strong>на</strong> вершину<br />
Count Метод TStack, TQueue Возвращает число членов (размер)<br />
AtLeast Метод TStack, TQueue Проверяет размер<br />
Capacity Свойство TList Максимальный размер<br />
Count Свойство TList Фактический размер<br />
Items Свойство TList Указатель <strong>на</strong> член<br />
List Свойство TList Указатель <strong>на</strong> список членов<br />
First Метод TList Указатель <strong>на</strong> первый член<br />
Last Метод TList Указатель <strong>на</strong> последний член<br />
Add Метод TList Добавляет член в вершину<br />
Insert Метод TList Добавляет член в заданное место<br />
Delete Метод TList Удаляет член с указанным индексом<br />
Remove Метод TList Удаляет указанный член<br />
Clear Метод TList Удаляет все члены<br />
Pack Метод TList Удаляет пустоты<br />
Exchange Метод TList Меняет местами два чле<strong>на</strong><br />
Move Метод TList Перемещает указанный член<br />
Expand Метод TList Увеличивает максимальный размер<br />
Sort Метод TList Сортирует члены<br />
Наиболее распространенной ди<strong>на</strong>мической структурой является стек<br />
(stack). Это последовательность данных, в которой помещение и выборка<br />
информации выполняется с одного конца, именуемого вершиной стека (top).<br />
Иными словами, стек обрабатывает данные по принципу «последним вошел<br />
– первым вышел» (LIFO – Last Input, First Output). Примеры:<br />
void _ _fastcall TForm1::StackClick (TObject *Sender) {<br />
AnsiString a[] = { "Иванов", "Петров", "Сидоров" }, b = "0\n";<br />
TStack *s = new TStack;<br />
for (int i=0; iPush (a[i].c_str ());<br />
b += AnsiString (s–>Count ()) + ' ' + (char*) s–>Peek () + '\n';
53<br />
} // 0 1 Иванов 2 Петров 3 Сидоров<br />
while (s–>AtLeast (1))<br />
b += AnsiString (s–>Count ()) + ' ' + (char*) s–>Pop () + '\n';<br />
ShowMessage (b + '0'); // 3 Сидоров 2 Петров 1 Иванов 0<br />
}<br />
void SimpleStack() {<br />
TStack *vStack = new TStack;<br />
vStack–>Push ("Пеpвое ");<br />
vStack–>Push ("Втоpое "); vStack–>Push ("Тpетье ");<br />
ShowMessage ("Добавили " + AnsiString (vStack–>Count ()));<br />
vStack–>Pop (); ShowMessage ("Осталось " + AnsiString (vStack–>Count ()));<br />
}<br />
Далее приведен пример разработки авторского символьного стека без<br />
привлечения библиотечного класса TSteak:<br />
const int ARRAYSIZE = 1000;<br />
struct vStack { char s[ARRAYSIZE]; int top; };<br />
void Reset (vStack *stk) { stk–>top = –1; }<br />
int Empty (const vStack *stk) { return stk–>top == –1; }<br />
int Full (const vStack *stk) { return stk–>top == ARRAYSIZE – 1; }<br />
void Push (char *str, vStack *stk) {<br />
int i = 0; while (str[i]) if (!Full (stk)) stk–>s[++stk–>top] = str[i++];<br />
}<br />
void Pop (vStack *stk) {<br />
AnsiString result;<br />
while (!Empty (stk)) result += AnsiString (stk–>s[stk–>top––]) + '\n';<br />
ShowMessage (result);<br />
}<br />
void MyStack () {<br />
vStack s;<br />
Reset (&s);<br />
Push ("Пеpвое ", &s); Push ("Втоpое ", &s); Push ("Тpетье ", &s); Pop (&s);<br />
}<br />
Очередью (queue) <strong>на</strong>зывают последовательность данных, в которой помещение<br />
данных выполняется с одного конца – входа очереди (input), а выборка<br />
– с другого конца – выхода очереди (output) по принципу «первым вошел<br />
– первым вышел» (FIFO – First Input, First Output):<br />
void _ _fastcall TForm1::QueueClick (TObject *Sender) {<br />
AnsiString a[] = { "Иванов", "Петров", "Сидоров" }, b = "0\n";
54<br />
TQueue *q = new TQueue;<br />
for (int i=0; iPush (a[i].c_str());<br />
b += AnsiString (q–>Count ()) + ' ' + (char*) q–>Peek () + '\n';<br />
} // 0 1 Иванов 2 Иванов 3 Иванов<br />
while (q–>AtLeast (1))<br />
b += AnsiString (q–>Count ()) + ' ' + (char*) q–>Pop () + '\n';<br />
ShowMessage (b + '0'); // 3 Иванов 2 Петров 1 Сидоров 0<br />
}<br />
void SimpleQueue () {<br />
TQueue *vQueue = new TQueue;<br />
vQueue–>Push ("Пеpвое ");<br />
vQueue–>Push ("Втоpое ");<br />
vQueue–>Push ("Тpетье ");<br />
ShowMessage ("Добавили " + AnsiString (vQueue–>Count ()));<br />
vQueue–>Pop ();<br />
ShowMessage ("Осталось " + AnsiString (vQueue–>Count ()));<br />
}<br />
Списком <strong>на</strong>зывается ди<strong>на</strong>мическая структура, в которой поддерживаются<br />
помещение и выборка данных в произвольных позициях. Каждый член<br />
списка, именуемый узлом (node), содержит поле данных и поле–указатель<br />
следующего узла. Список <strong>на</strong>зывают линейным (linear list), если обход его выполняется<br />
только в одном <strong>на</strong>правлении – от предшествующего узла к последующему,<br />
<strong>на</strong>чи<strong>на</strong>я с первого узла и кончая последним. Список <strong>на</strong>зывают<br />
циклическим (cyclic list), если последний узел его содержит указатель <strong>на</strong> первый<br />
узел; в нем обход выполняется также в одном <strong>на</strong>правлении, но из любого<br />
узла можно перейти в любой другой узел. Пример:<br />
void MyList () {<br />
TList *vList = new TList ();<br />
AnsiString item = "что-то есть";<br />
try {<br />
vList–>Add (item.c_str ());<br />
ShowMessage ("В списке " + IntToStr (vList–>Count));<br />
vList–>Remove (item.c_str ());<br />
ShowMessage ("В списке " + IntToStr (vList–>Count));<br />
}<br />
catch (...) { delete vList; throw; }<br />
delete vList;<br />
}
55<br />
void Info (TList *vl) {<br />
AnsiString b;<br />
for (int i=0; iCount; ++i)<br />
{ b += AnsiString (i) + ' ' + (char*) vl–>Items[i] + '\n'; }<br />
ShowMessage ("В списке: " + IntToStr (vl–>Count) + '\n' + b);<br />
}<br />
void __fastcall TForm1::ListClick (TObject *Sender){<br />
AnsiString a[] = {"Иванов", "Петров", "Сидоров", "Смирнов"};<br />
TList *vList = new TList;<br />
try {<br />
vList–>Capacity = 10;<br />
for (int i=0; iAdd (a[i].c_str ());<br />
Info (vList); // В списке 4: 0 Иванов 1 Петров 2 Сидоров 3 Смирнов<br />
vList–>Remove (vList–>Items[1]); vList–>Delete (1);<br />
Info (vList); // В списке 2: 0 Иванов 1 Смирнов<br />
vList–>Insert (1, a[0].c_str ());<br />
Info (vList); // В списке 3: 0 Иванов 1 Иванов 2 Смирнов<br />
ShowMessage ("Свободно " + IntToStr (vList–>Capacity–vList–>Count)); // 7<br />
vList–>Pack ();<br />
ShowMessage ("Свободно " + IntToStr (vList–>Capacity–vList–>Count)); // 7<br />
} catch (...) { delete vList; throw; }<br />
delete vList;<br />
}<br />
Пример разработки авторского списка без привлечения библиотечного<br />
класса TList:<br />
struct vList { int value; vList *p; };<br />
vList *current, *last, *first;<br />
int Get () {<br />
static value;<br />
int val = MessageDlg ("Продолжать", mtConfirmation,<br />
TmsgDlgButtons () p = 0;<br />
if (last) last–>p = current; else first = current;
56<br />
last = current;<br />
}<br />
}<br />
void Info () {<br />
AnsiString result;<br />
if (first) {<br />
result = "Список: ";<br />
current = first;<br />
do { result += AnsiString (current–>value) + ' '; current = current–>p; }<br />
while (current);<br />
result += " Конец";<br />
}<br />
ShowMessage (result);<br />
}<br />
void MyList () { Add (); Info (); Add (); Info (); }<br />
Другой пример посвящен двухмерному списку. Он демонстрирует технику<br />
выделения и освобождения памяти ди<strong>на</strong>мической структуре данных:<br />
struct vMatr { double **base; int row, column; };<br />
void Allocate (int r, int s, vMatr &m) {<br />
m.base = new double *[s];<br />
for (int i=0; i
Allocate (k[0], k[1], book1); Allocate (k[2], k[3], book2);<br />
for (i=0; i
58<br />
Можно использовать любой порядок заполнения узлов, но обычно этот<br />
порядок зависит от з<strong>на</strong>чений данных в них: узлы с меньшим з<strong>на</strong>чением присоединяют<br />
слева, а с большим з<strong>на</strong>чением – справа. Чтобы обойти дерево, отметив<br />
каждый узел один раз, используют один из трех способов (рис. 1). При<br />
прямом порядке обхода (depth-first traversal) («просмотре в глубину») движение<br />
<strong>на</strong>чи<strong>на</strong>ется от корня и выполняется от родителей с<strong>на</strong>чала к левым потомкам,<br />
а затем к правым. При обратном порядке (breadth-first traversal) обход<br />
<strong>на</strong>чи<strong>на</strong>ется с листьев и выполняется от потомков к родителям слева <strong>на</strong>право,<br />
завершаясь в корне. Наиболее распространенный обход в симметричном порядке<br />
(simmetrical traversal) <strong>на</strong>правлен от левого потомка через родителя к<br />
правому потомку от листьев к корню.<br />
Пример двоичного дерева:<br />
struct vTree { char *data; vTree *left; vTree *right; };<br />
vTree *root;<br />
void Search (vTree **tree, const char *s);<br />
AnsiString Process (vTree *node);<br />
AnsiString InOrder (vTree *node);<br />
void MyTree () {<br />
char s[128];<br />
// можно сортировать любые массивы<br />
randomize();<br />
for (int i=1; idata = strdup (s); p–>left = 0; p–>right = 0; *tree = p;<br />
}<br />
else {<br />
p = *tree;<br />
cmpResult = strcmp (s, p–>data); // сравнение строк<br />
if (cmpResultleft, s);<br />
else if (cmpResult>0) Search (&p–>right, s);
59<br />
else ShowMessage ("Такой член уже есть");<br />
}<br />
}<br />
AnsiString Process (vTree *node) {<br />
static AnsiString result;<br />
result += AnsiString (node–>data) + ' ';<br />
return result;<br />
}<br />
AnsiString InOrder (vTree *node) {<br />
AnsiString result;<br />
If (node != 0) {<br />
InOrder(node–>left);<br />
result = Process (node);<br />
InOrder (node–>right);<br />
}<br />
return result;<br />
}<br />
Стандарт<strong>на</strong>я библиотека STL<br />
Библиотека STL (Standard Templates Library) представляет <strong>на</strong>бор шаблонов<br />
классов <strong>С++</strong>, включающий контейнеры, итераторы, аллокаторы и алгоритмы,<br />
в пространстве имен std. Контейнеры (container) служат классами,<br />
организующими хранение объектов. Неупорядоченные контейнеры представляют<br />
vector, list, deque. К упорядоченным контейнерам относятся set,<br />
map, hash-set, hash-map и их разновидности. Итераторами (iterator) <strong>на</strong>зывают<br />
средства перемещения по объектам контейнеров. Обычно эту роль играют<br />
указатели, поддерживающие итерацию. Итераторы бывают прямыми и<br />
обратными, константными и неконстантными, изменяющими содержимое<br />
контейнера. Низкоуровневые функции распределения памяти контейнеров<br />
<strong>на</strong>зывают аллокаторами (allocator). С их помощью все члены STL автоматически<br />
выделяют и освобождают память, не требуя от программиста использования<br />
операторов new и delete. Для манипулирования данными в контейнерах<br />
служат алгоритмы (algorithm). Обычно <strong>на</strong> них возлагаются стандартные<br />
операции сортировки, поиска, сравнения, перестановки и т.п.<br />
Контейнер vector, описанный в файле заголовка является ди<strong>на</strong>мическим<br />
массивом переменного размера. Подобно стеку, он обеспечивает<br />
добавление объектов в конец функцией push_back и удаление их оттуда<br />
функцией pop_back. Кроме того, в нем реализова<strong>на</strong> вставка объектов в про-
60<br />
извольное место функцией insert и удаление их оттуда функцией erase.<br />
Функция clear удаляет из контейнера все объекты, а функция size возвращает<br />
число объектов в контейнере. Функция empty сообщает, пуст ли контейнер,<br />
reserve резервирует память для будущих объектов, а capacity возвращает текущий<br />
объем выделенной памяти.<br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::VectorClick (TObject *Sender) {<br />
vector n;<br />
vector::iterator j;<br />
vector::reverse_iterator rj;<br />
ListBox1–>Items–>Add (n.size ());<br />
ListBox1–>Items–>Add (n.capacity ()); // 0 0<br />
for (int i=0; iItems–>Add (n.size ());<br />
ListBox1–>Items–>Add (n.capacity ()); // 10 16<br />
for (j = n.begin (); j!=n.end (); ++j) ListBox2–>Items–>Add (*j); // 0,1,...,9<br />
for (rj = n.rbegin (); rj!=n.rend (); ++rj) ListBox3–>Items–>Add (*rj); // 9,8,...,0<br />
n.erase (n.begin ()+5, n.begin ()+7);<br />
// удаление 5-6-го<br />
n.insert (n.end ()–2, –1);<br />
// вставка –1 3-м с конца<br />
n.push_back (10);<br />
// вставка 10 в конец<br />
ListBox1–>Items–>Add (n.size ());<br />
ListBox1–>Items–>Add (n.capacity()); // 10 16<br />
for (j=n.begin (); j!=n.end (); ++j) ListBox4–>Items–>Add (*j);<br />
// 0,1,2,3,4,7,–1,8,9,10<br />
n.clear ();<br />
}<br />
Контейнер deque из одноименного файла заголовка, в отличие от vector,<br />
может заполняться и освобождаться от объектов с двух сторон.<br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::DequeClick (TObject *Sender) {<br />
deque n;<br />
deque::iterator j;<br />
deque::reverse_iterator rj;<br />
for (int i=0; iAdd (*j); // 4,3,2,1,0,5,6,7,8,9
61<br />
for (rj=n.rbegin (); rj!=n.rend (); ++rj) ListBox2–>Items–>Add (*rj);<br />
// 9,8,7,6,5,0,1,2,3,4<br />
n.pop_front ();<br />
n.pop_back ();<br />
for (j = n.begin (); j!=n.end (); ++j) ListBox3–>Items–>Add (*j); // 3,2,1,0,5,6,7,8<br />
n.erase (n.begin ()+5, n.begin ()+7);<br />
// удаление 5-6-го<br />
n.insert (n.end()–2, –1);<br />
// вставка –1 3-м с<br />
конца<br />
for (j = n.begin (); j!=n.end (); ++j) ListBox4–>Items–>Add (*j); // 3,2,1,0,–1,5,8<br />
n.clear ();<br />
}<br />
Контейнер map организован в виде множества упорядоченных пар<br />
(иногда троек). Каждая пара состоит из первого чле<strong>на</strong> (first), являющегося<br />
ключом, и второго чле<strong>на</strong> (second) – з<strong>на</strong>чения объекта. Сортировка пар выполняется<br />
автоматически по ключу, хотя возмож<strong>на</strong> также программ<strong>на</strong>я сортировка.<br />
#include <br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::MapClick (TObject *Sender) {<br />
map m;<br />
map::iterator j;<br />
double r = 10;<br />
m["pi"] = 3.14159;<br />
m["e"] = 2.718;<br />
m["Скорость света"] = 2.998E8;<br />
m["Площадь круга"] = pow(r, 2) * m["pi"];<br />
for (j=m.begin (); j!=m.end (); ++j)<br />
ListBox1–>Items–>Add (j–>first + '=' + FloatToStr (j–>second));<br />
// e=2.718; pi=3.14159; Площадь круга=314.159; Скорость света=2998...<br />
}<br />
Алгоритм find выполняет поиск объектов контейнера между двумя установленными<br />
итераторами с возвращением итератора <strong>на</strong>йденного объекта, а<br />
в случае отсутствия такового – с возвращением второго итератора.<br />
#include <br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::findClick (TObject *Sender) {<br />
vector v;
vector::iterator j;<br />
int what = 3;<br />
for (int i=–5; iItems–>Add (*j);<br />
j = std::find (v.begin (), v.end (), what);<br />
if (j != v.end ()) ShowMessage ("Нашел " + AnsiString (*j)); // Нашел 3<br />
else ShowMessage ("Не <strong>на</strong>шел");<br />
}<br />
62<br />
Алгоритмы min и max отыскивают экстремумы.<br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::minMaxClick (TObject *Sender) {<br />
ListBox1–>Items–>Add (max (15, 5)); // 15<br />
ListBox1–>Items–>Add (max ('a', 'b')); // b<br />
ListBox1–>Items–>Add (min (3.14, 24.5)); // 3,14<br />
}<br />
Алгоритмы random_shuffle и sort соответственно перемешивают и сортируют<br />
объекты контейнера.<br />
#include <br />
#include <br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::sortClick (TObject *Sender) {<br />
vector v;<br />
vector::iterator j;<br />
randomize ();<br />
for (int i='a'; iItems–>Add (*j);<br />
std::sort (v.begin (), v.end ());<br />
for (j = v.begin (); j!=v.end (); ++j) ListBox2–>Items–>Add (*j);<br />
std::sort (v.begin (), v.end (), greater ());<br />
for (j = v.begin (); j!=v.end (); ++j) ListBox3–>Items–>Add (*j);<br />
}<br />
Алгоритм binary_search ищет объекты <strong>на</strong> би<strong>на</strong>рном дереве.<br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::BinSearchClick (TObject *Sender) {<br />
// ab...z<br />
// zy...a
int i, d[] = { 5, 2, 3, 8, 3, 1, 3 };<br />
const int D = sizeof (d) / sizeof (int);<br />
for (i=0; iItems–>Add (d[i]);<br />
std::sort (d, d+D);<br />
for (i=0; iItems–>Add (IntToStr (i) +<br />
(binary_search (d, d+D, i) " есть" : " нет"));<br />
AnsiString s1[] = {"раз", "два", "четыре", "пять" },<br />
s2[] = { "раз", "три", "пять", "семь"};<br />
const int S1 = sizeof (s1) / sizeof (s1[0]), S2 = sizeof (s2) / sizeof (s2[0]);<br />
for (i=0; iItems–>Add (s1[i]);<br />
std::sort (s1, s1+S1);<br />
for (i=0; iItems–>Add (s2[i] +<br />
(binary_search (s1, s1+S1, s2[i]) " есть" : " нет"));<br />
}<br />
63<br />
Контейнер set поддерживает уникальные ключи и дву<strong>на</strong>правленные<br />
итераторы, являясь хранилищем упорядоченных объектов, удобным для поиска<br />
и сравнения с а<strong>на</strong>логичными хранилищами. Он незаменим для выполнения<br />
классических операций <strong>на</strong> множествах: при<strong>на</strong>длежности, объединения,<br />
пересечения, вычитания, дополнения. В отличие от vector и map, контейнер<br />
set не позволяет непосредственно воздействовать <strong>на</strong> з<strong>на</strong>чения объектов, требуя<br />
вместо этого вставки измененных объектов извне. В следующем примере<br />
множество s1 заполняется числами, и эти же числа копируются в s2. Затем<br />
отыскивается и удаляется первая полови<strong>на</strong> s2, остаток заполняется новыми<br />
числами, и вычисляются результаты объединения и пересечения s1 и s2. В<br />
программе работают алгоритмы advance – перемещение итератора, set_union<br />
– объединение, set_intersection – пересечение множеств.<br />
#include <br />
#include <br />
using namespace std;<br />
void __fastcall TForm1::SetClick (TObject *Sender) {<br />
set s1, s2, s3, s4;<br />
set::iterator j;<br />
for (int i=0; i>1);<br />
s2.erase (s2.begin (), j);
for (int i=10; iItems–>Add (*j); // 0...9<br />
for (j=s2.begin (); j!=s2.end (); ++j) ListBox2–>Items–>Add (*j); // 5...14<br />
for (j=s3.begin (); j!=s3.end (); ++j) ListBox3–>Items–>Add (*j); // 0...14<br />
for (j=s4.begin (); j!=s4.end (); ++j) ListBox4–>Items–>Add (*j); // 5...9<br />
}<br />
64<br />
Контрольные вопросы<br />
1. Для чего пред<strong>на</strong>з<strong>на</strong>чены массивы в программировании<br />
2. Как объявляются и инициализируются массивы<br />
3. Чем различаются статические и ди<strong>на</strong>мические массивы и в чем состоят<br />
достоинства и недостатки каждого из них<br />
4. Как передаются массивы в функции<br />
5. Какова связь между именем массива и его адресом Можно ли применять к<br />
массиву операцию взятия адреса.<br />
6. Как определяется адрес элемента массива по его индексу<br />
7. Каковы основные свойства компонента StringGrid<br />
8. Что такое рекурсия<br />
9. Какие свойства визуальных компонентов представлены массивами<br />
10. Как выполняется поиск максимального, минимального и среднего з<strong>на</strong>чений<br />
массива<br />
11. Каков принцип сортировки данных методом «пузырька»<br />
12. За сколько циклов выполняется сортировка методом «пузырька»<br />
13. Для чего функции qsort требуется в качестве аргумента авторская функция<br />
14. Чем структура отличается от массива<br />
15. В чем состоит разница между структурой и объединением<br />
16. Как инициализируются поля структуры<br />
17. Как рассчитывается объем памяти, занимаемый структурой<br />
18. Может ли структура содержать массив, а массив – структуру<br />
19. Какие известны ди<strong>на</strong>мические структуры данных<br />
20. Каков принцип действия стека<br />
21. Каков принцип действия очереди
22. Каков принцип действия линейного списка<br />
23. Каков принцип действия дека<br />
24. Чем определяется порядок обхода двоичного дерева<br />
25. Каково <strong>на</strong>з<strong>на</strong>чение стандартной библиотеки STL<br />
26. Каково <strong>на</strong>з<strong>на</strong>чение контейнеров и итераторов<br />
27. В чем состоит различие прямого и обратного итераторов<br />
28. Как организован контейнер map<br />
29. Как организован контейнер set<br />
30. Как подключается к проекту библиотека STL<br />
65<br />
Введение в модульное <strong>программирование</strong><br />
Требуется гораздо больше ума, чтобы передать<br />
свои мысли, чем чтобы их иметь<br />
Из Гельвеция<br />
Концепция модульного программирования предполагает конструирование<br />
программ из модулей, т. е. групп файлов каждая из которых освещает<br />
свой раздел общей проблемы. Текстовые, графические, исполнительные файлы,<br />
ресурсы и библиотеки тесно переплетаются в модулях. Модуль визуального<br />
программирования часто состоит из файлов отдельных форм: кода, заголовка,<br />
структуры и диаграммы, к которым после компиляции добавляются<br />
объектные файлы. Особого внимания требуют такие модульные системы, как<br />
библиотеки статической и ди<strong>на</strong>мической компоновки, справочные средства и<br />
потоки данных [7].<br />
Работа с текстовыми файлами<br />
Файл (file) представляет именованную область <strong>на</strong>копителя, пред<strong>на</strong>з<strong>на</strong>ченную<br />
для хранения, передачи и приема структурированной информации.<br />
Число одновременно открытых файлов и их размер определяются возможностями<br />
операционной системы и ресурсами компьютера.<br />
Ряд визуальных компонентов поддерживает файловый обмен своими<br />
методами SaveToFile и LoadFromFile. К ним относится строковый класс<br />
TStrings и производные от него классы.<br />
В частности, для загрузки и сохранения строк в текстовом файле используются<br />
методы SaveToFile и LoadFromFile компонента TStringList, орга-
66<br />
низующие обмен таким образом, что каждая строка файла соответствует<br />
строке списка. Компонент можно применять для заполнения элементов<br />
управления классов Memo и RichEdit или для сохранения содержимого элементов<br />
управления классов List и ComboBox, <strong>на</strong>пример:<br />
void StringLists1 () {<br />
TStringList *sl = new TStringList;<br />
sl–>Add (“Раз”);<br />
sl–>Add (“Два”);<br />
sl–>Add (“Три”);<br />
sl–>SaveToFile ("d:/Temp/File.txt"));<br />
sl–>Clear ();<br />
sl–>LoadFromFile ("d:/Temp/File.txt"));<br />
}<br />
// Заполнение поля Memo<br />
void StringLists2 () {<br />
AnsiString file = "test.INI";<br />
Form1–>Memo1–>Lines–>LoadFromFile (file);<br />
Form1–>Memo1–>Lines–>SaveToFile (ChangeFileExt (file,".BAK"));<br />
}<br />
// Списки в файл и из файла<br />
void _ _fastcall TForm1::StringListFilesClick (TObject *Sender) {<br />
AnsiString fName = "D:\\examples\\tempFile.txt";<br />
TStringList *sl = new TStringList;<br />
sl–>Add ("раз"); sl–>Add ("два"); sl–>Add ("три"); sl–>SaveToFile (fName);<br />
sl–>Clear (); sl–>LoadFromFile (fName);<br />
ListBox1–>Items–>LoadFromFile (fName); ComboBox1–>Items–>Assign (sl);<br />
delete sl;<br />
}<br />
// Строки в файл и из файла<br />
void __fastcall TForm1::ListFilesClick (TObject *Sender) {<br />
AnsiString fName ="tempFile.txt";<br />
StringListFilesClick (Sender);<br />
struct s { AnsiString as; int i; };<br />
s myS[3];<br />
for (int j=0; jItems–>Count; ++j) {<br />
myS[j].as = ListBox1–>Items–>Strings[j]; myS[j].i = j;<br />
}<br />
for (int j=0; j
StringGrid1–>Cells[0][j] = myS[j].as; StringGrid1–>Cells[1][j] = myS[j].i;<br />
}<br />
StringGrid1–>Cols[0]–>SaveToFile (fName);<br />
Memo1–>Lines–>LoadFromFile (fName);<br />
RichEdit1–>Lines–>LoadFromFile (fName);<br />
}<br />
67<br />
Для проведения файлового обме<strong>на</strong> без рассмотренных компонентов в<br />
C++Builder используются функции, приведенные в табл. 13. Работа с файлом<br />
ведется с помощью файловых переменных (file variable), связываемых с конкретными<br />
файлами. Файл открывается для чтения, записи или других операций.<br />
После работы файл следует закрыть.<br />
Таблица 13<br />
Функция<br />
Int FileCreate (AnsiString name)<br />
Int FileOpen (AnsiString name, int<br />
mode)<br />
Int FileWrite (int handle, void<br />
*buffer, int count)<br />
Int FileRead (int handle, void<br />
*buffer, int count)<br />
void FileClose(int handle)<br />
int FileSeek (int handle, int offset,<br />
int origin)<br />
bool DeleteFile (AnsiString name)<br />
bool FileExists (AnsiString name)<br />
bool DirectoryExists (AnsiString<br />
name)<br />
bool ForceDirectories (AnsiString<br />
dir)<br />
AnsiString FileSearch (AnsiString<br />
name, AnsiString dir)<br />
Функции файлового обме<strong>на</strong><br />
Наз<strong>на</strong>чение<br />
Создает новый или переписывает существующий<br />
файл с заданным именем, возвращая файловую переменную.<br />
Открывает файл в режимах (mode) fmOpenRead<br />
(чтение), fmOpenWrite (запись), fmOpenReadWrite<br />
(чтение-запись), возвращая файловую переменную.<br />
Пишет count байт в файл handle из *buffer, возвращая<br />
число записанных байтов<br />
Читает count байт из файла handle в *buffer, возвращая<br />
число прочитанных байтов.<br />
Закрывает файл.<br />
Позиционирует указатель в файле со смещением<br />
(offset) относительно базы (origin), равной 0 (<strong>на</strong>чало),<br />
1 (текущая позиция указателя) или 2 (конец<br />
файла), возвращая позицию указателя в файле.<br />
Удаляет файл.<br />
Проверяет <strong>на</strong>личие файла.<br />
Проверяет <strong>на</strong>личие папки.<br />
Создает папки.<br />
Выполняет поиск файла name в папках dir.<br />
Ниже даны примеры организации файлового обме<strong>на</strong>. Первый простой<br />
пример представляем вывод в файл содержания полей Edit1 и Edit2:<br />
void _ _fastcall TForm1::FieldsToFileClick (TObject *Sender) {<br />
AnsiString a = Edit1–>Text,<br />
b = Edit2–>Text;
int f = FileCreate ("tempFile.txt");<br />
if (f != –1) {<br />
FileWrite (f, a.c_str (), a.Length ());<br />
FileWrite (f, b.c_str (), b.Length ());<br />
FileClose (f);<br />
}<br />
}<br />
68<br />
Во втором примере создается файл, в него заносятся два числа, и файл<br />
закрывается. Затем файл открывается для чтения и возвращается его содержимое.<br />
void Files1 () {<br />
AnsiString name = "test.dat";<br />
if (FileExists (name))<br />
if (Application–>MessageBoxA ("Переписать файл",<br />
"Есть такой файл", MB_YESNO | MB_ICONQUESTION)!=IDYES) return;<br />
int iFile = FileCreate (name);<br />
if (iFile == –1) { ShowMessage ("Не создать файл"); return; }<br />
AnsiString first = MyForm–>Edit1–>Text;<br />
AnsiString second = MyForm–>Edit2–>Text;<br />
char *firstStr = first.c_str (), *secondStr = second.c_str ();<br />
int err = FileWrite (iFile, firstStr, first.Length ());<br />
if (err == –1) { ShowMessage ("Не записать файл"); return;}<br />
err = FileWrite (iFile, secondStr, second.Length ());<br />
if (err == –1) { ShowMessage ("Не дописать файл"); return; }<br />
FileClose (iFile);<br />
iFile = FileOpen (name, fmOpenRead);<br />
if (iFile == –1) { ShowMessage ("Не открыть файл"); return; }<br />
int readLen = first.Length () + second.Length ();<br />
char *getStr;<br />
getStr = new char[readLen + 1];<br />
err = FileRead (iFile, getStr, readLen);<br />
if (err == –1) { ShowMessage ("Не прочитать файл"); return; }<br />
FileClose (iFile);<br />
ShowMessage (AnsiString (getStr, readLen));<br />
}<br />
В третьем примере после открытия первого файла определяется его<br />
размер, указатель возвращается в <strong>на</strong>чало, и в массив читается его содержимое.<br />
Затем открывается второй файл для записи в него содержимого массива.
void Files2 () {<br />
AnsiString from = MyForm–>Edit1–>Text, to = MyForm–>Edit2–>Text;<br />
}<br />
try {<br />
int iFile = FileOpen (from, fmOpenRead);<br />
int len = FileSeek (iFile, 0, 2);<br />
FileSeek (iFile, 0, 0);<br />
char *c;<br />
c = new char [len + 1];<br />
len = FileRead (iFile, c, len);<br />
FileClose (iFile);<br />
iFile = FileOpen (to, fmOpenWrite);<br />
FileWrite (iFile, c, len);<br />
ShowMessage (from + " –> " + to + '\n' + AnsiString (c, len));<br />
delete [] c;<br />
FileClose (iFile);<br />
} catch (...) { ShowMessage ("Ошибка одной из файловых операций"); }<br />
69<br />
А<strong>на</strong>логичный пример посвящен отображению содержания файла в<br />
<strong>на</strong>дписи Label1:<br />
void _ _fastcall TForm1::LabelFromFileClick (TObject *Sender) {<br />
int f = FileOpen ("tempFile.txt", fmOpenRead);<br />
if (f != –1) {<br />
int size = FileSeek (f, 0, 2);<br />
char *s = new char[size+1];<br />
FileSeek (f, 0, 0);<br />
FileRead (f, s, size);<br />
Label1–>Caption = AnsiString (s, size);<br />
FileClose (f);<br />
delete [] s;<br />
}<br />
}<br />
Далее показано, как файл с текстом, указанным в Label1, дополняется<br />
содержимым поля Edit1:<br />
void _ _fastcall TForm1::InsertToFileClick (TObject *Sender) {<br />
int f = FileOpen ("tempFile.txt", fmOpenWrite);<br />
if (f != –1) {<br />
AnsiString is = Label1–>Caption, what = Edit1–>Text;<br />
is += what;<br />
Label2–>Caption = is;<br />
FileWrite (f, is.c_str (), is.Length ());
FileClose (f);<br />
}<br />
}<br />
70<br />
Следующий пример посвящен изменению содержимого файла. В<strong>на</strong>чале<br />
текст файла считывается в массив, который преобразуется в строку. В этой<br />
строке отыскивается заданное слово и заменяется фразой «[Удалено]», а<br />
файл заполняется пробелами. После этого изменен<strong>на</strong>я строка возвращается в<br />
файл.<br />
void Files3 () {<br />
AnsiString word = MyForm–>Edit1–>Text,<br />
file = MyForm–>Edit2–>Text,<br />
bText ="";<br />
try {<br />
int bFile = FileOpen (file, fmOpenReadWrite);<br />
int bLen = FileSeek (bFile, 0, 2);<br />
char *b = new char [bLen];<br />
FileSeek (bFile, 0, 0);<br />
FileRead (bFile, b, bLen);<br />
bText =AnsiString (b, bLen);<br />
delete [] b;<br />
int index = bText.AnsiPos (word.c_str());<br />
if (!index) {<br />
ShowMessage ("В файле нет такого слова");<br />
FileClose (bFile); return;<br />
}<br />
bText.Delete (index, word.Length ());<br />
bText.Insert ("[Удалено]", index);<br />
FileSeek (bFile, 0, 0);<br />
AnsiString c = AnsiString::StringOfChar (' ', bLen);<br />
FileWrite (bFile, c.c_str (), bLen);<br />
FileSeek (bFile, 0, 0);<br />
FileWrite (bFile, bText.c_str (), bText.Length ());<br />
FileClose (bFile);<br />
} catch (...) { ShowMessage ("Ошибка файловой операции"); }<br />
ShowMessage (bText);<br />
}<br />
Пример удаления фрагмента, заданного в Edit1, из файла, в котором содержится<br />
указан<strong>на</strong>я в Label1 фраза:
void _ _fastcall TForm1::DelFromFileClick (TObject *Sender) {<br />
AnsiString is = Label1–>Caption, what = Edit1–>Text, was = is;<br />
int f = is.AnsiPos (what.c_str ());<br />
ShowMessage (is+ ' ' +IntToStr (is.Length ()));<br />
If (f) is.Delete (f, what.Length ());<br />
ShowMessage (is + ' ' + IntToStr (is.Length ()));<br />
Label3–>Caption = is;<br />
f = FileOpen ("tempFile.txt", fmOpenWrite);<br />
if (f != –1) {<br />
is += AnsiString::StringOfChar (' ', was.Length ());<br />
FileWrite (f, is.c_str (), is.Length ());<br />
FileClose (f);<br />
}<br />
}<br />
71<br />
Еще один пример демонстрирует процесс поиска файла, в том числе с<br />
символом подстановки *, в текущей папке и в папке Windows:<br />
void Files4 () {<br />
char buffer[256];<br />
GetWindowsDirectory (buffer, sizeof (buffer));<br />
AnsiString file = FileSearch (MyForm–>Edit1–>Text, GetCurrentDir() +<br />
";" + AnsiString (buffer));<br />
if (file.IsEmpty ()) ShowMessage ("Нет файла " + MyForm–>Edit1–>Text);<br />
else ShowMessage ("Есть файл " + file + " !");<br />
}<br />
Работа с графическими файлами<br />
К графическим компонентам, поддерживающим файловый обмен<br />
своими методами SaveToFile и LoadFromFile, относятся TPicture, TOLE-<br />
Graphic, TGraphic и производные от них классы. Рассмотрим несколько примеров:<br />
Пример использования графических файлов:<br />
// Картинки из файлов<br />
void __fastcall TForm1::GraphFilesClick (TObject *Sender){<br />
AnsiString fName = "D:\\examples\\scan.bmp";<br />
Image1–>Picture–>LoadFromFile (fName);<br />
Form1–>Icon–>LoadFromFile ("scan.ico");<br />
Graphics::TBitmap *gb = new Graphics::TBitmap;<br />
gb–>LoadFromFile (fName); PaintBox1–>Canvas–>Draw (0, 0, gb);<br />
this–>Canvas–>CopyMode = cmSrcPaint;
this–>Canvas–>CopyRect<br />
(ClientRect, PaintBox1–>Canvas, PaintBox1–>ClientRect);<br />
delete gb;<br />
}<br />
// Кнопки с картинками из файла<br />
void _ _fastcall TForm1::BtnFilesClick (TObject *Sender) {<br />
AnsiString fName = "D:\\examples\\scan.bmp";<br />
BitBtn1–>Glyph–>LoadFromFile (fName);<br />
SpeedButton1–>Glyph–>LoadFromFile (fName);<br />
}<br />
72<br />
Некоторые особенности использования компонента Image отражает<br />
табл. 14.<br />
Таблица 14<br />
Свойство<br />
Picture<br />
Width, Height<br />
Proportional<br />
Strech<br />
AutoSize<br />
Center<br />
Visible<br />
Canvas<br />
Свойства компонента Image<br />
Наз<strong>на</strong>чение<br />
Отображаемый файл<br />
Шири<strong>на</strong> и высота<br />
Автоматическое масштабирование без искажения иллюстрации в границах<br />
элемента управления<br />
Подгонка иллюстрации под размер элемента управления с искажением<br />
Подгонка элемента управления под размер иллюстрации<br />
Размещение иллюстрации в центре элемента управления<br />
Вывод <strong>на</strong> экран<br />
Экран<strong>на</strong>я поверхность для отображения<br />
Компонент поддерживает графические форматы .BMP, .ICO и .WMF.<br />
Для работы с форматом .JPG <strong>на</strong>до подключить файл заголовка .<br />
Следующий пример показывает функцию, отображающую первый из<br />
графических файлов, размещенных в папке приложения, а также вариант ее<br />
использования:<br />
#include <br />
#include // для работы функции SelectDirectory<br />
AnsiString myPath = “”; // текущая папка с иллюстрациями<br />
TSearchRec sr;<br />
// результат поиска<br />
void TForm1::FirstPicture () {<br />
if (FindFirst (myPath + “*.jpg”, faAnyFile, sr) == 0) {<br />
Image1–>Picture–>LoadFromFile (myPath + sr.Name);<br />
Label1–>Caption = sr.Name;<br />
}
73<br />
Фрагмент обработчика кнопки перехода <strong>на</strong> следующий файл текущей<br />
папки может быть таким:<br />
if (FindNext (sr) == 0) {<br />
Image1–>Picture–>LoadFromFile (myPath + sr.Name);<br />
Label1–>Caption = sr.Name;<br />
}<br />
В свою очередь, обработчик кнопки поиска файлов открывает стандартное<br />
окно поиска папки с файлами и вновь вызывает функцию отображения<br />
первого файла папки:<br />
if (SelectDirectory ("Выбор папки", "", myPath))<br />
{ myPath += "\\"; FirstPicture (); }<br />
Визуальный компонент Bitmap поддерживает работу с битовыми образами<br />
– небольшими картинками формата .BMP, которыми удобно манипулировать<br />
в памяти компьютера, обращаясь к свойствам (табл. 15) и методам<br />
компонента.<br />
Таблица 15<br />
Свойство<br />
Width, Height<br />
Empty<br />
Transparent<br />
TransparentColor<br />
Canvas<br />
Свойства компонента Bitmap<br />
Наз<strong>на</strong>чение<br />
Шири<strong>на</strong> и высота загружаемой иллюстрации<br />
Приз<strong>на</strong>к отсутствия иллюстрации<br />
Уста<strong>на</strong>вливает «прозрачность», т. е. не отображает цвет, указанный в<br />
свойстве TransparentColor, по умолчанию – цвет нижнего левого<br />
пиксела<br />
Прозрачный цвет, который не выводится <strong>на</strong> экран при установленном<br />
свойстве Transparent<br />
Экран<strong>на</strong>я поверхность для отображения<br />
Следующий пример демонстрирует мультипликацию – движение<br />
битового образа sprite <strong>на</strong> фоне иллюстрации back. Рисование образа по канве<br />
Canvas выполняется методом канвы Draw в событии формы FormPaint по командам<br />
невидимого объекта класса Timer (Interval 100 мс). Коорди<strong>на</strong>ты x, y и<br />
объекты back и sprite созданы как переменные формы:<br />
TForm1 *Form1;<br />
int x, y;<br />
Graphics::TBitmap *back = new Graphics::TBitmap;<br />
Graphics::TBitmap *sprite = new Graphics::TBitmap;<br />
__fastcall TForm1::TForm1 (TComponent* Owner) : TForm (Owner) {<br />
back–>LoadFromFile ("1.bmp");
sprite–>LoadFromFile ("2.bmp");<br />
sprite–>Transparent = true;<br />
x = –20;<br />
// исход<strong>на</strong>я позиция за пределами формы<br />
y = 20;<br />
}<br />
void __fastcall TForm1::FormPaint (TObject *Sender) {<br />
Canvas–>Draw (0, 0, back);<br />
// отображение фо<strong>на</strong><br />
Canvas–>Draw (x, y, sprite);<br />
// и объекта<br />
}<br />
void __fastcall TForm1::Timer1Timer (TObject *Sender) {<br />
TRect reback;<br />
// восста<strong>на</strong>вливаемая область фо<strong>на</strong><br />
reback = Rect (x, y, x+sprite–>Width, y+sprite–>Height);<br />
Canvas–>CopyRect (reback, back–>Canvas, reback); // восстановление<br />
x += 2;<br />
// движение вправо<br />
if (x > ClientWidth) x = –20;<br />
// возврат<br />
Canvas–>Draw (x, y, sprite);<br />
// рисование объекта<br />
}<br />
74<br />
Работа с двоичными файлами<br />
Разработано множество способов запуска исполнительных файлов из<br />
приложений C++.<br />
В файле заголовка объявле<strong>на</strong> группа функций семейства<br />
exec… Эти функции загружают в память и открывают исполнительные файлы<br />
(дочерние процессы), одновременно лишая ресурсов и прекращая родительский<br />
процесс. Первым параметром этих функций всегда является путь к<br />
вызываемому дочернему процессу, а при запуске каждая такая функция передает<br />
несколько дополнительных параметров этому процессу:<br />
exec…(путь, параметры).<br />
Например:<br />
execl (“MyFile.exe”, “MyFile.exe”, NULL);<br />
Суффиксы l, v, p, e, добавляемые к имени exec, раскрывают требования<br />
к параметрам. Так, l свидетельствует о постоянном фиксированном числе параметров,<br />
v говорит о непостоянном массиве указателей в списке параметров,<br />
p указывает, что путь следует искать в переменной окружения path, а e – в<br />
списке параметров окружения дочернего процесса.<br />
В случае неудачного запуска exec… возвращает –1, а глобаль<strong>на</strong>я перемен<strong>на</strong>я<br />
errno уста<strong>на</strong>вливается в одно из следующих з<strong>на</strong>чений: EACCESS –
75<br />
отказ в доступе, EMFILE – много открытых файлов, ENOENT – не <strong>на</strong>йден<br />
путь, ENOEXEC – мало памяти.<br />
В файле заголовка shellapi.h объявле<strong>на</strong> серия полезных функций запуска<br />
приложений без закрытия родительского процесса. Среди них – WinExec,<br />
ShellExecute, CreateProcess и другие. В числе их параметров всегда присутствует<br />
режим ок<strong>на</strong>, который задается константами SW_MINIMIZE,<br />
SW_MAXIMIZE, SW_NORMAL, SW_RESTORE, SW_HIDE. Примеры:<br />
WinExec (“MyFile”, SW_RESTORE);<br />
ShellExecute (Handle, NULL, “MyFile.exe”, NULL, NULL, SW_RESTORE);<br />
ShellExecute (Handle, “open”, “File.doc”, NULL, NULL, SW_RESTORE);<br />
К двоичным файлам относятся, также, растровые файлы, з<strong>на</strong>чки, курсоры<br />
и файлы ресурсов (.RES). Все они просматриваются, создаются и редактируются<br />
графическим редактором Image Editor, доступным через меню<br />
Tools. Редактор достаточно прост в использовании и во многом <strong>на</strong>поми<strong>на</strong>ет<br />
известный редактор Microsoft Paint.<br />
Модули DLL и пакеты<br />
Одним из самых распространенных способов создания модульных приложений<br />
являеся использование библиотек ди<strong>на</strong>мической компоновки DLL и<br />
BPL. Такие библиотеки представляют особый вид выполняемого кода (компилированных<br />
функций, констант, форм, ресурсов), хранящийся в файлах с<br />
расширениями .DLL и .BPL, которые рассчитаны <strong>на</strong> одновременное использование<br />
несколькими программами. Пакеты (package) BPL ос<strong>на</strong>щены средствами<br />
работы с компонентами VCL. Они располагают большей, чем DLL,<br />
информацией о вызывающем приложении, а приложение, в свою очередь,<br />
лучше контролирует поведение связанного модуля. К сожалению BPL обслуживают<br />
только приложения C++Builder, тогда как DLL более универсальны<br />
и работают с любыми приложениями в разных операционных системах.<br />
Модули DLL и BPL можно подключать к приложению статически (design<br />
time mode) и ди<strong>на</strong>мически (runtime mode). Статически загружаемые модули<br />
связываются с главной программой при ее компоновке и остаются загруженными<br />
в память все время, пока выполняется приложение. Такие модули<br />
содержат экспортируемые функции, описание которых помещается в файлы<br />
библиотек импорта LIB – Library Import File, которые также подключаются<br />
<strong>на</strong> этапе компоновки. Ди<strong>на</strong>мически загружаемые модули в процессе работы<br />
при необходимости можно выгружать из памяти, освобождая при этом ре-
76<br />
сурсы. В ряде режимов возмож<strong>на</strong> работа таких приложений и без них. Дополнительные<br />
библиотеки импорта здесь не используются.<br />
Библиотека DLL создается командой File.New из шабло<strong>на</strong> DLLWizard. В<br />
состав проекта автоматически включаются файл проекта (<strong>на</strong>пример,<br />
myDLL.BPR) и файл модуля DLL (myDLL.CPP) без формы, которые следует<br />
дополнить файлом заголовка (myDLL.h) и при необходимости – файлами<br />
требуемых форм. Пример файла модуля, формирующего произвольное сообщение<br />
авторской функцией Message и открывающего форму О программе<br />
другой функцией About:<br />
//myDLL.CPP<br />
#include "myDLL.h"<br />
int WINAPI DllEntryPoint (HINSTANCE hinst,<br />
unsigned long reason, void* lpReserved) { return 1; }<br />
void Message (char *s) { ShowMessage ((AnsiString) s + " из DLL"); }<br />
void About (char *s) {<br />
Form1 = new TForm1 (0);<br />
Form1–>Label1–>Caption = (AnsiString) s + " из DLL";<br />
Form1–>ShowModal ();<br />
delete Form1;<br />
}<br />
Функция DllEntryPoint является главной функцией DLL. Функции Message<br />
и About здесь служат примерами библиотечных функций, которые будут<br />
подключаться к приложению с выводом передаваемой оттуда строки.<br />
Объявление этих функций как функций экспорта следует поместить в файл<br />
заголовка:<br />
extern "C" void __declspec (dllexport) Message (char *s);<br />
extern "C" void __declspec (dllexport) About (char *s);<br />
Лексема __declspec со спецификатором класса памяти dllexport гарантирует<br />
совместимость библиотеки с Microsoft C/C++. После компоновки проекта<br />
будет создан файл библиотеки (.LIB) и исполняемый файл (.DLL).<br />
Для статического подключения DLL к имеющемуся проекту <strong>на</strong>до добавить<br />
в него файл .LIB (Project.Add to Project) из проекта DLL и включить<br />
файл заголовка из DLL. После компоновки можно обращаться к хранящимся<br />
там данным.<br />
Пример использования библиотечных функций:
77<br />
//myApplication.cpp<br />
#include "myDLL.h"<br />
void __fastcall TForm1::Button1Click (TObject *Sender) { Message ("Привет "); }<br />
void __fastcall TForm1::Button2Click (TObject *Sender) { About ("Привет "); }<br />
Ди<strong>на</strong>мическое подключение выполняется и<strong>на</strong>че:<br />
void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />
HINSTANCE dllp = LoadLibrary ("myDLL.dll");<br />
if (dllp) {<br />
void (_ _stdcall *p) (char *s);<br />
p = (void (_ _stdcall *) (char*)) GetProcAddress (dllp,"_Message");<br />
if (p) p ("Привет ");<br />
else ShowMessage ("Нет функции DLL");<br />
FreeLibrary (dllp);<br />
}<br />
else ShowMessage ("Нет файла DLL");<br />
}<br />
Функция LoadLibrary возвращает тип HINSTANCE из библиотеки Windows<br />
API и загружает модуль DLL, либо NULL при его отсутствии. Функция<br />
GetProcAddress присваивает з<strong>на</strong>чение указателя функции DLL. Затем вызывается<br />
рабочая функция, а библиотека DLL выгружается из памяти функцией<br />
FreeLibrary.<br />
А<strong>на</strong>логичным образом экспортируются целые классы DLL.<br />
Пакеты BPL создаются <strong>на</strong> базе шабло<strong>на</strong> Package. При проектировании<br />
пакета используются файлы со следующими расширениями:<br />
.BPK – исходный файл параметров проекта в формате XML;<br />
.BPL – runtime-пакет, представляющий разновидность .DLL со специальными<br />
функциями C++Builder;<br />
.BPI – библиотека импорта пакета;<br />
.CPP – файл исходного текста с главной функцией проекта DllEntry-<br />
Point;<br />
.H – файл заголовка;<br />
.LIB – статическая библиотека (коллекция файлов OBJ) для подключения<br />
в режиме design time, если установлен параметр компиляции GI (Generate<br />
LIB file);<br />
.OBJ – двоичный образ модуля пакета.
78<br />
Для ди<strong>на</strong>мического подключения пакетов вызывающее приложение<br />
следует ос<strong>на</strong>щать функциями LoadPackage и FreePackage вместо LoadLibrary<br />
и FreeLibrary.<br />
Ресурсы<br />
Ресурсы – это битовые образы, хранящиеся непосредственно в исполнительном<br />
файле, а не отдельно от приложения. При создании приложения<br />
ресурсы помещаются в файл ресурсов проекта .RES автоматически или командой<br />
SaveToResource и могут загружаться из него командой LoadFromResource.<br />
Ресурсы создаются с помощью встроенного в C++Builder графического<br />
редактора Image Editor и им же включаются в файл ресурсов приложения как<br />
графические образы Bitmaps, указатели мыши Cursors и з<strong>на</strong>чки Icons. Этот<br />
же редактор используется для создания отдельных файлов типов .RES, .BMP,<br />
.ICO, .CUR, которые впоследствии могут быть подключены к другим проектам.<br />
Например, для добавления в текущий файл ресурсов нового з<strong>на</strong>чка <strong>на</strong>до<br />
в меню File.Open редактора Image Editor открыть файл ресурсов проекта<br />
(.RES). Далее через меню Resource.New.Icon создать новый з<strong>на</strong>чок и, выделив<br />
его, открыть окно редактора (Resource.Edit), <strong>на</strong>рисовать или импортировать<br />
з<strong>на</strong>чок и закрыть окно. Через меню Resource.Rename <strong>на</strong>звать з<strong>на</strong>чок,<br />
<strong>на</strong>пример, MYICON. и сохранить его.<br />
А<strong>на</strong>логичным образом через меню Resource.New.Cursor создать новый<br />
курсор, открыть окно редактора, <strong>на</strong>рисовать или импортировать курсор и закрыть<br />
окно. Через меню Resource.Rename <strong>на</strong>звать курсор, <strong>на</strong>пример,<br />
MYCURSOR. В заключение остается сохранить файл ресурсов и закрыть<br />
Image Editor.<br />
Для создания нового файла ресурсов с графическим образом дается команда<br />
New.Resource File в меню File. В окне ресурсов новый рисунок формируется<br />
по команде Resource.New.Bitmap редактором Resource.Edit или копируется<br />
через буфер обме<strong>на</strong> и сохраняется (File.Save) в одной папке с приложением.<br />
Пользуясь Image Editor, таким же путем создаются отдельные файлы<br />
з<strong>на</strong>чков и курсоры.
79<br />
Для подключения нового файла ресурсов к проекту вводится директива<br />
препроцессора #pragma resource "имяФайла.res" и дается команда загрузки,<br />
<strong>на</strong>пример:<br />
Image1–>LoadFromResourceName ((int)HInstance, “имяРесурса”)<br />
Примеры подключения новых курсоров и з<strong>на</strong>чков:<br />
const TCursor cur = 5;<br />
// свобод<strong>на</strong>я константа<br />
Screen–>Cursors[cur] = LoadCursor (HInstance, "MYCURSOR");<br />
Cursor = cur;<br />
// новый курсор<br />
Icon–>Handle = LoadIcon (HInstance, "MYICON"); // новый з<strong>на</strong>чок<br />
З<strong>на</strong>чки и курсоры из отдельных файлов подключаются методом<br />
LoadFromFile (имяФайла.ico) объекта Icon и функцией LoadCursorFromFile<br />
(имяФайла.cur) вместо LoadCursor.<br />
Для работы с обновленным файлом ресурсов следует закрыть приложение,<br />
затем вновь открыть и перестроить его.<br />
Потоки<br />
Потоки – это отдельно работающие части одной программы. Многие<br />
программы используют несколько потоков, среди которых можно выделить<br />
первичный поток, отвечающий за создание окон и обработку сообщений, и<br />
вторичные потоки, в которых выполняются фоновые операции: загрузка<br />
больших файлов, проведение вычислений и т. п.<br />
В <strong>С++</strong>Builder потоками управляет абстрактный класс TThread, и каждый<br />
вторичный поток представляет отдельный экземпляр <strong>на</strong>следника этого<br />
класса.<br />
Поток создается через меню File.New <strong>на</strong> базе шабло<strong>на</strong> TThread Object в<br />
виде модуля без формы и кроме обычного конструктора содержит заготовку<br />
исполнительного метода Execute. В отличие от конструктора формы, конструктор<br />
потока имеет параметр CreateSuspended. Когда он установлен (true),<br />
созданный поток сразу не запускается, а ожидает вызова стартового метода<br />
Resume. В противном случае сразу вслед за созданием происходит запуск<br />
потока.<br />
Наиболее распространенные характеристики класса TThread приведены<br />
в табл. 16.
80<br />
Свойства и методы объектов Thread<br />
Таблица 16<br />
Свойство, метод<br />
FreeOnTerminate<br />
Handle<br />
Priority<br />
ReturnValue<br />
Suspended<br />
Terminated<br />
ThreadID<br />
DoTerminate ()<br />
Execute ()<br />
Resume ()<br />
Suspend ()<br />
Synchronize ()<br />
Terminate ()<br />
WaitFor ()<br />
Наз<strong>на</strong>чение<br />
Автоматическое удаление потокового объекта по завершении работы<br />
потока<br />
Дескриптор потока для вызова функций API<br />
Приоритет потока<br />
Возвращаемое з<strong>на</strong>чение по завершении работы потока<br />
Приз<strong>на</strong>к приостановки работы потока<br />
Приз<strong>на</strong>к прекращения работы потока<br />
Идентификатор потока<br />
Вызов обработчика события OnTerminate без прекращения работы<br />
потока<br />
Код, выполняемый при запуске потока<br />
Возобновление работы приостановленного потока<br />
Приостановка работы потока<br />
Синхронизация обращения к библиотеке VCL в первичном потоке<br />
Прекращение работы потока<br />
Ожидание прекращения работы потока<br />
В метод Execute программист должен поместить код, который будет<br />
выполняться после загрузки потока в фоновом режиме по отношению к первичному<br />
потоку. Кроме того, в метод Execute обычно включают еще три выражения:<br />
if (Terminated) break;<br />
FreeOnTerminate = true;<br />
// для прекращения работы потока<br />
// для автоматического освобождения памяти<br />
// потоком после завершения его работы<br />
Synchronize (имяФункции); // для синхронизации доступа к объектам<br />
// библиотеки VCL, за исключением<br />
// графических объектов<br />
Функцию, имя которой выступает в качестве аргумента метода Synchronize,<br />
объявляют и определяют в этом же файле потока, <strong>на</strong>пример:<br />
void __fastcall TThread1::Display () { Form1–>Label1–>Caption = “Пример”; }<br />
Для работы с формой в файл кода потока включают заголовок файла<br />
формы. Если в методе Execute присутствует код работы с графикой, его обрамляют<br />
выражениями, блокирующими вывод графики из других потоков:<br />
Form1–>Image1–>Сanvas1–>Lock ();<br />
Вывод графики <strong>на</strong> канву<br />
Form1–>Image1–>Сanvas1–>Unlock ();
81<br />
Управление созданным потоком осуществляется из первичного потока,<br />
обычно – из формы. Для этого в файл кода формы включается заголовок<br />
файла потока. В класс формы добавляется ссылка <strong>на</strong> объект потока, <strong>на</strong>пример,<br />
TThread1 *thread, в конструкторе формы создается экземпляр потока,<br />
который потом используется по ходу программы:<br />
…<br />
TThread1 *thread;<br />
…<br />
thread = new TThread1 (true);<br />
…<br />
thread->Resume ();<br />
…<br />
thread–>Suspend ();<br />
…<br />
thread–>Terminate ();<br />
…<br />
// запуск потока<br />
// приостановка<br />
// завершение<br />
Дистрибутивы<br />
Установка приложений <strong>на</strong> компьютере пользователя обычно производится<br />
с помощью инсталляционного пакета – дистрибутива. Такие файловые<br />
пакеты готовятся с использованием программы InstallShield Express, которая<br />
создает проект – Setup Project – и в специальной среде разработки помогает<br />
программисту выполнить необходимые этапы подготовки дистрибутива.<br />
На этапе организации дистрибутива вводятся основные сведения (general<br />
information) об авторе, фирме, теме и <strong>на</strong>звании программного продукта,<br />
которые при установке будут занесены в реестр для последующего изменения<br />
или удаления приложения. Здесь же <strong>на</strong>з<strong>на</strong>чается папка для установки<br />
(INSTALLDIR) и приводится дополнитель<strong>на</strong>я информация. Разработчик определяет<br />
составные части (features) дистрибутива, его программные, графические,<br />
библиотечные, справочные файлы и разделяет их <strong>на</strong> две группы.<br />
Первая часть файлов уста<strong>на</strong>вливается в обязательном порядке (always install),<br />
тогда как вторая – по желанию пользователя (custom setup). В зависимости от<br />
<strong>на</strong>личия или отсутствия второй части <strong>на</strong>мечаются три способа установки<br />
продукта: Typical – автоматическое развертывание <strong>на</strong>иболее важных с точки<br />
зрения разработчика файлов, Minimal – автоматическое развертывание минимально<br />
допустимого <strong>на</strong>бора файлов, Custom – автоматическая установка
82<br />
обязательных файлов и произвольное развертывание файлов, выбираемых<br />
пользователем.<br />
На этапе определения состава приложения (specify application data)<br />
формируется файловый раздел дистрибутива. Здесь интегрирован<strong>на</strong>я среда<br />
InstallShield Express становится подобной Проводнику Windows, поддерживая<br />
традиционные механизмы работы с файлами: буфер обме<strong>на</strong> и перетаскивание<br />
(drag & drop). Пользуясь этим инструментом, разработчик копирует<br />
файлы из папок своего компьютера (source computer) в будущие папки машины<br />
пользователя (destination computer). Здесь же определяется потребность<br />
в дополнительных модулях и библиотеках (merge modules) и проверяется<br />
зависимость файлов приложения от программной среды (dependencies).<br />
На следующем этапе проектирования дистрибутива планируются действия<br />
по конфигурированию системы пользователя (target system). При этом<br />
задаются папки и ярлыки, которые потребуется создать; изменения, которые<br />
предстоит провести в меню, в реестре и в файлах инициализации (.ini); ресурсы,<br />
драйверы, базы и источники данных, которые необходимо сгенерировать.<br />
Задаются типы файлов (file extensions), которые развертываемое приложение<br />
будет обрабатывать особым образом.<br />
Далее формируется интерфейс пользователя дистрибутива. Разработчику<br />
предлагаются 12 стандартных окон диалога и баннеры для их оформления.<br />
В состав окон, которые можно использовать в ходе установки, входят:<br />
стартовое окно (Install Welcome), лицензионное соглашение (EULA – End-<br />
User License Agreement), комментарии к установке (Readme), сведения о<br />
пользователе (Customer Information), путь развертывания приложения (Destination<br />
Folder), выбор типа установки (Tipical/Minimal/Custom), стартовое<br />
предупреждение (Ready To Install), <strong>на</strong>блюдение за ходом установки (Setup<br />
Progress), завершение (Complete Success) и др.<br />
В ходе подготовки дистрибутива формируются, также, требования к<br />
аппаратному и программному обеспечению пользователя, производится создание<br />
дистрибутивных носителейи выполняется тестирование созданного<br />
продукта. Не покидая среды InstallShield Express, разработчик может провести<br />
имитацию установки приложения <strong>на</strong> своей машине, а также пробную установку<br />
программы.<br />
Проект сохраняется для последующего совершенствования или использования<br />
при создании других дистрибутивов.
83<br />
Контрольные вопросы<br />
1. Какие визуальные компоненты поддерживают файловые метолы<br />
2. Как <strong>на</strong>зывается и для чего пред<strong>на</strong>з<strong>на</strong>чено з<strong>на</strong>чение, возвращаемое функциями<br />
FileCreate и FileOpen<br />
3. Для чего пред<strong>на</strong>з<strong>на</strong>че<strong>на</strong> функция FileSeek и какие аргументы требуются<br />
для ее вызова<br />
4. Какие параметры имеет функция FileWrite<br />
5. Как программируется работа с графическими файлами<br />
6. В чем состоит принцип мультипликации<br />
7. Какие функции используются для открытия исполнительных файлов<br />
8. Как выполняется статическая компоновка<br />
9. Как выполняется ди<strong>на</strong>мическая компоновка<br />
10. В чем заключаются преимущества и недостатки пакетов перед библиотеками<br />
DLL<br />
11. Какие ресурсы можно использовать в приложениях<br />
12. В чем различие между файлом ресурсов и встроенными в приложение ресурсами<br />
13. Для чего используется многопоточное <strong>программирование</strong><br />
14. Для чего служит перемен<strong>на</strong>я FreeOnTerminate класса TThread<br />
15. Зачем нужен параметр в конструкторе класса TThread<br />
16. Что входит в состав дистрибутива<br />
17. Какие этапы сопровождают установку программного продукта<br />
Приложения баз данных<br />
У памяти хороший вкус<br />
Французское изречение<br />
База данных – это хранилище информации, организуемое и обрабатываемое<br />
в <strong>на</strong>копителях в соответствии с определенными правилами хранения<br />
и доступа. Логическая автономность данных является первым существенным<br />
отличием баз данных от прочего программного обеспечения. Строго<br />
оговорен<strong>на</strong>я при<strong>на</strong>длежность данных к определенным классам и их безуслов<strong>на</strong>я<br />
типизация отличает базу данных от текстовых и табличных процессоров,<br />
а широкая гамма допустимых операций <strong>на</strong>д множествами является важным<br />
преимуществом ее перед пакетами прикладных программ и системами
84<br />
программирования. Выделение базы данных как особой части программного<br />
обеспечения способствует эффективной структуризации информации,<br />
сведению к минимуму повторяющихся данных, ускорению обработки информации<br />
прямо <strong>на</strong> носителе, удобству обновления документов, обеспечению<br />
целостности данных, регулированию прав доступа к информации, облегчению<br />
автоматизации обработки данных и ведения отчетности.<br />
C++Builder позволяет создавать эффективные СУБД – системы управления<br />
базами данных (DBMS).<br />
Технологии создания приложений баз данных<br />
Для поддержки приложений баз данных разработаны различные технологии,<br />
приведенные в табл. 17.<br />
Таблица 17<br />
Технология<br />
ADO<br />
BDE<br />
dbExpress<br />
Технологии доступа к базам данных<br />
Область применения<br />
Все базы данных, поддерживающие стандарт OLE DB<br />
Реляционные базы данных xBase, Paradox, InterBase<br />
Различные базы данных<br />
Универсаль<strong>на</strong>я технология ADO – ActiveX Database Objects – разработа<strong>на</strong><br />
в «Microsoft» в соответствии со стандартом OLE DB (Object Linking and<br />
Embedding of DataBases). О<strong>на</strong> является развитием ODBC (Open DataBase<br />
Connectivity) – открытой двухуровневой системы подключения баз данных,<br />
ставшей стандартом для большинства приложений. Верхний уровень (клиент<br />
ODBC) играет роль интерфейса с приложением и поддерживает стандартные<br />
функции API. Нижний уровень выполняет функции специализированного<br />
драйвера преобразования информации в соответствии с форматом конкретной<br />
СУБД (сервера ODBC). Средства поддержки ADO строятся как модели<br />
составных объектов COM (Component Object Model). Технология ориентирова<strong>на</strong><br />
<strong>на</strong> Интернет (HTTP, InternetExpress, FastNet) и сети, работающие под<br />
Windows, при взаимодействии с любыми СУБД, ос<strong>на</strong>щенными драйверами<br />
ODBC. Кроме того, ADO предлагает разработчикам поддержку данных, хранящихся<br />
в не реляционном формате, <strong>на</strong>пример XML или сообщений электронной<br />
почты. Специальные версии ADO относятся к категории распределенных<br />
(многоуровневых) архитектур. В них программы разделяются <strong>на</strong> секции,<br />
взаимодействующие <strong>на</strong> основе разнообразных протоколов. Это CORBA
85<br />
для построения масштабируемых приложений, не зависящих от платформ;<br />
DCOM для реализации удаленного доступа; MIDAS для обслуживания приложений<br />
клиент/сервер.<br />
Базовая одноуровневая технология BDE – Borland Database Engine поддерживает<br />
форматы баз данных группы xBase, такие как dBase, FoxPro, Clipper,<br />
а также Paradox, текстовый формат с разделителями (ASCII-Delimited<br />
Text) и стандарт ODBC. Реализован<strong>на</strong>я в виде <strong>на</strong>бора системных файлов<br />
DLL, технология BDE удовлетворяет небольшим дешевым производительным<br />
приложениям. Для работы с корпоративными базами данных Oracle,<br />
SQL Server, DB2, InterBase между BDE и базой данных может быть помещен<br />
дополнительный уровень SQL Links – Structured Query Language Links. Архитектура<br />
BDE/SQL Links реализует более производительный сетевой обмен по<br />
технологии клиент/сервер. Выражения <strong>на</strong> языке SQL, непосредственно внедренные<br />
в программный код, позволяют манипулировать данными через<br />
BDE, обеспечивая <strong>на</strong>ивысшую производительность, хотя при этом игнорируются<br />
визуальные компоненты и связанные с ними преимущества быстрой<br />
разработки приложений.<br />
Специализированные технологии dbExpress предусматривают высокоскоростное<br />
подключение к каждому конкретному типу базы данных с учетом<br />
особенностей операционной среды. Компромиссным решением служит подключение<br />
к ODBC через специализированные компоненты, поставляемые<br />
сторонними производителями под API конкретных СУБД. Популярен и вариант<br />
API СУБД без использования технологии визуальной разработки. Он<br />
применяется в простых базах данных и поддерживается их разработчиками<br />
самостоятельно за счет средств доступа к данным самой СУБД.<br />
Обычно для построения приложений баз данных используется организация,<br />
состоящая из нескольких своеобразных программных «слоев»<br />
(рис. 2, а):<br />
• интерфейс пользователя (user interface);<br />
• компоненты связи интерфейса пользователя с <strong>на</strong>борами данных,<br />
именуемые источниками данных (data sources);<br />
• <strong>на</strong>боры данных (datasets), представляющие записи таблиц и запросов<br />
баз данных;<br />
• средства подключения к базам данных (data connections).
Программа<br />
86<br />
Программа<br />
Application server<br />
Интерфейс<br />
(User interface)<br />
Источник данных<br />
(Data source)<br />
Набор данных<br />
(Dataset)<br />
Подключение<br />
(Data connection)<br />
Данные<br />
Интерфейс<br />
пользователя<br />
Источник данных<br />
клиента<br />
Набор данных<br />
клиента<br />
Подключение<br />
к серверу<br />
Client<br />
Источник сервера<br />
(Provider)<br />
Целевой<br />
<strong>на</strong>бор данных<br />
(Unidirectional<br />
data source)<br />
SQL-соединение<br />
(SQL connection)<br />
Сервер базы данных<br />
(Database server)<br />
а. б.<br />
Рис. 2. Варианты организации приложений баз данных<br />
Интерфейс пользователя строится <strong>на</strong> базе традиционных форм. Источники<br />
данных связывают интерфейс пользователя с <strong>на</strong>борами данных. При<br />
этом несколько элементов управления интерфейса можно подключать к одному<br />
источнику данных для синхронной обработки одной и той же информации.<br />
Основу приложений составляют <strong>на</strong>боры данных, представляющие даталогические<br />
модели одной или нескольких таблиц или запросов и отражающие<br />
состояние и изменение физических данных носителя информации. Каждой<br />
таблице или запросу ставится в соответствие свой <strong>на</strong>бор данных.<br />
Архитектуры приложений баз данных<br />
Наборы данных различных типов используют разнообразные механизмы<br />
подключения к данным, которые, в свою очередь, определяют архитектуру<br />
приложений баз данных. К основным способам подключения относятся:<br />
• прямое подключение;<br />
• подключение через вспомогательный <strong>на</strong>бор данных;<br />
• архитектура «клиент-сервер».
87<br />
В C++Builder все подключения по технологии ADO производятся через<br />
компонент TADOConnection вкладки ADO панели визуальных компонентов,<br />
источниками данных служат визуальные компоненты TADOTable,<br />
TADOQuery, TADOStoredProc, TADODataSet, TADOCommand вкладки ADO,<br />
а <strong>на</strong>боры данных обычно представляются компонентом TDataSource вкладки<br />
Data Access.<br />
Подключения по технологии BDE выполняются через компонент<br />
TDataBase без <strong>на</strong>боров данных или с TDataSource, а роль источников данных<br />
здесь играют компоненты TTable, TQuery, TStoredProc вкладки BDE панели<br />
визуальных компонентов. Для работы с корпоративными базами данных<br />
InterBase служат компоненты TIBDataBase, TIBTable, TIBQuery, TIB-<br />
StoredProc вкладки InterBase.<br />
Технологии dbExpress для подключения используют компонент<br />
TSQLConnection, источниками данных являются TSQLTable, TSQLQuery,<br />
TSQLStoredProc вкладки dbExpress, а <strong>на</strong>боры данных представляет<br />
TSQLDataSet.<br />
Прямое подключение обычно используется для организации постоянных<br />
соединений, которые уста<strong>на</strong>вливаются в <strong>на</strong>чале сеанса связи с базой<br />
данных и разрываются в конце сеанса. Такое подключение просто реализуется,<br />
но работает только в однопользовательских системах без разграничения<br />
прав доступа. Технологиями BDE и dbExpress поддерживается подключение<br />
через вспомогательные <strong>на</strong>боры данных TBDEClientDataSet, TIBClientDataSet,<br />
TSQLClientDataSet.<br />
Архитектура «клиент-сервер» применяется в приложениях, где данные<br />
распределены по нескольким таблицам или нескольким корпоративным и<br />
локальным базам данных, в системах с несколькими клиентами, требующих<br />
организации совместного доступа и коллективной защиты. Схема организации<br />
таких приложений показа<strong>на</strong> <strong>на</strong> рис. 2,б. В отличие от предыдущих архитектур,<br />
для подключения здесь привлекается источник данных сервера – провайдер,<br />
к которому обращается клиент для получения или передачи данных.<br />
Приложение-сервер может предоставлять запрошенные данные или отказывать<br />
в сервисе в зависимости от принятой стратегии управления и ситуации в<br />
сети.
88<br />
Подключение к базам данных<br />
Хорошим стилем программирования считается отделение интерфейса<br />
пользователя от компонентов связи с базой данных. Этим достигается повышен<strong>на</strong>я<br />
гибкость проекта, позволяющая менять средства связи независимо от<br />
интерфейса или усовершенствовать интерфейс без изменения подключений.<br />
Инструментом такого разделения в C++Builder служат модули данных Data<br />
Module из библиотеки шаблонов Object Repository. Это специальный тип<br />
формы, применяемый в качестве хранилища невидимых компонентов, в первую<br />
очередь – компонентов баз данных. Как и формы, каждый модуль данных<br />
строится с использованием файла заголовков (.H), файла кода (.CPP) и<br />
файла структуры модуля (.DFM).<br />
Окно модуля данных удобно применять вместе с вкладкой Diagram и с<br />
окном Object TreeView, так как модуль данных отражает компоненты, а<br />
вкладка Diagram – их иерархическую структуру. При проектировании компоненты<br />
можно помещать прямо в модуль данных или <strong>на</strong> ветви Object Tree-<br />
View, в результате чего они становятся дочерними компонентами того объекта,<br />
<strong>на</strong> который помещаются. До завершения привязки объекты Object Tree-<br />
View помечаются вопросительными з<strong>на</strong>ками.<br />
В частности, для подключения базы данных через вспомогательный<br />
<strong>на</strong>бор данных по технологии ADO в модуль данных помещается объект<br />
ADOConnection с вкладки ADO. Его редактор свойства ConnectionString<br />
предлагает сформировать строку подключения (или использовать файл<br />
.UDL). Для формирования такой строки (под кнопкой Build) при подключении<br />
к персо<strong>на</strong>льным базам данных «Microsoft» можно выбрать провайдера<br />
(драйвера – поставщика информации) Microsoft Jet. На вкладке Connection<br />
этого же ок<strong>на</strong> в поле Select or enter a database name указывается подключаемая<br />
база данных. Кнопкой Test Connection проверяется подключение. В<br />
сформированной таким образом строке подключения указано имя провайдера,<br />
имя и пароль пользователя и папка с базой данных. В большинстве случаев<br />
свойство LoginPrompt объекта ADOConnection уста<strong>на</strong>вливается в false для<br />
открытия базы данных без пароля.<br />
Связь приложения с базой данных в архитектуре «клиент-сервер» выполняется<br />
с помощью источников данных, работающих через драйверы<br />
ODBC. С описанием таких источников в формате DSN (Data Source Notification)<br />
можно оз<strong>на</strong>комиться, если открыть Администратор источников данных
89<br />
ODBC в папке Панель управления Windows. Вкладки этой программы посвящены<br />
трем категориям источников:<br />
• пользовательский DSN единственного пользователя одного компьютера;<br />
• системный DSN всех пользователей компьютера;<br />
• файловый DSN, доступный всем пользователям сети, имеющих<br />
оди<strong>на</strong>ковые драйверы, независимо от компьютера.<br />
На отдельной вкладке представлены установленные <strong>на</strong> компьютере<br />
драйверы ODBC. Для работы с конкретной базой данных обычно подключают<br />
имеющийся или создают новый источник данных, используя соответствующий<br />
драйвер. Этот источник впоследствии распространяют в комплекте с<br />
приложением и базой данных.<br />
Поэтому для формирования строки подключения под кнопкой Build<br />
обычно выбирается Microsoft OLE DB Provider for ODBC Drivers, служащий<br />
универсальным механизмом доступа к продуктам «Microsoft». На вкладке<br />
Connection этого же ок<strong>на</strong> в поле переключателя Use connection string под<br />
кнопкой Build выбирается источник данных. Если требуемый источник данных<br />
отсутствует и его следует создать, то в поле DSN Name под кнопкой<br />
New указывается од<strong>на</strong> из строк:<br />
• Microsoft Access Driver, обслуживающая Microsoft Access;<br />
• Microsoft dBase Driver для работы с базами xBase;<br />
• Microsoft Excel Driver для работы с таблицами Microsoft Excel;<br />
• Microsoft Text Driver для подключения текстовых таблиц;<br />
• SQL Server для обращения к корпоративной базе данных<br />
и вводится произвольное имя нового источника. В следующем окне ODBC<br />
Microsoft Access Setup базу данных можно выбрать (под кнопкой Select) либо<br />
создать новую (под кнопкой Create), задав ее имя. В последнем случае<br />
должно поступить сообщение об успешном создании базы данных. Выделив<br />
<strong>на</strong>йденный или созданный источник и вернувшись <strong>на</strong> вкладку Connection,<br />
остается убедиться в его работоспособности кнопкой Test Connection.<br />
А вот пример программного подключения, выполняемого в конструкторе<br />
модуля данных:
_ _fastcall TDataModule1::TDataModule1 (TComponent* Owner)<br />
: TDataModule (Owner) {<br />
AnsiString s = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\<br />
bRobots.mdb;Persist Security Info=False";<br />
try {<br />
ADOConnection1–>Connected = false;<br />
ADOConnection1–>ConnectionString = s;<br />
ADOConnection1–>Connected = true;<br />
} catch (...) { ShowMessage ("Нет подключения к bRobots.mdb"); }<br />
}<br />
Фиксированную строку подключения можно заменить окном диалога:<br />
AnsiString s = PromptDataSource (0, "");<br />
Доступ к записям таблиц<br />
90<br />
Доступ к записям реализуется через таблицы, запросы и хранимые<br />
процедуры баз данных.<br />
Для работы с таблицами с помощью компонента ADOTable вкладки<br />
ADO в модуль данных добавляются элементы управления доступом к каждой<br />
таблице. Их свойство Connection инициализируется именем объекта<br />
ADOConnection, а в свойство TableName вписываются име<strong>на</strong> соответствующих<br />
таблиц. Каждый элемент управления ADOTable аккумулирует все записи<br />
и поля своей таблицы.<br />
Для подключения к форме с помощью компонента DataSource вкладки<br />
Data Access панели компонентов в модуль данных добавляется источник<br />
данных для каждой таблицы. В их свойстве DataSet указываются име<strong>на</strong> соответствующих<br />
компонентов ADOTable. Свойство AutoEdit разрешает режим<br />
редактирования записей, вводимых в поля <strong>на</strong>боров данных.<br />
После подключения в модуле данных уста<strong>на</strong>вливается в true свойство<br />
Active элементов доступа к таблицам для их открытия. А<strong>на</strong>логично выполняется<br />
открытие таблиц из программы, <strong>на</strong>пример:<br />
ADOTable1–>TableName = "Countries";<br />
ADOTable1–>Active = true;<br />
Следующий пример демонстрирует подключение к файлу MYDBF.DSN,<br />
соединяющему с базой данных формата FoxPro, dBase или Clipper с открытием<br />
таблицы T.
void _ _fastcall TDBForm1::ConnectClick (TObject *Sender) {<br />
AnsiString s = "Extended Properties=\"FILEDSN=myDBF.dsn;\";";<br />
TADOConnection *cnn = DataModule1–>ADOConnectionDBF;<br />
TADOTable *rst = DataModule1–>ADOTableDBF;<br />
try {<br />
cnn–>Connected = false;<br />
cnn–>ConnectionString = s;<br />
cnn–>Connected = true;<br />
rst–>TableName = "T";<br />
rst–>Active = true;<br />
} catch (...) {<br />
s = PromptDataSource(0, "");<br />
try {<br />
cnn–>Connected = false;<br />
cnn–>ConnectionString = s;<br />
cnn–>Connected = true;<br />
rst–>TableName = "T";<br />
rst–>Active = true;<br />
} catch (...) { ShowMessage (s); }<br />
}<br />
}<br />
91<br />
Возможно подключение и при отсутствии файла .DSN, в том числе – и<br />
без использования объектов ADOConnection, если известны все необходимые<br />
параметры командной строки. Еще один пример посвящен открытию доступа<br />
к таблице dBase и к листу Excel:<br />
void __fastcall TDBForm1::DBFConnectClick (TObject *Sender) {<br />
AnsiString s = "Provider=MSDASQL.1;\<br />
Extended Properties=\"DBQ=D:\\DATABASES;\<br />
Driver={Microsoft dBase Driver (*.dbf)};DriverId=533;FIL=dBase 5.0;\";";<br />
TADOConnection *cnn = DataModule1–>ADOConnectionDBF;<br />
TADOTable *rst = DataModule1–>ADOTableDBF;<br />
try {<br />
cnn–>Connected = false;<br />
cnn–>ConnectionString = s;<br />
cnn–>Connected = true;<br />
rst–>TableName = "Т";<br />
rst–>Active = true;<br />
} catch (...) { ShowMessage (“Увы…”); }<br />
}<br />
void __fastcall TDBForm1::XLSConnectClick (TObject *Sender) {<br />
AnsiString s = "Provider=MSDASQL.1;\
Extended Properties=\"DBQ=D:\\DATABASES\\Сотрудники.xls;\<br />
Driver={Microsoft Excel Driver (*.xls)};DriverId=790;FIL=excel 8.0;\";";<br />
TADOTable *rst = DataModule1–>ADOTableXLS;<br />
try {<br />
rst–>Active = false;<br />
rst–>ConnectionString = s;<br />
rst–>TableName = "Лист1";<br />
rst–>Active = true;<br />
DBGrid1–>DataSource = DataModule1–>DataSourceXLS;<br />
} catch (...) { ShowMessage (“Не подключились”); }<br />
}<br />
Работа с запросами<br />
92<br />
К подключенной базе данных можно обращаться с запросами <strong>на</strong> выборку,<br />
создание, обновление и удаление таблиц. Для этого <strong>на</strong> вкладке ADO<br />
присутствует компонент ADOQuery, создающий объекты-запросы. В свойстве<br />
Connection этого компонента указывается имя объекта ADOConnection, а в<br />
редакторе свойства SQL формируется требуемое SQL–выражение. Примеры<br />
запросов <strong>на</strong> выборку:<br />
select * from Robots where radius > 10<br />
select model, country, mass from Robots<br />
Через запросы <strong>на</strong> выборку при необходимости формируются вычисляемые<br />
(расчетные) поля. В следующих примерах с помощью расчетов создаются<br />
поля inertia и це<strong>на</strong>ВВалюте:<br />
select model, country, mass*radius*radius as inertia from Robots<br />
select модель, це<strong>на</strong>, це<strong>на</strong>/30 as це<strong>на</strong>ВВалюте from Товары<br />
Запросы <strong>на</strong> выборку удобны, также, для <strong>на</strong>хождения итоговых результатов:<br />
минимума, максимума, среднего по полю:<br />
select min (це<strong>на</strong>) as минимум, max (це<strong>на</strong>) as максимум, avg (це<strong>на</strong>) as<br />
среднее from Товары<br />
В свойстве SQL одного компонента сохраняется только один запрос.<br />
Поэтому ввод каждой строки запроса <strong>на</strong>до завершать установкой свойства<br />
Active в true для его проверки и выполнения. При этом C++Builder передает<br />
запрос серверу, который выполняет его и возвращает приложению результирующий<br />
<strong>на</strong>бор (result set). С этой же целью удобно использовать метод Requery,<br />
обновляющий элемент управления.
93<br />
Для подключения запросов <strong>на</strong> выборку к форме, в модуль данных добавляются<br />
источники данных DataSource.<br />
Запросы действия, не возвращающие <strong>на</strong>боров данных, можно формировать<br />
как с помощью компонента TADOQuery, так и с использованием TA-<br />
DOCommand. В первом случае после выполнения запроса в режиме проектирования<br />
или программным путем вводится следующее выражение SQL и<br />
вновь повторяется активизация элемента управления. В свою очередь, компонент<br />
TADOCommand, как правило, используется только для программной<br />
реализации запросов. Текстом запроса в нем инициализируется свойство<br />
CommandText, а для выполнения запроса запускается метод Execute.<br />
Примеры запросов <strong>на</strong> создание таблицы Robots с ключевым полем<br />
model и таблицы Countries с ключевым полем country:<br />
create table Robots (model string (10), country string (10),<br />
coord string (15), radius float, mass float, primary key (model))<br />
create table Countries (country string (10), primary key (country))<br />
Следующие примеры посвящены вставке в таблицу Robots новой записи,<br />
замене <strong>на</strong>звания страны и удалению всех записей, удовлетворяющих определенному<br />
условию:<br />
insert into Robots values ('Тест', 'Россия', 'Сферическая', 3, 5 )<br />
update Robots set country='Франция' where country='Россия'<br />
delete from Robots where country='Франция'<br />
В последнем примере приведен фрагмент программы заполнения случайными<br />
числами двух полей таблицы T, содержащей три записи:<br />
TADOCommand *ac = ADOCommand1;<br />
for (int i=0; iCommandText = “iisert into T(field1, field2) values (” +<br />
FloatToStr (random (100)) + “,” + FloatToStr (random (100)) + “)”;<br />
ac–>Execute ();<br />
}<br />
Связывание таблиц и запросов<br />
Формирование базы данных при необходимости продолжают <strong>на</strong> вкладке<br />
Diagram модуля данных, где из элементов управления ADOTable и ADO-<br />
Query собирается структур<strong>на</strong>я схема базы данных. На вкладке Diagram между<br />
ними можно просматривать и уста<strong>на</strong>вливать связи четырех типов:
94<br />
• Property от дочернего элемента управления к родителю (сплош<strong>на</strong>я<br />
линия со стрелкой), <strong>на</strong>пример, от полей к таблице;<br />
• Master/Detail от главной таблицы (master) к подчиненной (detail) (линия,<br />
<strong>на</strong> которой глав<strong>на</strong>я таблица помече<strong>на</strong> большим прямоугольником,<br />
а подчинен<strong>на</strong>я – маленьким прямоугольником и указано поле<br />
связи);<br />
• типа Lookup (линия с изображением глаза);<br />
• ссылка Comment Allude (стрелка).<br />
Пользуясь контекстными меню, эти связи удобно удалять и редактировать.<br />
При этом тип Lookup отображает поля подстановки данных из других<br />
таблиц, а Comment Allude подключает комментарии Comment Block к компонентам<br />
структуры, облегчая тем самым оформление схемы базы данных перед<br />
выводом ее в печать командой Print контекстного меню.<br />
Чтобы связать между собой две таблицы, следует при <strong>на</strong>жатой кнопке<br />
Master/Detail провести линию от главной таблицы к подчиненной, и в открывшемся<br />
окне указать име<strong>на</strong> зависимых полей и <strong>на</strong>жать кнопку Add. В результате<br />
автоматически заполнится свойство MasterFields подчиненной таблицы<br />
именем соответствующего поля главной таблицы MasterSource. Эти же<br />
данные можно занести вручную через редакторы <strong>на</strong>званных свойств.<br />
Чтобы создать в неактивной таблице новое поле подстановки, следует в<br />
контекстном редакторе ее ветви Fields ок<strong>на</strong> Object TreeView выбрать New<br />
Field и, установив переключатель Lookup, задать имя, тип и размер поля,<br />
ключи (Key), источник (Dataset) и поле подстановки (Result Field).<br />
Подготовленный модуль данных сохраняется, и в редакторе форм подключается<br />
к модулю формы (File.Include Unit Hdr). В форму помещаются элементы<br />
управления данными с вкладки Data Controls панели компонентов.<br />
Это таблицы DBGrid и <strong>на</strong>вигаторы DBNavigator, а также поля таблиц DBText,<br />
DBEdit, DBMemo, DBImage и другие. Компоненты вкладки Data Controls отличаются<br />
от рассмотренных ранее компонентов вкладки Standard <strong>на</strong>личием<br />
свойств DataSource и DataField, обеспечивающих их совместную работу с<br />
источниками данных. В свойстве DataSource указываются соответствующие<br />
объекты модуля данных, а в DataField – поля таблиц и запросов. Здесь также<br />
возмож<strong>на</strong> подстановка (свойство столбца PickList), но не из таблицы, а из<br />
фиксированного списка з<strong>на</strong>чений.
95<br />
Контрольные вопросы<br />
1. В чем преимущества баз данных перед авторскими файлами данных<br />
2. Каковы основные характеристики реляционных СУБД<br />
3. Каковы достоинства и недостатки технологии ADO<br />
4. Каковы достоинства и недостатки технологии BDE<br />
5. Каковы достоинства и недостатки технологии DBExpress<br />
6. Какие звенья служат для подключения приложения к базам данных<br />
7. Что <strong>на</strong>зывается провайдером (поставщиком) и источником данных, через<br />
которых осуществляется подключение<br />
8. Через какие визуальные компоненты выполняется редактирование данных<br />
в подключенных таблицах<br />
9. Какие функции выполняют запросы действия<br />
10. В чем разница между компонентами TADOQuery и TADOCommand<br />
11. Для чего и как производится связывание таблиц<br />
12. Чем различаются поля PickUp и Loolup<br />
<strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong><br />
Что посеешь, то и пожнешь<br />
Русская пословица<br />
Концепция объектно-ориентированного программирования предполагает<br />
конструирование программ из объектов – представителей классов. В качестве<br />
классов выступают библиотечные и пользовательские типы даных с<br />
характерными для них свойствами и методами поведения. Классификация<br />
способствует экономии памяти и упрощает описание и отображение объектов,<br />
облегчает их систематизацию и стандартизацию. Формирование программ<br />
из объектов является основным инструментом современного программирования.<br />
Главными механизмами объектно-ориентированного подхода<br />
служат инкапсуляция, полиморфизм и <strong>на</strong>следование.<br />
Инкапсуляция<br />
Создание авторских классов <strong>на</strong>чи<strong>на</strong>ется с их объявления в файле заголовка<br />
модуля. После объявления или одновременно с ним класс определяется<br />
(описывается). Встроенное объявление и определение членов выполняется в<br />
формате обычных переменных и функций:
96<br />
class имяКласса {<br />
[Метка]<br />
[объявлениеЧленов…]<br />
[встроенноеОпределениеЧленов…] };<br />
Если не дается встроенное определение, в файле кода модуля приводится<br />
внешнее определение членов:<br />
Тип имяКласса :: имяЧлен-данного [ =З<strong>на</strong>чение];<br />
Тип имяКласса :: имяЧлен-функции телоЧлен-функции<br />
Использование внешних определений повышает <strong>на</strong>глядность текста программы<br />
и облегчает его последующую модификацию. Члены класса могут<br />
при<strong>на</strong>длежать к другим классам, и их именуют в этом случае вложенными<br />
классами. Глуби<strong>на</strong> вложенности не ограничивается, хотя «большое число<br />
уровней абстракции так же плохо, как и их отсутствие» [5].<br />
После объявления и определения создаются объекты класса. Каждый<br />
класс может использоваться для порождения множества объектов:<br />
имяКласса имяОбъекта;<br />
имяКласса *имяОбъекта = new имяКласса;<br />
Инициализация и обращение к чле<strong>на</strong>м класса выполняются по разнообразным<br />
схемам. Обращение к чле<strong>на</strong>м своего класса производится по име<strong>на</strong>м,<br />
без указания имени объекта:<br />
имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />
При этом адресация выполняется с помощью неявного аргумента this.<br />
Так как this – адрес, то конструкция this–>имяЧле<strong>на</strong> является указателем <strong>на</strong><br />
объект, а *this представляет сам объект:<br />
this–>имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />
(*this).имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />
При обращении к чле<strong>на</strong>м других классов следует указывать имя объекта:<br />
имяОбъекта.имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />
ссылкаНаОбъект–>имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />
Пусть, <strong>на</strong>пример, требуется периодически выводить информацию о<br />
компьютерах. Чтобы не делать это в каждой функции некоторой программы,<br />
в проекте удобно создать новый модуль (File.New .Unit), и в его файле заголовка<br />
объявить класс VPC, получающий сведения через функцию Set и представляющий<br />
их функцией Out, как это показано в следующем примере:
class VPC {<br />
private:<br />
int type;<br />
AnsiString firm;<br />
public:<br />
double price;<br />
void Set (int a, AnsiString b) { type = a; firm = b; }<br />
void Out () {<br />
ShowMessage ("Пpоцессор фирмы " + firm + " стоит $" + price);<br />
}<br />
};<br />
97<br />
Для создания объектов класса в других формах, в их файлах кода размещаются<br />
ссылки <strong>на</strong> файл заголовка класса VPC. Ниже приведен пример<br />
функции MainVPC, «воспользовавшейся услугами» класса VPC для обработки<br />
объекта myCPU, и таких функций может быть множество:<br />
void MainVPC () {<br />
AnsiString company = "Acer";<br />
VPC myCPU;<br />
myCPU.Set (6, company); myCPU.price = 100; myCPU.Out ();<br />
}<br />
Другой пример посвящен классу VIntegral, реализующему численное<br />
интегрирование тремя методами: методом прямоугольников, методом трапеций<br />
и методом парабол. Класс объявлен в файле заголовка модуля, его членфункции<br />
Rect, Trap, Para определены в файле кода модуля, а объекты созданы<br />
функцией MainVIntegral в файле кода формы:<br />
class VIntegral {<br />
double j, x, sum, sum1, f;<br />
public:<br />
void Rect (double, double, double, double (*fnc) (double, double));<br />
void Trap (double, double, double, double (*fnc) (double, double));<br />
void Para (double, double, double, double (*fnc) (double, double));<br />
};<br />
void VIntegral :: Rect (double xk, double h, double f,<br />
double (*fnc) (double x, double y))<br />
{ for (x=0, sum=0; x
void VIntegral :: Trap (double xk, double h, double f,<br />
double (*fnc) (double x, double y))<br />
{ for (x=h, sum=0; x
99<br />
AnsiString result;<br />
VDeque *cursor = head;<br />
while (cursor != this) {<br />
result += AnsiString (cursor–>info) +'\t';<br />
cursor = cursor–>next;<br />
}<br />
ShowMessage (AnsiString (cursor–>info));<br />
}<br />
void MainVDeque () {<br />
VDeque a, b, c, d;<br />
a.Add ("IBM"); a.AddNode ();<br />
b.Add ("Siemens"); b.AddNode ();<br />
c.Add ("Motorola"); c.AddNode ();<br />
d.Add ("Apple"); d.AddNode ();<br />
b.Out ();<br />
}<br />
Конструкторы и деструкторы<br />
Для автоматической инициализации объектов класса используются<br />
конструкторы – бестиповые, одноименные с классом член-функции:<br />
class VMicroPC {<br />
AnsiString type;<br />
int price;<br />
public:<br />
VMicroPC (AnsiString a, int b) { type = a; price = b; } // конструктор<br />
void Out () { ShowMessage ("Компьютер " + type + " стоит $" + price); }<br />
};<br />
void MainVMicroPC() {<br />
AnsiString comp1 = "IBM", comp2 = "Macintosh";<br />
VMicroPC myCPU (comp1, 990), yourCPU (comp2, 1500);<br />
myCPU.Out();<br />
yourCPU.Out();<br />
}<br />
Конструктор вызывается не только при определении нового объекта<br />
класса для объявления данных, но и при копировании объекта или при ди<strong>на</strong>мическом<br />
выделении памяти новому объекту. В качестве аргументов копирующего<br />
конструктора (copying constructor) используются ссылки <strong>на</strong> копируемые<br />
объекты. Примером может служить конструктор типизированного<br />
класса AnsiString:
100<br />
struct computer { AnsiString name; int price; };<br />
class VList {<br />
computer *p;<br />
int i, max, current;<br />
public:<br />
VList (int);<br />
void Add (computer *);<br />
void Delete (computer *);<br />
AnsiString Out ();<br />
};<br />
VList::VList (int max) { VList::max = max; current = 0; p = new computer[max]; }<br />
void VList::Add (computer *v) {<br />
if (current < max) {<br />
p[current].name = v–>name;<br />
p[current++].price = v–>price;<br />
}<br />
}<br />
void VList::Delete (computer *v) {<br />
for (i=0; iname) {<br />
while (i < current–1) {<br />
p[i].name = p[i+1].name;<br />
p[i].price = p[i+1].price;<br />
i++;<br />
}<br />
––current;<br />
}<br />
}<br />
}<br />
AnsiString VList::Out () {<br />
AnsiString result;<br />
for (i=0; i
}<br />
101<br />
VList a (3);<br />
// конструктор класса VList<br />
a.Add (&x); a.Add (&y); a.Add (&z);<br />
ShowMessage ("Поступили:\nЭВМ–Це<strong>на</strong>\n\n" + a.Out ());<br />
a.Delete (&y);<br />
ShowMessage ("Остались:\nЭВМ–Це<strong>на</strong>\n\n" + a.Out ());<br />
Инициализация объектов класса может проводиться не только с помощью<br />
операторов в теле конструктора, но и посредством списка инициализации<br />
(initialisation list). Такой список помещают через двоеточие вслед за заголовком<br />
конструктора.<br />
Конструктор, не требующий аргументов, <strong>на</strong>зывается конструктором<br />
по умолчанию (default constructor), или параметризованным конструктором.<br />
Это может быть конструктор с пустым списком параметров или конструктор,<br />
в котором части объявления параметров, завершающей список, присвоены<br />
з<strong>на</strong>чения.<br />
Следующий пример сложения матрицы с числом демонстрирует инициализацию<br />
списком и работу конструктора по умолчанию:<br />
class VMatrix {<br />
int i, j;<br />
AnsiString temp;<br />
float m[3][3], k;<br />
public:<br />
VMatrix (float a[][3], float c = 1);<br />
void Out ();<br />
};<br />
VMatrix::VMatrix (float a[][3], float c): k(c) {<br />
for (i=0; i
}<br />
102<br />
VMatrix second (M, 5); // инициализируем з<strong>на</strong>чением 5<br />
second.Out (); // 6,7,8, 9,10,11, 12,13,14<br />
Процесс освобождения памяти после уничтожения объектов можно сопровождать<br />
деструктором (destructor) – бестиповым открытым методом без<br />
параметров, имя которого совпадает с именем класса и имеет префикс ~<br />
(тильда). Деструктор всегда вызывается автоматически, то есть неявно, при<br />
выходе из блока, в котором был объявлен объект. Он также включается при<br />
вызове оператора delete для указателя <strong>на</strong> объект, имеющий деструктор, или в<br />
том случае, когда необходимо удалить объект, вложенный в удаляемый. В<br />
любом случае работа деструктора сопровождает процесс освобождения памяти,<br />
выделенной конструктором. Каждый класс имеет только один деструктор,<br />
причем всегда открытый и без параметров. За работой деструктора можно<br />
по<strong>на</strong>блюдать в следующем примере:<br />
class VPair {<br />
int first, second;<br />
public:<br />
VPair (int one, int two): first (one), second (two)<br />
{ ShowMessage ("Объект создан"); }<br />
~VPair () { ShowMessage ("Объект удален"); }<br />
void Out () { ShowMessage (AnsiString (first) + '\n' + AnsiString (second)); }<br />
};<br />
void MainVPair () {<br />
{ VPair num (2,3); // объект создан<br />
num.Out (); // 2-3<br />
} // объект удален<br />
{ VPair num (4,5); // объект создан<br />
num.Out (); // 4-5<br />
} // объект удален<br />
}<br />
За освобождением памяти можно <strong>на</strong>блюдать и без деструктора. В приведенной<br />
ниже реализации объектов str, поочередно помещаемых в стек и<br />
очередь, деструктора нет:<br />
class VStackAndQueue {<br />
char *s;<br />
int i, size, top;<br />
AnsiString result;<br />
public:
};<br />
VStackAndQueue (AnsiString str);<br />
void Push (AnsiString str);<br />
void PopStack ();<br />
void PopQueue ();<br />
int Empty () { return top == –1; }<br />
int Full () { return top == size; }<br />
103<br />
VStackAndQueue::VStackAndQueue (AnsiString str) {<br />
size = str.Length (); s = new char[size]; top = –1; result = str + ":\n";<br />
}<br />
void VStackAndQueue::Push (AnsiString str) {<br />
for (i = 1; i
104<br />
friend void Show (VFriendPC x);<br />
};<br />
void Show (VFriendPC x) { ShowMessage (AnsiString ( x.num )); }<br />
void MainVFriendPC () { VFriendPC price (900); Show (price); }<br />
В приведенном примере Show объявле<strong>на</strong> функцией, дружественной<br />
классу VFriendPC, поэтому о<strong>на</strong> свободно обрабатывает закрытую переменную<br />
num этого класса.<br />
Особенно полезными аспектами механизма дружественных функций<br />
является то, что функция может быть дружественной по отношению к нескольким<br />
классам, член-функция одного класса может быть дружественной<br />
другому классу и, <strong>на</strong>против, сразу группа функций или целый класс могут<br />
быть дружественными рассматриваемому классу:<br />
class VTwo;<br />
class VOne {<br />
friend void Out (VOne &c1, VTwo &c2);<br />
private:<br />
char *s1;<br />
public:<br />
VOne () { s1 = "Проверка: ";}<br />
};<br />
class VTwo {<br />
friend void Out (VOne &c1, VTwo &c2);<br />
private:<br />
char *s2;<br />
public:<br />
VTwo () { s2 = "Sony, Siemens, Fuji"; }<br />
};<br />
void Out (VOne &c1, VTwo &c2) {<br />
ShowMessage (AnsiString ( c1.s1) + AnsiString (c2.s2));<br />
}<br />
void MainVOneVTwo () {<br />
VOne c1; VTwo c2; Out (c1, c2); // Проверка: Sony, Siemens, Fuji<br />
}<br />
Если все функции одного класса дружественны второму классу, то говорят<br />
о дружественном классе (friend class):<br />
class VFirst;<br />
class VSecond {<br />
AnsiString model, firm;
};<br />
105<br />
public:<br />
VSecond (AnsiString a, AnsiString b): model (a), firm (b) {}<br />
friend class VFirst;<br />
class VFirst {<br />
VSecond set;<br />
public:<br />
VFirst (AnsiString a, AnsiString b): set (a,b) {}<br />
void Model () { ShowMessage (AnsiString (set.VSecond::model)); }<br />
void Firm () { ShowMessage (AnsiString (set.VSecond::firm)); }<br />
};<br />
void MainVFirstVSecond () {<br />
AnsiString mod = "ATT", frm = "IBM";<br />
VFirst device (mod, frm);<br />
device.Model ();<br />
// AT<br />
device.Firm ();<br />
// IBM<br />
}<br />
Используя дружественные функции, следует помнить, что «подобно<br />
оператору goto, друзья снижают <strong>на</strong>дежность программного кода... Потребность<br />
в объявлении одних классов друзьями других свидетельствует о плохо<br />
продуманной иерархии классов» [9]. Поэтому обращаться к помощи друзей<br />
следует лишь при крайней необходимости.<br />
Полиморфизм<br />
Полиморфизм как способность оди<strong>на</strong>ковых операторов и одноименных<br />
функций к избирательному (зависящему от типа) поведению реализуется в<br />
C++ в первую очередь через механизмы перегрузки (overloading) операторов<br />
и функций.<br />
Суть перегрузки операторов заключается в создании функцииоператора,<br />
использующей в качестве имени лексему operator и следующий за<br />
ней перегружаемый оператор. Объявление перегрузки а<strong>на</strong>логично объявлению<br />
обычной дружественной функции или член-функции класса:<br />
Тип operator з<strong>на</strong>кОперации ( [объявлениеПараметров] );<br />
Перегрузке могут подлежать все простые операторы, за исключением<br />
«.», «.*», «:», «sizeof», «::». Язык не позволяет задать для перегружаемого<br />
оператора иной символ, кроме тех, что определены в списке операторов.<br />
Нельзя изменять синтаксис использования оператора: у<strong>на</strong>рный оператор не
106<br />
перегружается в би<strong>на</strong>рный, префиксный – в постфиксный и т.п. Операторы<br />
«+», «–», «*», «&» могут быть как би<strong>на</strong>рными, так и у<strong>на</strong>рными.<br />
Перегружаются только опеpатоpы в операциях, для которых по крайней<br />
мере один из параметров является членом структуры или класса. С учетом<br />
неявного указателя this, при выполнении одноместных операций аргумент<br />
может опускаться, а в двухместных операциях допустимо оставлять<br />
один аргумент.<br />
Для з<strong>на</strong>комства с перегрузкой одноместных операторов рассмотрим<br />
одну из распространенных задач программирования – вывод текущего времени<br />
<strong>на</strong> экран. На основании показаний системных часов компьютера такая<br />
программа формирует <strong>на</strong> экране время в днях, часах и минутах. Чтобы в каждом<br />
такте часов не пересчитывать время заново, здесь переопределен оператор<br />
инкремента, что позволяет использовать не абсолютный, а относительный<br />
отсчет времени:<br />
class VClock {<br />
private:<br />
__int64 total, secs, mins, hours, days;<br />
public:<br />
VClock (__int64 i);<br />
void Out ();<br />
VClock operator++ ();<br />
};<br />
VClock::VClock (__int64 i) {<br />
total = i; secs = total % 60; mins = (total / 60) % 60;<br />
hours = (total / 3600) % 24; days = total / 86400L;<br />
}<br />
void VClock::Out () {<br />
ShowMessage (AnsiString (total) + " сек.= "<br />
+ AnsiString (days) + " дн. " + AnsiString (hours) + " час. "<br />
+ AnsiString (mins) + " мин. " + AnsiString (secs) + " сек. ");<br />
}<br />
VClock VClock::operator++ () {<br />
VClock temp (++total);<br />
secs = temp.secs; mins = temp.mins; hours = temp.hours;<br />
days = temp.days; return *this;<br />
}<br />
void MainVClock () {<br />
VClock t1 (59), t2 (172799L);
}<br />
ShowMessage ("Старое время:");<br />
t1.Out ();<br />
t2.Out ();<br />
++t1; ++t2;<br />
ShowMessage ("Новое время:");<br />
t1.Out ();<br />
t2.Out ();<br />
107<br />
// 0 дн. 0 час. 0 мин. 59 сек.<br />
// 1 дн. 23 час. 59 мин. 59 сек.<br />
// 0 дн. 0 час. 1 мин. 0 сек.<br />
// 2 дн. 0 час. 0 мин. 0 сек.<br />
Следующий пример посвящен операции транспонирования матрицы<br />
M[3][3], представленной элементом управления StringGrid1, для выполнения<br />
которой перегружается оператор «!»:<br />
class VMatr {<br />
int i, j;<br />
double temp;<br />
double M[3][3];<br />
public:<br />
VMatr ();<br />
void Out ();<br />
VMatr operator ! ();<br />
};<br />
VMatr::VMatr () {<br />
for (i=0; iCells [i][j] );<br />
}<br />
void VMatr::Out () {<br />
for (i=0; iCells[i][j] = M[i][j];<br />
}<br />
VMatr VMatr::operator! () {<br />
for (i=0; i
};<br />
108<br />
public:<br />
VComputers (AnsiString a, int b) { type = a; price = b; }<br />
VComputers (const VComputers &par) {<br />
type = "Macintosh"; price = par.price * 2;<br />
}<br />
void Out () { ShowMessage (type + " стоит " + AnsiString (price)); }<br />
void MainVComputers() {<br />
VComputers myCPU ("Intel Pentium", 990);<br />
VComputers yourCPU (myCPU);<br />
myCPU.Out ();<br />
yourCPU.Out ();<br />
}<br />
Здесь объект myCPU инициализируется через конструктор VComputers<br />
(AnsiString, int), а объект yourCPU – через конструктор VComputers (const<br />
VComputers &). Оба выводятся <strong>на</strong> экран функцией Out.<br />
В следующем примере производится перегрузка конструктора для сложения<br />
транспонированнной матрицы с числом:<br />
class VMatrixPlus {<br />
int i, j; double k, m[3][3];<br />
public:<br />
double n[3][3];<br />
VMatrixPlus (double a[][3]);<br />
VMatrixPlus (double c): k (c) {}<br />
VMatrixPlus operator ! ();<br />
VMatrixPlus operator + (VMatrixPlus c);<br />
void Out ();<br />
};<br />
VMatrixPlus::VMatrixPlus (double a[][3]) {<br />
for (i=0; i
}<br />
109<br />
AnsiString temp;<br />
for (i=0; i
110<br />
}<br />
}<br />
else {<br />
if (m[j].heat < m[j–1].heat) {<br />
Swap (m[j–1].name, m[j].name); Swap (m[j–1].heat, m[j].heat);<br />
}<br />
}<br />
ShowMessage (m[0].name + AnsiString (m[0].heat) + '\n'<br />
+ m[1].name + AnsiString (m[1].heat) + '\n' + m[2].name<br />
+ AnsiString (m[2].heat));<br />
}<br />
Од<strong>на</strong>жды созданный шаблон можно сохранить в файле заголовка и вызывать<br />
его оттуда по имени:<br />
template void Sort (T array[], int size) {<br />
for (int i=0; i (const VPrice);<br />
friend AnsiString &operator
111<br />
};<br />
int VPrice::operator > (const VPrice amount) {<br />
return (dollars > amount.dollars) ||<br />
(dollars == amount.dollars && cents > amount.cents);<br />
}<br />
AnsiString &operator
const MAX = 10;<br />
112<br />
template class VStack {<br />
int num;<br />
T *items;<br />
public:<br />
VStack (int size = MAX) { num = 0, items = new T[size]; }<br />
~VStack () { delete [] items; }<br />
void Push (T t) { items[num++] = t; }<br />
T Pop () { return items[– –num]; }<br />
};<br />
void MainVStack () {<br />
VStack StackInt (10);<br />
StackInt.Push (33); StackInt.Push (44); StackInt.Push (55);<br />
ShowMessage ("В стеке целые числа: " + AnsiString (StackInt.Pop()) + ',' +<br />
AnsiString (StackInt.Pop()) + ',' + AnsiString (StackInt.Pop()));<br />
VStack StackDouble (10);<br />
StackDouble.Push (33.3); StackDouble.Push (44.2);<br />
StackDouble.Push (–55.1);<br />
ShowMessage ("В стеке вещественные числа: " +<br />
AnsiString (StackDouble.Pop ()) + ',' +<br />
AnsiString (StackDouble.Pop ()) + ',' + AnsiString (StackDouble.Pop ()));<br />
}<br />
И еще один шаблон класса. Он обрабатывает массивы разных типов:<br />
template class VVector {<br />
T *elem;<br />
// тип и число элементов<br />
public:<br />
VVector ();<br />
T &operator[] (int i) { return elem[i]; }<br />
void Out ();<br />
};<br />
template VVector :: VVector () {<br />
elem = new T[size];<br />
for (int i=0; i
113<br />
void MainVVector () {<br />
VVector d;<br />
// создаются векторы<br />
VVector f;<br />
VVector c;<br />
for (int i=0; i
114<br />
class VFunction {<br />
public:<br />
float Time (float R, float t) { return R*pow (sin (t), 2); }<br />
};<br />
class VRectangle: public VFunction {<br />
public:<br />
float step;<br />
VRectangle (float tk) { step = tk / 100; }<br />
};<br />
void MainVIntegral () {<br />
float j, tk = 0.8, t, sum;<br />
VRectangle myIntegral (tk);<br />
for (t=0, sum=0; t
115<br />
void MainVEqu() {<br />
// x + 4y = 9; –x + 6y = 1<br />
float member[] = { 1, 4, 9, –1, 6, 1 }; VEquX x (member); VEquY y (member);<br />
ShowMessage ("x = " + AnsiString (x.rx / x.re) + '\n' // x = 5<br />
+ "y = " + AnsiString (y.ry / y.re)); // y = 1<br />
}<br />
Здесь списки инициализации конструкторов пpоизводных классов<br />
VEquX, VEquY содержат запись VDet (r). Они инициализируют в базовом<br />
классе VDet те части классов EquX, EquY, которые при<strong>на</strong>длежат Det.<br />
Еще один пример <strong>на</strong>следования:<br />
class VStudent {<br />
protected:<br />
int course, group;<br />
public:<br />
int fac;<br />
AnsiString result;<br />
VStudent (AnsiString name, int y) {<br />
course = 10 – y / 1000; group = y; fac = group / 100 % 10;<br />
result = "\nСтудент " + name + " с факультета " + fac;<br />
}<br />
};<br />
class VBachalour: public VStudent {<br />
public:<br />
int spe;<br />
VBachalour (AnsiString n, int a, int b): VStudent (n,a) {<br />
spe = b;<br />
result += "\n" + AnsiString (course) + " курса " + AnsiString (group) +<br />
" группы – бакалавр по специальности " + AnsiString(b);<br />
}<br />
};<br />
void MainBac () {<br />
VBachalour first ("Сидоров", 6431, 225);<br />
if (first.spe > 200 && first.fac == 4)<br />
first.result += "\nЭто специалист по автоматике";<br />
else<br />
first.result += "\nЭто специалист по электротехнике";<br />
ShowMessage (first.result);<br />
}<br />
Пример множественного <strong>на</strong>следования:
AnsiString result;<br />
116<br />
class VStud {<br />
protected:<br />
__int64 group, fac, course;<br />
public:<br />
VStud (AnsiString name, __int64 x) {<br />
group = x;<br />
course = 10 – group / 1000;<br />
fac = group / 100 % 10;<br />
if (course > 0 && course < 6)<br />
result = "\nСтудент " + name + " факультета " + AnsiString (fac)<br />
+ "\nкурса " + AnsiString (course) + " группы " + AnsiString (group);<br />
else course = 0;<br />
}<br />
};<br />
class VPensioner {<br />
protected:<br />
__int64 zip;<br />
public:<br />
VPensioner (AnsiString name, __int64 z): zip (z) {<br />
if (zip > 195000L && zip < 200000L)<br />
result = "\nПенсионер " + AnsiString (name) + " из Петербурга";<br />
else zip = 0;<br />
}<br />
};<br />
class VPassenger: VStud, VPensioner {<br />
public:<br />
VPassenger (AnsiString name, __int64 data):<br />
VStud (name, data), VPensioner (name, data) {<br />
if (course>0 || zip) { result +="\n имеет право <strong>на</strong> льготы"; }<br />
else result = "\n Пассажир не имеет прав <strong>на</strong> льготы";<br />
ShowMessage (result);<br />
}<br />
};<br />
void MainVPassenger() {<br />
VPassenger w ("Сидоров", 8431);<br />
VPassenger x ("Козлов", 197376L);<br />
VPassenger y ("Тихонов", 1234);<br />
VPassenger z ("Степанов", 1);<br />
}
117<br />
Виртуальные функции и абстрактные классы<br />
При обычном <strong>на</strong>следовании объект производного класса обладает<br />
свойствами и поведением объектов базового класса. В этом случае говорят о<br />
раннем, или статическом, связывании <strong>на</strong> стадии компиляции. Виртуальными<br />
(virtual function) <strong>на</strong>зываются функции, которые определяются <strong>на</strong> стадии исполнения,<br />
то есть производится их позднее, или ди<strong>на</strong>мическое, связывание.<br />
Для этого объект производного класса с<strong>на</strong>бжается скрытым указателем <strong>на</strong><br />
базовый класс, который используется компилятором <strong>на</strong> стадии выполнения<br />
программы.<br />
При объявлении виртуальной функции к ее имени добавляется лексема<br />
virtual, что позволяет производным классам при необходимости переопределять<br />
такую функцию без изменения ее имени в списке параметров:<br />
virtual Тип имяФункции ([ объявлениеПараметров ]);<br />
«Виpтуальные функции служат центpальным моментом объектноориентированного<br />
программирования, потому что они позволяют опpеделять<br />
базовый класс общего <strong>на</strong>з<strong>на</strong>чения, не тpебуя з<strong>на</strong>ния особенностей, котоpые<br />
могут быть пpедусмотpены пpоизводным классом. Пpогpамма <strong>на</strong> C++ без<br />
виpтуальных функций, веpоятно, пpосто невеpно спpоектиpова<strong>на</strong>» [5]. Иными<br />
словами, виpтуальные функции опpеделяют возможности, котоpыми обладают<br />
пpоизводные классы, но котоpые не могут быть pеализованы <strong>на</strong><br />
уpовне базового класса.<br />
class VMarket {<br />
public:<br />
virtual void Market1 () { ShowMessage ("Телевизоры"); }<br />
void Market2 () { ShowMessage ("Компьютеры"); }<br />
};<br />
class VSection: public VMarket {<br />
public:<br />
void Market1 () { ShowMessage ("SONY"); }<br />
void Market2 () { ShowMessage ("MACINTOSH"); }<br />
};<br />
void MainVirtual1 () {<br />
VMarket *m = new VSection; // ссылка <strong>на</strong> производный класс<br />
m–>Market1 ();<br />
// SONY<br />
m–>Market2 ();<br />
// Компьютеры<br />
}
118<br />
Более сложный пример помогает разобраться с различными вариантами<br />
<strong>на</strong>следования:<br />
AnsiString result;<br />
class VProcessor {<br />
int frequency;<br />
public:<br />
VProcessor (int j = 100): frequency (j) {}<br />
virtual void Out () { result += "\nПроцессор " + AnsiString (frequency); }<br />
void Out (AnsiString s) { result += "\n" + s + AnsiString (frequency); }<br />
};<br />
class VComputer: public VProcessor {<br />
int hardDisk;<br />
public:<br />
VComputer (int j = 800): VProcessor (133), hardDisk (j) {}<br />
void Out () { result += "\nКомпьютер " + AnsiString (hardDisk); }<br />
int Out (AnsiString s) {<br />
result += "\n" + s + AnsiString (hardDisk);<br />
return hardDisk;<br />
}<br />
};<br />
void MainVirtual2 () {<br />
VProcessor intel, motorola (150), *cirix;<br />
VComputer ibm, macintosh (200),<br />
*proforma = &macintosh;<br />
intel.Out (); // Процессор 100<br />
motorola.Out (); // Процессор 150<br />
ibm.Out (); // Компьютер 800<br />
macintosh.Out (); // Компьютер 200<br />
intel.Out ("Intel "); // Intel 100<br />
motorola.Out ("Motorola "); // Motorola 150<br />
ibm.Out ("IBM "); // IBM 800<br />
macintosh.Out ("Macintosh "); // Macintosh 200<br />
cirix = proforma;<br />
cirix–>Out (); // Компьютер 200<br />
cirix–>Out ("Macintosh "); // Macintosh 133<br />
proforma–>Out (); // Компьютер 200<br />
proforma–>Out ("Macintosh "); // Macintosh 200<br />
ShowMessage (result);<br />
}<br />
Виртуаль<strong>на</strong>я функция, не определен<strong>на</strong>я в базовом классе, <strong>на</strong>зывается<br />
чистой виртуальной функцией (pure virtual function) и объявляется как
119<br />
virtual Тип имяФункции ([ объявлениеПараметров ]) = 0;<br />
Такая функция полностью определяется в производных классах. Базовый<br />
класс, содержащий, по крайней мере, одну чистую виртуальную функцию,<br />
<strong>на</strong>зывается абстрактным классом (abstract class).<br />
Абстрактный класс может использоваться лишь в качестве базового<br />
класса для некоторого другого класса. Никакие объекты абстрактного класса<br />
не могут создаваться и<strong>на</strong>че, как подобъекты, представляющие базовый класс<br />
в объектах некоторого производного от него класса.<br />
class VNewMarket {<br />
public:<br />
virtual void Market1 () = 0;<br />
void Market2 () { ShowMessage ("Компьютеры"); }<br />
};<br />
class VNewSection: public VNewMarket {<br />
public:<br />
void Market1 () { ShowMessage ("SONY"); }<br />
void Market2 () { ShowMessage ("MACINTOSH"); }<br />
};<br />
void MainVirtual3 () {<br />
VNewMarket *m = new VNewSection; // ссылка <strong>на</strong> производный класс<br />
m–>Market1 ();<br />
// SONY<br />
m–>Market2 ();<br />
// Компьютеры<br />
}<br />
В следующем примере абстрактный класс управляет выводом <strong>на</strong> экран<br />
разнообразной информации:<br />
class VMan {<br />
protected: long course;<br />
public: virtual AnsiString Out (AnsiString) = 0;<br />
};<br />
class VStudent: public VMan {<br />
protected:<br />
_ _int64 group, fac;<br />
public:<br />
VStudent (_ _int64 x) {<br />
group = x; fac = group / 100 % 10; course = 10 – x / 1000;<br />
if (course < 1 || course > 5) course = 0;<br />
}<br />
AnsiString Out (AnsiString name) {<br />
return "\nСтудент " + name + " факультета " + AnsiString (fac)
120<br />
+ "\nкурса " + AnsiString (course) + " группы " + AnsiString (group);<br />
}<br />
};<br />
class VPensioner: public VMan {<br />
public:<br />
VPensioner (__int64 x) {<br />
if (x > 195000L && x < 200000L) course = x;<br />
else course = 0;<br />
}<br />
AnsiString Out (AnsiString name) {<br />
return "\nПенсионер " + AnsiString(name) + " из Петербурга";<br />
}<br />
};<br />
class VPassenger: VStudent, VPensioner {<br />
public:<br />
AnsiString result;<br />
VPassenger (AnsiString name, __int64 data):<br />
VStudent (data), VPensioner (data) {<br />
If (!(VStudent::course) && !(VPensioner::course))<br />
ShowMessage (" Пассажир не имеет права <strong>на</strong> льготы.");<br />
else {<br />
if (VStudent::course) result = VStudent::Out (name);<br />
if (VPensioner::course) result = VPensioner::Out (name);<br />
ShowMessage (result + "\nимеет право <strong>на</strong> льготы.");<br />
}<br />
}<br />
};<br />
void MainVirtual4 () {<br />
VPassenger w ("Сидоров", 8431);<br />
VPassenger x ("Козлов", 197376L);<br />
VPassenger y ("Тихонов", 1234);<br />
VPassenger z ("Степанов", 1);<br />
}<br />
Контрольные вопросы<br />
1. Что такое объявление и определение класса<br />
2. Каково <strong>на</strong>з<strong>на</strong>чение конструкторов и деструкторов класса<br />
3. Что <strong>на</strong>зывается конструктором по умолчанию<br />
4. Что <strong>на</strong>зывается копирующим конструктором<br />
5. Как происходит передача объектов класса в функции<br />
6. Что такое дружественные функции и классы
121<br />
7. В чем заключается смысл перегрузки операторов<br />
8. Для чего выполняется перегрузка функций<br />
9. Какими достоинствами и недостатками обладают шаблоны<br />
10. В чем заключаются преимущества использования механизма <strong>на</strong>следования<br />
в программировании<br />
11. Как производится единичное <strong>на</strong>следование в программировании<br />
12. Какие данные и методы подлежат <strong>на</strong>следованию<br />
13. Что такое защищенные члены класса<br />
14. В чем смысл открытого и закрытого <strong>на</strong>следования<br />
15. Чем отличается виртуаль<strong>на</strong>я функция от «чистой» виртуальной функции<br />
16. Каково <strong>на</strong>з<strong>на</strong>чение абстрактных классов в програмировании
122<br />
Приложение. Программные документы<br />
Сведения, необходимые для разработки, изготовления, сопровождения<br />
и эксплуатации программных продуктов, оформляются в виде программных<br />
документов в соответствии с комплексом государственных стандартов<br />
ГОСТ 19.<br />
По ГОСТ 19.101-87 в состав программной документации входят:<br />
• спецификация – описание состава программы и ее документации;<br />
• ведомость держателей подлинников – перечень организаций, <strong>на</strong> которых<br />
хранятся подлинники программных документов (код документа<br />
05);<br />
• текст программы с необходимыми комментариями (12);<br />
• описание программы – описание ее логической структуры и функционирования<br />
(13);<br />
• программа и методика испытаний – сведения о параметрах, подлежащих<br />
проверке при испытаниях программы, а также о порядке и<br />
методике их контроля (51);<br />
• техническое задание – <strong>на</strong>з<strong>на</strong>чение и область применения программы,<br />
технические, технико-экономические и специальные требования,<br />
предъявляемые к ней, а также стадии, сроки разработки и виды<br />
испытаний;<br />
• пояснитель<strong>на</strong>я записка – схемы и описание алгоритмов и порядка<br />
его функционирования, а также обоснование принятых технических<br />
и технико-экономических решений (81);<br />
• эксплуатационные документы.<br />
Эксплуатационные документы содержат сведения, необходимые для<br />
обеспечения функционирования и эксплуатации программы:<br />
• ведомость эксплуатационных документов (20);<br />
• формуляр – основные характеристики программы, комплектность и<br />
сведения об эксплуатации (30);<br />
• описание применения – <strong>на</strong>з<strong>на</strong>чение и область применения программы,<br />
применяемые методы, класс решаемых задач, ограничения для<br />
применения, минималь<strong>на</strong>я конфигурация технических средств (31);
123<br />
• руководство системного программиста – сведения для проверки,<br />
обеспечения функционирования и <strong>на</strong>стройки программы <strong>на</strong> условия<br />
конкретного применения (32);<br />
• руководство программиста по эксплуатации программы (33);<br />
• руководство оператора – процедуры общения персо<strong>на</strong>ла с вычислительной<br />
системой в ходе выполнения программы (34);<br />
• описание языка, включая синтаксис и семантику (35);<br />
• руководство по техническому обслуживанию – порядок применения<br />
тестовых и диагностических программ при обслуживании (36).<br />
ГОСТ разрешает объединять отдельные виды эксплуатационных документов,<br />
за исключением первых двух. Необходимость такого объединения<br />
предусматривается техническим заданием. Объединенному документу присваивают<br />
<strong>на</strong>именование и обоз<strong>на</strong>чение одного из объединяемых документов.<br />
В соответствии с ГОСТ 19.103-78, обоз<strong>на</strong>чения программ и документов<br />
состоят из групп з<strong>на</strong>ков, разделенных точкой, пробелом и дефисом. Структура<br />
обоз<strong>на</strong>чения программы и ее спецификации:<br />
A.B.XXXXX-XX<br />
Здесь A – код страны, B – код организации-разработчика, XXXXX – регистрационный<br />
номер, XX – номер издания программы или редакции документа.<br />
Коды страны и организации-разработчика присваиваются в установленном<br />
порядке, номер издания программы или редакции документа – в порядке возрастания<br />
от 01 до 99.<br />
Структура обоз<strong>на</strong>чения других программных документов:<br />
A.B.XXXXX-XX YY ZZ-Z<br />
Здесь A.B.XXXXX-XX – обоз<strong>на</strong>чение программы или ее спецификации, YY –<br />
код вида документа по ГОСТ 19.101-87, ZZ – номер документа данного вида,<br />
Z – номер части документа.
124<br />
Список литературы<br />
1. C++Builder 5. Руководство разработчика. Уч. пособие в 2-х т. // Д. Холингвэрт,<br />
Д. Баттерфилд, Б. Сворт, Д. Оллсоп. М.: Изд. дом «Вильямс», 2001.<br />
880 и 832 с.<br />
2. Архангельский А. Я. Программирование в C++Builder 6. М.: Бином, 2003.<br />
1152 с.<br />
3. Бабэ Б. Просто и ясно о Borland C++. М.: Бином, 1995. 400 с.<br />
4. Водовозов В. М., Пожидаев А. К. Конструирование приложений для Windows:<br />
Учеб. пособие. СПб.: Изд-во СПбГЭТУ «ЛЭТИ», 2004. 72 с.<br />
5. Голуб А. Пpавила пpогpаммиpования C & C++. М.: Бином, 1996. 272 с.<br />
6. Кнут Д. Искусство программирования для ЭВМ. В 3-х кн. М.: Миp, 1999.<br />
7. Культин Н. Б. Самоучитель <strong>С++</strong>Builder. СПб.: БХВ-Петербург, 2005.<br />
320 с.<br />
8. Павловская Т. А. С/<strong>С++</strong>. Программирование <strong>на</strong> языке высокого уровня:<br />
Учебник. СПб.: Питер, 2003. 360 с.<br />
9. Сван Т. Освоение Borland C++ 4.5. Киев: Диалектика. 1996. 400 с.<br />
10. Шамис В. А. Borland C++Builder 6.0. Для профессио<strong>на</strong>лов. СПб.: Питер,<br />
2003. 798 с.
125<br />
Предметный указатель<br />
алгоритм, algorithm...................6, 59<br />
аллокатор, allocator.......................59<br />
аргумент, argument .......................10<br />
верши<strong>на</strong>, top ...................................52<br />
ветвь, branch ..................................57<br />
вход, input .......................................53<br />
вызов, call........................................10<br />
выход, output...................................53<br />
гиперссылка, hotspot........................7<br />
дек, deque........................................57<br />
дерево двоичное, binary tree..........57<br />
деструктор, destructor................102<br />
директива препроцессора,<br />
preprocessor directive ....................9<br />
дистрибутив, distributive..............81<br />
документ программный, program<br />
document....................................122<br />
заголовок, caption...........................11<br />
з<strong>на</strong>чение возвращаемое, return value<br />
........................................................8<br />
инициализация по умолчанию,<br />
default initialization .....................11<br />
интерфейс пользователя, user<br />
interface........................................85<br />
исключение, exception....................20<br />
источник данных, data source.......85<br />
итератор, iterator..........................59<br />
класс , class......................................14<br />
класс абстрактный, abstract class<br />
....................................................119<br />
класс дружественный, friend class<br />
....................................................104<br />
класс типизированный, typed class<br />
......................................................22<br />
константа глобаль<strong>на</strong>я, global<br />
constant ........................................12<br />
конструктор , constructor..............15<br />
конструктор копирующий, copying<br />
constructor....................................99<br />
конструктор по умолчанию, default<br />
constructor ................................. 101<br />
контейнер, container ..................... 59<br />
корень, root..................................... 57<br />
лист, sheet...................................... 57<br />
массив, array................................... 40<br />
моделирование, modelling............... 5<br />
модель, model................................... 5<br />
<strong>на</strong>бор данных, dataset.................... 85<br />
<strong>на</strong>следование единичное, unit<br />
inheritance ................................. 113<br />
<strong>на</strong>следование множественное,<br />
multiple inheritance ................... 113<br />
обработчик исключения, exception<br />
handler ......................................... 20<br />
объединение, union ........................ 51<br />
объявление, announcement ............ 10<br />
оператор разыменования,<br />
dereference operator .................... 24<br />
определение, declaration................ 10<br />
очередь, queue................................ 53<br />
пакет, package............................... 75<br />
параметр, parameter ..................... 10<br />
перегрузка, overloading ............... 105<br />
перемен<strong>на</strong>я внешняя, extern variable<br />
..................................................... 12<br />
перемен<strong>на</strong>я глобаль<strong>на</strong>я, global<br />
variable ........................................ 12<br />
перемен<strong>на</strong>я изменчивая, volatile<br />
variable ........................................ 12<br />
перемен<strong>на</strong>я локаль<strong>на</strong>я, local variable<br />
..................................................... 12<br />
перемен<strong>на</strong>я регистровая, register<br />
variable ........................................ 12<br />
перемен<strong>на</strong>я статическая, static<br />
variable ........................................ 12<br />
перемен<strong>на</strong>я файловая, file variable<br />
..................................................... 67<br />
перестановка, swap....................... 43
подключение, connection ...............85<br />
порядок заполнения, fill allocation52<br />
порядок обратный, breadth-first<br />
traversal........................................58<br />
порядок обхода, traversal...............52<br />
порядок прямой, depth-first traversal<br />
......................................................58<br />
порядок симметричный,<br />
simmetrical traversal....................58<br />
<strong>программирование</strong>, programming...5<br />
прототип, prototype......................10<br />
результирующий <strong>на</strong>бор, result set 92<br />
рекурсия, recursion.........................46<br />
сортировка , sorting .......................43<br />
сортировка быстрая, quick sorting<br />
......................................................44<br />
сортировка вставкой, insertion<br />
sorting...........................................44<br />
сортировка выборкой, exchange<br />
sorting...........................................44<br />
сортировка методом «пузырька»,,<br />
bubble sorting...............................44<br />
сортировка методом Шелла, Shell<br />
sorting...........................................44<br />
список инициализации, initialisation<br />
list...............................................101<br />
список линейный, linear list ...........54<br />
список связный, linked list.............51<br />
5<br />
список циклический, cyclic list...... 54<br />
ссылка, reference............................ 24<br />
стек, stack ...................................... 52<br />
структура , structure...................... 49<br />
структура ди<strong>на</strong>мическая, dynamic<br />
data structure................................ 51<br />
СУБД, DBMS................................. 84<br />
таблица глав<strong>на</strong>я, master ............... 94<br />
таблица подчинен<strong>на</strong>я, detail ........ 94<br />
тип параметра, parameter type ... 11<br />
тип порядковый, ordered data type<br />
..................................................... 22<br />
узел, node........................................ 54<br />
указатель, pointer.......................... 24<br />
файл, file......................................... 65<br />
функции тело, function body ........ 11<br />
функция , function............................. 8<br />
функция виртуаль<strong>на</strong>я, virtual<br />
function...................................... 117<br />
функция встроен<strong>на</strong>я, inline variable<br />
..................................................... 12<br />
функция глав<strong>на</strong>я, main function.... 10<br />
функция дружествен<strong>на</strong>я, friend<br />
function...................................... 103<br />
функция чистая виртуаль<strong>на</strong>я, pure<br />
virtual function .......................... 118<br />
шаблон, template .......................... 109<br />
язык, language .................................. 5
5<br />
Валерий Михайлович Водовозов,<br />
Федор Викторович Чмиленко<br />
<strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong> <strong>на</strong> <strong>С++</strong><br />
Учебное пособие<br />
Редактор<br />
Подписано в печать . Формат 60×84 1/16.<br />
Бумага офсет<strong>на</strong>я. Печать офсет<strong>на</strong>я. Печ. л. .<br />
Гарнитура «Times». Тираж 150 экз. Заказ .<br />
Издательство СПбГЭТУ «ЛЭТИ»<br />
197376, С.-Петербург, ул. Проф. Попова, 5