13.01.2015 Views

Объектно-ориентированное программирование на С++ - eDrive

Объектно-ориентированное программирование на С++ - eDrive

Объектно-ориентированное программирование на С++ - eDrive

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Федеральное агентство по образованию<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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!