13.01.2015 Views

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

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

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

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

Федеральное агентство по образованию<br />

_________________________________________<br />

Санкт-Петербургский государственный<br />

электротехнический университет «ЛЭТИ»<br />

____________________________________________________________<br />

В. М. Водовозов Ф. В. Чмиленко<br />

<strong>Объектно</strong>-<strong>ориентированное</strong><br />

<strong>программирование</strong> <strong>на</strong> <strong>С++</strong><br />

Учебное пособие<br />

Санкт-Петербург<br />

Издательство СПбГЭТУ «ЛЭТИ»<br />

2007


ББК 32.973.26-018<br />

В62<br />

УДК 681.3.016 (018)<br />

Водовозов В. М., Чмиленко Ф. В. <strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong><br />

<strong>на</strong> <strong>С++</strong>: Учеб. пособие. СПб.: Изд-во СПбГЭТУ «ЛЭТИ», 2007.<br />

128 с.<br />

Дано описание языка <strong>С++</strong>. Представлены основные этапы процедурного,<br />

структурного, модульного и объектно-ориентированного программирования<br />

под Windows. Показаны особенности работы с библиотеками, процедурами,<br />

функциями, файлами, базами данных и справочными системами.<br />

Пред<strong>на</strong>з<strong>на</strong>чено для студентов <strong>на</strong>правления 654500 – «Электротехника,<br />

электромеханика и электротехнологии» по специальностям: 180100 – «Электромеханика»,<br />

180400 – «Электропривод и автоматизация промышленных<br />

установок и робототехнических комплексов», 180500 – «Электротехнологические<br />

установки и системы».<br />

Рецензенты: кафедра информационных технологий СПбИДПО;<br />

директор Центра профессио<strong>на</strong>льной подготовки «ИЛН Системс»<br />

О. В. Степанова<br />

Утверждено<br />

редакционно-издательским советом университета<br />

в качестве учебного пособия<br />

ISBN 5-7629-0567-5 © В. М. Водовозов, Ф. В. Чмиленко, 2007


Оглавление<br />

Предисловие............................................................................................................. 5<br />

Введение в процедурное <strong>программирование</strong> ....................................................... 7<br />

Редактор кода.................................................................................................... 7<br />

Функции............................................................................................................. 8<br />

Выражения ...................................................................................................... 10<br />

Классы форм ................................................................................................... 14<br />

Обработка событий ........................................................................................ 16<br />

Программирование графики.......................................................................... 19<br />

Исключения..................................................................................................... 20<br />

Простые типы данных и операторы ............................................................. 22<br />

Тип AnsiString ................................................................................................. 26<br />

Контрольные вопросы.................................................................................... 30<br />

Составные операторы ........................................................................................... 32<br />

Операторы перехода....................................................................................... 32<br />

Условный оператор ........................................................................................ 33<br />

Оператор выбора ............................................................................................ 34<br />

Оператор цикла............................................................................................... 35<br />

Операторы повторения .................................................................................. 36<br />

Программирование диалогов ........................................................................ 38<br />

Контрольные вопросы.................................................................................... 39<br />

Введение в структурное <strong>программирование</strong> ...................................................... 40<br />

Массивы........................................................................................................... 40<br />

Перестановки и сортировки .......................................................................... 43<br />

Списки TStringList.......................................................................................... 46<br />

Структуры и объединения............................................................................. 49<br />

Ди<strong>на</strong>мические структуры данных ................................................................ 51<br />

Стандарт<strong>на</strong>я библиотека STL........................................................................ 59<br />

Контрольные вопросы.................................................................................... 64<br />

Введение в модульное <strong>программирование</strong>......................................................... 65<br />

Работа с текстовыми файлами ...................................................................... 65<br />

Работа с графическими файлами .................................................................. 71<br />

Работа с двоичными файлами ....................................................................... 74<br />

Модули DLL и пакеты ................................................................................... 75<br />

Ресурсы............................................................................................................ 78<br />

Потоки ............................................................................................................. 79<br />

Дистрибутивы ................................................................................................. 81<br />

Контрольные вопросы.................................................................................... 83<br />

Приложения баз данных ....................................................................................... 83<br />

Технологии создания приложений баз данных........................................... 84<br />

Архитектуры приложений баз данных......................................................... 86<br />

Подключение к базам данных....................................................................... 88<br />

Доступ к записям таблиц ............................................................................... 90


Работа с запросами ......................................................................................... 92<br />

Связывание таблиц и запросов ..................................................................... 93<br />

Контрольные вопросы.................................................................................... 95<br />

<strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong>................................................ 95<br />

Инкапсуляция.................................................................................................. 95<br />

Конструкторы и деструкторы ....................................................................... 99<br />

Дружественные функции и классы.............................................................103<br />

Полиморфизм................................................................................................105<br />

Шаблоны........................................................................................................109<br />

Наследование ................................................................................................113<br />

Виртуальные функции и абстрактные классы ..........................................117<br />

Контрольные вопросы..................................................................................120<br />

Приложение. Программные документы ...........................................................122<br />

Список литературы .............................................................................................124<br />

Предметный указатель........................................................................................125


5<br />

Предисловие<br />

Напрасно обучение без мысли.<br />

Из Конфуция<br />

Программирование не чуждо человеческой природе. Тот, кто пишет и<br />

понимает программы, – не робот и не «создание не от мира сего». Программирование<br />

– это разновидность моделирования, а без модели, упрощения и<br />

обобщения нет соз<strong>на</strong>ния. Программирование естественно вытекает из нужд и<br />

потребностей современного культурного общества.<br />

Более того, если естественный язык – это в первую очередь средство<br />

общения, то искусственный – это инструмент поз<strong>на</strong>ния. В этом кроется великая<br />

польза программирования. И в этом видится смысл известного высказывания<br />

Н. Винера о том, что «вычислитель<strong>на</strong>я маши<strong>на</strong> цен<strong>на</strong> ровно <strong>на</strong>столько,<br />

<strong>на</strong>сколько ценен использующий ее человек».<br />

«Прекрасное – трудно», считал Платон. И <strong>программирование</strong> далеко не<br />

просто. Оно требует внимания и терпения. Многие студенты из<strong>на</strong>чально не<br />

обладают этими качествами. Но это не з<strong>на</strong>чит, что они не могут <strong>на</strong>учиться<br />

программированию, а оз<strong>на</strong>чает лишь, что такие черты требуется выработать<br />

в ходе учебного процесса. Для этого воспользуйтесь некоторыми приемами<br />

освоения того, что Д. Кнут <strong>на</strong>звал «искусством программирования» [6].<br />

Во-первых, постарайтесь максимально использовать мощь визуального<br />

программирования. Визуальное сродни и ребенку, и взрослому, оно уз<strong>на</strong>ваемо,<br />

в нем много ассоциаций с жизнью. Визуальные объекты проще воспринимаются,<br />

в них сложнее сделать ошибку и легче такую ошибку <strong>на</strong>йти [1][2].<br />

Сам по себе принцип визуального программирования служит прекрасным<br />

стимулом для создания хотя бы внешне неповторимых программ. Уникальный<br />

дизайн форм и элементов управления удается всем. Исходя из этого, при<br />

конструировании своей программы приложите все усилия, чтобы ваш интерфейс<br />

отличался от других. Здесь <strong>на</strong> помощь может прийти пособие [4],<br />

предшествующее данному изданию.<br />

Но красивое приложение – это еще не программа, а просто пятно <strong>на</strong> экране.<br />

Программа – как девушка: нравится за внешнось, любится за содержание.<br />

Поэтому одно из первых умений программирования – это умение алго-


6<br />

ритмизировать поведение объектов, раскладывать его <strong>на</strong> действия, совершаемые<br />

во времени. Алгоритм – предшественник логики [10]. Если вы не в<br />

состоянии последовательно описать работу будущего приложения, то, скорее<br />

всего, вы не сможете и реализовать его <strong>на</strong> языке программирования. Начните<br />

с построения простейшего линейного приближения. Затем детализируйте<br />

свой алгоритм ветвлениями. Наконец, организуйте циклы. Продумывайте<br />

каждую разработку <strong>на</strong> уровне алгоритма, так как «если не поставить себя в<br />

жесткие рамки с самого <strong>на</strong>чала, программа превратится в огромного монстра,<br />

не поддающегося отладке» [8].<br />

Позаботьтесь, также, об уникальности имен переменных, констант,<br />

функций и файлов своих проектов. Давать свои (не такие, как в учебнике или<br />

у товарища) име<strong>на</strong> трудно, потому что для этого <strong>на</strong>до иметь свое мнение, которое<br />

невозможно сформировать без понимания различий между стандартными<br />

и авторскими элементами программы. Но это хороший стимул, так как<br />

является одним из первых элементов творчества.<br />

В ходе работы <strong>на</strong>д программой полезен коллективный подход. Од<strong>на</strong>ко,<br />

принимая помощь, не выпускайте из рук мышки, и<strong>на</strong>че помощь «прошмыгнет»<br />

мимо вас и вся достанется тому, кто вам помогает («хочешь <strong>на</strong>учиться<br />

сам, <strong>на</strong>учи товарища»).<br />

«Ясные мысли выражаются ясным и понятным языком», подметил<br />

Р. Гамзатов. Красивое оформление исходного текста программы тоже требует<br />

умений. Кроме того, оно вырабатывает дисциплину программирования,<br />

проясняет мысль, учит уважать читателя текста, повышает производительность<br />

разработки приложений. Когда использовать тот или иной з<strong>на</strong>к пунктуации,<br />

где вставлять отступы, пробелы и чистые строки, как использовать<br />

строчные и прописные буквы, что такое «говорящие име<strong>на</strong>» – эти и другие<br />

вопросы требуют серьезного внимания.<br />

Давая эти рекомендации, авторы отлично понимают, что самое простое<br />

в жизни – это давать советы, а самое сложное – чужим советам следовать.<br />

Так что совсем не обязательно с <strong>на</strong>ми соглашаться.<br />

Просто примите к сведению и идите своим путем.


7<br />

Введение в процедурное <strong>программирование</strong><br />

Упрощайте сложное, и вы получите<br />

самое существенное.<br />

Г. Бокль<br />

Концепция процедурного программирования предполагает «деление<br />

проблемы <strong>на</strong> такое множество задач, чтобы, решив каждую из них, можно<br />

было прийти к решению всей проблемы» [6]. Каждой задаче приводится в<br />

соответствие отдель<strong>на</strong>я процедура. В языках С/<strong>С++</strong>, С#, Java процедуры<br />

представлены функциями, в Visual Basic и Pascal – функциями и подпрограммами.<br />

Благодаря такой процедурной декомпозиции по принципу «разделяй<br />

и властвуй» легче управлять большими и сложными программами [8].<br />

О<strong>на</strong> обеспечивает логическую сегментацию проблемы и упрощает отладку.<br />

Концепция базируется <strong>на</strong> механизме стандартных типов данных – категорий<br />

(разрядов), <strong>на</strong> которые подразделяются данные в соответствии со способами<br />

их хранения и обработки. Без типизации невозможно <strong>программирование</strong>.<br />

В отличие от Excel, Mathcad или Visual Basic, язык С/<strong>С++</strong> не «прикрывает»<br />

типизацию автоматическим присваиванием типа по умолчанию, поэтому<br />

разбираться с типами данных следует сразу, не откладывая этот вопрос<br />

«до сессии».<br />

Редактор кода<br />

Файлы программ обрабатываются <strong>на</strong> вкладках ок<strong>на</strong> редактора кода, запускаемого<br />

через меню View.Units (), View.Toggle Form/Unit<br />

(). Вкладки с текстами открываются в редакторе кода через меню File<br />

.Open. Переход между ними удобно выполнять с помощью <strong>на</strong>вигационных<br />

стрелок в правом верхнем углу ок<strong>на</strong>. В левом поле выделения ок<strong>на</strong> щелчком<br />

можно расставлять и снимать точки прерывания выполнения программы. Переключение<br />

между кодами и файлами заголовков выполняется через контекстные<br />

меню Open Source/Header File () и Open File at Cursor (). Щелчки по име<strong>на</strong>м в текстах программ при <strong>на</strong>жатой клавише <br />

превращают их в гиперссылки (hotspot), с помощью которых обычно перемещаются<br />

между связанными файлами. Другие возможности системы редактирования<br />

Code Insight приведены в табл. 1:


8<br />

Подсказки в системе редактирования Code Insight<br />

Таблица 1<br />

Технология Клавиши Наз<strong>на</strong>чение<br />

Code Completion Список членов класса после стрелки –> и точки<br />

Code Parameters – Список параметров функции после скобки (<br />

Code Templates Список шаблонов программирования<br />

Tooltip Expression<br />

Подсказка з<strong>на</strong>чения переменной под курсором в<br />

Evaluation<br />

–<br />

окне отладчика<br />

Tooltip Symbol Insight – Подсказка объявления объекта в редакторе кода<br />

Функции<br />

Исходный текст программы <strong>на</strong> языке C++ составляют выражения,<br />

сгруппированные в функции.<br />

Функции – это единственный вид процедур, используемый в C++. Будучи<br />

разновидностью процедуры, функция (function) представляет относительно<br />

автономную именованную часть программы, описывающую поведение<br />

программных объектов через действия <strong>на</strong>д данными. Программирование<br />

с использованием функций сокращает размеры исходного текста и упрощает<br />

его организацию, хотя «экономия пространства не экономит времени» [6].<br />

В отличие от простой процедуры, функция обычно имеет возвращаемое<br />

з<strong>на</strong>чение (return value), то есть результат своей деятельности в вызывающем<br />

ее выражении.<br />

Каждая функция может многократно вызываться из других функций. В<br />

момент вызова функция получает исходные данные и <strong>на</strong>чи<strong>на</strong>ет выполнять<br />

предписанные действия <strong>на</strong>д ними.<br />

Различают встроенные библиотечные и авторские функции. Первые<br />

хранятся в библиотеках C++. Для доступа к библиотечным функциям в <strong>на</strong>чале<br />

текста программы указываются име<strong>на</strong> соответствующих файлов заголовков:<br />

#include < имяФайлаЗаголовка ><br />

или<br />

#include «имяФайлаЗаголовка»<br />

Файлы заголовков – «это хранилища информации, содержащие описания<br />

констант и объявления функций» [9]. Каждый такой файл сообщает компилятору<br />

име<strong>на</strong> и характеристики функций из других файлов и библиотек,


9<br />

которые используются в программе. Файлы заголовков главных библиотек<br />

фирмы Borland приведены в табл. 2.<br />

Таблица 2<br />

Файл заголовка<br />

alloc.h<br />

bios.h<br />

conio.h<br />

ctype.h<br />

dir.h<br />

fstream.h<br />

iomanip.h<br />

iostream.h<br />

math.h<br />

process.h<br />

stdio.h<br />

string.h, stdlib.h<br />

time.h<br />

vcl.h<br />

window.h<br />

Файлы заголовков библиотек фирмы Borland<br />

Наз<strong>на</strong>чение<br />

Функции распределения памяти<br />

Функции интерфейса с BIOS<br />

Функции консольного ввода и вывода<br />

Функции классификации символов<br />

Функции доступа к файлам<br />

Функции ввода и вывода файловых потоков<br />

Функции-манипуляторы потоков<br />

Функции потокового ввода и вывода<br />

Математические функции<br />

Функции управления процессами<br />

Функции стандартного ввода и вывода<br />

Функции преобразования строк<br />

Функции времени и даты<br />

Функции библиотек VCL<br />

Функции для работы в среде Windows<br />

Выражение, <strong>на</strong>чи<strong>на</strong>ющееся символом #, <strong>на</strong>зывают директивой препроцессора<br />

(preprocessor directive). Если имя файла заголовка в директиве<br />

препроцессора заключено в угловые скобки, компилятор отыскивает его в<br />

специальной папке \include. Если же это имя заключено в кавычки, поиск будет<br />

<strong>на</strong>чат из текущей папки.<br />

В текстах программ C++Builder много директив препроцессора, служащих<br />

инструкциями для компилятора. Все они располагаются в <strong>на</strong>чале исходного<br />

текста (табл. 3).<br />

Таблица 3<br />

Директивы препроцессора<br />

Директива препроцессора<br />

#include <br />

#include «файлЗаголовка»<br />

#define имяМакроса (список<br />

параметров) текстМакроса<br />

#pragma argsused<br />

#pragma hdrstop<br />

#pragma package (smart_init)<br />

#pragma resource "*.dfm"<br />

#ifdef (#if defined)… #endif<br />

#ifndef … #define… #endif<br />

Наз<strong>на</strong>чение<br />

Заменить строку содержимым файла заголовка<br />

Создать макрос<br />

Запрет вывода сообщений о неиспользуемых параметрах<br />

Ограничение списка ранее компилированных заголовков<br />

Инициализировать пакеты в порядке их объявления<br />

В качестве файла ресурсов использовать файлы .DFM<br />

Если имя ранее определено, выполнить все до #endif<br />

Если имя еще не определено, выполнить все до #endif


10<br />

С применением авторских функций связаны три понятия: объявление<br />

функции, определение функции и вызов функции. Каждая функция только<br />

один раз определяется (declare) (описывается) в тексте. При этом ей присваивается<br />

уникальное имя и сообщается <strong>на</strong>бор параметров (parameter), то<br />

есть данных, которые должны поступить к ней прежде, чем подпрограмма<br />

приступит к их обработке. В ряде случаев определению функции предшествует<br />

ее объявление (announcement). Объявления и определения функций могут<br />

выноситься в авторские библиотеки. Каждый программист помещает<br />

нужные ему функции в файлы заголовков и при необходимости обращается к<br />

этим файлам из файлов кодов, получая доступ к созданным ранее функциям.<br />

Такое обращение <strong>на</strong>зывается вызовом (call). При вызове функции передаются<br />

аргументы (argument), то есть з<strong>на</strong>чения параметров. В процессе компиляции<br />

автоматически создается список всех функций, необходимых обрабатываемому<br />

файлу, а в ходе последующей компоновки уста<strong>на</strong>вливаются необходимые<br />

связи.<br />

Име<strong>на</strong> функциям, равно как и переменным, константам, определяемым<br />

в программах, даются программистами. Име<strong>на</strong> чувствительны к регистру,<br />

<strong>на</strong>чи<strong>на</strong>ются с буквы или символа подчеркивания, не содержат пробелов и<br />

иных символов, кроме подчеркивания, цифр и латинских букв, и отличаются<br />

от лексем, служащих име<strong>на</strong>ми встроенных функций, операторов, констант и<br />

других элементов языка. Име<strong>на</strong> функций расширяют <strong>на</strong>бор лексем языка<br />

программирования, используемый в данной программе; они включаются в<br />

выражения <strong>на</strong>ряду с лексемами языка.<br />

Обязательной частью программы является единствен<strong>на</strong>я глав<strong>на</strong>я функция<br />

(main function), обычно WinMain, расположен<strong>на</strong>я в файле кода проекта.<br />

Раздел объявлений и другие функции необязательны. Когда компьютер приступает<br />

к выполнению программы, он <strong>на</strong>чи<strong>на</strong>ет ее с первой строки главной<br />

функции, независимо от взаимного расположения функций в программе.<br />

Выражения<br />

Любое объявление является выражением, содержащим сведения об области<br />

определения и области з<strong>на</strong>чений данных. Объявления функций <strong>на</strong>зывают<br />

прототипами (prototype), т.к. они представляют собой своеобразный<br />

шаблон, образец использования функции:


11<br />

[Модификатор] [типФункции] имяФункции<br />

( типПараметра [ имяПараметра [ = З<strong>на</strong>чение ]]...);<br />

Здесь и далее используется метаязык описания языка программирования,<br />

в котором в прямоугольные скобки принято заключать необязательные<br />

части выражений, а многоточием отмечается возможность повторения элементов<br />

синтаксической формулы.<br />

Все выражения в C++, кроме директив препроцессора и комментариев,<br />

завершаются точкой с запятой. Длинные выражения можно разбивать символом<br />

\ – приз<strong>на</strong>ком продолжения <strong>на</strong> следующей строке. В круглых скобках<br />

помещаются сведения о параметрах функции. При этом части из них, расположенной<br />

в конце списка параметров, можно сразу присваивать з<strong>на</strong>чения –<br />

это <strong>на</strong>зывается инициализацией по умолчанию (default initialization). Тип<br />

функции (function type) характеризует возвращаемое з<strong>на</strong>чение, а тип параметра<br />

(parameter type) – множество входных данных, обрабатываемых этой<br />

функцией. Кроме объявления встроенных функций из стандартных библиотек,<br />

<strong>на</strong>ходящихся в файлах заголовков, авторские функции, создаваемые программистом,<br />

также могут включаться в файлы заголовков. Но часто их объявляют<br />

и затем определяют прямо в тексте программы. В тех случаях, когда<br />

функция определяется до ее вызова, объявлять функции необязательно. C++<br />

не допускает объявления и определения функций внутри других функций:<br />

Формат определения авторской функции:<br />

Заголовок {<br />

[ объявлениеДанных...]<br />

Выражение…<br />

[ return возвращаемоеЗ<strong>на</strong>чение;] }<br />

Заголовок (caption) определяемой функции является своего рода копией<br />

ее объявления, но обязательно содержит име<strong>на</strong> параметров и не завершается<br />

точкой с запятой. Внутри фигурных скобок в определении функции заключается<br />

ее тело (body). Текст программы содержит выражения, состоящие из<br />

переменных, констант, операторов, вызовов функций и заканчивающиеся<br />

точкой с запятой. Фигурными скобками текст может разбиваться <strong>на</strong> отдельные<br />

блоки.<br />

Обычно каждый блок обрабатывает свои переменные и константы, «не<br />

заботясь» о других данных программы. В этом «правиле ограниченной видимости»<br />

заключается автономность, независимость блоков и функций. Дан-


12<br />

ные, обрабатываемые внутри блока, <strong>на</strong>зывают локальными (local variable),<br />

личными или автоматическими. Память под них выделяется <strong>на</strong> время работы,<br />

а по завершении работы функции их з<strong>на</strong>чения так же автоматически стираются,<br />

освобождая место в памяти переменным других частей программы.<br />

В C++ разрешено совмещать определение коротких функций с их объявлением<br />

с помощью лексемы inline. Благодаря этому механизму, код функции<br />

автоматически встраивается в то место, откуда происходит ее вызов, и<br />

вызов происходит з<strong>на</strong>чительно быстрее.<br />

Если требуется сохранять з<strong>на</strong>чения локальных переменных между вызовами<br />

функций, их объявления с<strong>на</strong>бжают модификатором static. В момент<br />

объявления статическую переменную можно явно инициализировать. Если<br />

этого не сделать, то при первом вызове ей автоматически присваивается нулевое<br />

з<strong>на</strong>чение. При повторных вызовах выражение инициализации игнорируется.<br />

Модификатор register <strong>на</strong>правляет локальную переменную <strong>на</strong> сохранение<br />

в регистры процессора. А модификатор volatile явно указывает, что локаль<strong>на</strong>я<br />

перемен<strong>на</strong>я «изменчива», то есть может в любой момент поменять<br />

свое з<strong>на</strong>чение.<br />

Кроме локальных данных, в программах существуют глобальные переменные<br />

(global variable) и глобальные константы (global constant), доступные<br />

из разных частей программы. Они размещаются в разделе объявлений файла<br />

и помогают функциям обмениваться информацией, минуя параметры, но порой<br />

снижают <strong>на</strong>дежность программной системы и увеличивают размер памяти,<br />

занимаемой программой в ходе выполнения.<br />

Внешний модификатор extern информирует компилятор о существовании<br />

внешних данных, <strong>на</strong>ходящихся в другом файле кода программы. За<br />

внешними и глобальными данными память закрепляется постоянно <strong>на</strong> все<br />

время работы программы, а в ходе первого вызова они инициализируются<br />

нулем.<br />

Каждый вызов функции представляет собой запрос <strong>на</strong> совершение определенных<br />

действий. Он осуществляется в соответствии с объявленным<br />

прототипом и семантической организацией программы:<br />

имяФункции ([ списокАргументов ]);


13<br />

В ответ <strong>на</strong> вызов копия кода функции передается в вызывающую<br />

функцию для обработки, после чего о<strong>на</strong> удаляется из памяти. Исключением<br />

являются функции, объявленные с модификатором inline, коды которых обрабатываются<br />

непосредственно. При вызове функций параметры, инициализированные<br />

по умолчанию, могут быть опущены в списке передаваемых аргументов.<br />

В любой части программы можно помещать комментарии – тексты, обрамленные<br />

символами /* */, или фрагменты строк, <strong>на</strong>чи<strong>на</strong>ющиеся символами<br />

//, не влияющие <strong>на</strong> ход выполнения программы.<br />

Ниже приведен пример текста файла проекта <strong>на</strong> языке C++Builder:<br />

#include <br />

#pragma hdrstop<br />

// Заголовочный файл описания классов VCL<br />

// Ограничитель списка прекомпилированных<br />

// заголовочных файлов<br />

// Файлы и име<strong>на</strong> форм проекта<br />

USEFORM ("bMainForm.cpp", MainForm);<br />

USEFORM ("bDataBase.cpp", DataBase);<br />

USEFORM ("bDiagram.cpp", Diagram);<br />

USEFORM ("bAbout.cpp", About);<br />

WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int)<br />

{ try {<br />

Application–>Initialize ();<br />

// Глав<strong>на</strong>я<br />

// функция<br />

// Инициализация OLE<br />

// Создание формы<br />

Application–>CreateForm (__classid (TMainForm), &MainForm);<br />

Application–>Run ();<br />

// Запуск приложения<br />

}<br />

catch (Exception &exception)<br />

{ Application–>ShowException (&exception); }<br />

catch (...) {<br />

try { throw Exception (""); }<br />

catch (Exception &exception)<br />

{ Application–>ShowException(&exception); }<br />

}<br />

return 0;<br />

}<br />

// Обработка исключений<br />

Здесь директива препроцессора #include подключает к тексту<br />

файл заголовка, ссылающийся <strong>на</strong> описание классов VCL, а директива<br />

#pragma hdrstop ограничивает список файлов заголовка, доступных для<br />

предварительной компиляции. Директивы USEFORM сообщают о модулях,


14<br />

используемых в проекте. Выражение Application–>Initialize () инициализирует<br />

OLE-серверы автоматизации. Каждое выражение Application–>CreateForm ()<br />

создает отдельную форму, а выражение Application–>Run() запускает приложение,<br />

переводя его в состояние ожидания <strong>на</strong>ступления событий. Конструкция<br />

try…catch используется для корректного завершения приложения в случае<br />

возникновения ошибки.<br />

Классы форм<br />

Меню View.Class Explorer открывает окно обозревателя классов. В нем<br />

представле<strong>на</strong> иерархия классов с указанием типов, член-данных, членфункций<br />

и глобальных переменных, входящих во все файловые модули.<br />

Каждой форме соответствует файл заголовка с определением ее класса<br />

в формате<br />

<strong>на</strong>пример:<br />

class имяКласса: public TForm<br />

{ [[ Метка ] обявлениеЧленов...]...};<br />

class TForm1 : public TForm {<br />

_ _published:<br />

TEdit *Edit1;<br />

TEdit *Edit2;<br />

TButton *Button1;<br />

TLabel *Label1;<br />

void _ _fastcall Button1Click (TObject *Sender);<br />

private:<br />

public:<br />

_ _fastcall TForm1 (TComponent* Owner);<br />

};<br />

Закрытые член-данные и член-функции класса должны размещаться<br />

под меткой private: или в <strong>на</strong>чале описания класса без метки, защищенные<br />

члены с<strong>на</strong>бжаются меткой protected:, а открытые члены перечисляются под<br />

меткой public:. Данные, свойства которых представлены в окне Object Inspector,<br />

<strong>на</strong>ходятся под меткой _ _published:. Объекты, порождаемые от закрытых<br />

членов класса, доступны лишь объектам своего класса. Объекты, порождаемые<br />

защищенными чле<strong>на</strong>ми, открыты объектам своего класса и производных<br />

от него классов. Объекты открытых членов могут использоваться во всех<br />

операциях и взаимодействовать с переменными программы.


15<br />

Данные в определении класса объявляются со своими типами и име<strong>на</strong>ми,<br />

а у член-функций указываются, кроме того, типы данных параметров. В<br />

рассматриваемом примере присутствуют указатели <strong>на</strong> данные Edit1, Edit2,<br />

Button1, Label1 классов TEdit, TButton, TLabel. Модификатор быстрого вызова<br />

_ _fastcall организует передачу аргументов методам VCL не через стек, а<br />

через процессорные регистры, если это не вещественные числа, не структуры<br />

данных и не функции. Лексема void является указателем <strong>на</strong> то, что функция<br />

Button1Click не возвращает никаких з<strong>на</strong>чений по окончании работы.<br />

Бестиповая функция TForm1, имя которой совпадает с именем класса,<br />

<strong>на</strong>зывается конструктором (constructor). Конструкторы (а их может быть несколько)<br />

пред<strong>на</strong>з<strong>на</strong>чены для автоматической инициализации данных в ходе<br />

порождения объектов.<br />

Класс формы является производным классом, и в его заголовке через<br />

двоеточие указано имя базового класса TForm с открытым типом доступа<br />

public. Производный класс <strong>на</strong>следует все члены базового класса, за исключением<br />

конструкторов и деструктора. В производном классе у<strong>на</strong>следованные<br />

члены могут иногда изменяться, а также добавляются новые члены.<br />

Одновременно с объявлением класса в файле заголовка задается одноимен<strong>на</strong>я<br />

с формой глобаль<strong>на</strong>я перемен<strong>на</strong>я, позволяющая обращаться к ней из<br />

любого файла проекта, <strong>на</strong>пример:<br />

extern PACKAGE TForm1 *Form1;<br />

Лексема PACKAGE является именем макроса, в котором содержится код экспорта<br />

из библиотеки пакетов BPL – Borland Package Library.<br />

Члены классов, объявленные в файле заголовка, определяются в файле<br />

кода формы в форматах:<br />

[Модификатор] [Тип] имяКласса :: имяЧлен-данного [= З<strong>на</strong>чение];<br />

[Модификатор][Тип] имяКласса :: имяЧлен-функции (списокПараметров)<br />

телоЧлен-функции<br />

В частности, каждой форме в ее файле кода автоматически прописывается<br />

пустой конструктор, получающий при вызове единственный параметр Owner,<br />

указывающий объекта-владельца, создающего форму. Например:<br />

TForm1 *Form1;<br />

_ _fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner) {}


16<br />

При удалении из формы элементов управления их объявления автоматически<br />

удаляются из описания класса формы, хотя объявления членфункций<br />

сохраняются, и при необходимости программисту следует удалить<br />

их вручную. При переименовании объектов переименовываются и все ссылки<br />

<strong>на</strong> них в файле заголовка и в определениях.<br />

Обработка событий<br />

Доступ к функциям форм, обрабатывающим события элементов управления,<br />

может осуществляться через вкладку Events инспектора объектов. Эти<br />

же обработчики можно и непосредственно открывать в окне редактора кода.<br />

По умолчанию C++Builder присваивает обработчику имя, образованное соединением<br />

имени компонента с именем события. Например,<br />

void _ _fastcall TForm1::Button1Click (TObject *Sender) {}<br />

– это обработчик щелчка – события OnClick кнопки Button1 в форме Form1,<br />

являющийся функцией класса TForm1.<br />

Текст обработчика программист <strong>на</strong>полняет выражениями, описывающими<br />

поведение формы или элементов управления, изменяющими их характеристики<br />

или посылающими сообщения. Элементы управления, обрабатываемые<br />

этими выражениями, характеризуются свойствами и методами. В обработчике<br />

свойства и методы связываются стрелками (–>) с указателями <strong>на</strong><br />

элементы управления. Ниже приведены примеры обработчиков трех пунктов<br />

меню и кнопки:<br />

void __fastcall TForm1::menuNewClick (TObject *Sender) {<br />

Edit1–>Text = "2003";<br />

Label1–>Caption = "Санкт-Петербург";<br />

}<br />

void __fastcall TForm1::menuDatabaseClick (TObject *Sender) {<br />

Form2–>Show();<br />

}<br />

void __fastcall TForm1::menuAboutClick (TObject *Sender) {<br />

FormAbout–>ShowModal();<br />

}<br />

void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />

Application–>Terminate();<br />

}


17<br />

Здесь при выборе пункта меню menuNew свойству Текст поля Edit1 присваивается<br />

з<strong>на</strong>чение «2003», а <strong>на</strong>дписи Label1 передается «Санкт-Петербург».<br />

При выборе пункта menuDatabase открывается форма Form2, а при выборе<br />

пункта menuAbout открывается модально форма FormAbout. При <strong>на</strong>жатии<br />

кнопки Button1 приложение завершает свою работу.<br />

В следующем примере показаны обработчики меню открытия и сохранения<br />

файла, обслуживающие элемент RichEdit1 и строку состояния Status-<br />

Bar1:<br />

void _ _fastcall TForm1::menuOpenClick (TObject *Sender) {<br />

OpenDialog1–>Execute()<br />

RichEdit1–>Lines–>LoadFromFile (OpenDialog1–>FileName);<br />

StatusBar1–>Panels–>Items[0]–>Text = OpenDialog1–>FileName;<br />

}<br />

void _ _fastcall TForm1::menuSaveAsClick (TObject *Sender) {<br />

SaveDialog1–>Execute()<br />

RichEdit1–>Lines–>SaveToFile (SaveDialog1–>FileName);<br />

AnsiString fn = SaveDialog1–>FileName;<br />

StatusBar1–>Panels–>Items[0]–>Text = fn;<br />

}<br />

Передача данных между формами, не являющимися модальными, осуществляется<br />

либо через открытые члены их классов, либо через свойства<br />

форм. Например, чтобы внести в <strong>на</strong>дпись Label1 активной формы-владельца,<br />

<strong>на</strong> которую ссылается указатель this, з<strong>на</strong>чение заголовка формы Modeless-<br />

Form, можно воспользоваться следующим выражением:<br />

this–>Label1–>Caption = ModelessForm–>Caption;<br />

Важным достоинством обработчиков является их способность создавать<br />

новые элементы управления в ходе выполнения программы (runtime<br />

mode), а не <strong>на</strong> стадии проектирования (design time mode). Новые объекты порождаются<br />

оператором new, который выделяет необходимое количество свободной<br />

памяти и вызывает конструктор объекта. Объекты, созданные оператором<br />

new, следует удалять из памяти оператором delete, освобождающим<br />

память.<br />

В частности, формам не обязательно выделять память при запуске приложения.<br />

Это можно делать ди<strong>на</strong>мически, по мере необходимости. На стадии<br />

проектирования име<strong>на</strong> ди<strong>на</strong>мических форм <strong>на</strong>до удалить из списка Auto-


18<br />

create forms вкладки Forms (меню Project.Options) или просто вычеркнуть<br />

соответствующие строки из WinMain, а в нужном месте программы создавать<br />

их и открывать не модально методом Show или модально методом ShowModal.<br />

В следующем примере в форме MainForm по щелчку кнопки Button1 создаются<br />

три формы: не модаль<strong>на</strong>я форма ModelessForm, модаль<strong>на</strong>я форма<br />

ModalForm, объявлен<strong>на</strong>я глобальной переменной в файле заголовка, и модаль<strong>на</strong>я<br />

форма LocalForm, не связан<strong>на</strong>я с глобальной переменной:<br />

#include <br />

#include "Unit1.h"<br />

#include "Unit2.h"<br />

#include "Unit3.h"<br />

TForm1 *Form1;<br />

__fastcall TForm1::TForm1 (TComponent* Owner): TForm (Owner) {<br />

ModelessForm–>Show();<br />

ModalForm = new TModalForm (this); // при <strong>на</strong>личии<br />

ModalForm–>ShowModal();<br />

// глобального<br />

delete ModalForm;<br />

// определения<br />

TLocalForm *LocalForm = new TLocalForm (0); // вместо<br />

LocalForm–>ShowModal();<br />

// глобального<br />

delete LocalForm;<br />

// определения<br />

ModelessForm–>Close();<br />

}<br />

В программе использован аргумент this, указывающий <strong>на</strong> владельца<br />

TMainForm, и аргумент 0, не связывающий новую форму с конкретным владельцем.<br />

Еще один фрагмент программы посвящен созданию кнопки:<br />

_ _fastcall TForm1::TForm1 (TComponent* Owner): TForm (Owner) {<br />

TButton *b = new TButton (this);<br />

b–>Parent = this;<br />

b–>Name = “NewButton”;<br />

b–>Width = 100;<br />

b–>Height = 30;<br />

b–>Left = 10;<br />

b–>Top =10;<br />

b–>Caption = “Новая кнопка”;<br />

b–>Visible = true;<br />

b–>OnClick = MyClick;<br />

}<br />

Текст обработчика этой кнопки:


19<br />

void _ _fastcall TForm1::MyClick (TObject *Sender) {<br />

ShowMessage (“Привет”);<br />

}<br />

Программирование графики<br />

Графические объекты строятся <strong>на</strong> канве, являющейся свойством формы<br />

и ряда визуальных компонентов. Табл. 4 посвяще<strong>на</strong> характерным методам<br />

формирования графических образов канвы.<br />

Таблица 4<br />

Методы работы <strong>на</strong> канве<br />

Метод<br />

Наз<strong>на</strong>чение<br />

Arc<br />

Строит дугу внутри заданной области<br />

Chord Строит сектор эллипса по хорде<br />

CopyRect Копирует фрагмент изображения с другой канвы<br />

Draw Создает графический объект с заданными свойствами и коорди<strong>на</strong>тами<br />

Ellipse Строит эллипс в заданных границах<br />

FillRect Строит прямоугольную область с заданным свойством Brush<br />

FloodFill Заполняет пространство канвы заданным свойством Brush<br />

FrameRect Строит прямоугольник с границами, заданными свойством Brush<br />

LineTo Ведет линию из позиции PenPos в точку с коорди<strong>на</strong>тами X–1 и Y–1<br />

MoveTo Переносит позицию рисования в точку с коорди<strong>на</strong>тами X–1 и Y–1<br />

Pie<br />

Строит сектор эллипса внутри заданного прямоугольника<br />

Polygon Строит замкнутый контур из отрезков, проходящих через заданные точки<br />

PolyLine Строит ломаную из отрезков, проходящих через заданные точки<br />

Rectangle Строит прямоугольник через левый верхний и правый нижний углы<br />

RoundRect Строит прямоугольник с закругленными углами<br />

StretchDraw Вписывает изображение в прямоугольник, автоматически масштабируя<br />

TextHeight Возвращает высоту текстовой строки <strong>на</strong> рисунке<br />

TextOut Формирует строку текста из точки с X и Y, перенося в конец ее курсор<br />

TextWidth Возвращает ширину текстовой строки <strong>на</strong> рисунке<br />

TextRect Размещает фрагмент строки в заданной области, обрезая излишки<br />

В следующем примере показан обработчик события формы FormPaint,<br />

рисующий Андреевский флаг:<br />

void _ _fastcall TAboutBox::FormPaint (TObject *Sender) {<br />

Canvas–>Brush–>Color = clBlue;<br />

Canvas–>FillRect (Rect (0,0,ClientWidth, ClientHeight));<br />

Canvas–>Pen–>Color = clWhite;<br />

Canvas–>Pen–>Width = 20;<br />

Canvas–>MoveTo (0,0);<br />

Canvas–>LineTo (ClientWidth, ClientHeight);<br />

Canvas–>MoveTo (0,ClientHeight);


Canvas–>LineTo (ClientWidth, 0);<br />

}<br />

20<br />

А<strong>на</strong>логичную задачу можно решить в элементе управления PaintBox по событию<br />

PainBoxPaint.<br />

Во втором примере обработчик события, связанного с <strong>на</strong>жатием кнопки,<br />

формирует диалог для вставки объекта OLE:<br />

void _ _fastcall TModalForm::Button1Click (TObject *Sender) {<br />

OleContainer1–>InsertObjectDialog();<br />

}<br />

Третий пример демонстрирует использование компонента Clipboard<br />

для копирования содержимого одного объекта Image в другой:<br />

#include <br />

void _ _fastcall TForm1::BitBtn1Click (TObject *Sender) {<br />

Image1–>Picture–>LoadFromFile ("d:/val/scan.bmp");<br />

Clipboard()–>Assign (Image1–>Picture);<br />

Image2–>Picture–>Bitmap–>Assign (Clipboard ());<br />

}<br />

Компонент Image способен самостоятельно обновляться. Он является<br />

удобным средством ускоренного отображения графики в форме: достаточно<br />

создать скрытый вариант Image и копировать его в канву формы по событию<br />

OnPaint:<br />

void _ _fastcall TForm1::FormPaint (TObject *Sender) {<br />

this–>Canvas–>CopyRect (ClientRect,<br />

Image1–>Canvas, Image1–>ClientRect);<br />

}<br />

Исключения<br />

C++Builder поддерживает стандартный механизм обработки исключений<br />

(exception), то есть ошибок, которые могут возникнуть при выполнении<br />

программы (<strong>на</strong>пример, при делении <strong>на</strong> нуль или <strong>на</strong>рушении обслуживания<br />

потоков). При встрече с ненормальной ситуацией он может передать управление<br />

другой части программы, продолжить ее выполнение либо завершить<br />

работу. Обычно определяют обработчик исключения (exception handler), выполняющий<br />

необходимые действия перед завершением программы. Блоки<br />

кода, генерирующие исключения, <strong>на</strong>чи<strong>на</strong>ются словом try и заключаются в


21<br />

фигурные скобки. В случае об<strong>на</strong>ружения ошибки происходит программное<br />

прерывание. При этом управление передается блоку обработчика исключения<br />

catch. В скобках указывают спецификацию класса конкретного исключения,<br />

а для перехвата всех ошибок – многоточие (…). Не менее одного такого<br />

блока следует за блоком try. Для каждой исключительной ситуации определен<br />

свой класс. Некоторые классы исключений приведены в табл. 5.<br />

Таблица 5<br />

Класс<br />

EAccessViolation<br />

EConvertError<br />

EDatabaseError<br />

EDivByZero<br />

EInOutError<br />

EPrinterError<br />

EStackOverflow<br />

EZeroDivide<br />

EOverflow<br />

Некоторые классы исключений<br />

Описание<br />

Ошибка доступа в память<br />

Ошибка преобразования данных<br />

Ошибка доступа к базе данных<br />

Ошибка деления <strong>на</strong> нуль целого числа<br />

Ошибка файлового обме<strong>на</strong><br />

Ошибка печати<br />

Переполнение стека<br />

Ошибка деления <strong>на</strong> нуль вещественного числа<br />

Переполнение регистров вещественных чисел<br />

В приведенном ранее тексте файла проекта в блок try функции WinMain<br />

помещены выражения инициализации объектов OLE Automation, создания<br />

главной формы и запуска приложения. В блоке catch в случае исключительной<br />

ситуации выводится сообщение с указанием ее причины.<br />

Простой пример обработки исключений показывает, как при возникновении<br />

любой ошибочной ситуации, <strong>на</strong>пример при отсутствии текста в поле,<br />

можно вывести сообщение:<br />

void _ _fastcall TForm1::Button2Click (TObject *Sender) {<br />

try {<br />

Label1–>Caption = Edit1–>Text ; }<br />

catch (...) { ShowMessage ("Ошибка"); }<br />

}<br />

Другой пример обработки исключительной ситуации связан с ошибкой ввода<br />

числовых данных:<br />

try { Roubles–>Text = FloatToStrF (floor (StrToFloat (EditDollars–>Text) *<br />

StrToFloat (EditRate–>Text) + 0.5), ffFixed, 10, 2);<br />

}<br />

catch (const EConvertError &) {<br />

LabelError–>Caption = “Неверный формат числа”;<br />

}


catch (const EOverflow &) {<br />

LabelError–>Caption = “Невер<strong>на</strong>я величи<strong>на</strong> числа”;<br />

}<br />

22<br />

В блоке try можно использовать лексему throw для вызова исключительной<br />

ситуации или для указания типа исключения, которое вызывает<br />

функция. Вслед за блоком try вместо catch часто располагают блок _ _finally,<br />

выполняющийся вне зависимости от <strong>на</strong>личия исключительной ситуации.<br />

Простые типы данных и операторы<br />

Типы данных C++Builder ориентированы <strong>на</strong> 32-разрядную платформу<br />

(табл. 6).<br />

Таблица 6<br />

Типы данных C++Builder<br />

Тип данных Байт З<strong>на</strong>чения Название<br />

char 1 ±128 Байт<br />

BYTE 1 0…255 Байт без з<strong>на</strong>ка<br />

short 2 ±32767 Короткий целый<br />

unsigned short, WCHAR 2 0…65534 Короткий целый без з<strong>на</strong>ка<br />

int, long 4 ±2147483648 Целый<br />

unsigned long 4 0…4294967295 Целый без з<strong>на</strong>ка<br />

__int64 8 9,2E±18 Длинный целый<br />

bool 1 true, false Логический<br />

float 4 1,2E±38 Вещественный<br />

double 8 2,2E±308 Двойной вещественный<br />

long double 10 3,3E±4932 Длинный двойной вещественный<br />

void * 8 2,2E±308 Указатель<br />

char * – – Строковый с нулем в конце<br />

void – – Пустой<br />

TDateTime 8 – Дата и время<br />

Currency 8 – Валюта, 4 цифры после запятой<br />

Variant 16 – Универсальный (int, float, char, bool)<br />

AnsiString – – Ди<strong>на</strong>мический строковый<br />

WideString – – Строковый, 2 байта <strong>на</strong> символ<br />

Первая октава образует категорию порядковых типов данных (ordered<br />

data type). Пять последних типов являются типизированными классами (typed<br />

class) C++Builder. Из большого многообразия типов данных рекомендуется в<br />

первую очередь использовать int и __int64, double, bool, AnsiString и void. Некоторые<br />

(фундаментальные) типы введены для совместимости с языком C:<br />

char, short, long, float.


23<br />

Типы указываются во всех объявлениях переменных и констант. Объявления<br />

переменных могут совмещаться с их инициализацией:<br />

[Модификатор] [Тип] имяПеременной [=З<strong>на</strong>чение];<br />

а во множестве различных объявлений констант инициализация обязатель<strong>на</strong>:<br />

const Тип имяКонстанты = З<strong>на</strong>чение;<br />

enum [Тип] { списокЗ<strong>на</strong>чений };<br />

enum { списокЗ<strong>на</strong>чений } имяТипа;<br />

#define имяКонстанты З<strong>на</strong>чение<br />

Последний вариант объявления констант введен для совместимости с C.<br />

Типы переменных, входящих в параметры функций, указываются в<br />

круглых скобках вслед за именем функции. Если параметр отсутствует, скобки<br />

остаются пустыми. Тип возвращаемого функцией з<strong>на</strong>чения указывается<br />

при ее объявлении и определении перед именем. Функция, не возвращающая<br />

з<strong>на</strong>чений, объявляется как функция типа void. Если у функции или данных не<br />

объявлен тип, они получают тип int.<br />

В соответствии с типами, различают символьные, строковые, целые и<br />

вещественные константы. Первые заключаются в апострофы (<strong>на</strong>пример, ‘e’<br />

или ‘1’), вторые – в кавычки («привет»). Целые бывают десятичными (<strong>на</strong>пример<br />

56), восьмеричными (056) и шест<strong>на</strong>дцатеричными (0хАС), а вещественные<br />

– десятичными (6.23) и экспоненциальными (5Е-12). Специальную<br />

группу символьных констант образуют управляющие последовательности<br />

(табл. 7).<br />

Таблица 7<br />

Управляющие последовательности<br />

'\0' Ноль '\t' Горизонталь<strong>на</strong>я табуляция<br />

'\a' Сиг<strong>на</strong>л '\v' Вертикаль<strong>на</strong>я табуляция<br />

'\b' Возврат <strong>на</strong> символ '\"' Кавычки<br />

'\f' Новая страница '\'' Апостроф<br />

'\n' Новая строка '\\' Обрат<strong>на</strong>я косая черта<br />

'\r' Начало строки – –<br />

Данные, которыми манипулирует программа, хранятся в ячейках памяти<br />

компьютера, и именованные ячейки памяти мы <strong>на</strong>звали переменными и<br />

константами. Hо память пред<strong>на</strong>з<strong>на</strong>че<strong>на</strong> не только для хранения з<strong>на</strong>чений, обрабатываемых<br />

в ходе выполнения программы. Часть ячеек памяти выделяется<br />

для указания <strong>на</strong> другие области, и эту часть <strong>на</strong>зывают указателями и ссыл-


24<br />

ками. «Введение связей с другими элементами данных через указатели –<br />

чрезвычайно важ<strong>на</strong>я идея в программировании; это ключ к представлению<br />

сложных структур» [6].<br />

Указатель (pointer) – это специальный тип данных, описывающий не<br />

з<strong>на</strong>чение, а размещение переменной, константы или возвращаемого з<strong>на</strong>чения<br />

функции, «одно из хитроумных понятий C» [9]. Его объявляют как Тип<br />

*имяУказателя. Оператор разыменования * (dereference operator) служит<br />

средством доступа к данным, а имяУказателя может быть другим указателем<br />

или адресом конкретной области памяти. В последнем случае указатель <strong>на</strong>зывают<br />

ссылкой (reference) и объявляют как Тип &имяСсылки. & – это оператор<br />

взятия адреса, а имяСсылки – з<strong>на</strong>чение хранящихся по этому адресу данных.<br />

Между операторами * и & установлено следующее соотношение: если<br />

имяУказателя – адpес данных, то *имяУказателя – их з<strong>на</strong>чение (т.е. указатель);<br />

если имяСсылки – з<strong>на</strong>чение данных, то &имяСсылки – их адpес (т.е.<br />

ссылка).<br />

Од<strong>на</strong>жды инициализировав ссылку, ей нельзя присвоить другое з<strong>на</strong>чение.<br />

И, в отличие от указателей, которые могут быть объявлены без инициализации<br />

или установлены в нуль, ссылки всегда указывают <strong>на</strong> конкретные<br />

данные.<br />

Использование указателей и ссылок играет важную роль в работе с<br />

функциями.<br />

Обычно аргументы, передаваемые в функцию при ее вызове, обрабатываются<br />

функцией как копии данных, и потому сами данные не меняются в<br />

ходе выполнения функции. В вызывающем модуле функция может изменить<br />

только одну переменную – ту, которую о<strong>на</strong> возвращает как результат своей<br />

работы. Если же требуется, чтобы функция изменяла более одной переменной,<br />

ей в качестве аргументов следует передавать не з<strong>на</strong>чения, а ссылки <strong>на</strong><br />

данные, и в первую очередь – адpеса переменных.<br />

А<strong>на</strong>логичное правило действует и при передаче функции в другую<br />

функцию в качестве аргумента.<br />

Для выполнения операций <strong>на</strong>д типизированными данными в C++ используется<br />

более 50 простых операторов. В табл. 8 они расположены по убыванию<br />

приоритета.


25<br />

Простые операторы C++<br />

Таблица 8<br />

Приоритет<br />

к данным тические ские<br />

Доступ Арифме-<br />

Логиче-<br />

Категория<br />

Двоичные<br />

10 Первичные<br />

() [] {} /* */ // ‘’<br />

“” :: ; # -> .<br />

Нет Нет Нет<br />

9 У<strong>на</strong>рные<br />

& * – + (тип)<br />

new delete sizeof<br />

++ –– ! ~<br />

8 Мультипликативные Нет * / % Нет >> =<br />

Нет<br />

== !=<br />

5 И Нет Нет && &<br />

4 Исключающее ИЛИ Нет Нет ^<br />

3 ИЛИ Нет Нет || |<br />

2 Условие Нет Нет : Нет<br />

1 Присваивание =<br />

*= /= %=<br />

=<br />

Нет<br />

+= –=<br />

&= |= ^=<br />

В круглые скобки заключаются списки аргументов, в квадратные – индексы<br />

массивов, в фигурные – блоки данных, в косые – комментарии. Кроме<br />

того, в круглые скобки заключают временно заменяемый тип данных. Кавычками<br />

обрамляются константы. Двойное двоеточие (::) образует оператор<br />

области видимости данных. Точка с запятой (;) завершает выражения. З<strong>на</strong>к<br />

номера (#) <strong>на</strong>чи<strong>на</strong>ет директивы препроцессора. Стрелка ( –> ) и точка (.) отделяют<br />

свойства и методы классов. Амперсанд (&) и звездочка (*) помечают<br />

адреса, ссылки и указатели. Оператором new создаются, а оператором delete<br />

удаляются объекты.<br />

У<strong>на</strong>рные операторы обрабатывают один операнд, а би<strong>на</strong>рные мультипликативные,<br />

аддитивные и т. п. – два операнда. В частности, у<strong>на</strong>рные операторы<br />

сложения и вычитания (+, –) меняют з<strong>на</strong>ки операндов. Оператор sizeof<br />

определяет размер данных. Операторы автоинкремента (++) и автодекремента<br />

(– –) могут действовать до операции или после ее. Так, действие j = i++<br />

а<strong>на</strong>логично выражениям j = i; i = i+1. А выражение j = – –i можно представить<br />

как i = i–1; j = i. То же можно сказать и о таких операторах, как *=, /= и им<br />

подобным. Восклицательный з<strong>на</strong>к (!) служит для отрицания, а тильда (~) –<br />

для инверсии.<br />

Кроме того, звездочка (*) является з<strong>на</strong>ком умножения, косая черта (/) –<br />

деления, а процент (%) – остатка от деления <strong>на</strong>цело. Двойными стрелками


26<br />

(>>,


Метод<br />

IsDelimiter (AnsiString& delimiters,<br />

int index)<br />

IsEmpty()<br />

LastDelimiter (AnsiString& delimit)<br />

Length()<br />

LoadStr (int ident)<br />

operator != (AnsiString& rhs)<br />

operator < (AnsiString& rhs)<br />

operator (AnsiString& rhs)<br />

operator >= (AnsiString& rhs)<br />

operator == (AnsiString& rhs)<br />

operator [] (int idx)<br />

operator = (AnsiString& rhs)<br />

operator + (AnsiString& rhs)<br />

operator += (AnsiString& rhs)<br />

Pos (AnsiString& subStr)<br />

printf (char* format, ...)<br />

sprintf (char* format, ...)<br />

SetLength (int newLength)<br />

StringOfChar (char ch, int count)<br />

SubString (int index, int count)<br />

ToDouble (), ToInt ()<br />

ToIntDef (int defaultVal)<br />

Trim (),TrimLeft (),TrimRight ()<br />

UpperCase ()<br />

27<br />

Окончание табл. 9<br />

Наз<strong>на</strong>чение<br />

Подтверждает <strong>на</strong>личие определенного символа (delimiter)<br />

в заданной позиции (index)<br />

Подтверждает, что строка пуста<br />

Последняя позиция в строке определенного символа<br />

(delimiter)<br />

Дли<strong>на</strong> строки в байтах<br />

Загружает строку из файла<br />

Сравнение с другой строкой rhs<br />

Символ под указанным индексом idx<br />

Инициализация строки другой строкой rhs<br />

Соединение с другой строкой rhs<br />

Позиция <strong>на</strong>чала подстроки subStr в строке<br />

Задают формат строки в стандарте языка С, возвращая<br />

длину или ссылку <strong>на</strong> строку<br />

Задает длину (newLength) строки<br />

Строка заданного числа (count) символов (ch)<br />

Подстрока длиной count в позиции index<br />

Преобразуют строку в число, в т.ч. – в число по<br />

умолчанию в случае ошибки преобразования<br />

Удаляют пробелы<br />

Преобразует в прописные буквы<br />

Отдельные символы строк класса AnsiString индексируются, <strong>на</strong>чи<strong>на</strong>я с 1.<br />

В следующем примере приведен текст конструктора формы Form1, в<br />

котором вычисляется округленное з<strong>на</strong>чение гипотенузы и результат <strong>на</strong>правляется<br />

в <strong>на</strong>дпись Label1. Затем вычисляется средняя дли<strong>на</strong> двух окружностей,<br />

и округленный результат <strong>на</strong>правляется в <strong>на</strong>дпись Label2. Тексты <strong>на</strong>дписей<br />

имеют тип данных AnsiString, поэтому используются функции преобразования<br />

типов FloatToStr и FloatToStrF.<br />

#include <br />

#include <br />

#pragma hdrstop<br />

#pragma resource "*.dfm"<br />

TForm1 *Form1;<br />

__fastcall TForm1::TForm1 (TComponent* Owner) : TForm(Owner) {<br />

double a = 5, b = 7;


Form1–>Label1–>Caption = FloatToStr (floor (hypot (a, b) + 0.5));<br />

double R1 = 5, R2 = 7;<br />

Form1–>Label2–>Caption = FloatToStrF (M_PI * (R1 + R2), ffFixed, 10, 2);<br />

}<br />

28<br />

В примере задействова<strong>на</strong> математическая библиотека Borland C , подключаемая<br />

файлом заголовка . Функции этой библиотеки описаны в<br />

табл. 10:<br />

Таблица 10<br />

Функции<br />

abs, fabs<br />

acos, asin, atan<br />

cos, sin, tan<br />

exp<br />

floor<br />

hypot<br />

pow, pow10<br />

seal<br />

sqrt<br />

Математические функции Borland C<br />

Наз<strong>на</strong>чение<br />

Абсолютное з<strong>на</strong>чение<br />

Обратные тригонометрические функции<br />

Тригонометрические функции<br />

Экспонента<br />

Ближайшее меньшее целое<br />

Гипотенуза<br />

Степень<br />

Ближайшее большее целое<br />

Квадратный корень<br />

Второй пример посвящен использованию типа AnsiString, а также типов<br />

TDateTime, Currency и Variant.<br />

void __fastcall TForm1::vAnsiStringClick (TObject *Sender) {<br />

AnsiString as = Edit0–>Text, as1 = as;<br />

Label0–>Caption = as.StringOfChar ('>', as.Length ())<br />

+ as.ToIntDef (0) + as.StringOfChar ('


29<br />

Variant v = Edit3–>Text;<br />

Label3–>Caption = (v.Type () == varString) v : BoolToStr (v.operator bool ());<br />

}<br />

В третьем примере да<strong>на</strong> форма с полями ввода Edit1 и Edit2, кнопкой<br />

Button1 и <strong>на</strong>дписью Label1:<br />

class TForm1 : public TForm {<br />

_ _published:<br />

TEdit *Edit1;<br />

TEdit *Edit2;<br />

TLabel *Label1;<br />

TButton *Button1;<br />

void _ _fastcall Button1Click (TObject *Sender);<br />

public:<br />

AnsiString CalcF (double, double);<br />

AnsiString CalcSF (AnsiString, double);<br />

AnsiString CalcS (AnsiString, AnsiString);<br />

};<br />

При <strong>на</strong>жатии кнопки данные из полей формы преобразуются к нужному<br />

формату, обрабатываются в соответствии с условием конкретной задачи, и<br />

результат возвращается в <strong>на</strong>дпись:<br />

void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />

AnsiString c = "";<br />

try {<br />

c = CalcF (StrToFloat (Form1–>Edit1–>Text),<br />

StrToFloat (Form1–>Edit2–>Text));<br />

} catch (Exception &EConvertError) {<br />

try {<br />

c = CalcSF (Form1–>Edit1–>Text, StrToFloat(Form1–>Edit2–>Text));<br />

} catch (Exception &EConvertError) {<br />

try {<br />

c = CalcS (Form1–>Edit1–>Text, Form1–>Edit2–>Text);<br />

} catch (...) { c = "Ошибка данных"; }<br />

}<br />

}<br />

Form1–>Label1–>Caption = c;<br />

}<br />

Далее следуют обработчики кнопки Button1, которые выводят в <strong>на</strong>дпись<br />

Label1 решение трех задач:


AnsiString TForm1::CalcF (double a, double b) { return a / b; }<br />

AnsiString TForm1::CalcSF (AnsiString a, double b) {<br />

return a + FloatToStr (++b);<br />

}<br />

AnsiString TForm1::CalcS (AnsiString a, AnsiString b) {<br />

return “Имя: “ + a + “\nФамилия: “ + b;<br />

}<br />

30<br />

Еще один пример посвящен передачи данных из модальной формы в<br />

обычную. Необходимые данные должны быть получены до того, как модаль<strong>на</strong>я<br />

форма будет уничтоже<strong>на</strong>. Это удобно делать, в частности, через дополнительный<br />

параметр конструктора. В рассматриваемом примере таким параметром<br />

является указатель *arg, который инициализируется в конструкторе.<br />

При <strong>на</strong>жатии кнопки Button1 запомненный по указанному адресу заголовок<br />

формы переносится в <strong>на</strong>дпись Label1 не модальной формы:<br />

class TLocalForm : public TForm {<br />

__published: TLabel *Label1;<br />

public:<br />

__fastcall TLocalForm (TComponent* Owner);<br />

__fastcall TLocalForm (String *arg, TComponent* Owner);<br />

};<br />

__fastcall TLocalForm::TLocalForm (String *arg, TComponent*Owner):<br />

TForm (Owner) { *arg = this–>Caption; }<br />

void __fastcall TForm1::Button1Click (TObject *Sender) {<br />

String s;<br />

TLocalForm *LocalForm = new TLocalForm (&s, 0);<br />

LocalForm–>ShowModal(); delete LocalForm;<br />

this–>Label1–>Caption = s;<br />

}<br />

Контрольные вопросы<br />

1. В чем заключается разница между объявлением, определением и вызовом<br />

функций<br />

2. Как обмениваются данными функции<br />

3. В чем состоит разница между локальными и глобальными данными<br />

4. В чем разница между параметром функции и ее аргументом<br />

5. Существует ли разница в терми<strong>на</strong>х функция, член-функция и метод


31<br />

6. Как передаются данные при вызове функции и при возврате результата в<br />

вызывающую функцию.<br />

7. В чем разница между переменными и константами<br />

8. Для чего члены класса делятся <strong>на</strong> закрытые и открытые<br />

9. В чем заключается разница между классом формы и объектом формы<br />

10. В чем разница между визуальным компонентом и элементом управления<br />

11. Для чего служит файл заголовка<br />

12. Для чего пред<strong>на</strong>з<strong>на</strong>чен конструктор формы<br />

13. Для чего служат и как используются события<br />

14. Чем обработчик события отличается от других функций<br />

15. Как формы обмениваются данными между собой<br />

16. Для чего служат операторы New и delete<br />

17. Каковы главные особенности графических объектов<br />

18. Какую роль играет событие FormPaint при работе с графическими объектами<br />

19. Какую роль играют исключения и их обработчики<br />

20. Как выполняется отладка программ в режиме обработки исключений<br />

21. Нужно ли указывать типы данных при объявлении и инициализации переменных<br />

22. Чем различаются типы данных char, char* и AnsiString<br />

23. Чем различаются типы данных int, float и double<br />

24. Для чего функция имеет тип<br />

25. Что такое указатель<br />

26. Для чего используются параметры-указатели<br />

27. Для чего служат операторы доступа к данным<br />

28. Для чего служат логические операторы<br />

29. Для чего служат двоичные операторы<br />

30. Чем различаются операторы || и |, && и &<br />

31. Можно ли применять операторы отношения к данным типов char, char* и<br />

AnsiString<br />

32. Можно ли применять оператор присваивания к разнотипным данным<br />

33. Для чего служат и как используются методы класса AnsiString<br />

34. Для чего операторы ранжируются по приоритетам<br />

35. Что такое типизированные классы


32<br />

Составные операторы<br />

Длин<strong>на</strong> та дорога, где нет поворота<br />

Английская пословица<br />

Использование составных операторов – первый шаг к структурному<br />

программированию. До сих пор <strong>на</strong>ши программы были линейными, они реализовали<br />

простейший алгоритм следования. Линейные задачи самые простые<br />

и кажутся более понятными, как более понятной бывает любая упрощен<strong>на</strong>я<br />

модель.<br />

Ветвящиеся алгоритмы <strong>на</strong>много сложнее, но ближе к действительности,<br />

ибо «каждая точка <strong>на</strong>шей жизни есть точка выбора» (А. Даниэль). Такие<br />

алгоритмы реализуются с использованием составных операторов перехода,<br />

условия, выбора, цикла и повторения. Ветвления в программах часто оказываются<br />

опасными и скользкими. Только практика позволяет освоить технику<br />

выбора необходимых управляющих структур, грамотного выхода из составных<br />

операторов и обработки ошибочных ситуаций.<br />

Операторы перехода<br />

Ветвящиеся и циклические алгоритмы управления информационными<br />

процессами реализуются с помощью составных операторов. Такие операторы<br />

часто входят друг в друга, образуя управляющих структуры.<br />

Традиционным средством организации нелинейных процессов являются<br />

операторы перехода, служащие для изменения хода вычислительных процессов.<br />

К таким операторам относятся: break, continue, return, goto Метка.<br />

Первый из операторов перехода прекращает выполнение самого внутреннего<br />

из составных операторов, передавая управление следующему за прерываемым<br />

оператором. Второй прерывает выполнение составного оператора,<br />

передавая управление в его конец. Третий завершает выполнение функции.<br />

Оператор goto передает управление <strong>на</strong> метку внутри текущей функции,<br />

состоящую из имени и двоеточия. Применение его не поощряется в современном<br />

программировании, так как <strong>на</strong>рушает принцип процедурного подхода.<br />

«Goto - неизбежное зло, ибо программы с безусловными переходами<br />

трудно понять» [3]; «любая программа может обойтись без оператора goto»<br />

[5].


Условный оператор<br />

33<br />

С помощью условного оператора ход вычислительного процесса организуется<br />

в зависимости от з<strong>на</strong>чения логического выражения:<br />

if (истинноеЛогическоеВыражение) Выражение…<br />

[else Выражение… ]<br />

Логическое выражение строится <strong>на</strong> базе логических операторов и переменных.<br />

Пример:<br />

void Ifs1 () {<br />

double x, y, r;<br />

x=StrToFloat (MyForm–>Edit1–>Text);<br />

y=StrToFloat (MyForm–>Edit2–>Text);<br />

r=StrToFloat (MyForm–>Edit3–>Text);<br />

if (x * x + y * y < r * r) MyForm–>Label1–>Caption = "Точка внутри круга";<br />

else MyForm–>Label1–>Caption = "Точка вне круга";<br />

}<br />

Другой пример посвящен сравнению двух слов:<br />

void Ifs2 () {<br />

AnsiString a, b;<br />

a= Edit1–>Text;<br />

b= Edit2–>Text;<br />

if (a > b) Label1–>Caption = "a”; else Label1–>Caption = "b”<br />

}<br />

Следующий пример демонстрирует, как можно с помощью условного<br />

оператора организовать построение произвольных линий, подобно тому, как<br />

это делается в редакторе Paint. Рисование <strong>на</strong>чи<strong>на</strong>ется в момент <strong>на</strong>жатия<br />

кнопки мыши и заканчивается в момент ее отпускания. Флаг <strong>на</strong>жатия drawing<br />

должен быть предварительно внесен в список открытых членов класса формы.<br />

void __fastcall TForm1::FormMouseDown (TObject *Sender,<br />

TMouseButton Button, TShiftState Shift, int X, int Y) {<br />

drawing=true;<br />

Canvas–>TextOut (X, Y, "Старт!"); Canvas–>MoveTo (X, Y);<br />

}<br />

void __fastcall TForm1::FormMouseUp (TObject *Sender,<br />

TMouseButton Button, TShiftState Shift, int X, int Y) {<br />

drawing = false; Canvas–>TextOut (X, Y, "Стоп!");<br />

}


void _ _fastcall TForm1::FormMouseMove (TObject *Sender,<br />

TShiftState Shift, int X, int Y) {<br />

if (drawing) Canvas–>LineTo (X, Y);<br />

}<br />

34<br />

Еще од<strong>на</strong> функция показывает работу методов Delete (удаление букв) и<br />

Length (дли<strong>на</strong> строки) класса AnsiString в операции сцепления первой буквы<br />

слова a, точки и слова b:<br />

void Ifs3 () {<br />

AnsiString a = MyForm–>Edit1–>Text;<br />

AnsiString b = MyForm–>Edit2–>Text;<br />

AnsiString c = a.Delete (2, a.Length () – 1);<br />

if (a > b) MyForm–>Label1–>Caption = c + '.' + b + '\n' + a;<br />

else MyForm–>Label1–>Caption = a + '\n' + c + '.' + b;<br />

}<br />

Составные операторы могут иметь любую глубину вложенности. Но<br />

«вложенный <strong>на</strong>бор операторов if...else иногда <strong>на</strong>поми<strong>на</strong>ет водопровод старого<br />

дома: система работает, но трудно понять, какая труба куда ведет» [9].<br />

Оператор выбора<br />

Оператор выбора служит для организации хода вычислительного процесса<br />

в зависимости от з<strong>на</strong>чения выражения или переменной:<br />

switch (порядковаяПеремен<strong>на</strong>я)<br />

{ case Константа : Выражение…<br />

...<br />

[default : Выражение…] }<br />

После выполнения каждого выражения действие передается следующему<br />

выражению. Для прерывания этой последовательности обработку каждой<br />

константы в операторе выбора завершают оператором break. Выражения,<br />

открываемые лексемой default, рассчитаны <strong>на</strong> обработку незапланированных<br />

з<strong>на</strong>чений логического выражения. Пример:<br />

void Cases () {<br />

AnsiString day;<br />

int n = StrToInt (MyForm–>Edit1–>Text);<br />

switch (n) {<br />

case 1: day = "Понедельник"; break;<br />

case 2: day = "Вторник"; break;<br />

case 3: day = "Среда"; break;


}<br />

35<br />

case 4: day = "Четверг"; break;<br />

case 5: day = "Пятница"; break;<br />

case 6: day = "Суббота"; break;<br />

case 7: day = "Воскресенье"; break;<br />

default: day = "Ошибка ввода";<br />

} MyForm–>Label1–>Caption = day;<br />

Следующий пример посвящен работе с формой, в классе которой есть<br />

параметр. Для передачи форме з<strong>на</strong>чения параметра newArg класс формы дополнен<br />

вторым конструктором:<br />

class TLocalForm : public TForm {<br />

__published: TLabel *Label1;<br />

public:<br />

__fastcall TLocalForm (TComponent* Owner);<br />

__fastcall TLocalForm (int newArg, TComponent* Owner);<br />

};<br />

__fastcall TLocalForm::TLocalForm (int newArg, TComponent* Owner):<br />

TForm (Owner) {<br />

switch (newArg) {<br />

case 1: Label1–>Caption = "Обыч<strong>на</strong>я форма"; break;<br />

case 2: Label1–>Caption = "Форма с параметром"; break;<br />

}<br />

}<br />

Оператор цикла<br />

Оператор цикла организует повторяющийся вычислительный процесс в<br />

заданном диапазоне изменения переменной-счетчика цикла:<br />

for ([ выражениеИнициализации ];<br />

[ выражениеУсловия ];<br />

[ выражениеИтерации ]) Выражение…<br />

Выражения цикла, условия и итерации строятся с использованием переменной-счетчика<br />

цикла, который относится к одному из порядковых типов.<br />

Выражение итерации может быть как увеличивающимся, так и уменьшающимся,<br />

<strong>на</strong>пример, a++, b–=2 или c– –. По завершении цикла з<strong>на</strong>чение переменной-счетчика<br />

остается неопределенным.<br />

Далее приведен простой пример с оператором цикла – вывод алфавита<br />

в заголовок формы:


void Fors1 () {<br />

char ch;<br />

// ch – перемен<strong>на</strong>я-счетчик цикла<br />

AnsiString alphabet;<br />

for (ch='A'; chLabel1–>Caption = alphabet;<br />

}<br />

36<br />

Во втором примере для извлечения символов в операциях сравнения<br />

строк и формирования строки символов алфавита служит метод operator []<br />

класса AnsiString. Этот метод возвращает символ строки, соответствующий<br />

указанному в качестве аргумента порядковому номеру.<br />

void Fors2 () {<br />

AnsiString chars="", aa, bb;<br />

aa = MyForm–>Edit2–>Text;<br />

bb = MyForm–>Edit3–>Text;<br />

char a=aa.operator [] (1);<br />

char b=bb.operator [] (1);<br />

for (char k=a; kLabel1–>Caption = chars;<br />

}<br />

Для досрочного завершения цикла в его тело включают операторы перехода.<br />

При отсутствии выражения условия или итерации образуются «вечные<br />

циклы», <strong>на</strong>пример for (;;).<br />

Операторы повторения<br />

Операторы повторения пред<strong>на</strong>з<strong>на</strong>чены для организации вычислительных<br />

процессов, повторяющихся заранее неизвестное число раз. Различают<br />

операторы повторения с предусловием и с постусловием:<br />

while (истинноеЛогическоеВыражение ) Выражение…<br />

do Выражение… while (истинноеЛогическоеВыражение );<br />

З<strong>на</strong>чение логического выражения обычно меняется в теле оператора в<br />

ходе выполнения выражений. В противном случае образуются «вечные циклы»<br />

(endless repetition) типа while (1) или do while (!0). До <strong>на</strong>чала работы оператора<br />

с предусловием должно быть сформировано истинное логическое выражение.<br />

Для досрочного завершения выполнения операторов повторения в<br />

выражениях размещаются операторы перехода. Примеры:


37<br />

void Whiles1 () {<br />

int temp = 10, count = 1;<br />

AnsiString result = MyForm–>Label1–>Caption;<br />

while (2 * temp > 3 * count++) result = result + IntToStr (temp––) + ' '; // 10 9 8 7<br />

MyForm–>Label1–>Caption = result;<br />

}<br />

Оператор повторения с постусловием относится к числу <strong>на</strong>именее <strong>на</strong>дежных<br />

управляющих структур, так как условие его завершения проверяется<br />

только после <strong>на</strong>чала работы.<br />

void Whiles2 () {<br />

int x = 9;<br />

do<br />

MyForm–>Label1–>Caption = MyForm–>Label1–>Caption +<br />

IntToStr (x––) + " в квадрате равно " + IntToStr (x * x) + '\n';<br />

while (x);<br />

}<br />

А вот пример «вечного цикла», выход из которого возможен только с<br />

помощью оператора перехода:<br />

void Whiles3 () {<br />

int leapYear, year;<br />

TLabel *lb = MyForm–>Label1;<br />

try { year = StrToInt (MyForm–>Edit1–>Text);<br />

} catch (...) {<br />

lb–>Caption = "Вводите годы целыми числами"; return;<br />

}<br />

while (1) {<br />

if (year > 0) {<br />

if ((year % 100) == 0) leapYear = ((year % 400) == 0);<br />

else leapYear = ((year % 4) == 0);<br />

if (leapYear) lb –>Caption = " – високосный";<br />

else lb –>Caption = " – не високосный";<br />

break;<br />

}<br />

else { lb –>Caption = " – неверный";<br />

break;<br />

}<br />

} lb –>Caption = IntToStr (year) + lb –>Caption + " год";<br />

}


Программирование диалогов<br />

38<br />

Составные операторы помогают организовать диалог с пользователем с<br />

помощью специально пред<strong>на</strong>з<strong>на</strong>ченных для этого функций (табл. 11), что<br />

особенно удобно <strong>на</strong> этапе подготовки и отладки приложений.<br />

Таблица 11<br />

Функции сообщений<br />

Функция<br />

int MessageDlg (AnsiString text, TMsgDlgType type,<br />

TMsgDlgButtons buttons, int helpContext)<br />

int MessageDlgPos (AnsiString text, TMsgDlgType type,<br />

TMsgDlgButtons buttons, int helpContext, int X, int Y)<br />

void ShowMessage (AnsiString text)<br />

bool SelectDirectory (AnsiString &dir, TSelectDirOpts options,<br />

int helpContext)<br />

Наз<strong>на</strong>чение<br />

Сообщение в центре экра<strong>на</strong> с<br />

возвратом результата<br />

Сообщение в позиции X,Y с<br />

возвратом результата<br />

Сообщение с кнопкой ОК<br />

Окно диалога для выбора<br />

папки<br />

Большинство функций диалога возвращает определенное з<strong>на</strong>чение, которое<br />

можно обрабатывать в составных операторах.<br />

Ниже дан пример обработчика, в котором <strong>на</strong>жатие кнопки открывает<br />

возможность выбора или создания новой папки, после чего ее адрес отображается<br />

в <strong>на</strong>дписи.<br />

#include <br />

#include <br />

#include <br />

#pragma hdrstop<br />

#pragma resource "*.dfm"<br />

TForm1 *Form1;<br />

void __fastcall TForm1::Button1Click (TObject *Sender) {<br />

if (MessageDlg ("Создать/выбрать папку", mtConfirmation,<br />

TmsgDlgButtons ()


39<br />

В следующем примере последовательность окон сообщений информирует<br />

о владельце, объектах владения и дочерних компонентах некоторой<br />

формы, созданной в ходе выполнения программы.<br />

void OwnersAndParents () {<br />

AnsiString info;<br />

int i;<br />

ShowMessage ("Этой формой владеет " + MyForm–>Owner–>Name);<br />

for (i=0; iComponentCount; ++i)<br />

info = info + '\n' + MyForm–>Components[i]–>Name;<br />

ShowMessage ("Объекты владения формы " + info);<br />

info.Delete (1,info.Length ());<br />

for (i=0; iControlCount; ++i)<br />

info = info + '\n' + MyForm–>Controls[i]–>Name;<br />

ShowMessage ("Дочерние объекты формы " + info);<br />

info.Delete (1, info.Length ());<br />

for (i=0; iToolBar1–>ControlCount; ++i)<br />

info = info + '\n' + MyForm–>ToolBar1–>Controls[i]–>Name;<br />

ShowMessage ("Дочерние объекты панели инструментов " + info);<br />

}<br />

Контрольные вопросы<br />

1. Для чего служат и как различаются между собой операторы перехода<br />

2. Как определить глубину вложенности составных операторов<br />

3. В чем состоит разница между условным оператором и оператором выбора<br />

4. Может ли оператор выбора использоваться без оператора break<br />

5. Может ли оператор выбора использоваться с операторами continue и goto<br />

6. Можно ли совмещать операторы условия и выбора<br />

7. Чем различаются операторы цикла и повторения и что у них общего<br />

8. Для чего применяется «вечный цикл»<br />

9. Для чего применяются цикл без счетчика, цикл без инициализации, цикл<br />

без итерации<br />

10. Какие ок<strong>на</strong> диалога используются в Windows<br />

11. Что <strong>на</strong>зывают возвращаемым з<strong>на</strong>чением ок<strong>на</strong> диалога<br />

12. Как используются возвращаемые з<strong>на</strong>чения окон диалога в составных операторах


40<br />

Введение в структурное <strong>программирование</strong><br />

Подобное к подобному влечется<br />

Из Плато<strong>на</strong><br />

Концепция структурного программирования предполагает ранжирование<br />

алгоритма решения задачи по своеобразным «уровням абстрактных машин»<br />

[6]. При этом производится пошаговое уточнение алгоритма за счет<br />

расчленения задачи <strong>на</strong> подзадачи до тех пор, пока мельчайшую из подзадач<br />

не удастся реализовать средствами языка программирования. Массивы,<br />

структуры, объединения, списки, стеки, деки, деревья, векторы и т. п. являются<br />

такими готовыми элементами языка, которые <strong>на</strong>до <strong>на</strong>учиться использовать.<br />

Массивы<br />

Для группового представления и обработки однотипных данных применяются<br />

составные типы – массивы (array). В объявлениях массива используются<br />

прямоугольные скобки:<br />

Тип имяМассива [ [Размер] ]…[={списокЗ<strong>на</strong>чений} ];<br />

Массивы инициализируются одним из двух способов:<br />

имяМассива [ Индекс ]…=З<strong>на</strong>чение;<br />

имяМассива [ [Размер] ]…= { списокЗ<strong>на</strong>чений };<br />

Размер и индекс относятся к целому типу. Они указываются в квадратных<br />

скобках вслед за именем массива. При <strong>на</strong>личии списка з<strong>на</strong>чений один из<br />

размеров можно опускать: он рассчитывается автоматически.<br />

Массивы индексируются с нуля. Число размеров определяет размерность<br />

массива, а число членов (элементов) массива равно произведению всех<br />

размеров. Пример:<br />

void Arrays1 () {<br />

AnsiString students[] = {"Иванов", "Петров", "Сидоров"};<br />

MyForm–>Edit1–>Text = students[0];<br />

MyForm–>Edit2–>Text = students[1];<br />

MyForm–>Edit3–>Text = students[2];<br />

}


41<br />

Число байтов, выделяемое массиву при инициализации, определяется<br />

как произведение размера типа и числа членов. Для резервирования и освобождения<br />

этой памяти целесообразно использовать выражения<br />

Пример:<br />

имяМассива = new Тип [ Размер ]…;<br />

delete [ ] имяМассива;<br />

void Arrays2 () {<br />

AnsiString *whos;<br />

whos = new AnsiString[4];<br />

whos[0] = MyForm–>Edit1–>Text;<br />

whos[1] = "Mr. " + whos[0];<br />

whos[2] = "Dr. " + whos[0];<br />

whos[3] = *(whos + 1);<br />

MyForm–>RichEdit1–>Text =<br />

whos[0] + '\n' + whos[1] +'\n' + whos[2] + '\n' + whos[3];<br />

delete [] whos;<br />

}<br />

Имя каждого чле<strong>на</strong> массива состоит из имени массива и индексов в<br />

квадратных скобках. Программа обрабатывает многомерные массивы, <strong>на</strong>чи<strong>на</strong>я<br />

с членов, отмеченных крайними справа индексами. Имя массива указывает<br />

<strong>на</strong> адрес его нулевого чле<strong>на</strong>. Так как <strong>на</strong>чальный адрес массива уста<strong>на</strong>вливается<br />

автоматически в момент инициализации, он не может быть переопределен<br />

пользователем и, следовательно, име<strong>на</strong> массивов нельзя присваивать<br />

друг другу. Копирование массивов выполняется почленно, в том числе с помощью<br />

специальных функций.<br />

Массив передается в функцию только по ссылке <strong>на</strong> свой адрес. При передаче<br />

массива в функцию в качестве аргумента указывается имя массива, а<br />

при определении функции с многомерным массивом параметром задается<br />

имя массива и все размеры, кроме первого:<br />

void F1 () { AnsiString x[3]; F2 (x); ShowMessage (x[0] + ‘ ‘ + x[1]); }<br />

void F2 (AnsiString y[3]) { y[0] = “Привет”; *(y+1) = “Салют”; }<br />

void F3 () {<br />

int x[2][2]; F4 (x); ShowMessage (IntToStr (x[0][0]) + ‘ ‘ + IntToStr (x[0][1]));<br />

}<br />

void F4 (int y[][2]) { y[0][0] = 1; y[0][1] = 2; }


42<br />

Примером массива является свойство Cells компонента StringGrid, которым<br />

задается содержание ячейки, адресуемой индексами. Массивами описывают<br />

также отдельные панели строки состояния и строки списков.<br />

void Arrays4 () {<br />

MyForm–>StringGrid1–>Cols[1]–>Add (“0-ая ячейка 1-го столбца”);<br />

MyForm–>StringGrid1–>Cols[1]–>Add (“1-ая ячейка 1-го столбца”);<br />

MyForm–>StringGrid1–>Cols[1]–>Append (“2-ая ячейка 1-го столбца”);<br />

MyForm–>StringGrid1–>Cells[2][3] = “Ячейка 2-го столбца 3-ей строки”;<br />

MyForm–>StatusBar1–>Panels–>Items[0]–>Text = "Левая панель";<br />

MyForm–>ListBox1–>Items–>Strings[0] = "Нулевая строка списка";<br />

}<br />

Массивы эффективно используются в графике, <strong>на</strong>пример, для рисования<br />

геометрических фигур по канве формы:<br />

void __fastcall TForm1::FormPaint (TObject *Sender) {<br />

TPoint v[5];<br />

int cw = ClientWidth, ch = ClientHeight;<br />

v[0] = Point (0, cw/10);<br />

v[1] = Point (cw/4, cw/10);<br />

v[2] = Point (cw/2, cw/4);<br />

v[3] = Point (cw/4, cw/4);<br />

v[4] = Point (0, cw/10);<br />

Canvas–>Polyline (v, 4);<br />

v[0] = Point (cw/2, ch/2);<br />

v[1] = Point (cw/2, ch);<br />

v[2] = Point (cw,ch);<br />

Canvas–>Polygon (v,2);<br />

Canvas–>Rectangle (0, ch/2, cw/2, ch);<br />

Canvas–>RoundRect (0, ch/2, cw/2, ch, cw/10, ch/10);<br />

Canvas–>Ellipse (0, ch/2, cw/2, ch);<br />

}<br />

Свойство Pixels типа TColor у компонентов Canvas можно использовать<br />

как двухмерный массив цвета точки, коорди<strong>на</strong>ты которой отсчитываются<br />

от левого верхнего угла изображения.<br />

void _ _fastcall TForm1::FormMouseDown (TObject *Sender,<br />

TMouseButton Button, TShiftState Shift, int X, int Y) {<br />

Label1–>Caption = X; Label2–>Caption = Y;<br />

Label3–>Caption = Canvas–>Pixels[X][Y];<br />

Canvas–>Pixels[X][Y] = clWhite;<br />

}


Перестановки и сортировки<br />

43<br />

Простым алгоритмом описывается широко распространен<strong>на</strong>я в программировании<br />

операция перестановки (swap) данных местами. Пример перестановки<br />

пары переменных выглядит так:<br />

void Swap (int &a, int &b) { int x = a; a = b; b = x; }<br />

void SwapVar () {<br />

int x = 2, y = 1;<br />

Swap (x, y);<br />

ShowMessage (x >y “x > y” : “x < y”);<br />

}<br />

Следующий пример иллюстрирует перестановку членов массива:<br />

void Swap (AnsiString a[], int n) {<br />

int i, j;<br />

for (i=0, j=n–1; i


44<br />

Сортировка объединением предусматривает получение одной последовательности<br />

данных, построенной по определенному правилу, из двух и более<br />

последовательностей, построенных по тому же правилу.<br />

Простейший из алгоритмов сортировки – алгоритм упорядочения методом<br />

«пузырька» (bubble sorting). В нем последовательно проверяются все<br />

пары данных сортируемого массива, и там, где первый член больше второго,<br />

выполняется перестановка.<br />

Сортировка методом выборки минимального или максимального чле<strong>на</strong><br />

(exchange sorting) заключается в последовательной выборке <strong>на</strong>именьшего из<br />

оставшихся членов списка. При этом перебор списка проводится более медленно,<br />

чем сравнение предыдущим методом. Еще медленнее работает метод<br />

вставки (insertion sorting), когда члены просматриваются по одному, и каждый<br />

новый член вставляется в подходящее место среди ранее упорядоченных<br />

данных. Этот алгоритм эффективен при малых массивах (до 50 – 100 членов),<br />

особенно, если они частично отсортированы. По методу Шелла (Shell sorting),<br />

в отличие от «пузырька», переставляются не соседние, а далеко отстоящие<br />

члены, что иногда повышает скорость в 1,5 раза.<br />

Примерно втрое большую скорость, чем сортировка методом «пузырька»,<br />

обеспечивает «быстрая сортировка» (quick sorting). При сортировке<br />

этим методом выбирается произвольный член, а остальной список сортируемых<br />

членов делится пополам, и каждый член одной половины сравнивается с<br />

чле<strong>на</strong>ми другой. Затем список вновь делится пополам и выполняется перестановка<br />

и т.д. Этот алгоритм положен в основу ряда библиотечных функций.<br />

void Swap (int &x, int &y) { int temp = x; x = y; y = temp; }<br />

void BubbleSort (int array[], int N) {<br />

for (int i=0; ii; ––j)<br />

if (array[j] < array[j–1]) Swap (array[j], array[j–1]);<br />

}<br />

void ExchangeSort (int array[], int N) {<br />

for (int i=0; i


}<br />

45<br />

for (int i=1; i=0 + 1; – –j)<br />

if (array[j–1] > array[j]) Swap (array[j], array[j–1]);<br />

void ShellSort (int array[], int N) {<br />

int delta = 1, sw, offset = (N–1+delta) / 2;<br />

while (offset > 0) {<br />

int limit = N–1+delta–offset;<br />

do {<br />

sw = 0;<br />

for (int i=1; iarray[i+offset–delta]) {<br />

Swap (array[i–delta], array[i+offset–delta]); sw = i;<br />

}<br />

limit = sw – offset;<br />

} while (sw);<br />

offset /= 2;<br />

}<br />

}<br />

void QuickSort (int array[], int left, int right) {<br />

int i = left, j = right, temp, test = array [(left+right) / 2];<br />

do {<br />

while (array[i] < test) ++i;<br />

while (test < array[j]) ––j;<br />

if (i RadioGroup1–>ItemIndex) {<br />

case 0: BubbleSort (array, N); break;<br />

case 1: ExchangeSort (array, N); break;<br />

case 2: InsertionSort (array, N); break;<br />

case 3: ShellSort (array, N); break;<br />

case 4: QuickSort (array, 0, N–1); break;


46<br />

}<br />

for (i=0; i 0, если elem1 больше, чем elem2:<br />

int Sort_function (const void *a, const void *b);<br />

void Qsorting () {<br />

char list[5] = {'c', 'r', 'a', 'p', 'n'};<br />

int i;<br />

AnsiString result;<br />

qsort ((void*) &list, 5, sizeof (list[0]), Sort_function);<br />

for (i = 0; i < 5; i++) result += AnsiString (list[i]) + ' ';<br />

ShowMessage (result);<br />

}<br />

int Sort_function (const void *a, const void *b) {<br />

return strcmp ((char*) a, (char*) b);<br />

}<br />

Списки TStringList<br />

Разновидностью массивов являются последовательности символьных<br />

строк – списки.<br />

Библиотека VCL поддерживает общий интерфейс со списками через<br />

класс TString и производный от него класс TStringList. Это один из самых популярных<br />

классов, представляющий удобный контейнер для хранения строк<br />

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


47<br />

нять, создавать списки, манипулировать строками, сортировать, вставлять,<br />

добавлять и удалять их.<br />

Временные списки и списки длительного пользования проектируют поразному.<br />

Первые обычно создаются и разрушаются в пределах одной функции:<br />

void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />

TStringList *TempList = new TStringList;<br />

try { Label1–>Caption = “Временный список используется”; }<br />

_ _finally { delete TempList;}<br />

}<br />

Вторые с<strong>на</strong>чала объявляются в файле заголовка формы как указатели<br />

<strong>на</strong> объекты класса TString. Объявленные объекты создаются в конструкторе<br />

формы и освобождают память при ее уничтожении. В приведенном ниже<br />

примере в файле заголовка формы был объявлен открытый указатель<br />

TStringList *ConstList:<br />

__fastcall TForm1::TForm1 (TComponent* Owner): TForm (Owner) {<br />

ConstList = new TStringList;<br />

}<br />

void __fastcall TForm1::FormClose (TObject *Sender, TCloseAction &Action) {<br />

delete ConstList;<br />

}<br />

void __fastcall TForm1::FormMouseDown (TObject *Sender,<br />

TMouseButton Button, TShiftState Shift, int X, int Y) {<br />

TVarRec v [] = {X,Y};<br />

ConstList–>Add (Format ("Курсор в (%d,%d)", v, ARRAYSIZE (v) – 1));<br />

}<br />

Новая строка в конец списка добавляется методом Add. Количество<br />

строк в списке вычисляется методом Count, а свойство Strings содержит весь<br />

массив строк, доступных по их индексам, <strong>на</strong>чи<strong>на</strong>я с 0. Метод IndexOf определяет<br />

индекс строки, содержащей указанный текст, <strong>на</strong>пример:<br />

void StringLists2 () {<br />

MyForm–>ConstList–>Add ("Конец");<br />

MyForm–>ConstList–>Strings [ConstList–>Count–1] =<br />

MyForm–>ConstList–>IndexOf ("Конец");<br />

}<br />

Чтобы добавить i-ю строку в список, используется метод Insert. Для<br />

добавления строк из другого списка служит методы Assign и AddStrings.<br />

Перемещение строк внутри списка выполняется методом Move. Удалением


48<br />

ремещение строк внутри списка выполняется методом Move. Удалением<br />

строки занимается метод Delete, а Clear очищает список:<br />

void StringLists3 () {<br />

TStringList *tsl = new TStringList;<br />

tsl–>Add (“Раз”);<br />

tsl–>Add (“Два”);<br />

ShowMessage (tsl–>Count);<br />

ShowStr (tsl);<br />

// Раз Два<br />

tsl–>Insert (1, “Полтора”); ShowStr (tsl); // Раз Полтора Два<br />

tsl–>Move (0, 2); ShowStr (tsl); // Полтора Два Раз<br />

tsl–>Exchange (0, 1); ShowStr (tsl); // Два Полтора Раз<br />

tsl–>Delete (1); ShowStr (tsl);<br />

// Два Раз<br />

tsl–>Sort (); ShowStr (tsl);<br />

// Два Раз<br />

int index; if (tsl–>Find (“Раз”, index)) ShowMessage (index); // 0<br />

tsl–>Clear (); ShowStr (tsl);<br />

TStringList *temp = new TStringList;<br />

temp–>Assign (tsl); ShowStr (temp); // Два Раз<br />

delete temp;<br />

Memo1–>Lines–>Assign (tsl);<br />

RichEdit1–>Lines–>Assign (tsl);<br />

tsl–>Assign (Memo1–>Lines);<br />

TStrings *ml = Memo1–>Lines;<br />

ml–>Add (“Три”);<br />

ShowMessage (ml–>Count);<br />

ml–>Move (0, 1);<br />

ml–>Insert (1, “Четыре”);<br />

ml–>Delete (1);<br />

ml–>Clear ();<br />

ComboBox1–>Items–>AddStrings (tsl);<br />

TStrings *lb = ListBox1–>Items;<br />

lb–>AddStrings (tsl);<br />

lb–>Add (“Три”);<br />

lb–>Append (“Четыре”);<br />

ShowMessage (lb–>Count);<br />

lb–>Insert (2, “Пять”);<br />

lb–>Move (0, 1);<br />

lb–>Exchange (0, 1);<br />

lb–>Delete (1);<br />

lb–>SaveToFile (“1.txt”);<br />

lb–>Clear ();


lb–>LoadFromFile (“1.txt”);<br />

ShowMessage (lb–>Text);<br />

ShowMessage (lb–>Strings[0]);<br />

ShowMessage (ListBox1–>ItemIndex); // номер выделенной строки<br />

if (ListBox1–>Selected[1] == true)<br />

ShowMessage (lb–>Strings[ListBox1–>ItemIndex]);<br />

ListBox1–>Sorted = true;<br />

ListBox1–>ItemIndex = 1;<br />

}<br />

void ShowStr (TStringList *z) {<br />

AnsiString s;<br />

for (int i=0; iCount; ++i) s+= z–>Strings[i] + ‘\n’;<br />

ShowMessage (s);<br />

}<br />

49<br />

Структуры и объединения<br />

Упростить представление и групповую обработку разнотипных данных<br />

помогают структуры (structure). Их объявления:<br />

struct имяТипаСтруктуры { объявлениеПолей };<br />

struct имяСтруктуры { объявлениеПолей } [=списокЗ<strong>на</strong>чений ];<br />

struct имяТипаСтруктуры имяСтруктуры [={ списокЗ<strong>на</strong>чений }];<br />

где объявление полей имеет формат<br />

Тип имяПоля;…<br />

Как и любой тип, структура представляет абстрактную категорию, характеризующую<br />

размер памяти и способ обработки данных. Для работы с<br />

данными создаются переменные:<br />

имяТипаСтруктуры списокИменСтруктур;<br />

Структуры инициализируют, подобно классам:<br />

имяСтруктуры.имяПоля = З<strong>на</strong>чение;<br />

указательНаСтруктуру–>имяПоля = З<strong>на</strong>чение;<br />

Име<strong>на</strong> полей уникальны в пределах структуры. Поля поддерживают все операции,<br />

характерные для их типов. Структуры и объединения могут вкладываться<br />

друг в друга. Примеры:<br />

void Structures1 () {<br />

struct Student {<br />

AnsiString firstName;<br />

int birthYear;


double scholarship;<br />

};<br />

Student example;<br />

example.firstName = MyForm–>Edit1–>Text ;<br />

example.birthYear = StrToInt (MyForm–>Edit2–>Text);<br />

example.scholarship = StrToFloat (MyForm–>Edit3–>Text) ;<br />

MyForm–>StringGrid1–>Cells[1][1] = example.firstName;<br />

MyForm–>StringGrid1–>Cells[2][1] = example.birthYear;<br />

MyForm–>StringGrid1–>Cells[3][1] = example.scholarship;<br />

}<br />

void Structures2 () {<br />

TStringGrid *sg = StringGrid1;<br />

int rows = sg–>RowCount;<br />

struct s { int n; AnsiString nm; };<br />

s group;<br />

try {<br />

for (int i=0; iCells[0][i]);<br />

group.nm = sg–>Cells[1][i];<br />

ShowMessage (IntToStr (group.n) + ' ' + group.nm);<br />

}<br />

} catch (...) { ShowMessage ("Заполнить ячейки"); }<br />

}<br />

50<br />

Следующий пример посвящен пузырьковой сортировке строк String-<br />

Grid с использованием структуры:<br />

void Structures3 () {<br />

TStringGrid *sg = StringGrid1;<br />

int rows = sg–>RowCount;<br />

struct s { AnsiString name; int year; };<br />

s temp;<br />

for (int i=1; ii; – –j)<br />

if (sg–>Cells[0][j] < sg–>Cells[0][j–1]) {<br />

temp.name = sg–>Cells[0][j];<br />

sg–>Cells[0][j] = sg–>Cells[0][j–1];<br />

sg–>Cells[0][j–1] = temp.name;<br />

temp.year = StrToInt (sg–>Cells[1][j]);<br />

sg–>Cells[1][j] = sg–>Cells[1][j–1];<br />

sg–>Cells[1][j–1] = temp.year;<br />

}<br />

}


51<br />

При передаче в функцию в качестве аргументов могут использоваться<br />

как име<strong>на</strong> полей, так и указатели <strong>на</strong> адреса. Применение указателя в качестве<br />

з<strong>на</strong>чения аргумента избавляет от необходимости копирования в функцию<br />

всех данных, но в этом случае они остаются беззащитными перед изменениями<br />

в теле функции. Для устранения этой проблемы перед указателем<br />

можно помещать лексему const. Пример передачи данных структуры:<br />

struct x { int size, price; };<br />

void Change1 (x y) { y.size = y.price; }<br />

void Change2 (x *y) { y–>size = y–>price; }<br />

void Structures4 () {<br />

x y = {1, 2};<br />

Change1 (y);<br />

MyForm–>Edit1–>Text = y.size – y.price;<br />

Change2 (&y);<br />

MyForm–>Edit2–>Text = y.size – y.price;<br />

}<br />

Близкие к структурам по смыслу объединения (union) позволяют хранить<br />

в одной и той же области памяти з<strong>на</strong>чения переменных разных типов,<br />

поочередно заменяя одно другим. Размер памяти, занимаемой данными объединения,<br />

<strong>на</strong>з<strong>на</strong>чается в соответствии с тем из типов, который требует больше<br />

памяти. Их объявление и пример:<br />

union имяТипаОбъединения {объявлениеПолей} имяОбъединения<br />

[={списокЗ<strong>на</strong>чений}];<br />

void Unions () {<br />

union intDbl {int i; double x;} id = {70};<br />

MyForm–>Edit1–>Text = "Масса в граммах" + IntToStr (id.i);<br />

id.x = 0.07;<br />

MyForm–>Edit2–>Text = "Масса в килограммах" + FloatToStr (id.x);<br />

}<br />

Ди<strong>на</strong>мические структуры данных<br />

В отличие от составных типов, ди<strong>на</strong>мические структуры данных (dynamic<br />

data structure), или связные списки (linked list), изменяются в размере по<br />

мере заполнения и освобождения их от данных и обычно не требуют последовательного<br />

размещения элементов в памяти компьютера. Ди<strong>на</strong>мические<br />

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


52<br />

мирования и присутствуют в них как классы. Они, как правило, разрабатываются<br />

программистами или берутся из библиотек – контейнеров. Каждая<br />

структура характеризуется порядком заполнения (allocation) и порядком обхода<br />

(traversal).<br />

Некоторые характеристики классов ди<strong>на</strong>мических структур C++Builder<br />

приведены в табл. 12:<br />

Таблица 12<br />

Свойства и методы контейнерных классов C++Builder<br />

Имя Член Классы Наз<strong>на</strong>чение<br />

Push Метод TStack, TQueue Добавляет член в вершину<br />

Pop Метод TStack, TQueue Удаляет члены из вершины<br />

Peek Метод TStack, TQueue Указатель <strong>на</strong> вершину<br />

Count Метод TStack, TQueue Возвращает число членов (размер)<br />

AtLeast Метод TStack, TQueue Проверяет размер<br />

Capacity Свойство TList Максимальный размер<br />

Count Свойство TList Фактический размер<br />

Items Свойство TList Указатель <strong>на</strong> член<br />

List Свойство TList Указатель <strong>на</strong> список членов<br />

First Метод TList Указатель <strong>на</strong> первый член<br />

Last Метод TList Указатель <strong>на</strong> последний член<br />

Add Метод TList Добавляет член в вершину<br />

Insert Метод TList Добавляет член в заданное место<br />

Delete Метод TList Удаляет член с указанным индексом<br />

Remove Метод TList Удаляет указанный член<br />

Clear Метод TList Удаляет все члены<br />

Pack Метод TList Удаляет пустоты<br />

Exchange Метод TList Меняет местами два чле<strong>на</strong><br />

Move Метод TList Перемещает указанный член<br />

Expand Метод TList Увеличивает максимальный размер<br />

Sort Метод TList Сортирует члены<br />

Наиболее распространенной ди<strong>на</strong>мической структурой является стек<br />

(stack). Это последовательность данных, в которой помещение и выборка<br />

информации выполняется с одного конца, именуемого вершиной стека (top).<br />

Иными словами, стек обрабатывает данные по принципу «последним вошел<br />

– первым вышел» (LIFO – Last Input, First Output). Примеры:<br />

void _ _fastcall TForm1::StackClick (TObject *Sender) {<br />

AnsiString a[] = { "Иванов", "Петров", "Сидоров" }, b = "0\n";<br />

TStack *s = new TStack;<br />

for (int i=0; iPush (a[i].c_str ());<br />

b += AnsiString (s–>Count ()) + ' ' + (char*) s–>Peek () + '\n';


53<br />

} // 0 1 Иванов 2 Петров 3 Сидоров<br />

while (s–>AtLeast (1))<br />

b += AnsiString (s–>Count ()) + ' ' + (char*) s–>Pop () + '\n';<br />

ShowMessage (b + '0'); // 3 Сидоров 2 Петров 1 Иванов 0<br />

}<br />

void SimpleStack() {<br />

TStack *vStack = new TStack;<br />

vStack–>Push ("Пеpвое ");<br />

vStack–>Push ("Втоpое "); vStack–>Push ("Тpетье ");<br />

ShowMessage ("Добавили " + AnsiString (vStack–>Count ()));<br />

vStack–>Pop (); ShowMessage ("Осталось " + AnsiString (vStack–>Count ()));<br />

}<br />

Далее приведен пример разработки авторского символьного стека без<br />

привлечения библиотечного класса TSteak:<br />

const int ARRAYSIZE = 1000;<br />

struct vStack { char s[ARRAYSIZE]; int top; };<br />

void Reset (vStack *stk) { stk–>top = –1; }<br />

int Empty (const vStack *stk) { return stk–>top == –1; }<br />

int Full (const vStack *stk) { return stk–>top == ARRAYSIZE – 1; }<br />

void Push (char *str, vStack *stk) {<br />

int i = 0; while (str[i]) if (!Full (stk)) stk–>s[++stk–>top] = str[i++];<br />

}<br />

void Pop (vStack *stk) {<br />

AnsiString result;<br />

while (!Empty (stk)) result += AnsiString (stk–>s[stk–>top––]) + '\n';<br />

ShowMessage (result);<br />

}<br />

void MyStack () {<br />

vStack s;<br />

Reset (&s);<br />

Push ("Пеpвое ", &s); Push ("Втоpое ", &s); Push ("Тpетье ", &s); Pop (&s);<br />

}<br />

Очередью (queue) <strong>на</strong>зывают последовательность данных, в которой помещение<br />

данных выполняется с одного конца – входа очереди (input), а выборка<br />

– с другого конца – выхода очереди (output) по принципу «первым вошел<br />

– первым вышел» (FIFO – First Input, First Output):<br />

void _ _fastcall TForm1::QueueClick (TObject *Sender) {<br />

AnsiString a[] = { "Иванов", "Петров", "Сидоров" }, b = "0\n";


54<br />

TQueue *q = new TQueue;<br />

for (int i=0; iPush (a[i].c_str());<br />

b += AnsiString (q–>Count ()) + ' ' + (char*) q–>Peek () + '\n';<br />

} // 0 1 Иванов 2 Иванов 3 Иванов<br />

while (q–>AtLeast (1))<br />

b += AnsiString (q–>Count ()) + ' ' + (char*) q–>Pop () + '\n';<br />

ShowMessage (b + '0'); // 3 Иванов 2 Петров 1 Сидоров 0<br />

}<br />

void SimpleQueue () {<br />

TQueue *vQueue = new TQueue;<br />

vQueue–>Push ("Пеpвое ");<br />

vQueue–>Push ("Втоpое ");<br />

vQueue–>Push ("Тpетье ");<br />

ShowMessage ("Добавили " + AnsiString (vQueue–>Count ()));<br />

vQueue–>Pop ();<br />

ShowMessage ("Осталось " + AnsiString (vQueue–>Count ()));<br />

}<br />

Списком <strong>на</strong>зывается ди<strong>на</strong>мическая структура, в которой поддерживаются<br />

помещение и выборка данных в произвольных позициях. Каждый член<br />

списка, именуемый узлом (node), содержит поле данных и поле–указатель<br />

следующего узла. Список <strong>на</strong>зывают линейным (linear list), если обход его выполняется<br />

только в одном <strong>на</strong>правлении – от предшествующего узла к последующему,<br />

<strong>на</strong>чи<strong>на</strong>я с первого узла и кончая последним. Список <strong>на</strong>зывают<br />

циклическим (cyclic list), если последний узел его содержит указатель <strong>на</strong> первый<br />

узел; в нем обход выполняется также в одном <strong>на</strong>правлении, но из любого<br />

узла можно перейти в любой другой узел. Пример:<br />

void MyList () {<br />

TList *vList = new TList ();<br />

AnsiString item = "что-то есть";<br />

try {<br />

vList–>Add (item.c_str ());<br />

ShowMessage ("В списке " + IntToStr (vList–>Count));<br />

vList–>Remove (item.c_str ());<br />

ShowMessage ("В списке " + IntToStr (vList–>Count));<br />

}<br />

catch (...) { delete vList; throw; }<br />

delete vList;<br />

}


55<br />

void Info (TList *vl) {<br />

AnsiString b;<br />

for (int i=0; iCount; ++i)<br />

{ b += AnsiString (i) + ' ' + (char*) vl–>Items[i] + '\n'; }<br />

ShowMessage ("В списке: " + IntToStr (vl–>Count) + '\n' + b);<br />

}<br />

void __fastcall TForm1::ListClick (TObject *Sender){<br />

AnsiString a[] = {"Иванов", "Петров", "Сидоров", "Смирнов"};<br />

TList *vList = new TList;<br />

try {<br />

vList–>Capacity = 10;<br />

for (int i=0; iAdd (a[i].c_str ());<br />

Info (vList); // В списке 4: 0 Иванов 1 Петров 2 Сидоров 3 Смирнов<br />

vList–>Remove (vList–>Items[1]); vList–>Delete (1);<br />

Info (vList); // В списке 2: 0 Иванов 1 Смирнов<br />

vList–>Insert (1, a[0].c_str ());<br />

Info (vList); // В списке 3: 0 Иванов 1 Иванов 2 Смирнов<br />

ShowMessage ("Свободно " + IntToStr (vList–>Capacity–vList–>Count)); // 7<br />

vList–>Pack ();<br />

ShowMessage ("Свободно " + IntToStr (vList–>Capacity–vList–>Count)); // 7<br />

} catch (...) { delete vList; throw; }<br />

delete vList;<br />

}<br />

Пример разработки авторского списка без привлечения библиотечного<br />

класса TList:<br />

struct vList { int value; vList *p; };<br />

vList *current, *last, *first;<br />

int Get () {<br />

static value;<br />

int val = MessageDlg ("Продолжать", mtConfirmation,<br />

TmsgDlgButtons () p = 0;<br />

if (last) last–>p = current; else first = current;


56<br />

last = current;<br />

}<br />

}<br />

void Info () {<br />

AnsiString result;<br />

if (first) {<br />

result = "Список: ";<br />

current = first;<br />

do { result += AnsiString (current–>value) + ' '; current = current–>p; }<br />

while (current);<br />

result += " Конец";<br />

}<br />

ShowMessage (result);<br />

}<br />

void MyList () { Add (); Info (); Add (); Info (); }<br />

Другой пример посвящен двухмерному списку. Он демонстрирует технику<br />

выделения и освобождения памяти ди<strong>на</strong>мической структуре данных:<br />

struct vMatr { double **base; int row, column; };<br />

void Allocate (int r, int s, vMatr &m) {<br />

m.base = new double *[s];<br />

for (int i=0; i


Allocate (k[0], k[1], book1); Allocate (k[2], k[3], book2);<br />

for (i=0; i


58<br />

Можно использовать любой порядок заполнения узлов, но обычно этот<br />

порядок зависит от з<strong>на</strong>чений данных в них: узлы с меньшим з<strong>на</strong>чением присоединяют<br />

слева, а с большим з<strong>на</strong>чением – справа. Чтобы обойти дерево, отметив<br />

каждый узел один раз, используют один из трех способов (рис. 1). При<br />

прямом порядке обхода (depth-first traversal) («просмотре в глубину») движение<br />

<strong>на</strong>чи<strong>на</strong>ется от корня и выполняется от родителей с<strong>на</strong>чала к левым потомкам,<br />

а затем к правым. При обратном порядке (breadth-first traversal) обход<br />

<strong>на</strong>чи<strong>на</strong>ется с листьев и выполняется от потомков к родителям слева <strong>на</strong>право,<br />

завершаясь в корне. Наиболее распространенный обход в симметричном порядке<br />

(simmetrical traversal) <strong>на</strong>правлен от левого потомка через родителя к<br />

правому потомку от листьев к корню.<br />

Пример двоичного дерева:<br />

struct vTree { char *data; vTree *left; vTree *right; };<br />

vTree *root;<br />

void Search (vTree **tree, const char *s);<br />

AnsiString Process (vTree *node);<br />

AnsiString InOrder (vTree *node);<br />

void MyTree () {<br />

char s[128];<br />

// можно сортировать любые массивы<br />

randomize();<br />

for (int i=1; idata = strdup (s); p–>left = 0; p–>right = 0; *tree = p;<br />

}<br />

else {<br />

p = *tree;<br />

cmpResult = strcmp (s, p–>data); // сравнение строк<br />

if (cmpResultleft, s);<br />

else if (cmpResult>0) Search (&p–>right, s);


59<br />

else ShowMessage ("Такой член уже есть");<br />

}<br />

}<br />

AnsiString Process (vTree *node) {<br />

static AnsiString result;<br />

result += AnsiString (node–>data) + ' ';<br />

return result;<br />

}<br />

AnsiString InOrder (vTree *node) {<br />

AnsiString result;<br />

If (node != 0) {<br />

InOrder(node–>left);<br />

result = Process (node);<br />

InOrder (node–>right);<br />

}<br />

return result;<br />

}<br />

Стандарт<strong>на</strong>я библиотека STL<br />

Библиотека STL (Standard Templates Library) представляет <strong>на</strong>бор шаблонов<br />

классов <strong>С++</strong>, включающий контейнеры, итераторы, аллокаторы и алгоритмы,<br />

в пространстве имен std. Контейнеры (container) служат классами,<br />

организующими хранение объектов. Неупорядоченные контейнеры представляют<br />

vector, list, deque. К упорядоченным контейнерам относятся set,<br />

map, hash-set, hash-map и их разновидности. Итераторами (iterator) <strong>на</strong>зывают<br />

средства перемещения по объектам контейнеров. Обычно эту роль играют<br />

указатели, поддерживающие итерацию. Итераторы бывают прямыми и<br />

обратными, константными и неконстантными, изменяющими содержимое<br />

контейнера. Низкоуровневые функции распределения памяти контейнеров<br />

<strong>на</strong>зывают аллокаторами (allocator). С их помощью все члены STL автоматически<br />

выделяют и освобождают память, не требуя от программиста использования<br />

операторов new и delete. Для манипулирования данными в контейнерах<br />

служат алгоритмы (algorithm). Обычно <strong>на</strong> них возлагаются стандартные<br />

операции сортировки, поиска, сравнения, перестановки и т.п.<br />

Контейнер vector, описанный в файле заголовка является ди<strong>на</strong>мическим<br />

массивом переменного размера. Подобно стеку, он обеспечивает<br />

добавление объектов в конец функцией push_back и удаление их оттуда<br />

функцией pop_back. Кроме того, в нем реализова<strong>на</strong> вставка объектов в про-


60<br />

извольное место функцией insert и удаление их оттуда функцией erase.<br />

Функция clear удаляет из контейнера все объекты, а функция size возвращает<br />

число объектов в контейнере. Функция empty сообщает, пуст ли контейнер,<br />

reserve резервирует память для будущих объектов, а capacity возвращает текущий<br />

объем выделенной памяти.<br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::VectorClick (TObject *Sender) {<br />

vector n;<br />

vector::iterator j;<br />

vector::reverse_iterator rj;<br />

ListBox1–>Items–>Add (n.size ());<br />

ListBox1–>Items–>Add (n.capacity ()); // 0 0<br />

for (int i=0; iItems–>Add (n.size ());<br />

ListBox1–>Items–>Add (n.capacity ()); // 10 16<br />

for (j = n.begin (); j!=n.end (); ++j) ListBox2–>Items–>Add (*j); // 0,1,...,9<br />

for (rj = n.rbegin (); rj!=n.rend (); ++rj) ListBox3–>Items–>Add (*rj); // 9,8,...,0<br />

n.erase (n.begin ()+5, n.begin ()+7);<br />

// удаление 5-6-го<br />

n.insert (n.end ()–2, –1);<br />

// вставка –1 3-м с конца<br />

n.push_back (10);<br />

// вставка 10 в конец<br />

ListBox1–>Items–>Add (n.size ());<br />

ListBox1–>Items–>Add (n.capacity()); // 10 16<br />

for (j=n.begin (); j!=n.end (); ++j) ListBox4–>Items–>Add (*j);<br />

// 0,1,2,3,4,7,–1,8,9,10<br />

n.clear ();<br />

}<br />

Контейнер deque из одноименного файла заголовка, в отличие от vector,<br />

может заполняться и освобождаться от объектов с двух сторон.<br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::DequeClick (TObject *Sender) {<br />

deque n;<br />

deque::iterator j;<br />

deque::reverse_iterator rj;<br />

for (int i=0; iAdd (*j); // 4,3,2,1,0,5,6,7,8,9


61<br />

for (rj=n.rbegin (); rj!=n.rend (); ++rj) ListBox2–>Items–>Add (*rj);<br />

// 9,8,7,6,5,0,1,2,3,4<br />

n.pop_front ();<br />

n.pop_back ();<br />

for (j = n.begin (); j!=n.end (); ++j) ListBox3–>Items–>Add (*j); // 3,2,1,0,5,6,7,8<br />

n.erase (n.begin ()+5, n.begin ()+7);<br />

// удаление 5-6-го<br />

n.insert (n.end()–2, –1);<br />

// вставка –1 3-м с<br />

конца<br />

for (j = n.begin (); j!=n.end (); ++j) ListBox4–>Items–>Add (*j); // 3,2,1,0,–1,5,8<br />

n.clear ();<br />

}<br />

Контейнер map организован в виде множества упорядоченных пар<br />

(иногда троек). Каждая пара состоит из первого чле<strong>на</strong> (first), являющегося<br />

ключом, и второго чле<strong>на</strong> (second) – з<strong>на</strong>чения объекта. Сортировка пар выполняется<br />

автоматически по ключу, хотя возмож<strong>на</strong> также программ<strong>на</strong>я сортировка.<br />

#include <br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::MapClick (TObject *Sender) {<br />

map m;<br />

map::iterator j;<br />

double r = 10;<br />

m["pi"] = 3.14159;<br />

m["e"] = 2.718;<br />

m["Скорость света"] = 2.998E8;<br />

m["Площадь круга"] = pow(r, 2) * m["pi"];<br />

for (j=m.begin (); j!=m.end (); ++j)<br />

ListBox1–>Items–>Add (j–>first + '=' + FloatToStr (j–>second));<br />

// e=2.718; pi=3.14159; Площадь круга=314.159; Скорость света=2998...<br />

}<br />

Алгоритм find выполняет поиск объектов контейнера между двумя установленными<br />

итераторами с возвращением итератора <strong>на</strong>йденного объекта, а<br />

в случае отсутствия такового – с возвращением второго итератора.<br />

#include <br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::findClick (TObject *Sender) {<br />

vector v;


vector::iterator j;<br />

int what = 3;<br />

for (int i=–5; iItems–>Add (*j);<br />

j = std::find (v.begin (), v.end (), what);<br />

if (j != v.end ()) ShowMessage ("Нашел " + AnsiString (*j)); // Нашел 3<br />

else ShowMessage ("Не <strong>на</strong>шел");<br />

}<br />

62<br />

Алгоритмы min и max отыскивают экстремумы.<br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::minMaxClick (TObject *Sender) {<br />

ListBox1–>Items–>Add (max (15, 5)); // 15<br />

ListBox1–>Items–>Add (max ('a', 'b')); // b<br />

ListBox1–>Items–>Add (min (3.14, 24.5)); // 3,14<br />

}<br />

Алгоритмы random_shuffle и sort соответственно перемешивают и сортируют<br />

объекты контейнера.<br />

#include <br />

#include <br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::sortClick (TObject *Sender) {<br />

vector v;<br />

vector::iterator j;<br />

randomize ();<br />

for (int i='a'; iItems–>Add (*j);<br />

std::sort (v.begin (), v.end ());<br />

for (j = v.begin (); j!=v.end (); ++j) ListBox2–>Items–>Add (*j);<br />

std::sort (v.begin (), v.end (), greater ());<br />

for (j = v.begin (); j!=v.end (); ++j) ListBox3–>Items–>Add (*j);<br />

}<br />

Алгоритм binary_search ищет объекты <strong>на</strong> би<strong>на</strong>рном дереве.<br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::BinSearchClick (TObject *Sender) {<br />

// ab...z<br />

// zy...a


int i, d[] = { 5, 2, 3, 8, 3, 1, 3 };<br />

const int D = sizeof (d) / sizeof (int);<br />

for (i=0; iItems–>Add (d[i]);<br />

std::sort (d, d+D);<br />

for (i=0; iItems–>Add (IntToStr (i) +<br />

(binary_search (d, d+D, i) " есть" : " нет"));<br />

AnsiString s1[] = {"раз", "два", "четыре", "пять" },<br />

s2[] = { "раз", "три", "пять", "семь"};<br />

const int S1 = sizeof (s1) / sizeof (s1[0]), S2 = sizeof (s2) / sizeof (s2[0]);<br />

for (i=0; iItems–>Add (s1[i]);<br />

std::sort (s1, s1+S1);<br />

for (i=0; iItems–>Add (s2[i] +<br />

(binary_search (s1, s1+S1, s2[i]) " есть" : " нет"));<br />

}<br />

63<br />

Контейнер set поддерживает уникальные ключи и дву<strong>на</strong>правленные<br />

итераторы, являясь хранилищем упорядоченных объектов, удобным для поиска<br />

и сравнения с а<strong>на</strong>логичными хранилищами. Он незаменим для выполнения<br />

классических операций <strong>на</strong> множествах: при<strong>на</strong>длежности, объединения,<br />

пересечения, вычитания, дополнения. В отличие от vector и map, контейнер<br />

set не позволяет непосредственно воздействовать <strong>на</strong> з<strong>на</strong>чения объектов, требуя<br />

вместо этого вставки измененных объектов извне. В следующем примере<br />

множество s1 заполняется числами, и эти же числа копируются в s2. Затем<br />

отыскивается и удаляется первая полови<strong>на</strong> s2, остаток заполняется новыми<br />

числами, и вычисляются результаты объединения и пересечения s1 и s2. В<br />

программе работают алгоритмы advance – перемещение итератора, set_union<br />

– объединение, set_intersection – пересечение множеств.<br />

#include <br />

#include <br />

using namespace std;<br />

void __fastcall TForm1::SetClick (TObject *Sender) {<br />

set s1, s2, s3, s4;<br />

set::iterator j;<br />

for (int i=0; i>1);<br />

s2.erase (s2.begin (), j);


for (int i=10; iItems–>Add (*j); // 0...9<br />

for (j=s2.begin (); j!=s2.end (); ++j) ListBox2–>Items–>Add (*j); // 5...14<br />

for (j=s3.begin (); j!=s3.end (); ++j) ListBox3–>Items–>Add (*j); // 0...14<br />

for (j=s4.begin (); j!=s4.end (); ++j) ListBox4–>Items–>Add (*j); // 5...9<br />

}<br />

64<br />

Контрольные вопросы<br />

1. Для чего пред<strong>на</strong>з<strong>на</strong>чены массивы в программировании<br />

2. Как объявляются и инициализируются массивы<br />

3. Чем различаются статические и ди<strong>на</strong>мические массивы и в чем состоят<br />

достоинства и недостатки каждого из них<br />

4. Как передаются массивы в функции<br />

5. Какова связь между именем массива и его адресом Можно ли применять к<br />

массиву операцию взятия адреса.<br />

6. Как определяется адрес элемента массива по его индексу<br />

7. Каковы основные свойства компонента StringGrid<br />

8. Что такое рекурсия<br />

9. Какие свойства визуальных компонентов представлены массивами<br />

10. Как выполняется поиск максимального, минимального и среднего з<strong>на</strong>чений<br />

массива<br />

11. Каков принцип сортировки данных методом «пузырька»<br />

12. За сколько циклов выполняется сортировка методом «пузырька»<br />

13. Для чего функции qsort требуется в качестве аргумента авторская функция<br />

14. Чем структура отличается от массива<br />

15. В чем состоит разница между структурой и объединением<br />

16. Как инициализируются поля структуры<br />

17. Как рассчитывается объем памяти, занимаемый структурой<br />

18. Может ли структура содержать массив, а массив – структуру<br />

19. Какие известны ди<strong>на</strong>мические структуры данных<br />

20. Каков принцип действия стека<br />

21. Каков принцип действия очереди


22. Каков принцип действия линейного списка<br />

23. Каков принцип действия дека<br />

24. Чем определяется порядок обхода двоичного дерева<br />

25. Каково <strong>на</strong>з<strong>на</strong>чение стандартной библиотеки STL<br />

26. Каково <strong>на</strong>з<strong>на</strong>чение контейнеров и итераторов<br />

27. В чем состоит различие прямого и обратного итераторов<br />

28. Как организован контейнер map<br />

29. Как организован контейнер set<br />

30. Как подключается к проекту библиотека STL<br />

65<br />

Введение в модульное <strong>программирование</strong><br />

Требуется гораздо больше ума, чтобы передать<br />

свои мысли, чем чтобы их иметь<br />

Из Гельвеция<br />

Концепция модульного программирования предполагает конструирование<br />

программ из модулей, т. е. групп файлов каждая из которых освещает<br />

свой раздел общей проблемы. Текстовые, графические, исполнительные файлы,<br />

ресурсы и библиотеки тесно переплетаются в модулях. Модуль визуального<br />

программирования часто состоит из файлов отдельных форм: кода, заголовка,<br />

структуры и диаграммы, к которым после компиляции добавляются<br />

объектные файлы. Особого внимания требуют такие модульные системы, как<br />

библиотеки статической и ди<strong>на</strong>мической компоновки, справочные средства и<br />

потоки данных [7].<br />

Работа с текстовыми файлами<br />

Файл (file) представляет именованную область <strong>на</strong>копителя, пред<strong>на</strong>з<strong>на</strong>ченную<br />

для хранения, передачи и приема структурированной информации.<br />

Число одновременно открытых файлов и их размер определяются возможностями<br />

операционной системы и ресурсами компьютера.<br />

Ряд визуальных компонентов поддерживает файловый обмен своими<br />

методами SaveToFile и LoadFromFile. К ним относится строковый класс<br />

TStrings и производные от него классы.<br />

В частности, для загрузки и сохранения строк в текстовом файле используются<br />

методы SaveToFile и LoadFromFile компонента TStringList, орга-


66<br />

низующие обмен таким образом, что каждая строка файла соответствует<br />

строке списка. Компонент можно применять для заполнения элементов<br />

управления классов Memo и RichEdit или для сохранения содержимого элементов<br />

управления классов List и ComboBox, <strong>на</strong>пример:<br />

void StringLists1 () {<br />

TStringList *sl = new TStringList;<br />

sl–>Add (“Раз”);<br />

sl–>Add (“Два”);<br />

sl–>Add (“Три”);<br />

sl–>SaveToFile ("d:/Temp/File.txt"));<br />

sl–>Clear ();<br />

sl–>LoadFromFile ("d:/Temp/File.txt"));<br />

}<br />

// Заполнение поля Memo<br />

void StringLists2 () {<br />

AnsiString file = "test.INI";<br />

Form1–>Memo1–>Lines–>LoadFromFile (file);<br />

Form1–>Memo1–>Lines–>SaveToFile (ChangeFileExt (file,".BAK"));<br />

}<br />

// Списки в файл и из файла<br />

void _ _fastcall TForm1::StringListFilesClick (TObject *Sender) {<br />

AnsiString fName = "D:\\examples\\tempFile.txt";<br />

TStringList *sl = new TStringList;<br />

sl–>Add ("раз"); sl–>Add ("два"); sl–>Add ("три"); sl–>SaveToFile (fName);<br />

sl–>Clear (); sl–>LoadFromFile (fName);<br />

ListBox1–>Items–>LoadFromFile (fName); ComboBox1–>Items–>Assign (sl);<br />

delete sl;<br />

}<br />

// Строки в файл и из файла<br />

void __fastcall TForm1::ListFilesClick (TObject *Sender) {<br />

AnsiString fName ="tempFile.txt";<br />

StringListFilesClick (Sender);<br />

struct s { AnsiString as; int i; };<br />

s myS[3];<br />

for (int j=0; jItems–>Count; ++j) {<br />

myS[j].as = ListBox1–>Items–>Strings[j]; myS[j].i = j;<br />

}<br />

for (int j=0; j


StringGrid1–>Cells[0][j] = myS[j].as; StringGrid1–>Cells[1][j] = myS[j].i;<br />

}<br />

StringGrid1–>Cols[0]–>SaveToFile (fName);<br />

Memo1–>Lines–>LoadFromFile (fName);<br />

RichEdit1–>Lines–>LoadFromFile (fName);<br />

}<br />

67<br />

Для проведения файлового обме<strong>на</strong> без рассмотренных компонентов в<br />

C++Builder используются функции, приведенные в табл. 13. Работа с файлом<br />

ведется с помощью файловых переменных (file variable), связываемых с конкретными<br />

файлами. Файл открывается для чтения, записи или других операций.<br />

После работы файл следует закрыть.<br />

Таблица 13<br />

Функция<br />

Int FileCreate (AnsiString name)<br />

Int FileOpen (AnsiString name, int<br />

mode)<br />

Int FileWrite (int handle, void<br />

*buffer, int count)<br />

Int FileRead (int handle, void<br />

*buffer, int count)<br />

void FileClose(int handle)<br />

int FileSeek (int handle, int offset,<br />

int origin)<br />

bool DeleteFile (AnsiString name)<br />

bool FileExists (AnsiString name)<br />

bool DirectoryExists (AnsiString<br />

name)<br />

bool ForceDirectories (AnsiString<br />

dir)<br />

AnsiString FileSearch (AnsiString<br />

name, AnsiString dir)<br />

Функции файлового обме<strong>на</strong><br />

Наз<strong>на</strong>чение<br />

Создает новый или переписывает существующий<br />

файл с заданным именем, возвращая файловую переменную.<br />

Открывает файл в режимах (mode) fmOpenRead<br />

(чтение), fmOpenWrite (запись), fmOpenReadWrite<br />

(чтение-запись), возвращая файловую переменную.<br />

Пишет count байт в файл handle из *buffer, возвращая<br />

число записанных байтов<br />

Читает count байт из файла handle в *buffer, возвращая<br />

число прочитанных байтов.<br />

Закрывает файл.<br />

Позиционирует указатель в файле со смещением<br />

(offset) относительно базы (origin), равной 0 (<strong>на</strong>чало),<br />

1 (текущая позиция указателя) или 2 (конец<br />

файла), возвращая позицию указателя в файле.<br />

Удаляет файл.<br />

Проверяет <strong>на</strong>личие файла.<br />

Проверяет <strong>на</strong>личие папки.<br />

Создает папки.<br />

Выполняет поиск файла name в папках dir.<br />

Ниже даны примеры организации файлового обме<strong>на</strong>. Первый простой<br />

пример представляем вывод в файл содержания полей Edit1 и Edit2:<br />

void _ _fastcall TForm1::FieldsToFileClick (TObject *Sender) {<br />

AnsiString a = Edit1–>Text,<br />

b = Edit2–>Text;


int f = FileCreate ("tempFile.txt");<br />

if (f != –1) {<br />

FileWrite (f, a.c_str (), a.Length ());<br />

FileWrite (f, b.c_str (), b.Length ());<br />

FileClose (f);<br />

}<br />

}<br />

68<br />

Во втором примере создается файл, в него заносятся два числа, и файл<br />

закрывается. Затем файл открывается для чтения и возвращается его содержимое.<br />

void Files1 () {<br />

AnsiString name = "test.dat";<br />

if (FileExists (name))<br />

if (Application–>MessageBoxA ("Переписать файл",<br />

"Есть такой файл", MB_YESNO | MB_ICONQUESTION)!=IDYES) return;<br />

int iFile = FileCreate (name);<br />

if (iFile == –1) { ShowMessage ("Не создать файл"); return; }<br />

AnsiString first = MyForm–>Edit1–>Text;<br />

AnsiString second = MyForm–>Edit2–>Text;<br />

char *firstStr = first.c_str (), *secondStr = second.c_str ();<br />

int err = FileWrite (iFile, firstStr, first.Length ());<br />

if (err == –1) { ShowMessage ("Не записать файл"); return;}<br />

err = FileWrite (iFile, secondStr, second.Length ());<br />

if (err == –1) { ShowMessage ("Не дописать файл"); return; }<br />

FileClose (iFile);<br />

iFile = FileOpen (name, fmOpenRead);<br />

if (iFile == –1) { ShowMessage ("Не открыть файл"); return; }<br />

int readLen = first.Length () + second.Length ();<br />

char *getStr;<br />

getStr = new char[readLen + 1];<br />

err = FileRead (iFile, getStr, readLen);<br />

if (err == –1) { ShowMessage ("Не прочитать файл"); return; }<br />

FileClose (iFile);<br />

ShowMessage (AnsiString (getStr, readLen));<br />

}<br />

В третьем примере после открытия первого файла определяется его<br />

размер, указатель возвращается в <strong>на</strong>чало, и в массив читается его содержимое.<br />

Затем открывается второй файл для записи в него содержимого массива.


void Files2 () {<br />

AnsiString from = MyForm–>Edit1–>Text, to = MyForm–>Edit2–>Text;<br />

}<br />

try {<br />

int iFile = FileOpen (from, fmOpenRead);<br />

int len = FileSeek (iFile, 0, 2);<br />

FileSeek (iFile, 0, 0);<br />

char *c;<br />

c = new char [len + 1];<br />

len = FileRead (iFile, c, len);<br />

FileClose (iFile);<br />

iFile = FileOpen (to, fmOpenWrite);<br />

FileWrite (iFile, c, len);<br />

ShowMessage (from + " –> " + to + '\n' + AnsiString (c, len));<br />

delete [] c;<br />

FileClose (iFile);<br />

} catch (...) { ShowMessage ("Ошибка одной из файловых операций"); }<br />

69<br />

А<strong>на</strong>логичный пример посвящен отображению содержания файла в<br />

<strong>на</strong>дписи Label1:<br />

void _ _fastcall TForm1::LabelFromFileClick (TObject *Sender) {<br />

int f = FileOpen ("tempFile.txt", fmOpenRead);<br />

if (f != –1) {<br />

int size = FileSeek (f, 0, 2);<br />

char *s = new char[size+1];<br />

FileSeek (f, 0, 0);<br />

FileRead (f, s, size);<br />

Label1–>Caption = AnsiString (s, size);<br />

FileClose (f);<br />

delete [] s;<br />

}<br />

}<br />

Далее показано, как файл с текстом, указанным в Label1, дополняется<br />

содержимым поля Edit1:<br />

void _ _fastcall TForm1::InsertToFileClick (TObject *Sender) {<br />

int f = FileOpen ("tempFile.txt", fmOpenWrite);<br />

if (f != –1) {<br />

AnsiString is = Label1–>Caption, what = Edit1–>Text;<br />

is += what;<br />

Label2–>Caption = is;<br />

FileWrite (f, is.c_str (), is.Length ());


FileClose (f);<br />

}<br />

}<br />

70<br />

Следующий пример посвящен изменению содержимого файла. В<strong>на</strong>чале<br />

текст файла считывается в массив, который преобразуется в строку. В этой<br />

строке отыскивается заданное слово и заменяется фразой «[Удалено]», а<br />

файл заполняется пробелами. После этого изменен<strong>на</strong>я строка возвращается в<br />

файл.<br />

void Files3 () {<br />

AnsiString word = MyForm–>Edit1–>Text,<br />

file = MyForm–>Edit2–>Text,<br />

bText ="";<br />

try {<br />

int bFile = FileOpen (file, fmOpenReadWrite);<br />

int bLen = FileSeek (bFile, 0, 2);<br />

char *b = new char [bLen];<br />

FileSeek (bFile, 0, 0);<br />

FileRead (bFile, b, bLen);<br />

bText =AnsiString (b, bLen);<br />

delete [] b;<br />

int index = bText.AnsiPos (word.c_str());<br />

if (!index) {<br />

ShowMessage ("В файле нет такого слова");<br />

FileClose (bFile); return;<br />

}<br />

bText.Delete (index, word.Length ());<br />

bText.Insert ("[Удалено]", index);<br />

FileSeek (bFile, 0, 0);<br />

AnsiString c = AnsiString::StringOfChar (' ', bLen);<br />

FileWrite (bFile, c.c_str (), bLen);<br />

FileSeek (bFile, 0, 0);<br />

FileWrite (bFile, bText.c_str (), bText.Length ());<br />

FileClose (bFile);<br />

} catch (...) { ShowMessage ("Ошибка файловой операции"); }<br />

ShowMessage (bText);<br />

}<br />

Пример удаления фрагмента, заданного в Edit1, из файла, в котором содержится<br />

указан<strong>на</strong>я в Label1 фраза:


void _ _fastcall TForm1::DelFromFileClick (TObject *Sender) {<br />

AnsiString is = Label1–>Caption, what = Edit1–>Text, was = is;<br />

int f = is.AnsiPos (what.c_str ());<br />

ShowMessage (is+ ' ' +IntToStr (is.Length ()));<br />

If (f) is.Delete (f, what.Length ());<br />

ShowMessage (is + ' ' + IntToStr (is.Length ()));<br />

Label3–>Caption = is;<br />

f = FileOpen ("tempFile.txt", fmOpenWrite);<br />

if (f != –1) {<br />

is += AnsiString::StringOfChar (' ', was.Length ());<br />

FileWrite (f, is.c_str (), is.Length ());<br />

FileClose (f);<br />

}<br />

}<br />

71<br />

Еще один пример демонстрирует процесс поиска файла, в том числе с<br />

символом подстановки *, в текущей папке и в папке Windows:<br />

void Files4 () {<br />

char buffer[256];<br />

GetWindowsDirectory (buffer, sizeof (buffer));<br />

AnsiString file = FileSearch (MyForm–>Edit1–>Text, GetCurrentDir() +<br />

";" + AnsiString (buffer));<br />

if (file.IsEmpty ()) ShowMessage ("Нет файла " + MyForm–>Edit1–>Text);<br />

else ShowMessage ("Есть файл " + file + " !");<br />

}<br />

Работа с графическими файлами<br />

К графическим компонентам, поддерживающим файловый обмен<br />

своими методами SaveToFile и LoadFromFile, относятся TPicture, TOLE-<br />

Graphic, TGraphic и производные от них классы. Рассмотрим несколько примеров:<br />

Пример использования графических файлов:<br />

// Картинки из файлов<br />

void __fastcall TForm1::GraphFilesClick (TObject *Sender){<br />

AnsiString fName = "D:\\examples\\scan.bmp";<br />

Image1–>Picture–>LoadFromFile (fName);<br />

Form1–>Icon–>LoadFromFile ("scan.ico");<br />

Graphics::TBitmap *gb = new Graphics::TBitmap;<br />

gb–>LoadFromFile (fName); PaintBox1–>Canvas–>Draw (0, 0, gb);<br />

this–>Canvas–>CopyMode = cmSrcPaint;


this–>Canvas–>CopyRect<br />

(ClientRect, PaintBox1–>Canvas, PaintBox1–>ClientRect);<br />

delete gb;<br />

}<br />

// Кнопки с картинками из файла<br />

void _ _fastcall TForm1::BtnFilesClick (TObject *Sender) {<br />

AnsiString fName = "D:\\examples\\scan.bmp";<br />

BitBtn1–>Glyph–>LoadFromFile (fName);<br />

SpeedButton1–>Glyph–>LoadFromFile (fName);<br />

}<br />

72<br />

Некоторые особенности использования компонента Image отражает<br />

табл. 14.<br />

Таблица 14<br />

Свойство<br />

Picture<br />

Width, Height<br />

Proportional<br />

Strech<br />

AutoSize<br />

Center<br />

Visible<br />

Canvas<br />

Свойства компонента Image<br />

Наз<strong>на</strong>чение<br />

Отображаемый файл<br />

Шири<strong>на</strong> и высота<br />

Автоматическое масштабирование без искажения иллюстрации в границах<br />

элемента управления<br />

Подгонка иллюстрации под размер элемента управления с искажением<br />

Подгонка элемента управления под размер иллюстрации<br />

Размещение иллюстрации в центре элемента управления<br />

Вывод <strong>на</strong> экран<br />

Экран<strong>на</strong>я поверхность для отображения<br />

Компонент поддерживает графические форматы .BMP, .ICO и .WMF.<br />

Для работы с форматом .JPG <strong>на</strong>до подключить файл заголовка .<br />

Следующий пример показывает функцию, отображающую первый из<br />

графических файлов, размещенных в папке приложения, а также вариант ее<br />

использования:<br />

#include <br />

#include // для работы функции SelectDirectory<br />

AnsiString myPath = “”; // текущая папка с иллюстрациями<br />

TSearchRec sr;<br />

// результат поиска<br />

void TForm1::FirstPicture () {<br />

if (FindFirst (myPath + “*.jpg”, faAnyFile, sr) == 0) {<br />

Image1–>Picture–>LoadFromFile (myPath + sr.Name);<br />

Label1–>Caption = sr.Name;<br />

}


73<br />

Фрагмент обработчика кнопки перехода <strong>на</strong> следующий файл текущей<br />

папки может быть таким:<br />

if (FindNext (sr) == 0) {<br />

Image1–>Picture–>LoadFromFile (myPath + sr.Name);<br />

Label1–>Caption = sr.Name;<br />

}<br />

В свою очередь, обработчик кнопки поиска файлов открывает стандартное<br />

окно поиска папки с файлами и вновь вызывает функцию отображения<br />

первого файла папки:<br />

if (SelectDirectory ("Выбор папки", "", myPath))<br />

{ myPath += "\\"; FirstPicture (); }<br />

Визуальный компонент Bitmap поддерживает работу с битовыми образами<br />

– небольшими картинками формата .BMP, которыми удобно манипулировать<br />

в памяти компьютера, обращаясь к свойствам (табл. 15) и методам<br />

компонента.<br />

Таблица 15<br />

Свойство<br />

Width, Height<br />

Empty<br />

Transparent<br />

TransparentColor<br />

Canvas<br />

Свойства компонента Bitmap<br />

Наз<strong>на</strong>чение<br />

Шири<strong>на</strong> и высота загружаемой иллюстрации<br />

Приз<strong>на</strong>к отсутствия иллюстрации<br />

Уста<strong>на</strong>вливает «прозрачность», т. е. не отображает цвет, указанный в<br />

свойстве TransparentColor, по умолчанию – цвет нижнего левого<br />

пиксела<br />

Прозрачный цвет, который не выводится <strong>на</strong> экран при установленном<br />

свойстве Transparent<br />

Экран<strong>на</strong>я поверхность для отображения<br />

Следующий пример демонстрирует мультипликацию – движение<br />

битового образа sprite <strong>на</strong> фоне иллюстрации back. Рисование образа по канве<br />

Canvas выполняется методом канвы Draw в событии формы FormPaint по командам<br />

невидимого объекта класса Timer (Interval 100 мс). Коорди<strong>на</strong>ты x, y и<br />

объекты back и sprite созданы как переменные формы:<br />

TForm1 *Form1;<br />

int x, y;<br />

Graphics::TBitmap *back = new Graphics::TBitmap;<br />

Graphics::TBitmap *sprite = new Graphics::TBitmap;<br />

__fastcall TForm1::TForm1 (TComponent* Owner) : TForm (Owner) {<br />

back–>LoadFromFile ("1.bmp");


sprite–>LoadFromFile ("2.bmp");<br />

sprite–>Transparent = true;<br />

x = –20;<br />

// исход<strong>на</strong>я позиция за пределами формы<br />

y = 20;<br />

}<br />

void __fastcall TForm1::FormPaint (TObject *Sender) {<br />

Canvas–>Draw (0, 0, back);<br />

// отображение фо<strong>на</strong><br />

Canvas–>Draw (x, y, sprite);<br />

// и объекта<br />

}<br />

void __fastcall TForm1::Timer1Timer (TObject *Sender) {<br />

TRect reback;<br />

// восста<strong>на</strong>вливаемая область фо<strong>на</strong><br />

reback = Rect (x, y, x+sprite–>Width, y+sprite–>Height);<br />

Canvas–>CopyRect (reback, back–>Canvas, reback); // восстановление<br />

x += 2;<br />

// движение вправо<br />

if (x > ClientWidth) x = –20;<br />

// возврат<br />

Canvas–>Draw (x, y, sprite);<br />

// рисование объекта<br />

}<br />

74<br />

Работа с двоичными файлами<br />

Разработано множество способов запуска исполнительных файлов из<br />

приложений C++.<br />

В файле заголовка объявле<strong>на</strong> группа функций семейства<br />

exec… Эти функции загружают в память и открывают исполнительные файлы<br />

(дочерние процессы), одновременно лишая ресурсов и прекращая родительский<br />

процесс. Первым параметром этих функций всегда является путь к<br />

вызываемому дочернему процессу, а при запуске каждая такая функция передает<br />

несколько дополнительных параметров этому процессу:<br />

exec…(путь, параметры).<br />

Например:<br />

execl (“MyFile.exe”, “MyFile.exe”, NULL);<br />

Суффиксы l, v, p, e, добавляемые к имени exec, раскрывают требования<br />

к параметрам. Так, l свидетельствует о постоянном фиксированном числе параметров,<br />

v говорит о непостоянном массиве указателей в списке параметров,<br />

p указывает, что путь следует искать в переменной окружения path, а e – в<br />

списке параметров окружения дочернего процесса.<br />

В случае неудачного запуска exec… возвращает –1, а глобаль<strong>на</strong>я перемен<strong>на</strong>я<br />

errno уста<strong>на</strong>вливается в одно из следующих з<strong>на</strong>чений: EACCESS –


75<br />

отказ в доступе, EMFILE – много открытых файлов, ENOENT – не <strong>на</strong>йден<br />

путь, ENOEXEC – мало памяти.<br />

В файле заголовка shellapi.h объявле<strong>на</strong> серия полезных функций запуска<br />

приложений без закрытия родительского процесса. Среди них – WinExec,<br />

ShellExecute, CreateProcess и другие. В числе их параметров всегда присутствует<br />

режим ок<strong>на</strong>, который задается константами SW_MINIMIZE,<br />

SW_MAXIMIZE, SW_NORMAL, SW_RESTORE, SW_HIDE. Примеры:<br />

WinExec (“MyFile”, SW_RESTORE);<br />

ShellExecute (Handle, NULL, “MyFile.exe”, NULL, NULL, SW_RESTORE);<br />

ShellExecute (Handle, “open”, “File.doc”, NULL, NULL, SW_RESTORE);<br />

К двоичным файлам относятся, также, растровые файлы, з<strong>на</strong>чки, курсоры<br />

и файлы ресурсов (.RES). Все они просматриваются, создаются и редактируются<br />

графическим редактором Image Editor, доступным через меню<br />

Tools. Редактор достаточно прост в использовании и во многом <strong>на</strong>поми<strong>на</strong>ет<br />

известный редактор Microsoft Paint.<br />

Модули DLL и пакеты<br />

Одним из самых распространенных способов создания модульных приложений<br />

являеся использование библиотек ди<strong>на</strong>мической компоновки DLL и<br />

BPL. Такие библиотеки представляют особый вид выполняемого кода (компилированных<br />

функций, констант, форм, ресурсов), хранящийся в файлах с<br />

расширениями .DLL и .BPL, которые рассчитаны <strong>на</strong> одновременное использование<br />

несколькими программами. Пакеты (package) BPL ос<strong>на</strong>щены средствами<br />

работы с компонентами VCL. Они располагают большей, чем DLL,<br />

информацией о вызывающем приложении, а приложение, в свою очередь,<br />

лучше контролирует поведение связанного модуля. К сожалению BPL обслуживают<br />

только приложения C++Builder, тогда как DLL более универсальны<br />

и работают с любыми приложениями в разных операционных системах.<br />

Модули DLL и BPL можно подключать к приложению статически (design<br />

time mode) и ди<strong>на</strong>мически (runtime mode). Статически загружаемые модули<br />

связываются с главной программой при ее компоновке и остаются загруженными<br />

в память все время, пока выполняется приложение. Такие модули<br />

содержат экспортируемые функции, описание которых помещается в файлы<br />

библиотек импорта LIB – Library Import File, которые также подключаются<br />

<strong>на</strong> этапе компоновки. Ди<strong>на</strong>мически загружаемые модули в процессе работы<br />

при необходимости можно выгружать из памяти, освобождая при этом ре-


76<br />

сурсы. В ряде режимов возмож<strong>на</strong> работа таких приложений и без них. Дополнительные<br />

библиотеки импорта здесь не используются.<br />

Библиотека DLL создается командой File.New из шабло<strong>на</strong> DLLWizard. В<br />

состав проекта автоматически включаются файл проекта (<strong>на</strong>пример,<br />

myDLL.BPR) и файл модуля DLL (myDLL.CPP) без формы, которые следует<br />

дополнить файлом заголовка (myDLL.h) и при необходимости – файлами<br />

требуемых форм. Пример файла модуля, формирующего произвольное сообщение<br />

авторской функцией Message и открывающего форму О программе<br />

другой функцией About:<br />

//myDLL.CPP<br />

#include "myDLL.h"<br />

int WINAPI DllEntryPoint (HINSTANCE hinst,<br />

unsigned long reason, void* lpReserved) { return 1; }<br />

void Message (char *s) { ShowMessage ((AnsiString) s + " из DLL"); }<br />

void About (char *s) {<br />

Form1 = new TForm1 (0);<br />

Form1–>Label1–>Caption = (AnsiString) s + " из DLL";<br />

Form1–>ShowModal ();<br />

delete Form1;<br />

}<br />

Функция DllEntryPoint является главной функцией DLL. Функции Message<br />

и About здесь служат примерами библиотечных функций, которые будут<br />

подключаться к приложению с выводом передаваемой оттуда строки.<br />

Объявление этих функций как функций экспорта следует поместить в файл<br />

заголовка:<br />

extern "C" void __declspec (dllexport) Message (char *s);<br />

extern "C" void __declspec (dllexport) About (char *s);<br />

Лексема __declspec со спецификатором класса памяти dllexport гарантирует<br />

совместимость библиотеки с Microsoft C/C++. После компоновки проекта<br />

будет создан файл библиотеки (.LIB) и исполняемый файл (.DLL).<br />

Для статического подключения DLL к имеющемуся проекту <strong>на</strong>до добавить<br />

в него файл .LIB (Project.Add to Project) из проекта DLL и включить<br />

файл заголовка из DLL. После компоновки можно обращаться к хранящимся<br />

там данным.<br />

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


77<br />

//myApplication.cpp<br />

#include "myDLL.h"<br />

void __fastcall TForm1::Button1Click (TObject *Sender) { Message ("Привет "); }<br />

void __fastcall TForm1::Button2Click (TObject *Sender) { About ("Привет "); }<br />

Ди<strong>на</strong>мическое подключение выполняется и<strong>на</strong>че:<br />

void _ _fastcall TForm1::Button1Click (TObject *Sender) {<br />

HINSTANCE dllp = LoadLibrary ("myDLL.dll");<br />

if (dllp) {<br />

void (_ _stdcall *p) (char *s);<br />

p = (void (_ _stdcall *) (char*)) GetProcAddress (dllp,"_Message");<br />

if (p) p ("Привет ");<br />

else ShowMessage ("Нет функции DLL");<br />

FreeLibrary (dllp);<br />

}<br />

else ShowMessage ("Нет файла DLL");<br />

}<br />

Функция LoadLibrary возвращает тип HINSTANCE из библиотеки Windows<br />

API и загружает модуль DLL, либо NULL при его отсутствии. Функция<br />

GetProcAddress присваивает з<strong>на</strong>чение указателя функции DLL. Затем вызывается<br />

рабочая функция, а библиотека DLL выгружается из памяти функцией<br />

FreeLibrary.<br />

А<strong>на</strong>логичным образом экспортируются целые классы DLL.<br />

Пакеты BPL создаются <strong>на</strong> базе шабло<strong>на</strong> Package. При проектировании<br />

пакета используются файлы со следующими расширениями:<br />

.BPK – исходный файл параметров проекта в формате XML;<br />

.BPL – runtime-пакет, представляющий разновидность .DLL со специальными<br />

функциями C++Builder;<br />

.BPI – библиотека импорта пакета;<br />

.CPP – файл исходного текста с главной функцией проекта DllEntry-<br />

Point;<br />

.H – файл заголовка;<br />

.LIB – статическая библиотека (коллекция файлов OBJ) для подключения<br />

в режиме design time, если установлен параметр компиляции GI (Generate<br />

LIB file);<br />

.OBJ – двоичный образ модуля пакета.


78<br />

Для ди<strong>на</strong>мического подключения пакетов вызывающее приложение<br />

следует ос<strong>на</strong>щать функциями LoadPackage и FreePackage вместо LoadLibrary<br />

и FreeLibrary.<br />

Ресурсы<br />

Ресурсы – это битовые образы, хранящиеся непосредственно в исполнительном<br />

файле, а не отдельно от приложения. При создании приложения<br />

ресурсы помещаются в файл ресурсов проекта .RES автоматически или командой<br />

SaveToResource и могут загружаться из него командой LoadFromResource.<br />

Ресурсы создаются с помощью встроенного в C++Builder графического<br />

редактора Image Editor и им же включаются в файл ресурсов приложения как<br />

графические образы Bitmaps, указатели мыши Cursors и з<strong>на</strong>чки Icons. Этот<br />

же редактор используется для создания отдельных файлов типов .RES, .BMP,<br />

.ICO, .CUR, которые впоследствии могут быть подключены к другим проектам.<br />

Например, для добавления в текущий файл ресурсов нового з<strong>на</strong>чка <strong>на</strong>до<br />

в меню File.Open редактора Image Editor открыть файл ресурсов проекта<br />

(.RES). Далее через меню Resource.New.Icon создать новый з<strong>на</strong>чок и, выделив<br />

его, открыть окно редактора (Resource.Edit), <strong>на</strong>рисовать или импортировать<br />

з<strong>на</strong>чок и закрыть окно. Через меню Resource.Rename <strong>на</strong>звать з<strong>на</strong>чок,<br />

<strong>на</strong>пример, MYICON. и сохранить его.<br />

А<strong>на</strong>логичным образом через меню Resource.New.Cursor создать новый<br />

курсор, открыть окно редактора, <strong>на</strong>рисовать или импортировать курсор и закрыть<br />

окно. Через меню Resource.Rename <strong>на</strong>звать курсор, <strong>на</strong>пример,<br />

MYCURSOR. В заключение остается сохранить файл ресурсов и закрыть<br />

Image Editor.<br />

Для создания нового файла ресурсов с графическим образом дается команда<br />

New.Resource File в меню File. В окне ресурсов новый рисунок формируется<br />

по команде Resource.New.Bitmap редактором Resource.Edit или копируется<br />

через буфер обме<strong>на</strong> и сохраняется (File.Save) в одной папке с приложением.<br />

Пользуясь Image Editor, таким же путем создаются отдельные файлы<br />

з<strong>на</strong>чков и курсоры.


79<br />

Для подключения нового файла ресурсов к проекту вводится директива<br />

препроцессора #pragma resource "имяФайла.res" и дается команда загрузки,<br />

<strong>на</strong>пример:<br />

Image1–>LoadFromResourceName ((int)HInstance, “имяРесурса”)<br />

Примеры подключения новых курсоров и з<strong>на</strong>чков:<br />

const TCursor cur = 5;<br />

// свобод<strong>на</strong>я константа<br />

Screen–>Cursors[cur] = LoadCursor (HInstance, "MYCURSOR");<br />

Cursor = cur;<br />

// новый курсор<br />

Icon–>Handle = LoadIcon (HInstance, "MYICON"); // новый з<strong>на</strong>чок<br />

З<strong>на</strong>чки и курсоры из отдельных файлов подключаются методом<br />

LoadFromFile (имяФайла.ico) объекта Icon и функцией LoadCursorFromFile<br />

(имяФайла.cur) вместо LoadCursor.<br />

Для работы с обновленным файлом ресурсов следует закрыть приложение,<br />

затем вновь открыть и перестроить его.<br />

Потоки<br />

Потоки – это отдельно работающие части одной программы. Многие<br />

программы используют несколько потоков, среди которых можно выделить<br />

первичный поток, отвечающий за создание окон и обработку сообщений, и<br />

вторичные потоки, в которых выполняются фоновые операции: загрузка<br />

больших файлов, проведение вычислений и т. п.<br />

В <strong>С++</strong>Builder потоками управляет абстрактный класс TThread, и каждый<br />

вторичный поток представляет отдельный экземпляр <strong>на</strong>следника этого<br />

класса.<br />

Поток создается через меню File.New <strong>на</strong> базе шабло<strong>на</strong> TThread Object в<br />

виде модуля без формы и кроме обычного конструктора содержит заготовку<br />

исполнительного метода Execute. В отличие от конструктора формы, конструктор<br />

потока имеет параметр CreateSuspended. Когда он установлен (true),<br />

созданный поток сразу не запускается, а ожидает вызова стартового метода<br />

Resume. В противном случае сразу вслед за созданием происходит запуск<br />

потока.<br />

Наиболее распространенные характеристики класса TThread приведены<br />

в табл. 16.


80<br />

Свойства и методы объектов Thread<br />

Таблица 16<br />

Свойство, метод<br />

FreeOnTerminate<br />

Handle<br />

Priority<br />

ReturnValue<br />

Suspended<br />

Terminated<br />

ThreadID<br />

DoTerminate ()<br />

Execute ()<br />

Resume ()<br />

Suspend ()<br />

Synchronize ()<br />

Terminate ()<br />

WaitFor ()<br />

Наз<strong>на</strong>чение<br />

Автоматическое удаление потокового объекта по завершении работы<br />

потока<br />

Дескриптор потока для вызова функций API<br />

Приоритет потока<br />

Возвращаемое з<strong>на</strong>чение по завершении работы потока<br />

Приз<strong>на</strong>к приостановки работы потока<br />

Приз<strong>на</strong>к прекращения работы потока<br />

Идентификатор потока<br />

Вызов обработчика события OnTerminate без прекращения работы<br />

потока<br />

Код, выполняемый при запуске потока<br />

Возобновление работы приостановленного потока<br />

Приостановка работы потока<br />

Синхронизация обращения к библиотеке VCL в первичном потоке<br />

Прекращение работы потока<br />

Ожидание прекращения работы потока<br />

В метод Execute программист должен поместить код, который будет<br />

выполняться после загрузки потока в фоновом режиме по отношению к первичному<br />

потоку. Кроме того, в метод Execute обычно включают еще три выражения:<br />

if (Terminated) break;<br />

FreeOnTerminate = true;<br />

// для прекращения работы потока<br />

// для автоматического освобождения памяти<br />

// потоком после завершения его работы<br />

Synchronize (имяФункции); // для синхронизации доступа к объектам<br />

// библиотеки VCL, за исключением<br />

// графических объектов<br />

Функцию, имя которой выступает в качестве аргумента метода Synchronize,<br />

объявляют и определяют в этом же файле потока, <strong>на</strong>пример:<br />

void __fastcall TThread1::Display () { Form1–>Label1–>Caption = “Пример”; }<br />

Для работы с формой в файл кода потока включают заголовок файла<br />

формы. Если в методе Execute присутствует код работы с графикой, его обрамляют<br />

выражениями, блокирующими вывод графики из других потоков:<br />

Form1–>Image1–>Сanvas1–>Lock ();<br />

Вывод графики <strong>на</strong> канву<br />

Form1–>Image1–>Сanvas1–>Unlock ();


81<br />

Управление созданным потоком осуществляется из первичного потока,<br />

обычно – из формы. Для этого в файл кода формы включается заголовок<br />

файла потока. В класс формы добавляется ссылка <strong>на</strong> объект потока, <strong>на</strong>пример,<br />

TThread1 *thread, в конструкторе формы создается экземпляр потока,<br />

который потом используется по ходу программы:<br />

…<br />

TThread1 *thread;<br />

…<br />

thread = new TThread1 (true);<br />

…<br />

thread->Resume ();<br />

…<br />

thread–>Suspend ();<br />

…<br />

thread–>Terminate ();<br />

…<br />

// запуск потока<br />

// приостановка<br />

// завершение<br />

Дистрибутивы<br />

Установка приложений <strong>на</strong> компьютере пользователя обычно производится<br />

с помощью инсталляционного пакета – дистрибутива. Такие файловые<br />

пакеты готовятся с использованием программы InstallShield Express, которая<br />

создает проект – Setup Project – и в специальной среде разработки помогает<br />

программисту выполнить необходимые этапы подготовки дистрибутива.<br />

На этапе организации дистрибутива вводятся основные сведения (general<br />

information) об авторе, фирме, теме и <strong>на</strong>звании программного продукта,<br />

которые при установке будут занесены в реестр для последующего изменения<br />

или удаления приложения. Здесь же <strong>на</strong>з<strong>на</strong>чается папка для установки<br />

(INSTALLDIR) и приводится дополнитель<strong>на</strong>я информация. Разработчик определяет<br />

составные части (features) дистрибутива, его программные, графические,<br />

библиотечные, справочные файлы и разделяет их <strong>на</strong> две группы.<br />

Первая часть файлов уста<strong>на</strong>вливается в обязательном порядке (always install),<br />

тогда как вторая – по желанию пользователя (custom setup). В зависимости от<br />

<strong>на</strong>личия или отсутствия второй части <strong>на</strong>мечаются три способа установки<br />

продукта: Typical – автоматическое развертывание <strong>на</strong>иболее важных с точки<br />

зрения разработчика файлов, Minimal – автоматическое развертывание минимально<br />

допустимого <strong>на</strong>бора файлов, Custom – автоматическая установка


82<br />

обязательных файлов и произвольное развертывание файлов, выбираемых<br />

пользователем.<br />

На этапе определения состава приложения (specify application data)<br />

формируется файловый раздел дистрибутива. Здесь интегрирован<strong>на</strong>я среда<br />

InstallShield Express становится подобной Проводнику Windows, поддерживая<br />

традиционные механизмы работы с файлами: буфер обме<strong>на</strong> и перетаскивание<br />

(drag & drop). Пользуясь этим инструментом, разработчик копирует<br />

файлы из папок своего компьютера (source computer) в будущие папки машины<br />

пользователя (destination computer). Здесь же определяется потребность<br />

в дополнительных модулях и библиотеках (merge modules) и проверяется<br />

зависимость файлов приложения от программной среды (dependencies).<br />

На следующем этапе проектирования дистрибутива планируются действия<br />

по конфигурированию системы пользователя (target system). При этом<br />

задаются папки и ярлыки, которые потребуется создать; изменения, которые<br />

предстоит провести в меню, в реестре и в файлах инициализации (.ini); ресурсы,<br />

драйверы, базы и источники данных, которые необходимо сгенерировать.<br />

Задаются типы файлов (file extensions), которые развертываемое приложение<br />

будет обрабатывать особым образом.<br />

Далее формируется интерфейс пользователя дистрибутива. Разработчику<br />

предлагаются 12 стандартных окон диалога и баннеры для их оформления.<br />

В состав окон, которые можно использовать в ходе установки, входят:<br />

стартовое окно (Install Welcome), лицензионное соглашение (EULA – End-<br />

User License Agreement), комментарии к установке (Readme), сведения о<br />

пользователе (Customer Information), путь развертывания приложения (Destination<br />

Folder), выбор типа установки (Tipical/Minimal/Custom), стартовое<br />

предупреждение (Ready To Install), <strong>на</strong>блюдение за ходом установки (Setup<br />

Progress), завершение (Complete Success) и др.<br />

В ходе подготовки дистрибутива формируются, также, требования к<br />

аппаратному и программному обеспечению пользователя, производится создание<br />

дистрибутивных носителейи выполняется тестирование созданного<br />

продукта. Не покидая среды InstallShield Express, разработчик может провести<br />

имитацию установки приложения <strong>на</strong> своей машине, а также пробную установку<br />

программы.<br />

Проект сохраняется для последующего совершенствования или использования<br />

при создании других дистрибутивов.


83<br />

Контрольные вопросы<br />

1. Какие визуальные компоненты поддерживают файловые метолы<br />

2. Как <strong>на</strong>зывается и для чего пред<strong>на</strong>з<strong>на</strong>чено з<strong>на</strong>чение, возвращаемое функциями<br />

FileCreate и FileOpen<br />

3. Для чего пред<strong>на</strong>з<strong>на</strong>че<strong>на</strong> функция FileSeek и какие аргументы требуются<br />

для ее вызова<br />

4. Какие параметры имеет функция FileWrite<br />

5. Как программируется работа с графическими файлами<br />

6. В чем состоит принцип мультипликации<br />

7. Какие функции используются для открытия исполнительных файлов<br />

8. Как выполняется статическая компоновка<br />

9. Как выполняется ди<strong>на</strong>мическая компоновка<br />

10. В чем заключаются преимущества и недостатки пакетов перед библиотеками<br />

DLL<br />

11. Какие ресурсы можно использовать в приложениях<br />

12. В чем различие между файлом ресурсов и встроенными в приложение ресурсами<br />

13. Для чего используется многопоточное <strong>программирование</strong><br />

14. Для чего служит перемен<strong>на</strong>я FreeOnTerminate класса TThread<br />

15. Зачем нужен параметр в конструкторе класса TThread<br />

16. Что входит в состав дистрибутива<br />

17. Какие этапы сопровождают установку программного продукта<br />

Приложения баз данных<br />

У памяти хороший вкус<br />

Французское изречение<br />

База данных – это хранилище информации, организуемое и обрабатываемое<br />

в <strong>на</strong>копителях в соответствии с определенными правилами хранения<br />

и доступа. Логическая автономность данных является первым существенным<br />

отличием баз данных от прочего программного обеспечения. Строго<br />

оговорен<strong>на</strong>я при<strong>на</strong>длежность данных к определенным классам и их безуслов<strong>на</strong>я<br />

типизация отличает базу данных от текстовых и табличных процессоров,<br />

а широкая гамма допустимых операций <strong>на</strong>д множествами является важным<br />

преимуществом ее перед пакетами прикладных программ и системами


84<br />

программирования. Выделение базы данных как особой части программного<br />

обеспечения способствует эффективной структуризации информации,<br />

сведению к минимуму повторяющихся данных, ускорению обработки информации<br />

прямо <strong>на</strong> носителе, удобству обновления документов, обеспечению<br />

целостности данных, регулированию прав доступа к информации, облегчению<br />

автоматизации обработки данных и ведения отчетности.<br />

C++Builder позволяет создавать эффективные СУБД – системы управления<br />

базами данных (DBMS).<br />

Технологии создания приложений баз данных<br />

Для поддержки приложений баз данных разработаны различные технологии,<br />

приведенные в табл. 17.<br />

Таблица 17<br />

Технология<br />

ADO<br />

BDE<br />

dbExpress<br />

Технологии доступа к базам данных<br />

Область применения<br />

Все базы данных, поддерживающие стандарт OLE DB<br />

Реляционные базы данных xBase, Paradox, InterBase<br />

Различные базы данных<br />

Универсаль<strong>на</strong>я технология ADO – ActiveX Database Objects – разработа<strong>на</strong><br />

в «Microsoft» в соответствии со стандартом OLE DB (Object Linking and<br />

Embedding of DataBases). О<strong>на</strong> является развитием ODBC (Open DataBase<br />

Connectivity) – открытой двухуровневой системы подключения баз данных,<br />

ставшей стандартом для большинства приложений. Верхний уровень (клиент<br />

ODBC) играет роль интерфейса с приложением и поддерживает стандартные<br />

функции API. Нижний уровень выполняет функции специализированного<br />

драйвера преобразования информации в соответствии с форматом конкретной<br />

СУБД (сервера ODBC). Средства поддержки ADO строятся как модели<br />

составных объектов COM (Component Object Model). Технология ориентирова<strong>на</strong><br />

<strong>на</strong> Интернет (HTTP, InternetExpress, FastNet) и сети, работающие под<br />

Windows, при взаимодействии с любыми СУБД, ос<strong>на</strong>щенными драйверами<br />

ODBC. Кроме того, ADO предлагает разработчикам поддержку данных, хранящихся<br />

в не реляционном формате, <strong>на</strong>пример XML или сообщений электронной<br />

почты. Специальные версии ADO относятся к категории распределенных<br />

(многоуровневых) архитектур. В них программы разделяются <strong>на</strong> секции,<br />

взаимодействующие <strong>на</strong> основе разнообразных протоколов. Это CORBA


85<br />

для построения масштабируемых приложений, не зависящих от платформ;<br />

DCOM для реализации удаленного доступа; MIDAS для обслуживания приложений<br />

клиент/сервер.<br />

Базовая одноуровневая технология BDE – Borland Database Engine поддерживает<br />

форматы баз данных группы xBase, такие как dBase, FoxPro, Clipper,<br />

а также Paradox, текстовый формат с разделителями (ASCII-Delimited<br />

Text) и стандарт ODBC. Реализован<strong>на</strong>я в виде <strong>на</strong>бора системных файлов<br />

DLL, технология BDE удовлетворяет небольшим дешевым производительным<br />

приложениям. Для работы с корпоративными базами данных Oracle,<br />

SQL Server, DB2, InterBase между BDE и базой данных может быть помещен<br />

дополнительный уровень SQL Links – Structured Query Language Links. Архитектура<br />

BDE/SQL Links реализует более производительный сетевой обмен по<br />

технологии клиент/сервер. Выражения <strong>на</strong> языке SQL, непосредственно внедренные<br />

в программный код, позволяют манипулировать данными через<br />

BDE, обеспечивая <strong>на</strong>ивысшую производительность, хотя при этом игнорируются<br />

визуальные компоненты и связанные с ними преимущества быстрой<br />

разработки приложений.<br />

Специализированные технологии dbExpress предусматривают высокоскоростное<br />

подключение к каждому конкретному типу базы данных с учетом<br />

особенностей операционной среды. Компромиссным решением служит подключение<br />

к ODBC через специализированные компоненты, поставляемые<br />

сторонними производителями под API конкретных СУБД. Популярен и вариант<br />

API СУБД без использования технологии визуальной разработки. Он<br />

применяется в простых базах данных и поддерживается их разработчиками<br />

самостоятельно за счет средств доступа к данным самой СУБД.<br />

Обычно для построения приложений баз данных используется организация,<br />

состоящая из нескольких своеобразных программных «слоев»<br />

(рис. 2, а):<br />

• интерфейс пользователя (user interface);<br />

• компоненты связи интерфейса пользователя с <strong>на</strong>борами данных,<br />

именуемые источниками данных (data sources);<br />

• <strong>на</strong>боры данных (datasets), представляющие записи таблиц и запросов<br />

баз данных;<br />

• средства подключения к базам данных (data connections).


Программа<br />

86<br />

Программа<br />

Application server<br />

Интерфейс<br />

(User interface)<br />

Источник данных<br />

(Data source)<br />

Набор данных<br />

(Dataset)<br />

Подключение<br />

(Data connection)<br />

Данные<br />

Интерфейс<br />

пользователя<br />

Источник данных<br />

клиента<br />

Набор данных<br />

клиента<br />

Подключение<br />

к серверу<br />

Client<br />

Источник сервера<br />

(Provider)<br />

Целевой<br />

<strong>на</strong>бор данных<br />

(Unidirectional<br />

data source)<br />

SQL-соединение<br />

(SQL connection)<br />

Сервер базы данных<br />

(Database server)<br />

а. б.<br />

Рис. 2. Варианты организации приложений баз данных<br />

Интерфейс пользователя строится <strong>на</strong> базе традиционных форм. Источники<br />

данных связывают интерфейс пользователя с <strong>на</strong>борами данных. При<br />

этом несколько элементов управления интерфейса можно подключать к одному<br />

источнику данных для синхронной обработки одной и той же информации.<br />

Основу приложений составляют <strong>на</strong>боры данных, представляющие даталогические<br />

модели одной или нескольких таблиц или запросов и отражающие<br />

состояние и изменение физических данных носителя информации. Каждой<br />

таблице или запросу ставится в соответствие свой <strong>на</strong>бор данных.<br />

Архитектуры приложений баз данных<br />

Наборы данных различных типов используют разнообразные механизмы<br />

подключения к данным, которые, в свою очередь, определяют архитектуру<br />

приложений баз данных. К основным способам подключения относятся:<br />

• прямое подключение;<br />

• подключение через вспомогательный <strong>на</strong>бор данных;<br />

• архитектура «клиент-сервер».


87<br />

В C++Builder все подключения по технологии ADO производятся через<br />

компонент TADOConnection вкладки ADO панели визуальных компонентов,<br />

источниками данных служат визуальные компоненты TADOTable,<br />

TADOQuery, TADOStoredProc, TADODataSet, TADOCommand вкладки ADO,<br />

а <strong>на</strong>боры данных обычно представляются компонентом TDataSource вкладки<br />

Data Access.<br />

Подключения по технологии BDE выполняются через компонент<br />

TDataBase без <strong>на</strong>боров данных или с TDataSource, а роль источников данных<br />

здесь играют компоненты TTable, TQuery, TStoredProc вкладки BDE панели<br />

визуальных компонентов. Для работы с корпоративными базами данных<br />

InterBase служат компоненты TIBDataBase, TIBTable, TIBQuery, TIB-<br />

StoredProc вкладки InterBase.<br />

Технологии dbExpress для подключения используют компонент<br />

TSQLConnection, источниками данных являются TSQLTable, TSQLQuery,<br />

TSQLStoredProc вкладки dbExpress, а <strong>на</strong>боры данных представляет<br />

TSQLDataSet.<br />

Прямое подключение обычно используется для организации постоянных<br />

соединений, которые уста<strong>на</strong>вливаются в <strong>на</strong>чале сеанса связи с базой<br />

данных и разрываются в конце сеанса. Такое подключение просто реализуется,<br />

но работает только в однопользовательских системах без разграничения<br />

прав доступа. Технологиями BDE и dbExpress поддерживается подключение<br />

через вспомогательные <strong>на</strong>боры данных TBDEClientDataSet, TIBClientDataSet,<br />

TSQLClientDataSet.<br />

Архитектура «клиент-сервер» применяется в приложениях, где данные<br />

распределены по нескольким таблицам или нескольким корпоративным и<br />

локальным базам данных, в системах с несколькими клиентами, требующих<br />

организации совместного доступа и коллективной защиты. Схема организации<br />

таких приложений показа<strong>на</strong> <strong>на</strong> рис. 2,б. В отличие от предыдущих архитектур,<br />

для подключения здесь привлекается источник данных сервера – провайдер,<br />

к которому обращается клиент для получения или передачи данных.<br />

Приложение-сервер может предоставлять запрошенные данные или отказывать<br />

в сервисе в зависимости от принятой стратегии управления и ситуации в<br />

сети.


88<br />

Подключение к базам данных<br />

Хорошим стилем программирования считается отделение интерфейса<br />

пользователя от компонентов связи с базой данных. Этим достигается повышен<strong>на</strong>я<br />

гибкость проекта, позволяющая менять средства связи независимо от<br />

интерфейса или усовершенствовать интерфейс без изменения подключений.<br />

Инструментом такого разделения в C++Builder служат модули данных Data<br />

Module из библиотеки шаблонов Object Repository. Это специальный тип<br />

формы, применяемый в качестве хранилища невидимых компонентов, в первую<br />

очередь – компонентов баз данных. Как и формы, каждый модуль данных<br />

строится с использованием файла заголовков (.H), файла кода (.CPP) и<br />

файла структуры модуля (.DFM).<br />

Окно модуля данных удобно применять вместе с вкладкой Diagram и с<br />

окном Object TreeView, так как модуль данных отражает компоненты, а<br />

вкладка Diagram – их иерархическую структуру. При проектировании компоненты<br />

можно помещать прямо в модуль данных или <strong>на</strong> ветви Object Tree-<br />

View, в результате чего они становятся дочерними компонентами того объекта,<br />

<strong>на</strong> который помещаются. До завершения привязки объекты Object Tree-<br />

View помечаются вопросительными з<strong>на</strong>ками.<br />

В частности, для подключения базы данных через вспомогательный<br />

<strong>на</strong>бор данных по технологии ADO в модуль данных помещается объект<br />

ADOConnection с вкладки ADO. Его редактор свойства ConnectionString<br />

предлагает сформировать строку подключения (или использовать файл<br />

.UDL). Для формирования такой строки (под кнопкой Build) при подключении<br />

к персо<strong>на</strong>льным базам данных «Microsoft» можно выбрать провайдера<br />

(драйвера – поставщика информации) Microsoft Jet. На вкладке Connection<br />

этого же ок<strong>на</strong> в поле Select or enter a database name указывается подключаемая<br />

база данных. Кнопкой Test Connection проверяется подключение. В<br />

сформированной таким образом строке подключения указано имя провайдера,<br />

имя и пароль пользователя и папка с базой данных. В большинстве случаев<br />

свойство LoginPrompt объекта ADOConnection уста<strong>на</strong>вливается в false для<br />

открытия базы данных без пароля.<br />

Связь приложения с базой данных в архитектуре «клиент-сервер» выполняется<br />

с помощью источников данных, работающих через драйверы<br />

ODBC. С описанием таких источников в формате DSN (Data Source Notification)<br />

можно оз<strong>на</strong>комиться, если открыть Администратор источников данных


89<br />

ODBC в папке Панель управления Windows. Вкладки этой программы посвящены<br />

трем категориям источников:<br />

• пользовательский DSN единственного пользователя одного компьютера;<br />

• системный DSN всех пользователей компьютера;<br />

• файловый DSN, доступный всем пользователям сети, имеющих<br />

оди<strong>на</strong>ковые драйверы, независимо от компьютера.<br />

На отдельной вкладке представлены установленные <strong>на</strong> компьютере<br />

драйверы ODBC. Для работы с конкретной базой данных обычно подключают<br />

имеющийся или создают новый источник данных, используя соответствующий<br />

драйвер. Этот источник впоследствии распространяют в комплекте с<br />

приложением и базой данных.<br />

Поэтому для формирования строки подключения под кнопкой Build<br />

обычно выбирается Microsoft OLE DB Provider for ODBC Drivers, служащий<br />

универсальным механизмом доступа к продуктам «Microsoft». На вкладке<br />

Connection этого же ок<strong>на</strong> в поле переключателя Use connection string под<br />

кнопкой Build выбирается источник данных. Если требуемый источник данных<br />

отсутствует и его следует создать, то в поле DSN Name под кнопкой<br />

New указывается од<strong>на</strong> из строк:<br />

• Microsoft Access Driver, обслуживающая Microsoft Access;<br />

• Microsoft dBase Driver для работы с базами xBase;<br />

• Microsoft Excel Driver для работы с таблицами Microsoft Excel;<br />

• Microsoft Text Driver для подключения текстовых таблиц;<br />

• SQL Server для обращения к корпоративной базе данных<br />

и вводится произвольное имя нового источника. В следующем окне ODBC<br />

Microsoft Access Setup базу данных можно выбрать (под кнопкой Select) либо<br />

создать новую (под кнопкой Create), задав ее имя. В последнем случае<br />

должно поступить сообщение об успешном создании базы данных. Выделив<br />

<strong>на</strong>йденный или созданный источник и вернувшись <strong>на</strong> вкладку Connection,<br />

остается убедиться в его работоспособности кнопкой Test Connection.<br />

А вот пример программного подключения, выполняемого в конструкторе<br />

модуля данных:


_ _fastcall TDataModule1::TDataModule1 (TComponent* Owner)<br />

: TDataModule (Owner) {<br />

AnsiString s = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\<br />

bRobots.mdb;Persist Security Info=False";<br />

try {<br />

ADOConnection1–>Connected = false;<br />

ADOConnection1–>ConnectionString = s;<br />

ADOConnection1–>Connected = true;<br />

} catch (...) { ShowMessage ("Нет подключения к bRobots.mdb"); }<br />

}<br />

Фиксированную строку подключения можно заменить окном диалога:<br />

AnsiString s = PromptDataSource (0, "");<br />

Доступ к записям таблиц<br />

90<br />

Доступ к записям реализуется через таблицы, запросы и хранимые<br />

процедуры баз данных.<br />

Для работы с таблицами с помощью компонента ADOTable вкладки<br />

ADO в модуль данных добавляются элементы управления доступом к каждой<br />

таблице. Их свойство Connection инициализируется именем объекта<br />

ADOConnection, а в свойство TableName вписываются име<strong>на</strong> соответствующих<br />

таблиц. Каждый элемент управления ADOTable аккумулирует все записи<br />

и поля своей таблицы.<br />

Для подключения к форме с помощью компонента DataSource вкладки<br />

Data Access панели компонентов в модуль данных добавляется источник<br />

данных для каждой таблицы. В их свойстве DataSet указываются име<strong>на</strong> соответствующих<br />

компонентов ADOTable. Свойство AutoEdit разрешает режим<br />

редактирования записей, вводимых в поля <strong>на</strong>боров данных.<br />

После подключения в модуле данных уста<strong>на</strong>вливается в true свойство<br />

Active элементов доступа к таблицам для их открытия. А<strong>на</strong>логично выполняется<br />

открытие таблиц из программы, <strong>на</strong>пример:<br />

ADOTable1–>TableName = "Countries";<br />

ADOTable1–>Active = true;<br />

Следующий пример демонстрирует подключение к файлу MYDBF.DSN,<br />

соединяющему с базой данных формата FoxPro, dBase или Clipper с открытием<br />

таблицы T.


void _ _fastcall TDBForm1::ConnectClick (TObject *Sender) {<br />

AnsiString s = "Extended Properties=\"FILEDSN=myDBF.dsn;\";";<br />

TADOConnection *cnn = DataModule1–>ADOConnectionDBF;<br />

TADOTable *rst = DataModule1–>ADOTableDBF;<br />

try {<br />

cnn–>Connected = false;<br />

cnn–>ConnectionString = s;<br />

cnn–>Connected = true;<br />

rst–>TableName = "T";<br />

rst–>Active = true;<br />

} catch (...) {<br />

s = PromptDataSource(0, "");<br />

try {<br />

cnn–>Connected = false;<br />

cnn–>ConnectionString = s;<br />

cnn–>Connected = true;<br />

rst–>TableName = "T";<br />

rst–>Active = true;<br />

} catch (...) { ShowMessage (s); }<br />

}<br />

}<br />

91<br />

Возможно подключение и при отсутствии файла .DSN, в том числе – и<br />

без использования объектов ADOConnection, если известны все необходимые<br />

параметры командной строки. Еще один пример посвящен открытию доступа<br />

к таблице dBase и к листу Excel:<br />

void __fastcall TDBForm1::DBFConnectClick (TObject *Sender) {<br />

AnsiString s = "Provider=MSDASQL.1;\<br />

Extended Properties=\"DBQ=D:\\DATABASES;\<br />

Driver={Microsoft dBase Driver (*.dbf)};DriverId=533;FIL=dBase 5.0;\";";<br />

TADOConnection *cnn = DataModule1–>ADOConnectionDBF;<br />

TADOTable *rst = DataModule1–>ADOTableDBF;<br />

try {<br />

cnn–>Connected = false;<br />

cnn–>ConnectionString = s;<br />

cnn–>Connected = true;<br />

rst–>TableName = "Т";<br />

rst–>Active = true;<br />

} catch (...) { ShowMessage (“Увы…”); }<br />

}<br />

void __fastcall TDBForm1::XLSConnectClick (TObject *Sender) {<br />

AnsiString s = "Provider=MSDASQL.1;\


Extended Properties=\"DBQ=D:\\DATABASES\\Сотрудники.xls;\<br />

Driver={Microsoft Excel Driver (*.xls)};DriverId=790;FIL=excel 8.0;\";";<br />

TADOTable *rst = DataModule1–>ADOTableXLS;<br />

try {<br />

rst–>Active = false;<br />

rst–>ConnectionString = s;<br />

rst–>TableName = "Лист1";<br />

rst–>Active = true;<br />

DBGrid1–>DataSource = DataModule1–>DataSourceXLS;<br />

} catch (...) { ShowMessage (“Не подключились”); }<br />

}<br />

Работа с запросами<br />

92<br />

К подключенной базе данных можно обращаться с запросами <strong>на</strong> выборку,<br />

создание, обновление и удаление таблиц. Для этого <strong>на</strong> вкладке ADO<br />

присутствует компонент ADOQuery, создающий объекты-запросы. В свойстве<br />

Connection этого компонента указывается имя объекта ADOConnection, а в<br />

редакторе свойства SQL формируется требуемое SQL–выражение. Примеры<br />

запросов <strong>на</strong> выборку:<br />

select * from Robots where radius > 10<br />

select model, country, mass from Robots<br />

Через запросы <strong>на</strong> выборку при необходимости формируются вычисляемые<br />

(расчетные) поля. В следующих примерах с помощью расчетов создаются<br />

поля inertia и це<strong>на</strong>ВВалюте:<br />

select model, country, mass*radius*radius as inertia from Robots<br />

select модель, це<strong>на</strong>, це<strong>на</strong>/30 as це<strong>на</strong>ВВалюте from Товары<br />

Запросы <strong>на</strong> выборку удобны, также, для <strong>на</strong>хождения итоговых результатов:<br />

минимума, максимума, среднего по полю:<br />

select min (це<strong>на</strong>) as минимум, max (це<strong>на</strong>) as максимум, avg (це<strong>на</strong>) as<br />

среднее from Товары<br />

В свойстве SQL одного компонента сохраняется только один запрос.<br />

Поэтому ввод каждой строки запроса <strong>на</strong>до завершать установкой свойства<br />

Active в true для его проверки и выполнения. При этом C++Builder передает<br />

запрос серверу, который выполняет его и возвращает приложению результирующий<br />

<strong>на</strong>бор (result set). С этой же целью удобно использовать метод Requery,<br />

обновляющий элемент управления.


93<br />

Для подключения запросов <strong>на</strong> выборку к форме, в модуль данных добавляются<br />

источники данных DataSource.<br />

Запросы действия, не возвращающие <strong>на</strong>боров данных, можно формировать<br />

как с помощью компонента TADOQuery, так и с использованием TA-<br />

DOCommand. В первом случае после выполнения запроса в режиме проектирования<br />

или программным путем вводится следующее выражение SQL и<br />

вновь повторяется активизация элемента управления. В свою очередь, компонент<br />

TADOCommand, как правило, используется только для программной<br />

реализации запросов. Текстом запроса в нем инициализируется свойство<br />

CommandText, а для выполнения запроса запускается метод Execute.<br />

Примеры запросов <strong>на</strong> создание таблицы Robots с ключевым полем<br />

model и таблицы Countries с ключевым полем country:<br />

create table Robots (model string (10), country string (10),<br />

coord string (15), radius float, mass float, primary key (model))<br />

create table Countries (country string (10), primary key (country))<br />

Следующие примеры посвящены вставке в таблицу Robots новой записи,<br />

замене <strong>на</strong>звания страны и удалению всех записей, удовлетворяющих определенному<br />

условию:<br />

insert into Robots values ('Тест', 'Россия', 'Сферическая', 3, 5 )<br />

update Robots set country='Франция' where country='Россия'<br />

delete from Robots where country='Франция'<br />

В последнем примере приведен фрагмент программы заполнения случайными<br />

числами двух полей таблицы T, содержащей три записи:<br />

TADOCommand *ac = ADOCommand1;<br />

for (int i=0; iCommandText = “iisert into T(field1, field2) values (” +<br />

FloatToStr (random (100)) + “,” + FloatToStr (random (100)) + “)”;<br />

ac–>Execute ();<br />

}<br />

Связывание таблиц и запросов<br />

Формирование базы данных при необходимости продолжают <strong>на</strong> вкладке<br />

Diagram модуля данных, где из элементов управления ADOTable и ADO-<br />

Query собирается структур<strong>на</strong>я схема базы данных. На вкладке Diagram между<br />

ними можно просматривать и уста<strong>на</strong>вливать связи четырех типов:


94<br />

• Property от дочернего элемента управления к родителю (сплош<strong>на</strong>я<br />

линия со стрелкой), <strong>на</strong>пример, от полей к таблице;<br />

• Master/Detail от главной таблицы (master) к подчиненной (detail) (линия,<br />

<strong>на</strong> которой глав<strong>на</strong>я таблица помече<strong>на</strong> большим прямоугольником,<br />

а подчинен<strong>на</strong>я – маленьким прямоугольником и указано поле<br />

связи);<br />

• типа Lookup (линия с изображением глаза);<br />

• ссылка Comment Allude (стрелка).<br />

Пользуясь контекстными меню, эти связи удобно удалять и редактировать.<br />

При этом тип Lookup отображает поля подстановки данных из других<br />

таблиц, а Comment Allude подключает комментарии Comment Block к компонентам<br />

структуры, облегчая тем самым оформление схемы базы данных перед<br />

выводом ее в печать командой Print контекстного меню.<br />

Чтобы связать между собой две таблицы, следует при <strong>на</strong>жатой кнопке<br />

Master/Detail провести линию от главной таблицы к подчиненной, и в открывшемся<br />

окне указать име<strong>на</strong> зависимых полей и <strong>на</strong>жать кнопку Add. В результате<br />

автоматически заполнится свойство MasterFields подчиненной таблицы<br />

именем соответствующего поля главной таблицы MasterSource. Эти же<br />

данные можно занести вручную через редакторы <strong>на</strong>званных свойств.<br />

Чтобы создать в неактивной таблице новое поле подстановки, следует в<br />

контекстном редакторе ее ветви Fields ок<strong>на</strong> Object TreeView выбрать New<br />

Field и, установив переключатель Lookup, задать имя, тип и размер поля,<br />

ключи (Key), источник (Dataset) и поле подстановки (Result Field).<br />

Подготовленный модуль данных сохраняется, и в редакторе форм подключается<br />

к модулю формы (File.Include Unit Hdr). В форму помещаются элементы<br />

управления данными с вкладки Data Controls панели компонентов.<br />

Это таблицы DBGrid и <strong>на</strong>вигаторы DBNavigator, а также поля таблиц DBText,<br />

DBEdit, DBMemo, DBImage и другие. Компоненты вкладки Data Controls отличаются<br />

от рассмотренных ранее компонентов вкладки Standard <strong>на</strong>личием<br />

свойств DataSource и DataField, обеспечивающих их совместную работу с<br />

источниками данных. В свойстве DataSource указываются соответствующие<br />

объекты модуля данных, а в DataField – поля таблиц и запросов. Здесь также<br />

возмож<strong>на</strong> подстановка (свойство столбца PickList), но не из таблицы, а из<br />

фиксированного списка з<strong>на</strong>чений.


95<br />

Контрольные вопросы<br />

1. В чем преимущества баз данных перед авторскими файлами данных<br />

2. Каковы основные характеристики реляционных СУБД<br />

3. Каковы достоинства и недостатки технологии ADO<br />

4. Каковы достоинства и недостатки технологии BDE<br />

5. Каковы достоинства и недостатки технологии DBExpress<br />

6. Какие звенья служат для подключения приложения к базам данных<br />

7. Что <strong>на</strong>зывается провайдером (поставщиком) и источником данных, через<br />

которых осуществляется подключение<br />

8. Через какие визуальные компоненты выполняется редактирование данных<br />

в подключенных таблицах<br />

9. Какие функции выполняют запросы действия<br />

10. В чем разница между компонентами TADOQuery и TADOCommand<br />

11. Для чего и как производится связывание таблиц<br />

12. Чем различаются поля PickUp и Loolup<br />

<strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong><br />

Что посеешь, то и пожнешь<br />

Русская пословица<br />

Концепция объектно-ориентированного программирования предполагает<br />

конструирование программ из объектов – представителей классов. В качестве<br />

классов выступают библиотечные и пользовательские типы даных с<br />

характерными для них свойствами и методами поведения. Классификация<br />

способствует экономии памяти и упрощает описание и отображение объектов,<br />

облегчает их систематизацию и стандартизацию. Формирование программ<br />

из объектов является основным инструментом современного программирования.<br />

Главными механизмами объектно-ориентированного подхода<br />

служат инкапсуляция, полиморфизм и <strong>на</strong>следование.<br />

Инкапсуляция<br />

Создание авторских классов <strong>на</strong>чи<strong>на</strong>ется с их объявления в файле заголовка<br />

модуля. После объявления или одновременно с ним класс определяется<br />

(описывается). Встроенное объявление и определение членов выполняется в<br />

формате обычных переменных и функций:


96<br />

class имяКласса {<br />

[Метка]<br />

[объявлениеЧленов…]<br />

[встроенноеОпределениеЧленов…] };<br />

Если не дается встроенное определение, в файле кода модуля приводится<br />

внешнее определение членов:<br />

Тип имяКласса :: имяЧлен-данного [ =З<strong>на</strong>чение];<br />

Тип имяКласса :: имяЧлен-функции телоЧлен-функции<br />

Использование внешних определений повышает <strong>на</strong>глядность текста программы<br />

и облегчает его последующую модификацию. Члены класса могут<br />

при<strong>на</strong>длежать к другим классам, и их именуют в этом случае вложенными<br />

классами. Глуби<strong>на</strong> вложенности не ограничивается, хотя «большое число<br />

уровней абстракции так же плохо, как и их отсутствие» [5].<br />

После объявления и определения создаются объекты класса. Каждый<br />

класс может использоваться для порождения множества объектов:<br />

имяКласса имяОбъекта;<br />

имяКласса *имяОбъекта = new имяКласса;<br />

Инициализация и обращение к чле<strong>на</strong>м класса выполняются по разнообразным<br />

схемам. Обращение к чле<strong>на</strong>м своего класса производится по име<strong>на</strong>м,<br />

без указания имени объекта:<br />

имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />

При этом адресация выполняется с помощью неявного аргумента this.<br />

Так как this – адрес, то конструкция this–>имяЧле<strong>на</strong> является указателем <strong>на</strong><br />

объект, а *this представляет сам объект:<br />

this–>имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />

(*this).имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />

При обращении к чле<strong>на</strong>м других классов следует указывать имя объекта:<br />

имяОбъекта.имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />

ссылкаНаОбъект–>имяЧле<strong>на</strong> [= З<strong>на</strong>чение];<br />

Пусть, <strong>на</strong>пример, требуется периодически выводить информацию о<br />

компьютерах. Чтобы не делать это в каждой функции некоторой программы,<br />

в проекте удобно создать новый модуль (File.New .Unit), и в его файле заголовка<br />

объявить класс VPC, получающий сведения через функцию Set и представляющий<br />

их функцией Out, как это показано в следующем примере:


class VPC {<br />

private:<br />

int type;<br />

AnsiString firm;<br />

public:<br />

double price;<br />

void Set (int a, AnsiString b) { type = a; firm = b; }<br />

void Out () {<br />

ShowMessage ("Пpоцессор фирмы " + firm + " стоит $" + price);<br />

}<br />

};<br />

97<br />

Для создания объектов класса в других формах, в их файлах кода размещаются<br />

ссылки <strong>на</strong> файл заголовка класса VPC. Ниже приведен пример<br />

функции MainVPC, «воспользовавшейся услугами» класса VPC для обработки<br />

объекта myCPU, и таких функций может быть множество:<br />

void MainVPC () {<br />

AnsiString company = "Acer";<br />

VPC myCPU;<br />

myCPU.Set (6, company); myCPU.price = 100; myCPU.Out ();<br />

}<br />

Другой пример посвящен классу VIntegral, реализующему численное<br />

интегрирование тремя методами: методом прямоугольников, методом трапеций<br />

и методом парабол. Класс объявлен в файле заголовка модуля, его членфункции<br />

Rect, Trap, Para определены в файле кода модуля, а объекты созданы<br />

функцией MainVIntegral в файле кода формы:<br />

class VIntegral {<br />

double j, x, sum, sum1, f;<br />

public:<br />

void Rect (double, double, double, double (*fnc) (double, double));<br />

void Trap (double, double, double, double (*fnc) (double, double));<br />

void Para (double, double, double, double (*fnc) (double, double));<br />

};<br />

void VIntegral :: Rect (double xk, double h, double f,<br />

double (*fnc) (double x, double y))<br />

{ for (x=0, sum=0; x


void VIntegral :: Trap (double xk, double h, double f,<br />

double (*fnc) (double x, double y))<br />

{ for (x=h, sum=0; x


99<br />

AnsiString result;<br />

VDeque *cursor = head;<br />

while (cursor != this) {<br />

result += AnsiString (cursor–>info) +'\t';<br />

cursor = cursor–>next;<br />

}<br />

ShowMessage (AnsiString (cursor–>info));<br />

}<br />

void MainVDeque () {<br />

VDeque a, b, c, d;<br />

a.Add ("IBM"); a.AddNode ();<br />

b.Add ("Siemens"); b.AddNode ();<br />

c.Add ("Motorola"); c.AddNode ();<br />

d.Add ("Apple"); d.AddNode ();<br />

b.Out ();<br />

}<br />

Конструкторы и деструкторы<br />

Для автоматической инициализации объектов класса используются<br />

конструкторы – бестиповые, одноименные с классом член-функции:<br />

class VMicroPC {<br />

AnsiString type;<br />

int price;<br />

public:<br />

VMicroPC (AnsiString a, int b) { type = a; price = b; } // конструктор<br />

void Out () { ShowMessage ("Компьютер " + type + " стоит $" + price); }<br />

};<br />

void MainVMicroPC() {<br />

AnsiString comp1 = "IBM", comp2 = "Macintosh";<br />

VMicroPC myCPU (comp1, 990), yourCPU (comp2, 1500);<br />

myCPU.Out();<br />

yourCPU.Out();<br />

}<br />

Конструктор вызывается не только при определении нового объекта<br />

класса для объявления данных, но и при копировании объекта или при ди<strong>на</strong>мическом<br />

выделении памяти новому объекту. В качестве аргументов копирующего<br />

конструктора (copying constructor) используются ссылки <strong>на</strong> копируемые<br />

объекты. Примером может служить конструктор типизированного<br />

класса AnsiString:


100<br />

struct computer { AnsiString name; int price; };<br />

class VList {<br />

computer *p;<br />

int i, max, current;<br />

public:<br />

VList (int);<br />

void Add (computer *);<br />

void Delete (computer *);<br />

AnsiString Out ();<br />

};<br />

VList::VList (int max) { VList::max = max; current = 0; p = new computer[max]; }<br />

void VList::Add (computer *v) {<br />

if (current < max) {<br />

p[current].name = v–>name;<br />

p[current++].price = v–>price;<br />

}<br />

}<br />

void VList::Delete (computer *v) {<br />

for (i=0; iname) {<br />

while (i < current–1) {<br />

p[i].name = p[i+1].name;<br />

p[i].price = p[i+1].price;<br />

i++;<br />

}<br />

––current;<br />

}<br />

}<br />

}<br />

AnsiString VList::Out () {<br />

AnsiString result;<br />

for (i=0; i


}<br />

101<br />

VList a (3);<br />

// конструктор класса VList<br />

a.Add (&x); a.Add (&y); a.Add (&z);<br />

ShowMessage ("Поступили:\nЭВМ–Це<strong>на</strong>\n\n" + a.Out ());<br />

a.Delete (&y);<br />

ShowMessage ("Остались:\nЭВМ–Це<strong>на</strong>\n\n" + a.Out ());<br />

Инициализация объектов класса может проводиться не только с помощью<br />

операторов в теле конструктора, но и посредством списка инициализации<br />

(initialisation list). Такой список помещают через двоеточие вслед за заголовком<br />

конструктора.<br />

Конструктор, не требующий аргументов, <strong>на</strong>зывается конструктором<br />

по умолчанию (default constructor), или параметризованным конструктором.<br />

Это может быть конструктор с пустым списком параметров или конструктор,<br />

в котором части объявления параметров, завершающей список, присвоены<br />

з<strong>на</strong>чения.<br />

Следующий пример сложения матрицы с числом демонстрирует инициализацию<br />

списком и работу конструктора по умолчанию:<br />

class VMatrix {<br />

int i, j;<br />

AnsiString temp;<br />

float m[3][3], k;<br />

public:<br />

VMatrix (float a[][3], float c = 1);<br />

void Out ();<br />

};<br />

VMatrix::VMatrix (float a[][3], float c): k(c) {<br />

for (i=0; i


}<br />

102<br />

VMatrix second (M, 5); // инициализируем з<strong>на</strong>чением 5<br />

second.Out (); // 6,7,8, 9,10,11, 12,13,14<br />

Процесс освобождения памяти после уничтожения объектов можно сопровождать<br />

деструктором (destructor) – бестиповым открытым методом без<br />

параметров, имя которого совпадает с именем класса и имеет префикс ~<br />

(тильда). Деструктор всегда вызывается автоматически, то есть неявно, при<br />

выходе из блока, в котором был объявлен объект. Он также включается при<br />

вызове оператора delete для указателя <strong>на</strong> объект, имеющий деструктор, или в<br />

том случае, когда необходимо удалить объект, вложенный в удаляемый. В<br />

любом случае работа деструктора сопровождает процесс освобождения памяти,<br />

выделенной конструктором. Каждый класс имеет только один деструктор,<br />

причем всегда открытый и без параметров. За работой деструктора можно<br />

по<strong>на</strong>блюдать в следующем примере:<br />

class VPair {<br />

int first, second;<br />

public:<br />

VPair (int one, int two): first (one), second (two)<br />

{ ShowMessage ("Объект создан"); }<br />

~VPair () { ShowMessage ("Объект удален"); }<br />

void Out () { ShowMessage (AnsiString (first) + '\n' + AnsiString (second)); }<br />

};<br />

void MainVPair () {<br />

{ VPair num (2,3); // объект создан<br />

num.Out (); // 2-3<br />

} // объект удален<br />

{ VPair num (4,5); // объект создан<br />

num.Out (); // 4-5<br />

} // объект удален<br />

}<br />

За освобождением памяти можно <strong>на</strong>блюдать и без деструктора. В приведенной<br />

ниже реализации объектов str, поочередно помещаемых в стек и<br />

очередь, деструктора нет:<br />

class VStackAndQueue {<br />

char *s;<br />

int i, size, top;<br />

AnsiString result;<br />

public:


};<br />

VStackAndQueue (AnsiString str);<br />

void Push (AnsiString str);<br />

void PopStack ();<br />

void PopQueue ();<br />

int Empty () { return top == –1; }<br />

int Full () { return top == size; }<br />

103<br />

VStackAndQueue::VStackAndQueue (AnsiString str) {<br />

size = str.Length (); s = new char[size]; top = –1; result = str + ":\n";<br />

}<br />

void VStackAndQueue::Push (AnsiString str) {<br />

for (i = 1; i


104<br />

friend void Show (VFriendPC x);<br />

};<br />

void Show (VFriendPC x) { ShowMessage (AnsiString ( x.num )); }<br />

void MainVFriendPC () { VFriendPC price (900); Show (price); }<br />

В приведенном примере Show объявле<strong>на</strong> функцией, дружественной<br />

классу VFriendPC, поэтому о<strong>на</strong> свободно обрабатывает закрытую переменную<br />

num этого класса.<br />

Особенно полезными аспектами механизма дружественных функций<br />

является то, что функция может быть дружественной по отношению к нескольким<br />

классам, член-функция одного класса может быть дружественной<br />

другому классу и, <strong>на</strong>против, сразу группа функций или целый класс могут<br />

быть дружественными рассматриваемому классу:<br />

class VTwo;<br />

class VOne {<br />

friend void Out (VOne &c1, VTwo &c2);<br />

private:<br />

char *s1;<br />

public:<br />

VOne () { s1 = "Проверка: ";}<br />

};<br />

class VTwo {<br />

friend void Out (VOne &c1, VTwo &c2);<br />

private:<br />

char *s2;<br />

public:<br />

VTwo () { s2 = "Sony, Siemens, Fuji"; }<br />

};<br />

void Out (VOne &c1, VTwo &c2) {<br />

ShowMessage (AnsiString ( c1.s1) + AnsiString (c2.s2));<br />

}<br />

void MainVOneVTwo () {<br />

VOne c1; VTwo c2; Out (c1, c2); // Проверка: Sony, Siemens, Fuji<br />

}<br />

Если все функции одного класса дружественны второму классу, то говорят<br />

о дружественном классе (friend class):<br />

class VFirst;<br />

class VSecond {<br />

AnsiString model, firm;


};<br />

105<br />

public:<br />

VSecond (AnsiString a, AnsiString b): model (a), firm (b) {}<br />

friend class VFirst;<br />

class VFirst {<br />

VSecond set;<br />

public:<br />

VFirst (AnsiString a, AnsiString b): set (a,b) {}<br />

void Model () { ShowMessage (AnsiString (set.VSecond::model)); }<br />

void Firm () { ShowMessage (AnsiString (set.VSecond::firm)); }<br />

};<br />

void MainVFirstVSecond () {<br />

AnsiString mod = "ATT", frm = "IBM";<br />

VFirst device (mod, frm);<br />

device.Model ();<br />

// AT<br />

device.Firm ();<br />

// IBM<br />

}<br />

Используя дружественные функции, следует помнить, что «подобно<br />

оператору goto, друзья снижают <strong>на</strong>дежность программного кода... Потребность<br />

в объявлении одних классов друзьями других свидетельствует о плохо<br />

продуманной иерархии классов» [9]. Поэтому обращаться к помощи друзей<br />

следует лишь при крайней необходимости.<br />

Полиморфизм<br />

Полиморфизм как способность оди<strong>на</strong>ковых операторов и одноименных<br />

функций к избирательному (зависящему от типа) поведению реализуется в<br />

C++ в первую очередь через механизмы перегрузки (overloading) операторов<br />

и функций.<br />

Суть перегрузки операторов заключается в создании функцииоператора,<br />

использующей в качестве имени лексему operator и следующий за<br />

ней перегружаемый оператор. Объявление перегрузки а<strong>на</strong>логично объявлению<br />

обычной дружественной функции или член-функции класса:<br />

Тип operator з<strong>на</strong>кОперации ( [объявлениеПараметров] );<br />

Перегрузке могут подлежать все простые операторы, за исключением<br />

«.», «.*», «:», «sizeof», «::». Язык не позволяет задать для перегружаемого<br />

оператора иной символ, кроме тех, что определены в списке операторов.<br />

Нельзя изменять синтаксис использования оператора: у<strong>на</strong>рный оператор не


106<br />

перегружается в би<strong>на</strong>рный, префиксный – в постфиксный и т.п. Операторы<br />

«+», «–», «*», «&» могут быть как би<strong>на</strong>рными, так и у<strong>на</strong>рными.<br />

Перегружаются только опеpатоpы в операциях, для которых по крайней<br />

мере один из параметров является членом структуры или класса. С учетом<br />

неявного указателя this, при выполнении одноместных операций аргумент<br />

может опускаться, а в двухместных операциях допустимо оставлять<br />

один аргумент.<br />

Для з<strong>на</strong>комства с перегрузкой одноместных операторов рассмотрим<br />

одну из распространенных задач программирования – вывод текущего времени<br />

<strong>на</strong> экран. На основании показаний системных часов компьютера такая<br />

программа формирует <strong>на</strong> экране время в днях, часах и минутах. Чтобы в каждом<br />

такте часов не пересчитывать время заново, здесь переопределен оператор<br />

инкремента, что позволяет использовать не абсолютный, а относительный<br />

отсчет времени:<br />

class VClock {<br />

private:<br />

__int64 total, secs, mins, hours, days;<br />

public:<br />

VClock (__int64 i);<br />

void Out ();<br />

VClock operator++ ();<br />

};<br />

VClock::VClock (__int64 i) {<br />

total = i; secs = total % 60; mins = (total / 60) % 60;<br />

hours = (total / 3600) % 24; days = total / 86400L;<br />

}<br />

void VClock::Out () {<br />

ShowMessage (AnsiString (total) + " сек.= "<br />

+ AnsiString (days) + " дн. " + AnsiString (hours) + " час. "<br />

+ AnsiString (mins) + " мин. " + AnsiString (secs) + " сек. ");<br />

}<br />

VClock VClock::operator++ () {<br />

VClock temp (++total);<br />

secs = temp.secs; mins = temp.mins; hours = temp.hours;<br />

days = temp.days; return *this;<br />

}<br />

void MainVClock () {<br />

VClock t1 (59), t2 (172799L);


}<br />

ShowMessage ("Старое время:");<br />

t1.Out ();<br />

t2.Out ();<br />

++t1; ++t2;<br />

ShowMessage ("Новое время:");<br />

t1.Out ();<br />

t2.Out ();<br />

107<br />

// 0 дн. 0 час. 0 мин. 59 сек.<br />

// 1 дн. 23 час. 59 мин. 59 сек.<br />

// 0 дн. 0 час. 1 мин. 0 сек.<br />

// 2 дн. 0 час. 0 мин. 0 сек.<br />

Следующий пример посвящен операции транспонирования матрицы<br />

M[3][3], представленной элементом управления StringGrid1, для выполнения<br />

которой перегружается оператор «!»:<br />

class VMatr {<br />

int i, j;<br />

double temp;<br />

double M[3][3];<br />

public:<br />

VMatr ();<br />

void Out ();<br />

VMatr operator ! ();<br />

};<br />

VMatr::VMatr () {<br />

for (i=0; iCells [i][j] );<br />

}<br />

void VMatr::Out () {<br />

for (i=0; iCells[i][j] = M[i][j];<br />

}<br />

VMatr VMatr::operator! () {<br />

for (i=0; i


};<br />

108<br />

public:<br />

VComputers (AnsiString a, int b) { type = a; price = b; }<br />

VComputers (const VComputers &par) {<br />

type = "Macintosh"; price = par.price * 2;<br />

}<br />

void Out () { ShowMessage (type + " стоит " + AnsiString (price)); }<br />

void MainVComputers() {<br />

VComputers myCPU ("Intel Pentium", 990);<br />

VComputers yourCPU (myCPU);<br />

myCPU.Out ();<br />

yourCPU.Out ();<br />

}<br />

Здесь объект myCPU инициализируется через конструктор VComputers<br />

(AnsiString, int), а объект yourCPU – через конструктор VComputers (const<br />

VComputers &). Оба выводятся <strong>на</strong> экран функцией Out.<br />

В следующем примере производится перегрузка конструктора для сложения<br />

транспонированнной матрицы с числом:<br />

class VMatrixPlus {<br />

int i, j; double k, m[3][3];<br />

public:<br />

double n[3][3];<br />

VMatrixPlus (double a[][3]);<br />

VMatrixPlus (double c): k (c) {}<br />

VMatrixPlus operator ! ();<br />

VMatrixPlus operator + (VMatrixPlus c);<br />

void Out ();<br />

};<br />

VMatrixPlus::VMatrixPlus (double a[][3]) {<br />

for (i=0; i


}<br />

109<br />

AnsiString temp;<br />

for (i=0; i


110<br />

}<br />

}<br />

else {<br />

if (m[j].heat < m[j–1].heat) {<br />

Swap (m[j–1].name, m[j].name); Swap (m[j–1].heat, m[j].heat);<br />

}<br />

}<br />

ShowMessage (m[0].name + AnsiString (m[0].heat) + '\n'<br />

+ m[1].name + AnsiString (m[1].heat) + '\n' + m[2].name<br />

+ AnsiString (m[2].heat));<br />

}<br />

Од<strong>на</strong>жды созданный шаблон можно сохранить в файле заголовка и вызывать<br />

его оттуда по имени:<br />

template void Sort (T array[], int size) {<br />

for (int i=0; i (const VPrice);<br />

friend AnsiString &operator


111<br />

};<br />

int VPrice::operator > (const VPrice amount) {<br />

return (dollars > amount.dollars) ||<br />

(dollars == amount.dollars && cents > amount.cents);<br />

}<br />

AnsiString &operator


const MAX = 10;<br />

112<br />

template class VStack {<br />

int num;<br />

T *items;<br />

public:<br />

VStack (int size = MAX) { num = 0, items = new T[size]; }<br />

~VStack () { delete [] items; }<br />

void Push (T t) { items[num++] = t; }<br />

T Pop () { return items[– –num]; }<br />

};<br />

void MainVStack () {<br />

VStack StackInt (10);<br />

StackInt.Push (33); StackInt.Push (44); StackInt.Push (55);<br />

ShowMessage ("В стеке целые числа: " + AnsiString (StackInt.Pop()) + ',' +<br />

AnsiString (StackInt.Pop()) + ',' + AnsiString (StackInt.Pop()));<br />

VStack StackDouble (10);<br />

StackDouble.Push (33.3); StackDouble.Push (44.2);<br />

StackDouble.Push (–55.1);<br />

ShowMessage ("В стеке вещественные числа: " +<br />

AnsiString (StackDouble.Pop ()) + ',' +<br />

AnsiString (StackDouble.Pop ()) + ',' + AnsiString (StackDouble.Pop ()));<br />

}<br />

И еще один шаблон класса. Он обрабатывает массивы разных типов:<br />

template class VVector {<br />

T *elem;<br />

// тип и число элементов<br />

public:<br />

VVector ();<br />

T &operator[] (int i) { return elem[i]; }<br />

void Out ();<br />

};<br />

template VVector :: VVector () {<br />

elem = new T[size];<br />

for (int i=0; i


113<br />

void MainVVector () {<br />

VVector d;<br />

// создаются векторы<br />

VVector f;<br />

VVector c;<br />

for (int i=0; i


114<br />

class VFunction {<br />

public:<br />

float Time (float R, float t) { return R*pow (sin (t), 2); }<br />

};<br />

class VRectangle: public VFunction {<br />

public:<br />

float step;<br />

VRectangle (float tk) { step = tk / 100; }<br />

};<br />

void MainVIntegral () {<br />

float j, tk = 0.8, t, sum;<br />

VRectangle myIntegral (tk);<br />

for (t=0, sum=0; t


115<br />

void MainVEqu() {<br />

// x + 4y = 9; –x + 6y = 1<br />

float member[] = { 1, 4, 9, –1, 6, 1 }; VEquX x (member); VEquY y (member);<br />

ShowMessage ("x = " + AnsiString (x.rx / x.re) + '\n' // x = 5<br />

+ "y = " + AnsiString (y.ry / y.re)); // y = 1<br />

}<br />

Здесь списки инициализации конструкторов пpоизводных классов<br />

VEquX, VEquY содержат запись VDet (r). Они инициализируют в базовом<br />

классе VDet те части классов EquX, EquY, которые при<strong>на</strong>длежат Det.<br />

Еще один пример <strong>на</strong>следования:<br />

class VStudent {<br />

protected:<br />

int course, group;<br />

public:<br />

int fac;<br />

AnsiString result;<br />

VStudent (AnsiString name, int y) {<br />

course = 10 – y / 1000; group = y; fac = group / 100 % 10;<br />

result = "\nСтудент " + name + " с факультета " + fac;<br />

}<br />

};<br />

class VBachalour: public VStudent {<br />

public:<br />

int spe;<br />

VBachalour (AnsiString n, int a, int b): VStudent (n,a) {<br />

spe = b;<br />

result += "\n" + AnsiString (course) + " курса " + AnsiString (group) +<br />

" группы – бакалавр по специальности " + AnsiString(b);<br />

}<br />

};<br />

void MainBac () {<br />

VBachalour first ("Сидоров", 6431, 225);<br />

if (first.spe > 200 && first.fac == 4)<br />

first.result += "\nЭто специалист по автоматике";<br />

else<br />

first.result += "\nЭто специалист по электротехнике";<br />

ShowMessage (first.result);<br />

}<br />

Пример множественного <strong>на</strong>следования:


AnsiString result;<br />

116<br />

class VStud {<br />

protected:<br />

__int64 group, fac, course;<br />

public:<br />

VStud (AnsiString name, __int64 x) {<br />

group = x;<br />

course = 10 – group / 1000;<br />

fac = group / 100 % 10;<br />

if (course > 0 && course < 6)<br />

result = "\nСтудент " + name + " факультета " + AnsiString (fac)<br />

+ "\nкурса " + AnsiString (course) + " группы " + AnsiString (group);<br />

else course = 0;<br />

}<br />

};<br />

class VPensioner {<br />

protected:<br />

__int64 zip;<br />

public:<br />

VPensioner (AnsiString name, __int64 z): zip (z) {<br />

if (zip > 195000L && zip < 200000L)<br />

result = "\nПенсионер " + AnsiString (name) + " из Петербурга";<br />

else zip = 0;<br />

}<br />

};<br />

class VPassenger: VStud, VPensioner {<br />

public:<br />

VPassenger (AnsiString name, __int64 data):<br />

VStud (name, data), VPensioner (name, data) {<br />

if (course>0 || zip) { result +="\n имеет право <strong>на</strong> льготы"; }<br />

else result = "\n Пассажир не имеет прав <strong>на</strong> льготы";<br />

ShowMessage (result);<br />

}<br />

};<br />

void MainVPassenger() {<br />

VPassenger w ("Сидоров", 8431);<br />

VPassenger x ("Козлов", 197376L);<br />

VPassenger y ("Тихонов", 1234);<br />

VPassenger z ("Степанов", 1);<br />

}


117<br />

Виртуальные функции и абстрактные классы<br />

При обычном <strong>на</strong>следовании объект производного класса обладает<br />

свойствами и поведением объектов базового класса. В этом случае говорят о<br />

раннем, или статическом, связывании <strong>на</strong> стадии компиляции. Виртуальными<br />

(virtual function) <strong>на</strong>зываются функции, которые определяются <strong>на</strong> стадии исполнения,<br />

то есть производится их позднее, или ди<strong>на</strong>мическое, связывание.<br />

Для этого объект производного класса с<strong>на</strong>бжается скрытым указателем <strong>на</strong><br />

базовый класс, который используется компилятором <strong>на</strong> стадии выполнения<br />

программы.<br />

При объявлении виртуальной функции к ее имени добавляется лексема<br />

virtual, что позволяет производным классам при необходимости переопределять<br />

такую функцию без изменения ее имени в списке параметров:<br />

virtual Тип имяФункции ([ объявлениеПараметров ]);<br />

«Виpтуальные функции служат центpальным моментом объектноориентированного<br />

программирования, потому что они позволяют опpеделять<br />

базовый класс общего <strong>на</strong>з<strong>на</strong>чения, не тpебуя з<strong>на</strong>ния особенностей, котоpые<br />

могут быть пpедусмотpены пpоизводным классом. Пpогpамма <strong>на</strong> C++ без<br />

виpтуальных функций, веpоятно, пpосто невеpно спpоектиpова<strong>на</strong>» [5]. Иными<br />

словами, виpтуальные функции опpеделяют возможности, котоpыми обладают<br />

пpоизводные классы, но котоpые не могут быть pеализованы <strong>на</strong><br />

уpовне базового класса.<br />

class VMarket {<br />

public:<br />

virtual void Market1 () { ShowMessage ("Телевизоры"); }<br />

void Market2 () { ShowMessage ("Компьютеры"); }<br />

};<br />

class VSection: public VMarket {<br />

public:<br />

void Market1 () { ShowMessage ("SONY"); }<br />

void Market2 () { ShowMessage ("MACINTOSH"); }<br />

};<br />

void MainVirtual1 () {<br />

VMarket *m = new VSection; // ссылка <strong>на</strong> производный класс<br />

m–>Market1 ();<br />

// SONY<br />

m–>Market2 ();<br />

// Компьютеры<br />

}


118<br />

Более сложный пример помогает разобраться с различными вариантами<br />

<strong>на</strong>следования:<br />

AnsiString result;<br />

class VProcessor {<br />

int frequency;<br />

public:<br />

VProcessor (int j = 100): frequency (j) {}<br />

virtual void Out () { result += "\nПроцессор " + AnsiString (frequency); }<br />

void Out (AnsiString s) { result += "\n" + s + AnsiString (frequency); }<br />

};<br />

class VComputer: public VProcessor {<br />

int hardDisk;<br />

public:<br />

VComputer (int j = 800): VProcessor (133), hardDisk (j) {}<br />

void Out () { result += "\nКомпьютер " + AnsiString (hardDisk); }<br />

int Out (AnsiString s) {<br />

result += "\n" + s + AnsiString (hardDisk);<br />

return hardDisk;<br />

}<br />

};<br />

void MainVirtual2 () {<br />

VProcessor intel, motorola (150), *cirix;<br />

VComputer ibm, macintosh (200),<br />

*proforma = &macintosh;<br />

intel.Out (); // Процессор 100<br />

motorola.Out (); // Процессор 150<br />

ibm.Out (); // Компьютер 800<br />

macintosh.Out (); // Компьютер 200<br />

intel.Out ("Intel "); // Intel 100<br />

motorola.Out ("Motorola "); // Motorola 150<br />

ibm.Out ("IBM "); // IBM 800<br />

macintosh.Out ("Macintosh "); // Macintosh 200<br />

cirix = proforma;<br />

cirix–>Out (); // Компьютер 200<br />

cirix–>Out ("Macintosh "); // Macintosh 133<br />

proforma–>Out (); // Компьютер 200<br />

proforma–>Out ("Macintosh "); // Macintosh 200<br />

ShowMessage (result);<br />

}<br />

Виртуаль<strong>на</strong>я функция, не определен<strong>на</strong>я в базовом классе, <strong>на</strong>зывается<br />

чистой виртуальной функцией (pure virtual function) и объявляется как


119<br />

virtual Тип имяФункции ([ объявлениеПараметров ]) = 0;<br />

Такая функция полностью определяется в производных классах. Базовый<br />

класс, содержащий, по крайней мере, одну чистую виртуальную функцию,<br />

<strong>на</strong>зывается абстрактным классом (abstract class).<br />

Абстрактный класс может использоваться лишь в качестве базового<br />

класса для некоторого другого класса. Никакие объекты абстрактного класса<br />

не могут создаваться и<strong>на</strong>че, как подобъекты, представляющие базовый класс<br />

в объектах некоторого производного от него класса.<br />

class VNewMarket {<br />

public:<br />

virtual void Market1 () = 0;<br />

void Market2 () { ShowMessage ("Компьютеры"); }<br />

};<br />

class VNewSection: public VNewMarket {<br />

public:<br />

void Market1 () { ShowMessage ("SONY"); }<br />

void Market2 () { ShowMessage ("MACINTOSH"); }<br />

};<br />

void MainVirtual3 () {<br />

VNewMarket *m = new VNewSection; // ссылка <strong>на</strong> производный класс<br />

m–>Market1 ();<br />

// SONY<br />

m–>Market2 ();<br />

// Компьютеры<br />

}<br />

В следующем примере абстрактный класс управляет выводом <strong>на</strong> экран<br />

разнообразной информации:<br />

class VMan {<br />

protected: long course;<br />

public: virtual AnsiString Out (AnsiString) = 0;<br />

};<br />

class VStudent: public VMan {<br />

protected:<br />

_ _int64 group, fac;<br />

public:<br />

VStudent (_ _int64 x) {<br />

group = x; fac = group / 100 % 10; course = 10 – x / 1000;<br />

if (course < 1 || course > 5) course = 0;<br />

}<br />

AnsiString Out (AnsiString name) {<br />

return "\nСтудент " + name + " факультета " + AnsiString (fac)


120<br />

+ "\nкурса " + AnsiString (course) + " группы " + AnsiString (group);<br />

}<br />

};<br />

class VPensioner: public VMan {<br />

public:<br />

VPensioner (__int64 x) {<br />

if (x > 195000L && x < 200000L) course = x;<br />

else course = 0;<br />

}<br />

AnsiString Out (AnsiString name) {<br />

return "\nПенсионер " + AnsiString(name) + " из Петербурга";<br />

}<br />

};<br />

class VPassenger: VStudent, VPensioner {<br />

public:<br />

AnsiString result;<br />

VPassenger (AnsiString name, __int64 data):<br />

VStudent (data), VPensioner (data) {<br />

If (!(VStudent::course) && !(VPensioner::course))<br />

ShowMessage (" Пассажир не имеет права <strong>на</strong> льготы.");<br />

else {<br />

if (VStudent::course) result = VStudent::Out (name);<br />

if (VPensioner::course) result = VPensioner::Out (name);<br />

ShowMessage (result + "\nимеет право <strong>на</strong> льготы.");<br />

}<br />

}<br />

};<br />

void MainVirtual4 () {<br />

VPassenger w ("Сидоров", 8431);<br />

VPassenger x ("Козлов", 197376L);<br />

VPassenger y ("Тихонов", 1234);<br />

VPassenger z ("Степанов", 1);<br />

}<br />

Контрольные вопросы<br />

1. Что такое объявление и определение класса<br />

2. Каково <strong>на</strong>з<strong>на</strong>чение конструкторов и деструкторов класса<br />

3. Что <strong>на</strong>зывается конструктором по умолчанию<br />

4. Что <strong>на</strong>зывается копирующим конструктором<br />

5. Как происходит передача объектов класса в функции<br />

6. Что такое дружественные функции и классы


121<br />

7. В чем заключается смысл перегрузки операторов<br />

8. Для чего выполняется перегрузка функций<br />

9. Какими достоинствами и недостатками обладают шаблоны<br />

10. В чем заключаются преимущества использования механизма <strong>на</strong>следования<br />

в программировании<br />

11. Как производится единичное <strong>на</strong>следование в программировании<br />

12. Какие данные и методы подлежат <strong>на</strong>следованию<br />

13. Что такое защищенные члены класса<br />

14. В чем смысл открытого и закрытого <strong>на</strong>следования<br />

15. Чем отличается виртуаль<strong>на</strong>я функция от «чистой» виртуальной функции<br />

16. Каково <strong>на</strong>з<strong>на</strong>чение абстрактных классов в програмировании


122<br />

Приложение. Программные документы<br />

Сведения, необходимые для разработки, изготовления, сопровождения<br />

и эксплуатации программных продуктов, оформляются в виде программных<br />

документов в соответствии с комплексом государственных стандартов<br />

ГОСТ 19.<br />

По ГОСТ 19.101-87 в состав программной документации входят:<br />

• спецификация – описание состава программы и ее документации;<br />

• ведомость держателей подлинников – перечень организаций, <strong>на</strong> которых<br />

хранятся подлинники программных документов (код документа<br />

05);<br />

• текст программы с необходимыми комментариями (12);<br />

• описание программы – описание ее логической структуры и функционирования<br />

(13);<br />

• программа и методика испытаний – сведения о параметрах, подлежащих<br />

проверке при испытаниях программы, а также о порядке и<br />

методике их контроля (51);<br />

• техническое задание – <strong>на</strong>з<strong>на</strong>чение и область применения программы,<br />

технические, технико-экономические и специальные требования,<br />

предъявляемые к ней, а также стадии, сроки разработки и виды<br />

испытаний;<br />

• пояснитель<strong>на</strong>я записка – схемы и описание алгоритмов и порядка<br />

его функционирования, а также обоснование принятых технических<br />

и технико-экономических решений (81);<br />

• эксплуатационные документы.<br />

Эксплуатационные документы содержат сведения, необходимые для<br />

обеспечения функционирования и эксплуатации программы:<br />

• ведомость эксплуатационных документов (20);<br />

• формуляр – основные характеристики программы, комплектность и<br />

сведения об эксплуатации (30);<br />

• описание применения – <strong>на</strong>з<strong>на</strong>чение и область применения программы,<br />

применяемые методы, класс решаемых задач, ограничения для<br />

применения, минималь<strong>на</strong>я конфигурация технических средств (31);


123<br />

• руководство системного программиста – сведения для проверки,<br />

обеспечения функционирования и <strong>на</strong>стройки программы <strong>на</strong> условия<br />

конкретного применения (32);<br />

• руководство программиста по эксплуатации программы (33);<br />

• руководство оператора – процедуры общения персо<strong>на</strong>ла с вычислительной<br />

системой в ходе выполнения программы (34);<br />

• описание языка, включая синтаксис и семантику (35);<br />

• руководство по техническому обслуживанию – порядок применения<br />

тестовых и диагностических программ при обслуживании (36).<br />

ГОСТ разрешает объединять отдельные виды эксплуатационных документов,<br />

за исключением первых двух. Необходимость такого объединения<br />

предусматривается техническим заданием. Объединенному документу присваивают<br />

<strong>на</strong>именование и обоз<strong>на</strong>чение одного из объединяемых документов.<br />

В соответствии с ГОСТ 19.103-78, обоз<strong>на</strong>чения программ и документов<br />

состоят из групп з<strong>на</strong>ков, разделенных точкой, пробелом и дефисом. Структура<br />

обоз<strong>на</strong>чения программы и ее спецификации:<br />

A.B.XXXXX-XX<br />

Здесь A – код страны, B – код организации-разработчика, XXXXX – регистрационный<br />

номер, XX – номер издания программы или редакции документа.<br />

Коды страны и организации-разработчика присваиваются в установленном<br />

порядке, номер издания программы или редакции документа – в порядке возрастания<br />

от 01 до 99.<br />

Структура обоз<strong>на</strong>чения других программных документов:<br />

A.B.XXXXX-XX YY ZZ-Z<br />

Здесь A.B.XXXXX-XX – обоз<strong>на</strong>чение программы или ее спецификации, YY –<br />

код вида документа по ГОСТ 19.101-87, ZZ – номер документа данного вида,<br />

Z – номер части документа.


124<br />

Список литературы<br />

1. C++Builder 5. Руководство разработчика. Уч. пособие в 2-х т. // Д. Холингвэрт,<br />

Д. Баттерфилд, Б. Сворт, Д. Оллсоп. М.: Изд. дом «Вильямс», 2001.<br />

880 и 832 с.<br />

2. Архангельский А. Я. Программирование в C++Builder 6. М.: Бином, 2003.<br />

1152 с.<br />

3. Бабэ Б. Просто и ясно о Borland C++. М.: Бином, 1995. 400 с.<br />

4. Водовозов В. М., Пожидаев А. К. Конструирование приложений для Windows:<br />

Учеб. пособие. СПб.: Изд-во СПбГЭТУ «ЛЭТИ», 2004. 72 с.<br />

5. Голуб А. Пpавила пpогpаммиpования C & C++. М.: Бином, 1996. 272 с.<br />

6. Кнут Д. Искусство программирования для ЭВМ. В 3-х кн. М.: Миp, 1999.<br />

7. Культин Н. Б. Самоучитель <strong>С++</strong>Builder. СПб.: БХВ-Петербург, 2005.<br />

320 с.<br />

8. Павловская Т. А. С/<strong>С++</strong>. Программирование <strong>на</strong> языке высокого уровня:<br />

Учебник. СПб.: Питер, 2003. 360 с.<br />

9. Сван Т. Освоение Borland C++ 4.5. Киев: Диалектика. 1996. 400 с.<br />

10. Шамис В. А. Borland C++Builder 6.0. Для профессио<strong>на</strong>лов. СПб.: Питер,<br />

2003. 798 с.


125<br />

Предметный указатель<br />

алгоритм, algorithm...................6, 59<br />

аллокатор, allocator.......................59<br />

аргумент, argument .......................10<br />

верши<strong>на</strong>, top ...................................52<br />

ветвь, branch ..................................57<br />

вход, input .......................................53<br />

вызов, call........................................10<br />

выход, output...................................53<br />

гиперссылка, hotspot........................7<br />

дек, deque........................................57<br />

дерево двоичное, binary tree..........57<br />

деструктор, destructor................102<br />

директива препроцессора,<br />

preprocessor directive ....................9<br />

дистрибутив, distributive..............81<br />

документ программный, program<br />

document....................................122<br />

заголовок, caption...........................11<br />

з<strong>на</strong>чение возвращаемое, return value<br />

........................................................8<br />

инициализация по умолчанию,<br />

default initialization .....................11<br />

интерфейс пользователя, user<br />

interface........................................85<br />

исключение, exception....................20<br />

источник данных, data source.......85<br />

итератор, iterator..........................59<br />

класс , class......................................14<br />

класс абстрактный, abstract class<br />

....................................................119<br />

класс дружественный, friend class<br />

....................................................104<br />

класс типизированный, typed class<br />

......................................................22<br />

константа глобаль<strong>на</strong>я, global<br />

constant ........................................12<br />

конструктор , constructor..............15<br />

конструктор копирующий, copying<br />

constructor....................................99<br />

конструктор по умолчанию, default<br />

constructor ................................. 101<br />

контейнер, container ..................... 59<br />

корень, root..................................... 57<br />

лист, sheet...................................... 57<br />

массив, array................................... 40<br />

моделирование, modelling............... 5<br />

модель, model................................... 5<br />

<strong>на</strong>бор данных, dataset.................... 85<br />

<strong>на</strong>следование единичное, unit<br />

inheritance ................................. 113<br />

<strong>на</strong>следование множественное,<br />

multiple inheritance ................... 113<br />

обработчик исключения, exception<br />

handler ......................................... 20<br />

объединение, union ........................ 51<br />

объявление, announcement ............ 10<br />

оператор разыменования,<br />

dereference operator .................... 24<br />

определение, declaration................ 10<br />

очередь, queue................................ 53<br />

пакет, package............................... 75<br />

параметр, parameter ..................... 10<br />

перегрузка, overloading ............... 105<br />

перемен<strong>на</strong>я внешняя, extern variable<br />

..................................................... 12<br />

перемен<strong>на</strong>я глобаль<strong>на</strong>я, global<br />

variable ........................................ 12<br />

перемен<strong>на</strong>я изменчивая, volatile<br />

variable ........................................ 12<br />

перемен<strong>на</strong>я локаль<strong>на</strong>я, local variable<br />

..................................................... 12<br />

перемен<strong>на</strong>я регистровая, register<br />

variable ........................................ 12<br />

перемен<strong>на</strong>я статическая, static<br />

variable ........................................ 12<br />

перемен<strong>на</strong>я файловая, file variable<br />

..................................................... 67<br />

перестановка, swap....................... 43


подключение, connection ...............85<br />

порядок заполнения, fill allocation52<br />

порядок обратный, breadth-first<br />

traversal........................................58<br />

порядок обхода, traversal...............52<br />

порядок прямой, depth-first traversal<br />

......................................................58<br />

порядок симметричный,<br />

simmetrical traversal....................58<br />

<strong>программирование</strong>, programming...5<br />

прототип, prototype......................10<br />

результирующий <strong>на</strong>бор, result set 92<br />

рекурсия, recursion.........................46<br />

сортировка , sorting .......................43<br />

сортировка быстрая, quick sorting<br />

......................................................44<br />

сортировка вставкой, insertion<br />

sorting...........................................44<br />

сортировка выборкой, exchange<br />

sorting...........................................44<br />

сортировка методом «пузырька»,,<br />

bubble sorting...............................44<br />

сортировка методом Шелла, Shell<br />

sorting...........................................44<br />

список инициализации, initialisation<br />

list...............................................101<br />

список линейный, linear list ...........54<br />

список связный, linked list.............51<br />

5<br />

список циклический, cyclic list...... 54<br />

ссылка, reference............................ 24<br />

стек, stack ...................................... 52<br />

структура , structure...................... 49<br />

структура ди<strong>на</strong>мическая, dynamic<br />

data structure................................ 51<br />

СУБД, DBMS................................. 84<br />

таблица глав<strong>на</strong>я, master ............... 94<br />

таблица подчинен<strong>на</strong>я, detail ........ 94<br />

тип параметра, parameter type ... 11<br />

тип порядковый, ordered data type<br />

..................................................... 22<br />

узел, node........................................ 54<br />

указатель, pointer.......................... 24<br />

файл, file......................................... 65<br />

функции тело, function body ........ 11<br />

функция , function............................. 8<br />

функция виртуаль<strong>на</strong>я, virtual<br />

function...................................... 117<br />

функция встроен<strong>на</strong>я, inline variable<br />

..................................................... 12<br />

функция глав<strong>на</strong>я, main function.... 10<br />

функция дружествен<strong>на</strong>я, friend<br />

function...................................... 103<br />

функция чистая виртуаль<strong>на</strong>я, pure<br />

virtual function .......................... 118<br />

шаблон, template .......................... 109<br />

язык, language .................................. 5


5<br />

Валерий Михайлович Водовозов,<br />

Федор Викторович Чмиленко<br />

<strong>Объектно</strong>-<strong>ориентированное</strong> <strong>программирование</strong> <strong>на</strong> <strong>С++</strong><br />

Учебное пособие<br />

Редактор<br />

Подписано в печать . Формат 60×84 1/16.<br />

Бумага офсет<strong>на</strong>я. Печать офсет<strong>на</strong>я. Печ. л. .<br />

Гарнитура «Times». Тираж 150 экз. Заказ .<br />

Издательство СПбГЭТУ «ЛЭТИ»<br />

197376, С.-Петербург, ул. Проф. Попова, 5

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

Saved successfully!

Ooh no, something went wrong!