31.01.2015 Views

язык программирования

язык программирования

язык программирования

SHOW MORE
SHOW LESS

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

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

В.Г. Давыдов<br />

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

и основы<br />

алгоритмизации<br />

Рекомендовано УМО по образованию в области радиотехники,<br />

электроники и биомедицинской техники и автоматизации<br />

в качестве учебного пособия для студентов высших<br />

учебных заведений, обучающихся по специальности<br />

«Управление и информатика в технических системах»<br />

Москва «Высшая школа» 2003<br />

'


УДК 004.4<br />

ББК 32.965<br />

Д 13<br />

Рецензенты:<br />

кафедра «Систем управления и информатики» Санкт-Петербургского государственного<br />

института точной механики и оптики (технического университета) (зав. кафедрой,<br />

д-р техн. наук, проф. В.В. Григорьев);<br />

канд. техн. наук, доц. Санкт-Петербургского электротехнического университета<br />

«ЛЭТИ» СВ. Власенко<br />

Давыдов, В.Г.<br />

Д 13 Программирование и основы алгоритмизации: Учеб. пособие/В.Г.<br />

Давыдов. — М.: Высш. шк., 2003. — 447 е.: ил.<br />

ISBN 5-06-004432-7<br />

Учебное пособие написано в соответствии с разработанной с участием автора<br />

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

Министерством образования Российской Федерации для подготовки бакалавров<br />

и специалистов по направлениям 5502 и 6519 «Автоматизация и управление».<br />

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

и умений — знание основных понятий программирования (слой 1), знание базового<br />

языка программирования C++ (слой 2) и умение решать задачи на ЭВМ (слой 3).<br />

Для удобства преподавателей и студентов приведено по 20 вариантов контрольных<br />

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

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

пособию компакт-диск содержит описание ПМ-ассемблера, его интегрированную<br />

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

Для студентов высших учебных заведений, обучающихся по специальности<br />

210100 — «Управление и информатика в технических системах».<br />

УДК 004.4<br />

ББК 32.965<br />

Учебное издание<br />

Давыдов Владимир Григорьевич<br />

ПРОГРАММИРОВАНИЕ И ОСНОВЫ АЛГОРИТМИЗАЦИИ<br />

Редактор ТВ. Рысева, Художник ТС. Лошаков<br />

Лицензия ид № 06236 от 09.П.01<br />

Изд. № РЕНТ-183. Подп в печать 14.08.03. Формат 60x88Vi6. Бум. газетная. Гарнитура «Тайме».<br />

Печать офсетная. Объем 27,44 усл. печ. л., 27,94 усл. кр.-отг.. Тираж 3000 экз. Заказ № 3184.<br />

ФГУП «Издательство «Высшая школа», 127994, Москва, ГСП-4, Неглинная ул., 29/14.<br />

Тел.: (095) 200-04-56. E-mail: mfo@v-shkola.ru http://www.v-shkola.ru<br />

Отдел реализации: (095) 200-07-69, 200-59-39, факс: (095) 200-03-01. E-mail: sales@v-shkola.ru<br />

Отдел «книга-почтой»: (095) 200-33-36. E-mail: bookpost@v-shkola.ru<br />

Отпечатано в ФГУП ордена «Знак Почета» Смоленской областной типографии им. В.И. Смирнова.<br />

214000, г. Смоленск, пр-т им. Ю. Гагарина, 2.<br />

ISBN 5-06-004432-7 © ФГУП «Издательство «Высшая школа», 2003<br />

Оригинал-макет данного издания является собственностью издательства «Высшая<br />

школа», и его репродуцирование (воспроизведение) любым способом без согласия издательства<br />

запрещается.


ПРЕДИСЛОВИЕ<br />

Учебное пособие обеспечивает курс "Программирование и основы<br />

алгоритмизации" и соответствует разработанной с участием<br />

автора примерной программе этого курса, рекомендованной Министерством<br />

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

по направлениям 5502 и 6519 "Автоматизация и управление". Пособие<br />

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

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

высших учебных заведений.<br />

Учебное пособие состоит из введения, двух частей и приложений.<br />

Во введении приводятся сведения о системах счисления, дается<br />

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

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

изучается язык C++ (за исключением его средств объектно-ориентированного<br />

программирования и стандартных библиотек)<br />

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

посвящена решению классических задач прикладного программирования,<br />

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

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

рассмотрение решения этих задач предметно знакомит с методологией<br />

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

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

и т.п. В прилоэюениях приведены следующие полезные сведения:<br />

• варианты тестов и программных проектов;<br />

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

средах программирования;<br />

• рекомендации по структуре программы и пример оформления ее<br />

исходного текста;<br />

• методика отладки программы;<br />

• примерная программа дисциплины "Программирование и основы<br />

алгоритмизации", рекомендованная Министерством образования<br />

и др.<br />

Для удобства преподавателей и студентов пособие содержит<br />

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

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

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

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

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

с учебным пособием можно приобрести компакт-диск, содержащий<br />

описание ПМ-ассемблера, его интегрированную среду программирования,<br />

полные тексты демонстрационных программ автора


и др.<br />

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

следующие три траектории изучения материала:<br />

1. Траектория, рассчитанная на 130 академических часов занятий<br />

в рамках подготовки бакалавров (направление 5502 "Автоматизация<br />

и управление") и специалистов (направление 6519). Это максимальная<br />

траектория, охватывающая весь изложенный в учебном<br />

пособии материал.<br />

2. Минимальная траектория, рассчитанная на 65 академических<br />

часов занятий. В рамках такого варианта:<br />

• не изучается материал, изложенный в подразд. 1.2, 6.6, 6.8, в<br />

разд. 7, 8 (кроме подразд. 8.1), 10, 11, 12 (кроме подразд. 12.1-<br />

12.6), 15 (кроме подразд. 15.8 и 15.9), 16 и 17 (кроме подразд.<br />

17.4);<br />

• не выполняются программные проекты, описанные в приложениях<br />

П.1.2.1 и П.1.2.3.<br />

3. Промежуточная траектория, рассчитанная на 100 академических<br />

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

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

в предыдущей траектории.<br />

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

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

davydov@aivt.ftk.spbstu.ru.<br />

Автор


1. ВВЕДЕНИЕ<br />

Электронная вычислительная машина (ЭВМ) представляет собой<br />

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

- это алгоритмы и структуры данных. Известна следующая<br />

формула Никлауса Вирта - разработчика языка Паскаль:<br />

Алгоритмы + структуры данных = программы<br />

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

и конечные результаты.<br />

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

необходимо применять к данным для получения требуемого<br />

конечного результата.<br />

1.1. Системы счисления<br />

в ЭВМ программы представлены с использованием двоичной<br />

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

Основным элементом ЭВМ является электронный ключ, имеющий<br />

два состояния - "включено" или "выключено". Это хорошо<br />

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

две цифры: "О" и "1", обозначающие один двоичный разряд - бит.<br />

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

системы счисления, применяемые в ЭВМ.<br />

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

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

системы счисления.<br />

В непозиционной системе счисления значение знака (символа)<br />

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

счисления.<br />

Позиционная система счисления - система, в которой значение<br />

цифры числа определяется ее положением (позицией) в числе. Любая<br />

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

Основание "^" позиционной системы счисления - количество цифр,<br />

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

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

\idi3b\bdiQMOQ развернутой формой записи числа:


где A^^j^ - произвольное число, записанное в системе счисления с основанием<br />

'V"; ^/ - коэффициенты ряда или значения разрядов числа<br />

(цифры системы счисления); п + 1 т - количество целых и дробных<br />

разрядов числа.<br />

На практике, для краткости, используют сокращенную запись<br />

чисел:<br />

Ая) = ^п


дробная - умножением на q = 2. Vi деление, и умножение выполняются<br />

в десятичной системе счисления.<br />

При делении 13 на 2 получаем частное 6 и остаток 1. При делении<br />

6 на 2 получаем частное 3 и остаток 0. При делении 3 на 2<br />

получаем остаток 1 и частное 1. Поскольку частное меньше двух,<br />

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

При умножении 0,125 на два получаем 0,250 {целая часть<br />

произведения 0). При умножении 0,250 на 2 получаем 0,500 {целая<br />

часть произведения 0). При умножении 0,500 на 2 получаем 1,000<br />

{целая часть произведения 7). Поскольку дробная часть теперь нулевая,<br />

то умножение на этом заканчивается.<br />

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

частное дают целую часть числа в двоичной системе счисления, а<br />

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

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

счисления<br />

1101,001(2)<br />

Обратите внимание, что перевод дробной части числа завершается<br />

при получении после точки всех нулей или при получении<br />

требуемого числа разрядов дробной части (требуемой точности).<br />

Особенно просто выполняется перевод чисел из двоичной системы<br />

счисления в системы счисления с основанием, равным степеням<br />

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

табл. 1.<br />

Табл. 1. Перевод чисел из двоичной системы в восьмеричную или<br />

шестнадцатеричную системы счисления и наоборот<br />

Восьмеричная Двоичная<br />

цифра<br />

триада<br />

0<br />

000<br />

1<br />

001<br />

2<br />

010<br />

3<br />

011<br />

4<br />

100<br />

5<br />

101<br />

6<br />

по<br />

7<br />

111<br />

Шестнадцатеричная<br />

цифра<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

А<br />

В<br />

С<br />

D<br />

Е<br />

F<br />

Двоичная<br />

тетрада<br />

0000<br />

0001<br />

0010<br />

ООН<br />

0100<br />

0101<br />

оно<br />

0111<br />

1000<br />

1001<br />

1010<br />

1011<br />

1100<br />

1101<br />

1110<br />

1111


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

1. 42)=1 101,1 1001(,) ^8)=<br />

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

двигаемся от точки влево, выделяя двоичные триады. Если оставшаяся<br />

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

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

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

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

в виде<br />

001 101 , 110010<br />

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

цифрами и получаем<br />

2. 42)=И01Д 1001(2) 4.6)=<br />

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

триад выделяются двоичные тетрады:<br />

1101 , 1100 1000<br />

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

цифрами и получаем<br />

^16) = -^'^^(16)<br />

3. 4,6)-^^1,^^06) 42)=<br />

в соответствии с табл. 1 заменяем шестнадцатеричные цифры<br />

тетрадами<br />

1010 1111 0001 ,1111 1110<br />

и получаем<br />

^2) =101011110001,1111111(2)<br />

4. 48)=710,526(,,) 42)=<br />

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

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

системы в шестнадцатеричную систему счисления и обратно. Такой<br />

перевод можно выполнить в два этапа - сначала из исходной системы<br />

счисления в двоичную систему и затем из двоичной в новую систему<br />

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

самостоятельно.<br />

1.2. Классификация языков программирования<br />

и их краткая характеристика<br />

Языки программирования делятся на две группы:


1. Машинно-зависимые языки, которые можно применять на<br />

одной ЭВМ или на ограниченном подмножестве машин с одинаковой<br />

архитектурой.<br />

2. Машинно-независимые языки - их можно использовать на<br />

любой ЭВМ. Языки этой группы называют универсальными языками.<br />

Машинно-зависимые языки^ в зависимости от их близости к<br />

машинным языкам, делятся на три группы:<br />

• машинные языки (языки нулевого уровня);<br />

• ассемблерные языки (языки первого уровня или языки типа 1:1,<br />

последнее означает, что одна ассемблерная команда после трансляции<br />

порождает ровно одну машинную команду);<br />

• макроассемблеры (языки второго уровня или языки типа \\п).<br />

Аналогично, маилинно-независимые языки включают следующие<br />

группы языков:<br />

• Процедурные языки (третий уровень): Си, C+-I-, Паскаль,<br />

ФОРТРАН, БЭЙСИК и др. Процедурные языки требуют детальной<br />

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

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

• Проблемные языки (четвертый уровень) или языки типа "заполни<br />

бланк". Это языки описания задач, специализированные языки.<br />

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

только, какую задачу надо решить и с какими данными.<br />

Как решить задачу - "знает" язык. В качестве примера проблемного<br />

языка можно назвать язык ПРОСПО, разработанный фирмой<br />

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

процессами.<br />

• Универсальные языки (пятый уровень): ПЛ/1, АЛГОЛ-68, Ада и<br />

др. При создании универсальных языков в их состав включили все<br />

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

1.2.1. Машинные языки<br />

Рассмотрим машинные языки на примере простого машинного<br />

языка (ПМ), разработанного для студентов кафедры "Автоматика и<br />

вычислительная техника" Санкт-Петербургского государственного<br />

политехнического университета проф. Лекаревым М.Ф. Язык ПМ<br />

подробно рассмотрен в [1] и его описание имеется на прилагаемом<br />

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

еще и компакт-диск, рекомендуем рассмотреть имеющийся в<br />

[1] материал, включая структурную организацию и функционирование<br />

простой ЭВМ.


Пример. Запрограммируем на машинном языке ПМ решение<br />

следующей задачи: ввести исходные данные<br />

А,В,С,<br />

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

Е=АВ + С/\,5<br />

Система команд машины ПМ содержит несколько десятков<br />

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

лишь те, которые будут необходимы для решения указанной задачи.<br />

КОП<br />

01<br />

02<br />

03<br />

04<br />

05<br />

06<br />

07<br />

08<br />

Табл. 2. Таблица кодов операций (КОП)<br />

Выполняемое действие (обозначение команды)<br />

Передача слова из ОЗУ в регистр А арифметико-логического устройства<br />

АЛУ (ЧТЕНИЕ)<br />

Передача слова из регистра А АЛУ в ОЗУ (ЗАПИСЬ)<br />

Слолсение содержимого регистра А АЛУ с операндом, заданным в команде.<br />

Результат помещается в регистр А АЛУ (+)<br />

Вычитание ( - ). Операция выполняется аналогично сложению<br />

Умножение ( * ).Операция выполняется аналогично сложению<br />

Деление ( / ).Операция выполняется аналогично сложению<br />

Ввод слова с устройства ввода в ОЗУ (ВВОД)<br />

Вывод слова из ОЗУ в устройство вывода (ВЫВОД)<br />

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

языке требуется решить следующие вопросы:<br />

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

например, в начале памяти (табл. 3)<br />

Имя переменной<br />

А<br />

В<br />

С<br />

"1,5"<br />

Е<br />

Табл. 3. Таблица символов (ТС)<br />

Адрес в ОЗУ (для удобства используется десятичный адрес<br />

вместо двоичного адреса)<br />

00000<br />

00001<br />

00002<br />

00003<br />

00004<br />

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

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

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

3. Составить собственно программу, т.е. записать коды машинных<br />

команд с указанием места их размещения в ОЗУ. Напоминаем,<br />

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

10


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

десятичными кодами. Машинная программа приведена в табл. 4.<br />

Проанализировав содержимое табл. 3, оценим возможности и<br />

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

В оперативном запоминающем устройстве (ОЗУ) между местом<br />

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

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

памяти нужно ликвидировать. Однако заранее оценить размер<br />

памяти, необходимой для размещения данных и команд, нельзя или,<br />

по крайней мере, очень трудно.<br />

1 Адрес<br />

команды<br />

01000<br />

01001<br />

01002<br />

01003<br />

01004<br />

01005<br />

01006<br />

01007<br />

01008<br />

01009<br />

01010<br />

01011<br />

01012<br />

01013<br />

КОП<br />

07<br />

08<br />

07<br />

08<br />

07<br />

08<br />

01<br />

06<br />

02<br />

01<br />

05<br />

03<br />

02<br />

08<br />

Табл.<br />

Адрес<br />

операнда<br />

00000<br />

00000<br />

00001<br />

00001<br />

00002<br />

00002<br />

00002<br />

00003<br />

00004<br />

00000<br />

00001<br />

00004<br />

00004<br />

00004<br />

4. Машинная программа<br />

Действие команды<br />

УВв -> ОЗУ по адресу 000000 (А)<br />

Содержимое ячейки ОЗУ с адресом 00000 (А) -^ УВыв<br />

УВв -> ОЗУ по адресу 000001 (В)<br />

Содержимое ячейки ОЗУ с адресом 00001 (В) —> УВыв<br />

УВв -> ОЗУ по адресу 000002 (С)<br />

Содержимое ячейки ОЗУ с адресом 00002 (С) —> УВыв<br />

Содержимое ячейки ОЗУ с адресом 00002 (С) -^<br />

регистр А АЛУ<br />

Содержимое регистра А АЛУ (С) разделить на<br />

содержимое ячейки ОЗУ с адресом 00003 (1.5) и<br />

результат поместить в регистр А АЛУ<br />

Содержимое регистра А АЛУ (С/1.5) -^ ОЗУ по адресу<br />

00004 (Е)<br />

Содержимое ячейки ОЗУ с адресом 00000 (А) —><br />

регистр А АЛУ<br />

Содержимое регистра А АЛУ (А) умножить на<br />

содержимое ячейки ОЗУ с адресом 00001 (В) и<br />

результат поместить в регистр А АЛУ<br />

Содержимое регистра А АЛУ (А*В) сложить с<br />

содержимым ячейки ОЗУ с адресом 00004 (С/1.5) и<br />

результат поместить в регистр А АЛУ<br />

Содержимое регистра А АЛУ (А*В+С/1.5) -^ ОЗУ по<br />

адресу 00004 (Е)<br />

Содержимое ячейки ОЗУ с адресом 00004 (А*В+С/1.5)<br />

-^ УВыв 1<br />

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

эти размеры меняются. Поэтому действия, указанные выше в пп. 1 -<br />

3, приходится повторять.<br />

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

языка заключаются в следующем.<br />

1. Действия, указанные в пп. 1-2 нетворческие, поэтому при<br />

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

можно автоматизировать с помош;ью ЭВМ.<br />

11


2. Реальные системы команд ЭВМ громоздки (сотни различных<br />

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

трудоемко и ненаглядно. Машинную программу трудно воспринимать<br />

(см. табл. 4 без левого и правого столбцов).<br />

Из-за отмеченных недостатков машинные программы давно<br />

перестали писать. Вместе с тем отметим достоинство программ, написанных<br />

с использованием машинных языков, - они имеют наибольшее<br />

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

это может быть достигнуто только ценой больших затрат времени<br />

квалифицированного программиста.<br />

Многие из названных выше недостатков машинных программ<br />

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

1.2.2. Ассемблерные языки (на примере ПМ-ассемблера)<br />

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

языков следующими особенностями:<br />

1. Вместо двоичных, восьмеричных, шестнадцатеричных и десятичных<br />

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

символическая запись. Перевод символических записей в<br />

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

с помощью ЭВМ.<br />

2. Размещение в ОЗУ данных и команд также автоматически<br />

выполняет ЭВМ, причем без "дырок". Действия, указанные в пп. 1 -<br />

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

транслятором (компилятором).<br />

3. Оптимальность программы по быстродействию и занятой<br />

памяти практически сохраняется (проигрыш не более 5... 10 %). Это<br />

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

программистов.<br />

Пример программы на языке ПМ-ассемблер, подобный примеру<br />

из подразд. i.2.1, приведен в [1] в разд. 7 (см. прилагаемый компакт-диск).<br />

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

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

(рис. 1). Трансляция выполняется в два прохода.<br />

Проход 1. Составляется таблица символов (ТС).<br />

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

(используются ТК и ТС).<br />

1.2.3. Макроассемблерные языки<br />

Макроассемблерные языки сейчас являются самыми мощными.<br />

Объясняется это тем, что макроассемблер обладает всеми возмож-<br />

12


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

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

примером.<br />

Пример. Предположим, что часто встречается вычисление<br />

R^{X^+Y^)/{X'Y)<br />

Текст программы<br />

на ассемблере<br />

Таблица кодов<br />

операций (ТК)<br />

Таблица<br />

символов (ТС)<br />

Текст программы<br />

на машинном<br />

языке<br />

Рис. 1. Схема трансляции в два прохода<br />

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

макроопределения, имеющего следующую структуру:<br />

[Г<br />

к 2 10<br />

МЕТКА<br />

11 20<br />

ОПЕРАЦИЯ<br />

21<br />

ОПЕРАНД<br />

к Стандартное начало , MACRO -<br />

К придумывает прог раммист<br />

MACRO<br />

ЧТЕНИЕ<br />

*<br />

ЗАПИСЬ<br />

ЧТЕНИЕ<br />

*<br />

+<br />

/<br />

/<br />

ЗАПИСЬ<br />

SUB(X, Y,<br />

X<br />

X<br />

Х2<br />

Y<br />

Y<br />

Х2<br />

X<br />

Y<br />

R<br />

MEND<br />

40<br />

ключевое<br />

R)<br />

КОММЕНТАРИЙ<br />

слоъо, SUB(X, Y, R) -<br />

Тело<br />

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

Стандартный конец<br />

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

С = (А^+В')/(А-В) и G-={E^+F^)/(E-F)<br />

Это можно сделать в программе с помощью однострочных<br />

макровызовов:<br />

13


2 10<br />

МЕТКА<br />

11 20<br />

ОПЕРАЦИЯ<br />

21 40<br />

ОПЕРАНД<br />

41<br />

КОММЕНТАРИЙ<br />

SUB(A, В, С)<br />

SUB(E, F, G)<br />

Здесь X, Y, R - формальные, а А, В, С и Е, F, G - фактические<br />

параметры. Вызов "SUB(A, В, С) " заменяется фактически модифицированным<br />

телом макроопределения, в котором формальные параметры<br />

X, Y, R заменены соответственно фактическими параметрами<br />

Л, В, С.<br />

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

частности, программист может, при необходимости, разработать<br />

свой набор макроопределений - это эквивалентно разработке в рамках<br />

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

- макровызовами. При этом сохраняется высокая эффективность<br />

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

и занятой памяти не превышает 10... 15 %.<br />

1.2.4. Машинно-независимые языки.<br />

Процедурные и универсальные языки<br />

Чтобы сопоставить машинно-независимые языки с процедурными<br />

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

и универсальных языках высокого уровня:<br />

/'*' пример на языке Си, Вычисляем D := А * В + С / 2 . 0 * /<br />

^include <br />

±nt main ( void )<br />

{<br />

float<br />

scanf(<br />

Л, B, C, D;<br />

" %f %f %f", &A,<br />

printf( " %f %f %f". A,<br />

D=A*B+C/2.0;<br />

printf( "\n %f", D ) ;<br />

return 0;<br />

&B, &C ) ;<br />

B, С ) ;<br />

PROGRAM PEMPAS ( INPUT, OUTPUT ) ;<br />

{ Пример на языке ПАСКАЛЬ. Вычисляем D : = A * B + C / 2 . 0 } |<br />

VAR<br />

А, Б, С, D: REAL;<br />

BEGIN { Для PRMPAS<br />

READ( А, В, С ) ;<br />

WRITE( А, В, С ) ;<br />

D := А ^ В + С / 2.0;<br />

WRITE( D )<br />

END. { Для PRMPAS<br />

14


PROGRAM PRMF7 7<br />

С Пример для языка ФОРТРАН 7. Вычисляем D : = A ' ^ B - h C / 2 . 0<br />

REJLL А, В, С, D<br />

READ *г ^f Br С<br />

PRINT *r А, В, С<br />

D = А ^ В + С / 2.0<br />

PRINT *, D<br />

STOP<br />

END<br />

100 REM Пример на БЭИСИКе. Вычисляем D : = A * B + C / 2 . 0<br />

110 INPUT А, В, С<br />

120 PRINT А, В, С<br />

130 PRINT А * В + С / 2 . 0<br />

140 END<br />

\ /^ Пример на языке PL/1. Вычисляем D := А * В + С / 2.0 */<br />

PRMPL1: PROCEDXmE OPTIONS ( MAIN ) ;<br />

DECLARE ( A, Br Cr D ) REAL FLOAT DEC ( 6 ) ;<br />

GET LIST( Ar Br С ) ;<br />

PUT LIST( Ar Br С ) ;<br />

D = A ^ В + С / 2.0<br />

PUT LIST ( D ) ;<br />

END PRMPLl;<br />

Из приведенных примеров видны удобства работы на машинно-независимых<br />

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

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

Эффективность программ, написанных на языках высокого<br />

уровня, понижена в 2 - 4 раза. Для программ на языках Си/С++ понижение<br />

эффективности составляет лишь 1,5 раза и объясняется это<br />

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

языков.


ЧАСТЬ 1. БАЗОВЫЙ ЯЗЫК<br />

ПРОГРАММИРОВАНИЯ<br />

2. ЯЗЫК ПРОГРАММИРОВАНИЯ<br />

ВЫСОКОГО УРОВНЯ C++<br />

Язык программирования Си был разработан в 1972 году Д.<br />

Ритчи в фирме Bell Laboratories (США) как универсальный язык<br />

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

системы UNIX. Эта операционная система (ОС) была, в<br />

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

на любые ЭВМ. Действительно, для каждой архитектуры ЭВМ достаточно<br />

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

ОС UNIX легко переносится на новую ЭВМ, принадлежащую<br />

соответствующей архитектурной линии.<br />

При разработке языка Си был принят компромисс мелсду низким<br />

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

Паскаль, ФОРТРАН, БЭЙСИК, ПЛ/1 и др. Многие и многие операции<br />

языка Си (манипулирования строками, ввода-вывода и др.) вынесены<br />

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

могут быть вызваны из Си-программ. Такое решение обеспечило<br />

высокую эффективность языка Си (высокое быстродействие и малые<br />

затраты памяти).<br />

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

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

Такими конструкциями являются следование, ветвление,<br />

циклы; модули, называемые функциями. Язык Си побуждает<br />

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

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

В дополнение к сказанному, язык 0++, являющийся дальнейшим<br />

развитием языка Си, содержит такой важный инструмент,<br />

как средства объектно-ориентированного программирования (ООП).<br />

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

то один из первых вопросов, на который нужно ответить<br />

"Да" - это вопрос "Умеете ли Вы программировать на языках<br />

Си/С++".<br />

16


2.1. Введение. Структурное и модульное<br />

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

Суть структурного программирования иллюстрирует рис. 2.<br />

Необходимо подробно проанализировать этот графический конспект-рисунок,<br />

потому, что, во-первых, он первый, и, во-вторых, на<br />

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

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

программ ирован ия.<br />

Что такое АЛГОРИТМ<br />

Как его записать<br />

Аль Хорезми<br />

Структурное<br />

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

Ветвления<br />

if - then - else<br />

switch<br />

Следование<br />

Дейкстра<br />

Циклы<br />

while<br />

do - while<br />

for<br />

Рис. 2. Алгоритм и технология структурного программирования<br />

Если посмотреть на верхнюю часть графического конспектарисунка,<br />

то внимание сразу привлекает слово АЛГОРИТМ.<br />

2.1.1. Алгоритм и способы его записи<br />

АЛГОРИТМ - одно из основных понятий современной математики<br />

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

математика IX в. Мухаммеда, уроженца Хорезма (по-арабски<br />

"Аль Хорезми"). Его работы по арифметике и алгебре были переведены<br />

на латинский язык в XII в. и оказали большое влияние на развитие<br />

математики в Европе. Сформулированные ученым правила<br />

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

"алгоризм", дальнейшая трансформация — "алгоритмус", "алгорифм"<br />

и "алгоритм". Интуитивное понятие алгоритма можно выразить следующим<br />

образом.<br />

Алгоритм - строгая и четкая система правил, которая определяет<br />

последовательность действий над некоторыми объектами и по-<br />

17


еле конечного числа шагов приводит к достижению поставленной<br />

цели.<br />

Примерами алгоритмов могут являться:<br />

• рецепты приготовления блюд;<br />

• пояснения, "как пройти", "как проехать";<br />

• правило умножения целых чисел "столбиком" и т.п.<br />

Вместе с тем, "школьная" таблица умножения не является алгоритмом.<br />

Способы записи алгоритмов. Один и тот же алгоритм может<br />

быть записан (описан) различными способами. Наибольшее распространение<br />

в практике программирования получили [2]:<br />

• текстуальная (словесная) запись алгоритма;<br />

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

способа - формализма);<br />

• запись алгоритма с использованием диаграммы Нэсси-<br />

Шнейдермана (разновидность визуального формализма);<br />

• запись алгоритма с использованием Р-схемы (разновидность визуального<br />

формализма);<br />

• запись алгоритма с помощью псевдокода^<br />

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

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

используются последовательно, на различных этапах решения задачи.<br />

Только три способа записи алгоритма — с использованием схемы,<br />

диаграммы Нэсси-Шнейдермана и Р-схемы - являются альтернативными<br />

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

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

записи алгоритмов. С этой целью один и тот же алгоритм запишем<br />

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

Текстуальная запись алгоритма. Имеется следующий алгоритм,<br />

записанный в текстуальной форме:<br />

1. Начало алгоритма.<br />

2. Выполнить некоторое действие (оператор) si.<br />

3. Если выполнено условие "Усл1", то выполнить операторы<br />

s2, s3 и перети к п. 4. Иначе - перейти к пп. 3.1.<br />

3.1. Пока выполняется условие "Усл2", выполнять пп. 3.2<br />

и 3.3. Иначе - перейти к п. 4.<br />

3.2. Если выполнено условие "УслЗ", то выполнить оператор<br />

s4, иначе — выполнить оператор s5.<br />

3.3. Выполнить оператор s6.<br />

4. Пока выполняется условие "Усл4", выполнять оператор s7.<br />

Иначе — перейти к п. 5.<br />

5. Выполнить оператор s8.<br />

6. Конец алгоритма.<br />

18


Запись алгоритма с помощью схемы (ГОСТ 19.701 - 90, совместим<br />

с меэюдународным стандартом). Запись того же самого<br />

алгоритма в виде схемы иллюстрирует рис. 3.<br />

В случае простой схемы сравнительно несложно обеспечить ее<br />

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

фрагмента алгоритма (программы), его логическая структура начинает<br />

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

схема алгоритма [2]. Поэтому практика использования схем<br />

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

ее как инструмент разработки только эпизодически. Там, где<br />

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

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

Запись алгоритма с помощью диаграммы Нэсси-Шнейдермана<br />

(рис. 4). Диаграмма Нэсси-Шнейдермана была предложена в сочетании<br />

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

проблемой "клубка спагетти", присущей схемам алгоритмов. Эта<br />

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

операторов двумерным (см. рис. 4). Тем не менее, по мере роста<br />

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

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

меньше. На рис. 4 приведена диаграмма Нэсси-Шнейдермана для<br />

того же самого алгоритма, что и ранее.<br />

Запись алгоритма с помощью Р-схемы (рис. 5). Используемая<br />

при этом Р-технология программирования разработана в Институте<br />

Кибернетики АН УССР. Согласно Р-технологии программа должна<br />

быть представлена в форме нагруженного по дугам структурного<br />

графа (Р-схемы). Р-схема состоит из подграфов, каждый из которых<br />

имеет один вход и один выход. Вершины графа называются состояниями<br />

Р-схемы. Переходы из одного состояния в другое представлены<br />

помеченными дугами, причем каждая дуга может быть помечена<br />

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

перехода. На рис. 5 приведена Р-схема для того же самого<br />

алгоритма.<br />

Запись алгоритма с помощью псевдокода. Довольно широкое<br />

распространение получил еще один способ записи алгоритма с помощью<br />

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

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

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

практики промежуточный язык. Это и не естественный язык, и не<br />

язык программирования, а их симбиоз. Псевдокод похож на язык<br />

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

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

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

19


а) Точка входа или выхода<br />

Операционный блок<br />

(функциональный узел)<br />

( )<br />

Решающий блок (предикатный узел)<br />

Циклическая конструкция<br />

Поток управления<br />

б)<br />

ГЦикл «Усл2»<br />

Не «Усл2»<br />

Цикл «Уcл4>^<br />

Не «Усл4»<br />


а)<br />

Программа<br />

Простая конструкция<br />

Заголовок программы<br />

Тело программы<br />

Оператор<br />

Условная конструкция<br />

Циклическая конструкция<br />

Условие<br />

Тело цикла<br />

б)<br />

т<br />

Пример программы<br />

s1<br />

Усл1<br />

F<br />

s2<br />

Усл2<br />

т \<br />

УслЗ / ^<br />

S3<br />

s4<br />

s5<br />

s6<br />

Усл4<br />

s8<br />

Рис. 4. Диаграмма Нэсси-Шнейдермана:<br />

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

2.1.2. Структурное и модульное программирование<br />

Применительно к решению задачи на ЭВМ, можно сформулировать,<br />

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

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

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

упомянутые действия. Действия описываются с помощью операторов,<br />

а данные - с помощью определений или объявлений.<br />

В 1965 г. профессор Эйндховенского университета Дейкстра<br />

(Нидерланды) начал пропагандировать стиль программирования,<br />

получивший название "программирование без оператора goto (безусловного<br />

перехода)", первоначально принятый большинством программистов<br />

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

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

s7<br />

21


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

этого подхода. По мере изучения программирования будут<br />

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

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

программирования.<br />

а) Состояние Р-схемы<br />

Совмещение двух состояний в<br />

одно (цикл)<br />

Дуга Р-схемы<br />

Условие<br />

Действие<br />

б)<br />

Усл1<br />

склгыэ<br />

Усл4<br />

s7<br />

Рис. 5. Р-схема:<br />

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

Основными управляющими конструкциями структурного программирования<br />

являются:<br />

• СЛЕДОВАНИЕ - если в записи алгоритма (программы) подряд<br />

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

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

• ЕСЛИ-ТО-ИНАЧЕ - условная конструкция, определяющая разветвление<br />

в порядке выполнения действий (операторов). Дословный<br />

перевод этой конструкции if-then-els е.<br />

• ЦИКЛЫ. В структурном программировании предусмотрены циклические<br />

конструкции трех видов:<br />

1. Цикл с предусловием ПОКА-ДЕЛАЙ: пока истинно некоторое<br />

условие, делай то-то и то-то (дословный английский перевод<br />

этой конструкции while).<br />

2. Цикл с постусловием ПОВТОРЯЙ-ПОКА (do-while). Отличается<br />

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

менее одного раза.<br />

22


3. Цикл с заранее заданным числом повторений (Jor).<br />

Этих трех элементарных конструкций, называемых базовыми<br />

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

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

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

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

Ветвление (условная конструкция)<br />

1) Общий случай (рис. 6


...<br />

else<br />

{<br />

С = F/ // ВЕТВЬ-ИНАЧЕ<br />

// . . .<br />

}<br />

2) Частный случай (см. рис. 6 б)<br />

"ЕСЛИ" УСЛОВИЕ "ТО" ВЕТВЬ-ТО "ВСЕ"<br />

// C+-h реализация<br />

±f( А > В )<br />

{<br />

D = Е; // ВЕТВЬ-ТО<br />

// . . .<br />

;<br />

Циклы<br />

1) С предусловием (рис. 7, 8). Пример<br />

SUMMA = Y,I<br />

"ПОКА" УСЛОВИЕ "ДЕЛАТЬ" ТЕЛО-ЦИКЛА "ВСЕ"<br />

// C++: ЦИКЛ С ПРЕДУСЛОВИЕМ<br />

SUMMA = 0 / 1 = 1 / // ПОДГОТОВКА ЦИКЛА<br />

while ( I


do<br />

{<br />

SUMMA -h= I;<br />

I += 1;<br />

}<br />

while( I 20<br />

SUMMA := SUMMA+1;<br />

I := 1 + 1;<br />

Цикл «I»<br />

Рис. 7. Цикл с предусловием (while)<br />

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

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

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

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

(разбиение) исходной задачи на функционально закончен-<br />

25


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

языках Си/С-ь+ называются функциями.<br />

Вариант по ГОСТ<br />

SUMMA := 0;<br />

Цикл «I»<br />

I >20<br />

1, 20, +1 - начальное, конечное<br />

значения "1" и шаг изменения "1"<br />

SUMMA :=<br />

SUMMA +I<br />

Цикл «I»<br />

Рис. 8. Цикл с предусловием {for)<br />

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

ее параметров можно использовать "правило семь ± два".<br />

Смысл этого правила заключается в том, что человек хорошо воспринимает<br />

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

операторов языка программирования и т.п. Таким образом, при хорошо<br />

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

обычно 25-81 строк текста, а количество параметров не превышает<br />

5-9. Размер функции 25-81 строк текста получается, если в ее блоке<br />

содержится не более 5-9 элементарных конструкций, каждая из<br />

которых занимает не более 5-9 строк. Модули (функции Си) можно<br />

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

сокращению сроков проектирования программных проектов<br />

и привлечению к работе над проектами коллективов программистов.<br />

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

нисходящего программирования, для сложных программных проектов<br />

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

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

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

соответствующих хорошему стилю программирования.<br />

26


Вариант по ГОСТ<br />

SUMMA= 1 +2 + ... + 20<br />

SUMMA:=0;<br />

I := 1;<br />

SUMMA:=0;<br />

I := 1;<br />

Цикл «I»<br />

(«ПОВТОРЯЙ»)<br />

SUMMA := SUMMA + I;<br />

I := I + 1;<br />

Нет<br />

(«ПОКА»)<br />

УСЛОВИЕ<br />

ТЕЛО-ЦИКЛА<br />

SUMMA := SUMMA + I<br />

I := I + 1;<br />

I >20<br />

Цикл «I»<br />

Рис. 9. Цикл с постусловием<br />

2.2. Язык программирования и его описание<br />

(на примере языков Си/С++)<br />

Совокупность понятий, относящихся к указанной в заголовке<br />

теме, иллюстрирует рис. 10. На нем сразу же обращают на себя внимание<br />

слова "ЯЗЫК ПРОГРАММИРОВАНИЯ", "АЛФАВИТ",<br />

"СИНТАКСИС + СЕМАНТИКА" и "цветок с лепестками". Обсудим<br />

эти понятия подробнее.<br />

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

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

не столько для общения между людьми, сколько для общения<br />

между человеком и ЭВМ. Это определяет следующие разумные<br />

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

1. Он должен быть достаточно простым^ чтобы быть доступным<br />

широкому кругу людей.<br />

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

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

3. Алгоритм, записанный на некотором языке, должен быть<br />

сначала переведен на машинный язык ЭВМ. Переводом занимается<br />

специальная программа - транслятор. Язык должен быть простым<br />

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

функционировании транслятора.<br />

27


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

Си/С++.<br />

Языки Си/С++, как и любые другие языки программирования,<br />

полностью определяются заданием их алфавита (словаря исходных<br />

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

(смысла) - "СИНТАКСИС + СЕМАНТИКА" (см. рис. 10).<br />

ж<br />

-<br />

Строчные<br />

и<br />

прописные<br />

латинские<br />

буквы<br />

J..<br />

Простота<br />

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

использовать нельзя<br />

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

/* */<br />

\ \п \г \t \" \'<br />

и др.<br />

/* + - ; % » «<br />

= == !=<br />

& I - '^ !.->{}() &&<br />

: = +=-= *= /= %= »= «=<br />

&= 1= '^= [ ] ++ - ,<br />

Только в языке C++<br />

:: // .* ->*<br />

и др.<br />

Специальные символы<br />

АЛФАВИТ (ЛИТЕРЫ)<br />

СИНТАКСИС + СЕМАНТИКА<br />

Однозначность<br />

ЯЗЫК ПРОГРАММИРОВАНИЯ<br />

Рис. 10. Язык программирования и его описание<br />

Алфавит языка - набор основных символов (литер), используемых<br />

для записи алгоритма. На рис. 10 изображены литеры языков<br />

Си/С++ - три "лепестка цветка". Следует отметить, что некоторые<br />

литеры алфавита являются составными, изображаются двумя или<br />

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

"+=" или "»=").<br />

Рис. 1 1 содержит информацию о способах описания синтаксиса<br />

языка, где обращают на себя внимание две фамилии — Д. Бэкус и<br />

Н. Вирт и приведена информация, касающаяся способов описания<br />

28


синтаксиса языка, предложенных Д. Бэкусом и Н. Виртом в противопоставлении<br />

друг другу (две параллельных колонки).<br />

Из допустимых символов языка, указанных на рис. 10, можно<br />

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

соответствии с синтаксисом языка. Удобными способами описания<br />

синтаксиса языка являются следующие способы.<br />

1. Использование металингвистических формул (предложены<br />

Д. Бэкусом, автором языка АЛГОЛ-60).<br />

2. Синтаксические диаграммы (предложены Н. Виртом, автором<br />

языка Паскаль).<br />

На рис. 11 приведены определения одних и тех же понятий как<br />

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

диаграммы.<br />

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

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

следующие обозначения:<br />

"::=" - знак, который читается как "это есть по определению";<br />

- пишется слева от "::=";<br />

I - обозначает "ИЛИ";<br />

( ) — круглые скобки, обозначают "И";<br />

{ } — фигурные скобки, обозначают неограниченное повторение<br />

ноль, один, два и т.д. раз, заключенной в них конструкции;<br />

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

заключенной в эти скобки.<br />

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

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

Вместе с тем отметим, что в интегрированных средах программирования<br />

на языке C++ различимая длина идентификаторов<br />

может задаваться программистом с помощью соответствующей опции.<br />

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

Так, в частности, ALPHA и alpha - разные идентификаторы.<br />

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

столбец на рис. 11. Синтаксическая диаграмма - это схема, составленная<br />

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

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

- литеры или составные символы языка. Сопоставляя определения<br />

понятий обоими способами легко понять смысл и особенности<br />

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

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

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

29


д. Бэкус<br />

Металингвистические<br />

формулы<br />

::= A|B|C|...|Z<br />

::= a|b|c|...|z<br />

Прописная_буква<br />

Н. Вирт<br />

Синтаксические<br />

диаграммы<br />

Строчная_буква<br />

-Буква> ::= ( |<br />

)<br />

-Ненул_восьм_цифра> 1|2|...|7<br />

Буква<br />

^ Прописнаябуква<br />

Строчнаябуква •—J<br />

Ненул_восьм_цифра<br />

^<br />

-Восьмеричная_цифра> ::=<br />

( |0 )<br />

::=<br />

( |8|9 )<br />

:;=<br />

( |0 )<br />

Восьмеричная_цифра<br />

1<br />

Ненул_восьм<br />

_цифра<br />

I<br />

Ненул_дес_цифра<br />

X<br />

Ненул_восьм<br />

_цифра<br />

Десятичная_цифра<br />

::= ( |_ )<br />

{ ( |<br />

|_)}<br />

!!! ДЛИНАИДЕнтификатора !!!<br />

Эквивалентно<br />

Ж ДЛИНАИДЕ 8! >К.<br />

Ненул_дес_<br />

цифра<br />

I<br />

Идентификатор<br />

Буква<br />

Буква<br />

о-<br />

Дес_цифра<br />

Рис. 1 1. Способы описания синтаксиса языка<br />

Семантика определяет смысл предложений (операторов), записанных<br />

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

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

30


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

совокупностями других предложений языков Си/С++.<br />

2.3. Структура и конструкция программы на Си/С++<br />

Базовыми элементами языков Си/С++ являются:<br />

• комментарии;<br />

• идентификаторы;<br />

.• служебные (зарезервированные) слова;<br />

• константы;<br />

• операторы;<br />

• разделители.<br />

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

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

2.3.1. Комментарии<br />

Синтаксическая диаграмма комментария к фрагменту Сипрограммы<br />

приведена на рис. 12.<br />

Комментарий<br />

>Ci*<br />

Печатный символ<br />

Рис. 12. Определение комментария в языке Си<br />

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

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

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

количество строк:<br />

/'*' Это однострочный комментарий */<br />

/-^<br />

Компилятор языка Си рассматривает эти<br />

строки как комментарий<br />

"-/<br />

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

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

компиляторов:<br />

31


Эта часть комментария правильная<br />

/'*• Начало этого комментария игнорируется. */<br />

Эта строка теперь находится вне комментария! Ошибка!<br />

Этот пример показывает, что внутренняя пара символов "/*"<br />

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

Тем самым, предпоследняя строка и последняя пара символов<br />

"*/" окажутся вне комментария и при попытке их компиляции будет<br />

выдано сообщение об ошибке.<br />

Наряду с рассмотренными вариантами в языке C++ имеется и<br />

другая форма записи комментария:<br />

// Это однострочный комментарий<br />

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

комментарии для пояснений к определению некоторого<br />

объекта или пояснений к отдельному оператору.<br />

/* Коммен- // Такое вложение возможно! -тарий */<br />

// Коммен- /* И так тоже можно! */ -тарий<br />

Двусмысленность!<br />

X = Y//* Это деление */Z;<br />

Надо так:<br />

X = Y/ /->" Это деление ^/ Z;<br />

2.3.2. Идентификаторы<br />

Идентификаторы были рассмотрены выше (см. рис. 11). Идентификатор<br />

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

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

2.3.3. Служебные слова<br />

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

специальное значение для компиляторов языков Си/С++. Их<br />

нельзя использовать как имя переменной. Ниже приведен список<br />

служебных слов языка C++:<br />

asm<br />

case<br />

const<br />

delete<br />

dynamic<br />

cast<br />

auto<br />

catch<br />

const<br />

do<br />

else<br />

cast<br />

bool<br />

char<br />

continue<br />

double<br />

enum<br />

break<br />

class<br />

default<br />

explicit<br />

32


export<br />

for<br />

Inline<br />

namespa.ce<br />

protected<br />

return<br />

static<br />

template<br />

try<br />

vmion<br />

void.<br />

extern<br />

friend<br />

int<br />

new<br />

public<br />

short<br />

static_cast<br />

this<br />

typedef<br />

unsigned<br />

volatile<br />

false<br />

goto<br />

long<br />

operator<br />

register<br />

signed<br />

struct<br />

throw<br />

typeid<br />

using<br />

wchar t<br />

float<br />

if<br />

xmitable<br />

private<br />

reinterpret^cast<br />

sizeof<br />

switch<br />

true<br />

typename<br />

virtual<br />

while<br />

Трансляторы языков Cu/C++, соответствующие требованиям<br />

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

строчными буквами. Функции служебных слов будут рассматриваться<br />

ниже по мере изучения материала.<br />

Напоминаем, что не следует использовать имена объектов<br />

(идентификаторы), совпадающие со служебными словами.<br />

2.3.4. Константы<br />

Определение константы с помощью синтаксической диаграммы<br />

приведено на рис. 13.<br />

Константы<br />

Целая_костанта<br />

#i Символьная константа<br />

Строковая_константа<br />

Константа с пл. точкой<br />

Рис. 13. Определение константы<br />

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

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

Си/С++.<br />

Целые константы. Целые константы (рис. 14) не имеют<br />

дробной части и не содержат десятичной точки. В отличие от констант<br />

с плавающей точкой они точно представляют изображаемое<br />

значение. Наиболее часто используются десятичные константы. Шестнадцатеричные<br />

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

33


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

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

восьмеричной и шестнадцатеричной констант приведено на рис. 15 -<br />

17.<br />

Целые константы могут быть обычной длины или длинные.<br />

Длинные целые константы оканчиваются буквой "/" или "L"<br />

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

(для шестнадцатиразрядного процессора — 2, для тридцатидвухразрядного<br />

— 4 байта). Длинная целая константа всегда занимает 4 байта.<br />

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

длинная целая константа и целая константа обычной длины.<br />

Целая_константа<br />

Десятичная_константа<br />

Восьмер._константа<br />

#J Шестнад._константа —••<br />

Десятичная_константа<br />

Рис. 14. Определение целой константы<br />

Ненул ._десят._цифра<br />

Десятичная_цифра<br />

11 -1028 57944L<br />

Рис. 15. Определение десятичной константы<br />

Восьмеричная_константа<br />

ч2>-<br />

Восьм._цифра<br />

013 02000 0160000L<br />

Рис. 16. Определение восьмеричной константы<br />

34


Шестнадцатеричная_константа<br />

М о<br />

\ ^<br />

) ^<br />

Шестнадцатеричная_цифра<br />

] — •<br />

щ<br />

Шестнадцатеричная цифра<br />

0X400<br />

ОхЬ<br />

OxEOOL<br />

Десят._цифра<br />

Рис. 17. Определение шестнадцатеричной константы<br />

Внутреннее представление константы целого типа в ЭВМ —<br />

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

константы старший бит числа интерпретируется как знаковый (О —<br />

положительное число, 1 — отрицательное). Для восьмеричных и шестнадцатеричных<br />

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

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

как часть кода числа, а не как его знак. Более подробное обсуждение<br />

внутреннего представления в ЭВМ целых констант выходит<br />

за рамки данной книги и будет рассмотрено при изучении<br />

арифметических основ построения ЭВМ.<br />

Диапазон значений десятичных констант обычной длины для<br />

шестнадцатиразрядного процессора - от -32768 до +32767, для тридцатидвухразрядного<br />

процессора - ^ ^ ...-г^^ i;; Диапазон значений<br />

восьмеричных и шестнадцатеричных констант обычной длины<br />

для шестнадцатиразрядного процессора - 0...(2'^-1), для тридцатидвухразрядного<br />

процессора - 0...(2^2 _])<br />

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

от разрядности процессора и составляет ^""-^ •+(2 -1)) Диапазон<br />

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

также не зависит от разрядности процессора и составляет 0...(232 _])<br />

Константы с плавающей точкой. Определение константы с<br />

плавающей точкой приведено на рис. 18. Внутреннее представление<br />

в ЭВМ констант с плавающей точкой состоит из двух частей — мантиссы<br />

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

float занимают 4 байта, из которых один двоичный разряд отводится<br />

под знак мантиссы, 8 разрядов под порядок и 23 под мантиссу. Мантисса<br />

- число большее 1,0, но меньшее 2,0. Поскольку старшая циф-<br />

35


pa мантиссы всегда равна 1, то она не хранится. Для констант с плавающей<br />

точкой типа double, занимающих 8 байт, под порядок и<br />

мантиссу отводятся соответственно 11 и 52 разряда. Длина мантиссы<br />

определяет точность числа, а длина порядка — диапазон числа.<br />

Для констант с плавающей точкой типа long double под число отводится<br />

10 байт. Также заметим, что более подробное обсуждение<br />

внутреннего представления fe ЭВМ констант с плавающей точкой<br />

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

арифметических основ построения ЭВМ.<br />

В языке C++, когда в конце константы с плавающей точкой отсутствуют<br />

буквы/ F, /, L, константа имеет тип double (8 байтов или<br />

64 бита с диапазоном значений ±l,7•10~^°^..±l,7 •Ю^'*^^). Если же константа<br />

заканчивается буквой/или F, то она имеет тип float, занимает<br />

4 байта и диапазон значений ±3,4•10'^^..±3,4•10^^^. Аналогичным<br />

образом, при завершении константы буквами / или L константа имеет<br />

тип long double, занимает 10 байт с диапазоном значений<br />

Константа_с_плавающей_точкой<br />

F-константа<br />

•СУ<br />

F-константа<br />

F-константа<br />

е Hh F-константа<br />

F-константа<br />

<br />

-•<br />

10. Ю.Гэкв. 10.F<br />

0.0054 0/00541 экв. 0.0054L<br />

.0054<br />

5.5е-3<br />

Десятичная_константа<br />

Рис. 18. Определение константы с плавающей точкой<br />

36


Символьные константы. Для кодирования одного символа<br />

используется байт (восемь битов). Благодаря этому набор символов<br />

содержит 256 символов, образующих две группы:<br />

• печатные символы;<br />

• непечатные символы.<br />

Непечатным символам соответствуют специальные управляющие<br />

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

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

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

например, работой принтера.<br />

Символьная константа в языках Си/С+-ь состоит либо из одного<br />

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

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

непечатные символы (табл. 5). Символьная константа рассматривается<br />

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

значений от О до 255. Константа '\0' называется нулевым символом<br />

или нулевым байтом.<br />

Примеры: 'д:' 'Г Лп'<br />

Табл. 5. Управляющие символы (коды)<br />

Управляющий код<br />

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

\п<br />

V<br />

\/<br />

\v<br />

\b<br />

Y<br />

\\<br />

\»<br />

\'<br />

\шестнадцатеричная_константа или<br />

\восьмеричная_константа<br />

Переход к новой строке<br />

Возврат каретки<br />

Горизонтальная табуляция<br />

Вертикальная табуляция<br />

Возврат на одну позицию<br />

Переход к новой странице<br />

Обратная косая черта<br />

Кавычка<br />

Апостроф<br />

Строковые константы. Строковая константа содержит последовательность<br />

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

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

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

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

байт является ограничителем строки.<br />

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

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

На рис. 19 показан пример размещения строки в оперативной памяти<br />

ЭВМ.<br />

Приведем еще несколько примеров строковых констант:<br />

37


"Эта строка содержит символ табуляции \t"<br />

"В строке указан символ,, вызывающий звуковой сигнал: \07'<br />

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

для ее хранения используется один байт - завершающий нулевой<br />

байт.<br />

"СтрокаЛп"<br />

С т Р о к а \п \0 Байты памяти, содержащие коды от<br />

О до 255<br />

Нулевой<br />

байт<br />

Рис. 19. Размещение строки в оперативной памяти<br />

2.3.5. Структура Си-программы<br />

Си-программа - совокупность одного или нескольких модулей.<br />

Модулем является самостоятельно транслируемый файл. Такой файл<br />

обычно содержит одну или несколько функций. Функция состоит из<br />

операторов языка Си. Структуру Си-программы иллюстрирует рис.<br />

20.<br />

Си-программа<br />

Модуль (файл с определениями данных и операторами)<br />

Внешние определения данных<br />

Функция<br />

Внутренние определения данных<br />

Операторы<br />

Функция<br />

Внутренние определения данных<br />

Операторы<br />

Модуль (файл с определениями данных и операторами)<br />

Внешние определения данных<br />

Функция<br />

Внутренние определения данных<br />

Операторы<br />

Функция<br />

Внутренние определения данных<br />

Операторы<br />

38<br />

Рис. 20. Структура Си-программы


Термин "функция" в языках Си/С+-*- охватывает понятия "подпрограмма",<br />

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

программирования. Как следует из рис. 20, Си/С++-программа<br />

может содержать одну функцию (главная функция main) или любое<br />

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

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

Си++. Программа с одним модулем (файлом) и двумя функция<br />

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

символом '\п ' . Каждый символ печатается вместе с его десятичным,<br />

восьмеричным и шестнадцатеричным кодами<br />

V<br />

^include // Для функций ввода-вывода<br />

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

// предыдущей строки помещается содержимое файла stdio.h<br />

// Прототип функции: используется компилятором для проверки<br />

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

// правильности вызова функции<br />

void convert( ±nt ) ;<br />

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

// ниже главной функции<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

±пЬ ch; // Прочитанный символ<br />

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

// функции экранного вывода printf<br />

print f ( "\п Программа изображает символы и их "<br />

"коды. \п" ) ;<br />

printf( "\п Наберите строку символов и нажмите клавишу "<br />

"Enter. \п" ) /<br />

ch = getchar( ) ; // Подождать ввода символа<br />

while ( ch != '\п' ) // '\п' вводится после нажатия Enter<br />

{<br />

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

// восьмеричного и 16-ричного кодов. Компилятор<br />

// контролирует правильность вызова функции,<br />

// используя ее прототип<br />

convert ( ch ) ;<br />

ch = getchar( ) ; // Ввод следующего символа<br />

}<br />

printf( "\п Обработка закончена. \п" ) ;<br />

return 0;<br />

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

39


void convert (<br />

±nt ch ) // Изображаемый символ<br />

{<br />

printf( "Символ 10 код 8 код 16 код \п" ) ;<br />

// Непечатные символы имеют десятичные коды О.. 31, а<br />

// десятичный код символа ' ' равен 32<br />

± f ( c h < ' ' )<br />

printf( "Управляющий (непечатный) символ: \п" ) ;<br />

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

// вначале в символьном формате %с, а затем<br />

// соответственно в форматах десятичного %d,<br />

// восьмеричного %о и 16-ричного %к чисел. Число<br />

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

// строкой аргументов совпадают<br />

print f( "%с %d %о %х \п",<br />

ch, ch, ch, ch ) ;<br />

}<br />

return;<br />

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

программы, но и иллюстрирует, как нужно оформлять программу,<br />

использовать комментарии и т.п.<br />

Проанализируем этот пример и подведем некоторые итоги.<br />

Каждая Си-программа должна иметь одну и только одну главную<br />

функцию с именем main. С этой функции начинается исполнение<br />

программы. Другие функции могут быть вызваны из функции main<br />

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

Эти функции могут находиться в том же модуле (файле),<br />

что и функция main, или в других модулях.<br />

Функция может иметь нудь или более аргументов. Аргументы<br />

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

между функциями (main не имеет аргументов, а функция<br />

convert имеет один аргумент - переменную ch).<br />

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

начинается с "{" и заканчивается "}". Блок содержит определения<br />

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

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

Операторы задают действия, которые должны быть выполнены<br />

над переменными.<br />

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

Определения данных всегда завершаются точкой с<br />

запятой. Операторы также завершаются точкой с запятой.<br />

40


2.4. Простой ввод-вывод в языках Си/С++<br />

Языки Си/С++ не содерэюат встроенных средств вводавывода.<br />

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

Си/С+-ь поставляется библиотека стандартных функций,<br />

содержащая наряду с другими функциями функции ввода-вывода.<br />

Функции ввода-вывода библиотеки позволяют читать данные из<br />

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

и на устройства. Библиотека Си поддерживает три уровня вводавывода:<br />

• ввод-вывод потока;<br />

• ввод-вывод нижнего уровня;<br />

• ввод-вывод для консоли и порта.<br />

Здесь мы рассмотрим только ввод-вывод потока.<br />

2.4.1. Ввод-вывод потока<br />

При вводе-выводе потока все данные рассматриваются как поток<br />

отдельных байтов - это либо файл на магнитном диске, либо физическое<br />

устройство (дисплей, печатающее устройство и т.п.). Таким<br />

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

или устройствами.<br />

Ввод-вывод потока позволяет.<br />

1. Открывать и закрывать потоки.<br />

2. Читать и записывать символ.<br />

3. Читать и записывать целые данные.<br />

4. Читать и записывать строки.<br />

5. Читать и записывать форматированные данные любого типа.<br />

6. Анализировать ошибки ввода-вывода потока и достижение<br />

конца потока (конца файла).<br />

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

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

файл stdio.h:<br />

^include<br />

<br />

Обработка данной директивы состоит в замене строки директивы<br />

содерэюимым текстового файла stdio.h. Этот файл содержит<br />

объявления и определения функций ввода-вывода, а также определения<br />

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

ввода-вывода потока.<br />

41


Открытие потока. Перед выполнением операций вводавывода<br />

для потока его нужно открыть. Для этой цели служит функция<br />

уЬрег7(^^, описание которой имеет вид:<br />

^include <br />

FILE * fopen ( // Возвращает указатель на открытый<br />

// файл<br />

const char *path. //<br />

//<br />

Указатель<br />

файла<br />

на имя открываемого<br />

const cha.i: *type ) ; // Указатель на вид доступа к файлу<br />

Функция открывает файл path в режиме доступа type. Символьная<br />

строка type задает вид доступа к файлу в соответствии с<br />

табл. 6. Функция/open возвращает указатель на открытый файл. Нулевой<br />

указатель (NULL) означает ошибку. Многочисленные примеры<br />

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

Вид доступа type<br />

"г"<br />

'V'<br />

"л"<br />

•V+"<br />

"w+"<br />

"а+"<br />

Табл. 6. Виды доступа к файлу<br />

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

Открывается файл для чтения. Если файл не существует или не<br />

может быть найден, то возникает ошибка<br />

Открывается пустой файл для записи. Если файл уже<br />

существует, то его содержимое будет уничтожено<br />

Открывается файл для записи в конец (добавления). Если файл<br />

не существует, то он сначала будет создан<br />

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

существовать, иначе - ошибка)<br />

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

уже существует, то его содержимое будет уничтожено<br />

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

(добавления). Если файл не существует, то он сначала будет<br />

создан<br />

Закрытие потока. Для закрытия потока служит функция<br />

fclose(), которую следует вызвать сразу же после окончания работы<br />

с потоком:<br />

ilnclude<br />

<br />

±nt<br />

FILE<br />

fclose(<br />

// Возвращает О при успехе и EOF при<br />

// ошибке<br />

stream ) ; / / Закрываемый поток<br />

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

ниже.<br />

Предопределенные указатели потока. В начале выполнения<br />

Си-программы автоматически открывается пять потоков:<br />

42


• стандартный ввод (предопределенный указатель stdin);<br />

• стандартный вывод (предопределенный указатель stdout);<br />

• стандартный вывод сообщений об ошибках (предопределенный<br />

указатель stderr);<br />

• стандартный дополнительный поток (предопределенный указатель<br />

stdaux);<br />

• стандартная печать (предопределенный указатель stdprn).<br />

По умолчанию stdin соответствует клавиатуре терминала,<br />

stdout и stderr - экрану терминала, stdaux - дополнительному порту и<br />

stdprn - печатающему устройству.<br />

Предопределенные указатели пяти перечисленных стандартных<br />

потоков можно использовать в любой функции ввода-вывода,<br />

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

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

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

в табл. 7.<br />

Объект<br />

операции<br />

Серия байтов<br />

Символ<br />

Данное int<br />

Строка<br />

Формат,<br />

данные<br />

Табл. 7. Функции чтения из потока и записи в поток<br />

Чтение<br />

Запись в<br />

из stdin<br />

stdout<br />

getc<br />

getchar<br />

gets<br />

scan/<br />

Чтение из<br />

любого<br />

потока<br />

fread<br />

fgetc<br />

fgetchar<br />

getw<br />

/gets<br />

fscanf<br />

Чтение<br />

из строки<br />

Си<br />

sscanf<br />

put<br />

putchar<br />

ungetc<br />

puts<br />

print/<br />

vprintf<br />

Запись в<br />

любой<br />

поток<br />

fwrite<br />

fputc<br />

fputchar<br />

putw<br />

/puts<br />

/print/<br />

v/print/<br />

Запись в<br />

строку Си<br />

sprint/<br />

vsprint/ 1<br />

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

рассмотрим лишь универсальные функции для ввода scan/-/scan/ и<br />

для вывода printf-fprint/<br />

2.4.2. Ввод с использованием функций scanf-fscanf<br />

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

Общей особенностью приведенных ниже программ-примеров является<br />

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

выполнения на ЭВМ.<br />

/*<br />

Программа-пример 1 (начало) .<br />

43


ввод в ЯЗЫКЕ Си • написать фрагмент Си-программы^ которая<br />

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

значения:<br />

±пЬ<br />

"exl.<br />

-^/<br />

float<br />

long<br />

char<br />

^Include<br />

Написать<br />

dat")<br />

1 г<br />

а.<br />

12;<br />

f;<br />

1;<br />

ch.<br />

str[<br />

вид<br />

<br />

int main ( void )<br />

{<br />

//<br />

//<br />

//<br />

//<br />

//<br />

//<br />

20 ];//<br />

OxFA<br />

-22<br />

074<br />

1.57<br />

-125874<br />

'Z '<br />

"Нам<br />

Тхань"<br />

читаемых данных (вид строк в файле<br />

// Для функций ввода-вывода<br />

// Возвращает О при успехе<br />

int i, 11^ 12;<br />

float f;<br />

long 1;<br />

char ch, str[ 20 ];<br />

FILE *f_ln; // Указатель на структуру со<br />

// сведениями о файле для ввода<br />

int ret code; // Возвращаемое значение для fscanf<br />

// Открываем файл exl.dat для чтения<br />

f_ln - fopen( "exl.dat", "г" ) ;<br />

if( f__ln -= NULL )<br />

{ // Ошибка открытия файла<br />

printf( "\n Файл exl.dat для чтения не открыт. " ) ;<br />

return 1;<br />

}<br />

// Читаем данные из файла exl.dat<br />

retcode = fscanf( f_ln,<br />

" 1 = %х ll=%d 12=%о f=^%f l = %ld ch^%c str=%s%c%s",<br />

&1, Sell, &12, &f, &1, &ch, str, &str[3], &str[4] ) ;<br />

if( retcode != 9 )<br />

{<br />

print f ( "\n Данные в fscanf прочитаны с ошибками." ) ;<br />

return 2;<br />

return О;<br />

}<br />

// Конец примера 1<br />

Вид строк исходных данных в файле exl.dat:<br />

l=OxFA 11=-22 12=074<br />

f==1.57 1 = -125874 ch=z<br />

str=HaM Тхань<br />

44


Как работает функция fscanf<br />

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

"...". Если очередным символом является символ "пробельной группы"<br />

(пробел или '\Г' или \п'), то в исходных данных (во входном потоке)<br />

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

пока не встретится другой символ.<br />

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

с символа "%", то из входного потока читается последовательность<br />

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

кодовый формат в соответствии с типом формата и записывается по<br />

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

адрес, по которому в оперативной памяти размещается переменная<br />

/). Если до пробельного символа раньше встретится символ, не<br />

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

формату остановится на этом символе. Символ управляющей строки,<br />

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

символов из входного потока в кодовый формат (табл. 8).<br />

!!! Число форматов и число аргументов в функции/уса«/обязательно<br />

должно быть одинаковым.<br />

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

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

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

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

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

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

пропускается и функция продолжает работу. Отмеченная особенность<br />

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

Это означает, что во входном потоке (в файле исходных данных) с<br />

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

относится вводимое значение (см. текст примера выше).<br />

Рассмотрим еще один пример, являющийся логическим продолжением<br />

предыдущего примера.<br />

/*<br />

Программа --пример 2 (начало) .<br />

ВВОД В ЯЗЫКЕ Си:<br />

1. Написать фрагмент Си-программы, которая из<br />

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

"exl.dat"<br />

ния:<br />

±пЬ<br />

short<br />

±nt<br />

float<br />

longchar<br />

1;<br />

11/<br />

12;<br />

f;<br />

1;<br />

ch.<br />

// Ох FA<br />

// -22<br />

// 074<br />

// 1.57<br />

// -125<br />

// 'z'<br />

файла<br />

значе-<br />

45


str[ 20 ];// "Нам Тхань"<br />

2, Написать вид читаемых данных<br />

"exl.dat")<br />

*/<br />

(вид строк в файле<br />

1 Символ<br />

за%<br />

d<br />

D<br />

о<br />

О<br />

х,Х<br />

i<br />

I<br />

и<br />

и<br />

\e,E,f,<br />

с<br />

S<br />

%<br />

/ •<br />

Р<br />

Табл. 8. Способы преобразования символов при вводе<br />

Тип, ожидаемый при вводе<br />

Тип аргумента<br />

Десятичное целое<br />

Десятичное целое<br />

Восьмеричное целое<br />

Восьмеричное целое<br />

Шестнадцатеричное целое без префиксов<br />

Ох или ОХ<br />

Десятичное, шестнадцатеричное или восьмеричное<br />

целое<br />

Десятичное, шестнадцатеричное или восьмеричное<br />

целое<br />

Десятичное целое без знака<br />

Десятичное целое без знака<br />

Величина с плавающей точкой из мантиссы<br />

и порядка<br />

Символ. Пробельные символы, которые<br />

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

указано "с". Чтобы прочесть из потока следующий<br />

не пробельный символ, используйте<br />

формат Vols<br />

Символьная строка<br />

Символ %<br />

Из потока ничего не читается<br />

Величина в виде XXXX:YYYY^ где цифры X<br />

и Y являются шестнадцатеричными цифрами<br />

верхнего регистра<br />

Указатель на int<br />

Указатель на long int<br />

Указатель на int<br />

Указатель па long int<br />

Указатель на int<br />

Указатель на int<br />

Указатель на long int<br />

Указатель на unsigned<br />

Указатель на unsigned<br />

int<br />

Указатель из.float<br />

Указатель на char<br />

int<br />

long<br />

Указатель на символьный<br />

массив, достаточно больпюй,<br />

чтобы разместить<br />

вводимое поле и завершающий<br />

нуль-символ '\0',<br />

добавляемый автоматически<br />

Не преобразуется, участвует<br />

во вводе как символ '%'<br />

Указатель на переменную<br />

типа int, в которую записывается<br />

количество символов,<br />

считанных из потока<br />

вплоть до этой точки при<br />

текущем вызове функции<br />

Указатель на объект (far*<br />

или near*). Формат %р выполняет<br />

преобразование<br />

указателя к требуемому<br />

указателю используемой<br />

модели памяти |<br />

46


^include • // Для функций ввода-вывода<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

Int i, 12;<br />

short 11;<br />

float f;<br />

long 1;<br />

char ch, str[ 20 ];<br />

FILE *f_ln/ // Указатель на структуру со<br />

// сведениями о файле для ввода<br />

±nt ret code/ // Возвращаемое значение для<br />

// fscanf<br />

// Открываем файл exl.dat для чтения<br />

f_ln = fopenC "exl.dat", "г" ) ;<br />

±f( f__ln == NULL )<br />

{<br />

printf( "\n Файл exl.dat для чтения не открыт. " ) ;<br />

return 1;<br />

}<br />

// Читаем данные из файла exl.dat<br />

retcode = fscanf( f_ln,<br />

" 1 = %х ll = %hd 12^%о f=^%f l = %41d874 ch = %c str=%s%c%s",<br />

&1, &11, &12, Scf, Sclr &chr str, &str[3], &str[4] ) ;<br />

±f( retcode != 9 )<br />

{<br />

prlntf( "\n Данные в fscanf прочитаны с ошибками." );<br />

return 2;<br />

}<br />

return 0;<br />

}<br />

// Конец примера 2<br />

Вид строк исходных данных в файле exl.dat:<br />

l^OxFA 11=-22 12=074<br />

f=1.57 1=-125874 ch=z<br />

str=HaM Тхань<br />

V<br />

Из рассмотренного примера следует, что в общем случае<br />

структура формата имеет вид:<br />

% ['^] [ширина ] [префикс] тип<br />

Звездочка (*), следующая за знаком процента, подавляет запоминание<br />

следующего вводимого поля. Поле считывается в соответ-<br />

47


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

"Ширина" - положительное десятичное целое, задающее<br />

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

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

Если "ширина" меньше, чем число символов до пробельного, то читаются<br />

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

(см. пример 2).<br />

Префиксами могут быть:<br />

N - используется для печати адресов near (формат %Np);<br />

F - используется для печати адресов far (формат УоГр);<br />

h - для ввода коротких целых с типом short (см. пример 2);<br />

/ - для ввода длинных целых и вещественных с типом long (см.<br />

пример 2).<br />

Рассмотрим еще один, более сложный, иллюстрирующий пример.<br />

V7* Программа - -пример 3 (начало) .<br />

ВВОД В ЯЗЫКЕ Си:<br />

1. Написать фрагмент Си-программы, которая из<br />

"exl.dat" на магнитном диске прочитает указанные ниже<br />

ни я:<br />

±пЬ<br />

float<br />

longchar<br />

±. //<br />

11, //<br />

12; //<br />

f; //<br />

1; //<br />

ch, //<br />

str[ 20 ];//<br />

OxFA или 250<br />

74<br />

18<br />

1,57<br />

-125874<br />

"Нам Тхань"<br />

2. Написать вид читаемых данных (вид строк в<br />

"exl.dat")<br />

V<br />

файла<br />

значефайле<br />

^include<br />

<br />

// Для функций ввода-вывода<br />

±nt main ( void )<br />

{<br />

// Возвращает О при успехе<br />

Int 1, 11, 12;<br />

float f;<br />

long 1;<br />

сЪаг ch, str[ 20 ];<br />

FILE *f_ln; // Указатель на структуру со<br />

// сведениями о файле для ввода<br />

±nt ret code; // Возвращаемое значение для<br />

// fscanf<br />

// Открываем файл exl.dat для чтения<br />

f_ln = fopen( "exl.dat", "г" ) ;<br />

±f( f_ln == NULL )<br />

{<br />

48


printf ( "\n Файл exl.dat для чтения не открыт. " ) ;<br />

}<br />

// Читаем данные из файла exl.dat<br />

retcode = fscanf( f_in,<br />

" %х %d %о %f %ld %c %s%c%s"r &±f &il, &i2, &f, &1,<br />

&ch, str, &str[3], &str[4] ) ;<br />

±f( retcode 1=9)<br />

{<br />

printf( "\n Данные в fscanf прочитаны с ошибками." ) ;<br />

r-etuirn 2;<br />

}<br />

// За крыва ем файл<br />

retcode = fclose( f_in ) ;<br />

±f( retcode == EOF )<br />

{<br />

printf( "\n Файл exl.dat не закрыт." ) ;<br />

return 3;<br />

}<br />

jzebvLzm 0;<br />

}<br />

// Конец примера 3<br />

Вид строк исходных данных в файле ех1.dat:<br />

OxFA 074<br />

22 1.57 -125874 z<br />

Нам Тхань<br />

V<br />

В этом примере /1 получает значение 74, так как читается по<br />

формату Vod (десятичный формат). Аналогично, /2 получает десятичное<br />

значение 18, так как читается по формату Voo (восьмеричный<br />

формат - восьмеричный код 22 соответствует десятичному коду 18).<br />

В заключение отметим, что функция 5са«/идентична функции<br />

fscanf^ но вместо входного потока, заданного первым аргументом,<br />

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

stdin. По этой причине в вызове функции scan/ CUWCOK аргументов<br />

начинается сразу с управляющей строки.<br />

2.4.3. Вывод с использованием функций printf-fprintf<br />

Вначале рассмотрим и проанализируем пример. Особенностью<br />

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

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

49


Программа-пример 4 (начало) .<br />

ВЫВОД В ЯЗЫКЕ Си/С++:<br />

Укажите вид строк печати в файле fl,out на магнитном диске<br />

после выполнения приведенной ниже программы<br />

'^7<br />

^include // Для функций ввода-вывода<br />

±nt main ( void. ) // Возвращает О при успехе<br />

{<br />

// Данные для печати<br />

float f = 1.5е2;<br />

long- double<br />

Id = 2.0e-3L;<br />

±пЬ i = 7/<br />

long- ±nb 11 = 121;<br />

short ±nt si = 5;<br />

FILE *f_out/ // Указатель на структуру со<br />

// сведениями о файле для вывода<br />

int retcode; // Возвращаемое значение для fсlose<br />

// Открываем файл fl.out для записи<br />

f_out = fopen( "fl.out", "w" );<br />

±f( f_out == NULL )<br />

{<br />

}<br />

print f ( "\n Файл fl.out для записи не открыт. " );<br />

return 1;<br />

// Записываем в файл fl.out<br />

fprlntfi f__out, " %30s\n f=%f %5s l = %10d\n",<br />

fprlntfi f_out, " ld=%-Lf f=%15f f=%15.2f f=%+15.2f\n".<br />

Id, frf. f );<br />

fprlntf( f_out, " l = %10.5d f=^%E ll = %ld sl = %hl\n",<br />

i/ ff 11, si );<br />

// Закрываем файл fl.out<br />

retcode = fclose( f_out );<br />

±f( retcode == EOF )<br />

{<br />

}<br />

printf( "\n Файл fl.out не закрыт." );<br />

return 2;<br />

return 0/<br />

}<br />

// Конец примера 4<br />

Как работает д^уякияя fprintjl<br />

Первый аргумент в вызове функции (foui) указывает поток, в<br />

который производится запись (вывод). Работа функции начинается с<br />

50


просмотра слева направо управляющей строки "...". Если в управляющей<br />

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

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

видов.<br />

1. Управляющие символы (в кодовой таблице первые 32 символа),<br />

примерами управляющих символов являются '\п\ \t\ '\0х7' и<br />

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

ему действия. Например, '\п' вызовет переход в потоке ( в<br />

нашем случае в файле /Lout) на следующую строку, '\^' - выполнит<br />

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

(табулятору может соответствовать 2-16 пробелов, обычно четыре<br />

или восемь) и т.д.<br />

2. Форматы, которые начинаются с символа "%". Если встретился<br />

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

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

типом формата и выводится в поток (в нашем случае в файл fJ.out).<br />

!!! Число форматов в управляющей строке должно быть равно<br />

числу аргументов !!!<br />

3. Остальные символы, которые называются печатными и выводятся<br />

в выходной поток в том виде, как они изображены.<br />

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

строки до конца или при возникновении ошибки.<br />

Формат имеет следующую структуру (в квадратных скобках<br />

указаны поля формата, которые могут отсутствовать):<br />

% [флаг] [ширина__поля_вывода ] [ . точность] [префикс] тип<br />

Допустимые значения полей "флаг", ".точность", "тип" и действия,<br />

выполняемые перечисленными полями, приведены в табл. 9 -<br />

11. Примеры их использования даны выше. Результаты действия<br />

форматов приведены в файле результатов/7.оwr, где условно с помощью<br />

символа "^" показано расположение пробелов.<br />

Поле "флаг" управляет выводом в поток ( табл. 9). В формате<br />

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

противоречат друг другу.<br />

Поле "ширина_поля_вывода" задает минимальное число выводимых<br />

символов. Это неотрицательное целое десятичное число. Если<br />

"ширина" излишняя, то слева или справа, в зависимости от флага<br />

"-", поле вывода дополняется пробелами. Если ширина недостаточна,<br />

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

выводимого данного не будет!<br />

51


Флаг<br />

-<br />

+<br />

Пробел<br />

#<br />

Табл. 9. Действие флагов форматирования<br />

Смысл<br />

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

заданного поля<br />

Вывод величины с указанием знака "+", если<br />

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

Вывод пробела перед величиной, если это положительное<br />

число со знаком<br />

Для форматов о, х или Х выводит перед числом<br />

префикс 0, Ох или ОХ соответственно. Для<br />

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

точкой.<br />

Для форматов g, G выводит число с десятичной<br />

точкой и предотвращает усечение лишних<br />

нулей.<br />

Значение по умолчанию<br />

Выравнивание по правому<br />

краю<br />

Знак выводится только<br />

для отрицательных<br />

величин<br />

Пробел не выводится<br />

Префикс в указанных<br />

случаях не выводится.<br />

Десятичная точка<br />

выводится, если за<br />

ней следует цифра.<br />

Лишние нули<br />

усекаются.<br />

"Точность"' - неотрицательное десятичное число, перед которым<br />

ставится точка (табл. 10). Обратите внимание, что "точность", в<br />

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

или ее округление (для переменной с плавающей точкой).<br />

Тип<br />

d, i, и,<br />

о, X, X<br />

Е, e,f<br />

g^G<br />

с<br />

S<br />

Табл. 10. Действие поля формата ".точность"<br />

Смысл<br />

Указывает минимальное число выводимых<br />

цифр. Если точность меньше, чем<br />

надо, то число не усекается. Если точность<br />

больше, чем надо, то число дополняется<br />

слева нулями.<br />

Указывает число цифр, выводимых после<br />

десятичной точки (в случае усечения последи^<br />

цифра округляется).<br />

Указывает максимальное число значащих<br />

цифр выводимого числа.<br />

Ни на что не влияет.<br />

Точность указывает максимальное число<br />

выводимых символов. Лишние символы<br />

строки не выводятся.<br />

Умолчание<br />

Если точность равна нулю,<br />

или вообще опущена, или<br />

стоит просто точка, то в качестве<br />

точности берется единица.<br />

Точность равна шести. Если<br />

она равна нулю или стоит<br />

просто точка, то десятичная<br />

точка не выводится.<br />

Выводятся все значащие<br />

цифры.<br />

Выводится символ.<br />

Символы выводятся до тех<br />

пор, пока не будет достигнут<br />

нуль-символ.<br />

"Префиксы h, I, L, N, F".<br />

Префикс "/г" (sHort) для типов d, i, о, х, А" (табл. 11) указывает<br />

на тип аргумента short int, а для типа и - на тип аргумента unsigned<br />

short int.<br />

Префикс "/" для типов d, i, о, х, Jf указывает на тип аргумента<br />

long int, для типа и - на тип аргумента unsigned long int, а для типов<br />

е, Е, f, g, G - на тип double.<br />

52


\d, i<br />

и<br />

0<br />

X или<br />

\х<br />

/<br />

\Е, е<br />

G'S<br />

с<br />

S<br />

п<br />

р<br />

int<br />

int<br />

int<br />

int<br />

float<br />

float<br />

float<br />

int<br />

Строка<br />

Указатель на<br />

целое<br />

Указатель типа<br />

void far *<br />

Табл. 11. Символы типа<br />

Десятичное целое со знаком<br />

Десятичное целое без знака<br />

Восьмеричное целое без знака<br />

Шестнадцатеричное целое без знака с использованием<br />

цифр "abcdef' или "ABCDEF"<br />

Величина со знаком вида [-]dd.dd, где d - десятичная цифра.<br />

Число цифр до точки определяется величиной числа, а<br />

после - точностью.<br />

Величина со знаком вида [-]d.ddddde[3HaK]ddd или [-<br />

ld.ddddd£[3HaK]ddd<br />

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

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

Отдельный символ<br />

i<br />

Символы выводятся или до достижения нуль-символа,<br />

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

"точность"<br />

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

момету. Эта величина присваивается переменной<br />

inl с адресом в аргументе.<br />

Выводит адрес, на который указывает аргумент, в виде<br />

ХХХХ : УУУУ (сегмент : смещение) с использованием<br />

шестнадцатеричных цифр верхнего регистра<br />

Префикс "Z," для типов е, Е, f, g, G указывает на тип аргумента<br />

long double.<br />

Префикс "F" для типов р, s, п указывает на тип аргумента<br />

дальний указатель, а префикс 'W" для тех же типов - на тип аргумента<br />

ближний указатель.<br />

Файл результатов работы программы/7.ow/' имеет следующий<br />

вид (символ ^ обозначает пробел):<br />

^i=''^'^^^00007^f=l.<br />

500000Ei-02^1±=12^si=5<br />

Обратите внимание, что функция print/ идентична функции*<br />

/print/, но вместо выходного потока, заданного первым аргументом,<br />

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

stdout. По этой причине в вызове функции/7гш(/'список аргументов<br />

начинается сразу с управляющей строки.<br />

Функции ргш^/х^^гш^/'возвращают число выведенных символов<br />

при успешном завершении или EOF при ошибке. Однако это возвращаемое<br />

значение обычно не контролируют.<br />

53


float<br />

±nt<br />

cha.r<br />

±nt<br />

2.4.4. Упражнения для самопроверки<br />

1. Имеется следующий фрагмент Си-программы:<br />

a, Ь;<br />

i^ J/<br />

cl , c2^<br />

c3<br />

retcode;<br />

retcode = fscanfi stdin, " %i %3d %c %c %c %f %f'\<br />

&i, &j, &cl, &c2r &c3r Sea, &b ) ;<br />

Строки исходных данных в файле с указателем stdin имеют<br />

следующий вид:<br />

17 123456<br />

2.4еЗ 112<br />

14,5<br />

с31<br />

Какие значения получат переменные retcode, а, Ь, i, J, cl, с2,<br />

2. Имеется следующий фрагмент Си-программы:<br />

float<br />

±nt<br />

char<br />

±nt<br />

char<br />

a;<br />

^f jr<br />

cl, c2, c3;<br />

retcode;<br />

c4, c5, s[20];<br />

Написать фрагмент программы, обеспечивающий чтение из<br />

файла f.dat, на магнитном диске следующих значений:<br />

а = 1.5 i = 21 j = -12 cl = 'в'<br />

с2 = 'е' сЗ = 'с' с4 = 'а' с5 = 'н'<br />

S => "Прочита иная-строка"<br />

Как при этом будут выглядеть строки исходных данных в файn^f.datl<br />

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

функциями библиотеки Си.<br />

3. В программе имеются следующие переменные:<br />

±nt d = 254;<br />

float f = 1234.56;<br />

char *str = "Строка символов";<br />

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

54


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

них символ ^ обозначает местоположение пробела):<br />

/•-/-254 " "-^"-^-^-"7 " " f " " ^^2547<br />

(^^^^^1234,5600) "" (1234. 5 600''^^^^)<br />

/Стр/^-^/м/<br />

Ответы и решения для этих и последующих упражнений для<br />

самопроверки можно проверить в разд. 18.


3. типы ДАННЫХ и их АТРИБУТЫ<br />

Вспомним еще раз определение программы по Н. Вирту:<br />

"Программа = структуры данных + алгоритм'<br />

В соответствии с приведенным определением начнем рассмотрение<br />

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

атрибутами:<br />

• именами;<br />

• типами;<br />

• областями действия;<br />

• временем жизни.<br />

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

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

как в языках Си/С++ функции могут получать и возвращать значения,<br />

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

функциями значения.<br />

3.1. Имена<br />

В языках Си/С++ любая область памяти компьютера, которая<br />

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

выражение, представляющее собой ссылку на объект, называется<br />

адресным выраэюением или, короче, адресом^ именем.<br />

Например, в рассмотренной выше программе объект "с/г" был<br />

определен следующим образом:<br />

int main ( sroldL )<br />

{<br />

±nt ch; // Объект с именем (адресом,<br />

// адресным выражением) ch и типом<br />

// ±nt<br />

retuxm 0;<br />

Имена объектов (например, ch) являются просто идентификаторами.<br />

Служебное слово int (INTeger, целый) указывает тип значения,<br />

которое будет содержать данный объект.<br />

56


3.2. Типы данных<br />

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

надо выделить для размещения объекта. Кроме того, он указывает<br />

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

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

с помощью служебного слова (слов) - спецификации типа. Предусмотрено<br />

следующие основные (стандартные) типы данных<br />

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

Служебное слово<br />

char<br />

wchart<br />

int<br />

bool<br />

float<br />

Табл. 12. Основные (стандартные) типы данных<br />

Размер в байтах<br />

1<br />

2<br />

Зависит от реализации<br />

1<br />

4<br />

Происхождение и перевод служебных слов:<br />

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

Для символа (-128 ... +127)<br />

Для символа из расширенного<br />

набора (-32768 ... +32767)<br />

Для целого значения<br />

Для логического значения<br />

(falsey true)<br />

Для значения с плавающей точкой<br />

(по абсолютной величине от<br />

3.4Е-38до3.4Е+38)<br />

char (CHARacter: буква, симвом);<br />

wchar_t (wide character type: расширенный символьный тип);<br />

int (INTeger: целое число);<br />

float (число с плавающей точкой).<br />

Существуют четыре спецификатора типа (табл. 13), уточняющих<br />

внутреннее представление и диапазон значений стандартных<br />

типов: unsigned (без знака), signed (со знаком), short (короткий),<br />

long (длинный).<br />

Символьный тип (char). Под величину символьного типа отводится<br />

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

256-символьного набора ASCII. Величины типа char применяются<br />

также для хранения целых чисел из диапазона +127 ... -128. По<br />

умолчанию тип char эквивалентен типу signed char. При использовании<br />

типа unsigned char значения могут находиться в диапазоне О<br />

... 255. Величины типа unsigned char применяются также для хранения<br />

целых чисел из диапазона О ... 255.<br />

Расигиренный символьный тип (wchart). Этот тип предназначен<br />

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

одного байта (например, для набора Unicode). Символьные<br />

и строковые константы с типом wchart записываются с префиксом<br />

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

57


iinclude <br />

int main ( void )<br />

{<br />

wpr±ntf( L"%s\n%c\n", L"string", L'A' ) ;<br />

}<br />

reburn 0;<br />

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

следующие строки:<br />

string<br />

А<br />

Табл. 13. Уточняющие спецификаторы типа<br />

Служебное слово Размер в байтах<br />

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

unsigned<br />

char<br />

unsigned или<br />

unsigned int<br />

short или<br />

short int<br />

unsigned short или<br />

unsigned short int<br />

long или<br />

long int<br />

unsigned long или<br />

unsigned long int<br />

double или<br />

long float<br />

long double<br />

1<br />

Зависит от реализации<br />

2<br />

2<br />

4<br />

4<br />

8<br />

10<br />

Байт с неотрицательным целым<br />

значением (0 ... 255)<br />

Для неотрицательногЬ целого<br />

значения<br />

Целое значение<br />

(от -32768 до +32767)<br />

Беззнаковое короткое целое<br />

(0 ... 65535)<br />

Целое длинное<br />

(-231...+ (2^1 -1)) 1<br />

Беззнаковое целое длинное<br />

(0...(232-1))<br />

Вещ. с двойной точностью<br />

±1,7.10-'^..±1,7.10^'°'<br />

Вещ. с повыщенной точностью<br />

±3,4•10-'^•'^..±3,4.10"'^''<br />

Целые типы. Размер типа int зависит от реализации, (для шестнадцатиразрядного<br />

процессора - 2, для тридцатидвухразрядного -<br />

4 байта). Диапазон значений для шестнадцатиразрядного процессора<br />

- от -32768 до +32767, для тридцатидвухразрядного процессора -<br />

(-231...+ (231 _1))<br />

Спецификатор short перед именем типа указывает компилятору,<br />

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

процессора. Спецификатор long означает, что целая величина<br />

занимает четыре байта. Таким образом, на шестнадцатиразрядном<br />

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

- int и long int.<br />

Внутреннее представление величины целого типа — целое число<br />

в двоичном коде. При использовании спецификатора signed<br />

58


старший бит числа интерпретируется как знаковый (О - положительное<br />

число, 1 — отрицательное). Спецификатор unsigned позволяет<br />

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

разряд рассматривается как часть кода числа.<br />

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

то есть спецификатор signed можно опускать.<br />

Логический тип (bool). Величины логического типа могут<br />

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

словами. Внутренняя форма представления значения false — О<br />

(нуль). Любое другое значение интерпретируется как true. При преобразовании<br />

к целому типу true имеет значение 1.<br />

Типы с плавающей точкой (floaty double и long double).<br />

Внутреннее представление для типов с плавающей точкой состоит<br />

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

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

под знак мантиссы, 8 разрядов под порядок и 23 под мантиссу.<br />

Мантисса — число большее 1,0, но меньшее 2,0. Поскольку старшая<br />

цифра мантиссы всегда равна 1, то она не хранится.<br />

Для величин типа double, занимающих восемь байт, под порядок<br />

и мантиссу отводятся соответственно 11 и 52 разряда. Длина<br />

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

числа.<br />

Спецификатор long перед именем типа double указывает, что<br />

под число отводится 10 байт.<br />

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

типа, можно написать программу, использующую операцию<br />

''sizeof {size - размер). Значением этой операции является размер<br />

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

байтах:<br />

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

// производных типов<br />

^include // Для функций ввода-вывода<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

printf ( "Тип объекта Его размер в байтах \п\п" ) ;<br />

prlntfC "char %d \п", slzeof( char ) ) ;<br />

printf ( "unsigned char %d \л", sizeof ( unslgnecL cbar ) ).<br />

printf ( "int %d \л", sizeof ( int ) ) ;<br />

printf( "unsigned %d \л", sizeof( unsigned ) )/<br />

printf( "short %d \n", sizeof( short ) ) ;<br />

printf( "unsigned short %d \n",<br />

sizeof( unsigned short ) ) ;<br />

printf( "long %d \n", sizeof( long ) )/<br />

printf( "unsigned long %d \л".<br />

59


sizeof ( unsigned long- ) ) ;<br />

printf( "float %d \n", sizeof ( float ) ) ;<br />

printf( "double %d \n", sizeof ( double ) ) ;<br />

print f ( "long double %d \n", sizeof ( long double ) ) ,<br />

}<br />

•retujETZi 0;<br />

Tun void. Кроме перечисленных, к основным типам языка относится<br />

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

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

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

тип указателей и в операции приведения типов. Все это будет<br />

рассмотрено далее.<br />

В заключение приведем с использованием синтаксических диаграмм<br />

правила определения объектов в программах на языке Си/С++<br />

(рис. 21). Следует заметить, что наряду с приведенными на этом<br />

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

типа и определяемого объекта, которые мы рассмотрим<br />

позже.<br />

В дополнение к имени и типу объекта существуют еще два атрибута:<br />

• область действия;<br />

• время жизни объекта.<br />

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

связывается с конкретным объектом.<br />

3.3. Класс хранения: область действия<br />

и время жизни<br />

Областью действия объекта (данного) называется та часть<br />

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

областью действия может быть:<br />

• блок операторов ({...} );<br />

• модуль (файл);<br />

• вся программа в целом.<br />

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

которого значение этого данного доступно в некоторой части<br />

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

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

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

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

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

следующие классы:<br />

60


• внешний;<br />

• внешний статический;<br />

• внутренний статический;<br />

• автоматический;<br />

• регистровый.<br />

Список_опред._объектов<br />

*н Спецификация_типа —г-н Определяемый_объект<br />

Специф._типа<br />

unsigned ><br />

и short У<br />

•( char У<br />

и long У<br />

•Г float \<br />

•Г long V^<br />

double<br />

><br />

Определяемый_объект<br />

Идентификатор<br />

Рис. 21. Правила определения объектов<br />

3.4. Внешние и внешние статические данные<br />

Дадим более полное определение модуля на рис. 22. Отсюда<br />

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

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

"внешнее объявление и определение данных".<br />

61


Рассмотрим далее несложный иллюстрирующий пример.<br />

Модуль (файл с объявлениями и определениями данных<br />

и операторами)<br />

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

Функция<br />

Внутренние определения данных<br />

Операторы<br />

Функция<br />

Внутренние определения данных<br />

Операторы<br />

Рис. 22. Обобш[ение определения модуля<br />

Файл Р2.СРР<br />

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

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

на рис. 19<br />

#include // Для функций ввода-вывода<br />

// Прототип функции<br />

void, save ( void. ) ;<br />

// Определение внешних данных: область действия - программа,<br />

// время жизни - программа<br />

int il, ±2;<br />

// Выполнение программы начинается с выполнения' следующей<br />

// ниже главной функции<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

}<br />

save ( ) / // Вызов функции: определяет И, i2<br />

printf( "\п 11 = %d 12 = %d"r 11, 12 ) ;<br />

re turn 0;<br />

// Определение, функции, задающей значения 11, 12<br />

void save( void )<br />

{<br />

11 = 10; 12 = 15;<br />

return;<br />

В этом примере определены внешние данные /1 и /2. Область<br />

действия внешних данных (в примере /1 и /2) распространяется на<br />

62


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

программы. Любая функция, находящаяся в этом файле (модуле)<br />

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

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

файла.<br />

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

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

extern {EXTERNal - внешний).<br />

В качестве иллюстрации перепишем программу для предыдущего<br />

примера с использованием двух файлов.<br />

Файл РЗ.СРР<br />

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

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

внешний класс хранения.<br />

V<br />

^include // Для функций ввода-вывода<br />

// Прототип функции: хотя определение этой функции находится<br />

// в другом файле данного программного проекта (SAVE.CPP),<br />

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

// для контроля правильности вызова функции<br />

void save ( void. )/<br />

// Объявление внешних данных: дополнительной памяти<br />

// объявление не занимает, а лишь говорит о том, что<br />

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

extern Int 11, 12;<br />

int<br />

{<br />

}<br />

main ( void )<br />

save( ) ;<br />

prlntf(<br />

return 0;<br />

"\n<br />

11<br />

// Возвращает О при успехе<br />

// Вызов функции: определяет 11, 12<br />

%d 12 = %d", 11, 12 ) ;<br />

Файл SAVE.CPP<br />

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

\рого имеется в файле РЗ.СРР.<br />

V<br />

// Прототип функции: в принципе, в этом файле прототип не<br />

// нужен, так как файл содержит определение этой функции.<br />

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

void save ( void ) ;<br />

// Определение внешних данных: занимает память, располагая в<br />

// ней соответствуюище данные<br />

63


±nt ilr ±2;<br />

-void save ( void. )<br />

;<br />

il 10; ±2 15;<br />

jretuim/<br />

в приведенном примере внешние данные /1 и /2 определены в<br />

файле SAVE.CPP. Такое определение должно присутствовать только<br />

в одном файле многофайлового программного проекта. Чтобы<br />

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

в файле РЗ.СРР), их следует объявить с использованием<br />

служебного слова extern. Таким образом, определение создает данное<br />

(см. файл SAVE.CPP), а объявление (см. файл РЗ.СРР)- только<br />

ссылается на данное, определенное в другом файле (рис. 23). Обратите<br />

внимание, что объявление внешних данных, в отличие от их<br />

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

проекта.<br />

ОПРЕДЕЛЕНИЕ<br />

Определить данное типа int<br />

, Имя нового данного<br />

int<br />

11; ОБЪЯВЛЕНИЕ<br />

Указывает, что данное определено в<br />

другом месте (в другом файле)<br />

Указывает тип данного<br />

Имя существующего данного<br />

extern int i1;<br />

Рис. 23. Определение и объявление внешних данных<br />

В определении данного перед спецификацией его типа можно<br />

использовать служебное слово static (статический). При этом область<br />

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

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

со временем выполнения программы.<br />

Приведем и для этого случая иллюстрирующий пример.<br />

__<br />

Файл Р4.СРР<br />

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

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

внешний и внешний статический классы хранения<br />

64


^include // Для функций ввода-вывода<br />

// Прототип функции: хотя определение этой функции находится<br />

// в другом файле данного программного проекта (SAVE1.СРР) ,<br />

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

// для контроля правильности вызова функции<br />

void savel ( void. ) ;<br />

// Объявление внешних данных: дополнительной памяти<br />

// объявление не занимает, а лишь говорит о том, что<br />

// соответствующее данные определены в другом файле<br />

// Gxtejcn int 11; Такое объявление ошибочно!<br />

extejcn int 12;<br />

int main ( void )<br />

{<br />

savel( ) ;<br />

prlntf( "\n 12<br />

// Возвращает 0 при успехе<br />

// Вызов функции: определяет<br />

// значение 12<br />

%d", 12 ) ;<br />

return 0;<br />

Файл SAVE1.CPP<br />

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

имеется в файле Р4.СРР<br />

V<br />

// Прототип функции: в принципе, в этом файле прототип не<br />

// нужен, так как файл содержит определение этой функции.<br />

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

void savel( void ) ;<br />

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

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

static int 11; // Доступно только в этом файле<br />

int 12; // Доступно в этом файле и файле<br />

// Р4,СРР<br />

void savel( void )<br />

{<br />

11 = 10; 12 = 15;<br />

}<br />

return;<br />

Обратите внимание, что в этом примере в файле Р4.СРР объявление<br />

extern int 11.<br />

65


было бы ошибочным, так как областью действия /1 является только<br />

файл SAVE1.CPP.<br />

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

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

рассуждениях, относящихся к объектам с описателем класса хранения<br />

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

три файла (рис. 24 а).<br />

н<br />

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

fl.cpp f2.cpp fS.cpp<br />

1 mill ^^^^^^И<br />

1 ovforn И- ^^^H<br />

1 extern int i1; ^^H<br />

{<br />

float i1;<br />

)<br />

a)<br />

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

H<br />

fl.cpp f2.cpp fS.cpp<br />

•"staticl^^<br />

1 ctatin i9- ^^^^H<br />

1 static char ch; ^H<br />

{<br />

char i2;<br />

)<br />

6)<br />

Рис. 24. Внешние объекты:<br />

a) с описателем класса хранения внешний;<br />

б) с описателем класса хранения внешний статический<br />

На этом рисунке область действия объекта /1 с описателем<br />

класса хранения extern содержит весь файл fl.cpp, часть файла<br />

f2.cpp и часть файла f3.cpp (выделена заливкой). Отметим, что начальная<br />

часть файла f2.cpp не входит в область действия объекта /1<br />

потому, что в этом файле объявление объекта /1 помещено в середину<br />

файла. Аналогично, в файле f3.cpp вложенный блок не входит<br />

66


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

/1. Область действия объекта /1с описателем класса<br />

хранения extern можно сделать максимальной — все файлы программного<br />

проекта. Для этого достаточно объявление объекта /1 в<br />

файлах fZ.cpp и О.срр поместить в их начало, а вложенные блоки не<br />

должны содержать переопределение объекта /1. Еще раз напомним,<br />

что определение объекта с описателем класса хранения extern<br />

долэюно быть только в одном файле программного проекта (любом),<br />

а в остальных файлах долэюно использоваться только объявление<br />

объекта.<br />

Рис. 24 б иллюстрирует области действия объектов с описателем<br />

класса хранения "внешний статический". Так, областью действия<br />

объекта/является весь файл П.срр (и только он), областью действия<br />

объекта ch является залитая часть файла f2.cpp, а областью<br />

действия объекта /2 является залитая часть файла О.срр.<br />

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

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

класса хранения "внешний" и "внешний статический":<br />

\. Временем эюизни внешних данных является интервал времени,<br />

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

внешних, так и для внешних статических данных. Следовательно,<br />

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

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

будет утрачено между вызовами функций.<br />

2. Областью действия внешнего данного в общем случае является<br />

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

содерэюатся переопределения данного с тем эюе именем. Вложенным<br />

блоком называется конструкция вида { ... }, в которой между<br />

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

3. Областью действия внешнего статического данного является<br />

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

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

с тем эюе именем.<br />

Обратите особое внимание на два последних уточнения' относительно<br />

областей действия внешних и внешних статических данных<br />

- это очень важно!<br />

Остальные классы хранения данных - автоматический, внутренний<br />

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

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

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

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

67


объ­<br />

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

явлениями (прототипами) функций и их вызовами.<br />

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

Выше типы данных и области действия рассматривались применительно<br />

только к объектам данных. В языках Си/С++ эти атрибуты<br />

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

объявления функций.<br />

Общий вид определения функции представлен на рис. 25.<br />

Класс: внешний (extern, по умолчанию) или<br />

статический (static).<br />

Будем пользоваться умолчанием - опускать extern,<br />

Тип возвращаемого значения (int - по умолчанию,<br />

void - отсутствует)<br />

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

static<br />

extern<br />

{<br />

float power( int number, float exponent)<br />

} Имена параметров<br />

Рис. 25. Определение функции<br />

Типы параметров<br />

Прежде всего, следует заметить, что функцию можно сделать<br />

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

Си/С++, по умолчанию, все функции трактуются как внешние, если<br />

только перед типом функции не указано служебное слово static. Это<br />

означает, что областью действия внешней функции является вся<br />

программа. Определение функции как статической сужает область<br />

ее действия на оставшуюся часть файла, в котором она определена.<br />

Из рис. 25 следует также, что функции можно приписать тип.<br />

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

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

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

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

в других файлах. Показанный на рис. 25 пример иллюстрирует также<br />

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

их типов.<br />

68


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

функции. Вид его аналогичен заголовку определения функции,<br />

за которым вместо блока функции { ... } следует символ ";". Другое<br />

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

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

функции, либо все имена параметров можно опустить.<br />

Давайте теперь рассмотрим иллюстрирующий пример — запишем<br />

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

наибольшее и наименьшее значение из двух аргументов. При<br />

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

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

записи прототипа функции. Спецификация (прототип) функции<br />

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

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

только ее интерфейсные свойства — входные и выходные данные.<br />

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

на рис. 26.<br />

double Arg1<br />

double Arg1<br />

MaxMin<br />

double &Max<br />

double &Min<br />

Исходные данные<br />

Процесс Результаты<br />

(передаются по<br />

значению)<br />

(передаются по ссылке)<br />

Рис. 26. Спецификация функции<br />

Теперь запишем исходный текст в виде законченной программы,<br />

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

Файл MAXMIN. СРР<br />

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

иллюстрирует работу с функцией: объявление (прототип) функции^<br />

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

значения и оба варианта передачи параметров функции - по зна- \<br />

чению и по ссылке<br />

V<br />

^include // Для функций ввода-вывода<br />

// Прототип функции: Argl, Агд2, Мах, М1п - параметры функции<br />

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

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

// определена<br />

void MaxMin ( double Argl, double Arg2, double &Max,<br />

69


double &Min ) ;<br />

±nt main ( void ) // Возвращает 0 при успехе<br />

(<br />

double al = 1.5, a2 - -17.1, Mx, Mn;<br />

// Вызов функции без возвращаемого значения: al, а2, Мх,<br />

// Мп - аргументы функции<br />

MaxMln ( al, а2, Мх, Мп ) ;<br />

prlntf( "\п al = %1д, а2 = %1д, Мх = %1д, Мп = %1д \п",<br />

al, а2, Мх, Мп ) ;<br />

}<br />

jretujrn О;<br />

// Определение функции, вычисляющей Мах:=наиб. (Argl,Агд2) и<br />

// М1п:^наим. (Argl,Агд2). Функция не имеет возвращаемого<br />

// значения<br />

void MaxMin (<br />

double Argl, // Исходное данное ~ передается по<br />

// значению<br />

double Агд2, // Исходное данное - передается по<br />

// значению<br />

double &Мах, // Ответ - передается по ссылке<br />

double ScMin ) // Ответ - передается по ссылке<br />

{<br />

±f( Argl>Arg2 )<br />

}<br />

{<br />

}<br />

else<br />

{<br />

}<br />

return;<br />

Max = Argl; Mln = Arg2;<br />

Max = Arg2; Min = Argl;<br />

Еще раз напомним, зачем нуэюен прототип (объявление) функции.<br />

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

вызова функции. В рассмотренном выше примере прототип функции<br />

MaxMin применяется компилятором при вызове этой функции Здесь<br />

компилятор сравнивает:<br />

• возвращаемое значение функции в прототипе (void - отсутствует)<br />

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

функции);<br />

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

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

70


в нашем примере имеет место их полное соответствие, что<br />

свидетельствует об отсутствии ошибок в вызове функции. Попутно<br />

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

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

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

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

Си. В языке же С-ь+, напротив, прототип является обязательным и<br />

это хорошо.<br />

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

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

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

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

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

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

Рассмотрим процесс передачи аргументов а\ и а2 функции<br />

MaxMin в приведенной выше программе. Что же функция MaxMin<br />

получает в действительности - копии значений аргументов а\ и а2<br />

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

копии значений аргументов al и а2. Передача функции копий значений<br />

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

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

При таком способе передачи значения аргументов al и а2 копируются,<br />

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

и используется в функции в качестве параметров ArgX и Arg2.<br />

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

и может быть повторно использована. При этом сами аргументы<br />

al и а2 остаются неизменными (даже, если в теле функции<br />

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

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

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

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

прежние значения.<br />

В языке C++, в отличие от языка Си, существует и другой способ<br />

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

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

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

Min используется сами аргументы Мх и Мп. По завершении работы<br />

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

перед завершением функции (в нашем случае Мх получает наибольшее,<br />

а Мп — наименьшее значение из al и а2). Такой способ передачи<br />

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

71


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

значения. Но ведь существуют и функции, имеющие возвращаемое<br />

значение. Когда же их следует применять Ответ на этот вопрос<br />

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

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

Рассмотрим пример, иллюстрирующий такой способ получения ответа.<br />

В качестве решаемой задачи рассмотрим более простую задачу,<br />

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

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

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

функции приведена на рис. 27.<br />

double Arg1<br />

double Arg1<br />

Max<br />

double<br />

Исходные данные<br />

(передаются по<br />

значению)<br />

Процесс Наибольшее из Arg1 и<br />

Arg2 получаем как<br />

возвращаемое<br />

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

Рис. 27. Спецификация функции с возвращаемым значением<br />

/*<br />

Файл МАХ.СВР<br />

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

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

ции, определение функции и вызов функции с<br />

чением<br />

Теперь запишем исходный текст в виде законченной программы,<br />

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

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

(прототип) функвозвращаемым<br />

зна-<br />

^include // Для функций ввода-вывода<br />

// Прототип функции: Argl^ Агд2 - параметры функции, функция<br />

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

// функции является обязательным, так как вызов функции<br />

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

dovLble Мах ( double Argl, double Arg2 ) ;<br />

±пЬ main ( void )<br />

{<br />

72<br />

double<br />

al<br />

// Возвращает 0 при успехе<br />

1.5, a2 = -17.1, Мх;<br />

// Вызов функции с возвращаемым значением: al, а2 -<br />

// аргументы функции


}<br />

Мх = Max( al, a2 ) ;<br />

printf ( "\л al = %lg, a2 = %lg, Mx = %lg \n",<br />

air a2, Mx ) ;<br />

z-etuni 0;<br />

// Определение функции<br />

double Max ( // Возвращает наиб. (Argl ,Агд2)<br />

double Arglr // Исходное данное - передается по<br />

// значению<br />

double Агд2 ) // Исходное данное - передается по<br />

// значению<br />

(<br />

±£( Argl>Arg2 )<br />

}<br />

{<br />

}<br />

retuim<br />

re bum A r g2 ;<br />

Argl;<br />

В рассмотренном примере функция Max имеет возвращаемое<br />

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

выражения присваивания. В этом выражении слева от знака операции<br />

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

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

имя функции. В нашем примере в вызове функции Мах имеет<br />

место точное соответствие прототипу этой функции и в части возвращаемого<br />

значения, что также говорит об отсутствии ошибок в<br />

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

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

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

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

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

точнее блоком функции за исключением тех вложенных в блок<br />

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

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

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

Временем эюизни параметра, передаваемого в функцию по<br />

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

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

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

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

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

могут иметь не только параметры функций, но и другие объекты.<br />

73


3.6. Автоматические, регистровые<br />

и внутренние статические данные<br />

Автоматические, регистровые и внутренние статические данные<br />

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

Си/С++. Общий синтаксис блока представлен на рис. 28.<br />

{ Начало блока<br />

Внутренние определения данных<br />

Операторы<br />

Конец блока<br />

Рис. 28. Общий синтаксис блока<br />

Отметим разницу в синтаксисе блока языков Си/С++. В языке<br />

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

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

данных и операторы блока могут быть перемешаны. Но при<br />

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

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

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

можно назвать блоки в операторе //, блоки в циклических операторах,<br />

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

блоков, которые будут рассмотрены далее.<br />

Данные можно определить внутри блока как имеющие либо<br />

автоматический auto {AUTOmatic), либо статический static, либо регистровый<br />

register классы хранения (рис. 29). По умолчанию, когда<br />

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

класс хранения!!!<br />

74


Определение_внутренних_данных<br />

^<br />

Специф._типа<br />

Идентификатор<br />

auto<br />

static<br />

register ><br />

Рис. 29. Определение внутренних данных<br />

Областью действия внутренних данных с классами хранения<br />

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

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

которых не содержатся переопределения тех же самых имен.<br />

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

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

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

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

классом хранения register (они могут иметь только целый тип int)<br />

хранятся в быстродействующих машинных регистрах, если это возможно,<br />

или они эквивалентны данным с классом хранения auto в<br />

противном случае.<br />

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

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

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

входами в блок, в котором они определены.<br />

Рассмотрим ряд иллюстрирующих примеров.<br />

Файл Р7.СРР<br />

Двухфайловый программный проект (файлы Р7.СРР и SAVE4.СРР)<br />

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

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

V<br />

^include<br />

<br />

// Прототипы функций<br />

voxd save4 ( float ) ;<br />

float get ( void ) ;<br />

int main ( void. )<br />

{<br />

// Для функций ввода-вывода<br />

// Возвращает О при успехе<br />

75


Определение внутренних автоматических данных. Можно и<br />

// в такой эквивалентной форме: float mv^ pi;<br />

auto float /nv, pi;<br />

pi = 22. Of / 7; sa\re4 ( pi ) ; mv = get ( ) ;<br />

print f ( "\n mv ^ %f pi = %f"^ mv, pi ) ;<br />

}<br />

retvLm 0;<br />

/*<br />

Файл SAVE4.CPP<br />

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

имеется в файле Р7.СРР<br />

// Прототипы функций<br />

void save4( float ) ;<br />

float get( void ) ;<br />

static float fv;<br />

void save4( float mv )<br />

{<br />

}<br />

fv = mv;<br />

return;<br />

float get( void )<br />

(•<br />

}<br />

return<br />

fv;<br />

Внутренняя автоматическая переменная wv, определенная в<br />

функции main, и параметр mv в функции save4 размещены в разных<br />

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

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

выполнения main. Параметр mv функции save4 имеет в качестве области<br />

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

save4.<br />

Автоматические, регистровые внутренние данные и внутренние<br />

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

друг в друга, что иллюстрирует следующий пример.<br />

/*<br />

Файл Р8.СРР<br />

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

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

*/<br />

76


^include // Для функций ввода-вывода<br />

int main ( void. ) // Возвращает О при успехе<br />

}<br />

{<br />

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

int counter^ // Действует в блоке main и во<br />

// вложенном блоке while<br />

i; // Действует в блоке main и не<br />

// действует во вложенном блоке<br />

// while<br />

counter = О; 1 = 10;<br />

while ( counter < i )<br />

{<br />

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

// переменной во вложенном блоке - она<br />

// располагается в другой области памяти<br />

// и действует в блоке while<br />

int i;<br />

i ^ 0; counter++;<br />

printf( "\n counter ^ %d i = %d", counter, i )/<br />

}<br />

print f ( "\n\n counter = %ci i = %d \л", counter, i ) ;<br />

retuizn 0;<br />

вид:<br />

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

counter = 1 i = О<br />

counter ^ 2 i = О<br />

counter = 3 i = О<br />

counter = 4 i == О<br />

counter = 5 i = О<br />

counter = 6 i = 0<br />

counter = 7 i ^ 0<br />

counter = 8 i = 0<br />

counter =91=0<br />

counter = 10 i = 0<br />

counter^ = 10 i = 10<br />

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

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

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

программы.<br />

// Определение функции<br />

void, save ( float mv )<br />

{<br />

// Определение внутреннего статического данного с его<br />

// инициализацией при трансляции<br />

static int counter == 0;<br />

11


counter++,<br />

}<br />

return/<br />

Значение counter будет сохраняться между вызовами функции<br />

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

эта функция.<br />

3.7. Инициализация данных<br />

в языках Си/С-1-+ большинство данных может быть явно или<br />

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

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

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

объектов Си-программ приведены в табл. 14.<br />

1 Класс<br />

хранения<br />

При компиляции<br />

Табл. 14. Области действия, время жизни<br />

и инициализация объектов<br />

Внешний Параметр Автоматическировый<br />

Регист­<br />

Внешний статический<br />

функции<br />

Файл Функция Блок Блок<br />

Область<br />

действия<br />

Время<br />

жизни<br />

Инициализируемость<br />

объектов<br />

[ Момент<br />

инициализации<br />

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

по<br />

умолчанию<br />

Программа<br />

Программа<br />

Все<br />

При компиляции<br />

Нулем<br />

Программа<br />

Все<br />

Нулем<br />

функция<br />

Нет<br />

Нет<br />

Нет<br />

Блок<br />

Все<br />

При каждом<br />

входе<br />

в блок<br />

Не<br />

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

Блок<br />

Все<br />

При каждом<br />

входе<br />

в блок<br />

Не<br />

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

Внутренний<br />

статический<br />

Блок<br />

Программа<br />

Все<br />

При компиляции<br />

Нулем<br />

При отсутствии явных указаний данным с классами хранения<br />

extern и static присваиваются нулевые начальные значения. Перечисленные<br />

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

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

их имени знака '=' и константного выраэюения:<br />

static ±nt counter = 0;<br />

// Константное выражение не содержит переменных<br />

long max_size = 512 * 200L;<br />

78


Данные с классами хранения extern и static инициализируются<br />

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

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

каждом входе в блок, в котором они определены.<br />

3.8. Упражнения для самопроверки<br />

1. Что напечатает следующая программа<br />

^include<br />

<br />

// Прототипы функций<br />

int next ( void ) ;<br />

tub reset ( void ) ;<br />

int last ( void ) ;<br />

int nw ( ±nb ) ;<br />

int i = 1;<br />

int main ( void )<br />

{<br />

auto Int 1, j ;<br />

1 = reset( ) ;<br />

fox:( j = 3; j


{<br />

static int j = 5;<br />

rGtuJcn 1 = j += 1;<br />

/ k k k k k k k k k k k k - k k k k k k - k k k k k фз^р[Л 3 k k k k k k k k k k k k k k k k k k k k k k k k k k k k /<br />

extern Int i /<br />

±nt reset ( void. )<br />

{<br />

jretuxn i/<br />

;<br />

2. Что напечатает следующая программа<br />

^include <br />

// Прото типы функций<br />

int next ( int );<br />

int reset ( void. ) ;<br />

int last ( int ) ;<br />

int i = 2;<br />

int main ( void )<br />

{<br />

auto int i, j;<br />

i = reset ( );<br />

£or( j = 1; j


i<br />

return j = i--/<br />

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

в разд. 18.<br />

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

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

данных, среди которых основными являются:<br />

• массивы;<br />

• структуры;<br />

• объединения.<br />

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

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

непрерывный (по отношению к расположению в памяти) набор<br />

однотипных объектов данных.<br />

Приведем пример определения массива:<br />

Этот массив символьного типа способен хранить 15 символов.<br />

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

дано без инициализации<br />

V<br />

char kaf__name [ 15 ];<br />

Индивидуальный доступ к отдельным элементам массива осуществляется<br />

с помощью индексированных имен:<br />

kaf_name[ О ] ... kaf_name [ 14 ]<br />

Обратите внимание, что индексы элементов массива изменяются<br />

в диапазоне О ... 14, а не в диапазоне 1 ... 15.<br />

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

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

данных. Массивы моэюно инициализировать (рис. 30):<br />

chstr kaf_name[ 15 ] = { 'К', 'а', 'ф\ 'е\ 'д'г<br />

'Р\ 'а', ' Ч 'АЧ ^B^, 'Г', ^\0^ }/<br />

По принятому соглашению массивы символов, содержащие<br />

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

байт.<br />

81


По этой причине массив kafjname может хранить в виде строки<br />

название кафедры, состоящее не более чем из 14 символов!<br />

kaf_name |<br />

0<br />

1<br />

а<br />

2 3 4 5 6 7 8 9 10 11<br />

к Ф е Д Р а А В Т<br />

Рис. 30. Инициализация массива<br />

\0<br />

12<br />

<br />

13<br />

<br />

14<br />

<br />

Символьный массив можно инициализировать и более удобным<br />

способом:<br />

char kaf_name[ 15 ] = "Кафедра АВТ";<br />

Это определение символьного массива с инициализацией полностью<br />

эквивалентно предыдущему. Вместе с тем, приведенное ниже<br />

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

иной результат:<br />

cJiax- kaf_name [ ] = "Кафедра АВТ"/<br />

В последнем случае в памяти резервируется 12 байтов - ровно<br />

столько, сколько требуется для хранения инициализирующей строки.<br />

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

типом элементов с инициализацией или без нее:<br />

// Определение массива из 9 элементов с типом double без<br />

// инициаЛИЗации<br />

double а[ 9 ];<br />

// Определение массива из 4 элементов с типом double с<br />

// инициализацией<br />

double b[ 4 ] = { 1.0, 2.0, -3.1, 4.5 };<br />

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

массив значений с плавающей точкой. Предварительно определим<br />

понятие стек, широко применяемое в программировании и<br />

вычислительной технике.<br />

Стек - непрерывная область оперативной памяти, в которой<br />

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

по правилу "последним записан - первым прочитан". По этой причине<br />

стек часто называют очередью типа LIFO (Last Input First<br />

Output).<br />

Файл P9.CPP<br />

Двухфайловый программный проект (файлы Р9.СРР и STACK.СРР)<br />

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

стек и извлечения из стека. Стек организован на базе внешнего<br />

82


статического массива, состоящего из элементов с типом float.<br />

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

а за счет своей области действия и времени жизни.<br />

^include // Для функций ввода-вывода<br />

// Прототипы функций<br />

void push ( float ) ;<br />

void, pop ( float &r int & ) /<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

}<br />

int flag; // 0 - извлечение из стека не<br />

// выполнено г иначе - выполнено<br />

float out_value;// Значение, полученное из стека<br />

push ( 2.4f ) ; // Занести в стек 2.4f<br />

push ( -17. 4f ) ; // Занести в стек -17.4f<br />

for( int 1 = 0; i < 3; ±++ )<br />

{<br />

}<br />

// Извлечение из стека<br />

pop ( out_value, flag ) ;<br />

// Анализ полученного результата<br />

if( flag )<br />

{<br />

printf( "\n Результат извлечения: %f ",<br />

out_value ) ;<br />

}<br />

else<br />

{<br />

printf( "\n Стек пуст" ) ;<br />

}<br />

return 0;<br />

Файл STACK.CPP<br />

Содержит функции для занесения элемента и извлечения элемента<br />

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

функция которого имеется в файле Р9.СРР<br />

V<br />

^include // Для функций ввода-вывода<br />

// Прототипы функций (в принципе - прототипы здесь не нужны,<br />

// мы оставляем их в этом файле только для унификации<br />

void push ( float ) ;<br />

void pop ( float &, int & ) ;<br />

^define N10 // Размер стека<br />

// Стек: массив из 10 элементов - доступен только в этом<br />

83


файле, время жизни - программа<br />

static float s[ N ] ;<br />

// Указатель вершины стека: вначале стек пуст<br />

static unsigned, int<br />

top;<br />

// Занесение в стек<br />

void push(<br />

float V ) // Заносимое значение<br />

{<br />

}<br />

if( top < N )<br />

{<br />

s[ top++ ] = V/<br />

}<br />

else<br />

{<br />

print f ( "\n Стек полон - занесение не выполнено" ) ;<br />

}<br />

return;<br />

// Извлечение из стека<br />

void pop (<br />

// Извлеченное значение - передается по ссылке<br />

float<br />

&out,<br />

// О - извлечение не выполнено (стек пуст)<br />

int &f )<br />

{<br />

if( top > О )<br />

{<br />

}<br />

else<br />

(<br />

}<br />

return;<br />

out = s[ --top ]; f = 1;<br />

f = 0;<br />

Результаты выполнения данной программы имеют вид:<br />

Результат извлечения: -17.400000<br />

Результат извлечения: 2.400000<br />

Стек пуст<br />

Рассмотренные выше примеры использовали так называемые<br />

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

с помощью одного индекса (индексного выражения). Указанное индексное<br />

выражение (например, /+/) должно иметь целый тип без<br />

84


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

индекса, равного нулю.<br />

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

массивы и массивы большей размерности. Рассмотрим еще один иллюстрирующий<br />

пример.<br />

Файл Р10.СРР<br />

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

Пример иллюстрирует работу с двумерными массивами из элементов<br />

символьного типа<br />

V<br />

^include // Для функций ввода-вывода<br />

^define N 8 // Строковый размер массива<br />

^define М 16 // Столбцовый размер массива<br />

// Определение двухмерного символьного массива с<br />

// инициализацией: N строк, каждая строка из М символов<br />

char arr__kaf_name [ N ] [ М] =<br />

{<br />

};<br />

"Состав ФТК:", // Инициализация первой строки<br />

"1. Кафедра АВТ",<br />

"2. Кафедра ТК",<br />

"3. Кафедра САУ",<br />

"4. Кафедра МУС",<br />

"3. Кафедра ИИТ",<br />

"6. Кафедра САПР",<br />

"7. Кафедра СУД"<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

auto ±nt Index; // Индекс строки двухмерного массива<br />

fori Index = 0; Index < N; lndex++ )<br />

{<br />

}<br />

print f ( "\n %s "r arr_kaf_name [ Index ] ).<br />

return 0;<br />

Результаты выполнения данной программы имеют вид:<br />

Состав ФТК:<br />

1. Кафедра АВТ<br />

2. Ка федра ТК<br />

3. Кафедра САУ<br />

4. Кафедра ИУС<br />

5. Кафедра ИИТ<br />

85


6, Кафедра САПР<br />

7. Кафедра СУД<br />

Используемый в программе двухмерный массив<br />

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

arrkafname<br />

Строки/<br />

столбцы<br />

0<br />

1<br />

2<br />

0<br />

С<br />

1<br />

2<br />

1<br />

о<br />

2<br />

с<br />

Табл.<br />

3 4<br />

т<br />

К<br />

К<br />

а<br />

а<br />

а<br />

1Ь.<br />

5<br />

в<br />

Ф<br />

Ф<br />

Структура<br />

6 7 8<br />

е<br />

е<br />

Ф<br />

Д<br />

д<br />

Г<br />

р<br />

р<br />

массива<br />

9 10 11<br />

К<br />

а<br />

а<br />

\0<br />

<br />

А<br />

Т<br />

12<br />

9<br />

В<br />

к<br />

13<br />

<br />

т<br />

\0<br />

14<br />

<br />

\0<br />

<br />

15<br />

<br />

7<br />

<br />

6<br />

7<br />

6<br />

7<br />

К<br />

К<br />

а<br />

а<br />

Ф<br />

Ф<br />

е<br />

е<br />

д<br />

д<br />

р<br />

р<br />

а<br />

а<br />

С<br />

с<br />

А<br />

У<br />

п<br />

Д<br />

р<br />

\0<br />

\0<br />

<br />

В этой таблице элемент массива аггjkaf_name[ 6][ 7 ] хранит<br />

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

располагаются в подряд идущих байтах по строкам:<br />

arr_kaf_name [ О ][ О ]<br />

arr_kaf_name [ О ] [ 15 J<br />

arr_kaf_name [ 1 ][ О ]<br />

агг kaf_name[ 1 ][ 15 ]<br />

arr_kaf__name [ 7 ] [ О ]<br />

arr_kaf__name [ 7 ][ 15<br />

arr_kaf__name[ О ][<br />

агг kaf namef 1 ] [<br />

arr_kaf_name [ 7 ] [<br />

3.9.2. Массивы - как аргументы функций<br />

Массив, в отличие от других видов данных, рассмотренных<br />

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

Потому, что массив может занимать много места в памяти и<br />

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

расточительно.<br />

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

имени массива в качестве аргумента языки Си/Сн-+<br />

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

массива. Модифицируем последний пример.<br />

Файл Р11,СРР<br />

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

иллюстрирует передачу массива в функцию<br />

^include // Для функций ввода-вывода<br />

86


^define N8 // Строковый размер массива<br />

idefine М 16 // Столбцовый размер массива<br />

// Определение двухмерного символьного массива с<br />

// инициализацией: N строк^ каждая строка из М символов<br />

static char arr_kaf_name [ N ] [ М] =<br />

{<br />

) ;<br />

"Состав ФТК:",<br />

"1 . Кафедра АВТ",<br />

// Инициализация первой строки<br />

"2. Кафедра ТК",<br />

"3, Кафедра САУ",<br />

"4. Кафедра МУС",<br />

"5. Кафедра ИИТ",<br />

"6. Кафедра САПР",<br />

"7. Кафедра СУД"<br />

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

// параметр - двумерный массив. Здесь прототип также не<br />

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

void display ( cha.r arr_kaf_name [ N] [ М ] ) ;<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

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

// внимание, как записывается аргумент-массив<br />

display ( arr__kaf_name ) ;<br />

}<br />

return 0;<br />

// Печать строк двухмерного массива<br />

void display (<br />

// Массив для печати: передается по ссылке, т.е. не<br />

// копируется<br />

char arr kaf name [ N] [ М ] )<br />

{<br />

auto int index; // Индекс строки двухмерного массива<br />

}<br />

fori index = О; index < N; index++ )<br />

{<br />

}<br />

return;<br />

printf( "\n %s ", arr_kaf_name[ index ] ) ;<br />

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

и у предыдущей программы.<br />

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

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

87


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

// инициализацией. Массив содержит элементы целого типа:<br />

// N строк, каждая строка из М элементов<br />

stable ±zit а[ 3 ] [ 4 ] =<br />

{<br />

} ;<br />

{ 1 , 3 , 5 , 1 } , // Инициализация первой строки<br />

{ 2, 4, 6, 8 },<br />

{ 3, 5, 7, 9 }<br />

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

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

однотипных элементов.<br />

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

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

элементов. Такое данное называют в языке Си структурой. Сразу<br />

же отметим, что объект с типом "структура" в языке C++ имеет более<br />

широкий смысл, чем в языке Си. Ниже мы вначале рассмотрим<br />

структуру с точки зрения языка Си.<br />

3.9.3. Упражнения для самопроверки<br />

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

функции для решения следующей задачи:<br />

• вычислить сумму элементов одномерного массива х[ 7V ] {N ~ 50) целого<br />

типа имеющих нечетные индексы;<br />

• получить одномерный массив z[N] (N = 40) из двух заданных массивов<br />

целого типа jc[ Л^ ], >'[ Л^ ] по правилу:<br />

z[ i ] := тах{ х[ 1 ], у[ i ] }<br />

Возможный вариант ответа можно посмотреть в разд. 18.<br />

3.9.4. Структуры<br />

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

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

можно выполнять в Си-программе по отдельности или же совместно.<br />

Поясним сказанное примерами.<br />

/*<br />

Объявление структуры, содержащей сведения о студенте. Обратите<br />

внимание, что данное объявление не размещает никакого<br />

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

тип<br />

3timet<br />

STUDENT__INFO<br />

88


};<br />

// Фа культет<br />

char fak_name[ 30 ];<br />

char fio[ 30 ];// ФИО<br />

// Номер группы<br />

char group_name[ 7 ];<br />

char date[ 9 ];// Дата поступления студента в ВУЗ<br />

float stip/ // Размер стипендии<br />

Здесь использованы следующие соглашения:<br />

struct - начало объявления и/или определения;<br />

STUDENT_INFO - имя (тэг) структуры;<br />

{<br />

} ;<br />

... - список элементов (полей) структуры<br />

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

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

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

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

новый тип с именем STUDENTINFO.<br />

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

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

// Данное определение размещает объект current в оперативной<br />

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

// объявление типа STDENT_INFO<br />

struct STUDENT_INFO<br />

current; // Для Си или C++<br />

ИЛИ<br />

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

// STUDENT__INFO<br />

STUDENT_INFO<br />

current;<br />

Приведенное выше объявление структуры STUDENT INFO и<br />

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

причем это даст такой же результат:<br />

/'^<br />

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

структурного объекта<br />

V<br />

struct STUDENT_INFO // Тэг структуры<br />

{<br />

// Факультет<br />

char fak name[ 30 ];<br />

89


char fio[ 30 ];// ФИО<br />

// Номер группы<br />

char group_name[ 7 ];<br />

char date[ 9 ];// Дата поступления студента в ВУЗ<br />

float stip; // Размер стипендии<br />

} current; // Имя структурного объекта<br />

Как и массивам^ структурам может быть приписан любой<br />

класс хранения, за исключением класса register. Тем самым будут<br />

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

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

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

класс хранения в языке Си использовать нельзя. Такая возможность<br />

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

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

размещается только один элемент структуры со статическим классом<br />

хранения, а не несколько.<br />

Для ссылки на элементы структурного объекта current следует<br />

использовать операцию "точка". Эту операцию называют квалификацией<br />

элемента.<br />

// Символ, начинающий название факультета<br />

current.fak_name[ О ]<br />

current.stip<br />

// Размер стипендии студента<br />

Сколько байтов памяти занимает current Точный размер этого<br />

объекта можно определить с помощью операции sizeof:<br />

s±zeo£( current ) или sizeof ( struct STUDENT__INFO ) ИЛИ<br />

sizeof( STUDENT_INFO )<br />

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

Имена элементов структуры не конфликтуют с такими же именами<br />

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

Int stip; // правильно<br />

struct STUDENT__INFO<br />

currentl; // Тоже правильно<br />

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

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

Как уже указывалось, структурные объекты можно инициализировать<br />

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

/*<br />

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

объекта с его инициализацией<br />

90


struct STUDENT_INFO // Тэг структуры<br />

{<br />

// Факультет<br />

сЬяг fak_name[ 30 ];<br />

char fio[ 30 ];// ФИО<br />

// Номер группы<br />

cbatr group__name [ 7 ] ;<br />

char date[ 9 ];// Дата поступления студента в ВУЗ<br />

float stip; // Размер стипендии<br />

} current = // Имя структурного объекта<br />

{<br />

"ФТК",<br />

"Иванов И. И. ",<br />

"1081/4",<br />

"01-09-97" ,<br />

100 000,Of<br />

} ;<br />

// Можно создавать массив структур, например:<br />

struct STUDENT_INFO<br />

group[ 12 ];<br />

group[ 5],stip // Стипендия шестого студента группы<br />

3.9.5. Структуры в качестве аргументов функций<br />

Структура может быть передана в функцию целиком. При этом<br />

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

адресу (по ссылке). Как указывалось ранее, передача структуры в<br />

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

при больших размерах структуры нецелесообразно. По этой причине<br />

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

в функцию по ссылке. Приведем два иллюстрирующих примера:<br />

V<br />

Файл Р14.СРР<br />

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

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

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

iinclude // Для функций ввода-вывода<br />

/*<br />

Объявление структурного типа, определение и инициализация<br />

структурированного объекта<br />

V<br />

struct STUDENT_INFO // Сведения о студенте<br />

{<br />

// Факультет<br />

char fak__name[ 30 ];<br />

91


char<br />

// Группа<br />

char<br />

char<br />

float<br />

current =<br />

{<br />

};<br />

"ФТК:",<br />

"Иванов И. И.<br />

"1081/4",<br />

"01-09-97",<br />

100000,Of<br />

fiol 20 ];// ФИО<br />

group^^name [ 7 ];<br />

date[ 9 ];// Дата поступления в университет<br />

stlp; // Размер стипендии<br />

// Определение объекта<br />

// Прототип: обратите внимание, как записывается параметр<br />

// структура при его передаче по значению. В принципе,<br />

// здесь прототип не обязателен, так как файл содержит<br />

// определение функции<br />

void dlsplay_s ( struct STUDENT_INFO ) ;<br />

int main ( void )<br />

{<br />

// Возвращает 0 при успехе<br />

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

// внимание, как записывается аргумент-структура<br />

d±splay_s ( current ) ;<br />

}<br />

retvLrn О;<br />

// Печать элементов структуры<br />

void display_s (<br />

struct STUDENT_INFO // Структура для печати: передается<br />

S ) // по значению, т.е. копируется<br />

{<br />

printf( "\п Факультет: %s S.fak name ) ;<br />

printf( "\n ФИО: %s ", s.fio ) ;<br />

printf ( "\n Номер группы: %s ", s.group_name ).<br />

printf( "\n Дата поступления: %s ", s.date ) ;<br />

printf( "\n Размер стипендии: %f ", s.stip ) ;<br />

return;<br />

/*<br />

Файл P15.CPP<br />

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

иллюстрирует передачу структуры в функцию по ссылке. Такой<br />

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

основного<br />

*/<br />

^include // Для функций ввода-вывода<br />

92


V<br />

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

структурного объекта<br />

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

struct STUDENT INFO // Сведения о студенте<br />

{<br />

// Факультет<br />

cha,r<br />

fak__name[ 30 ];<br />

char<br />

fio[ 20 ];// ФИО<br />

// Группа<br />

сЬа.г<br />

group__name [ 7 ];<br />

char<br />

date[ 9 ];// Дата поступления в<br />

float<br />

stip; // Размер стипендии<br />

университет<br />

current =<br />

// Определение объекта<br />

{<br />

};<br />

"ФТК:",<br />

"Иванов И. И.<br />

"1081/4",<br />

"01-09-97",<br />

100000,Of<br />

// Прототип: обратите внимание, как записывается параметр<br />

// структура при его передаче по ссылке.<br />

void display_s ( struct STUDENT_INFO & ) ;<br />

int main ( void )<br />

{<br />

// Возвращает 0 при успехе<br />

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

// внимание, как записывается аргумент-структура<br />

display_s( current ) ;<br />

}<br />

return 0;<br />

// Печать элементов структуры<br />

void display__s (<br />

struct STUDENT__INFO // Структура для печати: передается<br />

&S ) // по ссылке, т.е. не копируется<br />

printf( "\п Факультет: %s ", s.fak_name ) ;<br />

printf( "\n ФИО: %s ", s.fio ) ;<br />

print f ( "\n Номер группы: %s ", s.group_name ) ;<br />

printf( "\n Дата поступления: %s ", s.date ) ;<br />

printf( "\n Размер стипендии: %f ", s.stip ) ;<br />

}<br />

return;<br />

3.9.6 Упражнения для самопроверки<br />

1. В текстовом файле "ctrl4.dat" имеется 15 строк, каждая из которых<br />

имеет следующий формат:<br />

93


число_ 1<br />

число_2<br />

Здесь "число_Г' определяет вид геометрической фигуры (1 - квадрат, 2 -<br />

круг), а "число_2" - параметр фигуры (при "число 1" — 1 - длина стороны,<br />

а при "число_2" = 2 - радиус).<br />

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

сведений о геометрических фигурах. Каждый элемент массива<br />

должен иметь следующие поля:<br />

• имя фигуры;<br />

• длина стороны или радиус;<br />

• площадь фигуры.<br />

1.2. Написать фрагмент программы для чтения из файла на магнитном<br />

диске "ctrl4.dat" информации о геометрических фигурах.<br />

1.3. Написать фрагмент программы, вычисляющий площади<br />

геометрических фигур.<br />

1.4. Написать фрагмент программы, печатающий в файл<br />

"ctrl4.out" параметры геометрических фигур. Сведения об отдельных<br />

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

круг: радиус= . . . , площадь^ . . .<br />

квадрат: длина стороны= ..., площадь= ...<br />

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

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

представленный фрагмент.


4. ОПЕРАТОРЫ И УПРАВЛЕНИЕ<br />

ИХ ИСПОЛНЕНИЕМ<br />

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

может быть закодирована (записана) с помощью комбинаций трех<br />

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

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

конструкциями являются следующие конструкции:<br />

• последовательность операторов (следование);<br />

• выбор (ветвление);<br />

• итерация (цикл).<br />

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

Си, которые реализуют эти конструкции. К ним относятся операторы<br />

z/, while, do-while и for.<br />

Однако кроме элементарных конструкций в языках Си/С-н+<br />

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

Перечень операторов языков Си/С++ в форме синтаксической<br />

диаграммы показан на рис. 31.<br />

4.1. Пустой оператор<br />

Пустой оператор состоит из одного символа — ";". Он не выполняет<br />

никаких действий. Тогда возникает вопрос - а зачем он нужен<br />

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

сложных операторах, например, в операторах z/, for, while. По этой<br />

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

операторов и им подобных.<br />

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

Операторы-выражения представляют собой просто выражения,<br />

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

другой контекст. Многие операции, а, следовательно, и виды<br />

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

необходимым минимумом, изложенным выше.<br />

95


•<br />

Оператор<br />

->( return<br />

V<br />

^<br />

' ^<br />

L<br />

^<br />

< < ><br />

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

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

4'><br />

A<br />

L<br />

^ V ' У ^<br />

->^ break<br />

->r continue<br />

^<br />

^<br />

-^ goto J<br />

ь<br />

^ Идентификатор<br />

•<br />

jf<br />

Блок<br />

^<br />

switch<br />

'<br />

^<br />

while<br />

^<br />

do-while<br />

for<br />

ui ic;|^a 1 Kjyj<br />

Рис. 31. Перечень операторов<br />

4.3. Операторы break и continue<br />

Эти операторы (break - прервать, continue - продолжить), подобно<br />

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

switch, while, do-while и for. Как и в случае пустого оператора<br />

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

они могут входить.<br />

4.4. Блок операторов<br />

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

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

следует последовательность операторов. Блок заключается в<br />

96


фигурные скобки. Повторно отметим, что в языке C++ определения<br />

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

объектов долэюны всегда предшествовать их использованию.<br />

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

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

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

4.5. Оператор return<br />

Этот оператор имеет следующие две формы:<br />

return ( выражение ) ; ИЛИ эквивалентно return, выражение;<br />

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

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

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

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

но еще и возвращает значение в место вызова. Следовательно, эта<br />

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

При этом тип выражения в операторе return и тип возвращаемого<br />

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

Обратите внимание, что хотя оператор return\ может отсутствовать<br />

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

фигурной скобкой, завершающей функцию), хороший стиль<br />

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

сожалению, в существующей литературе эта рекомендация не всегда<br />

выполняется.<br />

4.6. Оператор if<br />

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

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

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

представлена на рис. 32.<br />

Работа этого оператора состоит в том, что вначале вычисляется<br />

значение заключенного в скобки "выражения". Если его значение<br />

отлично от нуля (!0, "истина"), то выполняется "оператор_1". Если<br />

использовано служебное слово else (иначе) и значение "выражения"<br />

равно нулю (=0, "ложь"), то выполняется "оператор_2", указанный<br />

после служебного слова else. После выполнения "оператора 1" или<br />

97


"оператора_2" управление передается следующему за if оператору<br />

программы.<br />

dXIH Выражение !=0<br />

= 0<br />

•Г else V^ Оператор_2<br />

Рис. 32. Синтаксическая диаграмма оператора if<br />

Если значение "выражения" равно нулю, но служебное слово<br />

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

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

значения "выражения" дает нецелый тип, то полученное значение<br />

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

операторов "оператор_1" или "операторе" можно использовать<br />

блоки операторов:<br />

±£( а > Ь )<br />

так = а;<br />

else<br />

max = b;<br />

Обратите также внимание на местоположение символов ';'. Наличие<br />

этих символов необходимо потому, что в качестве "оператора_Г'<br />

и "оператора_2" используются операторы-выражения, которые<br />

должны заканчиваться ';' (см. рис. 31).<br />

// Эквивалентная запись<br />

±f(a>b)<br />

{<br />

}<br />

else<br />

{<br />

)<br />

так = а;<br />

max = b;<br />

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

приведенной на рис. 31, после операторов-блоков символ ";" не ставится.<br />

По этой причине символ "точка с запятой" отсутствует после<br />

символов "}", завершающих блоки.<br />

Поскольку в качестве "оператора 1" и "оператора_2" можно, в<br />

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

быть вложенными:<br />

98


char<br />

ch;<br />

±f( ch -= 'a ' ;<br />

index = 1;<br />

else ±f( ch == 'b ' ;<br />

index =2;<br />

else ±f( ch == 'y' )<br />

index = 25;<br />

else ±f(ch== ' z ' )<br />

index = 26;<br />

else<br />

index = 0;<br />

В этом примере при значении ch, отличном от одной из строчных<br />

букв латинского алфавита, переменной index будет присвоено<br />

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

операторов if. Хороший стиль программирования рекомендует<br />

помещать вложенный if в охватывающий г^после else\<br />

// Пример с "подводными камнями"<br />

dovble а = 77. 7;<br />

int i - 5;<br />

if(i


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

достаточно использовать один оператор if. При ветвлении на три и<br />

более направлений можно использовать вложенные операторы if В<br />

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

далее.<br />

4.7. Оператор switch<br />

Вернемся к рассмотренному выше в конце подразд. 4.6 примеру.<br />

Этот пример трудно читать и еще труднее сопровождать. В<br />

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

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

можно использовать оператор switch (проверяемое выражение при<br />

этом не может иметь вещественный тип). Приведенный выше<br />

фрагмент при использовании оператора switch приобретает<br />

следующий более наглядный и удобный вид:<br />

char<br />

ch;<br />

switch ( ch )<br />

{<br />

}<br />

case 'a':<br />

1ndex = 1; break;<br />

case 'Ь':<br />

index = 2; break/<br />

case 'у':<br />

Index = 25; break;<br />

case 'z ':<br />

index =^ 26; break;<br />

default:<br />

index = 0;<br />

Синтаксис оператора switch (переключатель) поясняется диаграммой,<br />

приведенной на рис. 33.<br />

Выполнение оператора switch начинается с вычисления заключенного<br />

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

целого или символьного типа. Затем просматриваются друг за другом<br />

префиксы case (случай), вычисляются указанные после служебного<br />

слова case "константныевыражения" и полученные значения<br />

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

слова switch. Если эти результаты совпали, то управление передается<br />

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

case. Если ни одного совпадения не произошло и при этом указано<br />

необязательное служебное слово default (по умолчанию), то<br />

100


управление передается оператору, следующему за default. Если ни<br />

одного совпадения не произошло, а служебное слово default отсутствует,<br />

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

за последней фигурной скобкой оператора switch.<br />

OnepaTop_switch<br />

( switch V^T ( VH Выражение V->( ) Vw OnepaTop_case<br />

Оператор case<br />

case<br />

'N I Константное I ^/'^T^ [<br />

J \ _выражение | I V ' У I f I<br />

Оператор V-ffi }<br />

Ц^ default y<br />

Рис. 33. Синтаксическая диаграмма переключателя<br />

После передачи управления в блок операторов case исполнение<br />

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

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

операторов не будет изменена операторами break или<br />

goto. Действие оператора break сводится к передаче управления<br />

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

switch (рис. 34). Следует также отметить, что порядок следования<br />

case в блоке безразличен.<br />

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

ветвлений на три или более направлений имеются две альтернативы<br />

- использование вложенных операторов //или использование оператора<br />

switch. Из приведенных примеров видно, что использование<br />

оператора switch является более наглядным и простым. Однако этим<br />

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

имеет вещественный тип или если проверяется комбинация нескольких<br />

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

операторы if<br />

4,8. Оператор while<br />

Этот оператор уже рассматривался нами. Он является оператором<br />

цикла с предусловием (рис. 35):<br />

101


switch( выражение )<br />

{<br />

case конст._выр,_1:<br />

Операторы<br />

break; —<br />

// Выражение не может быть<br />

// вещественного типа<br />

case конст._выр,_2:<br />

Операторы<br />

break;<br />

Оператор while<br />

-Н(^ while ")-КГО~Л<br />

Рис. 34. Переключатель<br />

Выражение Оператор<br />

Рис. 35. Синтаксическая диаграмма оператора while<br />

Работа оператора while заключается в следующем.<br />

1. Вычисляется значение "выражения" и, если его тип отличен<br />

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

2. Если значение "выражения" отлично от нуля ("истина"), то<br />

выполняется "оператор" и осуществляется переход к п. 1.<br />

3. Если значение "выражения" равно нулю ("ложь"), то выполнение<br />

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

за оператором while.<br />

Таким образом, оператор while эквивалентен следующей последовательности<br />

операторов:<br />

cycle: ±f( выражение )<br />

{<br />

}<br />

опера тор<br />

// Оператор передает управление на оператор^<br />

// следующий за меткой cycle (см. подробнее об<br />

// операторе goto ниже)<br />

goto cycle;<br />

"Выражение", используемое в операторе while, называется условием<br />

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

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

должно быть изменение значений переменных, входящих в "выражение".<br />

В противном случае оператор while выполнялся бы бесконечно.<br />

102


Обычно тело цикла представляет собой блок операторов. Это<br />

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

Но в частном случае, если "выражение" имеет нулевое значение, тело<br />

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

особенностью циклов с предусловием.<br />

Рассмотрим и проанализируем ряд специальных примеров.<br />

// пример 1<br />

while ( fun( ) == 1 ) ; // В качестве тела цикла использован<br />

// пустой оператор<br />

В этом примере вся работа осуществляется за счет выполнения<br />

условия цикла и не требуется никаких других операторов (fun() -<br />

функция, возвращающая некоторое целое значение; как только она<br />

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

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

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

обозначает пустой оператор.<br />

// пример 2<br />

while ( 1 )<br />

{<br />

bxrea-k ;<br />

}<br />

Этот пример демонстрирует преднамеренное создание "бесконечного"<br />

цикла. Так как "выражение" в этом цикле всегда имеет<br />

значение 1 ("истина"), то цикл может исполняться неограниченное<br />

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

является использование в его теле оператора break. Как только это<br />

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

за оператором while. Оператор break подобным же образом<br />

действует в теле цикла и других циклических операторов, которые<br />

будут рассмотрены ниже.<br />

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

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

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

В языке предусмотрен оператор continue^ который позволяет<br />

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

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

по своему действию операторы break и continue являются операторами<br />

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

их показано на рис. 36.<br />

103


while( выражение)<br />

{<br />

break;<br />

while( выражение )<br />

{<br />

continue;<br />

}<br />

Рис. 36. Использование операторов break и continue в цикле while<br />

}<br />

4.9. Оператор do-while<br />

Оператор do-while, часто называемый оператор do, является<br />

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

на рис. 37 (обратите внимание на наличие в конце оператора<br />

точки с запятой).<br />

Оператор do-while<br />

-^/doj-H Оператор V-U while V^/ ( VH Выражение Н->Г ) \>( \ )—•<br />

Рис. 37. Синтаксическая диаграмма оператора do-while<br />

Работа оператора поясняется следующей эквивалентной последовательностью<br />

операторов:<br />

cycle:<br />

оператор<br />

±£( выражение ) goto cycle;<br />

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

отличие от цикла while, тело цикла do-while, независимо от значения<br />

"выражения", будет выполнено не менее одного раза. Для изменения<br />

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

операторами break и continue (рис. 38).<br />

do<br />

{<br />

do<br />

{<br />

Ьгяак"<br />

continue*<br />

}<br />

while( выражение );<br />

}<br />

while( выражение);<br />

Рис. 38. Операторы break и continue в цикле do-while<br />

104


Остальные особенности цикла while (использование пустого<br />

оператора, "бесконечный" цикл, вложенность циклов и т.п.) в равной<br />

степени относятся и к циклу do-while.<br />

4.10- Оператор for<br />

Этот оператор также является циклом с предусловием и имеет<br />

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

for поясняется следующей эквивалентной последовательностью<br />

операторов:<br />

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

cycle:<br />

if( выражение2 )<br />

{<br />

}<br />

опера тор<br />

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

goto cycle;<br />

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

списки выражений, в которых выражения разде^лены запятыми.<br />

Оператор for<br />

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

о<br />

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

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

•о<br />

Оператор<br />

Рис. 39. Синтаксическая диаграмма оператора/Ьг<br />

// пример<br />

char<br />

int<br />

namel 20 ] = "Кафедра АВТ"^<br />

kaf__name [ 20 ];<br />

1;<br />

// Копирование пате в kaf_name с использованием цикла for<br />

fori i = 0; пате[ i ] != '\0'; i + + )<br />

{<br />

kaf_name [ i ] = name [ i ]/<br />

}<br />

kaf name[ i ] = '\0' /<br />

105


Копирование name в kaf_name с использованием цикла while<br />

// Инициализация управляющей переменной цикла<br />

1 - О/<br />

while ( пате[ i ] != '\0' )<br />

{<br />

kaf__name[ i ] = name [ i ];<br />

// Модификация управляющей переменной цикла<br />

±++;<br />

}<br />

kaf_name[ i ] == '\0'/<br />

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

// записать с использованием цикла do-whlle<br />

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

// строковой функции St г еру (подробнее о строковых функциях<br />

// будет сказано ниже)<br />

^include // Для строковых функций •<br />

strcpy ( kaf__name, name ) ;<br />

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

оператор уЬг удобнее и легче для восприятия, чем операторы while и<br />

do'while. Это обусловлено тем, что все три выражения, связанные с<br />

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

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

исходный код в поисках выражений, обеспечивающих<br />

инициализацию и модификацию, как пришлось бы делать при<br />

применении операторов while и do-while.<br />

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

циклических операторов после завершения соответствующих<br />

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

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

примере.<br />

Из синтаксической диаграммы цикла for следует также, что<br />

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

выражений, входящих в состав этого оператора:<br />

for( ; ; ) оператор // Бесконечный цикл<br />

Как и для циклов while и do-while, для цикла for последовательность<br />

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

помощью операторов break и continue (рис. 40).<br />

106


for( выр1; выр2; вырЗ ) for( выр1; выр2; вырЗ )<br />

{ {<br />

break;<br />

continue;<br />

} Передача управления на<br />

«выражениеЗ» - см.<br />

эквивалентное представление<br />

Рис. 40. Использование операторов break и continue в цикле/Ьг<br />

Пример. Одномерный массив вещественного типа напечатать по<br />

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

JV<br />

^define N100 // Размер массива<br />

^include // Для функций ввода-вывода<br />

{<br />

float а[ N ]/ // Массив для печати<br />

printf( "\п" ); // Начать печать с новой строки<br />

for( int i = О; 1 < N; i-f-f ;<br />

{<br />

}<br />

±f( ( 1 % 4 ) == 0 ) printf( "\n" );<br />

pr±ntf( "%15g", a[ i ] );<br />

}<br />

В качестве упражнения рекрмендуем Вам запрограммировать<br />

эту же задачу с использованием циклов while и do-while.<br />

Повторно напоминаем, что в языке C++ в блоке можно чередовать<br />

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

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

В рассмотренном примере таким объектом является /. Область<br />

действия и время жизни / - от точки определения (заголовок цикла)<br />

и до конца блока (объект с автоматическим классом хранения).<br />

Завершая рассмотрение циклических операторов, отметим, что<br />

при программировании цикла есть три возможности:<br />

• использовать цикл/Ьг;<br />

• использовать цикл while;<br />

• использовать цикл do-while.<br />

Возникает вопрос: какой из этих альтернатив следует воспользоваться<br />

в конкретном случае Ответ прост - лучше всего, как было<br />

показано выше, использовать цикл for, а это всегда можно сделать,<br />

107


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

используются циклы while и do-while, причем цикл do-while следует<br />

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

раза.<br />

4.11. Оператор goto и метки операторов<br />

Операторы break и continue определялись выше как операторы<br />

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

ним языки Си/С++ предоставляют программисту и нашумевший<br />

оператор перехода ^о/'о:<br />

доЬо идентификатор;<br />

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

в виде<br />

идентификатор:<br />

Строго говоря, при написании программ применение оператора<br />

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

быть написана с помощью только трех элементарных конструкций,<br />

каждая из которых имеет только один вход и один выход:<br />

• следование;<br />

• ветвление;<br />

• цикл.<br />

Однако существуют ситуации (их немного), когда goto удобен,<br />

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

две.<br />

1. Обычно оператор goto служит для передачи управления в<br />

конец функции в случае обнаружения ошибки, особенно если ошибки<br />

могут возникать во многих местах функции. В конце функции,<br />

куда выполняется переход по goto, выполняется обработка ошибок и<br />

возвращается значение, соответствующее наличию ошибки.<br />

2. Другим примером целесообразного применения goto может<br />

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

break осуществляет выход только из того цикла, где он использован<br />

(рис. 41). В остальных случаях использовать goto не следует.<br />

4.12. Упражнения для самопроверки<br />

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

фрагменту Си-программы:<br />

108


if( с =^ 1 ) а + + / else ±f( с<br />

else ±£( с =^ 3 ) а += 1/<br />

2 ) а-<br />

while( выражение )<br />

{<br />

while( выражение )<br />

{<br />

lf( /* Ошибка */... ) goto loop_end;<br />

(oop_end:<br />

<<br />

Рис. 41. Использование оператора go/^о для выхода<br />

из гнезда циклов<br />

2. Записать фрагмент программы, соответствующий следующему<br />

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

предыдущему):<br />


5. Пусть определен массив<br />

±nt а[ 25 ];<br />

Напишите фрагмент Си-программы, который напечатает с новой<br />

строки значения элементов массива "а" по пять элементов в строке и по<br />

десять позиций на элемент. Решить задачу с помощью цикла while.<br />

6. При каких исходных значениях "А:" приведенный ниже цикл будет<br />

выполняться бесконечно<br />

±zit к;<br />

wh±lG( к < 5 ) к+ + ;


5. ВЫРАЖЕНИЯ И ОПЕРАЦИИ<br />

В языках Си/С++ предусмотрен богатый набор операций. В<br />

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

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

версии этих операций, побитовые операции и операции над<br />

адресами (указателями). Рассмотрим операции и выражения, использующие<br />

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

которые будут рассмотрены ниже.<br />

Можно выделить шесть категорий операций:<br />

• ссылки;<br />

• унарные операции;<br />

• бинарные операции;<br />

• тернарные операции;<br />

• присваивания;<br />

• операция "запятая".<br />

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

элементам массивов и структур.<br />

Унарные операции воздействуют на одно значение или выражение.<br />

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

- три.<br />

Приоритеты операций в порядке их убывания и порядок выполнения<br />

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

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

операций выделены операции присваивания.<br />

Табл. 16. Приоритеты и порядок выполнения операций<br />

Наименование<br />

Знаки операций<br />

операций<br />

Разрешение области<br />

видимости<br />

Ссылочные<br />

[ ] - доступ по индексу: адрес_начала[ выражение ]<br />

или выражение[ адрес_начала ])<br />

( ) — управление порядком выполнения операций в<br />

выражении; вызов функции:<br />

имя_функции( списокпараметров );<br />

конструирование значения:<br />

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

. — выбор члена класса посредством объекта:<br />

объект, членкласса<br />

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

указатель->член класса<br />

Порядок<br />

выполнения<br />

Нет<br />

Слева -<br />

направо<br />

111


Наименование<br />

операций<br />

Унарные<br />

Мультипликативные<br />

бинарные<br />

продолжение табл. 16<br />

Знаки операций<br />

++ - постфиксный инкремент: lvalue++<br />

— постфиксный декремент: lvaluenew<br />

- динамически создать объект (выделить<br />

динамическую память): new type или<br />

new type( списоквыражений)<br />

delete — уничтожить объект (освободить<br />

динамическую память): delete указагел<br />

ьнаобъект<br />

delete[ ] - уничтожить массив объектов:<br />

delete указательнамассивобъектов<br />

++ - префиксный инкремент: -ь+lvalue<br />

— префиксный декремент: —lvalue<br />

* - разадресация (разименование): * выражение<br />

& - получение адреса объекта: & lvalue<br />

+ - унарный плюс: + выражение<br />

— - унарный минус: - выражение<br />

! - логическое отрицание (not): !выражение<br />

'- - поразрядное дополнение: -выражение<br />

sizeof - размер в байтах: 812еоГ(объект)<br />

или sizeof(THn)<br />

typeidO - идентификация типа времени<br />

выполнения: typeid(type)<br />

(type) - приведение типа: (type)выpaжeниe -<br />

приведение типа выражения к типу в скобках<br />

(старый стиль), выполняется справа-налево<br />

COnst_cast — константное преобразование типа:<br />

const_cast(выpaжeниe)<br />

dynamic_cast - преобразование типа с проверкой<br />

во время выполнения:<br />

dynamiccast (выражение)<br />

reinterpret__cast - преобразование типа без<br />

проверки: reinteфret_cast(выpaжeниe)<br />

static__cast — преобразование типа с проверкой во<br />

время компиляции: static cast(выpaжeниe)<br />

.* - выбор члена класса посредством объекта:<br />

объект. *указатель_на_член_класса, выполняется<br />

слева-направо<br />

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

объект:<br />

указатель на объект ->* указатель на член класса,<br />

выполняется слева-направо<br />

Порядок<br />

выполнения<br />

Нет<br />

* - умножение: выражение * выражение Слева -<br />

/ - деление: выражение / выражение | направо<br />

% - остаток от деления (деление по модулю):<br />

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

Аддитивные бинарные + - сложение: выражение -^ выражение<br />

- - вычитание: выражение - выражение<br />

Сдвига бинарные « - сдвиг влево: выражение « выражение<br />

» - сдвиг вправо: выражение » выражение<br />

Слева -<br />

направо<br />

Слева -<br />

направо<br />

112


1 Наименование<br />

операций<br />

Отношения бинарные<br />

Отношения бинарные<br />

Поразрядная "И"<br />

бинарная<br />

Поразрядная<br />

"ИСКЛЮЧАЮЩЕЕ<br />

ИЛИ" бинарная<br />

Поразрядная<br />

"ВКЛЮЧАЮЩЕЕ<br />

ИЛИ" бинарная<br />

Логическая "И"<br />

бинарная(and)<br />

1 Логическая "ИЛИ"<br />

бинарная (or)<br />

Условная тернарР1ая<br />

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

Совмещенное<br />

присваивание<br />

Генерация исключения<br />

Запятая<br />

(последовательность)<br />

Продолжение табл. 16<br />

Знаки операций<br />

< - меньше: выражение < выражение<br />

> - больше: выражение > выражение<br />

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

== - равно: выражение == выражение<br />

!= - не равно: выражение != выражение<br />

& - поразрядное умножение:<br />

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

^ - выражение '^ выражение<br />

1 - выражение | выражение<br />

&& - логическое умножение:<br />

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

ii - логическое умножение: выражение || выражение<br />

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

= - lvalue = выражение<br />

*= - выражение *= выражение<br />

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

%= - выражение %= выражение<br />

+= - выражение += выражение<br />

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

«= - выражение «= выражение<br />

»= - выражение »= выражение<br />

&= - выражение &= выражение<br />

1= - выражение |= выражение<br />

^= - выражение ^= выражение<br />

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

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

Порядок<br />

выполнения<br />

Слева —<br />

направо<br />

Слева —<br />

направо |<br />

Слева -<br />

направо<br />

Слева -<br />

направо<br />

Слева -<br />

направо<br />

Слева -<br />

направо<br />

Слева -<br />

направо<br />

Справаналево<br />

Справа —<br />

налево<br />

Справа —<br />

налево<br />

Нет<br />

Слева -<br />

направо |<br />

5.1. Операции ссылки<br />

Порядок выполнения операций (раньше - позже) определяется<br />

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

в порядке их появления в выражении слева - направо или<br />

справа - налево. В этом плане операции ссылки после операции разрешения<br />

области видимости имеют наивысший приоритет и выполняются<br />

слева - направо (табл. 16).<br />

Имеются следующие разновидности операций ссылки:<br />

• ссылка на элемент массива [ ];<br />

113


• ссылка на элемент структуры .;<br />

• ссылка на элемент структуры с помощью указателя ->.<br />

Особое место в этой группе занимает операция "()", служащая<br />

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

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

имеет наивысший приоритет.<br />

Ссылка на элемент массива. Операция предназначена для<br />

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

операцию, выражение, называемое индексным и имеющее целое<br />

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

массива:<br />

^define N100 // Размер массива<br />

±nt arr[ N ]; // Определение массива целого типа<br />

// из N элементов<br />

... агг[ i+2 J // Ссылка на элемент массива<br />

... arrf 12 ] // Ссылка на элемент массива<br />

Повторно напомним, что у массива, содержащего N элементов,<br />

значения индекса изменяются в диапазоне от О до Л^-1. Имя массива,<br />

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

массива:<br />

агг эквивалентно


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

объектами происходит существенно медленнее и это следует<br />

иметь в виду.<br />

Получим функцию индексации для двумерного массива. Двумерный<br />

массив (в математике его называют матрицей) располагается<br />

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

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

адресов оперативной памяти, затем - второй строки и т.д.<br />

#define N10 // Строчный размер матрицы (массива)<br />

#define М 9 // Столбцовый размер массива<br />

// Определение двумерного массива<br />

short ±nt two_arr[ N ][ М ]/<br />

// Функция индексации для элемента массива two__arr[i] [j] :<br />

// 1. Вычисляем значения индексных выражений, указанных в<br />

// квадратных скобках (в данном случае этого не требуется) .<br />

// 2. Вычисляем сдвиг указанного элемента относительно начала<br />

// массива shift = (i*M+j) *s±zeo£(shoirb int) .<br />

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

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

// two__aгг [i ] [j ] , имеет следующий вид:<br />

// adr = two_arr-hshift = 8ctwo_arr[0] [0] + (i*M+j)<br />

// *3±zeof( short int ; .<br />

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

работы еще больше - на каждый элемент два умножения и два сложения.<br />

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

массивов вообще и особенно массивов высокой размерности.<br />

Ссылка на элемент структуры. Ссылка на элемент структуры<br />

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

current .stip (см. подразд. 3.9.3 о структурах)<br />

представляет значение элемента stip структуры current. Так как<br />

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

и на ссылку на элемент структуры. Например,<br />

bigstr.smallstr.elem<br />

представляет элемент elem структуры smallstr^ которая, в свою очередь,<br />

является элементом структуры bigstr.<br />

Операция ссылки на элемент структуры с помощью указателя<br />

выглядит следующим образом:<br />

115


STUDENT__INFO s_data, // Структура<br />

*ps_data = &s__data;<br />

// Указатель на эту же структуру<br />

... s_data.stlp /* эквивалентно */ ps__data->stlp<br />

5.2. Унарные операции<br />

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

выполняются они справа налево (см. табл. 16). Унарные операции<br />

представлены в табл. 17.<br />

-<br />

f<br />

++ —<br />

sizeo/{ имя типа )<br />

(спецификация типа)выражение<br />

~<br />

& *<br />

Табл.17. Унарные операции<br />

Инвертирование знака<br />

Логическое отрицание<br />

Увеличение, уменьшение<br />

Размер в байтах<br />

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

Поразрядная инверсия (обсуждается ниже в<br />

разделе "Поля битов и побитовые операции")<br />

Адресация, разадресация (обсуждается ниже<br />

в разделе "Указатели")<br />

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

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

арифметического типа.<br />

При использовании операции "-" тип результирующего значения<br />

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

Логическое отрицание. Операция отрицания "!" изменяет<br />

значение "истина" на значение "ложь", а значение "ложь" - на значение<br />

"истина". Напомним, что значению "истина" соответствует ненулевое<br />

целое значение, а значению "ложь" - нуль.<br />

Увеличение и уменьшение. Операции "++" и "—" могут предшествовать<br />

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

(постфиксные операции). Эти операции соответственно добавляют<br />

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

результат:<br />

X = X -h 1;<br />

у = у - 1;<br />

/'^ эквивалентно<br />

/* эквивалентно<br />

*•/<br />

V<br />

++х;<br />

--у;<br />

X = X + 1;<br />

а = а г г [ X ];<br />

/* эквивалентны<br />

= arrf -h-i-x 7/<br />

116


а = arr [ X ]; /* эквивалентны */ а = arr [ к + + ];<br />

к = к + 1;<br />

При использовании "++" и "—" тип результирующего значения<br />

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

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

явного изменения типа выражения:<br />

( спецификация_типа )выражение<br />

"Спецификациятипа" может быть любым служебным словом,<br />

задающим спецификацию типа, например, int, short, long, float и т.д.<br />

int 1 = 11000, w = 4;<br />

long a;<br />

// Если целое занимает 2 байта (разрядность процессора 16<br />

// бит), то здесь возникает ошибка при умножении:<br />

// 44000 > 32767. Как быть<br />

а = 1 "^ w;<br />

а = ( long ) ( 1 * W ) ; // И здесь возникает ошибка при<br />

// умножении: 44000 > 32 767<br />

а = ( long )1 * w; // Так правильно!<br />

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

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

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

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

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

char преобразуются к типу int.<br />

Операция sizeof Операция выполняется на этапе компиляции<br />

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

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

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

Применение этой операции демонстрировалось выше.<br />

Пример.<br />

±пЬ count, iarrayl 10 ];<br />

for( count = 0; count < slzeof( larray ) / sizeof ( ±nt ) ;<br />

count++ )<br />

printf( "iarray[%d] = %d\n", count, iarrayl count ] ) ;<br />

Применение операции sizeof всюду, rjxQ это возможно, считается<br />

хорошим стилем программирования.<br />

117


5.3. Бинарные операции<br />

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

в табл. 16. Бинарные операции воздействуют на два выражения:<br />

выражение Ыпор выражение<br />

Здесь Ыпор - одна из бинарных операций, приведенных в табл.<br />

18.<br />

Табл. 18. Бинарные операции<br />

, /, % Умножение, деление, взятие остатка от деления целого на целое<br />

Сложение, вычитание<br />

Операции сдвига (обсуждаются в разделе "Поля битов и побитовые<br />

» операции")<br />

Больше, меньше<br />

Больше или равно<br />

Меньше или равно<br />

Равно<br />

Не равно<br />

&, м<br />

Поразрядные логические операции (обсуждаются в разделе "Поля битов<br />

и побитовые операции")<br />

&&, Логическое И, логическое ИЛИ<br />

Арифметические операции: "*", "/", "%", "+" и "-". Эти операции<br />

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

типа. Операция "%" означает получение остатка от деления одного<br />

г^елого числа на другое:<br />

1 % j дает значение i - ( i/j ) * j<br />

Примеры. 12 % 6 дает О, 13 % 6 дает 1, 3 % 6 дает 3 и т.д.<br />

Если арифметическая операция или операция отношения содержит<br />

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

преобразование их типов, если замена типов явно не<br />

указана. Такое преобразование производится путем "продвижения"<br />

значений операндов к "наибольшему" типу:<br />

long double (наибольший тип)<br />

double<br />

float<br />

unsigned long int<br />

long int<br />

unsigned int<br />

int<br />

unsigned. char или unsigned short int<br />

ciiax- или short int (наименьший тип)<br />

118


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

операции ("*", "/", "+", "-") или операции отношения ("", ">=",<br />

"


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

может быть или ненулевое целое значение ("истина") или нулевое<br />

значение ("ложь"). Таким образом, после выполнения присваивания<br />

X = count < slzeof( la гray ) / 2<br />

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

С помощью операций отношения можно сравнивать и указатели,<br />

что будет рассмотрено ниже в разд. 6.<br />

Логические операции (**&&" и "||"Л Приоритет и порядок выполнения<br />

логических операций также представлены в табл. 16, а их<br />

смысл описан в табл. 19, называемой таблицей истинности.<br />

Табл. 19. Таблица истинности для логических операций<br />

Операнд1 Операнд2 Операнд1 && Операнд2 Операнд1 |j Операнд2<br />

НеО<br />

НеО<br />

0<br />

0<br />

НеО<br />

0<br />

НеО<br />

0<br />

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

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

1<br />

0<br />

0<br />

0<br />

О && ( А > в )<br />

2 \\ ( ( А+В ) < 4 )<br />

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

Пример. Логическая функция задана следующей таблицей истинности<br />

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

Табл. 20.Задание<br />

Параметр 1 ( р1 )<br />

НеО<br />

НеО<br />

0<br />

0<br />

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

±nt lf( ±nt pi, ±nt p2 )<br />

// Определение функции<br />

±nt If(<br />

±nt<br />

±nt<br />

P2 )<br />

(<br />

±f( ( !pl ) && p2 )<br />

120<br />

логической функции таблицей истинности<br />

Параметр 2 ( р2 )<br />

Значение функции<br />

НеО<br />

0<br />

НеО<br />

0<br />

1<br />

1<br />

1<br />

0<br />

0<br />

0<br />

НеО<br />

0<br />

// Возвращает значение функции<br />

// Параметр функции<br />

// Параметр функции


etuxm 1,<br />

jretux-n 0;<br />

// Пример вызова функции<br />

±nt value = lf( 4, 0 ) ;<br />

5.4. Тернарная операция<br />

Тернарная операция применяется для конструирования условных<br />

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

выраж__1 выраж_2<br />

: выраж__3<br />

Данное выражение интерпретируется следующим образом.<br />

Вначале вычисляется "выраж 1". Если его значение отлично от нуля<br />

("истина"), то вычисляется "выраж_2", следующее за знаком "". Если<br />

же "выраж 1" имеет нулевое значение ("ложь"), то за значение<br />

ус^ловного выражения принимается значение "выражЗ".<br />

Пример.<br />

max = ( a>b ) а : b;<br />

// Эквивалентно<br />

±f( a>b )<br />

max = a;<br />

else<br />

max = b;<br />

5.5. Операции присваивания<br />

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

указаны в табл. 16.<br />

Простая операция присваивания используется следующим<br />

образом:<br />

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

Приведенная запись получила название выраэюение присваивания.<br />

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

"выражения", стоящего справа от операции присваивания, которое<br />

присваивается объекту '4value", то выражения присваивания могут<br />

быть вложенными:<br />

max = min = 0;<br />

121


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

min = О, в результате которого ''min'' станет равно нулю, а затем<br />

это же значение будет присвоено "max",<br />

Операцию присваивания можно записывать в сокращенной<br />

форме, что широко используется:<br />

Обычна я ф орма<br />

count = count + 2;<br />

offset = offset / 2;<br />

s = s * ( f-h2 ) ;<br />

a[ i+2 ] = a[ i+2 ] - 10;<br />

Сокращенная форма<br />

count += 2;<br />

offset /= 2;<br />

s *= f + 2;<br />

a[ i+2 ] -= 10;<br />

Операции присваивания "+=", "/=", "*="^ "%=" и подобные им<br />

(см. табл. 16) называют составными операциями присваивания.<br />

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

• вначале вычисляется значение выражения, стоящего справа от<br />

операции присваивания;<br />

• полученное значение автоматически приводится к типу объекта,<br />

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

лучше делать явно с помощью операции приведения типа);<br />

• преобразованное значение присваивается объекту, указанному<br />

слева от операции присваивания.<br />

5.6. Операция "запятая"<br />

Приоритет и порядок выполнения операций "запятая" указаны<br />

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

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

выражений. Эта операция, чаще всего, применяется в операторе<br />

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

части этого оператора:<br />

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

£ог( ±пЬ 1=1, sum = 1; 1 < 10; i++, sum += i ) ;<br />

// To же самое, в более длинной форме<br />

±nt sum = 0;<br />

for( ±nt i = 1; 1


6. УКАЗАТЕЛИ<br />

Как известно, оперативная память ЭВМ и, в частности, память<br />

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

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

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

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

6.1. Зачем нужны указатели<br />

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

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

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

написанных на языках Си/С++.<br />

Зачем эюе нуэюны указатели!<br />

1. Применение указателей во многих случаях позволяет упростить<br />

программу и/или повысить ее эффективность.<br />

2. Для управления памятью ЭВМ. Так в состав Си-библиотек<br />

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

памяти в любой момент в процессе выполнения программы. В<br />

языке же C++ для этой цели предусмотрены операторы new и delete.<br />

Эти операторы C++ управляют динамической памятью. Управление<br />

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

программ при тех же ресурсах компьютера и приспосабливать их к<br />

изменяющимся размерам данных. Как программа "узнает", где расположен<br />

вновь занятый блок памяти С помощью указателя!<br />

6.2. Указатели и их связь с массивами и строками<br />

Рассмотрим несколько примеров.<br />

// iarray - адрес iarrayf О ]<br />

int Iarrayf 6 ];<br />

// Напечатаем 16-ричный адрес iarray[ О ]<br />

print f ( "iarray = %х \л", iarray ) ;<br />

В Си имеется операция "&" взятия адреса, в результате применения<br />

которой к имени данного получается адрес этого данного.<br />

Например, вызов<br />

printf( "iarray = %х \л", &iarray[ О ] ) ;<br />

123


дает точно такой же результат, как и предыдущий вызов.<br />

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

// Ошибка: имя массива является указателем на место<br />

// расположения массива в памяти^ ему нельзя присваивать<br />

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

"Это строка \п" // Значением строки является<br />

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

// строки^ т.е. адрес символа "Э"<br />

// в памяти<br />

6.3. Определение и применение указателей<br />

Определение указателей. Синтаксис определения указателя<br />

имеет вид:<br />

описатель_класса_хранения спецификация^ типа *идентификатор;<br />

"Спецификациятипа" может относиться к любому основному<br />

или производному типу данных. Символ "*" означает "указатель на".<br />

Примеры:<br />

±nt<br />

ex t em<br />

static<br />

chctr<br />

float<br />

*iptr;<br />

*cptr;<br />

*fptr;<br />

// Определяет iptr как указатель на<br />

// целое<br />

// Объявляет cptr как указатель на<br />

// символ<br />

// Определяет fptr как указатель на<br />

// статическое данное с плавающей<br />

// точкой<br />

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

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

&, приоритет и порядок выполнения которой указаны выше в<br />

табл. 16:<br />

±xit main ( void )<br />

{<br />

int Xr // Целое<br />

*px; // Указатель на целое: пользоваться<br />

// указателем пока нельзя^ так как<br />

// его значение еще не определено<br />

X = 2;<br />

рх = &х;<br />

*рх = 3;<br />

// Теперь указателем рх пользоваться<br />

// можно: *рх равно 2<br />

// Теперь *рх равно х равно 3<br />

}<br />

return 0;


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

& состоит в том, что ее нельзя применить к объекту с классом<br />

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

в разделе "Поля битов и битовые операции").<br />

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

как только ей присвоен допустимый адрес, можно применить<br />

операцию разадресации, обозначаемую "*". Приоритет и порядок<br />

выполнения этих операций также указаны выше в табл. 16.<br />

// Пример без разадресации<br />

int main ( void )<br />

{<br />

int X, у/<br />

X = 2; у = x;<br />

return 0;<br />

}<br />

// Эквивалентный пример с разадресацией<br />

int main( void )<br />

(<br />

int X, у г *рх;<br />

X = 2; рх = &х; у =<br />

*рх;<br />

return О;<br />

}<br />

// Здесь *рх означает извлечь значение^ находящееся по адресу<br />

// рх: косвенная адресация<br />

Из приведенного выше первого примера следует также, что<br />

операцию разадресации можно использовать и в левой части выражения<br />

присваивания.<br />

Инициализация указателей. Указатели, как и объекты других<br />

типов, можно определять, совмещая определение с инициализацией.<br />

Приведем несколько примеров такого рода.<br />

// Использование указателей в инициализирующих выражениях<br />

// (рх - адрес х)<br />

Lnt<br />

X, *рх = &х;<br />

char line [ 80 ], "upline = line;<br />

// pline - адрес line[ 0 ]<br />

int *page = 0xB800;<br />

// Указатель на начало видеобуфера<br />

// цветного дисплея<br />

125


Арифметические операции над указателями. В связи с<br />

арифметическими операциями над указателями рассмотрим несколько<br />

примеров.<br />

Пример 1.<br />

±пЬ i [ 6 ] г *pi = i /<br />

// i и pi - адрес i [ О ]<br />

Будем считать, что первый байт массива / имеет адрес 10 000.<br />

Тогда для первого элемента массива (его индекс равен нулю):<br />

• адрес равен 10 000, или &/[ О ], или /, или//;<br />

• значение элемента равно *10 000, или /[ О ], или */, или */>/.<br />

Для элемента массива с индексом/ (J = О, 1,2, 3, 4, 5):<br />

• адрес равен 10000 +/ * sizeofi int ), или &/[/ ], или / +/, или pi +<br />

У;<br />

• значение элемента равно *( 10 000 +/ * sizeoJ{ int ) ), или /[/ ],<br />

или *( / +/ ), или *(// +/ ).<br />

Пример 2.<br />

#define N 3 // Строчный размер массива<br />

^define М 4 // Столбцовый размер массива<br />

int а[ N ] [ М ] , *ра == а ;<br />

Будем считать, что первый байт массива "


Пример 3.<br />

// Программа добавляет строку s к концу строки t<br />

^include // Для функций ввода-вывода<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

char t[ 24 ] == "Персональная ",<br />

"^pt = t, // pt - адрес начала t<br />

s[ ] = "ЭВМ IBM PC",<br />

"^ps = s; // ps - адрес начала s<br />

while ( *pt != '\0' ) pt-h + ;<br />

// Здесь pt - адрес завершающего символа строки t, т.е.<br />

// адрес '\0'<br />

// Посимвольно копируем строку s в "хвост" строки t, пока<br />

// не будет скопирован нуль-символ<br />

while ( ( *pt = *ps ) != '\0' )<br />

{<br />

pt++; ps++;<br />

}<br />

printf( "\n Сцепление строк (конкатенация): %s", t ) ;<br />

}<br />

return 0;<br />

Хотя на практике это требуется не часто, можно определять<br />

объекты, которые будут указателями на указатели и использовать<br />

несколько уровней косвенной адресации:<br />

// Указатель на указатель на целое, т.е. адрес адреса целого<br />

// (уровень косвенной адресации 2)<br />

int '


Пример.<br />

// Сравнение указателей: копирование одной строки в другую<br />

^include // Для функций ввода-вывода<br />

±nt main ( void ) // Возвращает О при успехе<br />

{<br />

cbstr al [ 77, // Строка -приемник<br />

*р1 = al, // р1 - адрес начала al<br />

а2 [ ] = "Пример",<br />

// Строка-источник<br />

*р2 = а2; // р2 - адрес начала а2<br />

// Копировать а2 в al<br />

while ( р2 < ( а2 + sizeof( а2 ) ) )<br />

{<br />

*р1 = *р2; р1+ + ; р2+ + ;<br />

}<br />

pr±ntf( "\п Копия: %s", al ) ;<br />

jretujrn О;<br />

6.4. Указатели на структуры<br />

Другим распространенным применением указателей является<br />

манипулирование структурами:<br />

// Объявление структуры, содержащей сведения о студенте<br />

struct STUDENT_INFO<br />

{<br />

} ;<br />

// Факультет<br />

char fak_name[ 30 ];<br />

char fio[ 30 ];// ФИО<br />

// Номер группы<br />

char group_name [ 7 ];<br />

char date[ 9 ]/// Дата поступления студента<br />

float stip; // Размер стипендии<br />

// Указатель на структуру<br />

STUDENT_INFO s_data, // Определение структуры<br />

sl_data уг // Определение еще одной структуры<br />

*ps_data; // Указатель на структуру данного<br />

// типа<br />

ps_data = &s_data; // Указатель на s_data: адрес<br />

// первого байта s_data<br />

// Доступ к элементу структуры: обе приведенные ниже формы<br />

128


эквивалентны<br />

. . . s__data . stip . . . ps_data->stip . . .<br />

Приоритеты и порядок выполнения операций "." и "->"<br />

приведены выше в табл. 16.<br />

// Определение адреса элемента структуры: обе приведенные<br />

// ниже формы эквивалентны<br />

. . . &s__data , stip . . . &ps_data->stip . . .<br />

// Операция присваивания над структурами: оба операнда должны<br />

// быть объектами с одинаковыми типами (тегами)<br />

sl_data = s_data;<br />

К указателям на структуры можно также применять арифметические<br />

операции. Например,<br />

ps__data-i- +; // Эквивалентно<br />

ps_data += s±zeof( stxract STUDENT_INFO ) ;<br />

В языке Си (но не в языке C-I-+) обычно указатель на структуру<br />

используется для передачи адреса структуры в функцию. Это позволяет<br />

получить из функции в качестве результата модифицированное<br />

значение структуры. Заметим, что для этой цели в языке C++ лучше<br />

использовать передачу структуры по ссылке.<br />

6.5. Использование указателей в<br />

качестве аргументов функций<br />

Ранее была рассмотрена программа добавления одной строки в<br />

конец другой. Слияние (конкатенация) строк является достаточно<br />

распространенной операцией. Поэтому, почему бы не превратить<br />

эту программу в функцию Приведем текст такой функции. Функцию,<br />

как типовую, поместим в отдельный файл:<br />

Файл STRCAT.CPP<br />

Добавление строки с указателем ps к концу строки с указателем<br />

pt<br />

V<br />

// Определение функции<br />

void Streat(<br />

char *pt, // Указатель на строку-приемник<br />

char *ps ) // Указатель на строку-источник<br />

{<br />

while ( *pt ) pt + + /<br />

// Здесь pt - адрес завершающего символа строки с<br />

129


указателем pt, т.е. адрес '\0'<br />

// Посимвольно копируем строку с указателем ps в "хвост"<br />

// строки с указателем pt, пока не будет скопирован<br />

// нуль -символ<br />

while ( ( ±nt ) ( *pt = *ps ) )<br />

{<br />

}<br />

return;<br />

pt++/<br />

ps++;<br />

/^<br />

*/<br />

Файл P24.CPP<br />

Главная функция,<br />

использующая функцию strcat^<br />

^Include // Для функций ввода-вывода<br />

// Прототип<br />

void strcat ( char *, char * ) ;<br />

±Tib main ( void. ) // Возвращает 0 при успехе<br />

{<br />

// Строка-приемник<br />

char t[ 24 ] = "Персональная ";<br />

strcat ( t, "ЭВМ IBM PC" ) ;<br />

printf( "\n Конкатенация строк: %s", t ) ;<br />

}<br />

return 0;<br />

В связи с рассмотренным примером напомним, что имя массива<br />

эквивалентно указателю на первый байт этого массива, а значением<br />

строки является указатель на ее первый символ. Эти указатели,<br />

как исходные данные, передаются функции strcat по значению, т.е.<br />

функции передаются их копии, и поэтому изменение копий указателей<br />

в strcat не повлияет на значения указателей в главной функции.<br />

Напомним также, что в отличие от массивов, которые всегда<br />

передаются в функцию по ссы-пке, структуры могут передаваться в<br />

функцию, как по значению, так и по ссылке (по адресу). Примеры,<br />

иллюстрирующие оба названных способа передачи структур в функцию,<br />

рассмотрены выше в подразд. 3.5. Передачу структуры в<br />

функцию по значению не следует использовать при большом размере<br />

структуры даже, если структура передается в функцию как исходное<br />

данное. Объясняется это тем, что при выполнении функции<br />

при передаче структуры по значению в функции используется копия<br />

структуры, размещаемая в дополнительной памятки.<br />

130


Рассмотрим еще несколько примеров, целью которых является<br />

показать необходимость использования в списке параметров функций<br />

языка Си адресов объектов для получения из функций результатов<br />

(сказанное относится только к языку Си).<br />

/*<br />

Файл Р25.СРР<br />

Однофайловый программный проект с двумя функциями. Пример<br />

иллюстрирует возможность появления ошибки из-за отсутствия в<br />

Си передачи в функцию параметра по адресу (ссылке)<br />

V<br />

^include // Для функций ввода-вывода<br />

void swap ( int, int ) ; // Прототип<br />

int main ( void ) // Возвраш,ает 0 при успехе<br />

{<br />

int к = 10, у = 20;<br />

swap( X, у ) ;<br />

printf( "\п X = %d, у = %d", Xr у ) ;<br />

// Будет напечатано х = 10, у = 20<br />

}<br />

геЬлт О;<br />

// Перестановка значений не будет выполнена, так как<br />

// параметры функции передаются по значению<br />

void swap (<br />

int X,<br />

int у ) // X у<br />

{<br />

}<br />

int temp = x; x = y; у = temp/<br />

jcetvLm;<br />

Обращаем внимание, что будут напечатаны значения л: = 10 и<br />

у = 20. Почему Потому, что в функцию swap "х" и 'У передаются<br />

по значению, т.е. копии "х" и "у. Следовательно, изменятся только<br />

копии, а после выхода из swap они будут потеряны.<br />

Чтобы избежать этой ошибки в языке Си (это не относится к<br />

языку C++), нужно в функцию swap передавать адреса "х" и "jv" или,<br />

что то же самое, указатели на эти объекты:<br />

Файл Р26,СРР<br />

Однофайловый программный проект с двумя функциями. Пример<br />

иллюстрирует использование в функции Си параметра-адреса<br />

объекта для получения из нее измененного значения оаъекта<br />

131


^include // Для функций ввода-вывода<br />

// Прототип<br />

void swap ( int *, int * ) ;<br />

int main ( void. ) // Возвращает 0 при успехе<br />

{<br />

}<br />

int X = 10, у = 20;<br />

swap ( &x, &y ) ; // В функцию передаются указатели<br />

print f ( "\n X = %d, у = %d"r ^r У )f<br />

// Теперь будет напечатано x = 20, у = 10<br />

re turn Of<br />

// Перестановка значений будет выполнена, так как в качестве<br />

// параметров в функцию передаются адреса объектов<br />

void swap (<br />

int<br />

*рх,<br />

int *ру ) // Указатели<br />

{<br />

}<br />

int temp = *рх; *рх = *ру; *ру = temp;<br />

return;<br />

Как уже указывалось ранее, в языке С+4-, в отличие от языка<br />

Си, для получения результатов из функции используется передача<br />

соответствующих параметров по ссылке (по адресу). При этом в<br />

функции работа производится не с копией аргумента, а с самим аргументом,<br />

что иллюстрирует приводимый ниже пример:<br />

Файл Р2 7.СРР<br />

Однофайловый программный проект с двумя функциями. Пример<br />

иллюстрирует использование в функции языка C++ параметров,<br />

передаваемых по ссылке, для получения из нее измененных значений<br />

объектов<br />

_V<br />

^include // Для функций ввода-вывода<br />

// Прототип<br />

void swap ( int &, int & ) ;<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

int X = 10, у = 20;<br />

132<br />

swap ( X, у ) ;


printf( "\n X = %d, у = %d"r K, у ) ;<br />

// Будет напечатано x = 20, у = 10<br />

}<br />

retujcn 0;<br />

// Перестановка значений будет выполнена, так как параметры в<br />

// функцию передаются по ссылке<br />

void swap (<br />

±nt &х,<br />

±nt &у )<br />

{<br />

int temp = x; X = у; у = temp;<br />

return;<br />

6.6. Указатель как значение, возвращаемое функцией<br />

Иногда удобно получать от функции в качестве возвращаемого<br />

значения указатель, в частности при операциях над строками. В качестве<br />

примера рассмотрим функцию копирования, возвращающую<br />

указатель на конец строки-приемника. Последовательный вызов<br />

этой функции обеспечит конкатенацию (сцепление) строк:<br />

Файл Р28.СРР<br />

Одно файловый программный проект с двумя функциями. Пример<br />

иллюстрирует сцепление нескольких строк с помощью функции копирования<br />

строки, использующей указатели<br />

V<br />

^include // Для функций ввода-вывода<br />

// Прототип<br />

char * strcpy( char *, char * ) ;<br />

±nt main ( void. ) // Возвращает 0 при успехе<br />

{<br />

char t[ 24 ], // Строка-приемник<br />

*pt; // Указатель на конец строки-<br />

// приемника<br />

pt = strcpy ( t, "Персональная" ) ;<br />

// pt = t + 12<br />

//pt = t + 16<br />

pt = strcpy ( pt, " ЭВМ" ) ;<br />

// pt = t + 23<br />

pt = strcpy ( pt, " IBM PC" ) ;<br />

print f ( "\n Конкатенация строк: %s \n", t ) ;<br />

133


}<br />

return О;<br />

/ • "<br />

Копирует строку-источник с начальным адресом ps в строкуприемник<br />

с начальным адресом pt и возвращает указатель на последний<br />

символ строки-приемника<br />

*/<br />

char * St г еру (<br />

char *pt^ // Указатель на строку-приемник<br />

char *ps ) // Указатель на строку-источник<br />

{<br />

while ( ( ±nt ) ( *pt = *ps ) )<br />

(<br />

pt++/ ps++;<br />

}<br />

return<br />

pt;<br />

6.7. Массивы указателей<br />

Одним из распространенных производных типов данных в Си<br />

является массив указателей. Приведем несколько иллюстрирующих<br />

примеров.<br />

// Пример 1: пять указателей на целые значения - массив<br />

// указателей<br />

Int *piarray[ 5 ] ;<br />

Данное определение трактуется следующим образом:<br />

[] - наивысший приоритет - массив;<br />

* - приоритет ниже [] - указателей;<br />

int - связывается в последнюю очередь - на целые значения<br />

// Пример 2: указатель на массив из пяти целых значений<br />

int ( *p±array ) [ 5 ];<br />

Данное определение трактуется следующим образом:<br />

О - наивысший приоритет, как у [], но выполняется слева<br />

направо - указателъ;<br />

[] - наивысший приоритет как у () , но выполняется слева<br />

направо - на массив;<br />

int - связывается с идентификатором в последнюю очередь<br />

- целых значений<br />

// Пример 3: инициализация массмва указателей на строки<br />

// символов<br />

char *stud_info[ ] =<br />

{<br />

134


}.<br />

"ФТК" ,<br />

"Иванов И. И.<br />

"1081/3",<br />

"01-09-97"<br />

В памяти эти данные будут располагаться так, как это указано<br />

на рис. 42 (значения адресов байтов - условные). Нетрудно заметить,<br />

что при таком определении каждая строка будет занимать минимально<br />

необходимую память.<br />

char *studJnfo[ ]<br />

Сегмент данных<br />

Адрес<br />

Адрес<br />

Содержимое<br />

studJnfo[ 0 ]<br />

studJnfo[ 1 ]<br />

studJnfo[ 2 ]<br />

studJnfo[ 3 ]<br />

15000 1-<br />

15004 L<br />

15016 V<br />

1—•<br />

15000<br />

15001<br />

15002<br />

15003<br />

15004<br />

15005<br />

15006<br />

15007<br />

15008<br />

15009<br />

15010<br />

15011<br />

15012<br />

15013<br />

15014<br />

15015<br />

Рис. 42. Размещение в памяти<br />

'Ф'<br />

•т<br />

'К'<br />

ЛО'<br />

'И' 1<br />

'в'<br />

'а'<br />

'н'<br />

'о'<br />

'в'<br />

'И'<br />

'И'<br />

'\0<br />

1<br />

Как организовать доступ к массиву строк<br />

// Печать сформированного в памяти массива строк<br />

^include <br />

printf( " Факультет: %s \п", stud__lnfo[ О ] ) ;<br />

printf( " ФИО: %s \п", stud_lnfo[ 1 ] ) ;<br />

// Второй символ фамилии заменим на 'р'<br />

*( stud_info[l ] + 1 ) = 'р';<br />

Аргументы командной строки. По-видимому, наиболее распространенным<br />

приложением массива указателей является передача<br />

данных программе через командную строку операционной системы.<br />

135


предположим, что имеется программа prog.exe, которая должна читать<br />

исходные данные из файла prog.dat и писать результаты своей<br />

работы в файл prog.res.<br />

Тогда программу можно запустить из среды MS DOS с помощью<br />

следующей командной строки:<br />

prog.ехе prog,dat prog.res [Enter]<br />

Компилятор языка разбивает командную строку на слова,<br />

разделенные пробелами (в данном примере prog.exe, prog.dat и<br />

prog.res). Затем передает функции main в качестве аргументов число<br />

слов в командной строке (в примере 3) и массив указателей на каждое<br />

слово (в первом элементе массива указателей находится адрес<br />

"prog.exe'\ во втором — ''prog.daf\ в третьем - "prog.res'\ а значение<br />

четвертого - NULL).<br />

Файл Р29.СРР<br />

Однофайловый программный проект с одной функцией. Пример<br />

иллюстрирует передачу в программу аргументов командной строки.<br />

Программа печатает количество слов в командной строке и<br />

сами слова<br />

*/<br />

#include // Для функций ввода-вывода<br />

Izit main ( // Возвращает О при успехе<br />

±nt argCr // ARGument Counter: число слов в<br />

// командной строке<br />

сЬа.г *argv[ ] ) / / ARGument Value: массив указателей<br />

// на аргументы командной строки<br />

{<br />

printf ( "\п Число слов в командной строке: %d \п",<br />

а где ) ;<br />

print f ( "\п Передаваемые аргументы: \п" ) ;<br />

unsigned<br />

int<br />

i = 0;<br />

while( argv[ i ] )<br />

{<br />

printf( "%s \n", argvf i + + ] ) ;<br />

}<br />

// Другой вариант<br />

print f ( "\n Переда в a емые аргументы: \n" ) ;<br />

while( *argv )<br />

{<br />

printf( "%s \n", *argv++ ) ;<br />

}<br />

jretixm 0;<br />

136


6.8. Замена типов указателей<br />

Основное применение замены типов указателей связано с<br />

устранением предупреждений в выражениях присваивания.<br />

Рассмотрим следующий иллюстрирующий пример.<br />

Файл РЗО.СРР<br />

Однофайловый программный проект с одной функцией. Пример<br />

иллюстрирует замену типов указателей и производит побайтовое<br />

копирование одной структуры в другую<br />

V<br />

struct STUDENT_INFO // Сведения о студенте<br />

{<br />

// Факультет<br />

char fak_name[ 30 ];<br />

char fio[ 20 ];// ФИО<br />

// Группа<br />

char group__name[ 7 ];<br />

char date[ 9 ];// Дата поступления в университет<br />

float stip/ // Размер стипендии<br />

} s2 = // Определение объекта: источник<br />

{<br />

"ФТК:",<br />

"Иванов И, И,<br />

"1081/4",<br />

"01-09-98",<br />

100000.Of<br />

} .<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

STUDENT_INFO<br />

si; // Приемник<br />

// Адреса структур si и s2<br />

char *psl = ( char '*' )&sl,<br />

*ps2= (char *) &s2;<br />

// Побайтовое копирование структуры s2 в si<br />

fori unsigned 1 = 0; 1 < slzeof ( STUDENT_INFO ) ; i + + )<br />

{<br />

*psl = *ps2/ psl++; ps2++;<br />

}<br />

// Вообще-то следует иметь в виду, что возможно выражение<br />

// присваивания над структурами: si = s2;<br />

return 0;<br />

137


Операция замены типа {char *) указывает компилятору, что<br />

перед применением надо интерпретировать адрес структуры &s\ как<br />

указатель на символ.<br />

Рассмотрим еще один пример, демонстрирующий мощь и изящество<br />

указателей.<br />

/*<br />

Файл Р31.СРР<br />

Однофаиловыи программный проект с одной<br />

иллюстрирует "хитросплетение ссылок". Что<br />

программа<br />

функцией.<br />

напечатает<br />

Пример<br />

данная<br />

^include // Для функций ввода-вывода<br />

chai: *с[ ] = // Массив указателей на строки<br />

{<br />

"ENTER",<br />

"МЕР",<br />

"POINT",<br />

"FIRST"<br />

};<br />

// Массив указателей на элементы массива указателей на строки<br />

char **ср[ ] = { с+3, с+2, с+1, с };<br />

char ***срр = ср;<br />

// См. рис. 43 а<br />

±nt main ( void ) // Возвращает О при успехе<br />

{<br />

printf( "\n%s"r **++срр ) ;<br />

// См. рис. 43 б<br />

printf ( "%s ", *--*-i- + cpp+3 ) ;<br />

// См. рис. 43 в<br />

printf ( "%s", *срр[-2]+3 ) ;<br />

// См. рис. 43 г<br />

printf ( "%s\n", срр[-1] [-!]+! ) ;<br />

// См. рис. 43 д<br />

}<br />

return О;<br />

Перечислим операции, используемые в программе, в порядке<br />

убывания приоритетов:<br />

"[ ]" - выполняются слева направо;<br />

"++", "—", "*" - выполняются справа налево;<br />

"+" - выполняются слева-направо.<br />

138


а)<br />

срр I<br />

У:<br />

б;<br />

срр [/)<br />

ГР| ПГ"<br />

ГТ"<br />

[т1 FR"<br />

Гз"<br />

ГТ]<br />

"Т1<br />

\0<br />

\0<br />

i<br />

О<br />

N<br />

\0 \0<br />

4и<br />

*( *( ++СРР ))<br />

2<br />

1<br />

( *( -( *( ++СРР )))) + 3<br />

1<br />

2<br />

Операции одинакового<br />

приоритера, выполняются<br />

справа налево.<br />

Будет напечатано:<br />

POINT<br />

Рис. 43. "Хитросплетение ссылок"<br />

Рассмотрим еще один пример.<br />

4<br />

5-<br />

Операции одинакового<br />

приоритета, выполняются<br />

справа налево.<br />

Будет напечатано:<br />

POINTER<br />

Файл Р32.СРР<br />

Однофайловый программный проект с одной функцией. Пример<br />

иллюстрирует работу с массивом с использованием указателей.<br />

Что напечатает данная программа<br />

V 1<br />

^include<br />

<br />

// Для функций ввода-вывода<br />

int<br />

а[ 3 ] [ 3 ] == { { 1, 2, 3 },<br />

( 4, 5, 6 Ь<br />

{ 7 , 8 , 9 } } ,<br />

139


int main ( void. )<br />

{<br />

£оз:( ±nt i = О; i < 2; i + ч- )<br />

{<br />

// Возвращает 0 при успехе<br />

pr±ntf( "\n%d %d %d"r a[i][2-i], *a[i],<br />

*(*(a + i)+±) ) ;<br />

jretux-22 0;<br />

\0<br />

( *( срр[ -2 ])) + 3<br />

I<br />

I<br />

"pl гт~<br />

"Q| гг<br />

"Tl ГЦ<br />

"NI ГЦ<br />

Tl ГТ"<br />

d)<br />

срр<br />

'W<br />

Так как a[i] означает адрес первого элемента строки / массива<br />

"(з", то *л[/] есть значение этого элемента. Аналогично, a+i эквивалентно<br />

&а[/], *{a+i) эквивалентно a[i], *(a-^i)+i эквивалентно a[i]-^i и<br />

ср<br />

с<br />

1 ^<br />

н^^^^<br />

/<br />

/ ( < | \ \<br />

Е<br />

N<br />

Т<br />

Е<br />

R<br />

\0<br />

(<br />

N<br />

Е<br />

Р<br />

\0<br />

Р<br />

О<br />

1<br />

N<br />

Т<br />

1 \о<br />

г_<br />

F<br />

1<br />

R<br />

S<br />

Т<br />

1 \о 1<br />

( срр[ -1 ]) [ -1 ]) +<br />

1 1 1<br />

1<br />

2<br />

3<br />

г.<br />

Приоритет [ ] выше, 1<br />

чем *.<br />

3<br />

ого<br />

Будет напечатано с<br />

Операции приоритета, одинаков выполняются<br />

справа налево.<br />

учетом предыдущей<br />

печати:<br />

Будет напечатано с<br />

POINTER ST<br />

учетом предыдущей<br />

печати:<br />

Р OIN" ГЕК STEF 3<br />

Прод. рис. 43<br />

1<br />

140


эквивалентно &а[/][/]. Следовательно, *(*(а+/)-н/) эквивалентно<br />

a[im.<br />

Таким образом, программа напечатает:<br />

3 11<br />

5 4 5<br />

6.9. Упражнения для самопроверки<br />

1. Что напечатает следующая программа<br />

^include<br />

<br />

±nt main ( void )<br />

{<br />

int a[ ] = { 10, 11, 12, 13, 14, 15, 16 };<br />

±nt i, *p;<br />

fo2:( p = a, i = 0; p + 2*1 = a + 1; p -= 2 )<br />

printf ( " %3d", *p ) ;<br />

printf ( "\л" ; /<br />

return 0;<br />

2. Что будет напечатано<br />

^include<br />

<br />

int main ( voxd )<br />

{<br />

int a[ ] = { 10, 11, 12, 13, 14 };<br />

int *p[] = {a, a + 1, a+2, a + 3, a-i-4},<br />

int **pp = p/<br />

pp = pp + 4;<br />

printf ( "pp-p =%3d *pp-a =%3d **pp =%3d\n", pp-p,<br />

*pp-a, **pp ) ;<br />

*pp-~;<br />

printf ( "pp-p =%3d *pp-a =%3d **pp =%3d\n", pp-p,<br />

*pp-a, **pp ) ;<br />

*++pp;<br />

printf( "pp-p ==%3d *pp-a =%3d **pp =%3d\n", pp-p,<br />

*pp-a, **pp );<br />

141


--^рр;<br />

printf( "рр-р =%3d *рр-а<br />

*рр-а, **рр ) ;<br />

=%3d **рр =%3d\n", рр-р,<br />

)<br />

jcetuim О;<br />

Ответы можно посмотреть в разд. 18.


7. ПОЛЯ БИТОВ И ПОБИТОВЫЕ ОПЕРАЦИИ<br />

7.1. Поля битов<br />

в отличие от других языков высокого уровня в языке C++, как<br />

и в ассемблерных языках, имеется развитый набор средств манипулирования<br />

битами. На рис. 44 показано представление символа экрана<br />

в видеопамяти:<br />

Номера битов в байтах<br />

7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0<br />

Цвет<br />

Цвет фона<br />

Код символа<br />

символа<br />

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 О<br />

Номера битов в слове<br />

Старший байт<br />

Младший байт<br />

Интенсивность символа<br />

Признак мерцания<br />

Рис. 44. Представление символа экрана в видеопамяти<br />

Поля битов объявляются как элементы структуры по правилу:<br />

Спецификация_типа идентификатор: размер поля<br />

В качестве "спецификации типа" задается обычно unsigned int<br />

(для шестнадцати- или тридцатидвухразрядного процессора слово<br />

из 16 или 32 битов), а размер поля - целая константа в диапазоне от<br />

О до 16 или 32. Ниже будет рассматриваться случай с 16-разрядным<br />

процессором.<br />

// Представление слова видеопамяти^ представляющего символ на<br />

// экране, в виде структуры с битовыми полями<br />

stxract WORD<br />

{<br />

unsigned int blink: 1; // Мерцание<br />

unsigned, int bkgrd: 3; // Цвет фона<br />

unsigned int in tens: 1; // Интенсивность символа<br />

unsigned int forgrd: 3; // Цвет символа<br />

unsigned int ch : 8; // Код символа<br />

} sd;<br />

// Symbol Display<br />

В структуре поля битов можно смешивать с другими элементами,<br />

не являющимися полями битов. Если это происходит, то пер-<br />

143


вый же элемент структуры, не являющийся полем битов, но следующий<br />

за битовым полем, размещается со следующего слова памяти<br />

из 16 битов. При этом в предыдущем слове часть битов может<br />

оказаться неиспользованной.<br />

// Доступ к отдельным полям битов в структуре и присваивание<br />

// им значений<br />

sd.blink = 1; // Установить мерцание<br />

// Установить цвет символа - три единичных бита<br />

sd,forgrd = 1;<br />

sd.ch = 'А'; // Буква «А» прописная<br />

WORD *psd = &sd;<br />

psd->bkgrd = 3; // Установить цвет фона<br />

Допускаются два специальных объявления поля битов. Можно<br />

объявлять безымянные поля для того, чтобы следующее поле заняло<br />

заданные биты слова:<br />

// Использование безымянных полей битов<br />

stiracb CONTROL<br />

{<br />

} ;<br />

unsigned int flagl: 1;<br />

unsigned, int s__s : 4;<br />

: 2; // Два неиспользуемых бита<br />

unsigned int flag2: 1;<br />

Можно также объявлять поля битов нулевой длины. При этом<br />

размещение следующего поля битов начнется с нового слова из 16<br />

битов и, тем самым, в текущем слове будут автоматически оставлены<br />

неиспользованные биты. Так можно разделить промежутком два<br />

поля битов.<br />

И еще повторно сделаем важное замечание. В языках Си/С+-1-<br />

не допускаются указатели на поля битов и на массивы полей битов.<br />

7.2. Побитовые операции<br />

Побитовые операции можно применять только к объектам целого<br />

и символьного типа. С их помощью можно проверять и модифицировать<br />

биты в данных целого и символьного типа. Побитовые<br />

операции перечислены в табл. 21.<br />

Операции Иу ИЛИ, исключающее ИЛИ. Эти операции действуют<br />

на каждый бит соответствующего операнда (операндов) так,<br />

как это показано в табл. 22. При этом если операнды имеют различ-<br />

144


ные типы, то они перед выполнением операции приводятся к одинаковому<br />

(старшему) типу по правилам, аналогичным указанным в<br />

подразд. 5.3. С помощью операции "&" удобно проверять и обнулять<br />

биты, операции "|" - устанавливать биты, а операции "^" - проверять<br />

несовпадение битов.<br />

&-<br />

1<br />

А<br />

«<br />

»<br />

~<br />

Табл. 21.<br />

Операция<br />

бинарная<br />

- бинарная<br />

- бинарная<br />

- бинарная<br />

- бинарная<br />

- унарная<br />

Побитовые операции<br />

Назначение<br />

И<br />

ИЛИ<br />

Исключающее ИЛИ<br />

Сдвиг влево<br />

Сдвиг вправо<br />

Дополнение до единицы<br />

j<br />

Табл. 22. Определение операций "&", "|", '<br />

Бит левого<br />

операнда (Ь/)<br />

0<br />

1<br />

0<br />

1<br />

Бит правого<br />

операнда (Ьг)<br />

0<br />

0<br />

1<br />

1<br />

Ы&Ьг<br />

0<br />

0<br />

0<br />

1<br />

Ы\Ьг<br />

0<br />

1<br />

1<br />

1<br />

Л"<br />

Ы" Ьг<br />

0<br />

1<br />

1<br />

0 i<br />

Операция \ :<br />

0001001101100011<br />

0100001100100001<br />

0101001101100011<br />

Операция ^:<br />

0001001101100011<br />

0100001100100001<br />

0101000001000010<br />

Операция &:<br />

0001001101100011<br />

0000000000000001<br />

0000000000000001<br />

Операция сдвига влево. Формат операции:<br />

операнд « выражение<br />

В результате биты "операнда" будут сдвинуты влево на число<br />

битов, задаваемое значением "выражения". Освобождающиеся справа<br />

биты заполняются нулями. Допустимые значения "выражения"<br />

изменяются в диапазоне от О до 8*5'/zeoy( операнд ).<br />

/*<br />

Файл РЗЗ. СРР<br />

Однофаиловый программный<br />

иляюстрируе т действие операции<br />

V<br />

проект с<br />

сдвига<br />

одной<br />

влево<br />

функцие й.<br />

(ВС+ + 3. 1)<br />

Пример<br />

iinclude<br />

<br />

// Для функций ввода-вывода<br />

±nt main ( void. )<br />

{<br />

±nt к == 1;<br />

// Возвращает О при успехе<br />

145


print f( "\n%dr %d, %d, %d, %d, %d, %d, %d", x«I, x«2,<br />

x«3, x«0, x«30r х«-327б8, x«-32161,<br />

x«-32766 ) ;<br />

}<br />

те turn G;<br />

// Будет напечатано 2, 4, 8, 1, О, 1, 2, 4<br />

Нетрудно заметить, что сдвиг битов "операнда" влево на одну<br />

позицию эквивалентен умножению на два. Заметим также, что отрицательные<br />

значения "выражения" или значения, равные или превышающие<br />

число битов в операнде, в общем случае недопустимы и<br />

дают неопределенное значение, зависящее от реализации. Приведенный<br />

выше пример иллюстрирует особенности реализации языка<br />

Borland C++ версии 3.1 для подобной ситуации.<br />

Операция сдвига вправо. Формат операции:<br />

операнд » выражение<br />

В результате биты "операнда" будут сдвинуты вправо на число<br />

битов, задаваемое значением "выражения". Допустимые значения<br />

"выражения" изменяются в диапазоне от О до S'^sizeofl операнд ).<br />

Операция сдвига вправо выполняется аналогично сдвигу влево, но<br />

отличие состоит в способе заполнения освобождающихся битов:<br />

• если "операнд" беззнаковый, то освобождающиеся биты заполняются<br />

нулями;<br />

• иначе, если "операнд" знаковый, то освобождающиеся биты заполняются<br />

знаковым разрядом (нулями для положительного и<br />

единицами для отрицательного "операнда").<br />

Файл Р34.СРР<br />

Однофайловый программный проект с одной функцией. Пример<br />

иллюстрирует действие операции сдвига вправо (ВС++ 3.1)<br />

^include // Для функций ввода-вывода<br />

int main ( void ) // Возвращает О при успехе<br />

{<br />

int X = 64;<br />

printf( "\n%d, %d, %dr %d, %dr %dr %d, %d"r x»l, x»2,<br />

x»3, x»Or x»30r x»-32768r x»-32767,<br />

x>>-32 766 ) ;<br />

return 0;<br />

146


}<br />

// Будет напечатано 32, 16, 8, 64, О, 64, 32, 16<br />

Аналогично предыдущему случаю заметим, что сдвиг битов<br />

"операнда" вправо на одну позицию эквивалентен делению на два.<br />

Обратите внимание также, что отрицательные значения "выражения"<br />

или значения, равные или превышающие число битов в операнде,<br />

в общем случае недопустимы и дают неопределенное значение,<br />

зависящее от реализации. Приведенный выше пример показывает<br />

особенности реализации языка Borland С+-ь версии 3.1 для подобной<br />

ситуации.<br />

Дополнение до единицы. Операция изменяет значения всех<br />

битов операнда на противоположные значения:<br />

-выражение<br />

// Рассмотрим пример<br />

char с, d;<br />

с = с & 'OxlF'; // Обнулить (маскировать) старший<br />

/ / бит<br />

с = с & (- ' 0x7F) '; // Маскировать все биты, кроме<br />

// старшего<br />

Операции присваивания. Если бинарные побитовые операции<br />

используются в операторах присваивания вида<br />

value = value побитовая_операция выражение<br />

то можно использовать сокращенную запись (табл. 23):<br />

value побитовая_операция= выражение<br />

// Обычная форма Сокраш,енная форма<br />

с = с & '0x7F'; с &= '0x7F'/<br />

Приоритеты побитовых операций и порядок их выполнения<br />

рассмотрены в табл. 16.<br />

&=<br />

1=<br />

Л=:<br />

~=<br />

«=<br />

»=<br />

Табл. 23. Сокращенная<br />

Операция<br />

запись побитового присваивания<br />

Назначение<br />

Операция "И" и присваивание<br />

Операция "ИЛИ" и присваивание<br />

Операция "^" и присваивание<br />

Дополнение до единицы и присваивание<br />

Сдвиг влево и присваивание<br />

Сдвиг вправо и присваивание<br />

i


8. ДИНАМИЧЕСКОЕ РАЗМЕЩЕНИЕ ОБЪЕКТОВ В<br />

ПАМЯТИ.<br />

ОДНОНАПРАВЛЕННЫЙ НЕКОЛЬЦЕВОЙ<br />

ЛИНЕЙНЫЙ СПИСОК И ОПЕРАЦИИ С НИМ<br />

8.1. Понятие об однонаправленном линейном списке.<br />

Динамическое размещение объектов в памяти<br />

Сущность однонаправленного линейного списка (ЛС), предназначенного<br />

для хранения символов, представлена на рис. 45.<br />

Т'<br />

'е'<br />

'к'<br />

'с'<br />

'т'<br />

W<br />

W<br />

W<br />

W<br />

NULL<br />

I start<br />

Рис. 45. Однонаправленный линейный список<br />

Каждый элемент ЛС содержит две части (два поля):<br />

• основное поле, в котором хранится содержательная информация<br />

(в нашем примере - символ);<br />

• вспомогательное поле, в котором хранится указатель на следующий<br />

элемент линейного списка.<br />

Список содержит два элемента, которые являются особенными,<br />

отличными от других. Это первый (головной) элемент ЛС - его<br />

особенность состоит в том, что он снабжен указателем (на рис. 45<br />

таким указателем является Start), Без этого указателя нельзя работать<br />

с линейным списком. Последний элемент ЛС также является<br />

особенным, так как в его вспомогательном поле хранится указатель<br />

NULL, означающий, что следующего элемента нет.<br />

Из сказанного следует, что элемент ЛС в терминах языка С++<br />

можно определить в виде структуры:<br />

stxract EL ЕМ<br />

{<br />

char<br />

ELEM<br />

} ;<br />

ELEM<br />

ch;<br />

*neKt;<br />

*pe;<br />

// Основное поле: символ<br />

// Указатель на следующий элемент<br />

// Указатель на структуру<br />

148


• " /<br />

в языках Си/С++ имеется возможность динамического размещения<br />

некоторого объекта в оперативной памяти (функция malloc{ )<br />

и др. в библиотеке языка Си, оператор new языка СН-+) или освобождения<br />

занятой ранее динамической памяти (функция free{ ) в библиотеке<br />

языка Си, оператор delete языка C++):<br />

Пример размещения элемента<br />

памяти. Среда языка Си<br />

линейного списка в динамической<br />

^include /* Для функции та Нос */<br />

^include /* Для функций ввода-вывода */<br />

^include /'*' Для функции exit */<br />

ре = ( struct ELEM * ) malloc ( sizGof ( struct ELEM ) ) ;<br />

±f( pe == NULL ) /* Обработка результата размещения"^/<br />

{<br />

printf ( "\n Размещение элемента в динамической памяти не"<br />

" выполнено " ) ;<br />

exit( 1 ) ;<br />

}<br />

. . . pe->ch ... /'*' Значение поля данных структуры */<br />

. . . pe->next ... /* Указатель на следующий элемент '^/<br />

/* линейного списка */<br />

Пример размещения элемента линейного списка в динамической<br />

памяти. Среда языка C++<br />

*/<br />

^include // Для функций ввода-вывода<br />

#include // Для функции exit<br />

ре = new ELEM;<br />

±f( ре == NULL ) // Обработка результата размещения<br />

{<br />

printf ( "\п Размещение элемента в динамической памяти "<br />

"не выполнено " ) ;<br />

exit ( 1 )/<br />

}<br />

... pe->ch ... // Значение поля данных структуры<br />

... pe->next ... // Указатель на следующий элемент<br />

// линейного списка<br />

/*<br />

Пример освобождения динамической памяти^<br />

линейного списка. Среда языка Си<br />

занятой<br />

элементом<br />

finclude /* Для функции free */<br />

149


±f( ре != NULL )<br />

{<br />

free ре; ре == NULL;<br />

I<br />

/*<br />

Пример освобождения динамической памяти.<br />

линейного списка. Среда языка С++<br />

*/<br />

занятой элементом<br />

±f( ре != NULL )<br />

{<br />

}<br />

del&te ре; ре = NULL;<br />

Рассмотрим еще один практически значимый пример размещения<br />

в динамической памяти двумерного массива (матрицы) и освобождения<br />

занятой памяти.<br />

/*<br />

*/<br />

Файл DynMem, срр<br />

Демонстра ция работы с ма трицеи в динамической памяти<br />

^include // Для ввода-вывода<br />

^include // Для exit ( )<br />

// Ввод размеров матрицы, размещение матрицы в динамической<br />

// памяти и заполнение ее *<br />

srold ReadMatrix (<br />

chetr *pFileInp,// Указатель на файл данных<br />

unsigned &RowSize, // Строчный размер<br />

unsigned &ColSize, // Столбцовый размер<br />

int **&рМх ) // Указатель на матрицу<br />

{<br />

// Указатель на структуру со сведениями о файле данных<br />

FILE *pStructInp;<br />

// Открытие файла данных для чтения<br />

if( ( pStructlnp = fopeni pFilelnp, "г" ) ) == NULL )<br />

{<br />

printf( "\n Ошибка 10, Файл %s для чтения не "<br />

"открыт \п", pFilelnp );<br />

jretuarn/<br />

150<br />

// Чтение размеров матрицы<br />

int retcode = fscanf( pStructlnp, "%u %u",<br />

ScRowSize, &ColSize ) ;<br />

if( retcode != 2 )<br />

{<br />

printf(<br />

"\n Ошибка 20. Ошибка чтения размеров"


}<br />

return;<br />

" матрицы \п" ) /<br />

// Размещение в ДП массива указателей на строки матрицы<br />

рМх = new ±nt * [ RowSize ];<br />

±£( !рМх )<br />

}<br />

{<br />

printf ( "\п Ошибка 30. Ошибка размещения в ДП "<br />

"массива указателей на строки матрицы \п" )/<br />

return;<br />

// Размещение в ДП строк матрицы<br />

£ог( unsigned int 1=0; l


}<br />

// Здесь проверяем г, с, рМх<br />

// . . .<br />

// Освобождение динамической памяти, занятой матрицей<br />

// Освобождение ДП, занятой строками матрицы<br />

for( unsigned ±nt i=0; Кг; i++ )<br />

{<br />

delete [ ] pMx[ i ];<br />

}<br />

// Освобождение ДП, занятой массивом указателей на строки<br />

// матрицы<br />

delete [ ] рМх;<br />

ret-arn 0;<br />

Для работы с ЛС используются следующие основные операции:<br />

• инициализация;<br />

• добавление элемента в начало ЛС;<br />

• добавление элемента в конец ЛС;<br />

• создание ЛС таким образом, чтобы первый занесенный элемент<br />

оказался в начале списка;<br />

• создание ЛС таким образом, чтобы последний занесенный элемент<br />

оказался в начале списка;<br />

• удаление элемента из начала ЛС;<br />

• удаление элемента из конца JIC\<br />

• разрушение ЛС с освобождением занятой им динамической памяти;<br />

• печать содержимого ЛС;<br />

• добавление или удаление элемента после каждого элемента ЛС,<br />

содержащего заданное значение;<br />

• добавление или удаление элемента перед каждым элементом ЛС,<br />

содержащим заданное значение.<br />

Реализация перечисленных операций неоднозначна. Рассмотрим<br />

один из возможных вариантов программирования операций над<br />

ЛС, который представляется более удачным.<br />

8.2. Инициализация линейного списка<br />

Эта операция является тривиальной и заключается в присваивании<br />

указателю на начало линейного списка значения NULL^ означающего,<br />

что ЛС пуст. Инициализация списка выполняется самой<br />

152


первой. Прототип функции Initls^ выполняющей инициализацию<br />

ЛС, ее определение и пример вызова приведены ниже. На данном<br />

этапе рекомендуем из примера рассмотреть только часть программы,<br />

предшествующую главной функции, и все, что относится к<br />

функции Initls, Остальной материал будет рассмотрен далее.<br />

Файл LS.CPP<br />

Работа с динамической памятью. Однонаправленный линейный<br />

список и операции с ним<br />

^include // Для функций ввода-вывода<br />

^include // Для функции exit<br />

struct EL // Структура для элемента списка<br />

{<br />

} ;<br />

char ch; // Данные (символ)<br />

EL *next; // Указатель на следующий элемент<br />

// Указатель на начало списка: класс хранения внешний<br />

// (область действия и время жизни - вся программа),<br />

// используется во всех функциях программного проекта и<br />

// через список параметров функций не передается<br />

EL * start;<br />

// Прототипы функций<br />

void Init_ls ( void. ) ;<br />

void Dest^^ls ( void ) ;<br />

void Add_end ( cha.r с ) ;<br />

void Add__beg ( cbstr с ) ;<br />

void Del_end ( void ) ;<br />

void Del_beg ( void ) ;<br />

void Create_end ( void ) ;<br />

void Create__beg( void ) ;<br />

void Print_ls ( void ) ;<br />

void After_Add( char find,, сЪа.г add ) ;<br />

void Before__Add ( char find, char add ) ;<br />

void After_Del ( char find ) ;<br />

void Before_Del ( char find ) ;<br />

int main( void ) // Возвращает 0 при успехе<br />

{<br />

Init_ls ( ) ; // Инициализация списка<br />

// Заполнение линейного списка символами из файла LS.DAT:<br />

// первый прочитанный символ - в начале списка<br />

Сгеаte_beg( ) ;<br />

Print__ls ( ) ; // Вывод содержимого списка на экран<br />

Dest__ls ( ) ; // Разрушение списка<br />

After_Del ( '3'); // Удаление после '3'<br />

Print Is( ) ;<br />

153


Add end( 'С ) , // Добавление в конец<br />

// элемента 'С'<br />

списка<br />

After_Del ( '3'); // Удаление после '3'<br />

Pr±nt_ls ( ) ;<br />

Dest_ls ( ) ; // Разрушение списка<br />

// Заполнение линейного списка символами из файла LS.DAT:<br />

// последний прочитанный символ - в конце списка<br />

Create_end( ) ;<br />

Print Is( ) ;<br />

Dest_ls ( ) ;<br />

Add__end ( 'C<br />

Add_end (<br />

'D'<br />

Add beg(<br />

Add_beg (<br />

Print Is (<br />

Del_end( ) ;<br />

^B^<br />

'A'<br />

r ) ;<br />

) ;<br />

) ;<br />

) ;<br />

) ;<br />

//<br />

//<br />

//<br />

//<br />

//<br />

//<br />

//<br />

Разрушение списка<br />

Добавление в конец списка<br />

элемента 'С'<br />

Добавление в конец списка<br />

элемента 'D'<br />

Добавление в начало элемента<br />

Добавление в начало элемента<br />

// Удаление последнего элемента<br />

// списка<br />

Del_beg( ) ;<br />

// Удаление первого элемента спи<br />

Print_ls ( ) ;<br />

Dest_ls ( ) /<br />

// Разрушение списка<br />

// Заполнение списка пятью двойками<br />

for( Int i = 1; i


void Init_ls ( void )<br />

{<br />

start = NULL; // Вначале список пуст<br />

}<br />

rebvLrn;<br />

// Paзрушение списка<br />

void Dest_ls ( void )<br />

{<br />

if( start == NULL )<br />

{<br />

printf( "\n Список пуст. Удалять нечего" ) ;<br />

retuxm/<br />

}<br />

while ( start /- NULL )<br />

Del__beg ( ) ; // Удаление первого элемента списка<br />

}<br />

return/<br />

// Добавление элемента в конец списка<br />

void Add_end (<br />

char с ) // Данные добавляемого элемента<br />

{<br />

// Указатель на новый (добавляемый) элемент списка<br />

EL<br />

*temp,<br />

*сиг; // Указатель на текущий элемент<br />

temp = new EL; // 1: динамическое размещение<br />

// элемента<br />

if( temp == NULL )<br />

{<br />

printf( "\n Элемент списка не размещен" ) ;<br />

exit ( 1 ) ;<br />

}<br />

temp->ch = с; // 2: занесение данного<br />

temp->next = NULL; // 3: новый элемент является<br />

// последним<br />

if( start == NULL ) // Новый список (пустой)<br />

start = temp; // 4а: указатель на начало списка<br />

else<br />

{<br />

// 46: проходим весь список от начала, пока текущий<br />

// элемент не станет последним<br />

сиг = start;<br />

while( cur->next != NULL )<br />

// Продвижение по списку<br />

сиг = cur->next;<br />

// 4в: ссылка последнего элемента на новый,<br />

// добавляемый в конец списка<br />

155


}<br />

}<br />

return/<br />

cur~>next = temp;<br />

// Добавление элемента в начало списка<br />

void Add_beg(<br />

char с ) // Данные добавляемого элемента<br />

{<br />

}<br />

// Указатель на новый (добавляемый) элемент списка<br />

EL<br />

*temp;<br />

temp = new EL; // 1: динамическое размещение<br />

// элемента<br />

±f( temp == NULL )<br />

(<br />

printf( "\n Элемент списка не размещен" ) ;<br />

exit ( 2 ) ;<br />

}<br />

temp->ch = с/ // 2: занесение данного<br />

// 3: новый элемент ссылается на начало списка<br />

temp->next = start;<br />

start = temp; // 4: новый элемент становится<br />

/ / первым<br />

return;<br />

// Заполнение линейного списка символами из файла LS.DAT:<br />

// первый прочитанный символ - в начале списка<br />

void Create^beg( void )<br />

{<br />

// Данное для элемента, добавляемого в конец списка<br />

сЪаг с;<br />

// Указатель на структуру со сведениями о файле для<br />

// чтения<br />

FILE<br />

*f__in;<br />

156<br />

// Открываем файл для чтения<br />

±£( ( f_in = fopeni "Is.dat", "г" ) ) == NULL )<br />

{<br />

printf( "\n Файл Is.dat для чтения не открыт " ) ;<br />

exit ( 3 ) ;<br />

}<br />

// Создаем список<br />

while ( ( с = ( сНаг )fgetc( f_in ) ) != EOF )<br />

{<br />

Add_end( с ) ;<br />

}<br />

// Закрываем файл<br />

i£( ( fclose( f in ) ) == EOF )


}<br />

{<br />

}<br />

return;<br />

printf ( "\n Файл Is.dat не закрыт " ) ;<br />

exi t(4);<br />

// Заполнение линейного списка символами из файла LS.DAT:<br />

// последний прочитанный символ - в начале списка<br />

void Create_end ( void, )<br />

{<br />

// Данное для элемента^ добавляемого в конец списка<br />

сЬа.г с;<br />

// Указатель на структуру со сведениями о файле для<br />

// чтения<br />

FILE<br />

*f_in;<br />

}<br />

// Открываем файл для чтения<br />

±£( ( f__in = fopen( "Is.dat", "г" ; ; == NULL )<br />

{<br />

}<br />

print f ( "\n Файл Is.dat для чтения не открыт " ) ;<br />

exit(5);<br />

// Создаем список<br />

while ( ( с = ( char )fgetc( f_in ) ) != EOF )<br />

{<br />

Add_beg ( с ) ;<br />

}<br />

// Закрываем файл<br />

±f( ( fclose( f_in ) ) == EOF )<br />

{<br />

}<br />

rebum;<br />

printf( "\n Файл Is.dat не закрыт " ) ;<br />

exit(6);<br />

// Удаление последнего элемента списка<br />

void Del_end ( void )<br />

{<br />

EL *prev, // Указатель на предпоследний<br />

// элемент<br />

*end/ // Указатель на последний элемент<br />

i£( start -= NULL )<br />

{<br />

printf ( "\n Список пуст. Удалять нечего" ) ; retjim;<br />

}<br />

// 1: поиск последнего (и предпоследнего) элементов<br />

157


}<br />

prev = NULL; end = start;<br />

while( end->next /= NULL )<br />

{<br />

prev = end;<br />

end = end->next; // Продвижение no списку<br />

}<br />

d&lete end; // 2: удаление последнего элемента<br />

±f( prev != NULL )<br />

{<br />

// 3: бывший предпоследний элемент становится<br />

// последним<br />

prev~>next = NULL;<br />

}<br />

else<br />

(<br />

start = NULL; // 3: в списке был один элемент<br />

}<br />

return;<br />

// Удаление первого элемента списка<br />

void Del_beg ( void. )<br />

{<br />

}<br />

EL *del; // Указатель на удаляемый элемент<br />

if( start == NULL )<br />

{<br />

printf( "\n Список пуст. Удалять нечего" ) ;<br />

return;<br />

}<br />

// 1: подготовка первого элемента для удаления<br />

del = start;<br />

start = del->next; // 2: start сдвигается на второй<br />

// элемент<br />

delete del; // 1: удаление первого элемента<br />

return;<br />

// Печать содержимого списка на экран<br />

void Print_ls( void )<br />

{<br />

158<br />

EL *prn; // Указатель на печатаемый элемент<br />

i£( start == NULL )<br />

{<br />

printf( "\n Список пуст. Распечатывать нечего" ) ;<br />

return;<br />

}<br />

prn = start; // Указатель на начало списка<br />

print f ( "\п" ) ;


}<br />

while ( prn != NULL ) // До конца списка<br />

{<br />

}<br />

return/<br />

// Печать данных (символа) элемента<br />

printf( "%с", prn->ch ) ;<br />

prn = prn~>next; // Продвижение по списку<br />

// Добавление элемента add после элемента find<br />

void After_Add ( char find, char add )<br />

{<br />

EL *temp, // Указатель на добавляемый элемент<br />

*cur; // Указатель на текущий элемент<br />

if( start == NULL )<br />

{<br />

printf( "\n Список пуст. Нельзя найти нужный "<br />

"элемент " )/<br />

return/<br />

}<br />

// Поиск элементов, содержащих символ find, с добавлением<br />

// после них элемента с символом add<br />

сиг = start/<br />

while ( cur != NULL )<br />

{<br />

if( cur->ch == find )<br />

{<br />

// Нужный элемент найден (он является текущим)<br />

// 1: динамическое размещение элемента<br />

temp = new EL/<br />

if( temp == NULL )<br />

{<br />

printf( "\n Элемент списка не размещен" )/<br />

exit ( 7 ) /<br />

}<br />

return/<br />

}<br />

// 2: занесение данного<br />

temp->ch = add/<br />

// 3: ссылка на элемент, который стоял за текущим<br />

temp->next = cur->next/<br />

// 4: текущий элемент указывает на новый<br />

cur->next = temp/<br />

// 5: новый элемент становится текущим<br />

сиг = temp/<br />

)<br />

сиг = cur->next/ // Продвижение по списку<br />

/у i^:h*iir-^-k-^irTlf*^Th-^-^9r-^ir*i^-k*-*c***Thilc-^*-ki^-k-k*-!h***T^<br />

159


Добавление элемента add перед элементом find<br />

void Before_Add ( char find, cba.r add )<br />

{<br />

EL *temp, // Указатель на добавляемый элемент<br />

*cur, // Указатель на текущий элемент<br />

"^prev; // Указатель на элемент стоящий<br />

// перед текущим)<br />

}<br />

±f( start =- NULL )<br />

{<br />

printf( "\n Список пуст. Нельзя найти нужный "<br />

"элемент " ) ;<br />

jretujrn/<br />

;<br />

// Поиск элементов, содержащих символ find,<br />

// перед ними элемента с символом add<br />

с добавлением<br />

сиг = start;<br />

while ( cur /- NULL )<br />

{<br />

// Нужный элемент найден (он является текущим)<br />

±f( cur->ch == find )<br />

{<br />

// 1: динамическое размещение элемента<br />

temp = new EL;<br />

if( temp == NULL )<br />

{<br />

printf( "\n Элемент списка не размещен" ) ;<br />

exit ( 8 ) ;<br />

}<br />

return;<br />

}<br />

// 2: занесение данных<br />

temp->ch = add;<br />

// 3: новый элемент указывает на элемент с<br />

// символом find<br />

temp->next = cur;<br />

// 4: если элемент с символом find был первым,<br />

// то start смещается влево (на новый элемент)<br />

±£( сиг == start )<br />

start = temp;<br />

else<br />

// 4: элемент, стоящий перед сиг указывает на<br />

// новый<br />

prev->next = temp;<br />

}<br />

prev = cur; // Продвижение текущего и<br />

// предыдущего элементов<br />

сиг = cur->next; // по списку<br />

// Удаление элемента после элемента find<br />

void After Del( char find )<br />

160


}<br />

EL *del, // Указатель на удаляемый элемент<br />

*сиг; // Указатель на текущий элемент<br />

±£( start == NULL )<br />

{<br />

}<br />

printf( "\n Список пуст. Нельзя найти нужный "<br />

"элемент " ) ;<br />

jr&bVLJCZi;<br />

±f( start->next == NULL )<br />

{<br />

printf ( "\n В списке только один элемент. Нельзя "<br />

"выполнить данную операцию" ) ;<br />

return/<br />

}<br />

// Поиск элементов,, содержащих символ find,, с удалением<br />

// элементов г следуюшр1Х за найденными<br />

сиг = start;<br />

do<br />

{<br />

'±f( cur->ch == find )<br />

( // Нужный элемент найден (он является текущем)<br />

// 1 : указатель на элемент для удаления<br />

del = cur->next;<br />

// 2: связь текущего элемента с элементом,<br />

// следуюш;им за удаляемым<br />

cur->next = del->next;<br />

delete del; // 3: удаление элемента<br />

// 4: является ли теперь текущей элемент<br />

// последним Если "да" - выход из цикла и<br />

// функции<br />

±f( cur->next == NULL )<br />

геЬит;<br />

}<br />

cur = cur->next; // Продвижение по списку<br />

} while( cur->next != NULL ) ;<br />

return;<br />

// Удаление элемента перед элементом find<br />

void. Before_Del ( chstr find )<br />

{<br />

// Указатель на предпредыдуш:ий элемент по отношению к<br />

// заданному<br />

EL<br />

'*'pprev,<br />

'*'dei, // Указатель на удаляемый элемент<br />

*сиг; // Указатель на текущий элемент<br />

lf( start == NULL )<br />

{<br />

161


printf( "\n Список пуст. Нельзя найти нужный "<br />

"элемент " ) ;<br />

}<br />

}<br />

±£( start->next == NULL )<br />

{<br />

printf( "\n в списке только один элемент. Нельзя "<br />

"выполнить данную операцию" ) ;<br />

return/<br />

}<br />

// Поиск элементов, содержащих символ find, с удалением<br />

// элементов, предшествующих найденным<br />

pprev = NULL/ cur = start->next/<br />

while( cur != NULL )<br />

(<br />

±f( cur->ch == find )<br />

{ // Нужный элемент найден (он является<br />

// текущим)<br />

// Найденный элемент является вторым в ЛС<br />

±f( pprev == NULL )<br />

{<br />

// 1: будем удалять головной элемент<br />

del = start/<br />

// 2: первым элементом списка теперь будет<br />

// бывший второй<br />

start = start->next/<br />

}<br />

else<br />

}<br />

retujm/<br />

{<br />

// 1: указатель на удаляемый элемент<br />

del = pprev->next/<br />

// 2: связь предпредыдущего и текущего<br />

// элементов<br />

pprev->next = del->next/<br />

}<br />

delete del/ // Удаление элемента<br />

}<br />

else<br />

{ // Продвижение указателя pprev требуется, если на<br />

// очередном шаге не было удаления элемента<br />

±f( pprev == NULL )<br />

pprev = start/<br />

else<br />

pprev = pprev->next /<br />

}<br />

cur = cur->next/ // Продвижение этого указателя<br />

// требуется всегда<br />

Файл LS.DAT, из которого программа читает исходные данные,<br />

имеет следующий вид:<br />

162


1234567890<br />

Результаты выполнения программы выдаются на экран и имеют<br />

вид:<br />

1234567890<br />

Список пуст. Нельзя найти нужный элемент<br />

Список пуст. Распечатывать нечего<br />

В списке только один элемент. Нельзя выполнить данную операцию<br />

С<br />

0987654321<br />

ABCD<br />

ВС<br />

22222<br />

1212121212<br />

123123123123123<br />

11231123112311231123<br />

1122311223112231122311223<br />

11222231122223112222311222231122223<br />

1122223311222233112222331122223311222233<br />

11222231122223112222311222231122223<br />

12222122221222212222122223<br />

8.3. Добавление элемента в начало списка<br />

Эта операция имеет не только самостоятельное значение, но и<br />

может быть использована для создания линейного списка, когда последний<br />

занесенный элемент будет находиться в начале ЛС.<br />

Смысл операции иллюстрирует рис. 46. Прототип функции<br />

Add beg, выполняющей добавление элемента в начало списка, ее<br />

определение и пример вызова содержатся в вышеприведенном примере.<br />

На данном этапе также рекомендуем из примера рассмотреть<br />

только часть программы, которая относится к функции Addjbeg. Остальной<br />

материал рассмотрим далее, применяя указанный подход.<br />

8.4. Добавление элемента в конец списка<br />

Эта операция, как и предыдущая, имеет, как самостоятельное<br />

значение, так и может быть использована для создания линейного<br />

списка, когда первый занесенный элемент будет находиться в начале<br />

ЛС.<br />

Смысл операции иллюстрирует рис. 47. Прототип функции<br />

Add_end, выполняющей добавление элемента в конец списка, ее определение<br />

и пример вызова содержатся в примере, приведенном ранее.<br />

163


До выполнения операции<br />

Общий случай<br />

Особый случай<br />

• h> • W NULL<br />

Start = NULL;<br />

t Start<br />

После выполнения операции<br />

2: занесение данного «с» 2 занесение данного «с»<br />

t<br />

1: динамическое размещение<br />

temp = new EL;<br />

t<br />

4: Start = temp;<br />

•—Н NULL NULL 3: temp->next =<br />

Start;<br />

t<br />

1. динамическое размещение<br />

temp = new EL;<br />

t<br />

4: Start = temp;<br />

Рис. 46. Добавление элемента в начало линейного списка<br />

8.5. Создание ЛС с первым занесенным<br />

элементом в начале<br />

Для создания ЛС с первым занесенным элементом в начале<br />

списка достаточно в теле цикла вызывать функцию занесения одного<br />

элемента в конец списка с аргументом, представляющим собой<br />

очередное прочитанное число. Прототип функции Create_beg, выполняющей<br />

создание списка с первым прочитанным элементом в его<br />

начале, определение функции и пример вызова содержатся в примере,<br />

приведенном выше.<br />

8.6. Создание ЛС с первым занесенным элементом<br />

в конце списка<br />

Эта операция аналогична предыдущей. Для создания ЛС с первым<br />

занесенным элементом в конце списка достаточно в теле цикла<br />

вызывать функцию занесения одного элемента в начало списка с аргументом,<br />

представляющим собой очередное прочитанное число.<br />

Прототип функции Create end, выполняющей создание списка с<br />

первым прочитанным элементом в его конце, определение функции<br />

и пример вызова содержатся в том же примере.<br />

164


До выполнения операции<br />

Общий случай<br />

Особый случай<br />

•—h><br />

NULL<br />

Start = NULL,<br />

t Start<br />

После выполнения операции<br />

2: 2: занесение данного «о<br />

t Start<br />

# •^<br />

t<br />

4в:<br />

t<br />

NULL NULL 3: temp->next =<br />

NULL;<br />

t<br />

1- 1-<br />

динамическое размещение temp = new EL;<br />

46: cur<br />

4B: cur->next = temp; 4a: Start = temp;<br />

Рис. 47. Добавление элемента в конец списка<br />

t<br />

8.7. Удаление элемента из начала списка<br />

Данная операция, также как и операции добавления элемента в<br />

начало или конец линейного списка, позволяет решить две задачи:<br />

• удаление одного элемента из начала ЛС;<br />

• разрушение линейного списка с освобождением занятой им динамической<br />

памяти путем циклического выполнения операции<br />

удаления элемента из начала списка.<br />

Реализация операции, представленная в графической форме,<br />

дана на рис. 48. Прототип функции Del beg, выполняющей удаление<br />

первого элемента списка, ее определение и пример вызова содержатся<br />

в программном проекте, приведенном в подразд. 8.1.<br />

165


• • ^ NULL<br />

До выполнения операции<br />

Общий случай<br />

Особые случаи:<br />

а) в ЛС один элемент б)<br />

•——•!<br />

•—^><br />

NULL<br />

Start = NULL;<br />

I<br />

I<br />

Start t Start<br />

1: del = Start;<br />

t<br />

2: Start = del->next;<br />

3: delete del;<br />

После выполнения операции<br />

• ^ NULL функции<br />

Вывод сообщения<br />

и возврат из<br />

t<br />

1: del = Start;<br />

t<br />

2: Start = del->next = NULL;<br />

3- delete del;<br />

Рис. 48. Удаление элемента из начала списка<br />

8.8. Удаление элемента из конца списка<br />

Реализация операции, представленная в графической форме,<br />

дана на рис. 49. Прототип функции Delend, выполняющей удаление<br />

последнего элемента списка, ее определение и пример вызова содержатся<br />

в программном проекте, приведенном в подразд. 8.1.<br />

8.9. Разрушение ЛС с освобождением занятой<br />

им динамической памяти<br />

Разрушение линейного списка с освобождением занятой им<br />

динамической памяти может быть выполнено путем циклического<br />

выполнения операции удаления элемента из начала списка.<br />

Прототип функции Destls, выполняющей разрушение списка,<br />

ее определение и пример вызова содержатся в программном проекте,<br />

приведенном в подразд. 8.1.<br />

166


До выполнения операции<br />

Общий случай<br />

Особые случаи:<br />

а) в ЛС один элемент б)<br />

•—и<br />

• — — •<br />

NULL 1<br />

I NULL<br />

Start = NULL;<br />

t Start<br />

t<br />

Start<br />

После выполнения операции<br />

L__<br />

t<br />

start<br />

NULL<br />

t<br />

1: prev<br />

t<br />

end<br />

t<br />

1: prev = NULL;<br />

end<br />

2: delete end; 2: delete end;<br />

3 prev->next = NULL; 3: Start = NULL;<br />

Рис. 49. Разрушение линейного списка<br />

Вывод сообщения<br />

и возврат из<br />

функции<br />

8.10. Печать содержимого ЛС<br />

Печать содержимого линейного списка может быть выполнена<br />

путем циклического выполнения печати содержимого текущего<br />

элемента списка и продвижения по списку от начала до конца. Операция<br />

тривиальна и не требует особых пояснений.<br />

Прототип функции Printls, выполняющей печать содержимого<br />

линейного списка на экран, ее определение и пример вызова содержатся<br />

в программном проекте, приведенном в подразд. 8.1.<br />

8.11. Добавление элемента после каждого элемента<br />

ЛС, содержащего заданное значение<br />

Реализация операции, представленная в графической форме,<br />

дана на рис. 50.<br />

Прототип функции AfterAdd, выполняющей добавление элемента<br />

с данным add после каждого элемента ЛС, содержащего заданное<br />

значение y«(i, ее определение и пример вызова содержатся в<br />

программном проекте, приведенном в подразд. 8.1.<br />

167


До выполнения операции<br />

Общий случай<br />

Особые случаи<br />

t Start<br />

^<br />

find<br />

find<br />

w<br />

,, .^ NULL<br />

После выполнения операции<br />

2:<br />

4:<br />

W<br />

add<br />

3:<br />

• w<br />

NULL<br />

Start = NULL,<br />

Вывод<br />

сообщения и<br />

возврат из<br />

функции<br />

t t t<br />

Start cur 1: temp = new EL;<br />

2: temp->ch = add;<br />

3: temp->next = cur->next,<br />

4. cur->next = temp;<br />

t 5: cur = temp;<br />

Рис. 50. Добавление элемента после каждого элемента ЯС,<br />

содержащего заданное значение<br />

8.12. Добавление элемента перед каждым элементом<br />

ЛС, содержащим заданное значение<br />

Реализация операции, представленная в графической форме,<br />

дана на рис. 51.<br />

Прототип функции BeforAdd, выполняющей добавление элемента<br />

с данным add перед каждым элементом ЛС, содержащим заданное<br />

значение find, ее определение и пример вызова содержатся в<br />

программном проекте, приведенном в подразд. 8.1.<br />

8.13. Удаление элемента после каждого элемента ЛС,<br />

содержащего заданное значение<br />

Реализация операции, представленная в графической форме,<br />

дана на рис. 52.<br />

168


До выполнения операции<br />

Общий случай<br />

Особые случаи<br />

•—ь><br />

find<br />

NULL<br />

a) Start = NULL;<br />

6) cur = Start;<br />

t Start<br />

• \-><br />

t<br />

start<br />

После выполнения операции<br />

2:<br />

••<br />

4:<br />

W<br />

add<br />

3:<br />

' W<br />

find<br />

prev t cur<br />

1: temp = new EL;<br />

2: temp->ch = add;<br />

3: temp->next = cur;<br />

—• NULL<br />

a) Вывод<br />

сообщения и<br />

возврат из<br />

функции<br />

б) См.<br />

реализацию<br />

шага 4<br />

T 4: Start = temp при cur = Start<br />

или prev->next = temp в остальных<br />

случаях<br />

Рис. 51. Добавление элемента перед каждым элементом ЛС,<br />

содержащим заданное значение<br />

Прототип функции After_Del, выполняющей удаление элемента<br />

после каждого элемента ЛС, содержащего заданное значение find,<br />

ее определение и пример вызова содержатся в программном проекте,<br />

приведенном в подразд. 8.1.<br />

При реализации операции удаления производится просмотр<br />

элементов линейного списка, начиная с первого элемента до предпоследнего<br />

элемента списка.<br />

Отличительной особенностью операции является то, что если<br />

после удаления элемента текущий элемент является последним, то<br />

необходимо выйти из цикла просмотра элементов и из данной функции.<br />

Тем самым предотвращается ошибка, связанная с выполнением<br />

продвижения в ЯС на следующий элемент сиг — cur->next и последующей<br />

проверкой условия повтора цикла cur->next != NULL.<br />

169


8.14. Удаление элемента перед каждым элементом ЛС,<br />

содержащим заданное значение<br />

Реализация операции, представленная в графической форме,<br />

дана на рис. 53.<br />

До выполнения операции<br />

Общий случай<br />

Особые случаи<br />

1<br />

t start<br />

t<br />

start<br />

w<br />

find<br />

w<br />

w<br />

^ NULL<br />

а) Start = NULL,<br />

б) Start->next =<br />

NULL,<br />

После выполнения операции<br />

а) Вывод<br />

find<br />

сообщения и<br />

2:<br />

возврат из<br />

w<br />

w NULL функции<br />

б) Вывод<br />

t<br />

сообщения и<br />

возврат из<br />

cur 1 • del = cur->next;<br />

функции<br />

2: cur->next = del->next;<br />

3. delete del;<br />

4: если после удаления текущий элемент -<br />

последний, то выполняется выход из<br />

функции<br />

t<br />

Рис. 52. Удаление элемента после каждого элемента ЛС,<br />

содержащего заданное значение<br />

Прототип функции BeforDel, выполняющей удаление элемента<br />

перед каждым элементом ЛС, содержащим заданное значение<br />

find, ее определение и пример вызова содержатся в программном<br />

проекте, приведенном в подразд. 8.1,<br />

При реализации операции удаления производится просмотр<br />

элементов линейного списка, начиная со второго элемента до последнего<br />

элемента списка.<br />

Отличительной особенностью операции является то, что если<br />

удаление элемента выполнено, то продвижение указателя на предпредыдущий<br />

элемент не требуется. Указатель же на текущий элемент<br />

должен продвигаться всегда.<br />

Другой особенностью операции является то, что реализация<br />

шагов 1: и 2: в случае, когда текущий элемент является вторым в<br />

ЛС, отличаются (см. рис. 53).<br />

170


8.15. Зачем нужен линейный список<br />

Для хранения и обработки информации наряду с ЛС можно<br />

использовать и массивы. Например, вместо ЛС, приведенного на<br />

рис. 45, можно использовать символьный массив из пяти элементов,<br />

причем это сэкономило бы оперативную память. Значит ли это, что<br />

ЛС не следует использовать Конечно же, нет!<br />

До выполнения операции<br />

Общий случай<br />

Особые случаи<br />

t Start<br />

t<br />

start<br />

"•<br />

w<br />

... ^ w<br />

t t t<br />

pprev<br />

find<br />

,^ NULL<br />

а) Start = NULL,<br />

б) Start->next =<br />

NULL,<br />

После выполнения операции<br />

а) Вывод<br />

find<br />

сообщения и<br />

2:<br />

возврат из<br />

NULL<br />

w<br />

функции<br />

б) Вывод<br />

сообщения и<br />

возврат из<br />

cur<br />

функции<br />

1 • del = pprev->next или<br />

del = Start, если текущий<br />

элемент в ЛС второй<br />

2: pprev->next = del->next или<br />

Start = Start->next, если текущий<br />

элемент в ЛС второй<br />

3: delete del,<br />

Рис. 53. Удаление элемента перед каждым элементом ЛС,<br />

содержащим заданное значение<br />

Например, использование ЛС обеспечивает следующие преимущества:<br />

• вставка или удаление элементов в ЛС происходит проще и быстрее<br />

(вставка или удаление элемента в начальную часть массива<br />

большого размера требует выполнения значительного объема работы);<br />

• при работе с ЛС не требуется знать его максимальный размер<br />

(при размещении же массива надо заранее знать его требуемый<br />

размер или размещать массив максимального размера в расчете<br />

171


на наихудший случай).<br />

8.16. Упражнения для самопроверки<br />

Определены следующие данные:<br />

stjract ELEM // Структура для элемента списка<br />

{<br />

±nt dat; // Данное<br />

struct<br />

ELEM<br />

*next; // Указатель на следующий элемент<br />

} *сиг, // Указатель на текущий элемент<br />

// списка<br />

*start/ // Указатель на начало списка<br />

Во входном файле Is.dat содержится некоторое количество целых<br />

чисел, разделенных символами пробельной группы ( ' ', '\^', '\«' ).<br />

1. Написать прототип, определение и пример вызова функции<br />

для ввода из входного файла имеющихся там чисел, представив введенную<br />

информацию линейным списком, в котором каждый узел<br />

(динамически размещенная структура) содержит две компоненты.<br />

Первая компонента хранит данное (введенное число), а вторая -<br />

указывает адрес следующей структуры. При этом первое прочитанное<br />

число Должно находиться в начале линейного списка. Исходные<br />

данные и результаты работы функции следует передавать через список<br />

параметров.<br />

С целью обработки ошибок предусмотреть контроль значений,<br />

возвращаемых функциями библиотеки языков Си/С++.<br />

2. Дополнительно написать прототип, определение и пример<br />

вызова функции, которая в процессе просмотра списка выводит<br />

данные (числа) в файл на магнитном диске is.out. Требования к<br />

оформлению функции и обработке ошибок аналогичны указанным в<br />

п. 1 требованиям.<br />

3. Дополнительно написать прототип, определение и пример<br />

вызова функции, которая разрушает линейный список. Требования к<br />

оформлению функции и обработке ошибок аналогичны указанным в<br />

пункте 1 требованиям.


9. ПРЕПРОЦЕССОР ЯЗЫКА Си/С++<br />

Перед собственно компиляцией программы к ней применяется<br />

процедура предварительной обработки. Она выполняется программой,<br />

называемой препроцессором:<br />

ПРЕдварительный<br />

ПРОЦЕССОР<br />

Препроцессор расширяет возможности языка следующим.<br />

1. Подстановкой имен (заменой символических аббревиатур<br />

на соответствующие значения), т.е. наличием макроопределений.<br />

2. Включением файлов.<br />

3. Условной компиляцией.<br />

Препроцессор обеспечивает и некоторые другие, гораздо реже<br />

используемые возможности. По этой причине их рассматривать не<br />

будем.<br />

Наличие препроцессора сокращает затраты времени на разработку<br />

программ, обеспечивает создание переносимых, более читаемых<br />

и удобных для сопровождения и отладки программ.<br />

9.1. Директивы препроцессора<br />

Указания препроцессору вставляются в программу в виде директив.<br />

Директивой служит строка, в первой позиции которой указан<br />

символ диеза "#". Допускается, хотя и не рекомендуется, наличие<br />

предшествующих символу диеза пробелов и табуляторов. За<br />

символом диеза следует название директивы. Между ними допускается,<br />

хотя и не рекомендуется, произвольное число пробелов и/или<br />

табуляторов.<br />

Директивы можно размещать в любом месте программы и их<br />

действие остается в силе вплоть до конца того файла, в котором они<br />

находятся.<br />

9.2. Подстановка имен<br />

Подстановка имени (макроопределение) представляет собой<br />

символическое имя, которое присваивается фрагменту программы<br />

(строке элементов языка). Когда впоследствии препроцессор обна-<br />

173


руживает это символическое имя в программе, то он заменяет имя<br />

соответствующей строкой.<br />

Для подстановки имен предусмотрены две директивы препроцессора:<br />

• создать макроопределение (Ude/ine);<br />

• удалить макроопределение, т.е. сделать его неопределенным<br />

{Uundef).<br />

Пример использования директивы Udefine приведен на рис. 54.<br />

#define NULL ЛО'<br />

Признак директивы - его рекомендуется размещать<br />

в начале строки. Допускаются, но не рекомендуются,<br />

предшествующие пробелы и/или табуляторы<br />

Служебное слово препроцессора.<br />

По крайней мере один пробел или табулятор.<br />

I— Конец строки - завершает макроопределение<br />

(макроопределение должно размещаться в одной<br />

строке).<br />

Текст макроопределения - любое число символов в<br />

пределах одной строки.<br />

По крайней мере один пробел или табулятор<br />

Имя макроопределения - любой идентификатор Для<br />

более легкого «узнавания» макроопределения<br />

рекомендуется использовать в нем только прописные<br />

буквы.<br />

Между знаком диеза и служебным словом<br />

препроцессора допускаются, но не рекомендуются,<br />

пробелы и табуляторы.<br />

Рис. 54. Структура директивы на примере директивы Udeflne<br />

Приведем несколько примеров:<br />

#define SUCCES 1<br />

^define NULL '\0'<br />

^define MAX_SIZE 50<br />

^define UNIX // Пустое макроопределение<br />

^define printf myprintf<br />

Приведенные примеры показывают, что, как указывалось выше,<br />

запись имени макроопределения прописными буквами не обяза-<br />

174


тельна. Однако использование в именах макроопределения только<br />

прописных букв позволяет легко отличить макроопределения от<br />

других элементов программы.<br />

Обратите внимание на то, что директивы включения препроцессора<br />

не требуют завершающей точки с запятой. При ее наличии<br />

точка с запятой войдет в текст макроопределения, замещающий имя<br />

макроопределения. Это может стать причиной ошибки.<br />

Следующей, достаточно распространенной, ошибкой является<br />

включение пробелов в имя макроопределения, так как идентификатор<br />

не может содержать пробелов. Следует помнить, что для препроцессора<br />

имя макроопределения заканчивается на первом же пробеле<br />

или табуляторе. Все символы, следующие за ним, рассматриваются<br />

как замещающая строка.<br />

После определения имени макроопределения всякое его вхоэюдение<br />

в текст программы (за исключением вхождения в символьные<br />

и строковые константы) будет замещаться связанной с ним строкой<br />

символов.<br />

Примеры замещения макроопределений, созданных приведенными<br />

выше директивами ^define., содержатся в табл. 24.<br />

Табл. 24. Примеры замещения макроопределений<br />

До препроцессора<br />

if (scanner 0 = =SUCCES)pnntf("Cmon\n ");<br />

struct INDEXJNFO<br />

ifOine[pos]=-^NULL)<br />

printf("3mo NULLW):<br />

index[MAX_SIZE];<br />

После препроцессора<br />

if (scanner 0==l)myprintf("Cmon \n ");<br />

struct INDEX JNFO<br />

if(line[pos]=='\0')<br />

myprintf("3mo<br />

index[50];<br />

NULL\n"):<br />

Обратите внимание, что строка NULL внутри строковой константы<br />

не подверглась замене.<br />

Для отмены макроопределения можно воспользоваться<br />

директивой Uundef, действие которой иллюстрирует табл. 25.<br />

Отмена подстановки имени макроопределения с помощью директивы<br />

i^undef остается в силе до конца файла, в котором оно<br />

встретилось, если только это имя не будет заново определено новой<br />

директивой i^define.<br />

При использовании директивы Udefine можно указывать параметры<br />

при имени макроопределения:<br />

^include<br />

<br />

^define AREA (г) (3 . 14* (г) * (г) )<br />

175


Таблица площадей кругов<br />

±nt main ( void. )<br />

float radius = 1.Of;<br />

printf<br />

while(<br />

( "\n<br />

radius<br />

Радиус<br />

< 10.5f )<br />

Площадь \n" ) ;<br />

{<br />

printf(<br />

radius<br />

"%f<br />

-h= 1 . Of ;<br />

%f \л", radius, AREA( radius ) ).<br />

геЬмхпл 0;<br />

Табл. 25. Отмена макроопределения директивой i^undef<br />

До препроцессора<br />

include <br />

Ые/гпе print/myprintf<br />

int main( void )<br />

\{<br />

printf( "Введите дату: ");<br />

^undefprintf<br />

printf( "Введите время: "),<br />

return 0,<br />

J<br />

После препроцессора<br />

Текст файла stdio.h после обработки его<br />

препроцессором<br />

ini main( void)<br />

{<br />

myprintf( "Введите дату: "),<br />

}<br />

printf( "Введите время- ");<br />

return 0;<br />

В этом примере второй аргумент AREA( radius ) в вызове функции<br />

ргш^/'заменяется на (Ъ.\4*(radius)*(radius)). Обратите внимание<br />

также, что в макроопределении не только параметр г, но и весь текст<br />

макроопределения заключены в круглые скобки. Эти скобки<br />

позволяют избежать ошибок из-за возможных побочных эффектов,<br />

связанных с приоритетами выполнения операций:<br />

^include<br />

<br />

^define AREA (г) 3.14*r*r<br />

2.Of/AREA(radius) заменяется на<br />

2.0f/3.14*radius*radius,<br />

ошибка<br />

AREA(start-end) заменяется на<br />

3.14*start-end*start-end,<br />

также ошибка<br />

Директива Udefine моэюет содер^юатъ в круглых скобках не<br />

только один, но и любое число параметров, разделенных запятыми.<br />

176


9.3. Включение файлов<br />

Иногда один и тот же фрагмент программы встречается в нескольких<br />

файлах, образующих программу. Включение такого общего<br />

фрагмента в несколько исходных файлов программы выполняется<br />

с помощью директивы<br />

^include<br />

"путь_к_файлу"<br />

или<br />

^include<br />

<br />

Здесь путь_к_файлу<br />

означает корректную запись вида<br />

диск: \ путь_по__ка талогам\ имя__файла_с_ра<br />

сширением<br />

Для включаемых файлов принято использовать расширение .И<br />

или .hpp.<br />

Если указанный в директиве файл будет найден, то строка с<br />

директивой Uinclude будет заменена содержимым этого файла. Поиск<br />

включаемого файла выполняется в каталоге, указанном в директиве<br />

^include. Если<br />

диск:<br />

\путь_по_каталогам\<br />

отсутствует, то при использовании записи в форме:<br />

• "путькфайлу" поиск ведется сначала в текущем каталоге, а затем<br />

в каталогах включаемых файлов, определенных в интегрированной<br />

среде программирования;<br />

• поиск ведется сразу в каталогах, определенных в<br />

интегрированной среде программирования.<br />

Между названием директивы и путем к файлу может находиться<br />

любое число пробелов и табуляторов, в том числе и не одного,<br />

но рекомендуется использовать между ними один пробел.<br />

Во включаемые файлы помещают директивы Uinclude; прототипы<br />

функций; определения встроенных {inline) функций; объявления<br />

{extern) данных, определенных в другом файле; определения<br />

{const) констант; перечисления {епит), директивы условной трансляции<br />

{#ifndef, Uendif и др.), макроопределения {^define), именованные<br />

пространства имен {namespace), определения типов {class,<br />

struct), объявления и определения шаблонов {template), общие для<br />

нескольких исходных файлов, составляющих одну программу.<br />

177


Хотя многие из перечисленных средств нами еще не рассматривались,<br />

было целесообразно привести здесь указанные сведения.<br />

9.4. Условная компиляция<br />

Нередко, например, при отладке, требуется иметь возможность<br />

включать или исключать некоторые части программы на этапе компиляции.<br />

Именно для этих целей и предназначена условная компиляция.<br />

В зависимости от конкретного "условия" препроцессор может<br />

включать или исключать строки из программы. Для этих целей используется<br />

пять директив, указанных в табл. 26.<br />

Ш/<br />

Компилировать строки, следующие за<br />

директивой, если <br />

отлично от нуля ("истина")<br />

Компилировать строки, следующие за<br />

директивой, если "идентификатор" определен<br />

с помощью Udefine<br />

Компилировать строки, следующие за<br />

директивой, если "идентификатор" не<br />

определен с помощью директивы define или<br />

определение отменено с помощью Uundef<br />

Используется в сочетании с директивами Ш/,<br />

Uifdef, Uifndef как отрицание условия<br />

Заверщает область действия директив #if,<br />

m/def, m/ndef, Uelse<br />

Эти директивы подобны традиционной конструкции if-thenelse.<br />

Иллюстрирующий пример приведен в табл. 27.<br />

Если в этом примере удалить директиву<br />

#define<br />

TRACE<br />

ТО после обработки препроцессором будем иметь текст файла в следующем<br />

виде:<br />

void getline ( sroid )/<br />

±nt main ( void )<br />

{<br />

get line( ) ;<br />

return 0;<br />

178


void, get line ( void. )<br />

(<br />

return;<br />

Табл, 27. Использование директив условной компиляции<br />

До препроцессора<br />

После препроцессора<br />

#include <br />

Mefine TRACE<br />

void gedine( void );<br />

int main( void)<br />

и<br />

mfdef TRACE<br />

printf( "Main \n");<br />

Uendif<br />

getline();<br />

return 0;<br />

}<br />

void getlinef void)<br />

{<br />

mfdefTRACE<br />

printf( "Getline \n");<br />

^endif<br />

return;<br />

}<br />

Текст файла stdio.h после обработки его<br />

препроцессором<br />

void getline( void);<br />

int main( void )<br />

{<br />

printfC'Main<br />

getline();<br />

return 0;<br />

} -<br />

void getline( void )<br />

{<br />

}<br />

printf( "Getline<br />

return;<br />

\n");<br />

\n");<br />

Очень часто директивы условной трансляции используются<br />

для предотвращения многократного включения заголовочных файлов:<br />

/-"<br />

V<br />

stdlo h<br />

Definitions for stream Inpu t/OL itput.<br />

#lfndef STDIO_H<br />

^define STDIO_H<br />

// Текст включаемого файла<br />

#en'dlf<br />

Как указывалось выше, существуют и другие, менее употребительные<br />

директивы препроцессора. Например, в Visual C++ б имеются<br />

также следующие директивы:<br />

• #е///'(относится к директивам условной компиляции);<br />

• #Нпе (позволяет включать номера строк исходного кода заимствованных<br />

файлов);<br />

179


• i^error (обычно включается между директивами #(/'- if^endif для<br />

проверки какого-либо условия на этапе компиляции; при выполнениии<br />

такого условия компилятор выводит сообщение, указанное в<br />

terror и останавливается);<br />

• i^pragma (позволяет настраивать компилятор с учетом специфических<br />

особенностей конкретной машины или операционной<br />

системы - указанные особенности индивидуальны для каждого<br />

компилятора);<br />

• import (имеет отношение к включению библиотек типов в<br />

cow-технологии).<br />

Их обсуждение выходит за рамки данного пособия.<br />

9.5. Указания по работе с препроцессором<br />

Всякий раз, когда модифицируется включаемый файл имя. И,<br />

требуется заново компилировать каждый файл, в который файл заголовка<br />

включен с помощью директивы Uinclude (среда программирования<br />

не всегда контролирует изменение включаемого файла, хотя<br />

в последних версиях это предусмотрено).<br />

Препроцессор можно использовать для того, чтобы изменить<br />

внешний вид программы. Например, блок операторов можно оформить<br />

в стиле языка Паскаль следующим образом:<br />

^define begin {<br />

^define end }<br />

int main ( void. )<br />

begin<br />

end<br />

rGturn 0;<br />

Иногда случается, что текст макроопределения не помещается<br />

на одной строке. В подобных случаях признаком продолжения строки<br />

с текстом макроопределения является символ "\", например:<br />

ifdefine SUM_ZERO_ARRAY( array, size, sum )<br />

{ int i^O;<br />

sum = 0;<br />

while( i < size )<br />

{<br />

sum += array[ I ];<br />

array[ i J = 0;<br />

i + +;<br />

}<br />

}<br />

180


Большинство компиляторов языков Си/С++ поставляется вместе<br />

с набором заголовочных файлов. Одним из примеров такого рода<br />

является файл stdio,h. При использовании стандартных заголовочных<br />

файлов следует посмотреть их содержимое, чтобы случайно<br />

не переопределить стандартное имя. Стандартные заголовочные<br />

файлы разработаны квалифицированными программистами и по<br />

этой причине их также целесообразно посмотреть.


10. РЕДКО ИСПОЛЬЗУЕМЫЕ СРЕДСТВА<br />

ЯЗЫКОВ СИ/С++<br />

10.1. Объявление имени типа typedef<br />

с помощью typedef ыожпо приписать имя существующему типу<br />

данных. Примеры использования объявления имени типа приведены<br />

в табл. 28.<br />

Табл. 28. Объявление имени типа<br />

Объявление имени типа Пример применения<br />

typedef int INTEGER,<br />

typedef int SHORT,<br />

typedef long LONG,<br />

typedef char * STRPTR;<br />

typedef struct<br />

{<br />

double r;<br />

double i;<br />

}<br />

COMPLEX:<br />

INTEGER a, b,<br />

SHORT c,d,<br />

LONGe,f<br />

STRPTR ^, h,<br />

COMPLEX k.<br />

int a, b;<br />

int c, d;<br />

longe,f.<br />

char *g, *h,<br />

struct<br />

{<br />

double r;<br />

double i.<br />

Значение<br />

Из приведенной таблицы следует, что объявление имени типа<br />

в общем виде записывается следующим образом:<br />

typedef ;<br />

Заметим, что в объявлении имени типа и<br />

можно поменять местами, хотя делать это не рекомендуется.<br />

Чтобы можно было легче обнаружить в программе введенное<br />

имя типа лучше использовать в identifier прописные буквы, как это<br />

сделано в вышеприведенной таблице.<br />

Из табл. 28 также следует, что простейшая форма typedef похожа<br />

на директиву препроцессора ^define. Отличие заключается в<br />

том, что typedef обрабатывается компилятором, а директива Udefine<br />

- препроцессором. При этом компилятору доступны дополнительные<br />

проверки, обеспечивающие более глубокий уровень выявления<br />

ошибок.<br />

Имя, объявленное в typedef можно использовать в том же контексте,<br />

что для спецификации типа, например, как аргумент операции<br />

sizeof.<br />

Основными целями использования (ype


• повышение удобства чтения программы;<br />

• повышение мобильности программы.<br />

Если typedef находится внутри функции, то его область действия<br />

локальна и ограничена этой функцией. Если же объявление имени<br />

типа расположено вне функции, то его область действия глобальна.<br />

В последнем случае typedef часто помещают во включаемые<br />

файлы.<br />

10.2. Объекты перечислимого типа<br />

Объект перечислимого типа представляет собой объект, значения<br />

которого выбираются из фиксированного множества идентификаторов,<br />

называемых константами перечислимого типа. Синтаксис<br />

определения объекта перечислимого типа представлен на рис. 55 в<br />

виде синтаксической диаграммы.<br />

о<br />

Определение объекта<br />

перечислимого типа<br />

—•fenum Vw<br />

Идентификатор<br />

перечислимого<br />

типа<br />

{ >*<br />

Константа<br />

перечислимого<br />

типа<br />

^ }<br />

Идентификатор объекта<br />

перечислимого типа<br />

Константа<br />

перечислимого типа<br />

<br />

Идентификатор<br />

Константное выражение с<br />

целочисленным значением<br />

Рис. 55. Определение объектов перечислимого типа<br />

Пример определения объекта перечислимого типа:<br />

епгпа languages { с, разе, ada ^ modula2^ forth } master;<br />

или В эквивалентной форме<br />

183


enum languages{ с, разе, ada, modula2, forth };<br />

languages<br />

master;<br />

типа<br />

Здесь languages - новый перечислимый тип, a master - объект<br />

languages.<br />

Значением master может быть один из идентификаторов:<br />

с, pasc, adaг modula2, forth<br />

Например, можно написать:<br />

master = с;<br />

±f( master == с )<br />

printf( "\п Я знаю язык См" ) ;<br />

switch( master )<br />

{<br />

сазе с:<br />

break;<br />

сазе forth:<br />

break;<br />

cie£aul t:<br />

}<br />

Используя идентификатор перечислимого типа можно определить<br />

дополнительные объекты, например:<br />

languages о1, о2;<br />

Теперь имена ol, о2 обозначают объекты типа languages.<br />

Внутренним представлением каждой константы перечислимого<br />

типа служит целое значение (типа int). При объявлении перечислимого<br />

типа<br />

envaa languages { с, pasc^ ada, modula2, forth };<br />

его константам (слева направо) автоматически присваиваются возрастающие<br />

целые значения О, 1,2, 3, 4.<br />

Как следует из рис. 55, при объявлении перечислимого типа<br />

можно задать явное присваивание его константам целых значений,<br />

например:<br />

enum languages { с = -1, разе = 4, ada, modula2, forth = 4);<br />

Тем константам, значения которых явно не задано, присваивается<br />

значение предшествующей константы, увеличенное на единицу.<br />

184


Таким образом, константе ada соответствует значение 5, а константе<br />

modula2 - значение 6. Разным константам перечислимого типа может<br />

соответствовать одно и то же значение {pasc^ forth).<br />

Рассмотрим следующий пример:<br />

#include<br />

<br />

±nt mam ( vaid )<br />

{<br />

enxna t{ c==-l^ pasc=4^ ada, modula2, forth=4 };<br />

t m, ml;<br />

m = ada;<br />

// Следующее присваивание не вполне корректно -<br />

// компилятор формирует предупреждение, но программа<br />

// выполняется<br />

ml = 5 /<br />

printf ( "\п т = %d, ml = %d", т, ml ) ;<br />

// Данные присваивания также не вполне корректны -<br />

// компилятор формирует предупреждение, но программа<br />

// выполняется<br />

т = О; ml = 6;<br />

printf( "\л т = %d, ml = %d, ada = %d", m, ml, ada ) ;<br />

return 0;<br />

Результаты выполнения программы имеют вид:<br />

т =^ 5, ml = 5<br />

т = О, ml = б, ada = 5<br />

Объекты перечислимого типа, которым присвоены значения<br />

констант перечислимого типа, также как и константы перечислимого<br />

типа, можно использовать в любом выражении вместо целых констант.<br />

Вместо них подставляются соответствующие им целые значения.<br />

Сказанное подтверждает и приведенный выше пример.<br />

Объектам перечислимого типа можно присваивать любой<br />

класс хранения, кроме register. Область действия и время жизни для<br />

них определяются таким же образом, как и для рассмотренных ранее<br />

объектов других типов.<br />

Приведем еще несколько примеров перечислимых типов из<br />

файла .. \include \graphics. h:<br />

епит<br />

{<br />

COLORS<br />

BLACK, /* dark colors */<br />

BLUE,<br />

GREEN,<br />

185


CYAN,<br />

RED,<br />

MAGENTA,<br />

BROWN,<br />

LIGHTGRAY,<br />

DARKGRAY,<br />

LIGHTBLUE,<br />

LIGHTGREEN,<br />

LIGHTCYAN,<br />

LIGHTRED,<br />

LIGHTMAGENTA,<br />

YELLOW,<br />

WHITE<br />

/* light colors */<br />

enum<br />

{<br />

graphics_errors<br />

grOk<br />

grNoIni t Graph<br />

grNotDetected<br />

grFileNotFound<br />

grInvalidDriver<br />

grNo Loa dMem<br />

grNoScanMem<br />

grNoFlооdMem<br />

gr Font Not Found<br />

grNoFon tMem<br />

grInvalidMode<br />

grError<br />

grIOerror<br />

grInvalidFont<br />

grinvalidFontNum<br />

grInvalidVersion<br />

= 0,<br />

= -i.<br />

= -2,<br />

-^ -3,<br />

= -4,<br />

= -5,<br />

= -6,<br />

= -7,<br />

= -8,<br />

= -9,<br />

= -10,<br />

=<br />

=<br />

=<br />

=<br />

=<br />

-11.<br />

-12,<br />

-13,<br />

-14,<br />

-18<br />

graphresult error return codes */<br />

generic error */<br />

10.3. Объединения<br />

Подобно структуре, объединение представляет собой агрегатированный<br />

тип данных. Синтаксис объявления объединения идентичен<br />

синтаксису объявления структуры, только вместо служебного<br />

слова struct используется служебное слово union. Различие между<br />

структурой и объединением состоит в том, что каждому элементу<br />

объединения выделяется одна и та эюе область памяти^ а не различные<br />

области, как в структуре.<br />

Синтаксис объединения поясняется следующей записью:<br />

vLn±oTi []<br />

{<br />

<br />

;<br />

186


} []/<br />

Примеры:<br />

union INT__OR_LONG<br />

{<br />

int 1;<br />

long- 1;<br />

} а_питЬег, b_number;<br />

или в эквивалентной форме<br />

union INT__OR_LONG // Объявление типа объединения<br />

(<br />

int 1;<br />

long 1;<br />

};<br />

// Определение<br />

INT_OR_LONG<br />

объектов-объединений<br />

a_number, Ь_питЬег;<br />

с типом INT_OR_LONG<br />

Для объекта-объединения апитЬег или b_number можно легко<br />

выполнить преобразование целого значения в длинное целое или<br />

наоборот. Для преобразования целого значения в длинное целое<br />

достаточно выполнить следующие действия:<br />

а_питЬег. 1 = 7/ // Теперь а_питЬег. 1 имеет<br />

// значение 11<br />

Наряду с преобразованием типов с помощью объектаобъединения<br />

можно получить доступ к отдельным байтам объекта,<br />

как это показано в следующем примере.<br />

union<br />

{<br />

long<br />

lvalue;<br />

dovibl e dvalue;<br />

char<br />

chvalue;<br />

char cvaluel 8 ];<br />

} value;<br />

Определен объект с именем value, размер которого равен 8<br />

байтам (наибольший из размеров для типов long, double, char, char[<br />

8]).<br />

// Доступ к 4 байтам как к объекту типа long<br />

value.lvalue<br />

// Доступ к 8 байтам как к объекту типа double<br />

value.dvalue<br />

value.chvalue // Доступ к байту как к объекту типа<br />

// char<br />

187


к каждому из этих байтов можно осуществить доступ по отдельности,<br />

используя массив символов value.cvalue:<br />

value, cvalue[ 3 ] // Доступ к 4 байту<br />

Наряду с объектами-объединениями можно работать и с указателями<br />

на эти объекты, как показано ниже:<br />

union<br />

(<br />

long<br />

lvalue;<br />

dovLble dva lue;<br />

сЬлг<br />

chvalue;<br />

сЬа.з: cvalue[ 8 ];<br />

} valuef upvalue = &value;<br />

pvalue->lvalue /* эквивалентно */ value.lvalue<br />

Объектам с типом объединения можно присваивать любой<br />

класс хранения, кроме register. Область действия и время жизни для<br />

них определяются таким же образом, как и для рассмотренных ранее<br />

объектов других типов.


11. МОДЕЛИ ПАМЯТИ<br />

Материал данного раздела в основном освещает вопросы использования<br />

оперативной памяти процессора, относящиеся к приложениям<br />

для шестнадцатибитной среды DOS и WINDOWS с учетом<br />

особенностей процессоров INTEL 80x86.<br />

Модель памяти для программ на языках Си/С++, работающих<br />

в шестнадцатибитной среде, определяет, как программа использует<br />

память компьютера. Модель памяти связана с архитектурой процессора.<br />

Процессоры INTEL 80x86 используют сегментную организацию<br />

памяти, позволяющую адресовать 1 Мбайт памяти. Так как все<br />

регистры процессора шестнадцатиразрядные, то прямой доступ<br />

имеют только 64 Кбайта памяти (диапазон шестнадцатиразрядных<br />

беззнаковых адресов О, 1, 2, ..., 2^^-1). Эти 64 Кбайта памяти называются<br />

сегментом. Для того чтобы адресовать 1 Мбайт памяти, требуется<br />

двадцатибитовый адрес. Поэтому для представления двадцатибитового<br />

адреса используются два регистра (32 бита). Один регистр<br />

содержит адрес сегмента (регистр CS - указатель сегмента кода,<br />

DS - указатель сегмента данных, SS - указатель сегмента стека,<br />

ES - указатель дополнительного сегмента), а второй регистр содержит<br />

смещение в сегменте.<br />

Полный двадцатибитовый адрес, адресующий все адресное<br />

пространство процессора, вычисляется следующим образом (рис.<br />

56).<br />

ЮРА : 01С2<br />

16-ричный код смещения<br />

16-ричный код сегмента<br />

Сегментный регистр<br />

Сегментный регистр<br />

после сдвига<br />

Смещение<br />

0001 0000 1111 1010 (ЮРА)<br />

0001 0000 1111 1010 0000 (ЮРАО)<br />

0000 0001 1100 0010 (01С2)<br />

0001 0001 0001 0110 0010 (11162)<br />

Рис. 56. Получение полного двадцатиразрядного адреса<br />

189


Значение сегментного регистра сдвигается влево на четыре<br />

разряда (на одну шестнадцатиричную цифру) и к полученному значению<br />

добавляется смещение. Как следует из рис. 56, начальный адрес<br />

сегмента всегда является двадцатибитовым числом, а так как<br />

сегментный регистр имеет только шестнадцать бит, то недостающие<br />

младшие четыре бита всегда подразумеваются равными нулю. Это<br />

означает, что сегменты всегда начинаются на границе шестнадцати<br />

байт или параграфа (отрезок памяти из смежных шестнадцати байт<br />

называется параграфом).<br />

Сегменты памяти могут быть смежными, разделенными, перекрываться<br />

полностью или частично. Так как сегменты могут перекрываться,<br />

то одна и та же ячейка памяти может быть адресована<br />

более чем одним адресом. Например,<br />

10F0 : 0262 И 10Е0 : 0362<br />

указывают на один и тот же адрес памяти.<br />

Когда программа загружается в основную память, ее код и<br />

данные загружаются в отдельные сегменты памяти. Эти два сегмента<br />

называются сегментами по умолчанию.<br />

11.1. Адресация near, far и huge<br />

Специальные ключевые слова<br />

near - ближний г<br />

tan: - дальний,<br />

hugre - огромный<br />

используются в программах на языках Си/С+н- для модификации определений<br />

переменных и функций и определяют их размещение в<br />

памяти, отличное от стандартного размещения.<br />

Когда эти ключевые слова используются с указателями, то они<br />

изменяют размер указателя, который определяется выбранной моделью<br />

памяти. Имеется три типа указателей (три типа адресации): near<br />

(16 бит), far (32 бита) и huge (32 бита).<br />

Адрес near. Доступ внутри сегмента по умолчанию возможен<br />

через шестнадцатибитовое смещение, так как адрес сегмента по<br />

умолчанию всегда известен. Например, адрес объекта в сегменте<br />

данных по умолчанию получается сложением содержимого шестнадцатибитовой<br />

величины указателя на объект (смещения) с содержимым<br />

регистра сегмента данных DS, сдвинутым влево на четыре бита.<br />

Это шестнадцатибитовое смещение называется адресом near.<br />

190


Аналогично формируется адрес команды в сегменте команд по<br />

умолчанию (вместо регистра DS используется регистр CS).<br />

Доступ к данным или командам из сегментов по умолчанию в<br />

языках Си/С++ осуществляется через указатели near:<br />

тип near<br />

*near_pointer/<br />

Например,<br />

int near *^_Р^'<br />

Так как для доступа к данным или командам через адрес near<br />

требуется только шестнадцатибитовая арифметика, то ссылки near<br />

наиболее эффективны.<br />

Адрес far. Когда данные или код программы выходят за пределы<br />

сегментов по умолчанию, адрес должен состоять из двух частей:<br />

адреса сегмента и адреса смещения. Такие адреса называются адресами<br />

^аг. Доступ вне сегментов по умолчанию осуществляется через<br />

указатели Уаг:<br />

тип far<br />

*far_pointer;<br />

Например,<br />

±nt far<br />

'^f_p;<br />

Указатели y^fr позволяют адресовать всю память, но имеют следующие<br />

особенности.<br />

1. Пусть имеются три указателя/аг - ptrl ^ ptr2, ptr3 - на одну и<br />

ту же ячейку памяти:<br />

ptrl -- 5F20 ;: 0210,<br />

ptr2 -- 5F21 ;: 0200,<br />

ptr3 -- 5F41 : 0000.<br />

Над указателями допустимы операции сравнения и правомерны<br />

следующие выражения:<br />

ptrl === ptr2 ptrl == ptr3 ptr2 == ptr3<br />

Однако результатом всех трех сравнений будет значение<br />

"ложь", так как операции "==" и "!=" над указателями у^гг используют<br />

все 32 бита указателя как unsigned long int, а не как фактический<br />

адрес памяти.<br />

С другой стороны, операции сравнения "=" при<br />

сравнении указателей у^г используют только 16 бит смещения и для<br />

191


указателей far также не гарантируется правильность выполнения<br />

этих операций. Например, вычисление выражений<br />

ptrl > ptr2 ptrl > ptr3 ptr2 > ptr3<br />

Приводит к неожиданному результату: значением выражений будет<br />

"истина", хотя в действительности все три указателя адресуют одну<br />

и ту же ячейку памяти.<br />

2. Если добавить единицу к указателю У^г 1000:FFFF, то результатом<br />

будет 1000:0000, а не 2000:0000. Если вычесть единицу из<br />

указателя 1000:0000, то результатом будет 1000:FFFF, а не<br />

OFFF:OOOF. Таким образом, при увеличении или уменьшении указателя/аг<br />

изменяется только смещение. Следовательно, указателемУаг<br />

нельзя адресовать данные или код программы, размер которых превышает<br />

64 Кбайта.<br />

Адрес huge. Адрес huge, так же как и адрес/аг, состоит из адреса<br />

сегмента и смещения и занимает 32 бита. Адрес huge в языках<br />

Си/С++ задается указателем huge:<br />

тип huge<br />

*huge_pointer;<br />

Указатель huge имеет два отличия от указателя Уаг.<br />

1. Указатель huge нормализован и содержит максимально допустимое<br />

значение адреса сегмента для определяемого им адреса.<br />

Так как сегмент всегда начинается на границе, кратной 16 байтам,<br />

то значение смещения для нормализованного указателя будет в пределах<br />

от О до F. Например, нормализованной формой указателя<br />

35D2:1253 (определяемый адрес 36F73) будет 36F7:0003. Операции<br />

сравнения с указателями huge оперируют со всеми 32 битами и дают<br />

правильный результат.<br />

2. Для указателей huge нет ограничений на изменение значения<br />

указателя. Если при изменении указателя huge происходит переход<br />

через границу 16 байт, то изменяется адрес сегмента.<br />

Например, увеличение на единицу указателя 25B0:000F дает<br />

25В 1:0000 и, наоборот, уменьшение на единицу указателя 2531:0000<br />

дает 25B0:000F. Эта особенность указателя huge позволяет адресовать<br />

данные, размер которых превышает 64 Кбайта (занимают более<br />

одного сегмента). В языках Си/С++ указатели huge применяют для<br />

адресации массивов размером более 64 Кбайт.<br />

192


11.2.Стандартные модели памяти<br />

для шестнадцатибитной среды DOS<br />

Системы программирования Си/С++ для 16-битной среды DOS<br />

предоставляют пять стандартных моделей памяти:<br />

• крошечную (tiny);<br />

• малую (small);<br />

• среднюю (medium);<br />

• компактную (compact);<br />

• большую (large);<br />

• сверхбольшую (huge).<br />

Метод стандартных моделей памяти является наиболее простым<br />

способом управления доступом к коду и данным в основной<br />

памяти. В этом случае управление памятью осуществляется через<br />

режимы (опции) компилятора.<br />

Крошечная модель памяти. Данные, стек, динамическая память<br />

и код программы располагаются в одном и том же сегменте по<br />

умолчанию и занимают суммарно не более 64 Кбайт памяти. Для адресации<br />

кода, данных, стека и динамической памяти используются<br />

только адреса near , что убыстряет выполнение программы. Используется<br />

для построения .сот файлов.<br />

Малая модель памяти. Используется по умолчанию для<br />

большинства обычных программ на языках Си/С++. Программа с<br />

малой моделью памяти занимает только два сегмента по умолчанию:<br />

до 64 Кбайт для кода программы и до 64 Кбайт для данных, стека и<br />

динамической памяти программы. Для адресации кода, данных, стека<br />

и динамической памяти используются только адреса near, что<br />

убыстряет выполнение программы.<br />

Средняя модель памяти. Используется в программах с большим<br />

объемом кода программы (более 64 Кбайт) и небольшим объемом<br />

данных, стека и динамической памяти (не более 64 Кбайт).<br />

Средняя модель памяти обеспечивает один сегмент для данных, стека<br />

и динамической памяти программы и отдельный сегмент для каждого<br />

исходного модуля (файла) программы. Это значит, что программа<br />

может занимать до 1 Мбайта л^я кода и до 64 Кбайт для<br />

данных, стека и динамической памяти. Поэтому в программах со<br />

средней моделью памяти для адресации кода используются адреса<br />

far, а для адресации данных - адреса near.<br />

193


Компактная модель памяти. Используется в программах с<br />

большим объемом данных и стека программы (более 64 Кбайт до 1<br />

Мбайта) и небольшим объемом кода (не более 64 Кбайт). Компактная<br />

модель памяти обеспечивает один сегмент для кода программы<br />

и несколько сегментов для данных и стека программы. Поэтому в<br />

программах с компактной моделью памяти для адресации кода используются<br />

адреса near, а для адресации данных - адреса far.<br />

Большая модель памяти. Используется в программах с большим<br />

объемом кода, данных и стека программы. Обеспечивает несколько<br />

сегментов для кода, данных и стека программы. Это гарантирует<br />

до 1 Мбайта суммарной памяти. При этом отдельный элемент<br />

данных не может превышать 64 Кбайта. Используются только адреса<br />

far.<br />

Сверхбольшая модель памяти. Модель аналогична большой<br />

модели памяти за исключением того, что в сверхбольшой модели<br />

памяти снято ограничение на размер отдельного элемента данных.<br />

Для адресации кода адреса far, а для адресации данных - адреса<br />

huge.<br />

11.3. Изменение размера указателей в стандартных<br />

моделях памяти для шестнадцатибитной среды DOS<br />

Одним из недостатков концепции стандартных моделей памяти<br />

является то, что при изменении модели памяти меняются размеры<br />

адресов данных и кода. Однако можно подавить задаваемый по<br />

умолчанию способ адресации для конкретной модели, используя<br />

служебные слова near, far, huge.<br />

Данные можно определять с ключевыми словами near, far,<br />

huge. При этом модифицируется либо размещение данных, либо<br />

размер указателей на данные.<br />

Функции можно объявлять и определять только с ключевыми<br />

словами near и far (ключевое слово huge нельзя применять к функциям).<br />

Если ключевое слово near или far предшествует имени функции,<br />

оно определяет, будет ли функция размещаться как near (в сегменте<br />

кода по умолчанию) или как far (за пределами кода по умолчанию).<br />

Если ключевое слово near или far предшествует указателю на<br />

функцию, то оно определяет, будет ли для вызова функции использоваться<br />

адрес near (16 бит) или адрес far (32 бита).<br />

Для определения массивов размером более 64 Кбайт следует<br />

использовать ключевое слово huge:<br />

194


^include<br />

<br />

// Массив huge из 70000 байтов<br />

char hugre h_arr[ 10000 ] ;<br />

Использование операции sizeof для массивов huge имеет особенности.<br />

printf( "\п Размер массива h_arr: %ld ", sizeof ( h_arr ) ) ;<br />

Напечатается:<br />

Размер массива h_arr: 44 64<br />

(неверный ответ, так как ^/z^o/'возвращает unsigned int в диапазоне<br />

0...65535, а у нас 70000).<br />

Правильный вариант:<br />

printf( "\п Размер массива h_arr: %ld ",<br />

(unsigned, long inb) sizeof ( h_arr ) ) ;<br />

11.4. Макроопределения для работы с указателями<br />

в шестнадцатиразрядной среде DOS<br />

Заголовочный файл DOS.H определяет три макроса, облегчающих<br />

работу с указателями:<br />

• FP_OFF(fp) - возвращает смещение указателя^;<br />

• FP_SEG(fp) - возвращает сегмент указателя^;<br />

• MK_SEG( S, о ) ~ возвращает длинный указатель, составленный из<br />

сегмента s и смещения о , переданных в качестве аргументов.<br />

В качестве аргументов^ в приведенных выше макросах можно<br />

использовать не только указатели, но и адреса переменных.<br />

// Применение макросов FP__OFF и FP_SEG<br />

^include <br />

^include <br />

int mam ( -void. )<br />

{<br />

int 1;<br />

print f ( "Адрес локальной переменной: %p \л", &i ) ;<br />

printf( "Адрес локального значения: %04X:%04X \n"^<br />

FP_SEG( &i ;, FP_OFF( &1 ) ) ;<br />

195


eturn О;<br />

11.5. Работа с памятью для среды WINDOWS<br />

Приложения для шестнадцатибитной среды Windows (EXE) и<br />

Windows (DLL) при компиляции вместо шести могут использовать<br />

только одну из следующих четырех стандартных моделей памяти:<br />

• малую {smaH)\<br />

• среднюю {medium)\<br />

• компактную {compact)',<br />

• большую {large).<br />

Отличием стандартных моделей памяти для шестнадцатибитной<br />

среды Windows (DLL) от среды Windows (EXE) является то, что<br />

для данных и динамической памяти используется адресация far во<br />

всех моделях памяти.<br />

Другой отличительной особенностью всех приложений<br />

Windows для шестнадцатибитной среды является то, что сегмент не<br />

содержит реальный адрес памяти. Вместо этого сегмент содержит<br />

индекс (селектор), указывающий на строку в таблице (таблице дескрипторов),<br />

где этот адрес хранится. Для шестнадцатиразрядной<br />

же среды DOS процессор аппаратно суммирует значение сегментного<br />

регистра с указанным смещением, чтобы получить линейный адрес<br />

в оперативной памяти.<br />

Работа с памятью в тридцатидвухбитной среде<br />

WINDOWS. В тридцатидвухразрядных программах всегда используется<br />

сплошная (непрерывная) память. Управление этой памятью<br />

осуществляют интегрированная среда программирования и операционная<br />

система.


12. НОВЫЕ возможности ЯЗЫКА C+-i-,<br />

НЕ СВЯЗАННЫЕ С ОБЪЕКТНО-<br />

ОРИЕНТИРОВАННЫМ ПРОГРАММИРОВАНИЕМ<br />

[3,4]<br />

Язык C++ отличается от языка Си, в первую очередь, поддержкой<br />

объектно-ориентированного программирования (ООП).<br />

Однако, по сравнению с предшествующим языком Си, в нем есть<br />

еще ряд очень полезных нововведений, которые мы и рассмотрим в<br />

данном разделе.<br />

Комментарии. Как уже указывалось ранее, в языке C++ можно<br />

использовать два вида комментариев: обычные, оформленные по<br />

правилам языка Си, и однострочные, начинающиеся с символов // и<br />

продолжающиеся до конца строки. Многочисленные примеры их<br />

применения были рассмотрены выше.<br />

Размещение определений данных внутри блока. Напоминаем,<br />

что в языке Си все определения локальных данных внутри блока<br />

помещаются перед первым исполняемым оператором. В языке же<br />

C++ можно (и часто это оказывается более удобным) определять<br />

данные в любой точке блока перед их использованием:<br />

/'^<br />

Файл Р36.<br />

ми программ<br />

блока<br />

V<br />

СРР<br />

на<br />

(расширение .СРР принято для<br />

C+-h) . Ра змещение определении<br />

файлов с<br />

данных<br />

текставнутри<br />

^include<br />

<br />

int mam ( void ) // Возвращает 0 при успехе<br />

{<br />

// В языке C-h-h "модно" таким образом определять и<br />

// присваивать начальное значение управляющей<br />

// переменной цикла for<br />

fori xnt counterl = 0; counterl < 2; counterl++ )<br />

// Переменная counterl "видна"^ начиная с этой строки и<br />

// до конца та±п, а не только внутри блока for. Ей<br />

// присваивается значение О перед входом в цикл<br />

{<br />

// Автоматической переменной 1 присваивается значение<br />

// О при каждом проходе тела цикла,<br />

±пЬ i =- О;<br />

// а внутренняя статическая переменная j<br />

197


}<br />

// инициализируется нулем<br />

static ±nt<br />

J = О/<br />

for( Int counter2 = 0; counter2 < 5; counter2++ )<br />

pr±ntf( "\n ± = %d j = %d", i-h+r J++ ) ;<br />

}<br />

// counter2 "существует" до предыдущей фигурной скобки<br />

char quit_message[ J = "\n До свидания! \n";<br />

printf ( "%s", quit_message ) ;<br />

ret-am 0;<br />

В качестве упражнения рекомендуем определить, что напечатает<br />

данная программа, и проверить результаты Вашего анализа с<br />

помощью ЭВМ.<br />

12.1. Прототипы функций. Аргументы по умолчанию<br />

в языке Си наличие прототипов функций необязательно. Такая<br />

"снисходительность" часто порождает массу трудно обнаруживаемых<br />

ошибок, поскольку компилятор не может проверить, соответствуют<br />

ли при вызове функций типы передаваемых аргументов и тип<br />

возвращаемого значения определению данной функции. Язык Сн-+<br />

более строг: он требует, чтобы в файле, где происходит обращение к<br />

функции, причем обязательно до обращения к функции, присутствовало<br />

либо определение этой функции, либо ее объявление с указанием<br />

типов передаваемых аргументов и возвращаемого значения, или,<br />

по терминологии языков Си/С+н-, прототип. В последнем случае определение<br />

функции может находиться в другом файле. Обычно прототипы<br />

функций помещают в заголовочный файл, который включается<br />

в компилируемый файл директивой ^include.<br />

В языке C++ в прототипах функций моэюно задавать значения<br />

аргументов по умолчанию. Предположим, что написана функция<br />

DrawCircle, которая рисует на экране окружность заданного радиуса<br />

с центром в данной точке, и задан ее прототип:<br />

void DrawCircle( ±nt х=100, Int у=100, Int radius=100 ) ;<br />

Тогда вызовы этой функции будут проинтерпретированы, в зависимости<br />

от количества передаваемых аргументов, следующим<br />

образом:<br />

// Рисуется окружность с центром в точке (100, 100) и<br />

// радиусом 100<br />

DrawCircle ( ) ;<br />

198


Рисуется окружность с центром в точке (200, 100) и<br />

// радиусом 100<br />

DrawCircle ( 200 );<br />

// Рисуется окружность с центром в точке (200, 300) и<br />

// радиусом 100<br />

DrawCircle( 200, 300 );<br />

// Рисуется окружность с центром в точке (200, 300) и<br />

// радиусом 4 00<br />

DrawCircle( 200, 300, 400 );<br />

// Ошибка: аргументы можно опускать только справа<br />

DrawCircle ( , , 400 );<br />

Значения аргументов по умолчанию можно задавать не для<br />

всех аргументов, но начинать надо обязательно "справа":<br />

// ОшиОочный прототип<br />

void DrawCircle( Int х, int у=100, Int гad );<br />

// Ниже даны правильные варианты<br />

void DrawCircle( int к, int у=100, int radius=100 );<br />

void DrawCircle( int к, int y, int radius^lOO );<br />

12.2. Доступ к глобальным переменным,<br />

скрытым локальными переменными с тем же именем<br />

Оператор разрешения области видимости "::" позволяет воспользоваться<br />

глобальной переменной в случае, если она скрыта локальной<br />

переменной с тем же именем:<br />

^include<br />

<br />

int i = 2;<br />

int mam ( void )<br />

{<br />

float i = 5.3f;<br />

{<br />

}<br />

cJiax- *i = "Hello!";<br />

printf( "i-строка = %s i-целое = %d \n", i, ::i );<br />

}<br />

zr&tuxm 0;<br />

В результате выполнения программы получим:<br />

1-строка = Hello! i-целое = 2<br />

199


12.3. Модификаторы const и volatile<br />

Модификатор const, как и в языке Си, запрещает изменение<br />

значений данных. Разумеется, константа должна быть инициализирована<br />

при описании, ведь в дальнейшем ей ничего нельзя присваивать.<br />

Кроме того, в языке C+-I- данные, определенные как const, становятся<br />

недоступными в других файлах программного проекта, подобно<br />

статическим переменным:<br />

Файл Р37,СРР<br />

Модификатор const делает данное недоступным в других файлах<br />

программного проекта.<br />

Состав проекта: Р37.СРР<br />

CONST,СРР<br />

*/<br />

^include<br />

<br />

ехЬезпл floatt PI;<br />

±nt main ( void ) // Возвращает 0 при успехе<br />

{<br />

printfi "\n PI=%f'\ PI ) ;<br />

}<br />

return 0;<br />

Файл CONST. CPP. Используется в программном проекте P3 7.PRJ<br />

const float PI = 3.14159;<br />

Раздельная компиляция файлов из приведенного примера<br />

пройдет успешно, но компоновщик сообщит, что в файле Р37.СРР<br />

имеется не разрешенная внешняя ссылка.<br />

В большинстве случаев компилятор языка C++ трактует<br />

описанное как const данное, не локальное ни в одном блоке<br />

(областью действия его является файл), точно так же, как и<br />

макроопределение, созданное директивой препроцессора Udefine,<br />

т.е. просто подставляет в соответствующих местах величину,<br />

которой данное инициализировано. Однако const обладает тем<br />

преимуществом перед Udefine, что обеспечивает контроль типов,<br />

поэтому его использование может уберечь от многих ошибок.<br />

Модификатор volatile, напротив, сообщает компилятору, что<br />

значение данного может быть изменено каким-либо фоновым процессом<br />

- например, при обработке прерывания. С точки зрения ком-<br />

200


пилятора это означает, что, вычисляя значение выражения, в которое<br />

вход;ит такое данное, он должен брать его значение только из<br />

памяти (а не использовать копию, находящуюся в регистре, что допустимо<br />

в других случаях).<br />

12.4. Ссылки<br />

в большинстве языков программирования параметры передаются<br />

в подпрограмму (функцию) либо по ссылке, либо по значению.<br />

В первом случае подпрограмма (функция) работает непосредственно<br />

с аргументом, переданным ей, а во втором случае - с копией аргумента.<br />

Различие здесь очевидно: аргумент, переданный по ссылке,<br />

подпрограмма (функция) может модифицировать, а переданный по<br />

значению - нет.<br />

Как уже отмечалось выше, в языке Си аргументы передаются в<br />

функцию только по значению и общепринятый способ обеспечить<br />

функции непосредственный доступ к какому-либо данному из вызвавшей<br />

программы состоит в том, что вместо самого данного в качестве<br />

аргумента передается его адрес. При работе на языке C++ нет<br />

необходимости прибегать к таким ухищрениям - в языке C++ реализованы<br />

оба способа передачи параметров. Многочисленные примеры,<br />

иллюстрирующие сказанное рассмотрены выше.<br />

Ссылки в языке C++ можно использовать не только для передачи<br />

параметров в функции, но и для создания псевдонимов данных:<br />

int<br />

int<br />

xr = 2;<br />

ХГ+ + ;<br />

Однако,<br />

X ^ 1;<br />

&xr = X<br />

// Ссылка хг становится псевдонимом<br />

// к<br />

// Все равно, что к = 2;<br />

// Все равно, что х++;<br />

int X = 1;<br />

// Так как типы х и хг не совпадают, то компилятор создает<br />

// переменную типа char, для которой хг будет псевдонимом,<br />

// и присваивает ей (char)х<br />

char &ХГ = х;<br />

хг = 2; // Значение х не изменяется<br />

201


12.5. Подставляемые функции<br />

в языке Си для уменьшения расходов на вызовы небольших,<br />

часто вызываемых функций, принято использовать макроопределения<br />

с параметрами. Однако их применение сильно запутывает программу<br />

и служит неиссякаемым источником трудноуловимых ошибок.<br />

Что же предлагает взамен язык C++ Достаточно описать<br />

функцию как inline и компилятор, если это возможно, будет подставлять<br />

в соответствующих местах тело функции, вместо того, чтобы<br />

осуществлять ее вызов. Конечно же, определение подставляемой<br />

функции должно находиться перед ее первым вызовом:<br />

inline int InlineFunctionCube( int x )<br />

{<br />

}<br />

return<br />

x*x*x/<br />

int b = InlineFunctionCube( a ) ;<br />

int с = InlineFunctionCube( a++ )/<br />

Вот теперь можно повысить эффективность программы, пользуясь<br />

при этом всеми преимуществами контроля типов и не опасаясь<br />

побочных эффектов. Невозможна подстановка функций, содержащих<br />

операторы case, for, while, do-while, goto. Если для данной<br />

функции, определенной как inline, компилятор не может осуществить<br />

подстановку, то он трактует такую функцию как статическую,<br />

выдавая, как правило, соответствующее предупреждение.<br />

12.6. Операции динамического распределения памяти<br />

Так как занятие и освобождение блоков памяти является очень<br />

распространенной операцией, в языке C++ введены два "интеллектуальных"<br />

оператора new и delete, освобождающих программиста от<br />

необходимости явно использовать библиотечные функции malloc,<br />

calloc и free. Примеры использования этих операторов приведены<br />

выше. Остается добавить, что в программе, использующей new и<br />

delete, не запрещается применять также функции библиотеки языка<br />

Си malloc, calloc, free и им подобные.<br />

202


12.7. Перегрузка функций<br />

Предположим, что по ходу программы часто необходимо печатать<br />

значения типа ш/, double и char *. Почему бы не создать для<br />

этой цели специальные функции<br />

// в языке Си для вывода значений разного типа каждой из<br />

// функций придется дать особое имя<br />

void. print_int ( ±xit 1 )<br />

printf( "%d", i ;/ z-etuxn/<br />

voxd print_double ( dovible x )<br />

printf( "%lg"r X ) ; ) ; x-etum/<br />

•sroxdL print_str±ng ( chsir "^s )<br />

printf( "%s", s ) ; ) ; x-etixm/<br />

±nt j =5/<br />

print_lnt ( J ) ;<br />

print_double( 3,14159 ) ;<br />

print_string( "Hello" ) ;<br />

В стандартном языке Си потребовалось дать этим трем функциям<br />

различные имена, а вот в языке C++ можно написать "умную"<br />

функцию print, существующую как бы в трех ипостасях:<br />

i^include <br />

void, print ( Int i )<br />

pr±ntf( "%d", i ) ; return;<br />

void print ( double x )<br />

printf( "%lg", X ) ; return;<br />

void print ( сЪа.г *s )<br />

printf( "%s", s ) ; retuim;<br />

int mam ( void )<br />

203


}<br />

±nt j = 5;<br />

print( J );<br />

print( 3.14159 ) ;<br />

print( "Hello" ) ;<br />

iretuxTi 0;<br />

Компилятор сам выбирает, какую из трех перегруженных<br />

функций с именем print вызвать в каждом случае. Критерием выбора<br />

служат количество и типы аргументов, с которыми функция вызывается,<br />

причем, если не удается найти точного совпадения, компилятор<br />

выбирает ту функцию, при вызове которой "наиболее легко" выполнить<br />

для аргументов преобразование типа.<br />

Обратите внимание на два существенных обстоятельства.<br />

• Перегруженные функции не могут различаться только по типу<br />

возвращаемого значения:<br />

void. f( ±nt^ int );<br />

izit f( int^ int ); // Ошибка!<br />

• Перегрузка функций не должна приводить к конфликту с аргументами,<br />

заданными по умолчанию:<br />

void f ( int = о ) ;<br />

void f( void ) ;<br />

f(); // Какую функцию вызвать<br />

Компилятор языка C++ позволяет давать различным функциям<br />

одинаковые имена. Поэтому, помещая имена функций в объектный<br />

файл - результат компиляции, он должен их каким-то образом модифицировать,<br />

чтобы сделать уникальными. Модифицированные<br />

компилятором имена содержат информацию о количестве и типе параметров,<br />

так как именно по этому признаку перегруженные функции<br />

различаются между собой. Такая модификация получила название<br />

"декорирование имен".<br />

В некоторых ситуациях, например, при необходимости скомпоновать<br />

программу на языке C++ с объектными файлами или библиотеками,<br />

созданными "обычным" Си-компилятором, декорирование<br />

имен нежелательно. Чтобы сообщить компилятору языка C++,<br />

что имена тех или иных функций не должны декорироваться, их<br />

следует объявить как extern "С":<br />

// Отдельная функция<br />

204


exteim "С" ±nt fund ( ±nt ) ;<br />

extern "C" // Несколько функций<br />

}<br />

{<br />

void. func2 ( ±nt ) ;<br />

±nt funcS ( void. ) ;<br />

double fun с 4( double ) ;<br />

Модификатор extern "C" можно использовать не только при<br />

объявлении, но и при описании функций. Естественно, что функции<br />

с модификатором extern "С" не могут быть перегруженными.<br />

12.8. Шаблоны функций<br />

При написании программ на языке C++ часто приходится создавать<br />

множество почти одинаковых функций для обработки данных<br />

разных типов. Используя служебное слово template (шаблон),<br />

можно задать компилятору образец, по которому он сам сгенерирует<br />

код, необходимый для конкретных типов:<br />

1^^<br />

""/<br />

Файл<br />

^include<br />

iinclude<br />

Р38.СРР,<br />

<br />

<br />

Шаблоны<br />

функций<br />

// Замена местами переменных "а Ь". Компилятор создаст<br />

// подходящую функцию, когда "узнает", какой тип аргументов<br />

// Т подходит в конкретном случае<br />

template < class Т ><br />

void swap( Т &а, Т &Ь )<br />

(<br />

Т с; // Для обмена<br />

с = Ь; b = а; а = с/<br />

return/<br />

}<br />

int mam ( void ) // Возвращает О при успехе<br />

{<br />

Int i = О, J = 1;<br />

double X = 0.0, у = 1.0/<br />

char *sl = "Строка!", *s2 = "Строка2" /<br />

print f ( "\n Перед обменом: \n i = %d j = %d \n x=%lg "<br />

"y==%lg \n sl=%s s2=%s", 1, J, X, y, si, s2 ) /<br />

swap ( i, J )/ swap ( x, у )/ swap ( si, s2 )/<br />

printf ( "\n После обмена: \n i = %d j = %d \n x=%lg "<br />

205


"у=%1д \п sl = %s s2^%s", i, j, x, y, si, s2 ) ;<br />

}<br />

rebvim 0;<br />

Аргументы, помещаемые в угловые скобки после служебного<br />

слова template, называют параметрами настройки шаблона. Параметры<br />

настройки шаблонов функций обязательно должны быть<br />

именами типов.<br />

12.9. Перегрузка операций<br />

Если в языке C++ можно самому определять новые типы данных,<br />

например, структуры, то почему бы не заставить привычные<br />

операторы выполнять те же действия над определенными нами типами,<br />

которые мы хотим И такая возможность есть.<br />

Пусть @ есть некоторый оператор языка C++, кроме следующих<br />

операторов:<br />

, . * :: : sizeof<br />

Тогда достаточно определить функцию с именем operator@ с<br />

требуемым числом и типами аргументов так, чтобы эта функция выполняла<br />

необходимые действия:<br />

Файл Р39.СРР. Перегрузка операторов<br />

^include<br />

^include<br />

<br />

<br />

// Максимальная длина строки +1<br />

const ±nt MAX_STR_LEN = 80;<br />

stiract STRING // Структурный тип для строки<br />

{<br />

chsr s[ MAX_STR_LEN ];<br />

// Строка<br />

±nt str_len; // Текущая длина строки<br />

} ;<br />

// Переопределение ("перегрузка") оператора сложения для<br />

// строк - выполняет сцепление (конкатенацию) строк<br />

STRING орега,Ьог+ ( // Возвращает конкатенацию строк<br />

STRING &sl, // Первый операнд<br />

STRING &s2) // Второй операнд<br />

{<br />

206<br />

STRING TmpStr; // Для временного хранения


}<br />

// Длина строки результата равна сумме длин складываемых<br />

// строк. Позаботимся также о том, чтобы не выйти за<br />

// границу массива-суммы<br />

±f( ( TmpStr. str_len = si . str_len + s2. str__len ) >=<br />

MAX_STR_LEN )<br />

{<br />

TmpStr, s[ 0 ] = '\xO'/ TmpStr. Str__len = 0;<br />

re turn TmpStr;<br />

}<br />

// Выполним конкатенацию (сложение) строк<br />

strcpy( TmpStr.sг sl.s ) ; strcat ( TmpStr.s, s2.s ) ;<br />

rebvLrn<br />

TmpStr;<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

STRING strl, str2, str3;<br />

strcpy ( strl.Sr "Перегрузка операторов - " ) ;<br />

strl.str_len = strlen ( strl.s ) ;<br />

strcpy( str2.Sr "это очень здорово!" ) ;<br />

str2. str__len = strlen ( str2.s ) ;<br />

printf( "\n Первая строка: длинa=%d^ coдepжимoe=%s",<br />

strl . str__len ^ strl.s ) ;<br />

printf( "\n Вторая строка: длинa=%d, coдepжимoe=%s",<br />

str2. str_len,^ str2.s ) ;<br />

str3 = strl + str2;<br />

print f( "\n Конкатенация строк: длина = %d,. содержимое^%s ",<br />

str3. str__len, strJ.s ) ;<br />

rebvLrii. 0;


13. ТЕХНОЛОГИЯ СОЗДАНИЯ ПРОГРАММ [5]<br />

К настоящему моменту рассмотрен весь спектр средств языка<br />

С+-ь, кроме технологии объектно-ориентированного программирования<br />

(ООП) и стандартной библиотеки языка C++. Рассмотрим теперь,<br />

какими же принципами нужно руководствоваться, чтобы создать<br />

красивую, понятную и надежную программу.<br />

13.1. Кодирование и документирование программы<br />

С течением времени в процессе работы каждый программист<br />

вырабатывает собственные правила и стиль программирования. При<br />

этом полезно учиться не только на собственном опыте, но и разумно<br />

следовать приведенным ниже рекомендациям, основанным на достижениях<br />

ведущих программистов, которые, де-факто, стали негласным<br />

стандартом программирования. Это поможет избежать<br />

многих распространенных ошибок и неоправданно больших затрат<br />

времени на проектирование программных продуктов. Вместе с тем<br />

отметим, что, конечно же, что на все случаи жизни советы дать невозможно<br />

- ведь не зря программирование, особенно на заре его<br />

развития, считалось искусством.<br />

Главная цель, к которой нуэюно стремиться, - получить легко<br />

читаемую программу возможно более простой структуры [5]. В<br />

конечном итоге, все технологии программирования направлены на<br />

достижение именно этой цели, поскольку только таким путем можно<br />

добиться надежности и простоты модификации программы. В соответствии<br />

со сказанным, предпочтение при программировании следует<br />

отдавать не наиболее компактному и даже не наиболее эффективному<br />

способу программирования, а такому способу, который легче<br />

для понимания. Особенно важно это в случае, когда программу пишут<br />

одни программисты, а сопровождают другие, что является широко<br />

распространенной практикой [5].<br />

Первый шаг в написании программь/ - запись ее в так называемой<br />

текстуальной форме, возможно, с применением блок-схем.<br />

Текстуальная форма должна показать, что именно и как программа<br />

должна делать. Если же не можете записать алгоритм решения задачи<br />

в текстуальной форме, то велика вероятность того, что алгоритм<br />

плохо продуман. Текстуальная запись алгоритма полезна по нескольким<br />

причинам — она позволяет детально продумать алгоритм,<br />

обнаружить на самой ранней стадии некоторые ошибки, разбить<br />

программу на логическую последовательность функционально за-<br />

208


конченных фрагментов, а также обеспечить комментарии к программе.<br />

Каждый функционально законченный фрагмент алгоритма в<br />

соответствии с технологией модульного программирования следует<br />

оформить в виде функции. Каждая функция должна решать только<br />

одну задачу (не надо объединять два коротких независимых фрагмента<br />

в одну функцию). Предельные параметры функции (количество<br />

строк исходного текста и число параметров) определяются рассмотренным<br />

ранее правилом "семь плюс-минус два".<br />

Если некоторые действия встречаются в программе хотя бы<br />

дважды, их также нужно оформить в виде функции. Однотипные<br />

действия оформляются в виде перегруженных функций или функций<br />

с параметрами. Короткие, простые функции следует оформлять как<br />

подставляемые функции.<br />

Необходимо тщательно выбирать имена объектов (переменных,<br />

функций и т.п.). Рационально выбранные имена могут сделать<br />

программу в некоторой степени самодокументированной. Неудачные<br />

имена, наоборот, служат источником проблем. Не увлекайтесь<br />

сокращениями - они ухудшают читаемость текста. Общая тенденция<br />

состоит в том, что чем больше область видимости объекта,<br />

тем более длинным именем его надо снабжать. Перед таким именем<br />

часто ставится префикс типа (одна или несколько букв, по которым<br />

можно определить тип объекта). Для управляющих переменных коротких<br />

циклов, напротив, лучше использовать однобуквенными<br />

именами типа /, у, или к. Имена макросов предпочтительнее записывать<br />

прописными буквами, чтобы отличать их от других объектов<br />

программ. Не рекомендуется использовать имена, начинающиеся с<br />

одного или двух символов подчеркивания, имена типов, оканчивающиеся<br />

на "_/" и т.п.<br />

Переменные желательно инициализировать при их определении^<br />

а определять как можно ближе к месту их непосредственного<br />

использования. Но нет правил без исключений. Поэтому, с другой<br />

стороны, все определения локальных переменных блока лучше располагать<br />

в начале блока, чтобы их легко можно было найти.<br />

Локальные переменные предпочтительнее глобальных. Если<br />

глабальная переменная все же необходима, то лучше определить ее<br />

статической. Это ограничит область действия такой переменной одним<br />

исходным файлом.<br />

Всю необходимую функции информацию нужно стремиться<br />

передавать через список параметров, а не через глобальные переменные,<br />

изменение которых трудно отследить.<br />

Входные параметры функции, которые не дол:и€ны в ней<br />

изменяться, следует передавать по ссылке с модификатором<br />

const, а не по значению. Кроме улучшения читаемости программы и<br />

209


уменьшения случайных ошибок, этот способ позволяет экономить<br />

память при передаче сложных объектов. Исключение составляют<br />

параметры, размер которых меньше размера указателя - их лучше<br />

передавать по значению.<br />

Нельзя возвращать из функции ссылку на локальную переменную,<br />

потому что она автоматически уничтожается при выходе из<br />

функции, которая является ее областью действия. Не рекомендуется<br />

также возвращать ссылку на объект, созданный внутри функции с<br />

помощью функции malloc{ ) или операции new, так как это приводит<br />

к трудно контролируемым утечкам динамической памяти.<br />

Следует избегать использования в программе чисел в явном<br />

виде. Константы должны иметь осмысленные имена, заданные через<br />

const или епит (последнее предпочтительнее, так как память под<br />

перечисление не выделяется). Символическое имя делает программу<br />

более понятной. Кроме того, при необходимости изменить значение<br />

константы это можно сделать всего лишь в одном месте программы.<br />

Для записи каждого фрагмента программы необходимо использовать<br />

наиболее подходящие языковые средства. Любой цикл<br />

можно, в принципе, реализовать с помощью операторов if и goto, но<br />

это было бы нелепо, поскольку с помощью операторов цикла те же<br />

действия легче читаются, а компилятор генерирует более эффективный<br />

код. Ветвление на три или более направлений предпочтительнее<br />

программировать с использованием оператора switch, а не с помощью<br />

гнезда операторов if.<br />

Следует избегать лишних проверок условий. Например, вместо<br />

операторов<br />

±f( strstr( a,b ) > О ) { . . . }<br />

else ±f( strstr( a,b ) < 0 ) { ... ;<br />

else ( ... }<br />

лучше записать<br />

±nt ls_equal = strstr ( a,b ) ;<br />

±f( is_equal > 0 ) { ... }<br />

else ±f( is_equal < 0 ) { ... }<br />

else { ... }<br />

Бессмысленно использовать проверку на неравенство нулю<br />

(или, что еще хуже, на равенство true или false):<br />

bool<br />

is_busy;<br />

±f( is_busy == true ) { } // Лучше ±f( is_busy ) { }<br />

±f( is_busy == false ) { } // Лучше ±f( !is_busy ) { }<br />

while ( a == 0 ) { } // Лучше while ( !a ) { }<br />

210


Если одна из ветвей условного оператора короче, чем другая,<br />

то более короткую ветвь условного оператора лучше поместить<br />

сверху, иначе вся управляющая структура может не поместиться на<br />

экране, что затруднит отладку.<br />

В некоторых случаях тернарная операция лучше условного<br />

оператора:<br />

if( Z ) 2.-J; else i=k; // i = z j : k;<br />

При использовании циклов надо стремиться объединять<br />

инициализацию^ проверку условия повторения и приращение в<br />

одном месте. Сказанное означает, что лучше использовать цикл/or.<br />

Необходимо проверять коды возврата для выявления ошибок<br />

и предусматривать печать сообщений в тех точках программы,<br />

куда управление при нормальной работе программы передаваться<br />

не доллсно. Например, в переключателе должен использоваться<br />

вариант default с обработкой ситуации по умолчанию, особенно,<br />

если в переключателе перечислены все возможные варианты.<br />

Сообщение об ошибке дол:н€но быть информативным и подсказывать<br />

пользователю, как ее исправить. Например, при вводе неверного<br />

значения в сообщении должен быть указан допустимый<br />

диапазон.<br />

Операции выделения и освобо^исдения динамической памяти<br />

следует помещать в одну и ту Jtce функцию. Утечки памяти, когда<br />

ее выделили, а освободить забыли, создают большие проблемы в<br />

программах, продолжительность работы которых велика или не ограничена<br />

(на серверах баз данных, в операционных системах и т.п.<br />

программах).<br />

После написания программу надо тщательно отредактировать<br />

— убрать ненужные фрагменты, сгруппировать описания, оптимизировать<br />

проверки условий и циклы, проверить, оптимальное<br />

ли разбиение на функции и т.п. Подходить к написанию программы<br />

надо так, чтобы ее в любой момент можно было передать другому<br />

программисту.<br />

Комментарии имеют очень важное значение, поскольку<br />

программист чаще выступает как читатель, а не как писатель. Даже<br />

в том случае, если программу сопровождает автор программы, разбираться<br />

через год в плохо документированном тексте удовольствие<br />

сомнительное.<br />

По использованию комментариев и использованию форматирования<br />

текста мо:>§€но дать следующие рекомендации (они<br />

иллюстрируются многочисленными примерами исходных текстов<br />

программ, приведенными в этой книге).<br />

• Программа, если она используется, живет не один год, по-<br />

211


требность в каких-то ее новых свойствах появляется сразу же после<br />

ввода программы в эксплуатацию и сопровождение программы занимает<br />

гораздо больше времени, чем ее разработка. По этой причине<br />

основная часть документации долэюна находиться в тексте программы.<br />

Хорошие комментарии написать почти так же сложно, как<br />

и хорошую программу.<br />

а Комментарии долэюны представлять собой правильные<br />

предложения без сокращений и со знаками препинания и не должны<br />

подтверждать очевидное. В данной книге иногда есть отступы от<br />

этого в учебных целях.<br />

а Если комментарий к фрагменту программы занимает несколько<br />

строк, его лучиге разместить до фрагмента, чем справа от<br />

него, и выровнять по вертикали. Абзацный отступ комментария<br />

долэюен соответствовать отступу комментируемого блока.<br />

• Для разделения функций и других логически законченных<br />

фрагментов пользуйтесь пустыми строками и комментариями вида:<br />

//* ii^ic-kic*i(i


еле некоторого перерыва написанные фрагменты стираются и все<br />

повторяется заново.<br />

В процессе работы неоднократно изменяются структуры данных,<br />

разбиение на модули (функции) делается только тогда, когда<br />

просматривать текст программы становится неудобно и утомительно.<br />

При этом комментарии к программе не пишутся, а ее текст не<br />

форматируется. Из-за многочисленных неудач периодически высказываются<br />

сомнения в правильности работы компилятора, компьютера<br />

и операционной системы.<br />

Когда программа впервые доходит до стадии выполнения (а<br />

это случается не скоро), в нее вводятся произвольные исходные<br />

данные, после чего экран компьютера и файл результатов становятся<br />

объектами пристального удивленного изучения. "Работает" такая<br />

программа обычно только на одном наборе исходных данных, а внесение<br />

в них даже небольших изменений приводит автора к потере<br />

веры в себя и портит его настроение.<br />

Задача настоящего программиста состоит в том, чтобы научиться<br />

подходить к программированию профессионально. Профессионал<br />

отличается тем, что может достаточно точно оценить, сколько<br />

времени займет разработка программы, которая будет работать в<br />

точном соответствии с поставленной задачей. Для достижения подобных<br />

результатов требуется склонность к программированию,<br />

опыт, а также знание основных принципов, выработанных программистами<br />

в течение более чем полувека развития этой дисциплины.<br />

Даже к написанию самых простых программ нужно подходить последовательно,<br />

проходя в соответствии со структурным подходом<br />

ряд последовательных этапов.<br />

Структурный подход к программированию охватывает все<br />

стадии разработки проекта — спецификацию, проектирование, собственно<br />

программирование (кодирование) и тестирование. При этом<br />

стремятся к уменьшению числа возможных ошибок, их более раннему<br />

обнаружению и упрощению процесса исправления ошибок. В<br />

рамках структурного подхода используются нисходящая разработка,<br />

структурное и модульное программирование и нисходящее тестирование.<br />

Рассмотрим этапы создания программ, рассчитанные на достаточно<br />

большие программные проекты, разрабатываемые коллективом<br />

программистов [5]. Для неболыиих программ каждый из таких<br />

этапов упрощается, но содержание и последовательность этапов<br />

не изменяются,<br />

13.2.1. Этап 1: постановка задачи<br />

Создание любой программы начинается с постановки<br />

задачи,<br />

213


Изначально задача ставится в терминах некоторой предметной области<br />

и необходимо перевести ее в термины, более близкие к программированию.<br />

Поскольку программист редко досконально разбирается<br />

в предметной области, а заказчик - в программировании, то<br />

постановка задачи может стать весьма непростым итерационным<br />

процессом. Отметим, что здесь весьма полезным является использование<br />

объектно-ориентированного подхода, средства реализации<br />

которого в языке C++ будут рассмотрены во второй части книги.<br />

Постановка задачи заканчивается созданием технического задания,<br />

а затем и внешней спецификации программы, которая включает<br />

в себя.<br />

а Описание исходных данных и результатов (виды,<br />

представление, точность, ограничения и т.п.).<br />

• Описание задачи, реализуемой программой,<br />

а Способ обращения к программе.<br />

• Описание возможных особых и аварийных ситуаций и ошибок<br />

пользователя.<br />

На этом этапе программа рассматривается как "черный ящик",<br />

для которого определена выполняемая им функция, входные и<br />

выходные данные.<br />

13.2.2. Этап 2: разработка внутренних структур данных<br />

Большинство алгоритмов решения задач зависит от того, каким<br />

образом организованы данные. Из этого следует, что начинать<br />

проектирование программы надо не с алгоритмов, а с разработки<br />

структур входных, промежуточных и выходных данных. При этом<br />

принимаются во внимание такие факторы, как ограничения на размер<br />

данных, необходимая точность, взаимосвязь данных между собой,<br />

требования к быстродействию программы и т.п. Структуры<br />

данных могут располагаться в статической или динамической памяти.<br />

В последнем случае обеспечивается более экономное использование<br />

оперативной памяти.<br />

13.2.3. Этап 3: проектирование структуры программы<br />

и взаимодействия модулей<br />

На этом этапе применяется технология нисходящего проектирования,<br />

основная идея которой заключается в разбиении задачи<br />

на подзадачи меньшей сложности, которые можно разрабатывать<br />

раздельно. При этом используется метод пошаговой детализации.<br />

При этом программа сначала пишется на языке некоторой гипотетической<br />

машины, которая способна понимать самые обобщенные<br />

действия, а затем каждое из таких действий описывается на более<br />

214


низком уровне абстракции и т.д. Очень важной на этом этапе является<br />

спецификация интерфейсов, т.е. способов взаимодействия<br />

подзадач.<br />

Для каждой подзадачи создается внешняя спецификация, аналогичная<br />

указанной для этапа 1. Здесь же решаются вопросы разбиения<br />

программы на модули. Декомпозиция выполняется таким<br />

образом, чтобы минимизировать взаимодействие модулей. В результате<br />

может оказаться так, что одна задача реализуется с помощью<br />

нескольких модулей и, наоборот, в одном модуле может решаться<br />

несколько задач. На более низкий уровень проектирования переходят<br />

только после окончания проектирования верхнего уровня. Алгоритм<br />

записывают в обобщенной форме — например, текстуальной, в<br />

виде обобщенных блок-схем или другими способами.<br />

На этапе проектирования следует учитывать возможность будущих<br />

модификаций программы и стремиться проектировать программу<br />

таким образом, чтобы вносить изменения было возможно<br />

проще. Процесс проектирования является итерационным, поскольку<br />

в программе реального размера трудно продумать все детали с первого<br />

раза.<br />

13.2.4. Этап 4: структурное программирование<br />

Процесс программирования (кодирования) также организуется<br />

по принципу "сверху вниз": вначале кодируются модули самого<br />

верхнего уровня и составляются тестовые примеры для их отладки.<br />

При этом на месте еще не написанных модулей следующего уровня<br />

ставятся "заглушки" - временные программы. "Заглушка" в простейшем<br />

случае просто выдает сообщение о том, что ей передано<br />

управление, а затем возвращает его в вызывающий модуль. В других<br />

случаях "заглушка" может выдавать значения, заданные заранее или<br />

вычисленные по упрощенному алгоритму. Таким образом, сначала<br />

создается логический скелет программы, который затем обрастает<br />

"плотью" кода. Такая технология программирования получила название<br />

нисходящей технологии программирования, В литературе<br />

[5] показано, что эта технология по сравнению с восходящей технологией<br />

программирования имеет целый ряд преимуществ, что и обусловило<br />

ее широкое распространение.<br />

Рекомендации по записи алгоритмов в терминологии языка<br />

C++ приведены в подразд. 13.1. Ввиду важности, напомним еще раз,<br />

что главные цели — читаемость и простота структуры программы в<br />

целом и любой из составляющих ее функций.<br />

При программировании следует отделять интерфейс<br />

(функции, модуля, класса) от его реализации и ограничивать<br />

доступ к ненулсной информации. Небрежное, даже в мелочах, про-<br />

215


граммирование может привести к огромным затратам на поиск ошибок<br />

на этапе отладки.<br />

Этапы проектирования и программирования совмещены во<br />

времени: в идеале сначала проектируется и кодируется верхний уровень,<br />

затем следующий за ним и т.д. Такая стратегия хороша тем,<br />

что в процессе кодирования может возникнуть необходимость врести<br />

изменения, отражающиеся на модулях нижнего уровня.<br />

13.2.5. Этап 5: нисходящее тестирование<br />

Хотя этот этап рассматривается последним, это не значит, что<br />

тестирование не должно проводиться на предыдущих этапах. Проектирование<br />

и программирование должны обязательно сопровождаться<br />

написанием набора тестов — проверочных исходных данных<br />

и соответствующих им наборов эталонных реакций.<br />

Необходимо различать процессы тестирования и отладки программы.<br />

Тестирование представляет собой процесс, посредством<br />

которого проверяется правильность функционирования программы<br />

и соответствие всем проектным спецификациям. Таким образом,<br />

тестирование носит позитивный характер. Отладка - процесс исправления<br />

ошибок в программе. При отладке, в частности, исправляют<br />

ошибки, обнаруженные при тестировании. Следует заметить,<br />

что процесс обнаружения ошибок подчиняется экспоненциальному<br />

закону, т.е. большинство ошибок обнаруживается на ранних стадиях<br />

тестирования и, чем меньше в программе осталось ошибок, тем<br />

дольше придется искать каждую из них.<br />

Для исчерпывающего тестирования программы необходимо<br />

проверить каждую из ветвей алгоритма. Общее число ветвей определяется<br />

числом комбинаций всех альтернатив на последовательных<br />

участках алгоритма. Это конечное число, но оно может быть<br />

очень большим. Поэтому при тестировании программа разбивается<br />

на фрагменты, после исчерпывающего тестирования которых они<br />

рассматриваются как элементарные узлы более длинных ветвей.<br />

Тесты, в числе прочих возможностей, должны содержать проверку<br />

граничных условий (например, переход по условию л:>10 должен<br />

проверяться для значений 9, 10 и 11). Отдельно проверяется реакция<br />

программы на оигибочные исходные данные.<br />

Идея нисходящего тестирования предполагает, что к тестированию<br />

программы приступают еще до того, как завершено ее проектирование<br />

и кодирование. Это позволяет раньше опробовать основные<br />

межмодульные интерфейсы, а также убедиться в том, что программа<br />

в основном удовлетворяет требованиям пользователя. Только<br />

после того, как логическое ядро испытано настолько, что появляется<br />

уверенность в правильности реализации основных интерфей-<br />

216


сов, приступают к кодированию следующего уровня программы.<br />

Естественно, что полное тестирование программы, пока она<br />

представлена в виде скелета, невозможно, однако добавление каждого<br />

следующего уровня позволяет постепенно расширять область<br />

тестирования.<br />

Этап комплексной отладки на уровне системы при нисходящем<br />

проектировании занимает меньше времени, чем при восходящем,<br />

поскольку вероятность появления серьезных ошибок, затрагивающих<br />

большую часть системы, гораздо ниже. Кроме того, для каждого<br />

следующего, подключаемого к системе модуля уже создано<br />

его окружение и выходные данные отлаженных модулей можно использовать<br />

как входные для тестирования других. Но это, конечно,<br />

не значит, что модуль можно подключать к системе совсем "сырым"<br />

- бывает удобным провести часть тестирования автономно, поскольку<br />

сгенерировать на входе системы все варианты, необходимые<br />

для тестирования отдельного модуля, трудно.<br />

В приложении П.4 рассмотрена более подробно методика отладки<br />

программы применительно к возможностям двух популярных<br />

разновидностей интегрированных сред проектирования программ.


ЧАСТЬ 2. ПРИКЛАДНОЕ ПРОГРАММИРОВАНИЕ<br />

В число классических задач прикладного программирования<br />

принято включать следующие задачи, составляющие золотой багаж<br />

любого программиста:<br />

• сортировка (сортировка массивов, сортировка файлов);<br />

• транспортная задача (задача коммивояжера);<br />

• поиск в таблице;<br />

• обработка списков;<br />

• работа с очередями;<br />

• численный анализ (вычислительная математика).<br />

Численный анализ изучается в отдельном курсе, а остальные<br />

задачи прикладного программирования рассматриваются ниже.<br />

Перечисленные задачи прикладного программирования рассматриваются<br />

для овладения основами науки программирования и<br />

профессиональным стилем программирования. Умение решать эти и<br />

им подобные сложные задачи составляет основу профессиональной<br />

квалификации программиста.<br />

Прежде, чем перейти к рассмотрению некоторого подмножества<br />

классических задач прикладного программирования кратко обудим<br />

очень важный вопрос — структуры данных и, в частности, динамические<br />

структуры данных.<br />

14. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ [5]<br />

Любая программа предназначена для обработки данных. По<br />

этой причине алгоритмы обработки данных существенно зависят от<br />

способа организации данных и выбор структур данных должен<br />

предшествовать созданию алгоритмов. Стандартными способами<br />

организации данных в языках Си/С++ являются основные и составные<br />

типы. Очень часто в программах используются массивы, структуры<br />

и их сочетания — структуры или массивы структур, полями которых,<br />

в свою очередь, являются массивы и структуры.<br />

Оперативную память под данные можно выделять статически<br />

или динамически, причем в обоих случаях выделяется непрерывный<br />

участок памяти. Статическое распределение памяти для данных<br />

производится при компиляции. В этом случае требуемый объем<br />

218


оперативной памяти должен быть известен до начала выполнения<br />

программы и задан в виде константы. Заметим, что это возможно не<br />

всегда и тогда используют динамическое размещение данных в оперативной<br />

памяти. Динамическое размещение данных в памяти происходит<br />

во время выполнения программы с помощью операции new<br />

(только для языка С-+"Ь) или функции malloc (языки Си/С++). При<br />

этом необходимый объем требуемой памяти должен быть известен<br />

лишь к моменту динамического распределения памяти, а не до начала<br />

выполнения программы.<br />

Как уже было сказано, если до начала работы с данными невозможно<br />

определить, сколько памяти потребуется для их хранения,,<br />

то память выделяется по мере необходимости отдельными блоками,<br />

связанными друг с другом с помощью указателей. Такой способ организации<br />

получил название динамических структур данных, поскольку<br />

их размер изменяется во время выполнения программы. В<br />

качестве таких структур в программах часто используются линейные<br />

списки (см. разд. 8), бинарные деревья и очереди, частным случаем<br />

которых являются стеки. Они отличаются способами связи отдельных<br />

элементов друг с другом и допустимыми операциями. Отметим,<br />

что динамическая структура данных может занимать несмежные<br />

блоки оперативной памяти.<br />

Динамические структуры данных часто применяют и для более<br />

эффективной работы с данными, размер которых известен. К<br />

такого рода случаям можно отнести решение задач сортировки и<br />

поиска элементов. При сортировке упорядочивание динамических<br />

структур не требует перестановки элементов, а сводится к изменению<br />

указателей на эти элементы. Это особенно эффективно, если<br />

сортируемые элементы большого размера. При решении задачи поиска<br />

элемента в тех случаях, когда важна скорость, данные лучше<br />

всего представлять в виде бинарного дерева.<br />

Элемент любой динамической структуры данных представляет<br />

собой структуру {struct), содержащую, по крайней мере, два поля -<br />

для хранения данных и для указателя (см. разд. 8). В общем случае<br />

полей данных и указателей может быть несколько. Поля данных могут<br />

быть любого типа: стандартного (основного), составного или<br />

типа указатель.<br />

14.1. Линейные списки<br />

Самый простой способ связать множество элементов - сделать<br />

так, чтобы каждый элемент содержал ссылку на следующий. Такой<br />

список называется однонаправленным (односвязным). Исчерпывающий<br />

пример такого рода рассмотрен в разд. 8. Если добавить в каж-<br />

219


дыи элемент вторую ссылку — на предыдущий элемент, то получится<br />

двунаправленный список (двусвязный). Если же последний элемент<br />

связать указателем с первым, получится кольцевой список.<br />

Каждый элемент списка содержит ключ, идентифицирующий<br />

этот элемент. Ключ часто бывает целым числом или строкой и является<br />

частью поля данных. В качестве ключа в процессе работы со<br />

списком могут выступать различные части поля данных (например,<br />

фамилия, возраст, стаж работы и др.). Ключи разных элементов списка,<br />

в частном случае, могут совпадать.<br />

Над списками можно выполнять различные операции. Список<br />

операций над однонаправленным линейным списком и их реализация<br />

были рассмотрены в разд. 8.<br />

14.2. Бинарные деревья<br />

Бинарное дерево представляет собой динамическую структуру<br />

данных, состоящую из узлов (вершин), каждый из которых содержит,<br />

кроме данных, не более двух ссылок на различные бинарные<br />

деревья. На каждый узел имеется ровно одна ссылка. Начальный<br />

узел называется корнем дерева.<br />

На рис. 57 приведен алгоритм построения и пример бинарного<br />

дерева (его корень обычно располагается сверху).<br />

Узел, не имеющий поддеревьев, называется листом. Исходящие<br />

узлы называются предками, входящие — потомками. Высота<br />

дерева определяется количеством уровней, на которых располагаются<br />

его узлы. Использование двоичного дерева для сортировки<br />

массива рассмотрено в разд. 15.<br />

Если дерево организовано таким образом, что для каждого узла<br />

все ключи его левого поддерева меньше ключа этого узла, а все<br />

ключи его правого поддерева - больше, то оно называется деревом<br />

поиска. В дереве поиска можно найти элемент по ключу, двигаясь от<br />

корня и переходя на левое или правое поддерево в зависимости от<br />

значения ключа в каждом узле. При этом время поиска определяется<br />

высотой дерева, которая пропорциональна двоичному логарифму<br />

количества узлов. Пример использования дерева поиска рассматривается<br />

в разд. 17.<br />

Дерево является рекурсивной структурой данных, поскольку<br />

каждое поддерево также является деревом. Действия с такими<br />

структурами лучше всего описываются с помощью рекурсивных алгоритмов.<br />

Например, обход всех вершин дерева можно выполнить с<br />

помощью рекурсивной функции. Пример такой функции имеется в<br />

разд. 15.<br />

220


Корень<br />

J1=2*i<br />

_Z^X J<br />

8 10<br />

2, 3, 4, 5 - вершины<br />

6, 7, 8, 9, 10-листья<br />

Пример двоичного дерева для size =10<br />

Рис. 57. Алгоритм построения и пример бинарного дерева<br />

14.3. Очереди и их частные разновидности<br />

Универсальную очередь можно рассматривать как частный<br />

случай однонаправленного или двунаправленного линейного списка,<br />

когда определены только операции добавления или выборки элементов<br />

с любого из двух концов и, может быть, просмотра элементов<br />

очереди. Иные операции с очередью не определены. При выборке<br />

элемент исключается из очереди. В качестве применений очереди<br />

можно назвать моделирование систем массового обслуживания, составной<br />

частью которых являются очереди, диспетчеризация задач<br />

операционной системой, буферизация ввода-вывода и др.<br />

На практике часто используются более простые, частные случаи<br />

универсальной очереди - очереди типа FIFO и L1FO (стек). Очередь<br />

типа FIFO (First Input - First Output : первым занесен ~ первым<br />

извлечен) получается, если операция добавления элемента в очередь<br />

разрешена только для одного конца очереди, а операция выборки<br />

элемента - только для другого конца очереди.<br />

Стек - это частный случай однонаправленного линейного списка,<br />

добавление элементов в который и выборка выполняются<br />

только с одного конца, называемого вершиной стека. Говорят, что<br />

стек реализует дисциплину обслуживания LIFO (Last Input - First<br />

Output : последним занесен — первым извлечен). Стеки широко применяются<br />

в программировании, компиляторах, рекурсивных алгоритмах<br />

и т.п.<br />

221


14.4. Реализация динамических структур<br />

с помощью массивов<br />

Операции динамического выделения и освобождения памяти -<br />

дорогостоящее удовольствие. Поэтому, если максимальный размер<br />

данных можно определить до начала их использования и в процессе<br />

работы программы он не изменяется (например, при сортировке<br />

массива), то более эффективным может оказаться однократное выделение<br />

непрерывной области динамической памяти. Связи элементов<br />

при этом реализуются не через указатели, а через вспомогательные<br />

переменные или вспомогательные массивы, в которых хранятся<br />

номера (индексы) элементов массива. Такого рода приемы рассматриваются<br />

в разд. 15 и 17.<br />

Проще всего реализовать таким образом стек (см. подразд.<br />

3.9.1). Кроме массива элементов, соответствующих типу данных<br />

стека, достаточно иметь одну переменную целого типа для хранения<br />

индекса элемента массива, являющегося вершиной стека. При помещении<br />

элемента в стек индекс увеличивается на единицу, а при<br />

выборке - уменьшается.<br />

Для реализации очереди требуются две переменных целого типа<br />

- для хранения индексов элементов массива, являющихся началом<br />

и концом очереди.<br />

Для реализации линейного списка на базе массива требуется<br />

вспомогательный массив целых чисел и еще одна переменная (массив<br />

данных, вспомогательный массив и вспомогательную переменную<br />

можно оформить в виде полей структуры), например:<br />

10<br />

1<br />

0<br />

25<br />

2<br />

20<br />

3<br />

6<br />

4<br />

21<br />

5<br />

8<br />

6<br />

1<br />

7<br />

30<br />

-1<br />

- массив данных<br />

- вспомогательный массив<br />

- индекс первого элемента в списке<br />

/-ЫЙ элемент вспомогательного массива содержит для каждого /-го<br />

элемента массива данных индекс следующего за ним элемента. Отрицательное<br />

число используется как признак конца списка. Тот же<br />

массив после сортировки будет иметь следующий вид:<br />

10<br />

2<br />

6<br />

25<br />

7<br />

20<br />

4<br />

6<br />

5<br />

21<br />

1<br />

8<br />

0<br />

1<br />

3<br />

30<br />

-1<br />

- массив данных<br />

- вспомогательный массив<br />

~ индекс первого элемента в списке<br />

Аналогичным образом, для создания бинарного дерева можно<br />

использовать два вспомогательных массива с таким же размером,<br />

как и массив данных (содержат индексы вершин его левого и правого<br />

поддерева). Отрицательное число в этих массивах означает<br />

отсутствие соответствующего поддерева. Как и в предыдущем<br />

случае, массив данных и вспомогательные массивы можно офор-<br />

222


мить как поля структуры. В качестве упражнения можно предлагается<br />

самостоятельно и для этого случая составить иллюстрирующий<br />

пример.<br />

Обращаем Ваше внимание на то, что при работе с такими<br />

структурами необходимо контролировать возможный выход индексов<br />

за границы массива (нарушение индексации).<br />

И, наконец, важное заключительное замечание. Рассмотренный<br />

способ реализации позволяет использовать преимущества динамических<br />

структур (например, сортировать структуры из громоздких<br />

элементов данных без их физического перемещения в памяти)<br />

и при этом не расходовать время на выделение и освобождение<br />

динамической памяти для каэюдого элемента данных.


15. СОРТИРОВКА<br />

Под сортировкой понимают процесс перестановки объектов<br />

данного множества в определенном порядке. Цель сортировки - облегчить<br />

последующий поиск элементов в отсортированном множестве.<br />

В этом смысле элементы сортировки присутствуют во многих<br />

задачах прикладного программирования.<br />

Зависимость выбора алгоритмов решения задачи от структуры<br />

данных - явление довольно частое. В случае сортировки эта зависимость<br />

настолько сильна, что методы сортировки обычно разделяют<br />

на две категории:<br />

• сортировка массивов;<br />

• сортировка последовательных файлов.<br />

Эти две разновидности сортировок часто называют соответственно<br />

внутренней (сортировка массивов) и внешней (сортировка<br />

файлов) сортировками. Это объясняется тем, что массивы располагаются<br />

во "внутренней" (оперативной) памяти ЭВМ и для нее характерен<br />

быстрый произвольный доступ (прямой доступ). Файлы же<br />

хранятся в более медленной, но более вместительной "внешней" памяти,<br />

т.е. на запоминающих устройствах с механическим передвижением<br />

(магнитных дисках и лентах). Указанное существенное различие<br />

можно наглядно продемонстрировать на примере сортировки<br />

пронумерованных карточек.<br />

1. Представление карточек в виде массива с прямым доступом<br />

(рис. 58) означает, что все карточки одновременно видны и равнодоступны.<br />

0<br />

8<br />

9<br />

1<br />

2<br />

6<br />

4<br />

5<br />

Рис. 58. Произвольный (прямой) доступ<br />

2. Представление карточек в виде последовательного файла<br />

(рис. 59) предполагает, что видна и доступна только верхняя карточка.<br />

Чтобы добраться до остальных карточек необходимо, например,<br />

перекладывать карточки в колоде по одной спереди назад.<br />

Очевидно, что такое ограничение приведет к существенному<br />

изменению методов сортировки.<br />

224


ir<br />

Рис. 59. Последовательный доступ<br />

Терминология и обозначения. Будем считать, что нам даны<br />

элементы<br />

Сортировка означает перестановку этих элементов в таком порядке<br />

что при заданной функции упорядочения f справедливо отношение<br />

f(a,,)


рядоченный тип).<br />

Метод сортировки называется устойчивым, если относительный<br />

порядок элементов с одинаковыми ключами не меняется при<br />

сортировке. Устойчивость сортировки часто бывает желательной,<br />

если элементы упорядочены по каким-то вторичным ключам, т.е. по<br />

свойствам, не отраженным в первичном ключе.<br />

15.1. Сортировка массивов<br />

Основное требование к методам сортировки массивов - экономное<br />

использование памяти. В этом смысле говорят, что сортировку<br />

нужно выполнять in site (на том же месте) и другие методы,<br />

использующие копирование массива, для нас не представляют интереса.<br />

Удобной мерой эффективности алгоритмов сортировки "на<br />

месте" является число<br />

с<br />

(Compare)<br />

необходимых сравнений ключей и число<br />

М<br />

(Move)<br />

необходимых пересылок элементов.<br />

Хотя эффективные алгоритмы сортировки требуют порядка<br />

сравнений, где N - число элементов сортируемого массива, все же<br />

сначала обсудим несколько более простых методов сортировки, которые<br />

требуют порядка<br />

С-TV'<br />

сравнений, по следующим трем причинам.<br />

1. Простые методы особенно хорошо подходят для разъяснения<br />

свойств большинства принципов сортировки.<br />

2. Программы, основанные на этих методах, легки для понимания<br />

и коротки (следует помнить, что программы также занимают<br />

память!).<br />

3. Хотя сложные алгоритмы требуют меньшего числа операций,<br />

но эти операции более сложны. Поэтому при достаточно малых<br />

значениях Л^ простые методы работают также быстро, но их не<br />

следует использовать при больших N,<br />

Методы сортировки массивов "на месте" можно разбить на три<br />

основных класса:<br />

226


• сортировка выбором;<br />

• сортировка вставками;<br />

• сортировка обменом.<br />

Рассматриваемые программы будут работать с указателем агг<br />

на массив, компоненты которого нужно отсортировать "на месте", и<br />

структурным типом ELEMENT^ определенным выше.<br />

Указатель агг на массив можно определить так:<br />

±пЬ // Размер массива<br />

// Указатель на сортируемый массив<br />

ELEMENT *агг = new ELEMENT[ s };<br />

В простейшем случае элементы массива<br />

агг[ О ]г arrf 1 ], ..., arr[ s-1 ]<br />

будем располагать в порядке не убывания их ключей:<br />

arr[k^],key < arr[k2^.key < ... < arr[k^]Леу<br />

Сортировка выбором. Выбирается элемент с наибольшим<br />

значением ключа и меняется местами с последним. Затем то же самое<br />

повторяется для 5-1 первого элемента, найденный элемент с<br />

наибольшим значением ключа меняется местами с предпоследним<br />

элементом и т.д. (рис. 60).<br />

max s-1<br />

max s-2 s-1<br />

и т.д.<br />

Рис. 60. Сортировка простым выбором<br />

Сортировка включениями. Элементы разделяются на уже<br />

готовую последовательность (упорядоченную) и неупорядоченную<br />

(рис. 61). В начале упорядоченная часть содержит только один элемент.<br />

Очередной элемент из начала неупорядоченной части вставляется<br />

на подходящее место в упорядоченную часть. При этом упорядоченная<br />

часть удлиняется на один элемент, а неупорядоченная<br />

часть — укорачивается. Сортировка заканчивается при исчезновении<br />

неупорядоченной части.<br />

227


1 1-1<br />

—\ \<br />

S-1<br />

1 г<br />

i= 1,2, ..., s-1<br />

Вставим на подходящее место<br />

или оставим на месте<br />

Рис. 61. Сортировка простыми включениями<br />

Сортировка обменом. Основная характеристика процесса -<br />

обмен местами двух соседних элементов (перестановка), если они<br />

расположены не так, как требует отсортированный массив (рис. 62).<br />

На приведенном рисунке изображен только один шаг (просмотр).<br />

Сортировка массива гарантируется после s-\ просмотра.<br />

и т.д.<br />

Рис. 62. Сортировка простым обменом<br />

S-2 S-1<br />

15.2. Сортировка массива простым выбором<br />

Метод основан на следующем правиле.<br />

1. Выбирается элемент с наибольшим значением ключа.<br />

2. Он меняется местами с последним элементом агг[ 5-1 ]. Эти<br />

операции затем повторяются с оставшимися первыми ^-1 элементами,<br />

затем - с s-2 первыми элементами и т.д. до тех пор, пока не останется<br />

только один первый элемент - наименьший. Пример сортировки<br />

массива простым выбором приведен на рис. 63, в соответствии<br />

с которым, программу можно представить следующим образом:<br />

// Просмотр неотсортированных начальных сегментов массива<br />

// (вначале - весь массив, затем - сегмент из первых slze-1<br />

// элементов и т,д.)<br />

£о1т( L -= size-1; L >= 1; L— ;<br />

{<br />

// Присвоить indmax и max индекс и значение элемента<br />

// массива с наибольшим значением ключа из<br />

// агг[ О ]..arrf L ]<br />

// . . .<br />

// Поменять местами агг[ indmax ] и агг[ L ]<br />

arrf indmax ] = arrf L ]; arrf L ] = max;<br />

228


Начальные значения<br />

ключей элементов<br />

массива<br />

44<br />

44<br />

44<br />

44<br />

м—<br />

Об<br />

06<br />

06<br />

06<br />

55<br />

55<br />

55<br />

^<br />

18<br />

18<br />

18<br />

12<br />

12<br />

12<br />

12<br />

12<br />

12<br />

."1<br />

18<br />

18<br />

42<br />

42<br />

42<br />

42<br />

42 1<br />

Ф<br />

42<br />

42<br />

42<br />

94<br />

67<br />

м<br />

06<br />

06 1<br />

44<br />

44<br />

44<br />

44<br />

18<br />

18<br />

«"1<br />

Рис. 63. Сортировка массива простым выбором<br />

55<br />

55<br />

55<br />

55<br />

55<br />

06<br />

06 1<br />

Полный текст программы, иллюстрирующей все разработанные<br />

к настоящему времени методы сортировок массивов, приводится<br />

ниже. На данном этапе в этой программе не следует рассматривать<br />

функции, осуществляющие методы сортировки, отличные от<br />

сортировки массива простым выбором. Всю остальную часть программы<br />

следует внимательно изучить.<br />

67<br />

67<br />

67<br />

67<br />

67<br />

67<br />

67<br />

94<br />

94<br />

94<br />

94<br />

94<br />

94<br />

94<br />

/*<br />

Файл TestSortArr.срр. Тестирование сортировки динамически<br />

размещенных массивов с использованием различных методов.<br />

Определение методов сортировки приведено в файле<br />

SortArr.срр.<br />

Определение Функций размещения массива в динамической памяти<br />

и освобождения занятой динамической памяти находится в<br />

файле А1 ocFreeDM. срр,<br />

Определение функций ввода и печати значений элементов массива<br />

дано в файле SortlnOut.срр.<br />

Заголовочный файл программного проекта дан в файле SortArr.<br />

h.<br />

Давыдов В.Г. Консольное приложение. Visual C++ 6<br />

Ч<br />

// Включаемый файл программного проекта для сортировки<br />

// массивов<br />

^include "SortArr.h"<br />

int main ( // Возвращает О при успехе<br />

±nt ArgCr // ARGument Counter: число<br />

// аргументов в командной строке<br />

// ARGument Value: массив указателей на аргументы<br />

// командной строки (ArgV[ О ] - .ехе файл, в<br />

// интегрированной среде программирования известен и не<br />

229


{<br />

// задается; ArgV[ 1 ] - файл ввода; ArgVf 2 ] - файл<br />

// вывода)<br />

сЪаг *ArgV[ ] )<br />

// Проверка числа аргументов командной строки<br />

±f( ArдС /= 3 ;<br />

{<br />

printf(<br />

"\п Ошибка 5. В командной строке должно быть три аргумента: "<br />

"\л Имя_проекта. ехе имя_файла_ввода имя_файла__вывода \п" ) ;<br />

ех11 ( 5 ) ;<br />

}<br />

// Размещение массивов в динамической памяти:<br />

// а, al - указатели на начало массивов в динамической<br />

// памяти; S, 9 - размеры массивов<br />

AllocArrDMf аггг 8 ) ;<br />

// Для сортировки простыми включениями<br />

AllocArrDMl( arrl, 9 ) ;<br />

// Сортировка массива простым выбором<br />

ReadArr ( arr, ArgV[ 1 ] ) ;<br />

WriteArr ( arrг "\n Сортировка массива простым "<br />

"выбором. Массив до сортировки \п '\ ArgV[ 2 ],<br />

"w" ) ;<br />

SelectSort ( arr ) ;<br />

WriteArr ( arr, "\n Массив после "<br />

"сортировки \n ", ArgVf 2 ], "a" ) ;<br />

// Сортировка массива простыми включениями<br />

ReadArrl ( arrl, ArgV[ 1 ] ) ;<br />

WriteArrl ( arrl, "\n\n Сортировка массива простыми "<br />

"включениями. Массив до сортировки \п ", ArgV[ 2 ],<br />

"а " ) ;<br />

InsertSort ( arrl ) ;<br />

WriteArrl ( arrl, "\n Массив после "<br />

"сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

// Сортировка массива методом "пузырька"<br />

ReadArr ( arr, ArgV [ 1 ] ) ;<br />

WriteArr ( arr, "\n\n Сортировка массива методом "<br />

"\"пузырька\". Массив до сортировки \п ", ArgV[ 2 ],<br />

"а " ) ;<br />

BubbleSort( arr ) ;<br />

WriteArr( arr, "\п Массив после "<br />

"сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

230<br />

// Сортировка массива сложным выбором<br />

ReadArr ( arr, ArgV [ 1 ] ) ;<br />

WriteArr( arr, "\n\n Сортировка массива сложным "


"выбором. Массив до сортировки \п ", ArgVf 2 ],<br />

"а " ) ;<br />

TreeSort( arr ) ;<br />

WriteArr( arr, "\n Массив после "<br />

"сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

// Сортировка массива методом Шелла<br />

ReadArr( arr, ArgV[ 1 ] ) ;<br />

WriteArr ( arr, "\n\n Сортировка массива методом "<br />

"Шелла. Массив до сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

ShellSort ( arr ) ;<br />

WriteArr ( arr, "\n Массив после "<br />

"сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

// Сортировка массива методом Хоора (не рекурсивный<br />

// вариант)<br />

ReadArr ( arr, ArgV[ 1 ] ) ;<br />

WriteArr ( arr, "\n\n Сортировка массива методом "<br />

"Хоора. Массив до сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

Quicksort( arr ) ;<br />

WriteArr ( arr, "\n Массив после "<br />

"сортировки \п ", ArgVl 2 ], "а" ) ;<br />

;<br />

// Сортировка массива методом Хоора (рекурсивный вариант)<br />

ReadArr ( arr, ArgV[ 1 ] ) ;<br />

WriteArr ( arr, "\n\n Рекурсивная сортировка "<br />

"Хоора. Массив до сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

QuickSortl ( ) ;<br />

WriteArr ( arr, "\п Массив после "<br />

"сортировки \п ", ArgV[ 2 ], "а" ) ;<br />

//ккккккккккккккккккккккккккккккккккккккккккккккккккккккк<br />

// Освобождение динамической памяти, занятой массивами<br />

FreeArrDM( arr ) ;<br />

FreeArrDM( arrI ; ;<br />

xetujrn 0;<br />

/*<br />

Файл SortArr.h. Подключение стандартных заголовочных файлов,<br />

объявление объектов с описателями класса хранения "внешний",<br />

типов элементов сортируемого массива, стека и прототипов<br />

функций. Используется как заголовочный файл в программном<br />

проекте для сортировки массивов.<br />

Давыдов В.Г. Консольное приложение. Visual C++ 6<br />

V<br />

// Предотвращение возможности многократного подключения<br />

#ifndef SORTARR__H<br />

^define SORTARR Н<br />

231


^include // Для ввода-вывода<br />

^include // Для exit ( )<br />

const ±nt M = 16; // Размер стека отложенных<br />

// сегментов: >= (1од2(size)+1)<br />

// Структурный тип для элемента массива<br />

struct ELEMENT<br />

{<br />

} ;<br />

±nt key; // Ключ сортировки<br />

// Описание других компонент элемента<br />

// Объявления внешних объектов<br />

extern ELEMENT<br />

*arr; // Указатель на сортируемый массив<br />

extern ±nt size; // Размер сортируемого массива<br />

// Указатель на сортируемый массив для сортировки<br />

// простыми вставками<br />

extern ELEMENT<br />

*arrl;<br />

extern ±nt sizel;<br />

// Увеличенный на единицу размер<br />

// сортируемого массива<br />

// Структурный тип для элемента стека<br />

struct STACK<br />

{<br />

} ;<br />

±nt 1; // Левая граница сегмента<br />

±nt г; // Правая граница сегмента<br />

// Прототипы функций (имена параметров в прототипе не<br />

// используются и, поэтому, мы их не записываем<br />

void AllocArrDM( ELEMENT *&, int );<br />

void. AllocArrDMl ( ELEMENT *&, int );<br />

void ReadArr( ELEMENT [ ], char * );<br />

void ReadArrl ( ELEMENT [ ] , char * ) ;<br />

void SelectSort( ELEMENT [ ] ) Г<br />

void WriteArr ( ELEMENT [ ], char *, char *, char * );<br />

void WriteArrl( ELEMENT [ ], char *, char *, char * );<br />

void InsertSort ( ELEMENT [ ] );<br />

void BubbleSort( ELEMENT [ ] );<br />

void TreeSort ( ELEMENT [ ] );<br />

void Sift( int, int, ELEMENT [ ] );<br />

void ShellSort ( ELEMENT [ ] );<br />

void Quicksort ( ELEMENT [ J );<br />

void Push ( int, int, STACK [ ], int & );<br />

void Pop ( int Sc, int &, STACK [ ], int & );<br />

void QuickSortl ( void );<br />

void Split ( int, int );<br />

void FreeArrDM( ELEMENT *& );<br />

232


iendlf<br />

_<br />

Файл AlocFreeDM. cpp. Размещение одномерного массива в динамической<br />

памяти и освобождение динамической памяти, занятой<br />

массивом.<br />

Используется в программном проекте для сортировки массивов.<br />

Давыдов В.Г. Консольное приложение. Visual C-h-h 6<br />

V<br />

// Включаемый файл программного проекта<br />

Unciude "SortArr.h"<br />

// Размещение сортируемого массива в динамической памяти<br />

void AllocArrDMi<br />

ELEMENT *&arr, // Указатель на начало массива в<br />

// динамической памяти (передаем<br />

// по ссылке - это ответ)<br />

±пЬ S ) // Число элементов массива<br />

{<br />

// Контроль корректности размера массива<br />

±f( S < 2 )<br />

{<br />

}<br />

printf ( "\п Предупреждение 10. Массив должен "<br />

"содержать более двух элементов \п (задан размер, ''<br />

" равный %d) . Принимается размер массива, равный'^<br />

" 2. \п Выполнение программы продолжается ", s ) /<br />

S = 2/<br />

// Размещение массива в динамической памяти<br />

агг = new ELEMENT[ s ] ;<br />

±£( arr -= NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 20. Размещение массива в "<br />

"динамической памяти не выполнено ");<br />

exit ( 20 ) ;<br />

// Инициализация массива нулевыми значениями<br />

£ог ( int 1 = 0; i


void AllocArrDMl (<br />

ELEMENT *&arrl, // Указатель на начало массива в<br />

// динамической памяти (передаем<br />

// по ссылке - это ответ)<br />

±nt S ) // Число элементов массива<br />

{<br />

// Контроль корректности размера массива<br />

±£( S < 3 )<br />

{<br />

print f ( "\п Предупреждение 10. Массив должен "<br />

"содержать более трех элементов \п (задан "<br />

"размер, равный %d) . Принимается размер "<br />

"массива г равный 3,\п Выполнение программы<br />

"продолжается " ) ;<br />

S = 3;<br />

}<br />

// Размещение массива в динамической памяти<br />

arrl = new ELEMENT[ s ];<br />

±f( arrl == NULL )<br />

(<br />

printf( "\n Ошибка 20. Размещение массива в "<br />

"динамической памяти не выполнено " ) ;<br />

exit ( 20 ) ;<br />

}<br />

// Инициализация массива нулевыми значениями<br />

for( int i = 0; i


Используется в программном проекте для сортировки массивов.<br />

Давыдов В.Г. Консольное приложение^ Visual C++ 6<br />

V<br />

// Включаемый файл программного проекта<br />

§ include "SortArr.h"<br />

// Чтение значений элементов массива из файла данных<br />

void ReadArr (<br />

ELEMENT arr[ ] , // Сортируемый массив<br />

// Указатель на имя файла с исходными данными<br />

cha.r *pInpFile )<br />

{<br />

FILE *pStrInp; // Указатель на структуру со<br />

// сведениями о файле ввода<br />

±nt i, // Индекс элемента массива<br />

RetCode, // Возвращаемые значения fscanf( )<br />

// или их сумма<br />

RetCodel;// Возвращаемое значение fclose( )<br />

// Открытие файла ввода данных<br />

pStrlnp = fopen( pInpFiler "г" ) ;<br />

±£( pStrlnp -= NULL )<br />

{<br />

printf( "\n Ошибка 30. Файл %s для чтения не открыт"<br />

" \п", pInpFile ) ;<br />

exit ( 30 ) ;<br />

}<br />

// Ввод значений элементов массива<br />

Ret Code --= О;<br />

for( i = 0; 1 < size; i++ )<br />

{<br />

RetCode += fscanf( pStrlnp,<br />

" %d", &( arr[ i ].key ) ) ;<br />

}<br />

±f( RetCode < size )<br />

{<br />

}<br />

printf( "\n Ошибка 40. Ошибка чтения данных из файла"<br />

" %s \п", pInpFile ) ;<br />

exit ( 40 ) ;<br />

// Закрытие файла ввода<br />

RetCodel = fclose( pStrlnp ) ;<br />

±f( RetCodel == EOF )<br />

{<br />

printf( "\n Ошибка 50. Файл %s не закрыт \n ",<br />

pInpFile ) ;<br />

exit ( 50 ) ;<br />

}<br />

235


}<br />

return/<br />

// Чтение значений элементов массива из файла данных для<br />

// сортировки простыми включениями<br />

void. ReadArrl (<br />

ELEMENT arrl [ ], // Сортируемый массив<br />

// Указатель на имя файла с исходными данными<br />

char *pInpFile )<br />

{<br />

FILE *pStrInp; // Указатель на структуру со<br />

// сведениями о файле ввода<br />

int i, // Индекс элемента массива<br />

RetCode^ // Возвращаемые значения fscanf( )<br />

// или их сумма<br />

\<br />

RetCodel; // Возвраш.аемое значение f close ( )<br />

// Открытие файла ввода данных<br />

pStrlnp = fopen( pInpFile, "г" ) ;<br />

±f( pStrlnp -= NULL )<br />

{<br />

printf( "\n Ошибка 30. Файл %s для чтения не "<br />

"открыт \л", pInpFile ) ;<br />

exit ( 30 ) ;<br />

}<br />

// Ввод значений элементов массива ~ обратите внимание на<br />

// то, что первый элемент массива (служебный -<br />

// "барьер") не заполняется<br />

Ret Code = О;<br />

fori i = 0; 1 < size; i++ )<br />

{<br />

RetCode += fscanf( pStrlnp, " %d",<br />

&( arrl[ i+1 J.key ) ) /<br />

}<br />

±f( RetCode < size )<br />

{<br />

prmtf ( "\n Ошибка 40. Ошибка чтения данных из "<br />

"файла %s \п", pInpFile ) ;<br />

exit ( 4 0 ) ;<br />

}<br />

// Закрытие файла ввода<br />

RetCodel = fclose ( pStrlnp ) ;<br />

±f( RetCodel =- EOF )<br />

{<br />

prmtf( "\n Ошибка 50. Файл %s не закрыт \n ",<br />

pInpFile ) ;<br />

exit ( 50 ) ;<br />

}<br />

return/<br />

236


Печать значений элементов массива в файл результатов<br />

void. Wri teArr (<br />

ELEMENT arr[ ]^ // Сортируемый массив<br />

char '^'pMSG^ // Указатель на строку-сообщение о<br />

// печатаемом массиве<br />

char<br />

*pOutFile^//<br />

//<br />

Указатель<br />

файла<br />

на имя.расширение<br />

результатов<br />

char<br />

*Mode ) // Указатель на режим открытия файла<br />

{<br />

FILE<br />

int<br />

*pStrOut; // Указатель на структуру со<br />

// сведениями о файле результатов<br />

2, // Индекс элемента массива<br />

RetCodel; // Возвращаемое значение fclose( )<br />

// Открытие файла вывода<br />

pStrOut = fopen ( pOutFlle, Mode ) ;<br />

±£( pStrOut =- NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 60. Файл %s для вывода не '<br />

"открыт \л", pOutFile )/<br />

exit( 60 ) ;<br />

// Печать значений элементов массива с заголовком<br />

fprintf( pStrOut, pMSG ) ;<br />

tor( 1 = 0; i < size/ i++ )<br />

{ // Элементы выводятся по 6 в каждой строке из<br />

// расчета по 10 позиций на каждый элемент<br />

fprintf( pStrOut, "%10d", arr[ i ].key ) ;<br />

xf( ( ( i-hl ) % 6 ) == 0 )<br />

{<br />

fprintf( pStrOut, "\n " ) ;<br />

}<br />

}<br />

// Закрытие файла вывода<br />

RetCodel = fclose( pStrOut ) ;<br />

±f( RetCodel == EOF )<br />

{<br />

printf( "\n Ошибка 70. Файл %s не закрыт \n<br />

pOutFile ) ;<br />

exit ( 70 )/<br />

}<br />

return;<br />

// Печать значений элементов массива в файл результатов для<br />

// сортировки простыми включениями<br />

void WriteArrl(<br />

ELEMENT arrl [ ] г // Сортируемый массив<br />

237


char *pMSG, // Указатель на строку-сообщение о<br />

// печатаемом массиве<br />

cha.2: *pOutFile, // Указатель на имя .расширение файла<br />

// результатов<br />

cbajT *Mode ) // Указатель на режим открытия файла<br />

FILE *pStrOut; // Указатель на структуру со<br />

// сведениями о файле результатов<br />

±пЬ i, // Индекс элемента массива<br />

RetCodel; // Возвращаемое значение fclose( )<br />

// Открытие файла вывода<br />

pStrOut = fopen ( pOutFile, Mode ) ;<br />

±f( pStrOut == NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 60. Файл %s для вывода не "<br />

"открыт \л", pOutFlle )/<br />

exit ( 60 );<br />

// Печать значений элементов массива с заголовком<br />

fprintf( pStrOut, pMSG ) ;<br />

for( i = 1; i < sizel; i-h-h )<br />

{ // Элементы выводятся по 6 в каждой строке из расчета<br />

// по 10 позиций на каждый элемент<br />

fprintf( pStrOut, "%10d", arrlf i ].key );<br />

±f( ( 1 % 6 ) == 0 )<br />

}<br />

{<br />

}<br />

fprintf ( pStrOut, "\л " )/<br />

// Закрытие файла вывода<br />

RetCodel = fclose( pStrOut ) ;<br />

±f( RetCodel == EOF )<br />

{<br />

}<br />

printf( "\n Ошибка 10. Файл %s не закрыт \n ",<br />

pOutFile ) ;<br />

exit ( 70 );<br />

}<br />

return/<br />

Файл SortArr.cpp.<br />

Функции сортировки динамически размещенного массива по неубыванию:<br />

* простая сортировка массива выбором;<br />

* простая сортировка массива обменом;<br />

* простая сортировка массива вставками;<br />

* сортировка массива с помощью двоичного дерева;<br />

* сортировка Шелла;<br />

* не рекурсивная сортировка Хоора;<br />

238


'*' рекурсивная сортировка Хоора.<br />

Используется в программном проекте для сортировки массивов.<br />

Давыдов В.Г. Консольное приложение, Visual C++ 6<br />

V<br />

// Включаемый файл программного проекта<br />

^include<br />

"SortArr.h"<br />

// Определения объектов с описателем класса хранения внешний.<br />

// Их объявление имеется в заголовочном файле проекта и эти<br />

// объекты доступны в других файлах проекта<br />

ELEMENT<br />

±nt<br />

ELEMENT<br />

int<br />

*arr/<br />

size;<br />

*arrl;<br />

sizel;<br />

// Указатель на сортируемый массив<br />

// Размер сортируемого массива<br />

// Указатель на сортируемый массив<br />

// для простой сортировки<br />

// вставками<br />

// Увеличенный на единицу размер<br />

//<br />

//<br />

сортируемого массива для<br />

простой сортировки вставками<br />

// Эти объекты определяем в данном месте, чтобы при<br />

// рекурсивных вызовах в сортировке Хоора они не<br />

// создавались заново<br />

ELEMENT<br />

сору,<br />

median/<br />

int 1,<br />

J/<br />

// Копия элемента массива<br />

// Медиана разделяемого сегмента в<br />

// сортировке Хоора<br />

// Индекс кандидата на обмен слева<br />

// Индекс кандидата на обмен справа<br />

// в сортировке Хоора<br />

// Сортировка массива простым выбором - по неубыванию<br />

void SelectSort (<br />

ELEMENT arrf ] ) // Сортируемый массив<br />

{<br />

JLXm<br />

•LJr<br />

// Индекс последнего элемента из<br />

indmax.<br />

// пока неупорядоченных<br />

// Индекс наибольшего элемента среди<br />

// 1..L<br />

ELEMENT<br />

к;<br />

max;<br />

// Индекс анализируемого элемента<br />

// Для наибольшего элемента среди<br />

// 1. .L<br />

// Просмотр неотсортированных начальных сегментов массива<br />

// (вначале - весь массив, затем - сегмент из первых<br />

// size-1 элементов и т.д.)<br />

£ог( L ^ size-1; L >= 1; L-- )<br />

{<br />

// Присвоить indmax индекс элемента массива<br />

// наибольшим значением ключа из агг[ О ]. .агг[ L ]<br />

indmax = О; max = arr[ О ];<br />

for( к = 1; к


}<br />

}<br />

±f( arr[ к ] , key > max. key )<br />

(<br />

indmax = k; max = arrf к ];<br />

}<br />

// Поменять местами arr[ indmax ] и arrf L ]<br />

arrf indmax ] = arrf L ]; arrf L ] = max;<br />

)<br />

return;<br />

// Сортировка массива простыми включениями - по неубыванию<br />

void InsertSort(<br />

ELEMENT arrlf ] ) // Сортируемый массив<br />

}<br />

{<br />

// Используются следующие глобальные объекты:<br />

// i - индекс вставляемого элемента;<br />

// J - индекс элемента в упорядоченном сегменте;<br />

// сору = arrf i ]<br />

// Перебор вставляемых элементов<br />

£ог( i=2; 1


}<br />

sorted = О;<br />

// Цикл проходов<br />

while ( !sorted )<br />

{<br />

change = О;<br />

fori к = 1; к < size; к++ )<br />

{<br />

±f( arr[ k-1 ].key > arr[ к ].key )<br />

{<br />

temp = arr[ к ]; arr[ к J = arr[ k-1 J;<br />

arr/" k-1 ] = temp;<br />

change = 1;<br />

}<br />

}<br />

retvum;<br />

}<br />

sorted = !change;<br />

// Сортировка массива сложным выбором с использованием<br />

// пирамиды - двоичного дерева<br />

// Просеивание<br />

void Sift(<br />

Int rooty // Корень дерева или поддерева<br />

Int last у // Последняя вершина в дереве<br />

ELEMENT arr[ ] ) // Сортируемый массив<br />

{<br />

// 1 - позиция "дырки" (объект определен на внешнем<br />

// уровне)<br />

Int jl, // j1 = 2*1 -- следующая вершина<br />

// снизу и слева для i<br />

j2; // j2 = 2*1 + 1 - следующая вершина<br />

// снизу и справа для 1<br />

// j - претендент из jl и j2 на заполнение "дыры"<br />

// (объект определен на внешнем уровне)<br />

// сору - просеиваемый элемент (объект определен на<br />

// внешнем уровне)<br />

Int found; // 1 (нашли место для вставки сору)<br />

// Подготовка<br />

сору = агг[ root-1 ]; 1 = root; found = 0;<br />

while ( .'found )<br />

{<br />

// Определение jl и j2 для зафиксированного i<br />

jl = 2*i; j2 - jl-hl;<br />

// Анализ вариантов заполнения "дыры"<br />

lf( jl > last )<br />

{ // Следующего уровня внизу нет<br />

found = 1;<br />

}<br />

241


}<br />

etlse<br />

{ // Следующий внизу уровень есть<br />

±£( jl == last )<br />

}<br />

{<br />

}<br />

else<br />

{<br />

J = Jl/<br />

j = ( arrf J1-1 J.key >= arrf j2-l ] . key )<br />

jl : j2;<br />

}<br />

// Выяснение, кто заполняет "дыру"<br />

±f( arr[ j-1 ],key 0; temproot-- )<br />

{<br />

,^<br />

Sift( temproot, size, arr ) ;<br />

// Сортировка<br />

for( templast = size; templast >= 2; templast-- )<br />

{<br />

// Переставить максимум из корня дерева на<br />

// окончательное место<br />

tempcopy = arrf О ]; arrf О ] = arrf templast-1 ];<br />

arrf templast-1 ] = tempcopy;<br />

// Просеять новый корень на место - восстановить<br />

// дерево<br />

Sift( 1, templast-1, arr ) ;<br />

242


}<br />

}<br />

xretuxm/<br />

// Сложная сортировка массива вставками (метод Шелла)<br />

void ShellSort (<br />

ELEMENT arr[ ] ) // Сортируемый массив<br />

{<br />

izit d, // Дистанция Шелла<br />

fillpos; // Местоположение "дыры"<br />

// i - индекс анализируемого элемента (объект определен<br />

// на внешнем уровне)<br />

// J - индекс претендента слева на заполнение "дыры"<br />

// (объект определен на внешнем уровне)<br />

// сору = arrfi] (объект определен на внешнем уровне)<br />

int found; // 1 (нашли место для вставки сору)<br />

d = size;<br />

while ( d > 1 )<br />

{<br />

d = d/2;<br />

// Отсортировать вставками при текущем d<br />

£or( 1 = d; i < size; i++ )<br />

(<br />

copy = arr[ 1 ]; fillpos = i;<br />

// Найти место вставки copy<br />

found = 0;<br />

do<br />

{ .<br />

j = fillpos - d;<br />

±f( j < 0 )<br />

{ // Претендента слева нет<br />

found = 1;<br />

}<br />

else<br />

{ // Претендент слева больше - сдвиг<br />

if( arr[ j ].key


}<br />

retuxm/<br />

// Быстрая сортировка Хоора - нерекурсивный вариант<br />

// Занесение в стек сегментов<br />

void. Push (<br />

±пЬ left^ // Левая граница сегмента<br />

int rights // Правая граница сегмента<br />

STACK s[ 7/ // Стек границ сегментов<br />

int &sp ) // sp - указатель вершины стека<br />

{<br />

// В стек заносятся только сегменты из двух или более<br />

// элементов<br />

±f( ( right-left ) >= 1 )<br />

}<br />

{<br />

}<br />

return;<br />

sp+ + / s[ sp ],1 = left; s[ sp J.r = right/<br />

//<br />

// Извлчение сегмента из стека<br />

void Pop (<br />

int &i, // Указатель на левую границу<br />

// сегмента<br />

int &r, // Указатель на правую границу<br />

// сегмента<br />

STACK s[ ], // Стек границ сегментов<br />

int &sp ) // Указатель вершины стека<br />

(<br />

1 = s[ sp J.l; г = s[ sp ].r; sp--;<br />

}<br />

//<br />

re<br />

trim;<br />

// Быстрая сортировка массива - нерекурсивный вариант<br />

void Quicksort(<br />

ELEMENT arr[ ] ) // Сортируемый массив<br />

{<br />

244<br />

int left, // Левая граница разделяемого<br />

// сегмента<br />

right; // Правая граница разделяемого<br />

// сегмента<br />

// i - индекс кандидата на обмен слева - направо (объект<br />

// определен на внешнем уровне)<br />

// j - индекс кандидата на обмен справа - налево (объект<br />

// определен на внешнем уровне)<br />

// median - медиана разделяемого сегмента (объект


определен на внешнем уровне)<br />

// сору - для перестановки кандидатов (объект определен<br />

// на внешнем уровне)<br />

STACK s[ М ]; // Стек границ сегментов<br />

±Tib sp; // Указатель вершины стека<br />

sp = -1 ; // Вначале стек пуст<br />

Push ( О, size-lr S, sp );<br />

while ( sp >= О )<br />

{<br />

// Подготовка верхнего сегмента из стека для<br />

// разделения<br />

Pop ( left, right г s, sp );<br />

median = arr[ ( left+rlght )/2 ]; i = left;<br />

J = right;<br />

// Разделение текущего сегмента<br />

while ( 1


(<br />

Split ( Or size-1 );<br />

return;<br />

}<br />

//<br />

// Функция разделения сегментов<br />

void Split (<br />

±nt leftr // Левая граница сегмента<br />

int right ) // Правая граница сегмента<br />

{<br />

xf( ( right-left )


44 55 12 42 94 18 6 61<br />

файл результатов имеет следующий вид:<br />

Сортировка<br />

44<br />

6<br />

6<br />

61<br />

массива простым выбором. Массив до сортировки<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94<br />

Сортировка, массива простыми включениями. Массив до сортировки<br />

44<br />

6<br />

6<br />

61<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94<br />

Сортировка массива методом "пузырька". Массив до сортировки<br />

44<br />

6<br />

6<br />

61<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94<br />

Сортировка<br />

44<br />

6<br />

6<br />

61<br />

Сортировка<br />

44<br />

6<br />

6<br />

61<br />

Сортировка<br />

44<br />

6<br />

6<br />

61<br />

массива сложным выбором. Массив до сортировки<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94<br />

массива методом Шелла. Массив до сортировки<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94<br />

массива методом Хоора. Массив до сортировки<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94<br />

Рекурсивная сортировка Хоора. Массив до сортировки \<br />

44<br />

6<br />

6<br />

61<br />

55 12 42 94 18<br />

61<br />

Массив после сортировки<br />

12 18 42 44 55<br />

94 1<br />

В приведенной программе на данном этапе заслуживают также<br />

внимания следующие решения.<br />

1. Размещение сортируемого массива в динамической памяти<br />

и освобождение динамической памяти.<br />

2. Механизм передачи сортируемого массива в функции сортировок.<br />

247


3. Оформление включаемого файла программного проекта.<br />

Эффективность сортировки простым выбором. Число<br />

сравнений ключей не зависит от начального порядка ключей. Операция<br />

сравнения выполняется в теле цикла с управляющей переменной<br />

к и средним числом повторений size/2. Этот цикл, в свою очередь,<br />

находится в теле цикла с управляющей переменной L и числом<br />

повторений size-\. Таким образом, число сравнений<br />

С = {size -1) • size 12<br />

Число пересылок, напротив, зависит от начального порядка<br />

ключей. Если принять, что операция сравнения в теле цикла по к дает<br />

результат "истина" в половине случаев, то среднее число пересылок<br />

в этом цикле равно size!А. Цикл по L, как указывалось выше, выполняется<br />

sizeA раз и в теле цикла выполняется три пересылки и<br />

цикл по к. С учетом этого число пересылок<br />

М = (3 + size 14) • {size -1)<br />

Получаем, что при сортировке простым выбором и число сравнений,<br />

и число пересылок пропорционально size'^.<br />

15.3. Сортировка массива простыми включениями<br />

Идея алгоритма пояснена на рис. 61, а пример сортировки массива<br />

данным методом иллюстрирует рис. 64. Алгоритм сортировки<br />

простыми включениями выглядит следующим образом:<br />

for ( 2=2/ JL < sizel/ l+-h )<br />

{<br />

}<br />

copy = arr [ i ];<br />

// Вставка copy на нужное место среди отсортированных<br />

// элементов массива агг[ О ], ..., arrf i-l ]<br />

При поиске подходящего места удобно чередовать сравнения и<br />

пересылки, т.е. как бы "просеивать" сору, сравнивая его с очередным<br />

элементом arr[J ] и, либо вставляя сору, либо пересылая arr[j ]<br />

направо и передвигаясь налево. Заметим, что "просеивание" может<br />

закончиться при двух различных условиях.<br />

1. Найден элемент arr[j ] с ключом, меньшим, чем у сору.<br />

2. Достигнут левый конец упорядоченного сегмента и, следовательно,<br />

сору нужно вставить в левый конец упорядоченного сегмента.<br />

248


ключей элементов<br />

массива<br />

44 1 55<br />

44<br />

12<br />

12<br />

^ 1<br />

55 1<br />

44<br />

^<br />

42<br />

12<br />

12<br />

1<br />

55 1<br />

44<br />

42<br />

42<br />

42<br />

1<br />

55 1<br />

94<br />

94<br />

94<br />

94<br />

18<br />

18<br />

18<br />

18<br />

06<br />

06<br />

06<br />

06<br />

67<br />

67<br />

67<br />

67<br />

i = 2<br />

1 = 3<br />

i = 4<br />

1 = 5<br />

12<br />

42<br />

44<br />

55<br />

94 1 18<br />

06<br />

67<br />

1 = 6<br />

12<br />

18<br />

42<br />

44<br />

55<br />

94 1<br />

06<br />

67<br />

i = 7<br />

06<br />

12<br />

18<br />

42<br />

44<br />

55<br />

941<br />

67<br />

i = 8<br />

Массив отсортирован 06 12 18 42 44 55 67 94<br />

Рис. 64. Пример сортировки массива простыми включениями<br />

Это типичный пример цикла с двумя условиями окончания.<br />

При записи подобных циклов можно использовать известный прием<br />

фиктивного элемента ("барьера"), установив "барьер" слева в упорядоченной<br />

части массива агг[ О ] = сору (рис. 65).<br />

0 1 2 ... sJze-1<br />

I г 1 1 ~<br />

"Г<br />

агг<br />

«Барьер»<br />

Сортируемый массив<br />

Рис. 65. Использование "барьера" при сортировке массива<br />

ростыми включениями<br />

Прототип функции сортировки массива простыми включениями,<br />

ее определение и пример вызова даны в примере, приведенном в<br />

подразд. 15.2. Теперь наступила пора познакомиться с ними.<br />

Обратите внимание на то, что при использовании метода "левого<br />

барьера" размер массива, подлежащего сортировке, увеличен<br />

на один элемент. При этом элемент массива с нулевым индексом является<br />

вспомогательным и не сортируется. Таким образом, в массиве<br />

сортируются элементы с индексами 1, 2, ..., size 1-1. По этой причине<br />

для сортировки простым выбором используются функции размещения<br />

сортируемого массива в динамической памяти, заполнения<br />

его значениями из файла и печати значений элементов массива в<br />

файл, отличающиеся от аналогичных функций для других методов.<br />

Эффективность сортировки. Число С,, сравнений ключей<br />

249


при i-oM просеивании составляет самое большее /, а самое меньшее<br />

- 1. Число М, пересылок (присваиваний) элементов при /-ом<br />

просеивании равно<br />

(С,-1) + 3 = С,+2<br />

Это объясняется тем, что тело цикла while выполняется на<br />

один раз меньше, чем число проверок условия повтора цикла. Три<br />

других пересылки при /-ом просеивании есть:<br />

сору = агг1[ 1 ]; arrl [ О ] = сору/ arrl [ j+1 ] = copy;<br />

Поэтому обш^ее число сравнений и пересылок есть<br />

где (size-2) - число повторов цикла по /,<br />

v/rc'-l<br />

^МАХ ~ 21' ~ (size +1) • (size - 2) / 2,<br />

QpEfl = (CM,N + С ^ ) / 2 = ((size - 2) + (size +1) • (size - 2) / 2) / 2,<br />

uze-\<br />

^MiN = 3 • (size - 2), MMAX = X (' "^ ^) = ^^^^^ "^ ^) * ^^^^^ " 2) / 2,<br />

Л^сРЕд=(Л^м1м+^мАх)/2 = (3-(^/2е-2) + С9/2еч-5)-(^/^^-2)/2)/2.<br />

C^^iM и Л/^д,^, имеют место, если элементы массива с самого начала<br />

упорядочены, а С^^дх и Л/^АХ встречаются, если элементы массива<br />

расположены в обратном порядке.<br />

15.4. Сортировка массива простым обменом<br />

(метод "пузырька")<br />

Данный алгоритм основан на принципе сравнения и обмена<br />

пары соседних элементов до тех пор, пока не будут отсортированы<br />

все элементы массива. Пример сортировки массива методом "пузырька"<br />

приведен на рис. 66.<br />

Очевидно, что в наихудшем случае, когда минимальное значение<br />

ключа элемента имеется у самого правого элемента, число просмотров<br />

равно size-\.<br />

Прототип функции сортировки массива простым обменом, ее<br />

определение и пример вызова даны в примере, приведенном в подразд.<br />

15.2. Внимательно изучите их.<br />

250


Начальные значения ключей 44<br />

элементов массива (size = 8)<br />

55<br />

12<br />

12<br />

55<br />

42<br />

94<br />

18<br />

18<br />

94<br />

06<br />

6<br />

Конец первого просмотра 44 12 42 55 18<br />

Обратите внимание как «пузырек» 94 «всплыл» вправо!<br />

< • м •<br />

12<br />

44<br />

42<br />

55<br />

18<br />

55<br />

06<br />

06<br />

94<br />

—•<br />


сортировки массивов показывают, что наиболее быстрой является<br />

сортировка вставками, а наиболее медленной - сортировка обменом.<br />

3. Несмотря на плохое быстродействие, простые алгоритмы<br />

сортировки следует применять при малых значениях size.<br />

4. Наряду с простыми алгоритмами сортировки массивов существуют<br />

сложные алгоритмы сортировки, обеспечивающие время<br />

сортировки, пропорциональное не size^, а size-logjisize). При больших<br />

значениях size они обеспечивают существенный выигрыш. К их рассмотрению<br />

мы и переходим.<br />

15.6. Сортировка массива сложным выбором<br />

(с помощью двоичного дерева)<br />

Данная сортировка, по сравнению с сортировкой простым выбором,<br />

обладает большим быстродействием за счет сокращения затрат<br />

времени на выбор максимального элемента.<br />

/. Идея алгоритма. Исходное состояние двоичного дерева<br />

(ключи исходного массива структур агг с числом элементов size)<br />

показано на рис. 67.<br />

а Вначале двоичное дерево подготавливаем таким образом,<br />

чтобы элемент массива с максимальным значением ключа (94)<br />

находился в корне дерева.<br />

• Выбираем элемент с максимальным значением ключа, меняем<br />

его местами с последним элементом массива и затем восстанавливаем<br />

двоичное дерево (рис. 68). Вновь найденный элемент с максимальным<br />

значением ключа (67), который после восстановления<br />

двоичного дерева оказывается опять в его корне, меняется местами с<br />

предпоследним элементом массива и т.д. (всего size-\ раз).<br />

Таким образом, получаем общее число сравнений<br />

С = {size -1) • log 2 {size)<br />

и Какие в связи с этим возникают проблемы<br />

Как построить двоичное дерево (пирамиду) без дополнительных<br />

затрат памяти с тем, чтобы обеспечить сортировку "на месте",<br />

т.е. обойтись в дереве size вершинами вместо (5/z^*2-l) вершин<br />

Как организовать дерево в самом начале работы<br />

252


Всего ( 2*size-1 ) вершин<br />

94<br />

.55^<br />

/ \<br />

44 55<br />

.42,<br />

/ \<br />

12 42<br />

,94<br />

/ \<br />

94 18<br />

67<br />

/ \<br />

06 67<br />

Рис. 67. Исходное состояние двоичного дерева<br />

Вместо выбранного элемента с максимальным значением ключа (94)<br />

появились «дырки»<br />

На восстановление двоичного дерева потребовалось 1од2( size )<br />

сравнений претендентов на заполнение «дырок»: 1, 2, 3<br />

Рис. 68. Двоичное дерево после первой замены и восстановления<br />

2. Построение пирамиды (двоичного дерева) **на месте'* и ее<br />

начальное заполнение,<br />

2.1. Представление двоичного дерева из size элементов в одномерном<br />

массиве из size элементов. Из вершины двоичного дерева,<br />

в обш;ем случае, идут две дуги вниз (см. выше рис. 57), отсюда термин<br />

- двоичное дерево.<br />

В двоичном дереве могут встретиться следующие случаи:<br />

• у 1 > size - вершина / есть лист дерева;<br />

• j\ ~ size - нет7*2;<br />

• yi < size - есть у 1 иу2.<br />

Таким образом, для представления массива из size элементов<br />

253


требуется дерево с size вершин. У каждой вершины может быть О, 1<br />

или 2 дуги вниз (О -лист, 1 или 2 дуги вниз - промежуточная вершина<br />

или корень).<br />

Для рассмотренного выше примера мы получаем следующее<br />

дерево из size вершин (рис. 69).<br />

44<br />

42<br />

^ ^<br />

55<br />

о<br />

\..^^<br />

94<br />

12<br />

18 06<br />

/<br />

67<br />

А<br />

с:<br />

Рис. 69. Двоичное дерево из size вершин<br />

В отличие от первого дерева из (2*^/ze-l)=15 вершин ключ 94<br />

встречается один, а не четыре раза. Ключ 55 встречается один, а не<br />

три раза и т.д. Выигрыш налицо!<br />

2.2. Как подготовить дерево в самом начале, когда в массиве<br />

царит беспорядок<br />

В первую очередь рассмотрим поддеревья-листья 5, 6, 7, 8<br />

(нижний слой на рис. 69). Каждое из поддеревьев-листьев из одной<br />

вершины и, следовательно, они упорядочены.<br />

Вершина с максимальным номером, у которой есть следующие<br />

снизу вершины, имеет номер size/2 (при size=^ такая вершина имеет<br />

номер 4). Поэтому подготовку дерева надо начинать с вершины с<br />

номером size/2, потом - продолжать с вершины size/2'l и т.д., закончив<br />

вершиной 1 (см. рис. 70-73).<br />

Рассмотренный на рисунках процесс будем называть "просеиванием<br />

дыры" (sift).<br />

254


67<br />

Size/2=4<br />

В корне соответствующего поддерева образуем «дыру» путем<br />

копирования соответствующего корню элемента в сору. Ищем<br />

подходящее место для вставки сору и помещаем в него сору.<br />

42<br />

4<br />

1) Г—;<br />

-• j 1<br />

сору<br />

W 67<br />

«Дыра»<br />

4<br />

1 42 j<br />

^^^^ сору<br />

3) ^ 42<br />

8 8<br />

Рис, 70. Подготовка двоичного дерева для вершины 4<br />

67<br />

4<br />

/<br />

18<br />

12<br />

3<br />

1) Г 1<br />

-• 1 1<br />

\ сору<br />

06<br />

Size/2 -1=3<br />

12<br />

3 сору<br />

^ 18


3. функция просеивания<br />

3.1. Идея функции (см. рис. 57)<br />

±ль root^ // Корень дерева ими поддерева<br />

last; // Номер последней вершины дерева<br />

// или поддерева<br />

Возможны следующие ситуации:<br />

а у 1 > last - претендент на заполнение дыры есть сору\<br />

а yi = last - j2 нет и претендент на заполнение дыры есть<br />

тах{ сору.key., arr[J\ ].кеу };<br />

• yi < last - претендент на заполнение дыры есть<br />

тах{ с ору. key, arr[j\ ].key, arr[j2 ].key }.<br />

44<br />

Size/2 - 3 = = 1<br />

a) 6) в)<br />

1) Г--1<br />

copy<br />

( i<br />

i 44 i<br />

1 copy к 1<br />

• ^ " ^ l " ^ " " ^<br />

94 18<br />

94 18 ^<br />

18<br />

1 1 1 1 1 1 1 1 ^)ш\ L 1 1<br />

2 3 2 3 - ^ 2 ^;^ 3<br />

67 55<br />

с)) в итоге nonv ЧИМ<br />

94<br />

copy<br />

1 44 1<br />

67<br />

/<br />

42<br />

44<br />

4<br />

2<br />

55<br />

fS<br />

Рис. 73. Подготовка двоичного дерева для вершины 1<br />

256


3.2. Функции просеивания и сортировки сложным выбором.<br />

Прототипы функций просеивания (Sift), сортировки сложным<br />

выбором с помощью двоичного дерева (TreeSort), их определения и<br />

примеры вызовов содержатся в примере программы, приведенном<br />

выше в подразд. 15.2. Обратите внимание на то, что функция просеивания<br />

используется только для внутренних целей и, таким образом,<br />

не является интерфейсной (вызывается из функции сортировки<br />

сложным выбором). Функция же сортировки сложным выбором, напротив,<br />

является интерфейсной. Это означает, что она вызывается<br />

пользователем для сортировки массива.<br />

Эффективность сортировки. Ранее было показано, что число<br />

сравнений пропорционально {size-\)'\og2isize). Проанализируем<br />

эффективность сортировки более детально.<br />

В функции просеивания Sift цикл while в среднем выполняется<br />

{logjisize))/2 раз, содержит одну пересылку в теле цикла и две пересылки<br />

за пределами цикла. В функции TreeSort сортировки сложным<br />

выбором в теле цикла по templast делается три пересылки и пересылки<br />

в функции sift, а сам цикл выполняется (size-\) раз. В функции<br />

TreeSort в теле цикла по temproot выполняется функция sift, а<br />

тело цикла повторяется (size/2) раз. Таким образом, число пересылок<br />

Л/ = (3 + 2 + (log2 (size)) 12) - (size -1) + (2 + (logj (size))/2) • size/2<br />

пропорционально size • logj (size).<br />

В функции просеивания Sift в цикле while производится в среднем<br />

2'(\og2(size))/2 = \og2(size) сравнений. Поэтому общее число сравнений<br />

С = (log 2 (size))' (size -1) + (log 2 (size)) • size 12,<br />

что также пропорционально size*\og2(^ize).<br />

15.7. Сложная сортировка вставками<br />

(сортировка Шелла)<br />

Идея алгоритма,<br />

1. Используется несколько проходов.<br />

2. На первом проходе отдельно группируются и сортируются<br />

вставками элементы, отстоящие друг от друга на (i = size/2 позиций.<br />

3. На втором проходе аналогично группируются и сортируются<br />

вставками элементы, отстоящие друг от друга иг. d = d/2 позиций.<br />

4. Аналогично выполняются последующие проходы и сорти-<br />

257


ровка заканчивается последним проходом при d - I (как при простой<br />

сортировке вставками).<br />

Иллюстрирующий пример дан на рис. 74.<br />

Следует подчеркнуть, что ускорение сортировки происходит<br />

на первых этапах, когда сортировка вставками производится среди<br />

элементов, отстоящих друг от друга на болыиие расстояния. По<br />

этой причине на втором и последующих этапах перестановки элементов<br />

почти отсутствуют.<br />

Текст функции. Прототип функции ShellSort сложной сортировки<br />

вставками, ее определение и пример вызова приведены выше<br />

в программе, текст которой дан подразд. 15.2.<br />

О выборе последовательности значений d. У нас в примере<br />

использовалась последовательность значений d, равная 1, 2, 4, ...,<br />

d


^,=1, J, =3-^,_,+1 (A: = 2,3,...)<br />

1,4, 13, 40, 121, ... (в обратном порядке)<br />

Анализ показывает, что число сравнений при этом пропорционально<br />

size^'^, а не size^'^ ^ как в нашем варианте. И то, и другое при<br />

больших size хуже, чем size-Xogj^size),<br />

15.8. Сложная сортировка обменом<br />

(сортировка Хоора)<br />

/. Идея алгоритма (рис. 75). Исходный массив разбивается на<br />

два сегмента - левый, элементы которого агг[ i ].кеу = median.key. Далее, каждый<br />

из полученных сегментов можно сортировать автономно, аналогично<br />

предыдущему, до получения сегментов единичной длины.<br />

Тогда массив в целом будет отсортирован.<br />

Исходные значения ключей<br />

элементов массива агг[ i ].кеу<br />

(i = О, 1, 2, . ., size-1 ), size = 8<br />

left<br />

44 55 12 1 42 1 94 18 06<br />

п<br />

median<br />

median = arr[ (left+right )/2 ]<br />

Рис. 75. Идея алгоритма сортировки Хоора<br />

hpht<br />

67<br />

2. Как выполнить разделение на сегменты Решение этой<br />

задачи представлено на рис. 76.<br />

Анализ рисунка показывает, что за size сравнений {size - число<br />

элементов массива) исходный массив разделяется на два сегмента,<br />

которые можно сортировать автономно. Всего потребуется Xo^^i^ize)<br />

таких разделений, в результате которых получатся сегменты единичной<br />

длины. Следовательно, общее число сравнений пропорционально<br />

size • 1о§2 {size).<br />

259


left<br />

п<br />

median<br />

a)<br />

i = left, while( arr[ i ].key < median.key ) i++;<br />

п<br />

median Q)<br />

Всегда ли закончится цикл Да, всегда,<br />

поскольку, как минимум, справа есть median.<br />

После выхода из цикла получим<br />

агг[ I ] key >= median.key<br />

j = right; while( arr[ j ] key > median.key ) j—;<br />

right<br />

По тем же причинам, цикл будет<br />

заканчиваться всегда и после его завершения<br />

получим агг[ j ].кеу


Исходные значения ключей<br />

элементов массива arr[i] key<br />

(1 = 0,1, .., size-1), size = 8<br />

left<br />

right<br />

44 55 12 42 94 18 06 67<br />

4 5 6 7<br />

median=arr[(left+right)/2]<br />

a) Разбиение исходного массива<br />

1=0, j=7,6; arr[0] меняем местами с arr[6], i++; j--, i


в) Больший из полученных сегментов (левый) запоминаем в стеке для<br />

последующего разбиения. Работа с правым (меньшим) сегментом<br />

закончена, так как в нем один элемент. Извлекаем из стека и разбиваем<br />

очередной сегмент<br />

left right<br />

arr[j] key 06 12<br />

median<br />

i=0; j=1,0; arr[0] меняем местами с arr[0]; i++, j-<br />

i>j (разбиение на сегменты закончено). Оба<br />

полученных сегмента единичной или нулевой<br />

А^'^чь! и работа с ними закончена<br />

г) Извлекаем из стека и разбиваем очередной сегмент<br />

left<br />

right<br />

i=4; j=7,6; arr[4] меняем местами с агг[6];<br />

агг[1].кеу 94 55 44 67 i++; j—; ij (разбиение на сегменты<br />

закончено).<br />

Получаем левый left..j и правый 1..right<br />

(большего размера) сегменты. Больший<br />

сегмент запоминаем в стеке для<br />

последующего разбиения, а работа с<br />

меньшим сегментом закончена (в нем один<br />

элемент).<br />

д) Извлекаем из стека и разбиваем очередной сегмент<br />

left right<br />

arr[i].key 94 67<br />

7<br />

median<br />

i=6; j=7; агг[6] меняем местами с агг[7]; i++; j—;<br />

i>j (разбиение на сегменты закончено). Оба<br />

полученных сегмента имеют единичную длину и<br />

работа с ними закончена.<br />

Так как в стеке нет больше сегментов для<br />

разбиения,то и сортировка закончена:<br />

06 12 18 42 44 55 67 94<br />

0 1 2 3 4 5 6 7<br />

Прод. рис. 77 •<br />

Какой должна быть глубина стека М Величина М зависит от<br />

порядка разбиения полученных сегментов. Н. Вирт показал, что если<br />

в стек помещать более длинный из получившихся сегментов, а с<br />

коротким сегментом сразу "расправляться", то<br />

М =<br />

\0g2isize),<br />

где size - размер сортируемого массива.<br />

262


Пример 2<br />

Исходные значения ключей элементов<br />

массива агг[ i ].кеу (i = О, 1, .. , size-1), size =<br />

left<br />

nght<br />

3 2 - 7 5 4<br />

О<br />

Пример 3<br />

Исходные значения ключей элементов<br />

массива агг[ i ].кеу (i = О, 1, ..., size-1), size =<br />

Пример 4<br />

Исходные значения ключей элементов<br />

массива агг[ 1 ].кеу (1 = О, 1, ..., slze-1), size =<br />

median=arr[(left+right)/2]<br />

left<br />

right<br />

3 2 5 - 7 4<br />

1<br />

median=arr[(left+right)/2]<br />

left<br />

right<br />

3 2 4 - 7 5<br />

0 1<br />

, median=arr[(left+right)/2]<br />

Рис. 78. Частные случаи для сортировки Хоора<br />

Если, в целях унификации, границы исходного массива также<br />

заносить в стек, то<br />

Л/ = 1 + \og2(size).<br />

При занесении в стек сделаем так, что операция занесения не<br />

будет выполняться, если длина заносимого сегмента меньше или<br />

равна единице.<br />

Прототипы функций для занесения границ сегментов в стек<br />

(Push) и извлечения границ сегментов из стека (Pop), определения<br />

функций и примеры их вызова даны в примере программы из подразд.<br />

15.2. Эти функции являются служебными для нерекурсивной<br />

функции Quicksort. Подобные функции, как указывалось выше, называют<br />

неинтерфейсными функциями.<br />

5. Нерекурсивная сортировка Хоора, Прототип функции<br />

Quicksort, ее определение и пример вызова даны в примере программы<br />

из подразд. 15.2. Эта функция является интерфейсной функцией<br />

и может использоваться для сортировки массива.<br />

6. Рекурсивная сортировка Хоора.<br />

Напомним ваэюнейшие особенности рекурсии:<br />

1. При рекурсивных вызовах функции создаются поколения<br />

263


вызовов-функций. Это означает, что имеются вложенные друг в друга<br />

активные экземпляры рекурсивной функции.<br />

2. Каждый рекурсивный вызов помещает в системный стек:<br />

копии параметров рекурсивной функции, передаваемых по<br />

значению;<br />

адреса аргументов из вызова рекурсивной функции, соответствующих<br />

параметрам, передаваемым по ссылке;<br />

ее автоматические переменные;<br />

адрес возврата из функции;<br />

возвращаемое значение, если оно имеется.<br />

По этой причине в рекурсивной функции лучше иметь меньше параметров<br />

и автоматических переменных (помните, что поколений<br />

активных экземпляров рекурсивной функции может быть много).<br />

3. На рекурсивный вызов тратится больше времени, но зато<br />

программа получается нагляднее и проще.<br />

С учетом этих особенностей и была спроектирована рекурсивная<br />

сортировка Хоора. Прототип функции QuickSortl для рекурсивной<br />

сортировки Хоора, ее определение и пример вызова даны в<br />

примере программы из подразд. 15.2. Эта функция является интерфейсной<br />

и предназначена для сортировки массивов. Для разделения<br />

исходного сегмента на подсегменты функция Quicksort 1 использует<br />

служебную рекурсивную функцию Split. Ее прототип, определение и<br />

пример вызова даны также в примере программы из подразд. 15.2.<br />

Функция Split является рекурсивной и именно при ее разработке были<br />

учтены перечисленные выше важные особенности рекурсивных<br />

функций.<br />

15.9. Сравнительные показатели производительности<br />

различных методов сортировки массивов<br />

Приводимые ниже в табл. 29 данные получены для программы,<br />

написанной на языке Паскаль (ЭВМ SDS6400) для неупорядоченных<br />

массивов.<br />

Из приведенных в таблице данных следует, в частности, что<br />

даже для массива относительно небольшого размера из 512 элементов:<br />

1. Худшая по производительности из простых сортировок<br />

(простая сортировка обменом) работает в 35 раз медленнее быстрой<br />

сортировки Хоора.<br />

2. Самая быстрая из простых сортировок (простая сортировка<br />

вставками) работает медленнее в 4,2 раза, чем самая худшая по про-<br />

264


изводительности из сложных сортировок (сортировка Шелла).<br />

При увеличении размеров массива, указанные в пп. 1 и 2<br />

фекты проявляются еще в большей степени.<br />

Табл. 29. Сравнительные показатели производительности различных<br />

методов сортировки массивов<br />

Вставками<br />

Метод<br />

сортировки<br />

Простые методы сортировки<br />

Время<br />

сортировки<br />

для<br />

size==256,<br />

милисекунд<br />

356<br />

Время сортировки<br />

для<br />

size=512, милисекунд<br />

1444<br />

эф-<br />

Соотношение методов<br />

по производительности<br />

(относительное время<br />

сортировки)<br />

1<br />

Выбором<br />

509<br />

1956<br />

1.3<br />

Обменом<br />

1026<br />

4054<br />

3<br />

Обменом (Хоора)<br />

Выбором (с помощью<br />

двоичного дерева)<br />

Вставками (Шелла)<br />

Сложные методы сортировки<br />

60<br />

116<br />

НО<br />

241<br />

127<br />

349<br />

1 1<br />

1.7<br />

2.1


16. ГРАФЫ- ТРАНСПОРТНАЯ ЗАДАЧА<br />

(ЗАДАЧА КОММИВОЯЖЕРА)<br />

16,1. Терминология<br />

Граф - это пара (К, /), где V - конечное непустое множество<br />

вершин, а R -множество неупорядоченных пар вершин из<br />

множества К, называемых ребрами. Говорят, что ребро г — <br />

соединяет вершины "а" и "6". Ребро 'V" и вершина "


{a,b) G R тогда и только тогда, когда {Ь,а) е R<br />

а b а b<br />

О Ю о о<br />

а - предшественник вершины "Ь"<br />

b - преемник вершины "а"<br />

а) б)<br />

Рис. 80. Граф:<br />

а) ориентированный (направленный);<br />

б) неориентированный<br />

Путь в неориентированном графе, соединяющий вершины "а"<br />

и "Z", - это последовательность вершин Vo,v,,...,i/„(«>0) такая, что<br />

v/Q=a,v„=6, а для любого /(0


100 10.0<br />

10.0<br />

Рис. 81. Пример взвешенного неориентированного графа<br />

16.2. Формы задания графа<br />

Используются две основные формы:<br />

1. С помощью матрицы инциденций (соединений):<br />

а) для неориентированного взвешенного графа (рис. 82 а) мггтриц|^<br />

инциденций имеет число строк и столбцов, равное числу вершин,<br />

и симметрична;<br />

б) для ориентированного взвешенного графа матрица соединений<br />

не симметрична (рис. 82 б).<br />

а<br />

о<br />

20 b<br />

-О<br />

а<br />

о<br />

а О 20 а<br />

ь| 20 I О I b 0<br />

а b а<br />

а) б)<br />

Рис. 82. Способы задания графа<br />

2. С помощью списка ребер:<br />

0<br />

20<br />

20<br />

0<br />

b<br />

-ю<br />

±nt<br />

Num Top ,<br />

NumArc;<br />

// Число вершин<br />

// Число ребер<br />

\сЬ<br />

А<br />

// Ребро графа<br />

};<br />

±nt<br />

±nt<br />

float<br />

first;<br />

last /<br />

weight/<br />

// 1-я вершина ребра<br />

// 2-я вершина ребра<br />

// Вес ребра<br />

268


Адрес первого элемента массива структур с информацией о<br />

// ребрах графа<br />

А<br />

*рАгс;<br />

Задание графа на основе списка ребер удобйо свести в одну<br />

структуру:<br />

// Структурный тип для графа<br />

struct GRAPH<br />

{<br />

} ;<br />

±nt NumTop; // Число вершин<br />

Izit NumArc; // Число ребер<br />

А *рАгс; // Указатель на начало массива ребер<br />

// в динамической памяти<br />

Сопоставление указанных форм задания графа позволяет<br />

заключить следующее.<br />

1. Использование матрицы соединений требует хранения в памяти<br />

NumTop*NumTop элементов.<br />

2. Использование списка ребер требует хранения в памяти<br />

Ъ"^NumArc элементов.<br />

3. При Ъ'^NumArc < NumTop"^NumTop эффективнее использовать<br />

задание графа с помощью списка ребер.<br />

4. Использование списка ребер алгоритмичнее. Это означает,<br />

что алгоритмы решения задач с использованием графов, заданных<br />

списком ребер, проще и эффективнее.<br />

Для примера, приведенного на рис. 81, информация для списка<br />

ребер имеет следующий вид:<br />

1г 2,<br />

i, 4г<br />

2, 3,<br />

2г 5,<br />

3, 4,<br />

4, 5,<br />

80. .0<br />

10. .0<br />

20. ,0<br />

10. .0<br />

20. О<br />

10. 0<br />

Здесь в первой строке 1 и 2 - номера вершин, а 80.0 - вес соединяющего<br />

их ребра.<br />

16.3. Почему для решения задачи подходит<br />

рекурсивный алгоритм<br />

в общем случае путей из вершины start до вершины finish может<br />

быть несколько, но только один путь будет наилучшим (в частном<br />

случае, путь может вообще не существовать, например, в несвязанном<br />

графе, или может быть несколько наилучших, эквивалент-<br />

269


ных путей). Нас при поиске оптимального пути интересует на каждом<br />

этапе только один путь, а не все сразу, т.е. требуется последовательный<br />

перебор путей. Из информации на рис. 83 следует, что для<br />

перебора путей хорошо подходит рекурсивный алгоритм (аналогия с<br />

вычислением факториала).<br />

р,<br />

Вершина<br />

Путь<br />

Ребро<br />

Рис. 83. Рекурсивный перебор путей<br />

•<br />

16.4. Представление кратчайшего пути<br />

до каждой вершины<br />

Сведения о любом пути должны содержать следующую информацию.<br />

1. Имеется ли путь до вершины графа<br />

2. Суммарный вес пути, начиная от заданной начальной вершины<br />

(start)<br />

Но только этих сведений недостаточно, так как нет указаний,<br />

откуда и как двигаться. Для задания недостающих сведений можно<br />

организовать линейный список, который для графа, представленного<br />

выше на рис. 81, будет иметь вид, показанный на рис. 84 а. Такой<br />

линейный список можно организовать на базе массива структур<br />

(рис. 84 б).<br />

stmjct W<br />

{<br />

int<br />

±nt<br />

} ;<br />

float<br />

// Путь до одной вершины<br />

exist; // (!= 0) - путь имеется<br />

ref; // Предыдущая вершина^ через<br />

// которую проходит путь<br />

SumDlst; // Суммарная длина минимального пути<br />

// Адрес первого элемента массива с информацией о минимальном<br />

// пути между заданными вершинами<br />

W<br />

*pMinWay/<br />

270


start finish start finish<br />

1 4 5 2<br />

1 2<br />

1<br />

1<br />

1<br />

1<br />

exist<br />

1<br />

1<br />

3<br />

1<br />

4<br />

1<br />

5<br />

1<br />

0.0<br />

10.0<br />

20.0<br />

30.0<br />

SumDist<br />

0.0<br />

30.0<br />

30.0<br />

10.0<br />

20 0<br />

0<br />

1<br />

4<br />

5<br />

ref<br />

0<br />

5<br />

4<br />

1<br />

4<br />

J t i<br />

t<br />

(REFerence - ссылка): 0 - конец списка<br />

a) 6)<br />

Рис. 84. Представление кратчайшего пути между вершинами:<br />

а) с помощью линейного списка;<br />

б) на базе массива структур<br />

16.5. Как найти минимальный путь<br />

16.5.1. Требуется ли полный перебор путей<br />

При поиске минимального пути часть путей можно отбросить<br />

(рис. 85). Попытки, представленные на этом рис., неуместны, если,<br />

допустим, существует<br />

pMinWayf 18 ].SumDist = 50.0 и pMinWayf 18 ].exist != О<br />

Текущий путь длиной 200.0<br />

•в;.-;<br />

finish<br />

•о<br />

Рис. 85. Поиск минимального пути<br />

16.5.2. Организация перебора путей<br />

Как уже указывалось, для этой цели хорошо подходит рекурсивный<br />

алгоритм. Рассмотрим, как можно пройти отрезок пути от<br />

достигнутой промежуточной вершины (intermediate) до финиша (finish)<br />

- рис. 86.<br />

271


start<br />

а) intermediate = finish<br />

О •<br />

intermediate<br />

Вершина<br />

Конец<br />

О<br />

finish<br />

б) intermediate != finish<br />

intermediate<br />

•>\ Ребро Путь<br />

Рис. 86. Прохождение пути от достигнутой вершины до финиша<br />

Попытку шага вперед из достигнутой вершины по заданному<br />

ребру будем делать с помощью функции ForStep, а прохождение пути<br />

(если он есть) от достигнутой вершины до вершины finish - с помощью<br />

функции Pass Way (взаимно рекурсивный вызов PassWay -<br />

For Step).<br />

Для решения транспортной задачи спроектируем программу и<br />

в ней разработаем функции PassWay -ForStep. Спецификация функции,<br />

выполняющей шаг вперед, представлена на рис. 87. Обратите<br />

внимание, что в список параметров этой функции включены только<br />

три параметра - topl^ IndArc, top2. Это важно, так как для<br />

рекурсивных функций, как это было показано выше, число<br />

параметров следует минимизировать. Поэтому Gr, pMinWay определены<br />

как глобальные объекты. Исходный текст программы для<br />

решения транспортной задачи, включающий определение функции<br />

ForStep., приведен ниже. На данном этапе в этом тексте рекомендуем<br />

рассмотреть только введенные типы, данные и определения всех<br />

функций, кроме solution и PassWay. Указанные в конце функции будут<br />

рассмотрены позже.<br />

Достигнутая вершина int top1 -<br />

Индекс ребра, по которому<br />

шагаем<br />

int IndArc-<br />

Вершина на конце ребра int top2 -<br />

Граф<br />

GRAPH Gr-<br />

ForStep<br />

W *pMin\/Vay<br />

Массив с информацией<br />

о наилучшем<br />

пути<br />

input<br />

process<br />

output<br />

Рис. 87. Спецификация функции, выполняющей шаг вперед по ребру<br />

Обратите также внимание на то, что функция ForStep является<br />

служебной функцией и вызывается из функции PassWay, которая, в<br />

свою очередь, вызывается из функции solution.<br />

Ill


Файл TestGr.cpp. Тестирование решения транспортной задачи<br />

с размещением данных в динамической памяти.<br />

Определение функций^ используемых при решении транспортной<br />

задачи, приведено в файле Graph.срр.<br />

Определение Функций размеш,ения данных в динамической памяти<br />

и освобождения занятой динамической памяти находится в<br />

файле GrAlocFree.срр.<br />

Включаемый файл программного проекта находится в файле<br />

GrHead.h.<br />

Давыдов В.Г. Консольное приложение, Visual C-f-f- 6<br />

V<br />

// Включаемый файл программного проекта для решения<br />

// транспортной задачи<br />

^include "GrHead.h"<br />

±nt main ( // Возвраш,ает О при успехе<br />

±пЬ АгдС, // Число аргументов в командной<br />

// строке<br />

cha.r *ArgV[ ] ) / / Массив указателей на аргументы<br />

// командной строки<br />

{<br />

// Проверка числа аргументов командной строки<br />

±f( ArgC /= 3 )<br />

{<br />

printf(<br />

"\п Ошибка 5. В командной строке должно быть три аргумента:<br />

"\п Имя__проекта. ехе имя_файла_ввода имя_файла_вывода \п" ) ,<br />

exit ( 5 ) ;<br />

}<br />

// Чтение информации о графе<br />

ReadGraph ( ArgV[ 1 ] ) ;<br />

// Печать информации о графе<br />

WriteGraph( ArgV[ 2 ], "w" ) ;<br />

solution ( ) ; // Решение транспортной задачи<br />

// Вывод результатов решения<br />

OutRes ( ArgV[ 2 ], "а" ) ;<br />

}<br />

•returri Or<br />

Файл GrHead.h. Подключение стандартных заголовочных файлов,<br />

объявление используемых структурных типов, объявление<br />

объектов с описателями класса хранения "внешний" и прототипов<br />

функций. Используется как заголовочный файл в программном<br />

проекте для решения транспортной задачи (задачи коммивояжера)<br />

.<br />

Давыдов В.Г. Консольное приложение. Visual C++ 6<br />

273


_v<br />

// Предотвращение возможности многократного подключения<br />

iifndef GRHEAD_H<br />

^define GRHEAD Н<br />

274<br />

^include<br />

^include<br />

<br />

<br />

// Для ввода-вывода<br />

// Для exit ( )<br />

// Структурный тип для ребра графа<br />

stjnzct А<br />

{<br />

} ;<br />

±nt<br />

±nt<br />

£1олt<br />

first/<br />

last;<br />

weight;<br />

// Структурный тип для графа<br />

strvLcb GRAPH<br />

{<br />

};<br />

±nt<br />

±nt<br />

A<br />

NumTop;<br />

NumArc;<br />

*pArc;<br />

// 1-я вершина ребра<br />

// 2-я вершина ребра<br />

// Вес ребра<br />

// Число вершин<br />

// Число ребер<br />

// Указатель на начало массива ребер<br />

// в динамической памяти<br />

// Структурный тип пути до одной вершины<br />

struct W<br />

{<br />

}.<br />

int<br />

xnt<br />

exist;<br />

ref;<br />

//<br />

//<br />

(!=0) в графе имеется путь<br />

до вершины<br />

// Предыдущая вершина, через которую<br />

// проходит путь до данной вершины<br />

£loa,t SumDlst; // Суммарная длина минимального пути<br />

// до данной вершины<br />

// Объявления внешних объектов<br />

extern GRAPH<br />

Gr; // Граф<br />

// Указатель на массив структур для хранения информации о<br />

// минимальном пути от start до finish<br />

extern W *pMlnWay;<br />

extern int finish; // Вершина - финиш пути<br />

extern int start; // Вершина - старт пути<br />

// Прототипы функций<br />

void GrAllocDM( void, ) ;<br />

void GrFreeDM( void ) ;<br />

void ForStep ( int, int, int ) ;<br />

void PassWay ( int ) ;<br />

void solution ( void ) ;<br />

void OutRes ( char* *, char *pMode ) ;<br />

void ReadGraph ( char *pFlleInp ) ;


^endif<br />

void WriteGraph ( char ^pFileOut, char *pMode ) ;<br />

Файл GrInpOut.срр. Функции файлового чтения данных о графе<br />

и печати их.<br />

Используется в программном проекте для решения транспортной<br />

задачи (задачи коммивояжера) .<br />

Давыдов В.Г, Консольное приложение. Visual C++ 6<br />

V<br />

// Включаемый файл программного проекта для решения<br />

// транспортной задачи (задачи коммивояжера)<br />

#include "GrHead,h"<br />

// Чтение данных о графе<br />

void ReadGraph (<br />

// Указатель на файл данных<br />

char "^pFilelnp )<br />

{<br />

FILE *pStrInp; // Указатель на структуру со<br />

// сведениями о файле ввода<br />

±пЬ i , // Индекс ребра<br />

RetCode, // Возвращаемые значения fscanf( )<br />

// или их сумма<br />

RetCodel; // Возвращаемое значение fclose( )<br />

// Открытие файла ввода<br />

pStrlnp = fopen( pFilelnpr "г" ) ;<br />

±£( pStrlnp == NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 50, Файл %s для чтения не "<br />

"открыт \п", pFilelnp ) ;<br />

exit ( 50 ) ;<br />

// Чтение количества вершин и количества ребер<br />

RetCode = fscanf( pStrlnp, " %d", &Gr,NumTop ) ;<br />

±f( RetCode != 1 )<br />

(<br />

printf ( "\n Ошибка 60, Ошибка при чтении числа "<br />

"верш^^н графа \п" ) ;<br />

exit ( 60 ) ;<br />

}<br />

RetCode = fscanf ( pStrlnp, " %d", &Gr,NumArc ) ;<br />

±f( RetCode 1=1)<br />

{<br />

}<br />

printf ( "\n Ошибка 70, Ошибка при чтении числа "<br />

"ребер графа \п" ) ;<br />

exit ( 70 ) ;<br />

275


Размещение структур данных графа в динамической памяти<br />

GrAllocDM( ) ;<br />

// Заполнение массива ребер графа<br />

Ret Code = 0;<br />

£ою ( i = 0; 1 < Gr.NumArc; i + -h )<br />

{<br />

RetCode += fscanf( pStrlnp, " %d %d %g",<br />

&Gr.pArc[ i ].first, &Gr.pArc[ 1 J.last,<br />

&Gr.pArc[ 1 ].weight ) ;<br />

±f( ( Gr.pArcl i ].first < 0 ) \\<br />

( Gr.pArcl i ].first >= Gr.NumTop ) ||<br />

( Gr.pArc[ i ].last < 0 ) \\<br />

( Gr.pArcf 1 ].last >= Gr.NumTop ) )<br />

{<br />

printf( "\n Ошибка 75. Индексы вершин д.б. в "<br />

"диапазоне О. . %d \л", Gr.NumTop~l ) ;<br />

exi t ( 75 ) /<br />

}<br />

}<br />

±£( RetCode < 3*Gr.NumArc )<br />

{<br />

printf( "\n Ошибка 80. Ошибка чтения элементов "<br />

"массива ребер \п" ) ;<br />

exit ( 80 ) ;<br />

}<br />

// Чтение информации о вершинах - старте и финише пути<br />

RetCode = fscanf ( pStrlnp, " %d", &start ) ;<br />

±£( RetCode /= 1 )<br />

{<br />

printf( "\n Ошибка 90. Ошибка при чтении начальной"<br />

" вершины пути \п" ) ;<br />

exit ( 90 ) ;<br />

}<br />

RetCode = fscanf ( pStrlnp, " %d", & finish ) ;<br />

±£( RetCode != 1 )<br />

(<br />

printf( "\n Ошибка 100. Ошмбка при чтении конечной"<br />

" вершины пути \п" ) ;<br />

exit ( 100 ) ;<br />

}<br />

// Закрытие файла ввода<br />

RetCodel = fclose( pStrlnp ) ;<br />

±f( RetCodel == EOF )<br />

{<br />

printf( "\n Ошибка 110.<br />

pFilelnp ) ;<br />

Ошибка закрытия файла %s \п",<br />

exit ( 110 ) ;<br />

}<br />

xretuxm/<br />

276


Печать данных о графе<br />

void. WriteGraph (<br />

char *pFileOut,// Указатель на файл результатов<br />

char *pMode ) // Указатель на режим открытия файла<br />

{<br />

FILE *pStrOut/ // Указатель на структуру со<br />

// сведениями о файле результатов<br />

Int i, // Индекс элемента массива ребер<br />

RetCodel; // Возвращаемое значение fclose( )<br />

// Открытие файла вывода<br />

pStrOut = fopen( pFileOut, pMode );<br />

±f( pStrOut == NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 120, Файл %s для вывода не "<br />

"открыт \л", pFlleOut );<br />

exit ( 120 ) ;<br />

// Печать информации о графе<br />

fprlntf ( pStrOutг "\п Число вершин графа: %d,"<br />

" число ребер: %d \п"г Gr.NumTop, Gr.NumArc ) ;<br />

fprlntf ( pStrOutr<br />

" \j^* * * * * -^ * * * * * * * * * -^ * * * * * * * * * * * * * * * * * * "^ * * * "^^ * * * * * * * * * * * * *<br />

"\n Индекс ребра 1-я вершина 2-я вершина Вес ребра"<br />

"\п********************^**************************^*********"<br />

"\п" ) ;<br />

tori 1 = о; 1 < Gr.NumArc/ i + -i- )<br />

{<br />

}<br />

fprlntf( pStrOut, "%10d %14d %14d %17f \л", i,<br />

Gr.pArcf 1 ].first, Gr.pArc[ 1 ],lastr<br />

Gr.pArcf i ].weight );<br />

// Закрытие файла вывода<br />

RetCodel = fclose( pStrOut );<br />

±f( RetCodel -= EOF )<br />

{<br />

}<br />

printf( "\n Ошибка 150. Файл %s не закрыт \n ",<br />

pFileOut );<br />

exit ( 150 );<br />

}<br />

return;<br />

Файл GrAlocFree. cpp. Размеш,ение массива с информацией о<br />

ребрах графа в динамической памяти и освобождение динамической<br />

памяти, занятой этим массивом.<br />

Используется в программном проекте для решения транспортной<br />

задачи.<br />

277


_V<br />

Давыдов В.Г. Консольное приложение^ Visual C++ 6<br />

// Включаемый файл программного проекта для решения<br />

// транспортной задачи (задачи коммивояжера)<br />

#include "GrHead.h"<br />

j<br />

// Размещение массива с информацией о ребрах графа в<br />

// динамичнеской памяти<br />

void. GrAllocDM( void )<br />

{<br />

// Gr.NumTop - число вершин графа (определяется на<br />

// внешнем уровне)<br />

// Gr. NumArc - число ребер графа (определяется на внешнем<br />

// уровне)<br />

// Gr.pArc - указатель на начало массива с информацией о<br />

// ребрах^ размещенного в динамической памяти<br />

// (определяется на внешнем уровне)<br />

// pMinWay - указатель на начало массива с информацией о<br />

// наилучшем пути, размещенного в динамической<br />

// памяти (определяется на внешнем уровне)<br />

// Контроль корректности количества вершин графа<br />

i£( Gr.NumTop < 2 )<br />

{<br />

printf(<br />

"\п Предупреждение 10. Число вершин графа должно быть более"<br />

" одной вершины"<br />

"\п (задано число вершин, равное %d) . Принимается число"<br />

" вершин, равное 2."<br />

"\п Выполнение программы продолжается ", Gr.NumTop ) ;<br />

Gr.NumTop = 2;<br />

}<br />

// Контроль корректности количества ребер графа<br />

if( Gr. NumArc < 1 )<br />

{<br />

printf(<br />

"\n Предупреждение 20. Число ребер графа должно быть не"<br />

" менее одного ребра"<br />

"\п (задано число ребер, равное %d) . Принимается число"<br />

" ребер, равное 1. "<br />

Gr.NumArc = 1;<br />

}<br />

// Размещение массива ребер в динамической памяти<br />

Gr.pArc = new А[ Gr. NumArc ] ;<br />

if( Gr.pArc == NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 30. Массив ребер в динамической"<br />

" памяти не размещен " ) ;<br />

exit ( 30 ) ;<br />

11^


Размещение массива структур с информацией о наилучшем<br />

// пути в динамической памяти<br />

pMlnWay = new W[ Gr. NumTop ] ;<br />

±f( pMinWay == NULL )<br />

{<br />

printf(<br />

"\n Ошибка 40. Массив структур с информацией о наилучшем"<br />

" пути в \п"<br />

"\п динамической памяти не размещен " ) ;<br />

exit ( 4 0 ) ;<br />

}<br />

}<br />

// Инициализация массивов, размещенных в динамической<br />

// памяти, нулевыми значениями<br />

for( Int 1 = О; 1 < Gr.NumArc; i++ )<br />

{<br />

Gr.pArcf i J.firSt = 0; Gr.pArcf i ],last = 0;<br />

Gr.pArc[ 1 ].weight = O.Of;<br />

}<br />

£or( 1 = 0; 1 < Gr. NumTop/ 1 + + )<br />

{<br />

}<br />

return;<br />

pMlnWayf 1 ]. exist = 0; pMinWayf i ].ref = 0/<br />

pMinWay[ i ] . SumDist = 0.0;<br />

// Освобождение динамическом памяти, занятой массивами ребер<br />

// и структур с информацией о наилучшем пути<br />

void. GrFreeDM( void )<br />

{<br />

i£( Gr.pArc /= NULL )<br />

{<br />

}<br />

delete [ ] Gr.pArc; Gr.pArc = NULL;<br />

if( pMinWay != NULL )<br />

{<br />

delete [ ] pMinWay; pMinWay = NULL;<br />

}<br />

}<br />

return;<br />

Файл Graph. cpp.<br />

Функции решения транспортной задачи:<br />

* шаг вперед из достигнутой вершины по заданному ребру;<br />

* прохождение пути от достигнутой вершины InterMediate, если<br />

он есть, до вершины finish;<br />

* поиск пути минимального веса в неориентированном взвешенном<br />

279


графе;<br />

* печать информации о наилучшем пути от start до finish.<br />

Используется в программном проекте для решения транспортной<br />

задачи (задачи коммивояжера). |<br />

Давыдов В.Г, Консольное приложение^ Visual C++ 6<br />

'^/<br />

I<br />

// Включаемый файл программного проекта для решения транспортной<br />

задачи (задачи коммивояжера)<br />

#include "GrHead.h"<br />

// Определения объектов с описателем класса хранения внешний.<br />

// Их объявление имеется в заголовочном файле проекта и<br />

// доступно в других файлах проекта<br />

Inb start; // Вершина - старт пути<br />

// Эти объекты определяем в данном месте^ чтобы при<br />

// взаимнорекурсивных вызовах функций PassWay( ) и<br />

// ForStep ( ) они не создавались заново<br />

GRAPH Or; // Граф<br />

W *pMinWay; // Указатель на массив структур с<br />

// информацией о наилучшем пути из<br />

// вершины start в finish<br />

Int finish, // Вершина - финиш пути<br />

one, // 1-я вершина текущей дуги<br />

two/ // 2-я вершина текущей дуги<br />

// Шаг вперед по ребру с индексом IndArc из вершины topi в<br />

// вершину top2<br />

void. ForStep (<br />

±пЬ topi, // Достигнутая вершина, из которой<br />

// шагаем вперед<br />

int IndArc, // Индекс ребра, по которому<br />

// делается шаг вперед<br />

int top2 ) // Вершина на конце ребра<br />

{<br />

£2.аа.Ь NewDist; // Расстояние до top2 по пути через<br />

// topi<br />

NewDist = pMinWayl topi ] . SumDist +<br />

Or.pArc[ IndArc ].weight;<br />

±f( !pMinWay[ top2 ]. exist )<br />

{ // Пока пути до top2 нет<br />

pMinWayl top2 ].exist = 1;<br />

pMinWay[ top2 ]. SumDist = NewDist/<br />

pMinWayf top2 J.ref = topi/ PassWay ( top2 ) /<br />

}<br />

else<br />

{ // Путь до top2 уже существует<br />

if( pMinWay[ top2 ]. SumDist > NewDist )<br />

{ // Новый путь короче<br />

pMinWayl top2 ].SumDist = NewDist/<br />

280


}<br />

}<br />

pMinWayl top2 J.ref = topi; PassWay( top2 ) ;<br />

}<br />

return/<br />

// Прохождение пути от достигнутой вершины InterMedlate, если<br />

// он есть г до вершины finish<br />

void. PassWay (<br />

// Достигнутая вершина - отправная точка пути<br />

Int InterMedlate )<br />

{<br />

±nt к; // Индекс текущей дуги графа<br />

}<br />

±f( InterMedlate == finish )<br />

{ // ! ! ! Выход из рекурсии<br />

return/<br />

}<br />

else<br />

{<br />

// Перебор ребер графа<br />

£ог( к == О/ к < Gr.NumArc/ к++ )<br />

{<br />

one = Gr.pArc[ к ]. first/<br />

two = Gr.pArc[ к J.last/<br />

// Определения направления шага по ребру и<br />

// выполнение шага в найденном направлении<br />

±f( one == InterMedlate )<br />

{<br />

ForStep( one г к, two )/<br />

}<br />

}<br />

else ±f( two == InterMedlate )<br />

(<br />

ForStep ( two, k, one ) ;<br />

}<br />

}<br />

return/ // !!! Альтернативный вариант выхода<br />

// из рекурсии<br />

// Поиск пути минимального веса в неориентированном<br />

// взвешенном графе<br />

void, solution ( void )<br />

{<br />

int j / // Индекс вершины<br />

// Начальная подготовка массива структур с информацией о<br />

// наилучшем пути<br />

fori j = О/ j < Gr.NumTop/ j++ )<br />

{<br />

281


}<br />

pMinWayf j ], exist = 0;<br />

pMinWay [ start ]. exist = 1;<br />

pMinWay[ start ] . SumDist = O.Of;<br />

pMinWay[ start J.ref = -1;<br />

// Рекурсивное определение требуемого пути<br />

PassWay( start );<br />

}<br />

return/<br />

// Печать информации о наилучшем пути от start до finish<br />

void. OutRes (<br />

char *pFileOut, // Указатель на файл вывода<br />

char *pMode ) // Указатель на режим вывода в файл<br />

{<br />

FILE *pStrOut; // Указатель на структуру со<br />

// сведениями о файле результатов<br />

±пЬ TempTopf // Текуш,ая вершина пути<br />

RetCodel; // Возвраш;аемое значение fclose ( )<br />

// Открытие файла вывода<br />

pStrOut = fopen( pFileOut^ pMode );<br />

±f( pStrOut == NULL )<br />

{<br />

)<br />

printf( "\n Ошибка 140. Файл %s для вывода не "<br />

"открыт \л", pFileOut );<br />

exit ( 140 ) ;<br />

// Печать информации о найденном пути<br />

±f( !pMinWay[ fini'Sh ],exist )<br />

{<br />

printf (<br />

"\n Искомого пути не существует \п" ) ;<br />

}<br />

else<br />

{<br />

// Печать оптимального пути<br />

ТетрТор = finish/<br />

fprintf( pStrOut,<br />

"\п Вершина-финиш: %d, вершина - старт: %d "<br />

"\л Значение минимального пути: %д \л", finish,<br />

start, pMinWay[ finish ].SumDist )/<br />

fprintf ( pStrOut, "\n Список вершин, образуюш:их"<br />

" этот путь (от finish до start): \п" )/<br />

while ( ТетрТор != -1 )<br />

{<br />

fprintf( pStrOut, " %4d ", ТетрТор )/<br />

Temp Top = pMi nWay [ Temp Top ] . ref/<br />

}<br />

}<br />

282


Закрытие файла результатов<br />

RetCodel = fclose( pStrOut ) ;<br />

±f( RetCodel == EOF )<br />

{<br />

printf( "\n Ошибка 150. Файл %s не закрыт \n",<br />

pFileOut ) ;<br />

exit ( 150 ) ;<br />

}<br />

// Освобождение динамической памяти<br />

GrFreeDM( ) ;<br />

retujcn/<br />

5 6<br />

О 1 80<br />

0 3 10<br />

1 2 20<br />

1 4 10<br />

2 3 20<br />

3 4 10<br />

О 1<br />

При файле исходных данных<br />

получаем решение транспортной задачи в файле результатов в<br />

следующем виде:<br />

Число вершин графа: 5, число ребер: 6<br />

Индекс ребра 1-я вершина 2-я вершина Вес ребра<br />

0 О<br />

1 О<br />

2 1<br />

3 1<br />

4 2<br />

5 3<br />

В ерши на - финиш: 1, в ерши на - с тар т: О<br />

Значение минимального пути: 30<br />

Список вершин, образуюш;их этот путь (от finish до start) :<br />

1 , 4 3 О<br />

Спецификация функции прохождения пути от достигнутой<br />

вершины до finish, если он есть, представлена на рис. 88.<br />

1<br />

3<br />

2<br />

4<br />

3<br />

4<br />

80. ,000000<br />

10. ,000000<br />

20. ,000000<br />

10. ,000000<br />

20. ,000000<br />

10. ,000000<br />

283


Достигнутая вершина - отправная точка<br />

пути<br />

int InterMediate<br />

Финиш пути<br />

Граф<br />

int finish<br />

GRAPH Gr<br />

PassWay<br />

input process output<br />

Рис. 88. Спецификация функции прохождения пути<br />

от достигнутой вершины j\o finish<br />

Необходимо обратить внимание, что в список параметров этой<br />

функции включен только один параметр - InterMediate- так как Gr,<br />

finish определены на внешнем уровне (повторяем, что для рекурсивных<br />

функций число параметров нуэюно минимизировать). Текст<br />

функции PassWay приведен выше. На данном этапе рекомендуем<br />

рассмотреть только функцию PassWay. Остальные функции будут<br />

рассмотрены позже. Данная функция, как и функция ForStep, является<br />

вспомогательной и вызывается из функции solution.<br />

Из приведенной программы следует, что взаимно-рекурсивный<br />

вызов функций выглядит следующим образом (рис. 89).<br />

Pass Way ( start);<br />

Выход (InterMediate == finish или обработаны все ребра графа)<br />

Рис. 89. Взаимно-рекурсивный вызов функций Pass Way-ForStep<br />

Спецификация функции solution для решения задачи в целом<br />

представлена на рис. 90.<br />

Вершина - старт<br />

пути int start<br />

Граф<br />

GRAPH Gr<br />

solution<br />

Массив с информацией о<br />

-• наилучшем пути<br />

W *pMinWay<br />

input<br />

process<br />

Рис. 90. Решение задачи в целом<br />

output<br />

Список параметров этой функции пуст. Объясняется это тем.<br />

284


что start, Gr, pMinWay являются глобальными объектами<br />

(определены на внешнем уровне). Текст программы, включающий<br />

определение функции solution, приведен выше. Данная функция, в<br />

отличие от предыдущих функций Pass Way и ForStep, является интерфейсной<br />

функцией и вызывается для решения транспортной задачи.<br />

16.6. Пример поиска минимального пути в графе<br />

Схема графа приведена на рис. 91.<br />

first last weight<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

0<br />

0<br />

1<br />

1<br />

2<br />

3<br />

1<br />

3<br />

2<br />

4<br />

3<br />

4<br />

80.0<br />

10.0<br />

20.0<br />

10.0<br />

20.0<br />

10.0<br />

Внимание! Нумерация вершин и<br />

ребер начинается с нуля, так как<br />

минимальный индекс элемента<br />

массива с языках Си/С++ равен нулю.<br />

Рис. 91. Пример схемы графа<br />

Состояние массиваpMinWay после подготовки в функции solution<br />

перед вызовом функции PassWay{ start ) показано на рис. 92.<br />

pMinWay<br />

1 1<br />

0.0<br />

-1<br />

0<br />

0.0<br />

0<br />

0<br />

0.0<br />

0<br />

0<br />

0.0<br />

start finish<br />

Рис. 92. Состояние массива,pMinWay<br />

0<br />

0<br />

0.0<br />

0<br />

Индексы вершин<br />

exist<br />

SumDist<br />

ref (REFerence - ссылка):<br />

-1 означает конец списка<br />

после начальной подготовки<br />

В качестве задания для самостоятельной работы предлагается<br />

проанализировать работу программы и убедиться, что в результате в<br />

массиве pMin Way получится информация, показанная на рис. 93.<br />

Важное замечание! В данном частном случае массивpMinWay<br />

указывает наилучшие пути от start до всех остальных вершин. Но в<br />

общем случае это не гарантируется. Гарантируется лишь оптималь-<br />

285


ность пути из start ъ finish.<br />

pMJnWay<br />

1<br />

0.0<br />

1<br />

30.0<br />

1<br />

30.0<br />

1<br />

10.0<br />

1<br />

20.0<br />

Индексы вершин<br />

exist<br />

SumDist<br />

-1<br />

start<br />

4<br />

finish<br />

3<br />

0<br />

3<br />

i'<br />

ref (REFerence - ссылка):<br />

-1 означает конец списка<br />

Рис. 93. Состояние массива/МшЖду после решения<br />

транспортной задачи<br />

16.7. Печать информации о наилучшем пути<br />

в рассмотренном примере получена информация о наилучшем<br />

пути между вершинами start \\ finish в обратном порядке (рис. 94).<br />

Рис. 94. Информация о наилучшем пути между<br />

вершинами start и finish<br />

Прототип и определение функции OutRes^ в которой производится<br />

печать информации о найденном оптимальном пути, приведены<br />

в подразд. 16.5.2. Там же приведен текст функции, выполняющей<br />

тестирование спроектированного класса, и результаты тестирования.<br />

Советуем внимательно изучить этот пример и поэкспериментировать<br />

с ним.<br />

При этом рекомендуем обратить внимание на следуюш^ие особенности<br />

рассмотренного программного проекта:<br />

1. Взаимно-рекурсивный вызов функций Pass fVay-ForStep (варианты<br />

завершения рекурсии в методе Pass Way; минимизация количество<br />

параметров и внутренних данных в этих методах; алгоритмическое<br />

решение, обеспечивающее получение решения транспортной<br />

задачи при неполном переборе путей между заданными вершинами).<br />

2. Структуру спроектированной программы.<br />

3. Оформление исходных текстов<br />

286


4. Терминологию при работе с графами.<br />

5. Практическую значимость решения транспортной задачи<br />

(получение оптимального пути между городами, связанными разветвленной<br />

системой дорог; определение оптимального маршрута<br />

между заданными пунктами в крупном городе и т.п.).


17. поиск<br />

Поиск, как и сортировка, может быть двух видов.<br />

1. Внутренний поиск - поиск в оперативной памяти, в таблице<br />

(т.е. в массиве).<br />

2. Внегиний поиск- поиск на внешней памяти (на магнитном<br />

диске или магнитной ленте).<br />

Рассмотрим широко распространенные задачи внутреннего<br />

поиска.<br />

17.1. Постановка задачи внутреннего поиска<br />

Таблица данных располагается в оперативной памяти и содержит<br />

некоторое количество строк, вид которых представлен на рис.<br />

95.<br />

8 байт 62 байта (LDATA-1), данное - другие сведения<br />

(LKEY-1,<br />

LengthKEY).<br />

ключ для<br />

поиска<br />

Рис. 95. Структура строки таблицы<br />

Строка таблицы может занимать, например, 70 байт памяти<br />

(см. рис. 95). Байт хранит код некоторого символа.<br />

Приведем несколько примеров таблиц такого рода.<br />

1. Русско-английский словарь. Ключ - русское слово, данное -<br />

соответствующее английское слово и другие сведения.<br />

2. Англо-русский словарь. Аналогично.<br />

3. Таблица домашних адресов: ключ - фамилия, другие сведения<br />

- домашний адрес и телефон.<br />

Для дальнейшего примем допущение, что ключ поиска может<br />

содержать только строчные буквы латинского алфавита, цифры и<br />

символ пробела, начинается с буквы, а символ пробела (символы<br />

пробелов) может (могут) быть только завершающим (завершающими).<br />

В кодовых таблицах буквы латинского алфавита упорядочены,<br />

заглавные (прописные) буквы предшествуют строчным, цифры<br />

предшествуют заглавным буквам, а пробел - цифрам (в смысле ал-<br />

288


фавита). Русские буквы в кодовых таблицах - в общем случае неупорядочены.<br />

Отсюда вывод - сравнение ключей поиска, содержащих<br />

латинские буквы, можно проводить непосредственно (например, с<br />

помощью строковой функции strcmp, как в нашем случае). И наоборот,<br />

если ключ содержит русские буквы, то для сравнения ключей<br />

следует использовать специально написанную для этой цели функцию.<br />

Данные для поиска в таблице могут иметь следующий вид:<br />

const ±nt LKEY = 9; // Длина ключа в строке таблицы<br />

// LKEY~1<br />

// Длина данного в строке таблицы LDATA-1<br />

const xnt LDATA = 63;<br />

// Тип для строки таблицы<br />

struct STRTAB<br />

(<br />

// Ключ<br />

char кеу[ LKEY ];<br />

// Данные<br />

сЬаг data[ LDATA ];<br />

} ;<br />

// приведенные ниже объекты будут использованы практически во<br />

// веек функциях поиска в таблице и их целесообразно<br />

// определить с описателем класса хранения внешний - это<br />

// сделает указанные объекты доступными в других файлах<br />

// проекта и всех функциях<br />

STRTAB *рТаЫе; // Адрес первой строки таблицы в<br />

// динамической памяти<br />

±nt size; // Размер таблицы<br />

Спецификация функции, выполняющей поиск в таблице, представлена<br />

на рис. 96. Из нее следует, что хотя общее число исходных<br />

данных {input) и результатов, получаемых из функции поиска {output),<br />

равно пяти, все же в список параметров функции следует<br />

включить только три из них, так как два исходных данных - table и<br />

size — следует определить на внешнем уровне как глобальные объекты.<br />

STRTAB *рТаЬ1е И • Int &found<br />

int size м search<br />

char KeyWord[LKEY] И • int &line<br />

input process output<br />

Рис. 96. Спецификация функции поиска в таблице<br />

Решение задачи поиска заключается в том, что в таблице<br />

рТаЫе надо найти строку с полем ключа, совпадающим со словом<br />

289


Key Word (если строка найдена, то ее индекс line, а флаг результата<br />

поиска found=\) или получить ответ, что такой строки в таблице нет<br />

(found=0).<br />

Основными способами поиска в таблице являются.<br />

/. Последовательный поиск. Эффективность поиска (среднее<br />

число обращений к таблице для нахождения искомой строки) равна<br />

size/2.<br />

2, Логарифмический поиск (бинарный, с помощью двоичного<br />

дерева). Число обращений к таблице равно \Q>%^{size).<br />

J. ПоисКу использующий прямой доступ к таблице. Число<br />

обращений к таблице равно единице.<br />

4. Поиск с использованием перемеиганной, слабо заполненной<br />

таблицы (хэт-таблицы). Число обращений к таблице близко к<br />

единице.<br />

Рассмотрим перечисленные способы поиска, кроме малоупотребимого<br />

поиска с прямым доступом, рассмотренного в [6].<br />

17.2. Последовательный поиск<br />

Пример таблицы, заполненной для последовательного поиска,<br />

показан на рис. 97.<br />

lesson лекция<br />

П<br />

Р<br />

type<br />

тип<br />

о<br />

с<br />

size-1<br />

word<br />

work<br />

слово<br />

работа<br />

м<br />

о<br />

т<br />

ключ данное<br />

|р|<br />

^-*^<br />

Рис. 97. Пример таблицы для последовательного поиска<br />

Поиск выполняется в полностью заполненной таблице. Просмотр<br />

таблицы выполняется последовательно, в соответствии с ростом<br />

индексов строк таблицы. Если в какой-то строке таблицы поле<br />

ключа совпадает с KeyWord^ то поиск окончен с результатом "нашли".<br />

Если этого не произошло, а конец таблицы достигнут - поиск<br />

окончен с результатом "не нашли".<br />

Для таблицы, показанной на рис. 97, для ключевого слова word<br />

результатом поиска 6yjXQT found = 1; line = 2. Аналогично, для ключевого<br />

слова a«


тивность поиска составляет в среднем size/2 обращений.<br />

Программный проект, в котором содержатся определения<br />

функций для поиска в таблице и пример их использования, приводится<br />

ниже. В примере на данном этапе следует рассмотреть только<br />

данные и те фрагменты проекта, которые относятся к функции SequentialSearch<br />

для последовательного поиска в таблице. К числу таких<br />

фрагментов относятся, в том числе, функции AllocTableDM<br />

(размещение таблицы в динамической памяти), FreeTableDM (освобождение<br />

занятой таблицей динамической памяти), SeqlnpTab (заполнение<br />

таблицы), PrintTab (печать содержимого таблицы) и Print-<br />

Search (вывод результатов поиска).<br />

/*<br />

Файл TestSearch.срр.<br />

Тестирование поиска в таблице.<br />

Определение методов поиска в таблице приведено в файле<br />

Sea rch Tab! е. срр.<br />

Заголовочный файл проекта - файл SearchTable. h.<br />

Для откытия и закрытия файлов используются универсальные<br />

функции^ определенные в файлах OpenCloseFile.h и<br />

OpenCloseFile.срр.<br />

Давыдов В.Г. Консольное приложение. Visual C++ 6<br />

*/<br />

// Включаемый файл программного проекта для поиска в таблице<br />

^include "SearchTable.h"<br />

±nt main ( // Возвращает О при успехе<br />

±nt ArgC, // Число аргументов в командной<br />

// строке<br />

dbai: *ArgV[ ] ) / / Массив указателей на аргументы<br />

// командной строки (ArgV[ О ] -<br />

// .ехе файл, в интегрированной<br />

// среде программирования известен<br />

// и не задается/ ArgV[ 1 ] - файл<br />

// ввода/ ArgV[ 2 ] - файл вывода)<br />

{<br />

// Проверка числа аргументов командной строки<br />

±f( ArgC != 3 )<br />

{<br />

printf(<br />

"\n Ошибка 5. В командной строке должно быть три аргумента: "<br />

"\п Имя_проекта. ехе имя_файла_ввода имя_файла_вывода \п" )/<br />

exi t ( 5 ) /<br />

}<br />

// Создаем и инициализируем таблицу из 4 строк<br />

AllocTableDM( 4 ) /<br />

±пЬ found, // 1 - нашли ключевое слово<br />

line/ // Индекс найденной строки<br />

291


Заполняем таблицу для последовательного поиска и<br />

// печатаем ее<br />

SeqInpTab ( ArgV[ 1 ] ) ;<br />

PrintTab( ArgV[ 2 7/ "^"г<br />

" Состояние таблицы:" ) ;<br />

// Тестирование последовательного поиска в таблице<br />

FILE *pStructFlleOut = OpenFile( ArgV[ 2 ], "a",<br />

170 ) ;<br />

fprintf(<br />

pStructFileOutr<br />

"\n\n Тестирование последовательного поиска \л" ) ;<br />

CloseFile( pStructFileOut, ArgV[ 2 7, 180 ) ;<br />

SequentialSearch ( "and", founds line ) ;<br />

PrintSearchi ArgV[ 2 ], "a", found, line, "and" ) ;<br />

SequentialSearch( "word", found, line ) ;<br />

PrintSearch( ArgV[ 2 ], "a", found, line, "word" ) ;<br />

// Заполняем таблицу для логарифмического поиска и<br />

// печатаем ее<br />

InpTabLog( ArgV[ 1 ] ) ;<br />

PrintTab( ArgVf 2 ], "a",<br />

" Состояние таблицы:" ) ;<br />

// Тестирование логарифмического поиска в таблице<br />

pStructFileOut = OpenFile( ArgV[ 2 ], "а", 190 ) ;<br />

fprintf(<br />

pStructFileOut,<br />

"\n\n Тестирование логарифмического поиска \п" ) ;<br />

CloseFile( pStructFileOut, ArgV[ 2 ], 200 ) ;<br />

LogariphmSearch ( "and", found, line ) ;<br />

PrintSearch ( ArgVf 2 ], "a", found, line, "and" ) ;<br />

LogariphmSearch ( "word", found, line ) ;<br />

PrintSearch( ArgV[ 2 ], "a", found, line, "word" ) ;<br />

// Заполняем таблицу для хэш-поиска и печатаем ее<br />

BeginTable( ArgV[ 1 ], 2 ) ;<br />

PrintTab( ArgV[ 2 ], "a",<br />

" Состояние таблицы:" ) ;<br />

// Тестирование кэш-поиска в таблице<br />

pStructFileOut = OpenFile( ArgV[ 2 ], "а", 210 ) ;<br />

fprintf(<br />

pStructFileOut,<br />

"\n\n Тестирование хэш-поиска \n" ) ;<br />

CloseFile( pStructFileOut, ArgVf 2 ], 220 ) ;<br />

HashSearch ( "work", found, line ) ;<br />

PrintSearchi ArgV[ 2 ], "a", found, line, "work" ) ;<br />

HashSearch( "type", found, line ) ;<br />

PrintSearch ( ArgVf 2 ], "a", found, line, "type" ) ;<br />

// Освобождение динамической памяти, занятой таблицей<br />

FreeTableDMi ) ;<br />

292


}<br />

-_<br />

V<br />

retuxm 0;<br />

Файл SearchTable.h. Включаемый файл для поиска в таблице.<br />

Давыдов В. Г. Консольное приложение^ Visual C-h+ 6<br />

// Предотвращение возможности многократного подключения<br />

// данного файла<br />

Hfndef SEARCHTABLE_H<br />

^define SEARCHTABLE_H<br />

^include // Для строковых функций<br />

// Для открытия-закрытия файлов<br />

#Include "OpenCloseFile.h"<br />

const ±nt LKEY = 9; // Длина ключа в строке таблицы<br />

// LKEY-1<br />

// Длина данного в строке таблицы LDATA-1<br />

const ±nt LDATA = 63;<br />

// Тип для строки таблицы<br />

stmict STRTAB<br />

{<br />

} ;<br />

// Ключ<br />

char кеу[ LKEY ];<br />

// Данные<br />

char data[ LDATA ];<br />

// Объявление объектов с описателем класса хранения<br />

// внешний. Они доступны в других файлах проекта^ в<br />

// которых подключен данный файл<br />

extern STRTAB<br />

*рТаЫе; // Адрес первой строки таблицы в<br />

// динамической памяти<br />

extern ±nt size; // Размер таблицы<br />

// Указатель на структуру со сведениями о файле ввода<br />

extern FILE<br />

*pStructFlleInp;<br />

// Прототипы функций<br />

void AllocTableDM( int );<br />

void. FreeTableDM( void );<br />

void SeqInpTab ( char * ) ;<br />

void PrlntTab ( char *pFlleOut, chstr *, char *pHead );<br />

void SequentlalSearch ( char [ ], int


§endif<br />

±nt Hash ( chstr [ ] ) ;<br />

void. BeglnTable ( сЬлг *, ±nt ) ;<br />

void. HashSearch ( сЪах: [ ], inb &, inb & ),<br />

Файл OpenCloseFile.h. Включаемый файл для функций открытия<br />

и закрытия файлов.<br />

Используются в любых программных проектах.<br />

Давыдов В.Г. Консольное приложение,. Visual Сч-+ 6<br />

// Предотвращение возможности многократного подключения<br />

// данного файла<br />

i^lfndef OPENCLOSEFILE_H<br />

^define OPENCLOSEFILE_H<br />

#endlf<br />

^Include // Для ввода-вывода<br />

ilnclude // Для функции exit ( )<br />

// Прототипы функций<br />

FILE * OpenFlle ( char *, char *, inb ) ;<br />

void CloseFlle( FILE *, char *, int WarnNum ) ;<br />

Файл OpenCloseFile.cpp. Универсальные функции открытия и<br />

3акрытия файлов.<br />

Используются в любых программных проектах.<br />

Давыдов В.Г. Консольное приложение. Visual C++ 6<br />

*/<br />

// Включаемый файл<br />

ilnclude<br />

"OpenCloseFile.h"<br />

// Открытие файла<br />

FILE * OpenFlle ( // Возвращает указатель на структуру<br />

// со сведениями об открытом файле<br />

// Указатель на имя .расширение открываемого файла<br />

сЬаг *pFl 1 eNam е ,<br />

cha.r *pMode, // Указатель на режим открытия файла<br />

// Номер ошибки или предупреждения<br />

int ErrWarnNum )<br />

{<br />

// Указатель на структуру со сведениями об открытом файле<br />

FILE<br />

*pStructFlle/<br />

294<br />

// Открытие файла<br />

pStructFlle = fopen ( pFlleName, pMode ) ;<br />

if( IpStructFlle )<br />

{


printf(<br />

"\n Ошибка %d. Ошибка открытия файла %s в режиме<br />

ErrWarnNum, pFileName, pMode ) ;<br />

exit ( ErrWarnNum )/<br />

}<br />

\"%s\"\п",<br />

)<br />

jc&tum<br />

pStructFile;<br />

// Закрытие файла<br />

void. CloseFile (<br />

// Указатель на структуру со сведениями о закрываемом<br />

FILE *pStructFile,<br />

// Указатель на имя.расширение закрываемого файла<br />

char *pFileNaine^<br />

Int WarnNum ) // Номер предупреждения<br />

{<br />

// Закрытие файла<br />

х£( fclose( pStructFile ) == EOF )<br />

{<br />

printf(<br />

"\n Предупреждение %d. Файл %s не закрыт. \n"<br />

"\n Выполнение программы продолжается \n",<br />

WarnNum^ pFileName );<br />

}<br />

}<br />

геЬгит;<br />

Файл SearchTable.cpp.<br />

Функции поиска в таблице:<br />

* размещение таблицы в динамической памяти и ее<br />

инициа ЛИЗ а ция ;<br />

* освобождение динамической памяти, занятой таблицей/<br />

* заполнение массива значениями, читаемыми из файла на<br />

магнитном диске (для последовательного поиска);<br />

* заполнение таблицы значениями, читаемыми из файла на<br />

магнитном диске (для логарифмического поиска);<br />

* вывод содержимого таблицы в файл на магнитном диске/<br />

* последовательный поиск в таблице;<br />

* вывод результатов поиска в таблице в файл на магнитном<br />

диске;<br />

* обход вершин дерева с целью формирования словаря для<br />

бинарного (логарифмического) поиска;<br />

* бинарный (логарифмический) поиск в таблице, подготовленной<br />

в форме алфавитно-упорядоченного двоичного дерева/<br />

* преобразование символа ключа ~ строчная латинская буква,<br />

цифра или пробел - в его порядковый номер (целое число) ;<br />

* хэш-функция ключа "KeyWord" из "LKEY-1" символа (символ -<br />

строчная латинская буква, цифра или пробел) для таблицы из<br />

size строк;<br />

* начальная подготовка хэш-таблицы;<br />

295


* поиск в хэш-таблице.<br />

Используется в программном проекте для поиска в таблице.<br />

Давыдов В.Г. Консольное приложение^ Visual C-h+ 6 I<br />

JV<br />

// Включаемый файл программного проекта для поиска в таблице<br />

^include "SearchTable.h"<br />

// Определения объектов с описателем класса хранения внешний.<br />

// Их объявление имеется в заголовочном файле проекта и<br />

// доступно в других файлах проекта<br />

STRTAB *рТаЫе; // Адрес первой строки таблицы в<br />

// динамической памяти<br />

±nt size; // Размер таблицы<br />

// Указатель на структуру со сведениями о файле ввода<br />

FILE<br />

*pStructFileInp;<br />

i<br />

// Размеш.ение таблицы в динамической памяти и ее<br />

// инициализация<br />

void AllocTableDM(<br />

±nt s ) // Число строк таблицы<br />

{<br />

// Проверяем г подходит ли размер таблицы<br />

±£( S < 1 )<br />

{<br />

printf(<br />

"\п Предупреждение 10. Таблица должна содержать не менее"<br />

" двух строк"<br />

"\п (задано %d строк) . Принимается размер таблицы 2. "<br />

"\п Выполнение программы продолжается. " ) ;<br />

S = 2;<br />

}<br />

// Размещаем таблицу в динамической памяти<br />

рТаЫе = new STRTAB[ s ] ;<br />

±f( !рТаЫе )<br />

{<br />

}<br />

printf( "\n Ошибка 20. Таблица не была размеш,ена "<br />

"в динамической памяти " ) ;<br />

exit( 20 ) ;<br />

// Инициализация таблицы<br />

fori ±nt i = О; i < s; 1ч-+ )<br />

{<br />

pTablef i ] . key[ 0 ] = '\0'/<br />

pTablel i ].data[ 0 ] = '\0';<br />

}<br />

size = s;<br />

jretujrn/<br />

296


Освобождение динамической памяти, занятой таблицей<br />

void FreeTableDM( void )<br />

{<br />

xf( рТаЫе )<br />

{<br />

delete [ ] рТаЫе; рТаЫе = NULL;<br />

}<br />

}<br />

// Заполнение массива значениями, читаемыми из файла на<br />

// магнитном диске (для последовательного поиска)<br />

void SeqInpTab (<br />

// Указатель на файл ввода<br />

сЬаг *pFlleInp )<br />

{<br />

// Открытие файла для чтения<br />

FILE *pStructFileInp = OpenFile ( pFilelnp,<br />

"г", 30 ) ;<br />

// Заполнение массива<br />

£ою ( ±nt 1=0; Ksize; i + + )<br />

{<br />

}<br />

±f( fscanfi pStructFilelnp, " %s %s",<br />

pTablef i ].key, pTable[ i ].data ) != 2 )<br />

{<br />

}<br />

printf( "\n Ошибка 40. Ошибка чтения строки^<br />

" таблицы с индексом %d ", i ) ;<br />

exit( 40 ) ;<br />

// Закрытие файла ввода<br />

CloseFile( pStructFilelnp, pFilelnp, 50 ) ;<br />

}<br />

return/<br />

// Заполнение таблицы значениями, читаемыми из файла на<br />

// магнитном диске (для логарифмического поиска)<br />

void InpТаbLog (<br />

// Указатель на файл ввода<br />

сЬаг *pFileInp )<br />

{<br />

// Открытие файла для чтения<br />

pStructFilelnp = OpenFile( pFilelnp, "г", 60 ) ;<br />

Round( 1 ) ; // Рекурсивное заполнение таблицы<br />

// Закрытие файла ввода<br />

297


CloseFile( pStructFilelnp, pFllelnp, 70 )<br />

}<br />

retvLrn;<br />

// Вывод содержимого таблицы в файл на магнитном диске<br />

void PrintTab(<br />

char *pFlleOut,// Указатель на файл вывода<br />

char- *pMode^ // Указатель на режим открытия файла<br />

char "^pHead ) // Указатель на заголовок для печати<br />

{<br />

// Открытие файла для записи<br />

FILE *pStructFileOut = OpenFile ( pFileOut, pMode,<br />

80 ) ;<br />

}<br />

// Печать таблицы с заголовком<br />

fprintf( pStructFileOut, "\п %s \п", pHead ) ;<br />

tor( ±nt 1=0; l


}<br />

}<br />

// Шаг вперед по таблице<br />

if( i == size-1 )<br />

{<br />

EndTab = 1/<br />

;<br />

else<br />

{<br />

}<br />

i + + /<br />

}<br />

retuim;<br />

// Вывод результатов поиска в таблице в файл на магнитном<br />

// диске<br />

void PrintSearch(<br />

char *pFileOutг// Указатель на файл вывода<br />

char *pMode, // Указатель на режим открытия файла<br />

±nt found, // 1 - нашли в таблице<br />

±iib line г // Индекс строки в таблице<br />

// Ключевое слово для поиска<br />

char Keyword[ ] )<br />

{<br />

// Открытие файла для записи<br />

FILE *pStructFlleOut = OpenFile ( pFileOut, pMode,<br />

100 ) ;<br />

fprintf( pStructFlleOutr "\n Результаты поиска для"<br />

" ключевого слова: %s\n'\ KeyWord ) ;<br />

±f( found )<br />

(<br />

fprintf(<br />

pStructFileOutr<br />

"Индекс строки в таблице: %d. Найденная строка: \л", line ) ;<br />

fprintf( pStructFileOut, "%-8s%-62s \л",<br />

рТаЫе [ line J.key, рТаЫе [ line ] .data ) ;<br />

}<br />

else<br />

{<br />

fprintf( pStructFileOut, "Строка с ключом \"%s\" в"<br />

" таблице не найдена, \п", Keyword ) ;<br />

}<br />

// Закрытие файла вывода<br />

CloseFile( pStructFileOutг pFileOut, 110 ) ;<br />

}<br />

return/<br />

// Обход вершин дерева с целью формирования словаря для<br />

// бинарного (логарифмического) поиска, !!! Читаемые данные<br />

299


должны быть ал фа витно-упорядоченными по ключам<br />

void Round(<br />

±nt root ) // Корень дерева<br />

{<br />

±£( size < root )<br />

{ // !!! Выход из рекурсии<br />

}<br />

Round( 2*root ); // Обойти вершины левого поддерева<br />

// Приписать корню очередную по алфавиту строку таблицы<br />

±f( fscanf( pStructFilelnp, "%8s%62s",<br />

рТаЫе[ root-1 ].key, рТаЫе[ root-1 ],data ) ! =2 )<br />

{<br />

printf(<br />

"\n Ошибка 120. Ошибка чтения строки таблицы \п" );<br />

exit ( 120 );<br />

}<br />

Round( 2*root+l );<br />

// Обойти вершины правого поддерева<br />

return/<br />

// Бинарный (логарифмический) поиск в таблице, подготовленной<br />

// в форме алфавитно-упорядоченного двоичного дерева<br />

void. LogariphmSearch (<br />

// Ключ для поиска строки в таблице<br />

сЪаг Keyword [ ] ,<br />

±nt &found, // 1 - нашли<br />

±Tib Scline ) // Индекс найденной строки<br />

{<br />

int i, // Индекс вершины дерева<br />

EndTab; // 1 - достигнут конец таблицы<br />

300<br />

// Подготовка к поиску<br />

found = 0; EndTab = 0;<br />

// Поиск<br />

i = 1;<br />

while ( ! found && .'EndTab )<br />

{<br />

±f( !strcmp( Keyword, pTable[ i-1 ] . key ) )<br />

{ //Нашли<br />

found = 1; line = i-1;<br />

}<br />

else<br />

{ //Шаг вперед по таблице<br />

±f( strcmp( Keyword, pTable [ i-1 ] , key ) < 0 )<br />

{<br />

i = 2*i/<br />

}


else<br />

{<br />

1 = 2*i-i-l/<br />

}<br />

EndTab = (i > size ),<br />

return;<br />

// Преобразование символа ключа - строчная латинская буква,<br />

// цифра или пробел - в его порядковый номер (целое число)<br />

±п\ t Kod(<br />

// Порядковый номер символа<br />

char symbol )<br />

// Преобразуемый символ<br />

(<br />

switch ( S3 /mbol )<br />

{<br />

case ' a ': return 0;<br />

case<br />

case<br />

'b' :<br />

'c' :<br />

return<br />

return<br />

1;<br />

2;<br />

case d' : return 3;<br />

case<br />

case<br />

'e' :<br />

f :<br />

return<br />

return<br />

4;<br />

5;<br />

case g' : return 6;<br />

case h'; return 7;<br />

case<br />

case<br />

'i ': return<br />

J'.• return<br />

8;<br />

9;<br />

case ;c' : return 10<br />

case<br />

case<br />

case<br />

case<br />

1 ' : return<br />

m ': return<br />

n ': return<br />

o' : return<br />

11<br />

12<br />

13<br />

14<br />

case<br />

case<br />

p':<br />

q':<br />

return<br />

return<br />

15<br />

16<br />

case<br />

case<br />

case<br />

r' : return<br />

s' : return<br />

t': return<br />

17<br />

18<br />

19<br />

case u': return 20<br />

case<br />

case<br />

V* : return<br />

w' : return<br />

21<br />

22<br />

case X' : return 23<br />

case y': return 24<br />

case<br />

case<br />

case<br />

z ': return<br />

0' : return<br />

1 ': return<br />

25<br />

26<br />

27<br />

case 2': return 28<br />

case 3' : return 29<br />

case 4 ' : return 30<br />

case 5' : return 31<br />

case 6' : return 32<br />

case 7': return 33.<br />

301


case '8*: return 34;<br />

case '9': return 35;<br />

case ' ': return 36;<br />

dLefaul t:<br />

printf(<br />

"\n Ошибка 130. Ключ поиска содержит недопустимый символ. \п"<br />

"\п Ключ может содержать толька строчные латинские буквы, "<br />

" цифры и пробел \п" ) ;<br />

exit ( 130 ) ;<br />

}<br />

}<br />

return -1; // Этот оператор не будет<br />

// выполняться<br />

// Хэш-функция ключа "KeyWord" из "LKEY-1" символа (символ -<br />

// строчная латинская буква, цифра или пробел) для таблицы<br />

// из size строк<br />

int Hash( // Возвращает индекс строки таблицы<br />

// Ключ<br />

char Keyword [ ] )<br />

{<br />

unsigned. ±nt<br />

I Key; // Индекс символа в ключе<br />

±nt ih = 0; // Значение хэш-функции<br />

}<br />

// Вычисление индекса строки таблицы<br />

for( IKey = 0; I Key < strlen ( KeyWord ) ; IKey-h-h )<br />

{<br />

ih = ih * 37 -f Kod( KeyWordf IKey ] ) ;<br />

ih = ih % size;<br />

}<br />

return<br />

ih;<br />

// Начальная подготовка хэш-таблицы<br />

void BeginTable (<br />

char *pFileInp,// Указатель на файл ввода<br />

int ТаЫеЬеп ) / / Число вводимых строк<br />

{<br />

int i, // Индекс строки таблицы<br />

line, // Номер текуш,ей строки<br />

found; // 1 - найдена позиция вставки<br />

// Заносимое слово<br />

char Keyword [ LKEY ];<br />

302<br />

// Инициализация таблицы нуль-символ а ми<br />

£ог( i = О; i < size; i++ )<br />

{<br />

£or( int il = 0; il < LKEY; il++ )


}<br />

рТаЫе[ i ] . key [ 11 ] = '\0';<br />

fox:( int 12 = 0; 12 < LDATA; 12 + + )<br />

pTablef 1 ] .data[ 12 ] = '\0';<br />

// Отметка строк таблицы как свободных<br />

for( 1 = О; 1 < size; 1++ )<br />

(<br />

pTablef 1 J.keyf О ] ^ ' '/<br />

}<br />

// Открытие файла для чтения<br />

pStructFllelnp == OpenFlle( pFllelnpr "г", 140 ) ;<br />

// Занесение в таблицу исходных строк<br />

for( line = 0; line < TableLen; llne++ )<br />

{ // Цикл чтения исходных строк<br />

±f( fscanf( pStructFllelnp, " %s", Keyword ) != 1 )<br />

{<br />

}<br />

printf( "\n Ошибка 150. Ошибка чтения ключа из"<br />

" строки с индексом %d \п", line ) ;<br />

// Поиск индекса '1' строки таблицы для ее заполнения<br />

found = О; // Пока индекс не найден<br />

while( /found )<br />

{<br />

}<br />

±f( pTablef 1 ].key[ 0 ] == ' ' )<br />

{ // Индекс найден<br />

found - 1;<br />

}<br />

else<br />

{ // Конфликт - шаг по таблице<br />

1++; 1 = ( 1 > ( slze-1 ) 0: 1 ) ;<br />

}<br />

// Чтение данного<br />

±f( fscanf( pStructFllelnp, " %s",<br />

pTablef 1 ] .data ) != 1 )<br />

{<br />

printf( "\n Ошибка 160. Ошибка чтения ключа из"<br />

" строки с индексом %d \л", line )/<br />

exlt ( 160 ) ;<br />

}<br />

// Занесение ключа в строку "1" таблицы<br />

strcpyi pTablef 1 ].key, KeyWord )/<br />

// Закрытие файла ввода<br />

CloseFlle( pStructFllelnp, pFllelnp, 170 ) ;<br />

геЬмтсп;<br />

303


}<br />

// Поиск В хэш-таблице<br />

void HashSearch(<br />

// Ключевое слово для поиска<br />

char Keyword [ ],<br />

±nt бе founds // 1 - нашли<br />

±nt &11пе ) // Индекс найденной строки в таблице<br />

{<br />

±nt 1, // Индекс строки таблицы<br />

EndTah; // 1 - достигли свободной строки<br />

// Подготовка к поиску<br />

found = О; EndTab = О; i = Hash( KeyWord ) ;<br />

// Поиск в таблице<br />

while ( ( .'found ) && ( .'EndTab ) )<br />

{<br />

±f( pTablel 1 J.keyf 0 ] == ' ' )<br />

{ // Достигли свободной строки<br />

EndTab = 1;<br />

}<br />

else<br />

{<br />

±£( ! strcmp ( pTablef 1 ] . key, KeyWord ) )<br />

{ // Нашли<br />

found = 1; line = i;<br />

}<br />

else<br />

{ // Шаг no таблице<br />

i++; i = ( i > ( size-1 ) 0: i ) ;<br />

}<br />

}<br />

}<br />

retujni/<br />

Для файла исходных данных, имеющего вид<br />

call<br />

type<br />

word<br />

work<br />

был<br />

вызов<br />

тип<br />

слово<br />

работа<br />

получен следующий файл результатов:<br />

Состояние<br />

та блицы:<br />

call<br />

type<br />

word<br />

work<br />

вызов<br />

тип<br />

слово<br />

работа<br />

304


Тестирование последовательного поиска<br />

Результаты поиска для ключевого слова: and<br />

Строка с ключом "and" в таблице не найдена.<br />

Результаты поиска для ключевого слова: word<br />

Индекс строки в таблице: 2. Найденная строка:<br />

word слово<br />

Состояние<br />

та блицы:<br />

word<br />

type<br />

work<br />

call<br />

слово<br />

тип<br />

работа<br />

вызов<br />

Тестирование логарифмического поиска<br />

Результаты поиска для ключевого слова: and<br />

Строка с ключом "and" в таблице не найдена.<br />

.Результаты поиска для ключевого слова: word<br />

Индекс строки в таблице: О. Найденная строка:<br />

word слово<br />

Со стояние<br />

та блицы:<br />

call<br />

type<br />

вызов<br />

тип<br />

Тестирование<br />

хэш-поиска<br />

Результаты поиска для ключевого слова: work<br />

Строка с ключом "work" в таблице не найдена.<br />

Результаты поиска для ключевого слова: type<br />

Индекс строки в таблице: 2. Найденная строка:<br />

type тип<br />

17.3. Логарифмический (бинарный) поиск<br />

Кардинальное повышение эффективности поиска в таблице<br />

достигается полным пересмотром алгоритма поиска аналогично тому,<br />

как это было ранее в сложных алгоритмах сортировки массивов.<br />

Пример. В реальных словарях, например в англо-русском словаре,<br />

человек быстро находит нужные сведения, используя их упорядоченность<br />

по алфавиту. Указанный подход и использован при<br />

305


логарифмическом (с помощью двоичного дерева) поиске в таблице.<br />

Если исходную таблицу (словарь) предварительно подготовить<br />

в форме двоичного дерева так, чтобы ключи левого поддерева были<br />

раньше по алфавиту, чем ключ корня, а ключи правого поддерева -<br />

позже, то число обращений к таблице для сравнения с заданным<br />

ключевым словом не может превышать log^isize). При этом, после<br />

каждого обращения к таблице, область поиска сокращается в общем<br />

случае примерно в два раза.<br />

Начальная подготовка таблицы в форме двоичного дерева.<br />

Исходные данные при заполнении таблицы перед чтением должны<br />

быть упорядочены по алфавиту для ключевых слов. Это легко обеспечить,<br />

используя рассмотренные выше способы сортировки массивов.<br />

Пусть, например, читаемые данные содержат в каждой отдельной<br />

строке информацию для заполнения одной строки таблицы,<br />

причем они отсортированы по ключам по не убыванию (size=\0):<br />

А<br />

В<br />

С<br />

D<br />

Е<br />

F<br />

G<br />

Н<br />

I<br />

J<br />

Data<br />

Data<br />

Data<br />

Data<br />

Data<br />

Data<br />

Data<br />

Data<br />

Data<br />

Data<br />

for<br />

for<br />

for<br />

for<br />

for<br />

for<br />

for<br />

for<br />

for<br />

for<br />

string<br />

string<br />

string<br />

string<br />

string<br />

string<br />

string<br />

string<br />

string<br />

string<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9 (size-1)<br />

Двоичное дерево строится, как это было ранее рассмотрено, с<br />

использованием рекуррентного подхода (см. рис. 57). Для нашего<br />

примера после начальной подготовки двоичное дерево должно<br />

иметь вид, показанный на рис. 98.<br />

Соответственно этому алгоритму можно записать рекуррентную<br />

функцию Round и использующую ее функцию InpTabLog для<br />

начального заполнения и подготовки таблицы, прототипы и определения<br />

которых содержатся в вышеприведенной программе. Обратите<br />

внимание на то, что функция Round является вспомогательной<br />

для функции InpTabLog.<br />

Бинарный (логарифмический) поиск в таблице, подготовленной<br />

в форме двоичного дерева. Идея поиска состоит в следующем.<br />

1. Исходный ключ сравнивается с ключом, соответствующим<br />

корню дерева (номер соответствующей вершины дерева / = 1, а индекс<br />

элемента массива - (/ - 1) ). Если при этом ключи совпадают, то<br />

нужная строка найдена {found = 1), ее индекс line = i - 1 и поиск за-<br />

306


вершен.<br />

/<br />

А<br />

}<br />

В<br />

Л- с<br />

D<br />

2 ]\ F<br />

-V<br />

Е<br />

Последние слова по<br />

10<br />

алфавиту (H-I-J)<br />

присваиваются<br />

Первые слова по<br />

правому поддереву<br />

алфавиту (A-B-C-D-E-F)<br />

присваиваются левому<br />

поддереву<br />

Среднее слово по алфавиту (G) присваивается<br />

корню.<br />

Аналогично заполняются левые и правые<br />

поддеревья для частичных корней<br />

Рис. 98. Двоичное дерево после начального заполнения<br />

2. Если поиск не завершен, то определяется поддерево для<br />

продолжения поиска путем сравнения KeyWord < рТаЫо[ /-1 ].кеу.<br />

При положительном итоге необходимо вести поиск в левом поддереве<br />

и номер следующей вершины / = /*2. При выполнении же противоположного<br />

условия KeyWord > рТаЫе[ /-1 ].кеу, поиск следует<br />

вести в правом поддереве и номер следующей вершины дерева / =<br />

/*2+1.<br />

3. При выполнении условия i > size (вершину / дерево не содержит)<br />

поиск следует прекратить, так как строка с ключевым словом<br />

KeyWord отсутствует {EndTab = 1 w found = 0). Иначе - выполняется<br />

переход к п. 1 с новым значением /, соответствующим корню<br />

левого или правого поддерева.<br />

Легко заметить, что после каждого сравнения KeyWord с ключом<br />

рТаЫе[ /-1 ].кеу область поиска сокращается примерно в два<br />

раза и среднее число обращений к таблице (средняя длина поиска)<br />

составляет 1^^.,, =\og^{size), что существенно эффективнее, чем при последовательном<br />

поиске.<br />

/<br />

н<br />

/<br />

1<br />

3<br />

J<br />

307


в соответствии со сказанным, прототип, определение функции<br />

LogariphmSearch и пример ее вызова имеют вид, показанный в программе<br />

из подразд. 17.2.<br />

17.4. Поиск с использованием перемешанной таблицы<br />

(хэш-таблицы)<br />

при поиске с использованием хэш-таблицы используется организация<br />

данных в виде массива. Основная идея поиска состоит в<br />

преобразовании заданного ключа Key Word в индекс Hash( Key Word )<br />

соответствующей строки в таблице. Поэтому такой способ поиска<br />

иногда называют поиском с преобразованием ключей (рис. 99).<br />

рТаЫе<br />

Keyword<br />

Исходный<br />

ключ<br />

Hash(KeyWord)<br />

Индекс строки в<br />

таблице с<br />

key = Keyword<br />

Size-1<br />

Ключ (key) Данное (data)<br />

Рис. 99. Хэш-поиск в таблице<br />

Основная трудность преобразования ключей состоит в том,<br />

что множество возможных значений ключей намного обширнее, чем<br />

множество индексов строк в таблице. Так, например, если ключ содержит<br />

восемь символов, в качестве которых используются строчные<br />

латинские буквы, цифры и пробел (всего 37 возможных значений<br />

каждого символа в ключе), то всего имеется 37^ возможных<br />

значений ключей, что, естественно, во много раз превышает реальный<br />

размер таблицы size. Из сказанного следует, что функция Hash<br />

является отображением "много в один".<br />

Идея поиска с использованием хэил-таблицы состоит в следующем.<br />

Первый этап в операции поиска - вычисление соответствующего<br />

индекса Hash{ KeyWord ) в таблице, а второй - очевидно<br />

необходимый этап - проверка, действительно ли элемент с ключом<br />

KeyWord находится в таблице в строке с индексом Hash( KeyWord ).<br />

При этом сразу же возникают два вопроса.<br />

308


1. Какую функцию Hash( KeyWord ) следует использовать<br />

2. Как поступать в ситуации, когда функция Hash{ KeyWord )<br />

не дает местонахождения нужного элемента (! много ключей дают<br />

одинаковый индекс)<br />

Ответ на второй вопрос заключается в том, что нужно использовать<br />

какой-то метод для получения нового индекса в таблице, а<br />

если и там нет нужного элемента, то следующего индекса и т.д. Подобный<br />

случай, когда в строке Hash{ KeyWord ) находится другой<br />

ключ, а не ключ KeyWord^ называется конфликтом, а задача получения<br />

альтернативных индексов li^зыв2iQTCЯ разрешением конфликтов.<br />

Выбор функции преобразования. Основное требование к хорошей<br />

функции преобразования Hash{ KeyWord ) состоит в том,<br />

чтобы она распределяла ключи как можно более равномерно по<br />

шкале значений индексов. Разумеется, она должна также эффективно<br />

вычисляться, т.е. состоять из очень небольшого числа основных<br />

арифметических действий.<br />

Пусть ih определяет порядковый номер ключевого слова Key-<br />

Word во множестве всех возможных значений ключей и вычисляется<br />

следующим образом:<br />

unsigned. ±nt<br />

I Key; // Индекс символа в ключе<br />

Int ih = О; // Значение хэш-функции<br />

// Вычисление индекса строки таблицы<br />

for( IKey = 0; I Key < strlen ( KeyWord ) ; IKey-h-h )<br />

{<br />

ih = ih * 37 + Kod( KeyWord[ IKey ] ) ;<br />

}<br />

ih = ih % size;<br />

В результате вычислений ih получает значение из диапазона 0-<br />

36. К сожалению, величина ih существенно превышает максимально<br />

допустимое целое значение (2'^-1 или 2^'~1). По этой причине<br />

функцию Hash{ KeyWord ) следует построить несколько иначе — вычисление<br />

ih = ih % size;<br />

перенести в блок цикла. Прототип полученной таким образом хэшфункции<br />

и ее определение приведены в примере программы в подразд.<br />

17.2. Функция Hash{ KeyWord ) также является вспомогательной<br />

и используется при хэш-поиске. Эта функция обладает тем<br />

свойством, что значения ключей равномерно распределяются во<br />

всем интервале индексов строк таблицы. Исследованиями показано,<br />

что для большей равномерности распределения желательно, чтобы<br />

309


size было простым числом (см. Вирт Н., Алгоритмы + структуры<br />

данных = программы: Пер. с англ. М.: Мир, 1985. С. 305).<br />

Разрешение конфликтов. Если строка в таблице рТаЫе, соответствующая<br />

заданному ключу Key Word, не содержит нужный элемент,<br />

то имеет место конфликт. Это означает, что два или более<br />

элементов таблицы имеют ключи, отображающиеся в один и тот же<br />

хэш-индекс строки таблицы. Для разрешения конфликтов такого рода<br />

существуют различные методы получения вторичных индексов.<br />

Один из методов разрешения конфликтов состоит в просмотре<br />

одного за другим различных элементов таблицы, начиная со строки<br />

с индексом Hash( Key Word ), пока не будет найден нужный элемент<br />

или не встретится свободное, не заполненное место таблицы. Последнее<br />

означает отсутствие в таблице строки с заданным ключом.<br />

Этот метод называется открытой адресацией. Разумеется, что шаг<br />

просмотра элементов таблицы при вторичных пробах должен быть<br />

постоянным. Одним из таких методов является метод линейного апробирования<br />

с открытой адресацией. Реализация этого метода содержится<br />

в определении функции HashSearch.<br />

Отметка в таблице свободных мест. Для этой цели можно,<br />

например, в первый символ ключа (байт) свободной строки таблицы<br />

записать символ пробела:<br />

// Отметка строк таблицы как свободных<br />

fox:( 1 = О/ i < size/ i-h+ )<br />

(<br />

рТаЫе[ i ] . key [ О ] = ' ';<br />

I<br />

Начальная подготовка хэш-таблицы. При начальном заполнении<br />

хэш-таблицы также может иметь место конфликт. В связи с<br />

этим сделаем валсное замечание. При хэш-поиске и при начальной<br />

подготовке таблицы для разрешения конфликта следует использовать<br />

один и тот эюе метод. В нашем примере таким методом является<br />

метод линейного апробирования с открытой адресацией. Прототип<br />

функции BeginTable, используемой для начального заполнения<br />

хэш-таблицы, и ее определение имеются в примере, приведенном<br />

в подразд. 17.2. Функция BeginTable является интерфейсной<br />

функцией.<br />

Функция для поиска в хэш-таблице. Прототип функции<br />

HashSearch и ее определение имеются в примере, приведенном выше<br />

в подразд. 17.2. Функция HashSearch также является интерфейсной<br />

функцией. Обратите внимание на то, что функции BeginTable и<br />

310


HashSearch очень похожи друг на друга.<br />

Пример тестирования хэш-поиска в таблице имеется в подразд.<br />

17.2 (см. функцию main).<br />

Эффективность хэш-поиска. Проведенный для линейного<br />

апробирования анализ показал, что среднее значение числа проб при<br />

поиске (длина поиска)<br />

WP ~ \-al2<br />

\-а<br />

где a = TabLen/size есть коэффициент заполненности таблицы (табл.<br />

30).<br />

'<br />

а<br />

L,,,<br />

Табл.<br />

0.1<br />

1.056<br />

30. Эффективность хэш<br />

0.2<br />

0.3<br />

1.125<br />

1.214<br />

-поиска<br />

0.5<br />

1.5<br />

0.9<br />

5.5<br />

Из таблицы следует, что хэш-поиск имеет весьма высокую эффективность.<br />

Но при этом важно понимать и недостатки данного<br />

метода.<br />

1. Существенное повышение эффективности поиска достигается<br />

только при большой избыточности таблицы.<br />

2. Сложность удаления элемента из таблицы.<br />

В заключение отметим, что из перечисленных выше классических<br />

задач прикладного программирования^ составляющих золотой<br />

багаж любого программиста - сортировка массивов, транспортная<br />

задача (задача коммивояжера), поиск в таблице, обработка списков,<br />

работа с очередями; сортировка файлов ) — мы рассмотрели решение<br />

первых трех классов задач прикладного программирования. Остальные<br />

задачи будут рассмотрены в следующем учебном пособии "Технология<br />

программирования" в связи с изучением и освоейием других<br />

технологий программирования, таких как объектноориентированное<br />

программирование, программирование с использованием<br />

библиотеки стандартных классов языка C++ и др. Учебное<br />

пособие "Технология программирования" предназначено для обеспечения<br />

одноименной дисциплины, изучаемой в следующем, третьем<br />

семестре в рамках подготовки бакалавров (направление 5502) и<br />

специалистов (направление 6519).<br />

ЗП


18- ОТВЕТЫ И РЕШЕНИЯ К УПРАЖНЕНИЯМ<br />

ДЛЯ САМОПРОВЕРКИ<br />

Ответ к упражнению 1.<br />

18.1. Для подраздела 2.4.4<br />

retcode^l 1=17 j=123 с1=4 с2=5 сЗ=6 а=2400,000000<br />

Ъ=172,000000<br />

Ответ к упражнению 2.<br />

Файл 2_4_4_2.СРР<br />

2. Имеется следующий фрагмент Си-программы:<br />

float<br />

±nt<br />

cbSLr<br />

±nt<br />

ch^jc<br />

a;<br />

i^ jr<br />

cl, c2r c3;<br />

retcode;<br />

c4, c5r s[20]<br />

Написать фрагмент программыг обеспечивающий чтение из<br />

файлаf.dat на магнитном диске следующих значений:<br />

а = 1,5 1 = 21 j = -12 с1 = 'в'<br />

с2 = 'е' сЗ = 'с' с4 ^ 'а' с5 = 'н'<br />

S = "Прочитанная-строка"<br />

Как при этом будут выглядеть строки исходных данных в<br />

файле f.dat<br />

Предусмотреть контроль корректности значений^ возвращаемых<br />

функциями библиотеки Си.<br />

В. Давыдов^ консольное приложение (Microsoft Visual<br />

Studio C++ 6.О)<br />

*/<br />

#include // STanDart Input Output - для<br />

// стандартных функций ввода-<br />

// вывода<br />

±nt main ( void ) // Возвращает О при успехе<br />

{<br />

float а;<br />

Int i г j /<br />

chctr cl, c2, c3;<br />

char c4, c5^ s[20];<br />

312


FILE *f_in; // Указатель на файл для ввода<br />

±пЬ ret code; // Возвращаемое значение для функции<br />

// fscanf<br />

// Открываем файл f.dat для чтения<br />

f_±n = fopen( "f.dat", "г" );<br />

±f( f__in == NULL )<br />

{<br />

}<br />

printf( "\n Файл f.dat для чтения не открыт. " );<br />

jzebvucn 1;<br />

// Чтение данных из файла f.dat<br />

retcode = fscanf( f_±n, " a = %f 1 = %d j = %d "<br />

"cl = \'%c\' c2 = \'%c\' c3 = \'%c\' "<br />

"c4 ^ \'%c\' c5 = \*%c\^ S = \"%s\" ",<br />

&a, &i, &j, &cl, &c2, &c3, &c4, &c5, s );<br />

if( retcode != 9 )<br />

{<br />

}<br />

printf( "\n Данные прочитаны с ошибками." );<br />

retvim 2;<br />

// Закрываем файл ввода<br />

fclose ( f_in ) ;<br />

z-etiLm О;<br />

Ответ к упражнению 3.<br />

Файл 2_4_4_З.СРР<br />

3. В программе имеются следующие переменные:<br />

±nt d = 254/<br />

float f = 1234.56;<br />

cha.r *str = "Строка символов"/<br />

Используя, по возможности, только эти данные написать<br />

программу, выводящую в файл результатов file.out следующие<br />

строки (в них символ ^ обозначает местоположение пробела) :<br />

1+254^''^^^''^]^'^[^^''''^254]<br />

(^^^^^1234.5600) ^^ (1234.5600^^'^^^)<br />

/Стр/^^/м/<br />

В. Давыдов, консольное приложение (Microsoft Visual<br />

Studio C++ 6. 0)<br />

^Include // STanDart Input Output - для<br />

// стандартных функций ввода-<br />

313


вывода<br />

±nt main ( void ) // Возвращает О при успехе<br />

{<br />

int d = 254;<br />

float f = 1234.56f;<br />

cha,r *str = "Строка символов"/<br />

FILE *f_out; // Указатель на файл для взвода<br />

// Открываем файл file,out для записи<br />

f_out = fopen( "file.out"г "w" ) ;<br />

±f( f_out == NULL )<br />

{<br />

printf ( "\n Файл file, out для записи не открыт. " ).<br />

return 1;<br />

}<br />

// Вывод данных в файл file.out<br />

fprintf( f_out, "[%+-lld],%2c[%8d] \n (%14. 4f) %2c("<br />

"%-14.4f)\n/%.3s/%2c/%c/\n ", d, str[ 6 ], d, f,<br />

str[ 6 ;, f, str, strf 6 ]r str[ 9 ] ) ;<br />

// Закрываем файл взвода<br />

fclose ( f_out ) ;<br />

return 0;<br />

Ответ к упражнению 1.<br />

Будет напечатано:<br />

i=l j=3<br />

next ( )=11<br />

last ( )=0<br />

nw(i+j) =9<br />

Ответ к упражнению 2.<br />

Будет напечатано:<br />

i == 3 j = 1<br />

next ( i ) = 3<br />

last ( i ) =10<br />

i = 3 j ^ 2<br />

next ( i ) = 4<br />

last ( i ) = 9<br />

18.2. Для подраздела 3.8<br />

314


18.3. Для подраздела 3.9.3<br />

Ответ купрамснению 1,<br />

Фа ил 3_ 9_3_1.<br />

срр<br />

Написать прототип^ определение функции и пример вызова<br />

функции для вычисления суммы элементов одномерного массива<br />

х[ N ] (N = 50) целого типа ^ имеющих нечетные индексы<br />

В. Давыдов, Консольное приложение (Microsoft Visual<br />

Studio C++ 6.О)<br />

V<br />

^include // Для ввода-вывода<br />

^define N50 // Размер массива<br />

// Прототип<br />

±nt SumUneven( ±nt ar[ ] ) ;<br />

±nt main ( void ) // Возвращает 0 при успехе<br />

{<br />

Int a[ N ] ;<br />

// Инициализация массива<br />

toj: ( ±zib i=0/ i


Sum += ar[ i ]/<br />

}<br />

return<br />

Sum/<br />

Ответ к упражнению 2.<br />

Файл 3_9_3_2.<br />

срр<br />

Написать прототип, определение функции и пример вызова<br />

функции для получения одномерного массива z[ N ] (N = 40) из<br />

двух заданных массивов целого типа х[ N ], у[ N ] по правилу:<br />

z[ i ] := тах{ х[ ± ], у[ ± ] }<br />

В. Давыдов. Консольное приложение (Microsoft Visual<br />

Studio C+-h 6, О)<br />

*/<br />

^include // Для ввода-вывода<br />

^define N40 // Размер массивов<br />

// Прототип<br />

void. CreateArr ( Int x[ ], int y[ 7, Int z[ ] ) ;<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

±nt<br />

x[N],y[N],z[N];<br />

// Инициализация исходных массивов<br />

£or( int i=--0; i


}<br />

else<br />

z[ i ] = y[ ± ];<br />

18.4. Для подраздела 3.9.6<br />

/*<br />

Ответ к упражнению 1,<br />

Файл 3_9_3_2,<br />

срр<br />

1. В текстовом файле "ctrl4, dat" имеется 15 строк, каждая<br />

из которых имеет следующий формат:<br />

число_ 1 число_ 2<br />

Здесь "число_1" определяет вид геометрической фигуры (1 -<br />

квадрат, 2 - круг) , а "число_2" - параметр фигуры (при "число_1"<br />

= 1 ~ длина стороны, а при "число_2" = 2 - радиус) .<br />

1.1. Написать определение массива структур для хранения<br />

указанных сведений о геометрических фигурах. Каждый элемент<br />

массива должен иметь следующие поля:<br />

* имя фигуры;<br />

* длина стороны или радиус;<br />

* площадь фигуры.<br />

1.2. Написать фрагмент программы для чтения из файла на<br />

магнитном диске "ctг14.dat" информации о геометрических фигурах.<br />

1.3. Написать фрагмент программы, вычисляющий площади<br />

геометрических фигур.<br />

1.4. Написать фрагмент программы, печатающий в файл<br />

"ctrl4.out" параметры геометрических фигур. Сведения об отдельных<br />

фигурах располагаются в отдельной строке и имеют вид:<br />

круг: радиус= . . . , площадь= . . .<br />

или<br />

квадрат: длина стороны= . . . , площадь= . . .<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си, указать какие включаемые файлы<br />

требует представленный фрагмент.<br />

В. Давыдов. Консольное приложение (Microsoft Visual<br />

Studio C++ 6.0)<br />

V<br />

^include // Для ввода-вывода<br />

^include // Для строковых функций<br />

#define N15 // Размер массива структур<br />

int main ( void ) // Возвращает О при успехе<br />

(<br />

317


Определение массива фигур<br />

sbiract GeomFigure<br />

{<br />

cbai: name [ 8 ];// Название фигуры<br />

double pa ram; // Параметр фигуры: длина стороны<br />

// или радиус<br />

double square/ // Площадь фигуры<br />

) агг[ N ]; // Массив геометрических фигур<br />

// Заполнение массива структур со сведениями о<br />

// геометрических фигурах и вычисление их площадей<br />

FILE *f__ln; // Указатель на файл для ввода<br />

// Открываем файл ctrl4,dat для чтения<br />

f__in = fopen( "ctrl4,dat", "г" ) ;<br />

±£( f_±n == NULL )<br />

{<br />

print f ( "\n Файл ctrl4. dat для чтения не открыт. " );<br />

jretuxn 1;<br />

}<br />

±zib Tag; // 1 - квадрат^ 2 - круг<br />

double pa ram; // Параметр фигуры<br />

for( ±nt 1=0; KN; 1 ++)<br />

{<br />

±f( fscanf( f_ln, " %d %lf", &Tag, &param ) != 2 )<br />

{<br />

printf( "\n Ошибка чтения " ) ;<br />

return 2;<br />

}<br />

switch ( Tag )<br />

{<br />

ca.se 1 :<br />

strcpy ( arr[ i J.name, "Квадрат" ) ;<br />

arr[ 1 ] .pa ram = pa ram;<br />

arr[ 1 ]. square = pa ram "&param;<br />

break;<br />

case 2:<br />

strcpy( arr[ 1 J.name^ "Круг" ) ;<br />

arr[ 1 ].pa ram = pa ram;<br />

arr[ 1 ].square = 3.141592*param*param;<br />

break;<br />

default:<br />

return 3;<br />

}<br />

}<br />

// Закрываем файл чтения<br />

fclose ( f_in ) ;<br />

// Печать сведений о геометрических фигурах<br />

FILE *f_out; // Указатель на файл для вывода<br />

// Открываем файл ctrl4.out для записи<br />

f_out = fopen( "ctrl4.out", "w" ) ;<br />

lf( f out == NULL )<br />

318


prlntf ( "\n Файл ctrl4. out для записи не открыт. " ) ;<br />

jretujrn 4;<br />

}<br />

fo3z( 1=0/ KN/ 1++)<br />

{<br />

±£( !strcmp ( arrf 1 ] . name, "Квадрат" ) )<br />

{<br />

fprlntf( f_out, "\n Квадрат: длина стороны=%1д, "<br />

"площадь = %1д ", arr[ 1 ] ,param,<br />

arrf 1 ]. square ) ;<br />

}<br />

else<br />

{<br />

fprlntf( f_out, "\n круг: радиус=%1д, "<br />

"площадь=%1д ", arr[ 1 ] .param,<br />

arr[ 1 ]. square ) ;<br />

// Закрываем файл вывода<br />

fclose ( f_out ) ;<br />

retuim 0;<br />

18.5. Для подраздела 4.12<br />

Ответ к упражнению 1 (рис. 100).<br />

Нет<br />

Нет<br />

Рис. 100. Ответ к упражнению 1<br />

Ответ к упралснению 2.<br />

±f( a


}<br />

else<br />

r=l;<br />

r=3;<br />

Ответ к упражнению 3.<br />

swi tab( 1 )<br />

{<br />

case 4:<br />

n-h+ ;<br />

break;<br />

case 1: case 7; case 9:<br />

n=a+b;<br />

break;<br />

de£ault:<br />

n=a-b;<br />

Ответ к упражнению 4.<br />

Будет напечатано:<br />

•к<br />

-- О -- -1 — -2 -- -3 -- -4<br />

/*<br />

Ответ к упражнению 5.<br />

Файл 4__12_5. срр<br />

5. Пусть определен массив<br />

int а[ 25 ];<br />

Напишите фрагмент Си-программы, который напечатает с новой<br />

строки значения элементов "а" по 5 элементов в строке и<br />

по 10 позиций на элемент. Решить задачу с помош^ю цикла<br />

while.<br />

В. Давыдов. Консольное приложение (Microsoft Visual Studio<br />

C++ 6. 0)<br />

*/<br />

^include // Для ввода-вывода<br />

Int main ( vo±dL ) // Возвращает 0 при успехе<br />

{<br />

int a[ 25 ];<br />

// Инициализация массива<br />

for( int i=0; i


{<br />

}<br />

а[ i ] = 1;<br />

// Печать массива<br />

± = О;<br />

while( i


Определены следующие данные:<br />

struct ELEM<br />

{<br />

Int<br />

ELEM<br />

}<br />

dat;<br />

*next;<br />

*start<br />

// Структура для элемента списка<br />

// Данное<br />

// Указатель на следующей элемент<br />

// Указатель на начало списка<br />

Во входном файле Is,dat содержится некоторое количество<br />

целых чисел г разделенных символами пробельной группы ( ' ',<br />

'\t', '\л ' ; .<br />

1. Написать прототип, определение и пример вызова функции<br />

для ввода из входного файла имеющихся там чисел, представив<br />

введенную информацию линейным списком, в котором каждый узел<br />

(динамически размещенная структура) содержит две компоненты.<br />

Первая компонента хранит данное (введенное число), а вторая -<br />

указывает адрес следующей структуры. При этом первое прочитанное<br />

число должно находиться в начале линейного списка. Исходные<br />

данные и результаты работы функции следует передавать<br />

через список параметров,<br />

С целью обработки ошибок предусмотреть контроль значений,<br />

возвращаемых функциями библиотеки языков Си/C++,<br />

2. Дополнительно написать прототип, определение и пример<br />

вызова функции для печати в файл ks.out на магнитном диске<br />

содержимого линейного списка, Требования к оформлению функции<br />

и обработке ошибок аналогичны указанным выше требованиям,<br />

3. Дополнительно написать прототип, определение и пример<br />

вызова функции, которая разрушает линейный список. Требования<br />

к оформлению функции и обработке ошибок аналогичны указанным<br />

в пункте 1 требованиям,<br />

В. Давыдов, Консольное приложение (Microsoft Visual<br />

Studio C++ 6,0)<br />

*/<br />

^Include // Для функций ввода-вывода<br />

^Include // Для функции exit<br />

stmict EL // Структура для элемента списка<br />

{<br />

} ;<br />

int dat; // Данные (целое)<br />

EL *next; // Указатель на следующий элемент<br />

// Прототипы функций<br />

void Create__beg ( EL *&, char * ) ;<br />

void Add_end( EL *&, int ) ; \<br />

void Prlnt_ls ( EL *, char *, char * ) /<br />

void Dest_ls( EL *& ) ;<br />

void Del_beg( EL *& ) ;<br />

int main ( void ) // Возвращает 0 при успехе<br />

{<br />

// Указатель на начало списка<br />

322


EL<br />

*start;<br />

start = NULL/ // Инициализация списка<br />

// Заполнение линейного списка символами из файла LS.DAT:<br />

// первый прочитанный символ - в начале списка<br />

Create_beg( starts "LS.DAT" );<br />

// Вывод содержимого списка в файл<br />

Print_ls( start, "LS.OUT"r "w");<br />

Dest_ls ( start ) ; // Разрушение списка<br />

}<br />

jretixzm 0;<br />

// Заполнение линейного списка символами из файла LS.DAT:<br />

// первый прочитанный символ - в начале списка<br />

void. Crea te_beg (<br />

EL *&start, // Указатель на начало списка<br />

// Указатель на файл ввода<br />

cbstr *pFlleInp )<br />

{<br />

// Данное для элемента, добавляемого в конец списка<br />

Int 1 /<br />

// Указатель на структуру со сведениями о файле для<br />

// чтения<br />

FILE *f_±n;<br />

;<br />

// Открываем файл для чтения<br />

±£( ( f_in = fopen( pFllelnp, "г" ) ) == NULL )<br />

{<br />

}<br />

printf ( "\n Файл %s для чтения не открыт \л",<br />

pFilelnp ) ;<br />

exit(l);<br />

±nt<br />

re;<br />

// Указатель на файл ввода // Создаем список<br />

wb±le( ( ГС = fscanfC f_in, " %d ", &i ) ) == 1 )<br />

{<br />

Add_end( start, i );<br />

}<br />

// Закрываем файл<br />

±£( ( fclose( f_in ) ) =- EOF )<br />

{<br />

}<br />

jretixzm/<br />

printf( "\n Файл %s не закрыт \л", pFilelnp );<br />

exit(2);<br />

// Добавление элемента в конец списка<br />

void Add_end(<br />

EL *&start, // указатель на начало списка<br />

323


int i) // Данные добавляемого элемента<br />

// Указатель на новый (добавляемый) элемент списка<br />

EL<br />

*temp,<br />

*сиг; // Указатель на текущий элемент<br />

}<br />

temp = new EL; // 1: динамическое размещение<br />

// элемента<br />

±f( temp == NULL )<br />

{<br />

printf( "\n Элемент списка не размещен \п" )/<br />

exit ( 3 ) ;<br />

}<br />

temp->dat = 1; //2: занесение данного<br />

temp->next = NULL; // 3: новый элемент является<br />

// последним<br />

±£( start == NULL ) // Новый список (пустой)<br />

start = temp; // 4а: указатель на начало списка<br />

else<br />

{<br />

}<br />

геЬизпл;<br />

// 46: проходим весь список от начала^ пока текущий<br />

// элемент не станет последним<br />

сиг = start;<br />

wb±le( cur->next != NULL )<br />

// Продвижение по списку<br />

сиг = cur->next;<br />

// 4в: ссылка последнего элемента на новый^<br />

// добавляемый в конец списка<br />

cur->next = temp;<br />

// Печать содержимого списка на экран<br />

void Prlnt_ls (<br />

EL *start, // Указатель на начало списка<br />

сЬа2Г *pFileOut,// Указатель на файл вывода<br />

char *pMode ) // Указатель на режим открытия файла<br />

(<br />

EL *ргп; // Указатель на печатаемый элемент<br />

324<br />

±£( start == NULL )<br />

{<br />

printf ( "\п Список пуст. Распечатывать нечего \п" ) ;<br />

retu2m;<br />

}<br />

// Открываем файл вывода<br />

FILE *f_out = fopen( pFileOut, pMode ) ;<br />

±f( !f__out )<br />

{<br />

printf ( "\n Ошибка открытия файла вывода &s \л",<br />

pFlleOut ) ;


exit( 4 ) ;<br />

}<br />

prn = start; // Указатель на начало списка<br />

fprintf( f_out, "\л Состояние линейного списка: \п" )/<br />

int i ^ О;<br />

while ( prn ! = NULL ) // До конца списка<br />

{<br />

if( ! ( i%4 ) )<br />

fprintfi f_out, "\n" ) ;<br />

// Печать данных элемента<br />

fprintfi f_out, "%15d", prn->dat );<br />

prn = prn->next; // Продвижение no списку<br />

;<br />

// Закрываем файл вывода<br />

fclose( f_out )/<br />

rBturzi/<br />

}<br />

//***********************************************************<br />

// Разрушение списка<br />

void Dest_ls (<br />

EL *&start ) // Указатель на начало списка<br />

{<br />

±f( start == NULL )<br />

{<br />

printf( "\n Список пуст. Удалять нечего" ) ;<br />

return;<br />

}<br />

while( start /= NULL )<br />

Del_beg( start ) ; / / Удаление первого элемента списка<br />

return;<br />

}<br />

// ***********************************************************<br />

325


Удаление первого элемента списка<br />

void Del_beg(<br />

EL *&start ) // Указатель на начало списка<br />

{<br />

EL *del; // Указатель на удаляемый элемент<br />

±f( start == NULL )<br />

{<br />

printf ( "\n Список пуст. Удалять нечего" ) ;<br />

return/<br />

}<br />

// 1: подготовка первого элемента для удаления<br />

del - start;<br />

start = del->next; // 2: start сдвигается на второй<br />

// элемент<br />

delete del; // 1: удаление первого элемента<br />

return;


ПРИЛОЖЕНИЯ<br />

Приложение П.1. Тесты и программные проекты.<br />

Варианты заданий<br />

П.1.1. Тесты (контрольные работы)<br />

На практических занятиях по основным разделам курса целесообразно<br />

провести тестирование. Такими разделами являются:<br />

• программирование на ПМ-ассемблере;<br />

Q ввод;<br />

о вывод;<br />

• простейшие ветвления;<br />

а циклы;<br />

а структуры;<br />

Q функции;<br />

о области действия определений;<br />

о массивы и указатели;<br />

о работа с динамической памятью и операции с линейным списком;<br />

а препроцессор, перечисления, функции с умалчиваемыми значениями аргументов,<br />

перегрузка функций, шаблоны функций, перегрузка операций.<br />

П.1.1.1. Программирование на ПМ-ассемблере. Варианты<br />

тестов<br />

Изобразить схему программы и написать законченную<br />

программу на языке ПМ для решения заданной задачи. Для ввода и<br />

вывода использовать файлы MS DOS. Для обеспечения наглядности<br />

вывода использовать строковые данные.<br />

Вариант 1. Ввести и напечатать значения элементов массива<br />

целого типа с фиксированным размером 10 (для упрощения программы<br />

размер массива вводить не нужно). Вычислить и напечатать<br />

среднее значение для множества отрицательных элементов массива.<br />

Постарайтесь не потерять в вычисленном среднем значении дробную<br />

часть. Если массив не содержит элементов с отрицательными<br />

значениями, то в качестве ответа напечатать "В массиве нет отрицательных<br />

элементов".<br />

Вариант 2. Ввести и напечатать значения элементов массива<br />

целого типа с фиксированным размером 8 (для упрощения размер<br />

массива вводить не нужно). Вычислить и напечатать значение наи-<br />

327


большего элемента массива.<br />

Вариант 3. Ввести и напечатать значения элементов массива<br />

вещественного типа с размером 10. Вычислить и напечатать количество<br />

отрицательных элементов массива.<br />

Вариант 4, Ввести и напечатать значения элементов массива<br />

вещественного типа с размером 20. Вычислить и напечатать индекс<br />

наименьшего элемента массива.<br />

Вариант 5. Ввести и напечатать значения элементов массива<br />

целого типа с размером 20. Вычислить и напечатать среднее арифметическое<br />

для элементов массива. Постарайтесь, чтобы дробная<br />

часть в результате не потерялась.<br />

Вариант 6, Ввести и напечатать значение переменной х вещественного<br />

типа. Вычислить для нее восьмую степень и напечатать<br />

вычисленное значение. Решить задачу с использованием только<br />

трех умножений.<br />

Вариант 7. Ввести и напечатать значения переменных а, 6, с<br />

вещественного типа. Определить наибольшее значение среди них,<br />

присвоить его переменной d и напечатать. Решить задачу с использованием<br />

только двух сравнений.<br />

Вариант 8, Ввести и напечатать значения переменных а, Ь, с<br />

вещественного типа. Определить и напечатать, сколько среди них<br />

отличных от нуля.<br />

Вариант 9, Ввести и напечатать значения переменных а, b целого<br />

типа. Определить, равны ли они друг другу, и напечатать ответ.<br />

Вариант 10. Ввести и напечатать значения переменных а, b<br />

вещественного типа. Определить количество положительных значений<br />

среди заданных и напечатать ответ.<br />

Вариант 11, Ввести и напечатать значения переменной х вещественного<br />

типа. Вычислить и напечатать значение функции у :=<br />

\х\.<br />

Вариант 12. Ввести и напечатать значения переменных а, Ь, с,<br />

d вещественного типа. Определить и напечатать z := max( min( а, b ),<br />

max( с, d ) ).<br />

328


Вариант 13, Ввести и напечатать значения переменных х, у и<br />

Z вещественного типа. Вычислить и напечатать значения<br />

переменных и := тах( х, у, z ),/:= min( х, у, z ).<br />

Вариант 14. Ввести и напечатать значения переменных а, Ь, с,<br />

d вещественного типа. Сделать такую перестановку значений этих<br />

переменных, чтобы а приняло значение 6, b приняло значение с, с<br />

приняло значение а. Значения этих переменных после перестановки<br />

также напечатать.<br />

Вариант 15. Ввести и напечатать значения переменных хну<br />

вещественного типа. Вычислить и напечатать значения переменных<br />

и := тах( д:, у ),/:= min( д:, у ).<br />

Вариант 16. Ввести и напечатать значения переменных х, у^ z<br />

вещественного типа. Вычислить и напечатать целое/7 по правилу:<br />

Р : =<br />

1 при к = min{ X^ у^ Z ) ,<br />

2 при у = min{ X ^ у г Z ) ,<br />

3 при Z = min( X, у г z )<br />

Вариант 17. Ввести и напечатать значения переменных а, 6, с<br />

вещественного типа. Присвоить переменной а максимальное, а переменной<br />

Ъ - минимальное из указанных значений. После этого напечатать<br />

их значения.<br />

Вариант 18. Ввести и напечатать значение х вещественного<br />

типа. Вычислить и напечатать значение у:<br />

У : =<br />

+ 1<br />

О<br />

-1<br />

при X > О ^<br />

при X = О,<br />

при X < О<br />

Вариант 19. Ввести и напечатать значения переменных а, Ь, с,<br />

d вещественного типа. Определить и напечатать количество нулевых<br />

значений среди заданных.<br />

Вариант 20. Ввести и напечатать значения переменных а, 6, с,<br />

d вещественного типа, причем два из них одинаковы. Найти и напечатать<br />

значение, отличное от этих двух.<br />

329


п. 1.1.2. Ввод в языках Си/С++. Варианты тестов<br />

Ниже приведены варианты фрагмента программного кода, содержащего<br />

определения некоторых переменных. В комментариях к определениям<br />

переменных указано, какие значения переменных нужно ввести.<br />

Написать фрагмент программы, обеспечивающий:<br />

• открытие файла (потока Си) '4npuf^ для работы с файлом операционной<br />

системы "Test2.m'^;<br />

• ввод из этого файла (потока Си) значений переменных, указанных в<br />

комментариях к программному фрагменту соответствующего варианта;<br />

• закрытие файла (потока Си).<br />

Указать, как при этом будут выглядеть строки исходных данных<br />

в файле операционной системы ^^Test2Jn^^ (сделайте это обязательно,<br />

иначе Ваш ответ нельзя будет проверить).<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^foperC\ ^^fscanf\ Подключить необходимые<br />

стандартные заголовочные файлы.<br />

Вариант 7.<br />

double d;<br />

char s[ 3 ];<br />

unsigned long uli;<br />

//<br />

//<br />

//<br />

//<br />

4. 7<br />

"Ой<br />

31<br />

12<br />

short<br />

si;<br />

char c; // 'r'<br />

xnt 1; // -21<br />

Вариант^ 2.<br />

long double b; //4.7<br />

char s[ 3 ]; // "Я"<br />

long i; // -1<br />

short j ; //12<br />

Вариант 3.<br />

long double b; // 4.7e2<br />

char s[ 20 ]; // "4"<br />

int i; // 12<br />

unsigned j ; // 0x21<br />

Вариант 4.<br />

double b; //4.7<br />

char s[ 20 ]; // "Отлично'<br />

long int i; // -21<br />

330


unsxgned.<br />

long-<br />

Вариант<br />

float<br />

±nt<br />

char<br />

Вариант<br />

float<br />

±nt<br />

unstgnedL<br />

cbeir<br />

Вариант<br />

double<br />

float<br />

Int<br />

unsigned<br />

char<br />

Вариант<br />

long<br />

double<br />

float<br />

long<br />

±nt<br />

unsigned<br />

char<br />

Вариант<br />

float<br />

int<br />

char<br />

J/<br />

5.<br />


s[20]; // "Прочмтанная-строка'<br />

float<br />

long^<br />

char<br />

Варит чт 10.<br />

±nt<br />

br<br />

k;<br />

a;<br />

Clr<br />

s[20] ;<br />

// 5.0<br />

// 15,123<br />

// 27<br />

// 'B'<br />

// "Строка<br />

Вариант 11, Имеется следующий фрагмент Си-программы:<br />

float<br />

±nt<br />

char<br />

а, Ь;<br />

clr<br />

s[20]<br />

Строки исходных данных в файле (потоке Си)- ''stdin'' имеют<br />

следующий вид (каждая клетка содержит один символ):<br />

+ + + + + + + + + + + + + + +<br />

I I I - I 2 I I I 1 1 1 . 1 5 1 1.1 1 1 \п1<br />

+ + + + + + + + + + + + + + +<br />

| Э | т | о | | с ! г | р | о | к | а | I |\л|<br />

+ + + + + + + + + + + + + +<br />

1 1 1 2 1 I I I \л|<br />

+ + + + + + +<br />

Написать фрагмент программы, обеспечивающий чтение из<br />

файла с указателем ''stdin^\ следующих значений:<br />

а : 1.5 (должно быть прочитано значение 1.5)<br />

b : 14.7 i : -21 j : 12 cl : ' . '<br />

S : "строка"<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функцией библиотеки Си ^^scanf\ Подключить необходимые<br />

стандартные заголовочные файлы.<br />

float<br />

±nt<br />

char<br />

±nt<br />

Вариант 12, Имеется следующий фрагмент Си-программы:<br />

a г b;<br />

ir j ;<br />

clr c2r c3;<br />

RetCode;<br />

RetCode = fscanf( stdirir " %i %3d %c %c %c %f %f ",<br />

&ir &jr &clr &c2r Scc3r &br &a ) ;<br />

Строки исходных данных в файле (потоке Си) "stdin" имеют<br />

следующий вид (каждая клетка содержит один символ):<br />

332


c3<br />

+ + + + + + 4- + + + + + + + +<br />

I I 1 - I 7 I 7 I | - 1 2 | 4 | 3 1 5 | 5 | 7 | \ л |<br />

+ + + + + + + + + -|. + + + -I- +<br />

I \ 2 \ . \ 4 \ e \ 3 \ I I i I 4 I . I 7 I \л|<br />

+ + + + + + + + + + + + + +<br />

I I I 7 I 2 I I I \л|<br />

+ + + + + + +<br />

Какие значения получат переменные RetCode, а, b, /, j, cl, c2,<br />

Вариант<br />

13, Имеется следующий фрагмент Си-программы:<br />

float а, Ь/<br />

±nt i, j ;<br />

chsLx: cl, c2, c3;<br />

±nt<br />

RetCode;<br />

RetCode = fscanf( stdin, " %o 2%ld %c 5%c %c %f %f ",<br />

&i, &jr &cl, &c2, &c3, &b, &a ) ;<br />

Строки исходных данных в файле (потоке Си) ''stdin"<br />

следующий вид (каждая клетка содержит один символ):<br />

имеют<br />

сЗ<br />

I<br />

+<br />

I<br />

+<br />

I<br />

+<br />

17<br />

+<br />

17<br />

+<br />

1<br />

+<br />

I<br />

+<br />

I 2 I 4 I 3 I 5 I 5 I 7 I \л|<br />

+ + + + + + -I- +<br />

I \ 2 \ . \ 4 \ е \ 3 \ I \ 1 \ 4 \ . \ 7 \ \п\<br />

+ + + + + + + + + + + + + +<br />

I I I 7 I 2 I I I \л|<br />

+ + + + + + +<br />

Какие значения получат переменные RetCode, а, Ь, i, j, с 1, с2,<br />

Вариант<br />

14, Имеется следующий фрагмент Си-программы:<br />

float<br />

±nt<br />

chstr<br />

а г Ь;<br />

i/ J/<br />

clr s[ 20 ];<br />

Написать фрагмент программы, обеспечивающий:<br />

открытие файла (потока Си) ''Input" для работы с файлом операционной<br />

системы "Test2.in";<br />

ввод из этого файла (потока Си) следующих значений указанных ниже<br />

переменных:<br />

а<br />

1<br />

с1<br />

• 1.5<br />

• -21<br />

t г<br />

Ь .<br />

J •<br />

S .<br />

4. 7<br />

12<br />

"String<br />

• закрытие файла (потока Си).<br />

При этом строки исходных данных в файле операционной системы<br />

"Test2Jn" имеют следующий вид (каждая клетка содержит<br />

333


один символ):<br />

+ + + + + + + + + + + + + + +<br />

I I \ S \ t \ r \ i \ n \ g \ \ 1 \ 2 \ 1 1\л|<br />

I J I . I 5 I I \ - \ 2 \ 1 \ I 4 I . I 7 I \л|<br />

+ + + + + + + + + + + + + +<br />

I I - I . I - I I \л|<br />

+ + + + + + +<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подключить<br />

необходимые стандартные заголовочные файлы.<br />

Вариант 15. Имеется следующий фрагмент Си-программы:<br />

flo&t<br />

double<br />

±nt<br />

unsigned<br />

a;<br />

Ь;<br />

i;<br />

J r<br />

Написать фрагмент программы, обеспечивающий:<br />

• открытие файла (потока Си) '4npuf для работы с файлом операционной<br />

системы ''Test2.w''\<br />

• ввод из этого файла (потока Си) следующих значений указанных ниже<br />

переменных:<br />

а : 1.5 b : 4.7<br />

i : -21 j : 0x12<br />

• закрытие файла (потока Си).<br />

При этом строки исходных данных в файле операционной системы<br />

''Test2An'' имеют следующий вид (каждая клетка содержит<br />

один символ):<br />

+ + + + + + + + + + + + + + +<br />

I I I I I I I \ о \ к \ 1 \ 2 \ I \ \п\<br />

+ + + + + + + + + + + + + + +<br />

\ 1 \ . \ 5 \ I \ - \ 2 \ 1 \ I 4 I . I 7 I \л|<br />

-I- + 4- + + + + + + + + • + + +<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подключить<br />

необходимые стандартные заголовочные файлы.<br />

Вариант 16. Имеется следующий фрагмент Си-программы:<br />

flostt а;<br />

double Ь;<br />

long int i;<br />

unsigned long j ;<br />

Написать фрагмент программы, обеспечивающий:<br />

334


• открытие файла (потока Си) "Input'' для работы с файлом операционной<br />

системы "Test2Jn'';<br />

• ввод из этого файла (потока Си) следующих значений указанных ниже<br />

переменных:<br />

а: 1,5 Ь : 4.7<br />

±: -21 j : 0x12<br />

• закрытие файла (потока Си).<br />

При этом строки исходных данных в файле операционной системы<br />

"Test2Jn'' имеют следующий вид (каждая клетка содержит один символ):<br />

-j. + + + + -j- + + + -I- + + + -I- +<br />

I I I I I I I 10 1 x 1 1 1 2 1 I I \л|<br />

I i 1 . I 3 I I 1 - I 2 I I I I 4 I . I 7 I \л|<br />

+ + + + + + + + + + + + + +<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подключить<br />

необходимые стандартные заголовочные файлы.<br />

Вариант 17, Имеется следующий фрагмент Си-программы:<br />

float а г Ь;<br />

±nt i, j, ret code/<br />

chcir cl, c2 r c3;<br />

RetCode = fscanf( stdin, " %i %4d %c %c %c %f %f ",<br />

&i, &j, &cl, &c2, &c3, &ar &b ) ;<br />

При этом строки исходных данных в потоке stdin имеют следующий<br />

вид (каждая клетка содержит один символ):<br />

+ + + + + + + + + + + + + + +<br />

I I 1 1 1 7 1 \ 1 \ 2 \ 3 \ 4 \ 5 \ 6 \ 1 \ \ \п\<br />

+ + + + + + + + + + + 4- + + +<br />

\ 2 \ . \ 4 \ е \ 2 \ 1 1 I 7 I 2 I | 1 | . | 5 | \ л |<br />

+ + + + + + + + + + + + + + +<br />

Какие значения получат переменные RetCode, а, Ь, i, j, cl, с2,<br />

сЗ<br />

Вариант 18. Имеется следующий фрагмент Си-программы:<br />

float<br />

±nt<br />

cha.r<br />

а, b;<br />

^r J/<br />

cl, s[ 20 ];<br />

Написать фрагмент программы, обеспечивающий:<br />

открытие файла (потока Си) '4npuf' для работы с файлом операционной<br />

системы ''Test2dn''\<br />

335


• ввод из этого файла (потока Си) следующих значений указанных ниже<br />

переменных:<br />

а : 1.5 b : 14.7<br />

1 : 1 j : 12<br />

cl: ' .' s : "Это хорошо"<br />

• закрытие файла (потока Си).<br />

При этом строки исходных данных в файле операционной системы<br />

"Test2.iii" имеют следующий вид (каждая клетка содержит один<br />

символ):<br />

+ + + + + + + -1- + + -I- + + + +<br />

\ Э \ т \ о \ \ к \ о \ р \ о \ ш \ о \ I | 1 1 \ п |<br />

1 1 1 . 1 5 1 I 1 I 2 I I I 1 . 1 I I \л|<br />

+ + + + + + + + + + + + + +<br />

\ 1 \ 4 \ . \ 7 \ I I I I I ! I I \л|<br />

+ + + + + + + + + + + + + +<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подключить<br />

необходимые стандартные заголовочные файлы.<br />

Вариант 19, Имеется следующий фрагмент Си-программы:<br />

float<br />

±nt<br />

unsigned.<br />

char<br />

b;<br />

J, retcode;<br />

u;<br />

c4, s[ 20 ]<br />

Написать фрагмент программы, обеспечивающий:<br />

• открытие файла (потока Си) "Input'' для работы с файлом операционной<br />

системы "Test2»in";<br />

• ввод из этого файла (потока Си) следующих значений указанных ниже<br />

переменных:<br />

и : 21 b : 14.7<br />

j : 12<br />

с4: 'р' S : "Зима-вечер"<br />

• закрытие файла (потока Си).<br />

При этом строки исходных данных в файле операционной системы<br />

"Test2.in" имеют следующий вид (каждая клетка содержит один символ):<br />

336


+ + + + + + + + + + + + + + +<br />

I S l M l M l a l - l B l e l ^ J l e l p I \ p \ \ \n\<br />

+ + --- + + + + + + + + + + + + +<br />

I I 1 I 1 1 1 2 1 I I \ 2 \ 1 \ \ \n\<br />

+ + + -f + + + -I- -f + 4- -I- 4- +<br />

I i I 4 I . I 7 I I I I 1 I I I I \ л |<br />

H + + + + + + H + + + + + +<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подключить<br />

необходимые стандартные заголовочные файлы.<br />

Вариант 20. Имеется следующий фрагмент Си-программы:<br />

float<br />

ant<br />

unsigned.<br />

ciiajT<br />

Ь;<br />

jr retcode;<br />

и;<br />

c4, s[ 20 ];<br />

Написать фрагмент программы, обеспечивающий:<br />

• открытие файла (потока Си) ''Input" для работы с файлом операционной<br />

системы "Test2Jn";<br />

• ввод из этого файла (потока Си) следующих значений указанных ниже<br />

переменных:<br />

и :<br />

1 •<br />

с4:<br />

21<br />

12<br />

'Р'<br />

Ь .<br />

S .<br />

14. 7<br />

"Ура-вечер"<br />

• закрытие файла (потока Си).<br />

При этом строки исходных данных в файле операционной системы<br />

"Test2Jn'' имеют следующий вид (каждая клетка содержит один символ):<br />

+ + 4- + + + + + + + + + + + +<br />

1 " | 3 ^ 1 р | а | - | в ! е | ч | е | р | " | | |\л|<br />

+ + + + + + + + + + + + + + +<br />

I ' I р I ' I I I I 2 I I I I 2 I I I I \л|<br />

+ + -|. 4- + + + + + + Ч- + + +<br />

I 1 1 4 I . I 7 I I I I I I I I I \ л |<br />

-I- + ^ + 4- + + + + + + + + +<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подключить<br />

необходимые стандартные заголовочные файлы.<br />

П. 1.1.3. Вывод в языках Си/С++. Варианты тестов<br />

Ниже приведены варианты фрагмента программного кода, содержащего<br />

вывод в файл (поток Си) ''stdouf\ Укажите, как будут<br />

выглядеть строки вывода в файл (поток Си) ''stdouf после выполнения<br />

заданного фрагмента. Для удобства в приведенных вариантах<br />

337


фрагментов программного кода символ "^" обозначает пробельный<br />

символ.<br />

Вариант 1.<br />

floett г;<br />

±пЬ 1 = 17;<br />

г = l,5f * 2.0elf;<br />

fprlntf ( stdout, "*r=%5.2e^%s'^*l = %—i'd\n*%-3s\n".<br />

Вариант 2.<br />

float r;<br />

±nb 1 = 17/<br />

r = 1.5 * 2,Offprint<br />

f( stdout, "*r^%5.2f^%5s^*l=%-+10d\n*%-30s\n",<br />

r, "_", 1, "*", );<br />

Вариант 3.<br />

double r;<br />

int 1 = 17;<br />

r = 1.543 * 2. 0;<br />

fprlntf( stdoutr "*r=%5.21f'^%-4s^-^l = %- + 10d\n*%-8s\n",<br />

Вариант^ 4.<br />

float r = 3.0;<br />

int 1 = 17;<br />

fprlntf ( stdoutr "*r=%5.2f''%5s^*l = %- + 10d\n*%-30s\n",<br />

T^ II n ,• II Tic " ) .<br />

Вариант 5.<br />

float<br />

r = 1.5e2;<br />

±nt 1=7;<br />

fprlntf( stdout, "^%30s\n^r=%f^%5s^l=%10d\n", "*", r,<br />

""", 1 );<br />

Вариант 6.<br />

float r = 1.5e2;<br />

int 1=5;<br />

fprlntf( stdout, "^r=%f'^%-5s^l = %+10d\n'^%2.3s",<br />

Гг "*", 1, "строка" );<br />

Ниже приведены варианты фрагментов программного кода,<br />

содержащие определения данных и их инициализацию.<br />

338


Написать фрагмент программы, обеспечивающий:<br />

• Опфытие файла (потока Си) ^^Outpuf^ для работы с файлом операционной<br />

системы ^^ Tests. ouf\<br />

• Вывод в открытый поток ''Outpuf строк заданного вида.<br />

Указание. При выводе максимально использовать указанные в вариантах<br />

данные и возможности форматированного вывода.<br />

• Закрытие файла (потока Си).<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Qnfopen п/close. Подключить необходимые<br />

стандартные заголовочные файлы.<br />

Вариант<br />

7. Фрагмент Си-программы:<br />

long d = 254;<br />

double f = 1234.56;<br />

cha.r str[ ] = "Строка 1";<br />

Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

[+254^^]-^^ [-^^254]<br />

(+1234.6^) (1.234560E-h03^^)<br />

Вариант 8. Фрагмент Си-программы:<br />

±nt d = 254;<br />

float f = 1234.56;<br />

chstr str[ ] = "Строка";<br />

Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

[^'^•f-254]'^[254]<br />

(+1234.6'^) (1.234560Е+03)<br />

Вариант 9. Фрагмент Си-программы:<br />

±nt<br />

float t<br />

cha.r<br />

d = 254;<br />

f = 1234.56;<br />

str[ ] = "Строка<br />

Вид выводимых строк (ниже знак '^ обозначает пробел):<br />

[^'^+254]^[254]<br />

(+1234.6^) (1.234560Е+03)<br />

/^^^-^^^-^^^^Стр/<br />

Вариант 10. Фрагмент Си-программы:<br />

±nt d = 254;<br />

flosLt f = 1234.56;<br />

сЬлг *str = "Строка символов";<br />

339


Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

[+254] '^^[''^^^^254]<br />

(^^1234, б) ^-^ (1.234560Е+03)<br />

/^^'-^^^'^^^^Стр/^^ /м/<br />

Вариант 77. Фрагмент Си-программы:<br />

int d = 254;<br />

float f = 1234.56;<br />

cha.xr "^str = "Строка символов";<br />

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел):<br />

(-^'-^^^1234,5600) ^^ (1234.5600^^^^'')<br />

/Стр/^^/м/<br />

Вариант 12. Фрагмент Си-программы:<br />

±пЬ d = 113;<br />

float f = 12.34;<br />

char *str = "Строка символов";<br />

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел):<br />

[ + 113^^^^^]^^[^^^'-^254]<br />

(--^+12.3)<br />

/Строка^^^^^/^^/ил/<br />

Вариант, 13, Фрагмент Си-программы:<br />

±nt d = 254;<br />

float f = 1234.56;<br />

сЪат *str = "Строка символов";<br />

Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

^^^^^254]'^^[^^254^^''^^^^'^^^]<br />

(+1234. 6) ^'^ (^^^^^1.23Е+03)<br />

/ ^ ^ ^ ^ ^Строка/ "^ ^ / ^ "^лов/<br />

Вариант 14. Фрагмент Си-программы:<br />

±nt d - 254;<br />

float f = 1234.56;<br />

char *str = "Строка символов";<br />

Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

Y ^ ^ ^ ^ ^+254]^"[254 -^---;<br />

(1234. 5-----;^^(^1.234Е+03)<br />

/Стр --'---/'- ^/мв/<br />

340


Вариант 15. Фрагмент Си-программы:<br />

±пЬ d = 254;<br />

float, f = 1234.5 6;<br />

char *str = "Строка символов";<br />

Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

[-254^^^^^]^^[^^^^^+254]<br />

(^^^+1234.5600)<br />

/Строка симв^^^^/^^/т/<br />

Вариант 16. Фрагмент Си-программы:<br />

xnt d = 123;<br />

float f = 1234.56;<br />

char *str = "Прочитанная строка";<br />

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел):<br />

[^-^^^^^123]^^[1234. 6-^^^^^]<br />

(123-----)<br />

/^^^^^^^^^^Просто/<br />

Вариант 17. Фрагмент Си-программы:<br />

±Tit d = 123;<br />

float f = 1234.56;<br />

char *str = "Прочитанная строка";<br />

Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

[----- + 123]--[ + 1234. 6-----J<br />

(-----123.)<br />

Вариант 18. Фрагмент Си-программы:<br />

±nt d = 254;<br />

float f = 1234.56;<br />

char *str = "Строка символов";<br />

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел):<br />

[+254]--[-----254]<br />

(--1234. 6) -- (1.234560Е+03)<br />

/^^^^^-^^^^^Стр/-- /м/<br />

Вариант 19. Фрагмент Си-программы:<br />

int d = 123;<br />

float f = 1234.56;<br />

char *str = "Прочитанная строка";<br />

341


Вид выводимых строк (ниже знак ^ обозначает пробел):<br />

(123 ;<br />

Вариант 20. Фрагмент Си-программы:<br />

±пЬ d = 123;<br />

float f = 1234.56;<br />

сЪ.а.х: *str = "Прочитанная строка";<br />

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел):<br />

[^^^^-^ + 123] ^^[ + 1234. 6^^^^^]<br />

(^^^^^123,)<br />

* П.1.1.4. Простейшие ветвления. Варианты тестов<br />

Вариант 1. С помощью операторов ветвлений и присваивания<br />

записать фрагмент программы, вычисляющий значение переменной<br />

п по следующему правилу:<br />

[ л+1 при 1=1 или 1=5^<br />

п := [ а+Ь при 1=7 или 1=12,<br />

[ а-Ь в остальных случаях<br />

Вариант 2. С помощью операторов ветвлений и присваивания<br />

записать фрагмент программы, вычисляющий значение переменной<br />

п по следующему правилу:<br />

[ п+1 при а>0 и Ь=0,<br />

п := [ а-^Ь при а


[ 1 при i=l или 2 или 1,<br />

л := [ 2 при 1=10,<br />

[ О в остальных случаях<br />

Вариант 6. Изобразить фрагмент схемы алгоритма, соответствующий<br />

следующему фрагменту программы:<br />

±f( с == 1 ) а + + ; else ±f( с == 2 ) а-~; else ±f( с === 3 )<br />

а -h= 1/<br />

Вариант 7. С помощью операторов ветвлений и присваивания<br />

записать фрагмент программы, вычисляющий значение переменной<br />

п по следующему правилу:<br />

[ л+1 при 1=4,<br />

л := [ а+Ь при 1=1 или 7 или 9,<br />

[ а-Ь в остальных случаях<br />

Вариант 8. С помощью операторов ветвлений и присваивания<br />

записать фрагмент программы, вычисляющий значение переменной<br />

Z по следующему правилу:<br />

[ х+5 при а>2 и Ь=0,<br />

Z := [ а+Ь при а^ Да<br />

у<br />

Нет<br />

R:=X; P:=Y;<br />

R:=Y; Р:=Х:<br />

i к<br />

Q:=1;<br />

Рис. 101. Фрагмент схемы программы<br />

Вариант 11. Записать фрагмент программы, соответствующий<br />

следующему фрагменту схемы программы (рис. 102):<br />

343


X>Y<br />

Да<br />

Q:=1;<br />

Нет<br />

R:=Y; P:=X;<br />

Рис. 102. Фрагмент схемы программы<br />

Вариант 12. Изобразить фрагмент схемы алгоритма, соответствующий<br />

следующему фрагменту программы:<br />

±£( с < 3 ) ±f( с == 2 ) a-h + ; else b+-h; ±f( с < 2 ) c+-h/<br />

else a +=^ 1; { C+ + / b+ +; }<br />

Вариант 13. Изобразить фрагмент схемы алгоритма, соответствующий<br />

следующему фрагменту программы:<br />

[ х+5 при 1=1,3,5;<br />

Z := [ а-\-Ь при 1=2,4,6;<br />

[ к в остальных случаях<br />

Вариант 14. Записать фрагмент программы, соответствующий<br />

следующему фрагменту схемы программы (рис. 103):<br />


Вариант 17, С помощью операторов ветвлений и присваивания<br />

записать фрагмент программы, вычисляющий величину i ( i >= О<br />

) по следующему правилу:<br />

[ л+1 при 1=0,<br />

л := [ а+Ь при 1=2,4,6,8,10 и т.д.<br />

[ а-Ь в остальных случаях<br />

Вариант 18. Изобразить фрагмент схемы алгоритма, соответствующий<br />

следующему фрагменту программы:<br />

±£( с < 5 ) ±f( с == 1 ) а + + ; Ь- -= 1;<br />

Вариант 19. С помощью операторов ветвлений и присваивания<br />

записать фрагмент программы, вычисляющий значение переменной<br />

Z по следующему правилу:<br />

Z :=<br />

[ х-ь5 при а>2 и Ь=0,<br />

[ а+Ь при а


Вариант 2. Задан массив:<br />

double а[ 104 ];<br />

Написать фрагмент программы, который напечатает с новой<br />

строки значения элементов массива по пять элементов в строке и по<br />

пятнадцать позиций на элемент. Печатаемые значения прижимать к<br />

правой границе поля вывода, а положительные значения печатать со<br />

знаком плюс. Решить задачу с помощью цикла do-while.<br />

Вариант J. Задан массив:<br />

dLovible а[ 43 ] ;<br />

Написать фрагмент программы, который напечатает с новой<br />

строки значения элементов массива по четыре элемента в строке и<br />

по девятнадцать позиций на элемент. Печатаемые значения прижимать<br />

к левой границе поля вывода, положительные значения печатать<br />

со знаком плюс, а в дробной части печатать три цифры. Решить<br />

задачу с помощью цикла while.<br />

Вариант 4, Задан массив:<br />

воллЫе а[ 50 ];<br />

Написать фрагмент программы, который напечатает с новой<br />

строки значения элементов массива по четыре элемента в строке и<br />

по двадцать позиций на элемент. Печатаемые значения прижимать к<br />

правой границе поля вывода, а в дробной части печатать 6 цифр.<br />

Решить задачу с помощью цикла do..while.<br />

Вариант 5. Задан массив:<br />

double а[ 50 ];<br />

Написать фрагмент программы, который напечатает в уже открытый<br />

поток ''Output с новой строки значения элементов массива<br />

по четыре элемента в строке и по двадцать позиций на элемент.<br />

Печатаемые значения прижимать к правой границе поля вывода, а в<br />

дробной части печатать шесть цифр. Решить задачу с помощью цикла<br />

do..while. Использовать только один цикл.<br />

Подключить необходимые стандартные заголовочные файлы.<br />

Вариант 6, Дано следующее определение:<br />

±пЬ к;<br />

346


При каких исходных значениях к приведенный ниже цикл будет<br />

выполняться бесконечно<br />

wb±le ( к < 5 )<br />

k+'h;<br />

Возможные варианты ответов: при к = ..., или<br />

таких к не существует.<br />

Вариант<br />

±пЬ к, п;<br />

7. Пусть определены переменные<br />

Укажите, что напечатает следующий фрагмент программы (ниже<br />

знак ^ обозначает пробел):<br />

prlntf( "\n\n^%-2s-\n"r " " ) ;<br />

for( к = 7; к >= 5; к— ;<br />

I<br />

printf( "\п\п" ) ;<br />

п = 6 - к; printf( "%i--%-h3d-%5s--'\ к, л, "-" ) ;<br />

}<br />

Вариант 8. Дано следующее определение:<br />

int к;<br />

При каких исходных значениях к приведенный ниже цикл будет<br />

выполняться бесконечно<br />

Willie ( к >^ 15 ) к+ + ;<br />

Возможные варианты ответов: при А: — ..., или<br />

таких к не существует.<br />

Вариант 9. Пусть определены переменные:<br />

int к, п;<br />

Укажите, что напечатает следующий фрагмент программы (ниже<br />

знак ^ обозначает пробел):<br />

printf( "\n\n\t^%-2s-\n"r "12345" ) ;<br />

for( к = 5; к > 5; к++ )<br />

{<br />

printf( "\л\л" ; /<br />

п = б - к/ pr±ntf( "%±-^%4d^%2s^^", кг п, "-" ) ;<br />

}<br />

Вариант 10. Дано следующее определение:<br />

347


±nt к;<br />

При каких исходных значениях к приведенный ниже цикл будет<br />

выполняться бесконечно<br />

do<br />

{<br />

к+ + ;<br />

} while ( к > 10 ) ;<br />

Возможные варианты ответов: при к = ..., или<br />

таких к не существует.<br />

Вариант 11, Пусть определены переменные:<br />

±пЬ к, п;<br />

Укажите, что напечатает следующий фрагмент программы (ниже<br />

знак ^ обозначает пробел):<br />

printf( "\n%-3.2s\n", "*****" ) ;<br />

foi:( к = 5; к > 5; к-- )<br />

{<br />

}<br />

п ^ 6 - к; printf( "%i--%04d-%2s--", к, л, "-" ) ;<br />

Вариант 12. Дано следующее определение:<br />

int к;<br />

При каких исходных значениях к приведенный ниже цикл будет<br />

выполняться бесконечно<br />

do<br />

{<br />

к++ ;<br />

} while ( к > -5 ) ;<br />

Возможные варианты ответов: при к = ..., или<br />

таких к не существует.<br />

Вариант 13. Пусть определены переменные:<br />

int к, п;<br />

фрагмент программы (ни­<br />

Укажите, что напечатает следующий<br />

же знак ^ обозначает пробел):<br />

printf( "\n\n-%-5s-\n", " " ;/<br />

348


fojc( к ^ 5; к >= 5; к— ;<br />

(<br />

printf( "\п\п" ) ;<br />

п = 6 - к; printf( "%l--%4d-%2s--", к, л, "-" ) ;<br />

}<br />

Вариант 14. Дано следующее определение:<br />

±nt к;<br />

При каких исходных значениях к приведенный ниже цикл будет<br />

выполняться бесконечно<br />

while( к < 12 ) к++;<br />

Возможные варианты ответов: при к = ..., или<br />

таких к не существует.<br />

Вариант 15. Пусть определены переменные:<br />

±пЬ кг п;<br />

Укажите, что напечатает следующий фрагмент программы (ниже<br />

знак ^ обозначает пробел):<br />

printf( "\n%3s\n", "-" ) ;<br />

for( к = 5; к > 5; к-- )<br />

{<br />

}<br />

п = б - к; printf( "%i--%4d-%2s--", к, л, "-" ) ;<br />

Вариант 16. Сколько раз будет выполнено тело приведенного<br />

ниже цикла<br />

for( ±nt к=4; к= 1; к— ;<br />

(<br />

349


}<br />

п = 8 - к/ pr±ntf( "%i--%4d-%2s--", к, л, "-12" ) ;<br />

Вариант 18. Сколько раз будет выполнено тело приведенного<br />

ниже цикла<br />

±пЬ с = 3;<br />

foxi ±nt к=4/ к= -3; к— ;<br />

pr±ntf( "-%5d-%3s-"r к, "--" ) ;<br />

Вариант 20. Пусть определены переменные:<br />

±nt к^ п;<br />

Укажите, что напечатает следующий фрагмент программы (ниже<br />

знак ^ обозначает пробел):<br />

printf( "\n%6s\n", "-" ) /<br />

toxi к = 5/ к >= 1; к-- )<br />

{<br />

}<br />

п = 6 - к; pr±ntf( "%i--%4d-%2s--", к, л, "***" ) ;<br />

П.1.1.6. Структуры. Варианты тестов<br />

В ответах на приведенные ниже варианты тестов необходимо<br />

выполнить следующее.<br />

Закрыть открытые файлы, как только они станут не нужны.<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopeti'^ ^^fscanf\ Указать, какие<br />

включаемые файлы требует представленный фрагмент.<br />

Вариант 1. В файле операционной системы "Task4Jn'' хранится<br />

в текстовой форме ведомость сдачи экзаменов студентами некоторой<br />

группы. Каждая строка этого файла содержит сведения об одном<br />

студенте, представленные в следующем формате: позиции 1..,2 -<br />

350


порядковый номер студента в группе; позиция 3 - пробельная литера;<br />

позиции 4...22 - фамилия студента длиной не более 18 символов,<br />

в произвольном месте поля; позиция 23 - пробельная литера; позиция<br />

24.- четыре оценки по четырем предметам, разделенные не менее<br />

чем одной пробельной литерой. Количество студентов в группе<br />

равно 16. Пример строк указанного файла:<br />

01 Андреев 5 4 5 5<br />

02 Быков 5 5 5 5<br />

16 Яковлев 4 4 5 4<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости; 2) фрагмент программы, который заполнит экзаменационную<br />

ведомость данными, вводимыми из файла операционной<br />

системы "Task4Jn" (ввод данных должен осуществляться в текстовом<br />

режиме; 3) фрагмент программы, который вычисляет среднюю<br />

экзаменационную оценку по всем предметам и студентам (т.е.<br />

среднюю оценку из 64 оценок), а затем выводит значение этого показателя<br />

в файл операционной системы "Task4,out",<br />

Вариант 2. В файле операционной системы "f.in" имеется 10<br />

строк, каждая из которых содержит длины сторон прямоугольников<br />

(значения длин задаются в формате с плавающей точкой и разделены<br />

пробелами).<br />

Написать: 1) определение массива структур для хранения указанных<br />

длин сторон прямоугольников, их площадей и периметров;<br />

2) фрагмент программы для чтения длин сторон прямоугольников из<br />

файла операционной системы "/.ш"; 3) фрагмент программы, вычисляющий<br />

и печатающий площади и периметры прямоугольников в<br />

файл операционной системы ''f.ouf\<br />

Вариант 3. Имеется следующий фрагмент программы:<br />

stiract ExamReport // Строка экз. ведомости<br />

{<br />

// Фамилия студента<br />

char Name [ 15 ] ;<br />

unsigned Mark; // Экзаменационная оценка<br />

} ;<br />

/ / MA ТНета tics : в едомость по ма тема тике<br />

ExamReport Math [ 16 ];<br />

Написать фрагмент программы, который заполнит экзаменационную<br />

ведомость ^^Math^^ данными, вводимыми из файла операционной<br />

системы "Task4.in". Ввод данных должен осуществляться в<br />

текстовом режиме. В каждой строке файла "Task4.in" содержатся<br />

351


следующие поля данных: фамилия студента длиной не более 13<br />

символов, начинающаяся с позиции 1; экзаменационная оценка (в<br />

позиции 16). Между последней литерой фамилии и оценкой расположены<br />

пробельные литеры.<br />

Вариант 4, В файле операционной системы ''Task4.m" хранится<br />

в текстовой форме ведомость сдачи экзаменов студентами некоторой<br />

группы. Каждая строка этого файла содержит сведения об одном<br />

студенте, представленные в следующем формате: позиции 1...2 -<br />

порядковый номер студента в группе; позиция 3 - пробельная литера;<br />

позиции 4... 15 - фамилия студента длиной не более 11 символов,<br />

в произвольном месте поля; позиция 16 - пробельная литера; позиция<br />

17 - три оценки по трем предметам, разделенные не менее чем<br />

одной пробельной литерой. Количество студентов в группе равно<br />

16. Пример строк указанного файла:<br />

01 Андреев 5 4 5<br />

02 Быков 5 5 5<br />

16 Яковлев 4 5 4<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости, причем в связи с каждым студентом необходимо<br />

хранить только фамилию и три оценки, а порядковый номер студента<br />

должен быть представлен неявно, индексом элемента массива<br />

структур; 2) фрагмент программы, который заполнит экзаменационную<br />

ведомость данными, вводимыми из файла операционной системы<br />

''Task4Jn" (ввод данных должен осуществляться в текстовом режиме);<br />

3) фрагмент программы, который вычисляет среднюю экзаменационную<br />

оценку по всем предметам и студентам (т.е. среднюю<br />

оценку из 48 оценок), а затем выводит значение этого показателя в<br />

файл операционной системы "Task4.out'\<br />

Замечание, Очевидно, каждая строка исходных данных содержит<br />

лишние сведения: порядковый номер студента в группе<br />

(в начале строки). При вводе эти номера следует игнорировать<br />

(каким-либо способом).<br />

Вариант 5. Имеется следующий фрагмент программы:<br />

struct ExamReport // Строка экз. ведомости<br />

{<br />

// Фамилия студента<br />

cJiar- Name [ 15 ];<br />

unsigned. Mark; // Экзаменационная оценка<br />

} ;<br />

// MATHematlcs: экзаменационная ведомость ,по математике<br />

ExamReport Math[ 16 ];<br />

352


в каждой строке файла "Task4.in" содержатся следующие поля<br />

данных: фамилия студента длиной не более 13 символов, начинающаяся<br />

с позиции 1; экзаменационная оценка (в позиции 16). Между<br />

последней литерой фамилии и оценкой расположены.пробельные<br />

литеры.<br />

lianncaTb фрагмент программы, который заполнит экзаменационную<br />

ведомость ^'Math*^ данными, вводимыми из файла операционной<br />

системы "Task4.in" (ввод данных должен осуществляться в<br />

текстовом режиме).<br />

Вариант 6» В файле операционной системы ^^Test6.in" имеется<br />

пять строк, каждая из которых содержит длины сторон прямоугольников<br />

(значения длин разделены двумя пробелами).<br />

Написать: 1) определение массива структур для хранения указанных<br />

длин сторон прямоугольников, их площадей и периметров;<br />

2) фрагмент программы для чтения длин сторон прямоугольников из<br />

файла операционной системы "Test6.in"; 3) фрагмент программы,<br />

вычисляющий и печатающий площади и периметры прямоугольников<br />

в файл операционной системы "Test6.ouf\<br />

Вариант 7. Имеется следующий фрагмент программы:<br />

sbract ExamReport // Строка экз. ведомости<br />

{<br />

// Фамилия студента<br />

char Name [ 15 ];<br />

unsigned Markl; // Экзаменационная оценка 1<br />

unsigned. Mark2; // Экзаменационная оценка 2<br />

} Ехат[ 16 ] ;<br />

В каждой строке файла "Task4.in" содержатся следующие поля<br />

данных: фамилия студента длиной не более 13 символов, начинающаяся<br />

с позиции 1; экзаменационная оценка (в позиции 16); пробел<br />

(в позиции 17); экзаменационная оценка (в позиции 18). Между последней<br />

литерой фамилии и первой оценкой расположены пробельные<br />

литеры.<br />

Написать фрагмент программы, который заполнит экзаменационную<br />

ведомость "£xa/z" данными, вводимыми из файла операционной<br />

системы "Task4,in" (ввод данных должен осуществляться в<br />

текстовом режиме).<br />

Вариант 8. Имеется следующий фрагмент программы:<br />

struct EXAM_REPORT // Строка экзаменационной ведомости<br />

{<br />

353


chajT fam[ 21 ];// Фамилия экзаменуемого<br />

int mark; // Экзаменационная оценка<br />

} math[ 16 ];<br />

// Абсолютная успеваемость (процент студентов с<br />

// положительными оценками)<br />

£1оа.Ь<br />

аи;<br />

// Качественная успеваемость ( процент студентов, получивших<br />

// "4" и "5" )<br />

float<br />

ки;<br />

В каждой строке этого файла содержится фамилия студента<br />

длиной не более 19 символов, начинающаяся с позиции 1, и экзаменационная<br />

оценка (поз. 22). Между фамилией и оценкой расположены<br />

"пробелы".<br />

Написать: 1) фрагмент программы для чтения экзаменационной<br />

ведомости из текстового файла "/./«"; 2) фрагмент программы,<br />

вычисляющей и печатающей в файл "/.ow/" абсолютную и качественную<br />

успеваемость группы по математике.<br />

Вариант 9, В файле операционной системы ''Task4,in'' хранится<br />

в текстовой форме ведомость со сведениями о продуктах. Каждая<br />

строка этого файла содержит сведения об одном виде продукта,<br />

представленные в следующем формате: позиции 1...2 - порядковый<br />

номер продукта; позиция 3 - пробельная литера; позиции 4... 15 — название<br />

продукта длиной не более 11 символов, в произвольном месте<br />

поля; позиция 16 - пробельная литера; позиции 17... 19 - содержание<br />

белка в 100 граммах продукта (целое).<br />

Количество продуктов в ведомости равно 16. Пример строк<br />

указанного файла:<br />

01<br />

02<br />

16<br />

минтай<br />

щука<br />

сметана<br />

20<br />

21<br />

15<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости и фрагмент программы, который заполнит ведомость<br />

данными, вводимыми из файла операционной системы<br />

"Task4.in'' (ввод данных должен осуществляться в текстовом режиме);<br />

2) фрагмент программы для нахождения и печати (в файл<br />

"Task4.out") информацию о продукте с наибольшем содержанием<br />

белка.<br />

Вариант 10, В текстовом файле ''Task4.in'' содержится список<br />

книг библиотеки, имеющий следующий вид:<br />

01 Иванов Программирование 20,500<br />

354


15 Петров Архиваторы 7.200<br />

Каждая строка списка содержит сведения об одной книге: первые<br />

две позиции - порядковый номер книги, третья позиция - "пробел",<br />

с поз. 4 начинается фамилия автора длиной не более 13 символов,<br />

поз. 18...34 - название книги (из одного слова), поз. 35 - "пробел",<br />

с поз. 36 - стоимость книги.<br />

Написать: 1) определение массива структур для хранения указанного<br />

списка и фрагмент программы для чтения списка из файла<br />

"Task4Jn''; 2) фрагмент программы, вычисляющей и печатающей<br />

среднюю стоимость книг в библиотеке в файл ''Task4.ouf\<br />

Вариант 11, В текстовом файле "Task4.in" содержится информация<br />

о квартире, имеющая следующий вид:<br />

01 Комната 15<br />

05 Кухня 5<br />

Каждая строка содержит сведения об одной комнате: первые<br />

две позиции - порядковый номер комнаты, третья позиция - "пробел",<br />

с поз. 4 начинается название комнаты длиной не более 15 символов,<br />

с поз. 21 - метраж комнаты.<br />

Написать: 1) определение массива структур для хранения указанных<br />

данных и фрагмент программы для чтения данных о квартире<br />

из файла "Task4.m"; 2) фрагмент программы для нахождения и<br />

печати общего метража данной квартиры в файл "Task4.out".<br />

Вариант 12. В текстовом файле "Task4.i7" содержится ведомость<br />

сдачи экзаменов студентами некоторой группы, имеющая следующий<br />

вид:<br />

01 Андреев 5 4 5<br />

16 Петров 4 5 4<br />

Каждая строка ведомости содержит сведения об одном студенте:<br />

первые две позиции - порядковый номер студента, третья позиция<br />

- "пробел", с поз. 4 начинается фамилия студента длиной не более<br />

11 символов, поз. 16...20 — оценки по трем предметам. Каждой<br />

оценке предшествует пробел, а первой оценке может предшествовать<br />

и большее число "пробелов".<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости и фрагмент программы для чтения ведомости из<br />

файла ''Task4.in''\ 2) фрагмент программы для нахождения и печати<br />

355


списка должников (студентов, имеющих хотя бы одну двойку ) в<br />

файл "Task4.ouf\<br />

Вариант 13. В текстовом файле "Task4.in" содержится ведомость<br />

сдачи экзаменов студентами некоторой группы, имеющая следующий<br />

вид:<br />

01 Андреев 5 4 5<br />

1 б Петров 4 5 4<br />

Каждая строка ведомости содержит сведения об одном студенте:<br />

первые две позиции - порядковый номер студента, третья позиция<br />

- "пробел", с поз. 4 начинается фамилия студента длиной не более<br />

11 символов, поз. 16...20 — оценки по трем предметам (математике,<br />

программированию и физике). Каждой оценке предшествует<br />

пробел, а первой оценке может предшествовать и большее число<br />

"пробелов".<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости и фрагмент программы для чтения ведомости из<br />

файла "Task4Jn"; 2) фрагмент программы для нахождения и печати<br />

списка должников по программированию в файл "Task4.out".<br />

Вариант 14. В текстовом файле "Task4.in" имеется ведомость<br />

сдачи экзаменов студентами некоторой группы:<br />

01 Андреев 5 4 5<br />

1 б Петров 4 5 4<br />

Каждая строка ведомости содержит сведения об одном студенте:<br />

первые две позиции - порядковый номер студента, третья позиция<br />

- "пробел", с поз. 4 начинается фамилия студента длиной не более<br />

10 символов, поз. 16...20 — оценки по трем предметам (математике,<br />

программированию и физике). Каждой оценке предшествует<br />

пробел, а первой оценке может предшествовать и большее число<br />

"пробелов".<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости и фрагмент программы для чтения ведомости из<br />

файла "Task4Jn"; 2) фрагмент программы для нахождения и печати<br />

списка студентов, сдавших физику на "отлично" в файл "Task4.out".<br />

Вариант 15. В текстовом файле "Task4.in" содержатся сведения<br />

о предприятиях сферы обслуживания районов города, имеющие<br />

следующий вид: ^<br />

356


1. Калининский 10 20 7<br />

10. Выборгский 15 10 9<br />

Каждая строка содержит сведения об одном районе: первые<br />

две или три позиции - номер района (с точкой), далее следует один<br />

или два "пробела", поз. 5.. 19 — название района длиной не более 14<br />

символов, далее следуют три целых числа, каждому из которых<br />

предшествуют один или более пробелов. Первое число задает количество<br />

аптек, второе — универсамов, а третье - химчисток.<br />

Написать: 1) определение массива структур для хранения указанной<br />

информации и фрагмент программы для чтения данных из<br />

файла "Task4.in''; 2) фрагмент программы для нахождения и печати<br />

в файл "Task4.out" названия района (или районов ), в котором (в<br />

которых) находится больше всего аптек.<br />

Вариант 16, В текстовом файле "Task4.m" содержится информация<br />

о квартире, имеющая следующий вид:<br />

01 Комната 15<br />

05 Кухня 5<br />

Каждая строка содержит сведения об одной комнате: первые<br />

две позиции - порядковый номер комнаты, третья позиция - "пробел",<br />

с поз. 4 начинается название комнаты длиной не более 15 символов,<br />

с поз. 21 - метраж комнаты.<br />

Написать: 1) определение массива структур для хранения указанных<br />

данных и фрагмент программы для чтения данных о квартире<br />

из файла "Та^-Ы.ш"; 2) фрагмент программы для нахождения и<br />

печати в файл "Task4.ouf' метража самой большой по площади комнаты<br />

в квартире.<br />

Вариант 17» Ъ текстовом файле ''Task4.in'' содержится список<br />

книг библиотеки, имеющий следующий вид:<br />

01 Иванов Программирование 20.500<br />

15 Петров Архив а торы 7.200<br />

Каждая строка списка содержит сведения об одной книге: первые<br />

две позиции - порядковый номер книги, третья позиция - "пробел",<br />

с поз. 4 начинается фамилия автора длиной не более 12 символов,<br />

поз. 18.,.34 - название книги (из одного слова), поз. 35 - "пробел",<br />

с поз. 36 - стоимость книги.<br />

Написать: 1) определение массива структур для хранения указанного<br />

списка и фрагмент программы для чтения списка из файла<br />

357


''Task4An''\ 2) фрагмент программы, вычисляющей и печатающей<br />

полные данные (номер, автор, название и цена) самой дорогой книги<br />

в библиотеке в файл "Task4.ouf\<br />

Вариант 18. В текстовом файле "Task4.m" содержится ведомость<br />

сдачи экзаменов студентами некоторой группы, имеющая следующий<br />

вид:<br />

01 Андреев 5 4 5<br />

16 Петров 4 5 4<br />

Каждая строка ведомости содержит сведения об одном студенте:<br />

первые две позиции - порядковый номер студента, третья позиция<br />

- "пробел", с поз. 4 начинается фамилия студента длиной не более<br />

11 символов, поз. 16..20 — оценки по трем предметам. Каждой<br />

оценке предшествует пробел, а первой оценке может предшествовать<br />

и большее число "пробелов".<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости и фрагмент программы для чтения ведомости из<br />

файла "Task4.in"; 2) фрагмент программы для нахождения и печати<br />

списка студентов-тоечников (сдавших экзамены на одни тройки) в<br />

файл ''Task4.ouf\<br />

Вариант 19. В файле операционной системы "/ш" имеется 10<br />

строк, каждая из которых содержит длины сторон прямоугольников<br />

(значения длин задаются в формате с плавающей точкой и разделены<br />

пробелами).<br />

Написать: 1) определение массива структур для хранения указанных<br />

длин сторон прямоугольников, их площадей и периметров;<br />

2) фрагмент программы для чтения длин сторон прямоугольников из<br />

файла операционной системы "/ш"; 3) фрагмент программы, вычисляющий<br />

и печатающий длины сторон прямоугольников, имеющих<br />

максимальные периметр и площадь в файл операционной системы<br />

у.оиГ,<br />

Вариант 20. В файле операционной системы "Task4.m" хранится<br />

в текстовой форме ведомость со сведениями о продуктах. Каждая<br />

строка этого файла содержит сведения об одном виде продукта,<br />

представленные в следующем формате: позиции 1...2 - порядковый<br />

номер продукта; позиция 3 - пробельная литера; позиции 4... 15<br />

- название продукта длиной не более 11 символов, в произвольном<br />

месте поля; позиция 16 - пробельная литера; позиции 17... 19 — содержание<br />

белка в 100 граммах продукта (целое); позиция 20 - пробельная<br />

литера; позиции 21...23 — калорийность 100 грамм продукта<br />

358


(целое). Количество продуктов в ведомости равно 16. Пример строк<br />

указанного файла:<br />

01 минтай<br />

02 щука<br />

16 сметана<br />

20<br />

21<br />

15<br />

100<br />

120<br />

150<br />

Написать: 1) определение массива структур для хранения указанной<br />

ведомости и фрагмент программы, который заполнит экзаменационную<br />

ведомость данными, вводимыми из файла операционной<br />

системы ''Task4.in^' (ввод данных должен осуществляться в<br />

текстовом режиме); 2) фрагмент программы для нахождения и<br />

печати (в файл ''Task4.ouf') названия продукта (продуктов) с<br />

наибольшей калорийностью.<br />

П.1.1.7. Функции. Варианты тестов<br />

В ответах на приведенные ниже варианты тестов выполнить<br />

следующее.<br />

Для решения указанных в вариантах тестов задач написать<br />

прототип, определение функции и пример ее вызова.<br />

Для передачи в функцию исходных данных и получения из нее<br />

ответов использовать список параметров. Бдинственный ответ луч><br />

ше получать из функции как возвращаемое значение.<br />

Вариант 1, Вычислить тах:=наиб{а,^,с}. Исходные данные<br />

имеют тип с плавающей точкой.<br />

Вариант 2, В массиве целого типа определить количество положительных,<br />

отрицательных и нулевых элементов.<br />

Вариант 3. Вычислить тах:=наиб{л,/)} и тш:=наим{а,6}.<br />

Вариант 4. Подсчитать в одномерном массиве целого типа<br />

размером 100 элементов наименьшее значение среди положительных<br />

элементов.<br />

Вариант 5. Подсчитать в одномерном массиве целого типа<br />

размером 100 элементов среднее арифметическое значение. Постарайтесь<br />

не потерять в ответе дробную часть.<br />

Вариант 6. Подсчитать в одномерном массиве целого типа<br />

размером 100 элементов индекс и значение последнего из положительных<br />

элементов.<br />

359


Вариант 7. Подсчитать в одномерном массиве целого типа<br />

размером 100 элементов количество нулевых значений.<br />

Вариант 8, Сформировать одномерный массив с элементами<br />

z[ i ] ( О


Вариант 14. Поменять местами первый и последний элемент,<br />

второй и предпоследний и т.д. в одномерном массиве вещественного<br />

типа заданного размера.<br />

Вариант 15. Вычислть среднее арифметическое для положительных<br />

чисел в одномерном массиве целого типа заданного размера.<br />

Постарайтесть не потерять дробную часть результата и избежать<br />

возможного деления на нуль.<br />

Вариант 16. Написать функцию нахождения минимального<br />

элемента среди отрицательных и максимального элемента среди положительных<br />

в одномерном массиве целого типа заданного размера.<br />

Вариант 17. Написать функцию нахождения максимального<br />

положительного числа кратного пяти в одномерном массиве целого<br />

типа заданного размера.<br />

Вариант 18. Найти количество нулевых элементов в одномерном<br />

массиве целого типа заданного размера и сформировать новый<br />

массив из ненулевых элементов исходного массива.<br />

Вариант 19. В одномерном массиве вещественного типа заданного<br />

размера найти сумму элементов, расположенных между<br />

максимальным и минимальным элементами. Указанный результат<br />

получить из функции как возвращаемое значение.<br />

Вариант 20. Сжать одномерный массив вещественного типа<br />

заданного размера. С этой целью удалить из массива все элементы,<br />

абсолютное значение которых меньше единицы. Освободившиеся в<br />

конце массива элементы заполнить нулями.<br />

П.1.1.8. Области действия определений. Варианты тестов<br />

В ответах на приведенные ниже варианты тестов укажите, как<br />

будут выглядеть строки, выведенные на экран в результате выполнения<br />

программы, приведенной в соответствующем варианте. В ответе<br />

укажите также местоположение пробелов.<br />

Вариант 1. Что напечатает следующая программа<br />

^include <br />

±nt i == Or j = 2;<br />

int main ( void )<br />

{<br />

auto int i = 0/<br />

361


printf( "± = %d j^%d \л", i, j ) ;<br />

{<br />

±nt i =2, j ^ 0;<br />

pr±ntf( "± = %d j = %d \n", 1, j ) ;<br />

{<br />

Izit j = 10; i += 1; j += 2;<br />

printf( "i = %d j=%d \л", i, j ) ;<br />

}<br />

printf( "i = %d j=%d \л", i, j ) ;<br />

}<br />

printf( "i = %d j = %d \n", i, j ) ;<br />

z-etuzrn 0;<br />

} // end function "main"<br />

Вариант 2, Что напечатает следующая программа<br />

^include <br />

±nt i = 10, j = 2;<br />

±nt main ( void )<br />

}<br />

{<br />

auto ±nt i == 8;<br />

{<br />

±nt j = 0; printfi "i = %d j = %d \л", i, j ).<br />

{<br />

int j = 10; i += 1; j += 2;<br />

printf( "i=%d j=%d \n", 1, j ) ;<br />

}<br />

j + + ; printfi "i = %d j=^%d \n", i, j ) ;<br />

}<br />

printfi "i = %d j = %d \л", i, j ) ;<br />

return. 0;<br />

Вариант 3. Что напечатает следующая программа<br />

^include <br />

Int i, j = 1;<br />

±nt maini void )<br />

}<br />

{<br />

±nt i = 5;<br />

{<br />

{<br />

±nt j = 2; j += 3;<br />

}<br />

j += 5; printfi "i+l=%d j=%d \n", i+1, j ) ;<br />

}<br />

printfi "i = %d j = %d \n", i, j ) ;<br />

return 0;<br />

Вариант 4. Что напечатает следующая программа<br />

362


^include <br />

±пЬ 1 =^ 1, j = 10;<br />

Inb main ( void. )<br />

(<br />

±nt i = 3;<br />

{<br />

printf( "i + l=%d j=%d \л", i+1, j ) ;<br />

}<br />

{<br />

±nt j = 1; j += 3;<br />

}<br />

j -f- 5; printf( "l=%d j^%d \л", i, j ) ;<br />

}<br />

printfi "i = %d j-i-l=%d \л", i, j-hl ) ;<br />

iretuxn 0;<br />

Вариант 5. Что напечатает следующая программа<br />

^include <br />

int i = 1; j = 10;<br />

±nb main ( void. )<br />

{<br />

int i = 3;<br />

I<br />

printf( "i+l^%d j = %d \n"r i+1, j ) ;<br />

(<br />

int j = 1; j +=^ 3;<br />

}<br />

j += 5; printf( "i=%d j=%d \n", i, j ) ;<br />

}<br />

printf( "i = %d j + l==%d \n'\ i, j+1 ) ;<br />

геЬ\12ПЛ 0;<br />

}<br />

Вариант 6. Что напечатает следующая программа<br />

^include <br />

int i, j ;<br />

int main ( void )<br />

}<br />

{<br />

auto int i = 3;<br />

{<br />

printf( "i + l=%d j=%d \л", i + 1, j ) ;<br />

{<br />

auto int j = 1; j += 3;<br />

printf( "i = %d j = %d \n", i/j ) ;<br />

}<br />

j += 5; printf( "i = %d j = %d \n", i, j ) ;<br />

}<br />

printf( "i = %d j + l = %d \n", i, j+1 ) ;<br />

return 0;<br />

363


Вариант<br />

^include <br />

±nt 1 = 10^ j /<br />

int main ( void, )<br />

{<br />

}<br />

7. Что напечатает следующая программа<br />

static int i = 3/<br />

{<br />

printf( '4 = %d j = %d \n", i, j ) ;<br />

{<br />

auto int j = 10; i += 1; j += 2;<br />

prlntf( '4 = %d j = %d \n", 1, j ) ;<br />

}<br />

j += 5; prlntf( "l = %d j = %d \n", 1, j ) ;<br />

}<br />

prlntfi "l = %d j = %d \n", i, j+1 ) ;<br />

return 0;<br />

Вариант 8. Что напечатает следующая программа<br />

^Include <br />

int 1=10, j =2;<br />

int main ( void )<br />

{<br />

auto int 1=8;<br />

{<br />

int j = 0; prlntf( "l=%d j=%d \n'\ i, j )<br />

{<br />

int j = 10; 1 += 1; j += 2;<br />

prlntf( "l = %d j = %d \n", 1, j ) ;<br />

}<br />

j++; prlntfi "l = %d j = %d \л", i, j ) ;<br />

}<br />

prlntf( '4 = %d j = %d \л", i, j ) ;<br />

return 0;<br />

Вариант 9. Что напечатает следующая программа<br />

^Include <br />

// Прототипы функций<br />

int next ( int ) ; int reset ( void ) ;<br />

int last ( int ) ; int naw ( int ) ;<br />

int 1=1;<br />

int main ( void )<br />

{<br />

364<br />

auto int 1, j ; 1 = reset( ) ;<br />

fori j = 1; 3


printf( "last( i ) - %d\n", last ( i ) ) ;<br />

prlntf ( "naw( 1+j ) = %d\n", naw ( 1-hj ) ) ;<br />

}<br />

retuxrn 0;<br />

±nt reset ( void. )<br />

return 1/<br />

±nt next ( xnt j )<br />

return ( j = i + + ) ;<br />

int last ( int j )<br />

static int 1 =^ 10; return ( j = l-~ ) ;<br />

int naw ( int i ;<br />

auto int j = 10; return( i = j += 1 ) ;<br />

Вариант 10, Что напечатает следующая программа<br />

^include <br />

// Прототипы функций<br />

int next ( int ) ; int reset ( void ) ;<br />

int last ( int ) ; int naw( int ) ;<br />

int 1=1;<br />

int main ( void )<br />

{<br />

auto int Ir j ; 1 = reset ( ) ;<br />

fori j = 1; j


±nt naw ( ±пЬ i )<br />

{<br />

}<br />

auto ±nb j - 10; return( 1 - j ~= i ) ;<br />

Вариант 11. Что напечатает следующая программа<br />

^include <br />

// Прототипы функций<br />

±nt next ( ±zit ) ; int reset ( -void ) ;<br />

±nt last ( ±nt ) / i^t naw( ±nt ) ;<br />

Int i = 1;<br />

±nt main ( void )<br />

{<br />

auto ±nt i, j ; i = reset( ) ;<br />

fori J, = I; J


{<br />

print f ( "\nl = %d j = %d\n", i, J )/<br />

print f( "next ( 1 ) = %d\n"^ next ( 1 ) ) ;<br />

prlntf( "last( i ) = %d\n"r last ( 1 ) ) ;<br />

print f ( "naw( 1+j ) = %d\n" r naw ( 1+j ) )/<br />

}<br />

jcetum 0;<br />

int reset( void )<br />

jretujrn i /<br />

int next ( ±nt j )<br />

jcGtuim ( j = --i ) ;<br />

int last ( int j )<br />

static int 1; jret-ami j = i-f-/- ) ;<br />

int naw ( int 1 )<br />

auto int j = 5; retuim ( 1 = j += 1++ ) /<br />

Вариант 13, Что напечатает следующая программа<br />

^Include <br />

// Up ототипы функций<br />

int next ( int ) ; int reset ( void ) ;<br />

int last ( int ) ; int naw( int ) ;<br />

int 1 = 2;<br />

int main ( void )<br />

{<br />

auto int i, j ; 1 = reset ( ) ;<br />

for( j = 0; j


{<br />

static int i = 4; return ( j = i + + ) ;<br />

}<br />

±Tit naw ( ±nt 1 )<br />

{<br />

}<br />

auto int j = 3; return ( 1 = j += i ) ;<br />

Вариант 14. Что напечатает следующая программа<br />

^include <br />

// Прототипы функций<br />

int next ( int ) ; void reset( void ) ;<br />

int last ( int ) ; int naw( int ) ;<br />

int i;<br />

int main ( void )<br />

{<br />

auto int j ; reset( ) ;<br />

for( j = 2; j


auto ±nt j , i; i = reset ( ) ;<br />

£or( j = 2; j


;<br />

±nt last ( int j )<br />

{<br />

st&tic int i = 6; jretixm {" j = --i ) ;<br />

}<br />

int naw( int 1 )<br />

{<br />

}<br />

auto int j = 3; JoetvLTni i = j -= 1 ) ;<br />

Вариант 17. Что напечатает следующая программа<br />

^include <br />

// Прототипы функций<br />

int next ( int ) ; int reset ( void. ) ;<br />

int last ( int ) ; int naw( int ) ;<br />

int 1 = 4/<br />

int main ( void )<br />

{<br />

auto int J, 1/ 1 = reset( ) ;<br />

£or( j = 2; j < 4 ; j++ )<br />

{<br />

prlntf( "\nl = %d j = %d\n", reset ( ) , j ) ;<br />

print f( "next( 1 ) = %d\n", next ( 1 ) ) ;<br />

prlntf( "last( 1 ) = %d\n", last( 1 ) ) ;<br />

prlntfi "naw( 1+j ) = %d\n", naw( 1+j ) ) ;<br />

}<br />

return 0;<br />

int reset ( void )<br />

return 1++;<br />

int next ( int j )<br />

return ( j = i-- ) ;<br />

int last ( int j )<br />

static int 1 = 5; return( j = 1++ ) ;<br />

int naw( int 1 )<br />

auto int j = 4; return( 1 = j += 1 ) ;<br />

Вариант 18. Что напечатает следующая программа<br />

^Include <br />

// Прототипы функций<br />

int next ( int ) ; int reset ( void ) ;<br />

int last ( int ) ; int naw( int ) ;<br />

int 1 = 10;<br />

370


int main ( void )<br />

{<br />

auto int jr i/ i = reset( ) ;<br />

fori j = 2; j < 4; j+ч- )<br />

{<br />

printf( "\ni = %d j = %d\n"r reset ( ) , j ) ;<br />

{<br />

static int i = 7/ int j = 10;<br />

prlntf( "\ni = %d j = %d\n", i-h+, j ) ;<br />

}<br />

print f ( "next ( i ) = %d\n", next ( i ) ) ;<br />

printf( "last( i ) = %d\n", last ( i ) ) ;<br />

printfC "naw( i-f-j ) = %d\n", naw( i+j ) ) ;<br />

}<br />

return 0;<br />

int reset ( void )<br />

return( i + 5 ) ;<br />

int next ( int j )<br />

return( j = i~- ) ;<br />

int last ( int j )<br />

static int 1=^1; return ( j = i-h+ )/<br />

int naw( int i )<br />

auto int j = 3; return( i = j -= i ) ;<br />

Вариант<br />

19. Что напечатает следующая программа<br />

^include <br />

// Пр ото типы функций<br />

int next ( int ) ; int reset ( void ) ;<br />

int last ( int ) ; int naw( int ) ;<br />

int i = 10;<br />

int main ( void )<br />

{<br />

auto int j , i; i = reset ( ) ;<br />

for( 1 = 2; j < 4 ; j-h+ )<br />

{<br />

print f ( "\ni = %d j = %d\n", reset f ^, j ) ;<br />

printf( "next ( i ) = %d\n", next ( i ) ) ;<br />

printf( "last( i ) = %d\n", last ( i ) ) ;<br />

printf( "naw( i+j ) = %d\n"r naw( i+j ) ) ;<br />

}<br />

return 0;<br />

}<br />

int reset ( void )<br />

371


eturn( i + 5 ) ;<br />

±nt next ( ±nt j )<br />

return ( j = i-- )/<br />

±nt last ( ±nt j )<br />

static ±nt i = 1; return ( j = i-h+ ) ;<br />

int naw ( int 1 )<br />

auto int j = 3; return ( 1 = j -= 1 ) ;<br />

Вариант 20. Что напечатает следующая программа<br />

^Include <br />

// Прото типы функций<br />

int next ( int ) ; int reset ( int ) ;<br />

int last ( int ) ; int navj ( int ) ;<br />

int 1=3;<br />

int main ( void )<br />

{<br />

auto int jr 1 = 5; 1 = reset ( 1/2 ) ;<br />

£or( j = 6; j < 8 ; j++ )<br />

{<br />

prlntf( "\nl = %d j = %d\n'\ 1, j ) ;<br />

prlntf ( "next ( 1 ) = %d\n" r next ( 1 ) ) ;<br />

prlntf( "last( 1 ) = %d\n", last( 1 ) ) ;<br />

prlntf ( "naw( 1+j ) = %d\n'\ naw( 1+j ) ) /<br />

I<br />

return 0;<br />

int reset( int 1 )<br />

retuim. 1;<br />

int next ( int j )<br />

return( j = -~1 ) ;<br />

int last ( int j )<br />

static int 1 = 4; retum( j = l + -h ) ;<br />

int naw ( int 1 )<br />

auto int j = 4; return ( 1 = j ~= 1 ) ;<br />

372


п.1.1.9. Массивы и указатели. Варианты тестов<br />

В ответах на приведенные ниже варианты тестов укажите, как<br />

будут выглядеть строки, выведенные на экран в результате выполнения<br />

программы, приведенной в соответствующем варианте. В ответе<br />

укажите также местоположение пробелов.<br />

Вариант 7. Что напечатает следующая программа<br />

^include <br />

±nt Array[ ] = { 0 , 4 , 5 , 2 , 3 } ;<br />

±nt main ( void )<br />

{<br />

}<br />

int Index^ ^Pointer;<br />

£or( Index = 0/ Index


Int Index ^ "^Pointer;<br />

£or( Index = 1; Index


Вариант 6. Что напечатает следующая программа<br />

^Include <br />

±nt Array[ ] = { 0 г 4 г 5 г 2 , 3 } ;<br />

±пЬ main ( void )<br />

{<br />

}<br />

±nt Index, ^Pointer;<br />

£ог( Index = 0; Index


^include <br />

Int Array[ J = { 0 , 4 , 5 , 2 , 3 } /<br />

±nt main ( void, )<br />

}<br />

{<br />

±nt Index, '^'Pointer;<br />

£or( Index = 0; Index


£ою ( р = а, ± = О; р + i = a + 1; p-- )<br />

printf( " %3d", *p— ) ;<br />

printf( "\n" ) ;<br />

retvLirn 0;<br />

Вариант 13. Что напечатает следующая программа<br />

^include <br />

xnt main ( void. )<br />

{<br />

I<br />

±nt a[ ] = { 10, 11, 12, 13, 14, 15 }, i, *p;<br />

for( p = a+2, i = 0; p + i =a + l; p— ;<br />

printf ( " %3d", *—p ) ;<br />

printf ( "\n" ) ;<br />

return 0;<br />

Вариант 14. Что напечатает следующая программа<br />

^include <br />

±пЬ main ( void. )<br />

{<br />

Int a[ ] = { 10, 11, 12, 13, 14, 15 }, i, *p;<br />

fori p == a, i = 0;p-hi= a ; p~- )<br />

printf( " %3d", *p— ;/<br />

printf ( "\n" ) ;<br />

jretujm 0;<br />

}<br />

Вариант 15. Что напечатает следующая программа<br />

^include <br />

Int main ( void )<br />

{<br />

int a[ ] ^ { 15, 11, 10, 13, 14, 10 }, i, *p;<br />

£or ( p = a, i = 0;p-hi= a ; p -= 2 )<br />

printf( " %3d", *p ) ;<br />

printf ( "\n" ) ;<br />

return 0;<br />

Ъ11


Вариант 16, Что напечатает следующая программа<br />

^include <br />

±nt main ( void )<br />

{<br />

}<br />

±nt a[ 3 J[ 3 J ^ { { 1, 2, 3 },<br />

{ 4, 5, 6 Ь<br />

( 7, 8, 9 } };<br />

±nt *pa[ 3 ] = { a[ 1 J, a[ 2 ], a[ 1 ] }/<br />

for( int 1 = 0; 1 < 3 ; i++ )<br />

printf( "%d %d %d\n", a[ 1 ][ 2-i J,<br />

*(*(ач-±) +!),*( pa [ 1 ] ) ) ;<br />

jretujrn 0;<br />

Вариант 17. Что напечатает следующая программа<br />

^Include <br />

±пЬ main( void )<br />

{<br />

±nt a[ 3 ][ 3 ] = { { 1, 2, 3 },<br />

{ 4, 5, 6 Ь<br />

/ 7 Й Я ) } '<br />

int *pa[ 3 ] = { a[ 2 ]\ a] 0 ], a[ 2 ] };<br />

for( int i = 0; i < 2 ; i++ )<br />

printf( "%d %d %d\n", a[ i ][ 2-i ],<br />

*(*(a + i ) + i ) , * ( p a [ i ] ) ) ;<br />

return 0;<br />

Вариант 18, Что напечатает следующая программа<br />

^include <br />

int main ( void )<br />

}<br />

{<br />

int a[ 3 ][ 3 ] = { { 1, 2, 3 },<br />

{4,5,6 Ь<br />

{ 7, 8, 9 } };<br />

int *pa[ 3 ] = { a[ 2 ], a[ 0 ], a[ 1 ] };<br />

for( int i = 0; i < 2 ; i++ )<br />

printf( "%d %d %d\n", a[ i ][ 2-i 7,<br />

*(*(a + i ) + i ) , ^ ( p a [ i ] ) ) ;<br />

return 0;<br />

Вариант 19, Что напечатает следующая программа<br />

^include <br />

int main ( void )<br />

{<br />

378


±nt a[ 3 ] [ 3 ] == { { Ir 2, 3 } г<br />

{ 4, 5, 6 Ь<br />

{ 7, 8, 9 } };<br />

±nt *pa[ 3 ] - { a[ 2 ], a[ 0 ], a[ 1 ] };<br />

fo2:( int i = 0/ i < 2 ; i++ )<br />

printf( "%d %d %d\n"r a[ 1 ][ 2-i 7,<br />

*(*(a + i ) + l ) , ^ ( p a [ l ] ) ) /<br />

return 0;<br />

Вариант 20. Что напечатает следующая программа<br />

^Include <br />

int main ( void, )<br />

{<br />

±nt a[ 3 ] [ 3 ] -' { { 1, 2, 3 },<br />

( 4, 5, 6 ;,<br />

{ 7, 8, 9 } };<br />

int *pa[3] = { a [ 0 ] , a [ l ] , a [ 2 ] } .<br />

£or( int i = 2; i > 0 ; i— ;<br />

printf( "%d %d %d\n'\ a[ i ][ 2~i ],<br />

*(*(a + i ) + i ) , * ( p a [ i ] ) ) ;<br />

z-etuxn 0;<br />

П. 1.1.10. Операции над линейным списком.<br />

Работа с динамической памятью. Варианты тестов<br />

Вариант /. Определен следующий структурный тип:<br />

struct Node // NODE: узел линейного списка<br />

{<br />

} ;<br />

Node *рЫпк/ // Pointer LINK:<br />

// указатель на очередной узел<br />

floatt Info; // INFOrmation: информация<br />

В текстовом файле операционной системы ''TestSAn'' содержится<br />

некоторое количество вещественных чисел, разделенных<br />

символами пробельной группы ( ' ', '\/', '\«' ).<br />

Написать прототип, определение и пример вызова функции, которая<br />

должна ввести из файла ^'TestS.in" содержащиеся в нем вещественные<br />

числа и запомнить их в узлах линейного списка, в котором каждый<br />

узел (динамически размещенная в памяти структура) имеет тип Node.<br />

При этом первое прочитанное число должно находиться в последнем от<br />

начала узле линейного списка, второе число - в предпоследнем узле и<br />

т.д.<br />

Все исходные данные (указатель на **имя. расширение**<br />

файла ввода) и все результаты работы функции (указатель на<br />

начало линейного списка) должны передаваться через список<br />

379


параметров. С целью обработки ошибок предусмотреть контроль<br />

значений, возвращаемых функциями библиотеки Си<br />

^^fopen^\ ^^fscanf^ и операцией new. Подключить необходимые<br />

стандартные заголовочные файлы.<br />

Вариант 2, Определен следующий указатель на начало линейного<br />

списка:<br />

stJTuct Node<br />

{<br />

Node<br />

dovible<br />

}<br />

*рЫпк;<br />

In fo ;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrmat ion: инф ормация<br />

Написать прототип, определение и пример вызова функции,<br />

которая должна определить, сколько в линейном списке имеется<br />

элементов с отрицательными значениями. В частном случае, перед<br />

вызовом этой функции линейный список может быть пуст.<br />

Все исходные данные (указатель на начало линейного списка)<br />

и все результаты работы функции (количество найденных<br />

элементов) должны передаваться через список параметров — это<br />

обязательное требование.<br />

Вариант 3. Определен следующий указатель на начало линейного<br />

списка:<br />

stxnict<br />

{<br />

}<br />

Node<br />

float<br />

Node<br />

// NODE: узел линейного списка<br />

*pL±nk; // Pointer LINK:<br />

// указатель на очередной узел<br />

In fo; // INFOrma tion: информа ция<br />

*start/<br />

Написать прототип, определение и пример вызова функции,<br />

которая должна определить, сколько в линейном списке имеется<br />

элементов, в которых хранится заданное значение add. В частном<br />

случае, перед вызовом этой функции линейный список может быть<br />

пуст.<br />

Все исходные данные (add^ указатель на начало линейного<br />

списка) и все результаты работы функции (количество найденных<br />

элементов) должны передаваться через список параметров —<br />

это обязательное требование.<br />

Вариант 4. Определен следующий /-^азатель на начало линейного<br />

списка:<br />

380


struct Node // NODE: узел линейного списка<br />

{<br />

Node *pLlnk; // Pointer LINK:<br />

// указатель на очередной узел<br />

±nt Info; // INFOrmation: информация<br />

} *start;<br />

Написать прототип, определение и пример вызова функции,<br />

которая должна в начало линейного списка добавить еще один элемент,<br />

в котором будет храниться значение add. В частном случае,<br />

перед вызовом этой функции линейный список может быть пуст.<br />

Все исходные данные {add^ указатель на начало линейного<br />

списка) и все результаты работы функции (указатель на начало<br />

линейного списка) должны передаваться через список параметров.<br />

С целью обработки ошибок предусмотреть контроль значения,<br />

возвращаемого операцией new.<br />

Вариант 5. Определен следующий указатель на начало линейного<br />

списка:<br />

struct Node // NODE: узел линейного списка<br />

{<br />

Node *pLink; // Pointer LINK:<br />

// указатель на очередной узел<br />

float Info; // INFOrmation: информация<br />

} *start;<br />

Написать прототип, определение и пример вызова функции,<br />

которая должна в конец линейного списка добавить еще один элемент,<br />

в котором будет храниться значение add. В частном случае,<br />

перед вызовом этой функции линейный список может быть пуст.<br />

Все исходные данные {add, указатель на начало линейного<br />

списка) и все результаты работы функции (указатель на начало<br />

линейного списка) должны передаваться через список параметров.<br />

С целью обработки ошибок предусмотреть контроль значения,<br />

возвращаемого операцией new.<br />

Вариант 6. Определен следующий структурный тип:<br />

struct Node // NODE:<br />

{ // узел линейного списка<br />

Node ^pLink; // Pointer LINK: указатель на<br />

// очередной узел списка<br />

in t Info; // INFOrm at ion:<br />

// содержательная информация<br />

} ;<br />

В текстовом файле операционной системы "TestS.in"<br />

содер-<br />

381


жится некоторое количество целых чисел, разделенных символами<br />

пробельной группы ( ' ', V, '\«' ).<br />

Написать прототип, определение и пример вызова функции,<br />

которая должна BBCCTJI ИЗ файла ''TestS.in'' содержащиеся в нём целые<br />

числа и запомнить их в узлах линейного списка, в котором каждый<br />

узел (динамически размещенная в памяти структура) имеет тип<br />

Node. При этом первое прочитанное число должно находиться в<br />

первом от начала узле линейного списка, второе число - во втором<br />

узле и т.д.<br />

Все исходные данные (указатель на "имя. расширение**<br />

файла ввода) и все результаты работы функции (указатель на<br />

начало линейного списка) должны передаваться через список<br />

параметров. С целью обработки ошибок предусмотреть контроль<br />

значений, возвращаемых функциями библиотеки Си<br />

^^fopen^\ ^^fscanf^ и операцией new. Подключить необходимые<br />

стандартные заголовочные файлы.<br />

Вариант 7. Определен следующий указатель на начало линейного<br />

списка:<br />

зЬгасЬ Node // NODE: узел линейного списка<br />

{<br />

Node *рЫпк; // Pointer LINK:<br />

// указатель на очередной узел<br />

float Info; // INFOrmation: информация<br />

} *start;<br />

Написать прототип, определение и пример вызова функции,<br />

которая в процессе просмотра списка выводит данные (числа) в<br />

файл на магнитном диске ^'f.ouV\ не разрушая информацию в линейном<br />

списке. В частном случае, перед вызовом этой функции линейный<br />

список может быть пуст.<br />

Все исходные данные (указатель на начало линейного списка,<br />

указатель на **имя. расширение** файла вывода) должны передаваться<br />

через список параметров. С целью обработки ошибок<br />

предусмотреть контроль значения, возвращаемого функцией<br />

библиотеки Си ^^fopen^\ Подключить необходимые стандартные<br />

заголовочные файлы.<br />

Вариант 8. Определен следующий указатель на начало линейного<br />

списка:<br />

struct Node // NODE: узел линейного списка<br />

{<br />

Node *рЫпк; // Pointer LINK:<br />

// указатель на очередной узел<br />

382


I<br />

float<br />

Info;<br />

*start;<br />

// INFOrm at ion: информа ция<br />

Написать прототип, определение и пример вызова функции,<br />

которая в процессе просмотра списка выводит данные (числа) в<br />

файл на магнитном диске "f.ouf\ одновременно освобождая память,<br />

занятую линейным списком. В частном случае, перед вызовом этой<br />

функции линейный список может быть пуст.<br />

Все исходные данные (указатель на начало линейного списка,<br />

указатель на **имя. расширение** файла вывода) и результаты<br />

работы функции (указатель на начало линейного списка)<br />

должны передаваться через список параметров. С целью обработки<br />

ошибок предусмотреть контроль значения, возвращаемого<br />

функцией библиотеки Си ^^fopen^\ Подключить необходимые<br />

стандартные заголовочные файлы.<br />

Вариант 9. Определен следующий указатель на начало линейного<br />

списка:<br />

stxnjct Node<br />

{<br />

Node<br />

float<br />

}<br />

*рЫпк;<br />

Info;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrm ation : информа ция<br />

Написать прототип, определение и пример вызова функции<br />

для удаления из списка к последних элементов с освобождением<br />

занятой ими памяти. В частном случае, перед вызовом этой функции<br />

линейный список может быть пуст или может содержать любое количество<br />

элементов.<br />

Все исходные данные (указатель на начало линейного списка,<br />

количество удаляемых элементов) и результаты выполнения<br />

функции (указатель на начало линейного списка) должны<br />

передаваться через список параметров.<br />

Вариант 10. Определен следующий указатель на начало линейного<br />

списка:<br />

stjract Node<br />

(<br />

Node<br />

float<br />

*рЫпк;<br />

Info;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrm ation: информа ция<br />

Написать прототип, определение и пример вызова функции<br />

383


для удаления из списка к первых элементов с освобождением занятой<br />

ими памяти. В частном случае, перед вызовом этой функции линейный<br />

список может быть пуст или может содержать любое количество<br />

элементов.<br />

Все исходные данные (указатель на начало линейного списка,<br />

количество удаляемых элементов) и результаты выполнения<br />

функции (указатель на начало линейного списка) должны<br />

передаваться через список параметров.<br />

Вариант 11, Определен следующий указатель на начало линейного<br />

списка:<br />

stmict Node<br />

{<br />

}<br />

Node<br />

±nt<br />

*pLink/<br />

In fo ;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrm ati on : ин форма ция<br />

Написать прототип, определение и пример вызова функции<br />

для вставки в линейный список после каждого элемента, в котором<br />

хранится значение find, элемента, в котором будет храниться значение<br />

add. В частном случае, перед вызовом этой функции линейный<br />

список может быть пуст или может содержать любое количество<br />

элементов.<br />

Все исходные данные (find^ add^ указатель на начало линейного<br />

списка) должны передаваться через список параметров.<br />

С целью обработки ошибок предусмотреть контроль значения,<br />

возвращаемого операцией new.<br />

Вариант 12. Определен следующий указатель на начало линейного<br />

списка:<br />

stmjct Node<br />

{<br />

}<br />

Node<br />

±nt<br />

*рЫпк;<br />

In fo ;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrm at Ion: информа ция<br />

Написать прототип, определение и пример вызова функции<br />

для удаления из линейного списка элемента, следующего после каждого<br />

элемента, в котором хранится знгченне find. В частном случае,<br />

перед вызовом этой функции линейный список может быть пуст или<br />

может содержать любое количество элементов.<br />

Все исходные данные (Jind^ указатель на начало линейного<br />

списка) должны передаваться через список параметров.<br />

384


Вариант 13. Определен следующий указатель на начало линейного<br />

списка:<br />

stxract Node<br />

{<br />

}<br />

Node<br />

±nt<br />

*pLink;<br />

Info;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrmation: информация<br />

Написать прототип, определение и пример вызова функции<br />

для удаления из линейного списка элемента, предшествующего каждому<br />

элементу, в котором хранится значение y«(i. В частном случае,<br />

перед вызовом этой функции линейный список может быть пуст или<br />

может содержать любое количество элементов.<br />

Все исходные данные {find^ указатель на начало линейного<br />

списка) и результаты выполнения функции (указатель на начало<br />

линейного списка) должны передаваться через список параметров.<br />

Вариант 14. Определен следующий указатель на начало линейного<br />

списка:<br />

зЬгасЬ Node<br />

{<br />

}<br />

Node<br />

*pLink;<br />

Info;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrm at ion: информа ция<br />

Написать прототип, определение и пример вызова функции<br />

для вставки в линейный список перед каждым элементом, в котором<br />

хранится значение find, элемента, в котором будет храниться значение<br />

add. В частном случае, перед вызовом этой функции линейный<br />

список может быть пуст или может содержать любое количество<br />

элементов.<br />

Все исходные данные (find, add^ указатель на начало линейного<br />

списка) и результаты выполнения функции (указатель<br />

на начало линейного списка) должны передаваться через список<br />

параметров. С целью обработки ошибок предусмотреть контроль<br />

значения, возвращаемого операцией new.<br />

Вариант 15. Определен следующий указатель на начало линейного<br />

списка:<br />

stzTict Node // NODE: узел линейного списка<br />

385


}<br />

Node<br />

±nt<br />

*pL±nk;<br />

Info;<br />

*start;<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

/ / INFOrm at ion: мн ф орма ция<br />

Написать прототип, определение и пример вызова функции<br />

для удаления из линейного списка второго, четвертого и т.д. элементов.<br />

В частном случае, перед вызовом этой функции линейный<br />

список может быть пуст или может содержать любое количество<br />

элементов.<br />

Вариант 16. Определен следующий указатель на начало линейного<br />

списка:<br />

struct Node<br />

{<br />

}<br />

Node<br />

±nt<br />

*рЫпк;<br />

Info/<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

/ / INFOrm at ion: ин форма ция<br />

Написать прототип, определение и пример вызова функции<br />

для модификации каждого из элементов линейного списка, в котором<br />

хранится значение find. Модификация подобных элементов заключается<br />

в хранении в них значения add. В частном случае, перед<br />

вызовом этой функции линейный список может быть пуст или может<br />

содержать любое количество элементов.<br />

Все исходные данные (find^ add^ указатель на начало линейного<br />

списка) должны передаваться через список параметров.<br />

Вариант 17. Определен следующий указатель на начало линейного<br />

списка:<br />

stxract Node<br />

{<br />

}<br />

Node<br />

±nt<br />

*pLink;<br />

Info;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrm at ion: информа ция<br />

Написать прототип, определение и пример вызова функции<br />

для удаления первого и последнего элементов списка с освобождением<br />

занятой ими памяти. В частном случае, перед вызовом этой<br />

функции линейный список может быть пуст или может содержать<br />

любое количество элементов.<br />

Все исходные данные (указатель на начало линейного списка)<br />

и результаты работы функции (указатель на начало линей-<br />

386


ного списка) должны передаваться через список параметров.<br />

Вариант 18. Определен следующий указатель на начало линейного<br />

списка:<br />

stjTuct Node<br />

{<br />

}<br />

Node<br />

*pLink;<br />

Informs<br />

tart;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrmation: информация<br />

Написать прототип, определение и пример вызова функции<br />

для вставки новых элементов и в начало (в него помещается значение<br />

addjbeg), и в конец (в него помещается значение add_end) линейного<br />

списка. В частном случае, перед вызовом этой функции линейный<br />

список может быть пуст или может содержать любое количество<br />

элементов.<br />

Все исходные данные {addbeg^ add_end указатель на начало<br />

линейного списка) и результаты работы функции (указатель<br />

на начало линейного списка) должны передаваться через список<br />

параметров. С целью обработки ошибок предусмотреть контроль<br />

значения, возвращаемого операцией new.<br />

Вариант 19. Определен следующий указатель на начало линейного<br />

списка:<br />

stxTict Node<br />

{<br />

}<br />

Node<br />

±nt<br />

*pLlnk;<br />

Info;<br />

*start;<br />

// NODE: узел линейного списка<br />

// Pointer LINK:<br />

// указатель на очередной узел<br />

// INFOrmation: информация<br />

Написать прототип, определение и пример вызова функции<br />

для вставки в линейный список после каждого элемента, в котором<br />

хранится значение//7(i, двух элементов, в которых будут храниться<br />

значения addl и add2, В частном случае, перед вызовом этой функции<br />

линейный список может быть пуст или может содержать любое<br />

количество элементов.<br />

Все исходные данные {find^ addl^ add2^ указатель на начало<br />

линейного списка) должны передаваться через список параметров.<br />

С целью обработки ошибок предусмотреть контроль значения,<br />

возвращаемого операцией new.<br />

Вариант 20. Определен следующий указатель на начало линейного<br />

списка:<br />

387


stmict Node // NODE: узел линейного списка<br />

{<br />

Node *pLink; // Pointer LINK:<br />

// указатель на очередной узел<br />

xnt Info; // INFOrmation: информация<br />

} *start;<br />

Написать прототип, определение и пример вызова функции<br />

для вставки в линейный список перед каждым элементом, в котором<br />

хранится значение yz«


Вариант 3. Функции с умалчиваемыми значениями параметров.<br />

Имеется следующий фрагмент программного кода:<br />

voxd. DrawCircle ( izit к=100, Int у=100, ±nt radius=100 ) ;<br />

Является ли запись прототипа функции правильной (обоснуйте<br />

ответ) Являются ли правильными приведенные ниже вызовы функции<br />

В случае положительного ответа укажите, с какими значениями<br />

параметров функция будет выполняться<br />

DrawCircle( ) ;<br />

DrawCircle( 200 ) ;<br />

DrawCircle( 200, 300 ) ;<br />

DrawCircle( 200, 300, 400 ) ;<br />

DrawCircle( , , 400 ) ;<br />

Являются ли правильными приводимые ниже записи прототипов<br />

функций (обоснуйте ответ)<br />

void. DrawCircle ( int х, int у=100, Int rad ) ;<br />

void. DrawCircle ( int x, int y=100, int radlus=100 ) ;<br />

void DrawCircle ( int x, int y, int radlus=100 ) ;<br />

Вариант 4. Шаблоны функций, В одномерном массиве, состоящем<br />

из п элементов, вычислить сумму отрицательных элементов.<br />

Исходные данные и полученные результаты обязательно<br />

передавать через список параметров. Написать прототип, определение<br />

шаблона функций и пример ее вызова для типов int, float и<br />

double.<br />

Вариант 5. Перегрузка операций для пользовательских типов.<br />

Определен следующий пользовательский тип для работы с комплексными<br />

данными:<br />

struct CMP // CoMPlex: комплексный тип<br />

(<br />

dovible г; // Вещественная часть<br />

double i; // Мнимая часть<br />

} ;<br />

Написать определение функции, перегружающей операцию<br />

суммирования комплексных данных, и пример вызова этой функции.<br />

Имейте ввиду, что вещественная часть суммы равна сумме вещественных<br />

частей операндов. Аналогично — для мнимых частей.<br />

Вариант б. Перегрузка операций для пользовательских типов.<br />

Определен следующий пользовательский тип для работы с комплексными<br />

данными:<br />

389


struct CMP // CoMPlex: комплексный тип<br />

{<br />

} ;<br />

double r; // Вещественная часть<br />

double ±; // Мнимая часть<br />

Написать определение функции, перегружающей операцию<br />

вычитания комплексных данных, и пример вызова этой функции.<br />

Имейте ввиду, что вещественная часть разности равна разности вещественных<br />

частей операндов. Аналогично - для мнимых частей.<br />

Вариант 7. Шаблоны функций. В одномерном массиве, состоящем<br />

из п вещественных элементов, вычислить сумму элементов<br />

массива с нечетными номерами. Исходные данные и полученные<br />

результаты обязательно передавать через список параметров.<br />

Написать прототип, определение шаблона функций и пример ее вызова<br />

для типов intafloat и double.<br />

Вариант 8. Перегрузка операций для пользовательских типов.<br />

Определен следующий пользовательский тип:<br />

struct<br />

{<br />

} ;<br />

V<br />

int arr[ 4 ]; // Вектор<br />

Написать определение функции, перегружающей операцию<br />

вычитания векторов, и пример вызова этой функции. Имейте ввиду,<br />

что разность векторов равна поэлементной разности векторов.<br />

Вариант 9. Перегрузка операций для пользовательских типов.<br />

Определен следующий пользовательский тип:<br />

struct<br />

{<br />

} ;<br />

V<br />

double arr[ 4 ]; // Вектор<br />

Написать определение функции, перегружающей операцию<br />

суммирования векторов, и пример вызова этой функции. Имейте<br />

ввиду, что сумма векторов равна поэлементной сумме векторов.<br />

Вариант 10. Функции с умалчиваемыми значениями параметров.<br />

Имеется следующий фрагмент программного кода:<br />

void Rect ( float w, tloat 1=1,5 ) ;<br />

390


Является ли запись прототипа функции правильной (обоснуйте<br />

Ваш ответ) Являются ли правильными приведенные ниже вызовы<br />

функции В случае положительного ответа укажите, с какими значениями<br />

параметров функция будет выполняться<br />

Rect ( ) ;<br />

Rect ( 2.0 ) ;<br />

Rect ( 2.00, 3.00 ) ;<br />

Вариант 11. Шаблоны функций. В одномерном массиве, состоящем<br />

из п элементов, вычислить наибольшее значение элемента<br />

массива. Исходные данные и полученные результаты обязательно<br />

передавать через список параметров. Написать прототип, определение<br />

шаблона функций и пример ее вызова для типов long,<br />

float и double.<br />

Вариант 12. Директивы препроцессора. Опишите:<br />

• действия препроцессора по директиве include;<br />

• различие форматов ^include и include ''file.h'\<br />

Вариант 13. Функции с умалчиваемыми значениями параметров.<br />

Где следует указывать умалчиваемые значения параметров<br />

функции (в прототипе, в заголовке определения функции, в обоих<br />

перечисленных местах)<br />

Вариант 14. Функции с умалчиваемыми значениями параметров.<br />

Имеется следующий фрагмент программного кода:<br />

void Point ( double х, double у=-1.5 ) ;<br />

Является ли запись прототипа функции правильной (обоснуйте<br />

Ваш ответ) Являются ли правильными приведенные ниже вызовы<br />

функции В случае положительного ответа укажите, с какими значениями<br />

параметров функция будет выполняться<br />

Point ( , ) ;<br />

Point ( 2.0, -1.5 ) ;<br />

Point ( 2.00, 3.00, 4.7 ) ;<br />

Point ( 4.7 ) ;<br />

Вариант 15. Шаблоны функций. В одномерном массиве, состоящем<br />

из п элементов, вычислить среднее арифметическое значение<br />

для отрицательных элементов массива. Постарайтесь не потерять<br />

дробную часть результата. Исходные данные и полученные<br />

результаты обязательно передавать через список параметров.<br />

Написать прототип, определение шаблона функций и пример ее вы-<br />

391


зова для типов int и double.<br />

Вариант 16. Шаблоны функций. В одномерном массиве, состоящем<br />

из п элементов, вычислить максимальный по модулю отрицательный<br />

элемент массива. Исходные данные и полученные результаты<br />

обязательно передавать через список параметров. Написать<br />

прототип, определение шаблона функций и пример ее вызова<br />

для типов int и double.<br />

Вариант 17. Шаблоны функций. В матрице, состоящей из п<br />

строк и т столбцов, определить количество строк, не содержащих<br />

ни одного нулевого элемента. Исходные данные и полученные результаты<br />

обязательно передавать через список параметров. Написать<br />

прототип, определение шаблона функций и пример ее вызова<br />

для типов int и double.<br />

Вариант 18. Шаблоны функций. В матрице, состоящей из п<br />

строк и т столбцов, определить максимальное из отрицательных<br />

значений элементов матрицы. Исходные данные и полученные результаты<br />

обязательно передавать через список параметров. Написать<br />

прототип, определение шаблона функций и пример ее вызова<br />

для типов int и double.<br />

Вариант 19. Директивы препроцессора. Что напечатает данная<br />

программа<br />

^include <br />

^define AREA (г) 3.14*г*г<br />

±пЬ main ( void )<br />

{<br />

printf( "%f\n'\ AREA( 2.0-1.0 ) ) ;<br />

jretujm 0;<br />

}<br />

Вариант 20. Шаблоны функций. В одномерном массиве, состоящем<br />

из п элементов, вычислить среднее арифметическое значение<br />

элементов массива (не потеряйте дробную часть) и индекс наибольшего<br />

элемента. Исходные данные и полученные результаты<br />

обязательно передавать через список параметров. Написать прототип,<br />

определение шаблона функций и пример ее вызова для типа<br />

int.<br />

П.1.2. Программные проекты<br />

На практических занятиях студенты выполняют три про-<br />

392


граммных проекта:<br />

• решение простой задачи с использованием ПМ-ассемблера (выполняется<br />

по усмотрению преподавателя и требует наличия компактдиска,<br />

прилагаемого к данному учебному пособию);<br />

• структурное программирование средствами языков Си/С++;<br />

• средства модульного программирования в языке C++.<br />

П. 1.2.1. Программирование на ПМ-ассемблере. Варианты<br />

программных проектов<br />

Среда программирования. Интегрированная среда программирования<br />

ПМ-ассемблера описана в [1] и имеется на компактдиске.<br />

Формулировка решаемой задачи. Задача, предложенная для<br />

решения, должна предусматривать работу с массивами с использованием<br />

косвенной адресации. Варианты программных проектов<br />

приведены ниже. Для ввода и вывода использовать файлы MS<br />

DOS. Для обеспечения наглядности вывода использовать строковые<br />

данные.<br />

Содермсание отчета<br />

1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ - формулировка решаемой задачи,<br />

требования к программному проекту, язык программирования.<br />

2. ТЕКСТ ПРОГРАММЫ - назначение программы, листинг с<br />

исходным текстом программы в самодокументируемой форме.<br />

Многочисленные примеры оформления исходных текстов ПМпрограмм<br />

имеются в [1] и на компакт-диске.<br />

3. ОПИСАНИЕ ПРОГРАММЫ - назначение программы; метод<br />

решения задачи и основные расчетные соотношения; схема программы<br />

с необходимыми пояснениями, выполненная в соответствии<br />

с действующими стандартами.<br />

3. ПРОГРАММА И МЕТОДИКА ИСПЫТАНИЙ - разработка<br />

контрольного примера (примеров) с их обоснованием и анализом,<br />

результаты вычислений по отлаженной программе, выводы.<br />

Варианты 1-5, В качестве первых пяти вариантов можно использовать<br />

приведенные выше варианты 1-5 из подразд. П. 1.1.1.<br />

Вариант 6. Ввести и напечатать значения элементов массива<br />

целого типа с заданной размерностью. Вычислить и напечатать<br />

сумму элементов массива, расположенных до минимального элемента.<br />

393


Вариант 7. Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Вычислить и напечатать<br />

произведение положительных элементов массива. Если массив<br />

не содержит элементов с положительными значениями, то в качестве<br />

ответа напечатать "В массиве нет положительных элементов".<br />

Вариант 8. Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Вычислить и напечатать<br />

сумму положительных элементов массива, расположенных до<br />

максимального элемента. Если массив не содержит элементов с положительными<br />

значениями, то в качестве ответа напечатать "В массиве<br />

нет положительных элементов".<br />

Вариант 9. Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Вычислить и напечатать<br />

количество отрицательных элементов массива.<br />

Вариант 10, Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Преобразовать массив<br />

таким образом, чтобы вначале располагались все элементы, отличающиеся<br />

от максимального не более, чем на 20%. Модифицированный<br />

массив напечатать.<br />

Вариант 1L Ввести и напечатать значения элементов массива<br />

целого типа с заданной размерностью. Вычислить и напечатать<br />

сумму элементов массива, расположенных после последнего нулевого<br />

элемента. Если массив не содержит нулевых элементов, то в качестве<br />

ответа напечатать "В массиве нет нулевых элементов".<br />

Вариант 12. Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. В массиве все отрицательные<br />

элементы заменить их квадратами и определить их количество.<br />

Модифицированный массив и количество измененных элементов<br />

напечатать.<br />

Вариант 13. Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Вычислить и напечатать<br />

сумму модулей элементов массива, расположенных после максимального<br />

элемента.<br />

Вариант 14. Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Преобразовать массив<br />

таким образом, чтобы вначале располагались все отрицательные<br />

элементы. Модифицированный массив напечатать.<br />

394


Вариант 15, Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Упорядочить массив<br />

по возрастанию значений элементов. Отсортированный массив напечатать.<br />

Вариант 16, Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Ввести значения границ<br />

диапазона. Вычислить и напечатать количество элементов<br />

массива, лежащих в заданном диапазоне, и информацию о заданном<br />

диапазоне.<br />

Вариант. 17, Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Ввести значения границ<br />

диапазона. Сжать массив, удалив из него элементы, значения<br />

которых находятся в заданном диапазоне. Освободившиеся элементы<br />

заполнить нулями. Модифицированный массив и заданный диапазон<br />

значений напечатать.<br />

Вариант 18, Ввести и напечатать значения элементов массива<br />

вещественного типа с заданной размерностью. Преобразовать массив<br />

таким образом, чтобы в первой его половине располагались элементы,<br />

стоявшие в четных позициях, а во второй половине — элементы,<br />

стоявшие в нечетных позициях. Модифицированный массив<br />

напечатать.<br />

Вариант 19, Ввести и напечатать значения элементов массива<br />

целого типа с заданной размерностью. Преобразовать массив таким<br />

образом, чтобы нулевые элементы располагались в конце массива.<br />

Модифицированный массив напечатать.<br />

Вариант 20, Ввести и напечатать значения элементов массива<br />

целого типа с заданной размерностью. Преобразовать массив таким<br />

образом, чтобы нулевые элементы располагались в начале массива.<br />

Модифицированный массив напечатать.<br />

П.1.2.2. Структурное программирование средствами языков Си/С++.<br />

Варианты программных проектов<br />

Среда программирования. Любая интегрированная среда программирования<br />

языка С+-ь. На начальном этапе обучения можно рекомендовать<br />

использование простой интегрированной среды программирования<br />

Borland С+-ь 3.1 с переходом в будущем на более со-<br />

395


временную и широко распространенную среду программирования<br />

Microsoft Visual Studio C++ 6.0.<br />

Формулировка решаемой задачи, С использованием средств<br />

структурного программирования языков Си/С++ спроектировать три<br />

элементарных программы для решения.<br />

1. Задачи с линейным следованием операторов. Например, вычислить<br />

значение функции<br />

у = arctg( 1<br />

14 • 1п(«))<br />

с проверкой области допустимых значений ее аргументов.<br />

2. Задачи с ветвлением (использовать структурированные<br />

операторы if, switch). Например, вычислить значение функции<br />

[ а+Ь при х


у =<br />

fa+b<br />

} ab<br />

Уа-b<br />

при<br />

при<br />

при<br />

х


у =<br />

f<br />

а-х+4<br />

Ja(l-e-^)<br />

I 0<br />

при<br />

при<br />

в<br />

x>4,<br />

0


у =<br />

(a+b+c<br />

J a+b<br />

[ -<br />

при<br />

при<br />

в<br />

\b\=1,<br />

х


Вариант 13. Вычислить значения функций и сумму ряда<br />

У =<br />

с b а<br />

(\g{a/{a+b) при (а'Ь+с)>5,<br />

< sin(a)<br />

I 0<br />

при<br />

в<br />

00 и<br />

хZ ,<br />

2 2<br />

X >Z ,<br />

случаях<br />

у = иК2-а+\Г<br />

а=0<br />

Вариант 15. Вычислить значения функций и сумму ряда<br />

у =<br />

1"<br />

при<br />

\'<br />

при<br />

в<br />

л:>0.<br />

-l


п<br />

Вариант<br />

17. Вычислить значения функций и сумму ряда<br />

^.(^-)2-a.(,_5)3.sm(^.10-5<br />

{sin(x) при х>0,<br />

1 при х=0,<br />

п<br />

ij—x в остальных случаях<br />

а=\<br />

Вариант 18. Вычислить значения функций и сумму ряда<br />

е~^'^+]<br />

х^ .^-4<br />

\%{х 1{а-\-Ь)) при (сг+^))>0,<br />

^ 1 \а +6|-lg(jc) при (а+Ь)3.<br />

1


o^l ^'<br />

Указания no выполнению программных проектов<br />

• При вычислении значения функции следует проверить область<br />

допустимых значений аргументов функции (например, при вычислении<br />

х^, где а - вещественное, должно быть д:>0; подкоренное<br />

выражение, аргументы логарифмических функций должны быть<br />

также положительными; делитель должен быть отличен от нуля; аргумент<br />

тангенса не должен быть кратен ж/2 и т.п.).<br />

• Для получения возможности использования математических<br />

функций необходимо подключить соответствующий заголовочный<br />

файл:<br />

^include<br />

<br />

При этом следует иметь ввиду, что большинство математических<br />

функций используют аргументы и имеют возвращаемое значение с<br />

типом double. Поэтому аргументы функций, вычисляемых в программных<br />

проектах 1 и 2 также должны иметь тип double. Исчерпывающий<br />

перечень и описание стандартных математических и других<br />

стандартных функций приведен в [5].<br />

П. 1.2,3. Средства модульного программирования в языке C++.<br />

Варианты программных проектов<br />

Среда программирования. Любая интегрированная среда программирования<br />

языка C++. Повторяем, что начальном этапе обучения<br />

можно рекомендовать использование простой интегрированной<br />

среды программирования Borland C++ 3.1 с переходом в будущем на<br />

более современную и широко распространенную среду программирования<br />

Microsoft Visual Studio C++ 6.0 или 7.0 (.NET).<br />

Задание (формулировка решаемой задачи). Задача, предложенная<br />

для решения, может, в частности, предусматривать работу с<br />

массивами. Например, с использованием средств структурного и<br />

модульного программирования языка C++ спроектировать программу<br />

для обработки двумерного целочисленного массива. Характеристикой<br />

строки такого массива является сумма элементов строки с<br />

положительными четными значениями. Переставляя строки заданного<br />

массива, расположить их в соответствии с ростом характеристик.<br />

Варианты программных проектов такого рода приводятся ниже.<br />

Отличительной особенностью данного программного проекта<br />

402


является использование модульного программирования, в рамках<br />

которого студент осваивает методологию нисходящего иерархического<br />

программирования, в соответствии с которой обоснованно<br />

проектирует файловую и функциональную структуру программного<br />

продукта. Другой важной особенностью программного проекта является<br />

изучение и практическое освоение методики отладки программных<br />

проектов.<br />

Рекомендации по созданию программного проекта приведены<br />

в приложении П.5.<br />

Содержание отчета.<br />

1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ - формулировка решаемой задачи,<br />

требования к программе (в том числе та часть спецификации,<br />

которая относится к обработке ошибок и предупреждений), язык<br />

программирования.<br />

2. ТЕКСТ ПРОГРАММЫ - для программы в заголовкекомментарии<br />

указать ее назначение, привести листинг с исходным<br />

текстом в самодокументируемом виде. Пример оформления исходного<br />

текста программы приведен в приложении П.5.<br />

3. ОПИСАНИЕ ПРОГРАММЫ - описание файловой и функциональной<br />

структур программного проекта (вторая часть<br />

спецификации), краткое описание работы программы и схемы 2-3<br />

функций, выполненные в соответствии с действующими стандартами.<br />

4. ПРОГРАММА И МЕТОДИКА ИСПЫТАНИЙ - описание<br />

методики отладки, требования к контрольным примерам, разработка<br />

контрольных примеров с их обоснованием и анализом, результаты<br />

вычислений по отлаженной программе, выводы.<br />

Указания по выполнению программных проектов,<br />

1. Предусмотреть запуск программного проекта с<br />

использованием командной строки.<br />

2. Использовать файловый ввод-вывод.<br />

3. Массив размещать в динамической памяти (особенности<br />

размещения матрицы в динамической памяти рассмотрены выше в<br />

разд. 8).<br />

Вариант 1. Найти максимальное число, встречающееся в заданном<br />

векторе более одного раза.<br />

2. Определить норму заданной матрицы, т.е. значение<br />

Вариант<br />

Вариант<br />

тах(Х|Ф][У]|)<br />

у<br />

3. По заданной квадратной матрице размером N-N<br />

403


построить вектор длиной (2Л^-1), элементы которого - максимумы<br />

элементов диагоналей, параллельных главной, включая главную<br />

диагональ.<br />

Вариант 4, Характеристикой строки матрицы назовем сумму<br />

ее положительных элементов, имеющих четные значения индексов.<br />

Переставляя строки заданной матрицы, расположить их в соответствии<br />

с ростом характеристик.<br />

Вариант 5. Для заданной квадратной матрицы найти минимум<br />

среди сумм модулей элементов диагоналей, параллельных побочной<br />

диагонали.<br />

Вариант 6. Говорят, что матрица имеет седловой элемент<br />

Ф][уЪ если элемент a[i][j] является минимальным в /-ой строке и<br />

максимальным в у-ом столбце. Найти номера строки и столбца какого-либо<br />

седлового элемента и его значение.<br />

Вариант 7. Найти значение наибольшего элемента матрицы<br />

среди всех элементов тех строк матрицы, которые упорядочены либо<br />

по возрастанию, либо по убыванию значений элементов.<br />

Вариант 8, Характеристикой столбца матрицы назовем сумму<br />

его отрицательных элементов, имеющих нечетные значения индексов.<br />

Переставляя столбцы заданной матрицы, расположить их в соответствии<br />

с убыванием характеристик.<br />

Вариант 9, Элемент матрицы называется локальным минимумом,<br />

если его значение строго меньше значений всех имеющихся<br />

соседей. Подсчитать количество локальных минимумов заданной<br />

матрицы и напечатать информацию о каждом из них.<br />

Вариант 10, Составить программу нахождения элемента<br />

вектора, имеющего максимальное значение. Элементы, стоящие<br />

после максимального, заменить нулями и переставить в начало<br />

вектора. Исходный и полученный векторы напечатать.<br />

Вариант 11, Составить программу нахождения максимального<br />

значения элемента вектора среди отрицательных и минимального<br />

значения — среди положительных элементов.<br />

Вариант 12, Написать программу, которая упорядочивала бы<br />

элементы вектора по знаку, сначала положительные, а затем — отри-<br />

404


дательные, в таком же порядке, как в исходном векторе.<br />

Вариант 13. Составить программу, позволяющую найти максимальный<br />

элемент вектора и, если он не равен нулю, то разделить<br />

на него все элементы вектора. Если же максимальный элемент вектора<br />

равен нулю, то вектор не изменять.<br />

Вариант 14. Составить программу поиска элементов, встречающихся<br />

в векторе более одного раза. Из найденных элементов<br />

сформировать новый вектор.<br />

Вариант 15. Составить программу упорядочения по возрастанию<br />

элементов каждой строки матрицы. Сортировка строк должна<br />

выполняться на месте, что означает, что вспомогательный вектор не<br />

должен использоваться.<br />

Вариант 16. Составить программу вычисления количества<br />

положительных элементов в левом нижнем треугольнике квадратной<br />

матрицы. Треугольник включает диагональ матрицы.<br />

Вариант 17. Составить программу обмена местами максимального<br />

элемента главной диагонали квадратной матрицы и минимального<br />

элемента побочной диагонали.<br />

Вариант 18. Составить программу печати значений элементов<br />

той строки матрицы, сумма элементов которой минимальна.<br />

Вариант 19. Составить программу нахождения суммы значений<br />

элементов тех строк матрицы, у которых на главной диагонали<br />

расположены элементы, имеющие отрицательные значения.<br />

Вариант 20. Составить программу перестановки строк матрицы<br />

по убыванию значения их первого элемента.<br />

П.1.3. Экзаменационное тестирование<br />

Наряду с традиционной формой, экзаменационное тестирование<br />

можно проводить в форме тестовых вопросов.<br />

На экзамене каждому студенту может быть предложена комплексная<br />

проверочная работа, содержащая пять вопросов по некоторым<br />

из перечисленных основных разделов курса:<br />

• программирование на ПМ-ассемблере;<br />

• ввод;<br />

а вывод;<br />

405


• простейшие ветвления;<br />

а циклы;<br />

• структуры;<br />

а функции;<br />

• области действия определений;<br />

• массивы и указатели;<br />

• работа с динамической памятью и операции с линейным<br />

списком;<br />

• препроцессор, перечисления, функции с умалчиваемыми<br />

значениями аргументов, перегрузка функций, шаблоны функций,<br />

перегрузка операций.<br />

Комплексная проверочная работа рассчитана на 1 ч. 15 мин.<br />

Ответ на каждый тестовый вопрос, в зависимости от правильности и<br />

полноты, оценивается О, 0,25, 0,5, 0,75 или 1 баллом. Таким образом,<br />

максимальная сумма баллов может достигнуть 5.<br />

В соответствии с набранными баллами выставляются следующие<br />

экзаменационные оценки:<br />

• "отлично" (4,25-5 баллов);<br />

• "хорошо" (3,5-4 балла);<br />

• "удовлетворительно" (2,5-3,25 балла);<br />

• "неудовлетворительно" (менее 2,5 баллов).<br />

Примеры формулировок тестовых экзаменационных вопросов<br />

содержатся в подразд. П.1Л.<br />

КОМПЛЕКСНАЯ ЭКЗАМЕНАЦИОННАЯ РАБОТА<br />

Пример варианта<br />

!• Структуры. В файле операционной системы "Task4.in'' хранится<br />

в текстовой форме ведомость сдачи экзаменов студентами некоторой<br />

группы. Каждая строка этого файла содержит сведения об<br />

одном студенте, представленные в следующем формате:<br />

позиции 1...2 - порядковый номер студента в группе;<br />

позиция 3 - пробельная литера;<br />

позиции 4...22 - фамилия студента длиной не более 18 символов<br />

в произвольном месте поля;<br />

позиция 23 - пробельная литера;<br />

позиция 24 - четыре оценки по четырем предметам, разделенные<br />

не менее чем одной пробельной литерой.<br />

Количество студентов в группе равно 16. Пример строк указанного<br />

файла:<br />

01 Андреев 5 4 5 5<br />

02 Быков 5 5 5 5<br />

16 Яковлев 4 4 5 4<br />

406


1.1. Написать объявление массива структур для хранения указанной<br />

ведомости.<br />

1.2. Написать фрагмент программы, который заполнит экзаменационную<br />

ведомость данными, вводимыми из файла операционной<br />

системы "Task4.in". Ввод данных должен осуществляться в текстовом<br />

режиме.<br />

1.3. Написать фрагмент программы, который вычисляет среднюю<br />

экзаменационную оценку по всем предметам и студентам (т.е.<br />

среднюю оценку из 64 оценок), а затем выводит значение этого показателя<br />

в файл операционной системы ''Task4.ouf\<br />

Примечание.<br />

Закрыть открытые файлы, как только они станут не нужны.<br />

Предусмотреть контроль корректности значений, возвращаемых<br />

функциями библиотеки Си ^^fopen^\ ^^fscanf\ Указать,<br />

какие включаемые файлы требует представленный фрагмент.<br />

2. Функции. Написать прототип, определение функции и пример<br />

вызова функции, которая подсчитывает тах:=наиб{а,6,с}. Исходные<br />

данные имеют тип с плавающей точкой.<br />

Все исходные данные должны передаваться через список<br />

параметров, а найденный максимум следует получить как значение,<br />

возвращаемое функцией. Выполнение этого требования<br />

является обязательным.<br />

3. Массивы и указатели. Что напечатает следующая программа<br />

^include<br />

<br />

xnt Array[ ] = { 0 , 4 , 5 , 2 , 3 } ;<br />

±nt main ( void )<br />

{<br />

±nt<br />

±nt<br />

Index;<br />

^Pointer;<br />

for( Index = 0; Index


4. Операции с линейным списком. Работа с динамической<br />

памятью. Определен следующий указатель на начало линейного<br />

списка:<br />

stJTuct Node // NODE: узел линейного списка<br />

{<br />

Node *pLink; // Pointer LINK:<br />

// указатель на очередной узел<br />

double Info; // INFOrmation: информация<br />

} * start;<br />

Написать прототип, определение и пример вызова функции,<br />

которая должна определить, сколько в линейном списке имеется<br />

элементов с отрицательными значениями. В частном случае, перед<br />

вызовом этой функции линейный список может быть пуст.<br />

Все исходные данные (указатель на начало линейного списка)<br />

и все результаты работы функции (количество найденных<br />

элементов) должны передаваться через список параметров — это<br />

обязательное требование.<br />

5. Шаблоны функций. В одномерном массиве, состоящем из п<br />

элементов, вычислить сумму отрицательных элементов. Написать<br />

прототип, определение шаблона функций и пример ее вызова для<br />

типов int, float и double.<br />

Приложение П.2. Создание программного проекта<br />

Ниже рассматривается создание программного проекта в двух<br />

средах программирования:<br />

• в интегрированной среде проектирования программ (IDE - Integrated<br />

Development Environment) MS Visual Studio C++ 6.0;<br />

• в IDE Borland C++ 3.1.<br />

П.2.1. IDE MS Visual Studio C++ 6.0.<br />

Создание программного проекта<br />

Интегрированная среда проектирования программ (IDE) представляет<br />

собой комплект программных инструментов - Tools (рис.<br />

106). Этот комплект инструментов - хороший, инструментов — много,<br />

но среда не русифицирована (в ней используется английский<br />

язык).<br />

408


Проекты (Projects). Проекты IDE характеризуются следующими<br />

особенностями.<br />

1. Единицей работы IDE является проект. Проект — это комплект<br />

файлов.<br />

2. Виды файлов в составе проекта:<br />

• исходные файлы, написанные программистом {*.срр — С Plus Plas<br />

— тексты на языке 0++ и *./; — Header — заголовочные файлы), IDE<br />

содержит инструменты, которые позволяют автоматизировать составление<br />

исходных файлов;<br />

• служебные файлы, которые автоматически создаются IDE, но по<br />

инструкциям программиста.<br />

3. Каталог проекта. Служебные файлы обязательно располагаются<br />

в этом каталоге. Исходные файлы хотя и могут располагаться<br />

где угодно, но, чтобы не запутаться, их тоже следует поместить в<br />

каталог проекта.<br />

4. Проекты IDE и проекты программного обеспечения. Простые<br />

программы представляют собой просто один проект IDE.<br />

Сложное программное обеспечение реализуется в виде некоторого<br />

множества проектов IDE.<br />

Компилятор<br />

СИ/С++<br />

Компоновщик<br />

(Linker)<br />

Редактор<br />

текстов (Text<br />

Editor)<br />

Символический<br />

отладчик<br />

(Debugger)<br />

Рис. 106. Интегрированная среда проектирования программ<br />

MS Visual Studio С-ь+ 6.0<br />

Tools<br />

Создание нового проекта для консольного прило:исения. Для<br />

того чтобы создать новое приложение (программу), необходимо создать<br />

новый проект. Для этого в IDE выполните команду New... из<br />

меню File, в результате чего на экране появится диалоговое окно<br />

New.<br />

В этом окне необходимо выполнить следующее:<br />

• Выбрать тип создаваемого приложения. В данном случае<br />

следует выбрать опцию Win32 Console Application, поскольку мы<br />

создаем консольное приложение, которое является Windowsаналогом<br />

старого доброго знакомого - программы для MS DOS.<br />

• Выбрать место расположения нового проекта. Информация<br />

о расположении новой рабочей области проекта<br />

(диск:\путь\подкаталог) вводится в поле Location (местоположение).<br />

409


Это можно сделать, набрав путь вручную, или воспользовавшись<br />

расположенной справа кнопкой Browse... (просмотр). Разумеется,<br />

что соответствующий подкаталог должен быть предварительно создан.<br />

• Указать утилите Project имя файла проекта. Одновременно<br />

с вводом имени проекта в поле Project Name (имя проекта) это же<br />

имя автоматически добавляется в качестве подкаталога в поле Location.<br />

После выполнения указанных действий для создания проекта<br />

следует нажать кнопку [ОК], в результате чего на экране появится<br />

диалоговое окно мастера создания консольного приложения. В этом<br />

окне выбираем переключатель An empty project (пустой проект).<br />

При этом создаются только служебные файлы проекта. Для того<br />

чтобы наполнить созданный проект, необходимо добавить в него<br />

файл(ы), содержащий(ие) текст программы.<br />

Это можно сделать двумя способами.<br />

1. Добавить в проект уже существующий файл(ы), созданный(ые)<br />

ранее в текстовом редакторе и имеющий(ие) расширение<br />

*.с//. Повторно обращаем внимание на то, что следует предварительно<br />

поместить существующий(ие) файл(ы) в каталог проекта<br />

(лучше все иметь в одном месте).<br />

2. Создать новый файл и вставить его в проект.<br />

Добавление в проект существующего файла. Для этого необходимо<br />

выбрать в меню Project пункты Add to Project (добавить в<br />

проект) и Files.... В результате этих действий на экран будет выведено<br />

диалоговое окно Insert Files into Project (добавление файлов в<br />

проект). Здесь следует выбрать те файлы, имеющие расширение<br />

.срр, которые хотите включить в проект. Это можно сделать, либо<br />

дважды щелкнув кнопкой мыши на имени файла, либо выделив<br />

нужные файлы и нажав кнопку [OKJ.<br />

Создание нового файла и включение его в проект. Для создания<br />

нового файла необходимо из меню File выполнить команду<br />

New... и в появившемся диалоговом окне New выбрать вкладку<br />

Files, где представлены все типы файлов, которые можно создавать.<br />

Флажок Add to Project (добавить в проект) должен быть установлен,<br />

чтобы создаваемый файл автоматически был добавлен в проект.<br />

В списке Files выберите тип создаваемого файла — C/C++ Header<br />

File или C++ Source File, а в поле File name: - имя файла. Осталось<br />

нажать кнопку [ОК]. В результате Visual C++ создаст файл и откроет<br />

пустое поле редактирования текста.<br />

После набора и сохранения всех текстов можно переходить к<br />

следующему этапу - отладке программного проекта.<br />

410


Открытие для работы существующего проекта. Для существующего<br />

проекта необходимо из меню File выполнить команду<br />

Open Workspace ... и в появившемся диалоговом окне Open Workspace<br />

войти в каталог проекта и "кликнуть" по файлу с расширением<br />

.dsw. В результате проект загружается в IDE для последующей работы.<br />

П.2.2. IDE Borland C++ 3.1.<br />

Создание программного проекта<br />

Создание нового проекта П08'Прило:>§сения, Для того чтобы<br />

создать новое приложение (программу), необходимо создать новый<br />

проект. Для нового проекта следует предварительно создать каталог.<br />

С помощью встроенного в IDE текстового редактора в каталоге проекта<br />

следует создать файлы проекта с исходным текстом с расширениями<br />

*.срр, *.h или *.hpp.<br />

После создания исходных файлов надо создать файл проекта.<br />

Для этого надо выбрать в меню Project команду Open Project..., в<br />

появившемся окне Open Project File ввести имя файла проекта и<br />

нажать кнопку [ОК].<br />

Для включения в проект файлов с расширениями *.с/р (в частном<br />

случае в проекте такой файл может быть единственным) следует<br />

на рабочем столе активизировать окно Project, выбрать в меню<br />

Project команду Add Item..., в появившемся окне Add to Project<br />

List "кликнуть" по каждому из файлов с расширением *.ср/7 и нажать<br />

кнопку [Done]. В результате этого будет создан требуемый<br />

программный проект.<br />

После создания программного проекта необходимо проверить<br />

и, при необходимости, скорректировать информацию о местоположении<br />

каталогов стандартных включаемых файлов. С этой целью<br />

достаточно в меню Options выполнить команду Directories, в появившемся<br />

окне Directories указать расположение каталогов стандартных<br />

включаемых файлов и нажать кнопку [ОК]. После этого<br />

программный проект готов к работе.<br />

Открытие для работы существующего проекта DOSприлоэн:ения.<br />

Для существующего проекта необходимо из меню<br />

Project выполнить команду Open Project..., в появившемся окне<br />

Open Project File в каталоге проекта выбрать имя файла проекта и<br />

нажать кнопку [ОК]. В результате проект загружается в IDE для последующей<br />

работы.<br />

411


Приложение П.З. Рекомендации по структуре<br />

однофайловой программы с одной функцией<br />

и пример оформления исходного текста<br />

приведенный ниже пример оформления исходного текста простейшего<br />

однофайлового программного проекта с единственной<br />

функцией попутно преследует и другую цель — он демонстрирует<br />

какой должна быть структура программы:<br />

Файл<br />

TASK01.CPP<br />

Проект<br />

Назначение<br />

Состав<br />

ЭВМ<br />

Среда<br />

проекта<br />

программирования:<br />

однофайловый с единственной<br />

функцией (главной)<br />

пример простой программы г<br />

вычисляющей с := а + b<br />

файл проекта TASK01.PRJ/<br />

файл TASK01.СРР (главная функция);<br />

файл TASK01.DAT (файл данных);<br />

файл TASK01.OUT (файл результатов)<br />

IBM 80386<br />

ВС31 (C++)<br />

Операционная<br />

система<br />

DR DOS 6.0<br />

Дата<br />

Дата<br />

создания<br />

корректировки<br />

08.11.2000<br />

V<br />

Иванов И. И,, каф. АВТ, ФТК, гр. 1081/4<br />

Санкт-Петербургским государственный политехнический<br />

университет<br />

// Для работы с функциями ввода-вывода: STanDard Input Output<br />

^include <br />

±nt main( void. )<br />

{<br />

412<br />

// Возвращает 0 при успехе<br />

±nt a, Ь, // Аргументы функции<br />

с, // Значение функции<br />

ret code; // Возвращаемое значение для fscant<br />

FILE *£_±Пг // Указатель на структуру со<br />

// сведениями о файле для чтения<br />

*f_out; // Указатель на структуру со<br />

// сведениями о файле для записи<br />

// Открываем файл для чтения<br />

±£( ( f_ln = fopen( "task01.dat", "г" ) ) == NULL )<br />

{<br />

printf( "\n Ошибка 10. Файл taskOl.dat для чтения не"<br />

" открыт \п" ) ;


}<br />

retvLm 10;<br />

// Читаем значения аргументов функции<br />

retcode = fscanf( f_±n, " %i %i", &a, &b );<br />

±£( retcode != 2 )<br />

{<br />

printf( "\n Ошибка 20. Произошла ошибка чтения<br />

" файла taskOl.dat \п" );<br />

из"<br />

jtretujm 20;<br />

}<br />

// Закрываем файл для чтения<br />

±f( fclose( f_in ) == EOF )<br />

{<br />

printf( "\n Ошибка 30. Файл task01.dat<br />

"закрыт \п" );<br />

не "<br />

return 30;<br />

}<br />

// Открываем файл для записи<br />

з.£( ( f_out = fopen( "task01.out"r "w" ) ) == NULL )<br />

{<br />

printf( "\n Ошибка 40. Файл taskOl.out<br />

"не открыт \п" );<br />

для записи "<br />

return 40;<br />

}<br />

// Печатаем заголовок и аргументы функции<br />

fprintf( f_out,<br />

"\п Иванов И. И. г каф. АВТ, ФТК, гр. 1081/4<br />

"\п С.-Петербургский государственный политехнический унверситет "<br />

"\п (семестр 1, программный проект 1) \п"<br />

"\п с : = а + b \п"<br />

"\п Аргументы функции: a=%i b=%i \л", а, b );<br />

// Закрываем файл для записи<br />

±f( fclose( f_out ) -= EOF )<br />

{<br />

printf( "\n Ошибка 50. Файл taskOl.out не закрыт \n" );<br />

return 50;<br />

}<br />

// Вычисляем значение функции - в этом месте обычно делается<br />

// довольно много работы: проверяется область допустимых<br />

// значений прочитанных данных (аргументов функции) и<br />

// выполняется решение задачи<br />

с = а + Ь;<br />

// Открываем файл для дозаписи<br />

±f( ( f_out = fopen( "taskOl. out ", "a" ) ) == NULL )<br />

{<br />

printf( "\n Ошибка 60. Файл taskOl.out<br />

"не открыт \п" );<br />

для дозаписи "<br />

return 60;<br />

}<br />

// Печатаем значение функции<br />

fprintf ( f_outr<br />

413


"\n Значение функции: с=%1'\ с );<br />

// Закрываем файл для записи<br />

±f( fclose( f_out ) == EOF )<br />

{<br />

printf( "\n Ошибка 10. Файл taskOl.out не закрыт \n" );<br />

r&tuxm 10;<br />

}<br />

return 0;<br />

Рекомендуем использовать этот пример как "образец для подражания".<br />

Обращаем внимание на следующие особенности.<br />

1. В программе используется файловый ввод-вывод. Это наиболее<br />

рациональный способ ввода-вывода.<br />

2. Программа имеет следующую структуру:<br />

• открытие файла данных для чтения, чтение из него данных и закрытие<br />

файла (обратите внимание, что файл данных закрывается<br />

сразу же после завершения из него чтения, а не в конце работы<br />

программы - так выгоднее);<br />

• открытие файла результатов для записи, вывод в него заголовка и<br />

прочитанных данных и закрытие файла (обратите внимание, что<br />

файл результатов закрывается сразу же после завершения вывода<br />

в него значений прочитанных данных, а не в конце работы<br />

программы - так также выгоднее);<br />

• проверка области допустимых значений прочитанных данных<br />

(это обязательно нужно делать) и содержательное решение задачи,<br />

которое в большинстве случаев является довольно сложным —<br />

поэтому-то на это время держать файл результатов открытым<br />

невыгодно;<br />

• открытие файла результатов для дозаписи, вывод в него результатов<br />

решения задачи и закрытие файла.<br />

3. Для обработки ошибок в программе используются значения,<br />

возвращаемые функциями библиотеки Си. Это нужно делать всегда.<br />

4. Чтобы сделать программу наглядной, легко читаемой в программе<br />

используется рациональная ступенчатая запись и комментарии.<br />

Никогда не пренебрегайте подобными элементами оформления<br />

и внимательно изучите пример с этой точки зрения.<br />

Приложение П.4. Методика отладки программы<br />

Известное высказывание о том, что после обнаружения последней<br />

ошибки в программе остается еще хотя бы одна, стало аксиомой.<br />

Поэтому отладке программы уделялось и уделяется боль-<br />

414


шое внимание. Как же вести отладку программы<br />

Все обнаруживаемые в программе ошибки можно разделить на<br />

три большие категории.<br />

1. Синтаксические ошибки, которые автоматически выявляются<br />

на этапе компиляции. Уяснить смысл синтаксических ошибок и<br />

устранить их достаточно легко, так как здесь в качестве достаточно<br />

хорошего помощника выступает компилятор. В зависимости от языка<br />

программирования, компилятор лучше или хуже выявляет такие<br />

ошибки. В ряде случаев синтаксическая ошибка в программе влечет<br />

за собой неадекватную реакцию компилятора. Например, отсутствие<br />

скобки часто приводит к тому, что компилятор обнаруживает ошибку<br />

через десятки строк кода. В последнем случае можно рекомендовать<br />

одновременный набор открывающей и закрывающей скобок<br />

(например, { }) с последующим вводом текста между ними.<br />

2. Логические (часто их также называют алгоритмическими)<br />

ошибки. Их бывает наиболее трудно обнаружить и исправить. Часть<br />

из них выявляется на этапе отладки, часть на этапе сопровождения,<br />

а некоторые приводят к тяжелым последствиям.<br />

3. Информационные ошибки. В частности, к Появлению информационных<br />

ошибок может привести отсутствие обработки ошибок<br />

ввода-вывода, попытки деления на ноль, переполнение разрядной<br />

сетки компьютера и т.п. Для исключения и/или обработки информационных<br />

ошибок в ряде случаев приходится значительную<br />

часть исходного кода программы отводить для всевозможных проверок.<br />

П.4.1. Компиляция и компоновка программного проекта.<br />

Устранение синтаксических ошибок<br />

На этапе отладки необходимо устранить все синтаксические и<br />

большую часть логических ошибок в программном проекте, допущенных<br />

на предыдущих этапах создания приложения. Для этого необходимо<br />

скомпилировать каждый созданный файл. Это можно сделать<br />

несколькими способами.<br />

• Скомпилировать каждый файл с расширением .срр. Для этой<br />

цели в IDE MS Visual Studio C++ 6.0 можно использовать команду<br />

Build I Compile имя_файла или комбинацию клавиш , а в<br />

IDE Borland C++ 3.1 - команду Compile | Compile имя_файла или<br />

комбинацию клавиш . Компиляцию отдельного файла<br />

удобно использовать в больших проектах, чтобы сосредоточиться на<br />

конкретном файле. При этом следует иметь в виду, что ссылки между<br />

файлами не проверяются.<br />

• Скомпилировать и скомпоновать все файлы проекта ("собрать"<br />

или "построить" исполняемый файл), воспользовавшись для<br />

415


этого в IDE MS Visual Studio C++ 6.0 командами Build | Build<br />

имя_файла эквивалентно или Build | Rebuild All. Единственным<br />

отличием этих команд является то, что команда Rebuild All не<br />

проверяет даты создания файлов проекта и компилирует все файлы,<br />

а не только те, которые были модифицированы после компиляции.<br />

Аналогичным образом, в IDE Borland C++ 3.1 можно использовать<br />

команды Compile | Маке имя_файла эквивалентно или Compile<br />

I Build All. В результате создается исполняемый файл с расширением<br />

.ехе,<br />

• Можно сразу запустить приложение, выполнив в IDE MS<br />

Visual Studio C++ 6.0 команду Build | Execute имя_файла или по<br />

комбинации клавиш , а в IDE Borland C++ 3.1 -команду<br />

Run I Run имя_файла или по комбинации клавиш . Если<br />

в программный проект были внесены какие либо изменения, то в<br />

IDE MS Visual Studio C++ 6.0 на экране будет высвечено диалоговое<br />

окно с запросом на построение исполняемого файла. Для построения<br />

указанного файла следует нажать кнопку [Да]. В IDE Borland<br />

C++ 3.1 подобный запрос не выполняется.<br />

Если программный проект содержит синтаксические ошибки,<br />

то в IDE MS Visual Studio C++ 6.0 при выполнении любой из представленных<br />

команд информация об ошибках автоматически отображается<br />

во вкладке Build окна Output, по умолчанию расположенного<br />

в нижней части окна IDE. Если окно Output было удалено с экрана,<br />

то его можно вывести снова на экран с помощью команды View |<br />

Output или по комбинации клавиш . Каждое сообщение об<br />

ошибке или предупреждении начинается с имени файла, где они обнаружены,<br />

за которым следует номер строки, где это произошло, а<br />

далее идут двоеточие и слово "error" (ошибка) или "warning" (предупреждение)<br />

и соответствующий номер. В конце приводится краткое<br />

описание ошибки или предупреждения. Если дважды щелкнуть<br />

левой кнопкой мыши на строке с сообщением или предупреждением,<br />

то ошибочная строка будет отмечена стрелкой в левой части в<br />

соответствующем окне редактирования. Лучше всего добиться, чтобы<br />

в окончательном варианте не было ни того, ни другого, хотя с<br />

предупреждениями исполняемый файл создается и может быть запущен.<br />

Аналогичным образом, в IDE Borland C++ 3.1 при наличии<br />

синтаксических ошибок при выполнении любой из представленных<br />

выше команд информация об ошибках автоматически отображается<br />

в появившемся окне Message. Каждое сообщение об ошибке или<br />

предупреждении начинается со слова "error" (ошибка) или<br />

"warning" (предупреждение), за которым следуют имя файла и номер<br />

ошибочной строки, а далее идут двоеточие и приводится краткое<br />

описание ошибки или предупреждения. Если дважды щелкнуть<br />

416


левой кнопкой мыши на строке с сообш^ением или предупреждением,<br />

то в соответствуюш[ем окне редактирования в ошибочную строку<br />

будет помещен курсор, а текст сообщения будет повторен в нижней<br />

части окна редактирования.<br />

После устранения синтаксичесих ошибок следует запустить<br />

программу, выполнив команду Build | Execute имя__файла либо по<br />

комбинации клавиш (IDE MS Visual Studio C++ 6.0) или<br />

команду Run | Run имя_файла либо по комбинации клавиш<br />

(IDE Borland C++ 3.1). При этом также можно получить<br />

сообщение об ошибке (или ошибках). Это тот самый случай, когда<br />

программный проект не содержит синтаксических ошибок, а приложение<br />

не работает. Вызвано это так называемыми логическими<br />

(алгоритмическими) ошибками, для обнаружения которых можно<br />

использовать разные методы (например, закомментировать фрагменты<br />

программы).<br />

Однако лучше всего воспользоваться имеющимся в IDE<br />

встроенным отладчиком.<br />

П.4.2. Отладка программного проекта.<br />

Устранение логических (алгоритмических) ошибок<br />

Встроенный отладчик предоставляет следующие возможности.<br />

• Пошаговое выполнение программы.<br />

а Просмотр значений переменных в любом месте программы.<br />

Для пошагового выполнения программы отладчик предоставляет<br />

несколько возможностей, основные из которых мы и рассмотрим<br />

вначале для IDE MS Visual Studio C++ 6.0, a затем и для IDE<br />

Borland C++3.1.<br />

Встроенный отладчик IDE MS Visual Studio C++ 6,0. Для запуска<br />

исполняемого файла в режиме отладки можно выполнить команду<br />

Build I Start Debug | Go (выполнить) или эквивалентно нажать<br />

клавишу . Однако если просто выполнить эту команду, не<br />

предпринимая никаких предварительных действий, работа программы<br />

не будет отличаться от запуска в обычном режиме, разве что при<br />

завершении во вкладке Debug в нижней части окна Output интегрированной<br />

среды разработки появится информация о параметрах завершения<br />

работы программы.<br />

Чтобы перейти в режим пошагового выполнения, предварительно<br />

перед выполнением команда Go необходимо установить так<br />

называемые точки останова {breakpoints), которые можно рассматривать<br />

как стоп-сигналы для отладчика. Обычно они устанавливаются<br />

в местах, которые вызывают сомнение в правильности выполнения.<br />

При этом предполагается, что все операторы, предшествую-<br />

417


ш^ие первой точке останова, выполняются правильно. Самый простой<br />

способ установки точки останова заключается в следующем.<br />

Курсор устанавливается на строку, на которой нужно остановить<br />

работу программы, и нажимается клавиша . Повторное нажатие<br />

клавиши удаляет точку останова. Строка останова в окне редактирования<br />

отмечена темно-красным кружком в крайней левой<br />

позиции. Если, после задания точки останова, программу запустить<br />

по команде Build | Start Debug | Go, либо нажав клавишу , то<br />

все операторы программы, предшествующие точке останова, будут<br />

выполняться в обычном режиме и только перед строкой останова<br />

выполнение программы приостановится.<br />

При этом внешний вид интегрированной среды разработки<br />

существенно изменится. Во-первых, изменипся состав основного<br />

меню. Во-вторых, строка, которая будет выполняться следующей,<br />

будет отмечена желтой (по умолчанию) стрелкой. И, наконец, появится<br />

два новых окна - Variables (переменные) и Watch (наблюдение),<br />

которые позволяют просматривать и менять значения переменных.<br />

Если одно из окон или оба окна на экране отсутствуют, то<br />

их можно поместить на экран, используя комбинации клавиш<br />

для переменных и/или для наблюдения.<br />

Для пошагового выполнения в отладчике имеются следующие<br />

команды.<br />

• Debug I Step Over (шаг через) или эквивалентно -<br />

выполняет текущий оператор или функцию и переходит к следующей<br />

строке.<br />

• Debug I Step Into (шаг внутрь) или эквивалентно -<br />

выполняет текущий оператор языка C++ или переходит к первому<br />

оператору функции.<br />

а Debug | Step Out (шаг вне) или эквивалентно -<br />

завершает выполнение текущей функции и переходит к строке, непосредственно<br />

следующей за ее вызовом.<br />

• Debug I Run to Cursor (выполнить до курсора) или эквивалентно<br />

- выполняет программу до строки, где в текущий<br />

момент находится курсор.<br />

В окне Variables (переменные) автоматически отображаются<br />

только локальные переменные текущего блока. После каждого шага<br />

выполнения программы значения этих переменных обновляются. В<br />

строке Context указывается, в какой функции (блоке) в данный момент<br />

находимся.<br />

Переменные, которые нужно контролировать или изменять по<br />

желанию программиста, можно задать в окне Watch (наблюдение).<br />

Для этого в свободной строке столбца Name для контроля значения<br />

переменной достаточно набрать идентификатор переменной и на-<br />

418


жать клавишу [Enter]. Для изменения значения переменной в процессе<br />

отладки следует выбрать строку с именем этой переменной, с<br />

помощью клавиши [Tab] перейти в столбец Value, набрать там новое<br />

значение и нажать клавишу [Enter]. При дальнейшей отладке,<br />

вместо прежнего значения, будет использовано модифицированное<br />

таким образом значение переменной.<br />

Для просмотра значения переменной в реэюиме отладки, наряду<br />

с использованием окон Variables и Watch, можно в окне редактирования<br />

поставить курсор на имя интересующей нас переменной.<br />

Если переменной было присвоено значение, то появится всплывающее<br />

окно со значением этой переменной Эта возмолсностъ наиболее<br />

удобна и мы рекомендуем ее использовать как моэюно чаще.<br />

Встроенный отладчик IDE Borland C++ 5.7. Для запуска исполняемого<br />

файла в режиме отладки следует также предварительно<br />

задать точки останова (breakpoints). Предполагаем, что все операторы,<br />

предшествующие первой точке останова, выполняются правильно.<br />

Самый простой способ установки точки останова заключается<br />

в следующем. Курсор устанавливается на строку, на которой<br />

нужно остановить работу программы, и вводится комбинация клавиш<br />

. Повторный ввод этой комбинации удаляет точку<br />

останова. Строка останова в окне редактирования отмечена красным<br />

цветом. Если, после задания точки останова, программу запустить<br />

по команде Run | Run, либо введя комбинацию клавиш ,<br />

то все операторы программы, предшествующие точке останова, будут<br />

выполняться в обычном режиме и только перед строкой останова<br />

выполнение программы приостановится. При этом строка останова<br />

сохранит подсветку, но изменит цвет подсветки.<br />

Для пошагового выполнения в отладчике имеются следующие<br />

команды.<br />

а Run I Trace over (шаг поверх) или эквивалентно - выполняет<br />

текущий оператор или функцию и переходит к следующей<br />

строке.<br />

а Run I Trace into (шаг внутрь) или эквивалентно - выполняет<br />

текущий оператор языка C++ или переходит к первому оператору<br />

вызываемой функции.<br />

• Run I Go to cursor (выполнить до курсора) или эквивалентно<br />

- выполняет программу до строки, где в текущий момент<br />

находится курсор.<br />

Переменные, которые нужно контролировать в точке останова<br />

можно посмотреть в окне Watch. Чтобы это окно появилось в IDE и<br />

отображало значение требуемого объекта, достаточно поместить<br />

курсор на идентификатор объекта, ввести комбинацию клавиш<br />

и в появмвшемся окне Add Watch нажать кнопку [ОК].<br />

419


П.4.3. Тестирование программного проекта<br />

Как и любой другой продукт производства, программа перед<br />

использованием должна быть тщательно проверена. Этот этап является<br />

едва ли не самым сложным во всем процессе создания программы<br />

- необходимо учесть все варианты ее поведения. Поэтому<br />

его нужно начинать не после завершения отладки, а одновременно с<br />

разработкой алгоритма.<br />

Одним из путей проверки или тестирования программы является<br />

ее выполнение по одному разу с каждой из возможных комбинаций<br />

входных данных — т.е. тестирование с использованием заранее<br />

подготовленного набора контрольных примеров.<br />

Требования к контрольным примерам. Какие же требования<br />

следует предъявить к контрольным примерам<br />

Таких требований всего два.<br />

• Набор контрольных примеров должен быть достаточным,<br />

чтобы показать выполнение всех требований технического задания<br />

и обеспечить полную проверку программного проекта — протестировать<br />

все ветви, имеющиеся в программе.<br />

• Контрольные примеры должны быть простыми в том смысле,<br />

чтобы анализ ожидаемых результатов был несложным (примеры<br />

должны быть небольшой размерности со значениями исходных данных,<br />

удобными для анализа).<br />

Отметим, что создание достаточного и простого набора контрольных<br />

примеров является нетривиальной задачей.<br />

Структура контрольного примера. Структура контрольного<br />

примера может быть, например, следующей:<br />

• цель примера;<br />

• исходные данные (для примеров с нормальным завершением<br />

привести ссылку на листинг файла данных) или как моделировать<br />

ошибку (для примеров с аварийным завершением);<br />

• анализ ожидаемых результатов (для примеров с нормальным завершением<br />

- анализ ожидаемых результатов в точках останова);<br />

• полученные результаты, выводы (для примеров с нормальным завершением<br />

привести ссылку на листинг файла результатов).<br />

Выбор точек останова. При выборе точек останова можно<br />

руководствоваться следующими основными правилами:<br />

• точки останова следует выбирать после выполнения каждой<br />

420


функции программного проекта (в них следует проверить<br />

результаты работы функции);<br />

• если декомпозиция задачи выполнена не очень удачно и функция<br />

получилась большой (более страницы текста), то следует в ее теле<br />

выбрать промежуточные точки останова, разбив тело функции на<br />

функционально законченные части;<br />

• если функция была отлажена ранее, то после нее точку останова<br />

выбирать не следует;<br />

• если функция результаты своей работы выводит в файл на магнитном<br />

диске, на экран или на принтер, то после такой функции<br />

точки останова тоже не нужны.<br />

Методика тестирования программы для контрольных примеров<br />

с нормальным завершением.<br />

При тестировании программы выполняются следующие шаги.<br />

• Программа запускается до первой точки останова так, как<br />

это указывалось выше. Если полученные машинные результаты совпадают<br />

с результатами анализа, приведенного в контрольном примере,<br />

то аналогично программа запускается до следующей точки останова<br />

и т.д.<br />

• Если в очередной точке останова машинные результаты отличаются<br />

от ожидаемых, то текущий сеанс отладки с помощью команды<br />

Debug I Stop Debugging (IDE MS Visual Studio C++ 6.0) или<br />

Run I Program Reset (IDE Borland C++ 3.1) прекращается. Программа<br />

повторно запускается до последней точки останова с хорошими<br />

результатами и с этого места выполняется по шагам с проверкой полученных<br />

результатов (выполняется трассировка ошибочного участка).<br />

По результатам пошаговой проверки обнаруживается ошибка и<br />

текущий сеанс отладки также прекращается. Затем в исходный текст<br />

программы вносятся необходимые исправления и трассировка ошибочного<br />

участка повторяется. Этот процесс заканчивается после исправления<br />

ошибок, о чем будет свидетельствовать получение в очередной<br />

точке останова ожидаемых результатов.<br />

Приложение П.5. Рекомендации по созданию<br />

многофайлового программного проекта<br />

с несколькими функциями и пример оформления<br />

исходного текста.<br />

П.5.1. Спецификация программного проекта<br />

Работа над программным проектом начинается с разработки<br />

421


его спецификации. Спецификация программного проекта включает<br />

требования к обработке ошибок и предупрелсдений, а такэюе сведения<br />

о файловом и функциональном составе программного проекта<br />

и о взаимодействии функций проекта друг с другом.<br />

Прежде всего рассмотрим, что же понимается под предупреждениями<br />

и ошибками. Предупреэюдение необходимо выдавать при<br />

наступлении некоторого события, которое требует информирования<br />

пользователя, но не препятствует продолжению работы программы.<br />

Ошибка возникает при наступлении события, когда дальнейшая работа<br />

программы невозможна. При обработке ошибок и предупреждений<br />

для каждого предупреждения или сообщения об ошибке в начале<br />

диагностического сообщения следует напечатать номер предупреждения<br />

или сообщения об ошибке, обеспечив нумерацию в возрастающем<br />

порядке. Предупреждения, как правило, следует выдавать<br />

в файл результатов, а сообщения об ошибках - на экран.<br />

Основные особенности использования функций в программных<br />

проектах рассмотрены выше в подразд. 2.1 и 3.5. Функциональный<br />

типовой состав программного проекта, как минимум, включает<br />

главную функцию, из которой последовательно вызываются функции<br />

ввода исходных данных, их печати, решения задачи и печати<br />

полученных ответов. Для удобства использования функций универсальные<br />

функции с широкой областью применения целесообразно<br />

размещать в отдельных файлах, причем взаимосвязанные универсальные<br />

функции можно помещать в отдельный общий файл. Специализированные<br />

же функции, напротив, размещают обычно в том<br />

же файле, где находится главная функция программного проекта.<br />

Приведем пример спецификации программного проекта для<br />

решения следующей задачи. Выполнить обработку матрицы, заключающуюся<br />

в том, что в каждой строке матрицы ищется максимальный<br />

элемент. Элементы, стоящие после максимального элемента,<br />

следует заменить нулями и поместить в начало строки. Исходную и<br />

вновь полученную матрицы напечатать. Предусмотреть запуск программного<br />

проекта с использованием командной строки. В файле<br />

исходных данных последовательно содержатся строчный размер<br />

матрицы, число столбцов матрицы и значения элементов матрицы,<br />

разделенные символами пробельной группы (' ', '\^', '\«'). Матрица<br />

размещается в статической памяти.<br />

Файловая и функциональная структура программного<br />

(рис. 107).<br />

проекта<br />

422


Файл Main.cpp<br />

main()<br />

Главная функция<br />

Файл ErWarnW.cpp<br />

ErrorWarningWork()<br />

Обработка ошибок и<br />

предупреждений<br />

Файл CheckCS.cpp<br />

Proglnfo()<br />

Информация о программе<br />

и командной<br />

строке<br />

CheckComString()<br />

Контроль командной<br />

строки<br />

ReadMatrix()<br />

Чтение матрицы<br />

WriteMatrix()<br />

Печать матрицы с<br />

заголовком<br />

SwapUnits()<br />

Перестановка<br />

элементов строк<br />

OpenFile()<br />

Открытие файла<br />

CloseFile()<br />

Закрытие файла<br />

Файл FileOC.cpp<br />

1, 2, ..., 6 - порядок вызова<br />

функций<br />

Функция OpenFile()<br />

вызывается из функций<br />

ErrorWarningWork(),<br />

ReadMatrix() и<br />

WriteMatrix()<br />

Функция CloseFile()<br />

вызывается из функций<br />

ErrorWarningWork(),<br />

ReadMatrix() и<br />

WriteMatrix()<br />

Функция ErrorWarningWork() вызывается из функций<br />

ReadMatrix() и WriteMatrix()<br />

Файл Matrix.срр<br />

^ Вызов функции<br />

Возврат из функции<br />

Рис. 107. Файловая и функциональная структура программного<br />

проекта<br />

Желаемый состав и интерфейс функций. Чтобы функции<br />

ввода и печати массива стали универсальными, надо их снабдить<br />

следующим интерфейсом:<br />

// Прототипы функций<br />

void ReadArray(<br />

±nt Arr[ N ], // Вводимый массив<br />

// Файл ввода<br />

СЪАГ Filelnp[ ] ) ;<br />

void. PrlntArray (<br />

int Arr [ N ] r // Выводимый массив<br />

// Файл вывода<br />

char FileOutf 7,<br />

cha.r Mode [ ] r // Режим открытия файла вывода<br />

423


Заголовок для печати<br />

char Header[ ] ) ;<br />

Перечисленные функции целесообразно разместить в отдельном<br />

файле как универсальные и взаимосвязанные.<br />

Типовыми универсальными операциями являются операции<br />

открытия-закрытия файлов. Поэтому их следует реализовать в виде<br />

функций, расположенных в общем отдельном файле:<br />

FILE * OpenFile ( // Возвращает указатель на структуру<br />

// со сведениями об открытом файле<br />

// Открываемый файл<br />

сЬлг FileName[ ],<br />

сЬа.г Mode [ ] , // Режим открытия файла<br />

хпЬ ErrCode ) ; / / Код ошибки<br />

void CloseFile(<br />

// За крыв аемый файл<br />

char FileName [ ],<br />

// Указатель на структуру со сведениями о закрываемом<br />

// файле<br />

FILE<br />

*pStrInfoFlle,<br />

int ErrCode ) ; / / Код ошибки<br />

Об использовании командной строки при запуске программного<br />

проекта. С этой целью можно использовать, например, командную<br />

строку следующего вида:<br />

Task02.еке Task02.1пр Task02,out [Enter]<br />

вид:<br />

При этом заголовок главной функции может иметь следующий<br />

int main ( // Возвращает О при успехе<br />

±пЬ АгдС, // ARGument Count: число аргументов<br />

// командной строки (в примере 3)<br />

char *ArgV[ ] ) / / Argument Value: массив указателей<br />

// на аргументы командной строки<br />

// ( в примере ArgV [ 1 ]<br />

// эквивалентно "Task02,1пр"^<br />

// ArgVf 2 ] эквивалентно<br />

// "Task02.out")<br />

Для обработки ошибок в формате командной строки можно<br />

использовать функцию следующего вида (эта функция универсальна<br />

и ее целесообразно поместить в отдельный файл):<br />

лго±


±nt ErrCode ) // Код ошибки<br />

(<br />

±f( ArgC != 3 )<br />

{<br />

printf(<br />

"\n Ошибка %d. Непредусмотренный формат командной строки. "<br />

"\л Для запуска программы используйте командную строку вида:"<br />

"\п Т4сполняемый_файл Файл_ввода Файл_вывода'\ ErrCode ) ;<br />

}<br />

exi t ( ErrCode ) ;<br />

Об обработке ошибок и предупреждений. Чтобы многократно не<br />

дублировать аналогичные фрагменты, целесообразно использовать для<br />

обработки ошибок и предупреждений универсальную функцию, которую<br />

следует поместить в отдельный файл. Эта функция может иметь,<br />

например, такой вид:<br />

// Прототип<br />

•vo±(X ErrWarnWork ( xnt ErrWarnCode, сЬат Msg[ ] , char Type^<br />

cbar FileOutl ] = "", сЬаг Mode[ ] = "" ) ;<br />

// Определение<br />

void ErrWarnWork(<br />

// Код ошибки или предупреждения<br />

±nt<br />

ErrWarnCode,<br />

char Msg[ ], Строка сообщения<br />

char<br />

Type,<br />

'е' - ошибка, сообш,ение выдается<br />

на экран, 'w' - предупреждение,<br />

сообш,ение выдается в файл на МД<br />

// Файл вывода<br />

char FlleOutf ],<br />

cha.r Mode [ ] ) // Режим открытия файла вывода<br />

swibch( Туре )<br />

{<br />

ca.se 'е' :<br />

//<br />

//<br />

//<br />

//<br />

printf( "\п Ошибка %d. %s ", ErrWarnCode, Msg ) ;<br />

exit ( ErrWarnCode ) ;<br />

case<br />

'w*:<br />

// Здесь открывается файл FileOut в режиме Mode<br />

// (получаем указатель pFileOut с типом FILE *)<br />

fprintf ( pFileOut, "\п Предупреждение %d. %s ",<br />

ErrWarnCode, Msg ) ;<br />

// Здесь закрывается файл с указателем pFileOut<br />

break;<br />

cLefavLl Ь:<br />

printf(<br />

425


"\п Ошибка %d. Использован недопустимый тип сообщения:"<br />

"\п используйте \'е\' или \'w\' ", ErrWarnCode )/<br />

exit( ErrWarnCode ) ;<br />

}<br />

}<br />

retvLrn;<br />

// Пример вызова для предупреждения<br />

// Для сообш,ения<br />

ch&r buff 200 ];<br />

// Формируем текст предупреждения<br />

sprint f ( bufr " ... Управляюш,ая строка с форматами . . . ",<br />

список аргументов для форматов ) ;<br />

ErrWarnWork( 40, buf, 'W, ArgV[ 2 ], "a" ) ;<br />

// Пример вызова для ошибки<br />

// Формируем текст сообщения об ошибке<br />

sprint f ( buf, "... Управляющая строка с форматами . . .",<br />

список аргументов для форматов ) ;<br />

ErrWarnWork( 50, buf, 'е' ) ;<br />

// !!! Два последних аргумента не нужны и их не записываем.<br />

// Так можно делать, так как 2 последних параметра имеют<br />

// значения по умолчанию - см. прототип<br />

Обработка ошибок и предупреждений,<br />

1. Ошибка открытия файла.<br />

При возникновении данной ошибки программа прерывает работу,<br />

выдавая на экран следующее сообщение:<br />

"Ошибка № XX. Ошибка открытия файла для чтения/записи/дозаписи.<br />

"<br />

Код ошибки и код возврата задаются в вызове функции открытия<br />

файла,<br />

2. Предупреждение о том, что файл не закрыт.<br />

При возникновении данной ситуации программа выдает на экран<br />

следующее сообщение:<br />

"Предупреждение № XX. Файл не закрыт. Выполнение<br />

программы продолжено."<br />

Код предупреждения задается в вызове функции закрытия<br />

файла.<br />

3. Неверный режим открытия файла.<br />

При возникновении данной ошибки программа прерывает работу,<br />

выдавая на экран следующее сообщение:<br />

"Ошибка № XX. Использован непредусмотренный режим открытия<br />

файла . Используйте режимы "г", "rt",<br />

"w", "wt", "а" или "at" (на любом регистре) . "<br />

Код ошибки и КОД возврата задаются в вызове функции закры-<br />

426


тия файла.<br />

3. Неверный тип выдаваемого сообщения.<br />

При возникновении данной ситуации программа выдает на экран^<br />

следующее сообщение:<br />

"Предупреждение № XX. Использован непредусмотренный выдаваемого<br />

сообщени вместо 'е' или 'w'. Применен режим 'w',<br />

выполнение программы продолжено, "<br />

Код предупреждения задается в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

4. Недопустимое значение количества строк матрицы.<br />

При возникновении данной ситуации программа продолжает<br />

работу, выдавая в файл результатов следующее сообщение:<br />

"Предупреждение № XX. Из файла прочитано недопустимое<br />

значение количества строк матрицы, равное ... (количество<br />

строк должно лежать в диапазоне от 2 до ...) . Принимается<br />

количество строк 2, выполнение программы продолжается."<br />

Код предупреждения задается в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

5. Недопустимое значение количества столбцов матрицы.<br />

При возникновении данной ситуации программа продолжает<br />

работу, выдавая в файл результатов следующее сообщение:<br />

"Предупреждение 1 XX. Из файла прочитано недопустимое<br />

значение количества столбцов матрицы, равное<br />

(количество столбцов должно лежать в диапазоне от 2 до ...) .<br />

Принимается количество столбцов 2, выполнение программы продолжается.<br />

"<br />

Код предупреждения задается в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

6. Ошибка чтения значения количества строк матрицы.<br />

При возникновении данной ошибки программа прерывает работу,<br />

выдавая на экран следующее сообщение:<br />

"Ошибка I XX. Ошибка чтения значения количества строк матрицы<br />

из файла ."<br />

Код ошибки и код возврата задаются в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

7. Ошибка чтения значения количества столбцов матрицы.<br />

При возникновении данной ошибки программа прерывает работу,<br />

выдавая на экран следующее сообщение:<br />

"Ошибка № XX. Ошибка чтения значения количества столбцов матрицы<br />

из файла ."<br />

Код ошибки и код возврата задаются в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

427


8. Ошибка чтения значения элемента матрицы.<br />

При возникновении данной ошибки программа прерывает работу,<br />

выдавая на экран следующее сообщение:<br />

"Ошибка № XX. Ошибка чтения значения элемента матрицы с номером<br />

строки ... и номером столбца ... из файла . "<br />

Код ошибки и код возврата задаются в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

9. Преждевременный конец файла исходных данных.<br />

При возникновении данной ситуации программа продолжает<br />

работу, выдавая в файл результатов следующее сообщение:<br />

"Предупреждение № XX. Файл ввода содержит<br />

недостаточное количество данных (преждевременный конец файла)<br />

. Непрочитанные элементы матрицы инициализируются нулями.<br />

Выполнение программы продолжается."<br />

Код предупреждения задается в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

10. Избыточное количество данных в файле исходных данных.<br />

При возникновении данной ситуации программа продолжает<br />

работу, выдавая в файл результатов следующее сообщение:<br />

"Предупреждение № XX. Файл ввода содержит<br />

лишние данные г которые игнорируются программой. Выполнение<br />

программы продолжается."<br />

Код предупреждения задается в вызове функции обработки<br />

предупреждений и сообщений об ошибках.<br />

11. Неверное количество аргументов командной строки.<br />

При возникновении данной ошибки программа прерывает работу,<br />

выдавая на экран следующее сообщение:<br />

"Ошибка № XX. Использован неверный формат командной строки.<br />

Обработка матрицы, заключающаяся в том, что в каждой<br />

строке матрицы ищется максимальный элемент. Элементы, стояш^е<br />

после максимального элемента, заменяются нулями и помещаются<br />

в начало строки. Исходная и вновь полученная матрицы печатаются.<br />

Запуск программы выполняется с использованием командной<br />

строки вида:<br />

имя_выполняемого_файла . ехе файл__ввода файл_вывода . "<br />

Код ошибки и код возврата задаются в вызове функции обработки<br />

командной строки.<br />

П5.2. Пример оформления исходного текста программы<br />

/*<br />

Файл<br />

TASK02.CPP<br />

Проект : многофайловый с функциями.<br />

428


расположенными в отдельных файлах<br />

Назначение : вычисление площади садового<br />

участка Square := Length * Width<br />

Состав проекта (файл<br />

проекта TASK02.PRJ) : файл TASK02.СРР (главная функция<br />

проекта);<br />

файл TASK02_1. СРР (ввод длины и<br />

ширины садового участка) ;<br />

файл TASK02_2.СРР (печать длины и<br />

ширины садового участка) ;<br />

файл TASK02_3.СРР (вычисление<br />

площади садового участка) ;<br />

файл 4. СРР (печать площади<br />

садового участка)<br />

ЭВМ : IBM 80386<br />

Среда программирования: ВС31 (C++)<br />

Операционная система : DR DOS 6.О<br />

Дата создания : 02.11.2002<br />

Дата корректировки<br />

_V<br />

Иванов И. И., ФТКг гр. 1081/4<br />

Санкт-Петербургский государственный политехнический<br />

университет<br />

// Стандартные включаемые файлы и прототипы функций<br />

iinclude "task02.h"<br />

±nt main ( void ) // Возвращает О при успехе<br />

{<br />

float Lenght,• // Длина садового участка<br />

Width, // Ширина садового участка<br />

Square; // Площадь садового участка<br />

// Ввод длины и ширины садового участка<br />

ReadData( Lenght, Width ) ;<br />

// Печать длины и ширины садового участка<br />

WriteDat( Lenght, Width ) ;<br />

// Вычисление площади садового участка<br />

Area ( Lenght, Width, Square ) ;<br />

WriteRes ( Square ) ; // Печать площади садового участка<br />

jcebvLxrn. О;<br />

} // Конец файла TASK02.CPP<br />

429


Файл TASK02.H<br />

Включаемый файл для проекта TASK02.PRJ:<br />

ные включаемые файлы и прототипы функций.<br />

содержит стандарт-<br />

// предотвращение многократного включения данного файла<br />

#ifndef TASK02__H<br />

^define TASK02_H<br />

^include // Для функций ввода-вывода<br />

^include // Для функции exit<br />

// Прото типы функций<br />

void. ReadData ( float &Length, float &Width );<br />

void WriteDat( float Length, float Width );<br />

void. Area ( float Length, float Width, float &Square )<br />

void WriteRes( float Square );<br />

#endif<br />

// Конец файла TASK02.H<br />

Файл TASK02_1.CPP<br />

Чтение исходных данных из файла TASK02.DAT. Используется в<br />

поограммном проекте TASK02.PRJ<br />

V<br />

// Стандартные включаемые файлы и прототипы функций<br />

^include "task02,h"<br />

void<br />

{<br />

430<br />

ReadData(<br />

float &Lenght, // Длина садового участка<br />

float &Width ) // Ширина садового участка<br />

FILE *f__in; // Указатель на структуру со<br />

II сведениями о файле для чтения<br />

// Открываем файл для ввода<br />

±f( ( f_in = fopen( "task02.dat", "г" ; ; =- NULL )<br />

{<br />

}<br />

printf( "\n Ошибка 10. Файл task02,dat для чтения не"<br />

" открыт \п" ) ;<br />

exit ( 10 ) ;<br />

// Читаем данные<br />

±f( fscanf( f__in, " %f %f", &Lenght, &Width ) != 2 )<br />

(<br />

printf( "\n Ошибка 20. Произошла ошибка чтения из"<br />

" файла task02.dat \п" );<br />

exit ( 20 );<br />

}<br />

// Закрываем файл для чтения


±f( fclose ( f_in ) == EOF )<br />

{<br />

}<br />

printf( "\n Ошибка 30. Файл task02.dat не "<br />

"закрыт \n" );<br />

exit ( 30 );<br />

iretuirn/<br />

// Конец файла TASK02_1.CPP<br />

/*<br />

Файл TASK02 2.CPP<br />

Печать исходных данных в файл<br />

программном проекте TASK02.PRJ<br />

TASK02.RES. Используется в<br />

// Стандартные включаемые файлы и прототипы функций<br />

^Include "task02.h"<br />

void<br />

(<br />

WrlteDat(<br />

float Lenght^ // Длина садового участка<br />

float Width ) // Ширина садового участка<br />

FILE *f_out/ // Указатель на структуру со<br />

// сведениями о файле для записи<br />

// Открываем файл для записи и печатаем исходные данные<br />

±f( ( f_out = fopen( "task02.res", "w" ) ) == NULL )<br />

{<br />

printf( "\n Ошибка 40. Файл task02.res для записи не"<br />

" открыт \п" );<br />

exit ( 4 0 );<br />

}<br />

fprlntf ( f_OUtr<br />

"\n Вычисление площади садового участка \п"<br />

"\п Длина садового участка: %д "<br />

"\п Ширина садового участка: %д'\ Lenght, Width );<br />

// Закрываем файл для вывода<br />

if( fclose( f_out ) == EOF )<br />

{<br />

}<br />

prlntfi "\n Ошибка 50. Файл task02.res не "<br />

"закрыт \n" ) /<br />

exit ( 50 ) ;<br />

return/<br />

} // Конец файла TASK02 2.GPP<br />

Файл TASK02_3.CPP<br />

Вычисление плош,ади прямоугольника.<br />

граммном проекте TASK02.PRJ<br />

Используется в про-<br />

431


Стандартные включаемые файлы и прототипы функций<br />

iinclude "task02.h"<br />

void. Area (<br />

float Lenght, // Длина садового участка<br />

float Widths // Ширина садового участка<br />

float & Square ) // Площадь садового участка<br />

{<br />

// Вычисление площади садового участка<br />

Square = Lenght * Width;<br />

retxum;<br />

} // Конец файла TASK02_3.CPP<br />

__<br />

Файл TASK02_4.CPP<br />

Печать результатов в файл TASK02. RES. Используется в программном<br />

проекте TASK02.PRJ<br />

*/<br />

// Стандартные включаемые файлы и прототипы функций<br />

^include "task02.h"<br />

void<br />

{<br />

WriteRes(<br />

float Square ) // Площадь садового участка<br />

FILE *f_out; // Указатель на структуру со<br />

// сведениями о файле для записи<br />

// Открываем файл для дозаписи<br />

±f( ( f_out = fopen( "task02.res", "a" ) ) == NULL )<br />

' {<br />

}<br />

printf( "\n Ошибка 60. Файл task02.res для дозаписи"<br />

" не открыт \п" ) ;<br />

exit ( 60 ) ;<br />

// Печать результатов работы программы<br />

fprintf ( f_out,<br />

"\п\п Площадь садового участка: %д", Square ) ;<br />

// Закрываем файл<br />

±f( fclose ( f_out ) =- EOF )<br />

}<br />

{<br />

printf( "\n Ошибка 70. Файл task02.res<br />

" закрыт \n" ) ;<br />

не"<br />

exit ( 70 ) ;<br />

return;<br />

} // Конец файла TASK02 4.CPP<br />

432


в приведенном примере рекомендуем.<br />

1. Обратить внимание на оформление заголовочного файла и<br />

его подключение к файлам проекта.<br />

Повторим здесь еще раз, что обычно в заголовочный файл помещают<br />

директивы ^include', прототипы функций; определения<br />

встроенных (inline) функций; объявления (extern) данных, определенных<br />

в другом файле; определения (const) констант; перечисления<br />

(епит), директивы условной трансляции (#ifndef, i^endif VL др.), макроопределения<br />

(Udeflne), именованные пространства имен (namespace),<br />

определения типов (class, struct), объявления и определения<br />

шаблонов (template).<br />

Заголовочные файлы никогда не должны содержать определения<br />

невстроенных функций, определения данных (объектов), определения<br />

массивов и неименованные пространства имен.<br />

2. Обратить внимание на оформление функций и, особенно, на<br />

оформление заголовка функции.<br />

3. Обратить внимание на оформление заголовочных комментариев<br />

файлов программного проекта.


Приложение П.б. Примерная программа дисциплины<br />

"Программирование и основы алгоритмизации".<br />

МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ<br />

УТВЕРЖДАЮ<br />

Начальник Управления образовательных программ<br />

и стандартов высшего и среднего профессионального<br />

образования<br />

2000 г.<br />

ПРИМЕРНАЯ ПРОГРАММА дисциплины<br />

'Программирование и основы алгоритмизации"<br />

Рекомендуется Минобразованием России для подготовки бакалавров по направлению<br />

5502 "Автоматизация и управление" (и подготовки специалистов по<br />

направлению 6519 "Автоматизация и управление")<br />

434


1. Цели и задачи дисциплины<br />

Цель дисциплины состоит в поэтапном формировании у студентов следующих<br />

слоев знаний и умений,<br />

• Слой 1: знание основных понятий программирования.<br />

• Слой 2: знание базового языка программирования.<br />

• Слой 3: умение решать задачи на вычислительных машинах ВМ.<br />

Формированию отмеченных уровней (слоев) знаний и умений соответствуют<br />

разделы дисциплины. Изучение курса предполагает, что студенты знакомы с принципами<br />

работы ВМ, десятичной, двоичной, восьмеричной и шестнадцатеричной<br />

системами счисления, а также основными понятиями информатики.<br />

2. Требования к уровню освоения содержания дисциплины<br />

В результате изучения дисциплины студенты должны:<br />

1. Знать основные понятия программирования.<br />

2. Знать базовый язык программирования.<br />

3. Уметь решать задачи на ВМ.<br />

Примечание. Слой 3 в полном объеме и слой 4 знаний и умений -умение проектировать<br />

программное обеспечение — формируются у студентов в процессе изучения<br />

дополнительной дисциплины "Технология программирования", которая является<br />

органическим продолжением данной.<br />

3. Объем дисциплины и виды учебной работы<br />

Вид учебной работы<br />

Общая трудоемкость дисциплины<br />

Аудиторные занятия<br />

Лекции<br />

Практические занятия (упражнения)<br />

Курсовая работа<br />

Самостоятельная работа<br />

Вид итогового контроля (зачет, экзамен)<br />

4. Содержание дисциплины<br />

4.L Разделы дисциплины и виды занятий<br />

Всего часов<br />

130<br />

68<br />

34<br />

17<br />

17<br />

62<br />

Экзамен<br />

Семестр<br />

2<br />

2<br />

2<br />

2<br />

2<br />

2<br />

2<br />

iN» п/п<br />

1<br />

2<br />

3<br />

Раздел дисциплины<br />

Основные понятия профаммрфования<br />

Си/С+-1-: базовый язык программирования (алфавит,<br />

синтаксис, типы данных, выражения и операции,<br />

структурное программирование, операторы ветвлений<br />

и циклов, массивы и структуры, модальное программирование,<br />

функции, ввод и вывод, описатели класса<br />

хранения, области действия и время жизни данных,<br />

указатели, препроцессор)<br />

Прикладное профаммирование (сортировки массивов,<br />

рекурсия и итерация, транспортная задача, элементы<br />

обработки списков, работа с динамической памятью)<br />

Лекции<br />

*<br />

•<br />

•<br />

ПЗ<br />

ЛР<br />

(упражнения) (курс, работа)<br />

*<br />

*<br />

*<br />

435


4,2. Содермсание разделов дисциплины<br />

Раздел 1. Основные понятия программирования<br />

(лекции и самостоятельная работа: 16 часов)<br />

1. Алгоритм, данные, программа, структура данных.<br />

2. Регистр: разрядность, графическое изображение. Совокупность регистров как основа<br />

оперативного запоминающего устройства (ОЗУ). Адрес, хранимое слово.<br />

Работа ОЗУ в режимах чтения и записи.<br />

3. Простейший набор арифметических операций. Выполнение простейших арифметических<br />

операций.<br />

4. Работа ВМ при последовательном выполнении команд. Ветвления в программах.<br />

Команды условных переходов и безусловной передачи управления.<br />

5. Прямая и косвенная адресация. Зачем нужна косвенная адресация<br />

6. Классификация и краткая характеристика языков программирования. Машиннозависимые<br />

язьпси: машинные (О GL - О Generation of the Language), ассемблеры (1<br />

GL) и макроассемблеры (2 GL). Машинно-независимые языки: процедурные (3<br />

GL), проблемные (4 GL) и универсальные (5 GL).<br />

7. Введение в языки ассемблера (1 GL). Операторы языка ассемблера ВМ с "очень<br />

простой" архитектурой. Символические команды. Псевдокоманды. Схема<br />

трансляции в два прохода.<br />

8. Периферийные устройства ВМ и их разновидности. Накопители на магнитных<br />

дисках (НМД) с жёсткими дисками. Прямой и последовательный доступ к информации.<br />

Монитор, работа в текстовом и графическом режимах. Клавиатура и<br />

мышь. Принтеры. Взаимодействие программ с ПУ.<br />

9. Программные продукты. Основные виды, этапы проектирования и жизненный<br />

цикл.<br />

Раздел 2. Си/С++: базовый язык программирования<br />

(лекции, практические занятия и самостоятельная работа: 80 часов)<br />

Цель этой части курса - овладение подмножеством языка C++, изобразительные<br />

средства которого обеспечиваются большинством языков третьего поколения (3<br />

GL).<br />

\. Программирование на языках высокого уровня (на примере Си/С++). Язык Си:<br />

история, первоначальная область применения (системное программирование).<br />

Принцип построения: компилируемые конструкции и интерпретируемые средства<br />

(библиотека стандартных функций). Раздельная трансляция, компилятор и редактор<br />

связей.<br />

2. Алфавит языка. Способы описания синтаксиса языка: металингвистические формулы<br />

и синтаксические диаграммы. Определение понятия "идентификатор".<br />

Служебные слова. Комментарии.<br />

3. Типы данных. Имена и объявления. Целые типы данных: int, short, long, char.<br />

Кодовый формат. Описание данных, литералы. Тип целых констант. Арифметические<br />

операции для целых операндов.<br />

4. Плавающие типы данных: float, double. Кодовый формат. Описание данных, литералы.<br />

Тип констант с плавающей точкой. Арифметические операции для вещественных<br />

операндов. Математические функции стандартной библиотеки Си. Назначение<br />

стандартных заголовочных файлов. Компоновка программы из объектных<br />

модулей и библиотек.<br />

5. Понятие преобразования данных. Зачем нужны преобразования Примеры преобразований.<br />

Явное преобразование типа. Правила преобразования операндов в<br />

436


процессе вычислений.<br />

6. Оператор-выражение. Операции уменьшения и увеличения, префиксная и постфиксная<br />

форма. Операции простого и составного присваивания. Приоритеты операций.<br />

7. Оператор-выражение. Операции отношения. Результат вычисления отношений.<br />

Представление булевских значений "ложь", "истина" в Си. Логические операции:<br />

!, II, &&. Операции простого и составного присваивания. Приоритеты операций.<br />

8. Структурное программирование. Операторы ветвления: if и switch. Оператор if,<br />

синтаксис, выполнение оператора. Операции отношения. Составной оператор.<br />

Сложные условия и логические операции: !, ||, &&. Вложение операторов if, оператор<br />

if... else if... else. Оператор switch, выполнение, использование оператора<br />

break.<br />

9. Структурное программирование. Операторы цикла с предусловием (while) и постусловием<br />

(do-while). Примеры применения циклов. Изменение хода выполнения<br />

цикла с помощью операторов break и continue.<br />

10. Одномерные массивы, пример использования. Связь массивов и указателей. Обращение<br />

к элементу массива, адресная арифметика. Структурное программирование.<br />

Оператор цикла с шагом: for. Строки, кодовый формат, строковые литералы.<br />

Двумерные массивы. Функция индексации для двумерного массива.<br />

11. Структуры, описание, пример использования. Операция выбора элемента структуры.<br />

Операции над структурой в целом. Структуры как аргументы функций.<br />

Объявление типа: typedef. Размещение структур в памяти.<br />

12. Модульное программирование. Функции. Рациональные размер и количество параметров<br />

функции. Пример функции. Аргументы и параметры. Передача аргументов<br />

по значению и по ссылке. Прототипы функций. Преобразование аргументов<br />

в точке вызова. Оператор return.<br />

13. Ввод и вывод (передача) с преобразованиями. Понятие набора данных и файла.<br />

Открытие и закрытие потоков. Функции передачи: fscanf и fprintf. Управляющая<br />

строка, форматы при вводе и при выводе. Передача без преобразований (в кодовых<br />

форматах).<br />

14. Время жизни и способ размещения данных. Спецификация класса памяти. Статический<br />

способ размещения. Спецификаторы класса памяти extern и static. Динамический<br />

процесс исполнения программы и концепция памяти auto / register.<br />

Данные с управляемым способом размещения, операции new и delete. Инициализация<br />

данных.<br />

15. Объявления и определения. Область действия описаний. Структура программы<br />

на языке Си. Переобъявления во вложенных блоках. Определения и объявления<br />

на внешнем уровне. Определения и объявления на внутреннем уровне.<br />

16. Указатели, адресная арифметика, указатели и массивы. Введение в обработку<br />

списков. Создание и уничтожение узлов списка, вставка и исключение узлов списка.<br />

17. Препроцессор Си. Директива препроцессора #define. Терминология: макроопределение,<br />

макрообращение, макрорасширение. Макросы с параметрами, аналогия<br />

с функциями. Директива препроцессора #include. Директивы условной компиляции.<br />

По материалу разд. 2 в рамках практических занятий и самостоятельной работы<br />

выполняются следующие практические работы.<br />

Проверочные работы по следующим темам (каждая длительностью 20-30 минут):<br />

- ввод-вывод с использованием функций fscanf - fprintf;<br />

437


- ветвления и циклы;<br />

- структуры;<br />

- функции;<br />

- области действия и время жизни объектов;<br />

- указатели;<br />

- операции с линейным списком.<br />

Простой программный проект, предусматривающий решение трех элементарных<br />

задач с использованием языка C++ (однофайловые программы с одной функцией):<br />

- программирование формулы:<br />

- программирование ветвления;<br />

- программирование цикла.<br />

В процессе выполнения программного проекта изучаются программная документация:<br />

- единая система программной документации (ЕСПД);<br />

- состав ЕСПД, назначение и содержание программных документов (пять из них -<br />

"Техническое задание", "Текст программы", "Описание программы", "Программа и<br />

методика испытаний" и схема программы - используются при оформлении программного<br />

проекта).<br />

Раздел 3, Прикладное программирование<br />

(лекции, практические занятия и самостоятельная работа: 34 часа)<br />

Цель этого раздела - овладение основами науки программирования, а также<br />

профессиональным стилем программирования на Си/С++.<br />

Выделено шесть часто встречающихся в приложениях типов задач: сортировка<br />

массивов, рекурсивные методы, обработка списков, работа с таблицами, работа с<br />

файлами, обработка текстов. Решение задач включает алгоритмизацию и программирование.<br />

Умение решать эти задачи составляет основу профессиональной квалификации<br />

любого программиста.<br />

Профессиональный стиль программирования подразумевает разработку простых<br />

и понятных исходных текстов программ. Важное внимание уделяется выбору<br />

наиболее подходящих в каждом случае изобразительных средств языка и оформлению<br />

программ с учётом особенностей психики человека.<br />

Рассматриваемые ниже задачи демонстрируют использование при проектировании<br />

программного продукта иерархической декомпозиции задачи.<br />

1. Сортировка: виды, терминология, обозначения. Простые алгоритмы сортировки<br />

(выбором, вставками, обменом). Разработка функций, оценка производительности.<br />

2. Сортировка сложным выбором: с помощью двоичного дерева. Идея, пример работы,<br />

разработка функции, оценка производительности.<br />

3. Сортировка сложными вставками: метод Шелла. Идея, пример работы, разработка<br />

функции, оценка производительности.<br />

4. Сортировка сложным обменом: быстрая сортировка Хоора (нерекурсивный вариант).<br />

Идея, пример работы, разработка функции, оценка производительности.<br />

5. Рекурсия и итерация. Рекурсия как метод вычислений. Рекурсивный вариант быстрой<br />

сортировки Хоора. Когда не следует использовать рекурсию Поиск пути<br />

минимального суммарного веса во взвешенном неориентированном графе.<br />

6. Элементы обработки списков. Инвертирование списка ссылок в задаче поиска<br />

пути минимального суммарного веса во взвешенном неориентированном графе.<br />

7. Сортировка массива сложным обменом: быстрая сортировка Хоора (рекурсив-<br />

438


ный вариант).<br />

По материалу разд. 3 в рамках практических занятий и самостоятельной работы<br />

выполняется более сложный программный проект, реализующий решение некоторой<br />

содержательной задачи. Проект содержит несколько файлов с исходным текстом и<br />

несколько функций, расположенных в этих файлах. Большое внимание в проекте<br />

уделяется методике тестирования спроектированного программного продукта.<br />

Как указывалось выше, для более полной подготовки в области программирования<br />

требуется формирование дополнительных знаний и умений, включая дальнейшее<br />

развитие слоя 3 и освоение дополнительного слоя 4: умение проектировать<br />

программное обеспечение. Достижению этой цели способствует последующий курс<br />

"Технология программирования".<br />

5. Лабораторный практикум (не предусматривается)<br />

6. Учебно-методическое обеспечение дисциплины<br />

6.7. Рекомендуемая литература<br />

Основная литература:<br />

1. Трои Д. Программирование на языке Си для персонального компьютера IBM PC:<br />

Пер. с англ. - М.: Радио и связь, 1991.<br />

2. Уэйт М., Прата С, Мартин Д. Язык Си: Пер. с англ. - М.: Мир, 1988.<br />

3. Керниган Б., Ритчи Д., Фъюер А. Язык программирования Си: Задачи по языку<br />

Си: Пер. с англ. - М.: Финансы и статистика, 1985.<br />

4. Давыдов В.Г. Теория и технология программирования. Конспект лекций. Ч. 1. ~-<br />

Санкт-Петербургский государственный технический университет, СПб.: 2000.<br />

5. Давыдов В.Г. Теория и технология программирования. Конспект лекций. Ч. 2. -<br />

Санкт-Петербургский государственный технический университет, СПб.: 2001.<br />

Дополнительная литература:<br />

1. Рассохин Д. От Си к Си++. М.: Издательство "ЭДЕЛЬ", 1993.<br />

2. От Си к Си++ / Е.И. Козелл, Л.М. Романовская, Т.В. Русс и др. М.: Финансы и<br />

статистика, 1993.<br />

3. Эллис М., Строуструп Б. Справочное руководство по языку программирования<br />

C++ с комментариями: Пер. с англ. - М.: Мир, 1992.<br />

4. Бруно Б. Просто и ясно о C++: Пер. с англ. - М.: БИНОМ, 1995.<br />

5. Пол Ирэ. Объектно-ориентированное программирование с использованием C++:<br />

Пер. с англ. / Ирэ Пол. - К.: НИПФ "ДиаСофт Лтд", 1995.<br />

6. Ю. Тихомиров. Visual C++ 6 - СПб.: БХВ - Санкт-Петербург, 1999.<br />

7. Давыдов В.Г., Пекарев М.Ф. Учебный машинно-ориентированный язык (ПМассемблер):<br />

Учебное пособие. Санкт-Петербургский государственный технический<br />

университет, СПб.: 2000.<br />

6.2, Средства обеспечения освоения дисциплины<br />

Любая интегрированная среда программирования языка C++ (желательно более<br />

простая, чтобы сосредоточить внимание на вопросах программирования). Выбирается<br />

по усмотрению вуза.<br />

439


7. Материально-техническое обеспечение дисциплины<br />

Компьютерный класс, оснащенный ПК не ниже Pentium 75 МГц с ОС<br />

Windows 95 и выше или Windows NT.<br />

8. Методические рекомендации по организации изучения<br />

дисциплины<br />

Использование специального методического обеспечения не предусмотрено.<br />

Программа составлена в соответствии с Государственным образовательным<br />

стандартом высшего профессионального образования по направлению<br />

5502 "Автоматизация и управление" подготовки бакалавров и по направлению<br />

6519 "Автоматизация и управление" подготовки специалистов.<br />

Программу составили:<br />

Лекарев Михаил Федорович, д.т.н., профессор, СПбГТУ (ЛПИ)<br />

Давыдов Владимир Григорьевич, к.т.н., доцент, СПбГТУ (ЛПИ)<br />

Программа одобрена на заседании учебно-методического совета по направлению<br />

5502 "Автоматизация и управление" 14.11.2000, протокол №<br />

Председатель Совета УМО


Приложение П.7. Прилагаемый компакт-диск.<br />

На прилагаемом компакт-диске содержатся исходные тексты<br />

всех примеров программ. Имеются также и исполняемые файлы<br />

этих программ, так что Вам не надо обязательно компилировать заинтересовавшие<br />

Вас примеры. Все программные проекты примеров<br />

"самодостаточны". Это означает, что ни одному из них не требуются<br />

файлы других проектов.<br />

Кроме исходных текстов примеров программ на компакт-диске<br />

имеются:<br />

• описание ПМ-ассемблера в формате текстового редактора Word<br />

2000;<br />

• интегрированная среда программирования ПМ-ассемблера (работает<br />

как DOS-приложение);<br />

• файл с полным текстом приложений к учебному пособию в формате<br />

текстового редактора Word 2000 и др.<br />

Более полные сведения о содержимом компакт-диска и работе<br />

с этой информацией имеется в файле ReadMe, расположенном в<br />

корневой папке компакт диска.<br />

Обратите внимание, что пользование данной книгой возможно<br />

и без компакт-диска, но его наличие обеспечит Вам большие удобства<br />

и дополнительный сервис. В частности, без компакт-диска Вы<br />

не будете располагать описанием ПМ-ассемблера, специально разработанного<br />

для использования в учебном процессе, и его интегрированной<br />

средой программирования.


ЛИТЕРАТУРА<br />

1. Давыдов В.Г., Пекарев М.Ф. Учебный машинноориентированный<br />

язык (ПМ-ассемблер): Учебное пособие. - СПб.:<br />

Санкт-Петербургский государственный политехнический университет,<br />

2002.<br />

2. Пекарев М.Ф. Модули с двумя выходами в программных<br />

проектах. - СПб.: СПбГТУ, 2000.<br />

3. Рассохин Д. От Си к C++. - М.: Издательство "ЭДЕЛЬ",<br />

1993.<br />

4. От Си к C++ / Е.И. Козелл, Л.М. Романовская, Т.В. Русс и<br />

др. - М.: Финансы и статистика, 1993.<br />

5. C/C++. Программирование на языке высокого уровня / Т.А.<br />

Павловская. - СПб.: Питер, 2001.<br />

6. Давыдов В.Г. Теория и технология программирования: Конспект<br />

лекций. Ч. 2. СПб: Издательство СПбГПУ, 2001.


СОДЕРЖАНИЕ<br />

ПРЕДИСЛОВИЕ 3<br />

1. ВВЕДЕНИЕ 5<br />

1.1. Системы счисления 5<br />

1.2. Классификация языков программирования и их<br />

краткая характеристика 8<br />

1.2.1. Машинные языки 9<br />

1.2.2. Ассемблерные языки (на примере<br />

ПМ-ассемблера) 12<br />

1.2.3. Макроассемблерные языки 12<br />

1.2.4. Машинно-независимые языки 14<br />

ЧАСТЬ 1. БАЗОВЫЙ ЯЗЫК 16<br />

2. ЯЗЫК ПРОГРАММИРОВАНИЯ ВЫСОКОГО<br />

УРОВНЯ C++ 16<br />

2.1. Введение. Структурное и модульное<br />

программирование 17<br />

2.1.1.Алгоритм и способы его записи 17<br />

2.1.2. Структурное и модульное программирование 21<br />

2.2. Язык программирования и его описание 27<br />

2.3. Структура и конструкция программы на Си/С++ 31<br />

2.3.1. Комментарии 31<br />

2.3.2. Идентификаторы 32<br />

2.3.3. Слуэюебные слова 32<br />

2.3.4. Константы 33<br />

2.3.5. Структура Си-программы 38<br />

2.4. Простой ввод-вывод в языках Си/С++ 41<br />

2.4.1. Ввод-вывод потока 41<br />

2.4.2. Ввод с использованием функций scanf-fscanf 43<br />

2.4.3. Вывод с использованием функций printf-fprintf 49<br />

2.4.4. Упразднения для самопроверки 54<br />

3. ТИПЫ ДАННЫХ И ИХ АТРИБУТЫ 56<br />

3.1. Имена 56<br />

3.2. Типы данных 57<br />

3.3. Класс хранения: область действия и время жизни 60<br />

3.4. Внешние и внешние статические данные 61<br />

3.5. Функции 68<br />

3.6. Автоматические, регистровые и внутренние<br />

статические данные 74<br />

3.7. Инициализация данных 78<br />

3.8. Упражнения для самопроверки 79<br />

443


3.9. Производные типы данных 81<br />

3.9.1. Массивы 81<br />

3.9.2. Массивы — как аргументы функций 86<br />

3.9.3. Упраэюнения для самопроверки 88<br />

3.9.4. Структуры 88<br />

3.9.5. Структуры в качестве аргументов функций 91<br />

3.9.6. Упраэюнения для самопроверки 93<br />

4. ОПЕРАТОРЫ И УПРАВЛЕНИЕ ИХ ИСПОЛНЕНИЕМ .... 95<br />

4.1. Пустой оператор 95<br />

4.2. Операторы-выражения 95<br />

4.3. Операторы break и continue 96<br />

4.4. Блок операторов 96<br />

4.5. Оператор return 97<br />

4.6. Оператор if 97<br />

4.7. Оператор switch 100<br />

4.8. Оператор while 101<br />

4.9. Оператор do-while 104<br />

4.10. Оператор for 105<br />

4.11. Оператор goto и метки операторов 108<br />

4.12. Упражнения для самопроверки 108<br />

5. ВЫРАЖЕНИЯ И ОПЕРАЦИИ 111<br />

5.1. Операции ссылки 113<br />

5.2. Унарные операции 116<br />

5.3. Бинарные операции 118<br />

5.4. Тернарная операция 121<br />

5.5. Операция присваивания 121<br />

5.6. Операция "запятая" 122<br />

6. УКАЗАТЕЛИ 123<br />

6.1. Зачем нужны указатели 123<br />

6.2. Указатели и их связь с массивами и строками 123<br />

6.3. Определение и применение указателей 124<br />

6.4. Указатели на структуры 128<br />

6.5. Использование указателей в качестве аргументов<br />

функций 129<br />

6.6. Указатель как значение, возвращаемое функцией 133<br />

6.7. Массивы указателей 134<br />

6.8. Замена типов указателей 137<br />

6.9. Упражнения для самопроверки 141<br />

7. ПОЛЯ БИТОВ И ПОБИТОВЫЕ ОПЕРАЦИИ 143<br />

7.1. Поля битов 143<br />

7.2. Побитовые операции 144<br />

444


8. ДИНАМИЧЕСКОЕ РАЗМЕЩЕНИЕ ОБЪЕКТОВ В<br />

ПАМЯТИ. ОДНОНАПРАВЛЕННЫЙ НЕКОЛЬЦЕВОЙ<br />

ЛИНЕЙНЫЙ СПИСОК И ОПЕРАЦИИ С НИМ 148<br />

8.1. Понятие об однонаправленном линейном списке.<br />

Динамическое размещение объектов в памяти 148<br />

8.2. Инициализация линейного списка 152<br />

8.3. Добавление элемента в начало списка 163<br />

8.4. Добавление элемента в конец списка 163<br />

8.5. Создание ЛС с первым занесенным элементом<br />

в начале 164<br />

8.6. Создание ЛС с первым занесенным элементом<br />

в конце списка 164<br />

8.7. Удаление элемента из начала списка 165<br />

8.8. Удаление элемента из конца списка 166<br />

8.9. Разрушение ЛС с освобождением занятой им<br />

динамической памяти 166<br />

8.10. Печать содержимого ЛС 167<br />

8.11. Добавление элемента после каждого элемента ЛС,<br />

содержащего заданное значение 167<br />

8.12. Добавление элемента перед каждым элементом ЛС,<br />

содержащим заданное значение 168<br />

8.13. Удаление элемента после каждого элемента ЛС,<br />

содержащего заданное значение 168<br />

8.14. Удаление элемента перед каждым элементом ЛС,<br />

содержащим заданное значение 170<br />

8.15. Зачем нужен линейный список 171<br />

8.16. Упражнения для самопроверки 172<br />

9. ПРЕПРОЦЕССОР ЯЗЫКА СИ/С++ 173<br />

9.1. Директивы препроцессора 173<br />

9.2. Подстановка имен 173<br />

9.3. Включение файлов 177<br />

9.4. Условная компиляция 178<br />

9.5. Указания по работе с препроцессором 180<br />

10. РЕДКО ИСПОЛЬЗУЕМЫЕ СРЕДСТВА<br />

ЯЗЫКОВ СИ/С++ 182<br />

10.1. Объявление имени типа typedef 182<br />

10.2. Объекты перечислимого типа 183<br />

10.3. Объединения 186<br />

11. МОДЕЛИ ПАМЯТИ 189<br />

11.1. Адресация near, far и huge 190<br />

11.2. Стандартные модели памяти для<br />

шестнадцатибитной среды DOS 193<br />

445


11.3. Изменение размера указателей в<br />

стандартных моделях памяти для<br />

шестнадцатибитной среды DOS 194<br />

11.4. Макроопределения для работы с указателями 195<br />

11.5. Работа с памятью для среды WINDOWS 196<br />

12. НОВЫЕ ВОЗМОЖНОСТИ ЯЗЫКА C++, НЕ<br />

СВЯЗАННЫЕ С ОБЪЕКТНО-ОРИЕНТИРОВАННЫМ<br />

ПРОГРАММИРОВАНИЕМ 197<br />

12.1. Прототипы функций. Аргументы по умолчанию 198<br />

12.2. Доступ к глобальным переменным, скрытым<br />

локальными переменными с тем же именем 199<br />

12.3. Модификаторы const и volatile 200<br />

12.4. Ссылки 201<br />

12.5. Подставляемые функции 202<br />

12.6. Операции динамического распределения памяти 202<br />

12.7. Перегрузка функций 203<br />

12.8. Шаблоны функций 205<br />

12.9. Перегрузка операций 206<br />

13. ТЕХНОЛОГИЯ СОЗДАНИЯ ПРОГРАММ [5] 208<br />

13.1. Кодирование и документирование программы 208<br />

13.2. Проектирование и тестирование программы [5] 212<br />

13.2.1. Этап 1: постановка задачи 213<br />

13.2.2. Этап 2: разработка внутренних<br />

структур данных 214<br />

13.2.3. Этап 3: проектирование структуры<br />

программы и взаимодействия модулей 214<br />

13.2.4. Этап 4: структурное программирование 215<br />

13.2.5. Этап 5: нисходящее тестирование 216<br />

ЧАСТЬ 2. ПРИКЛАДНОЕ ПРОГРАММИРОВАНИЕ 218<br />

14. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ [5] 218<br />

14.1. Линейные списки 219<br />

14.2. Бинарные деревья 220<br />

14.3. Очереди и их частные разновидности 221<br />

14.4. Реализация динамических структур с<br />

помощью массивов 222<br />

15. СОРТИРОВКА 224<br />

15.1. Сортировка массивов 226<br />

15.2. Сортировка массива простым выбором 228<br />

15.3. Сортировка массива простыми включениями 248<br />

15.4. Сортировка массива простым обменом<br />

(метод "пузырька") 250<br />

15.5. Выводы по простым методам сортировки 251<br />

446


15.6. Сортировка массива сложным выбором<br />

(с помощью двоичного дерева) 252<br />

15.7. Сложная сортировка вставками (сортировка Шелла) ... 257<br />

15.8. Сложная сортировка обменом (сортировка Хоора) 259<br />

15.9. Сравнительные показатели производительности<br />

различных методов сортировки массивов 264<br />

16. ГРАФЫ. ТРАНСПОРТНАЯ ЗАДАЧА (ЗАДАЧА<br />

КОММИВОЯЖЕРА) 266<br />

16.1. Терминология 266<br />

16.2. Формы задания графа 268<br />

16.3. Почему для решения задачи подходит<br />

рекурсивный алгоритм 269<br />

16.4. Представление кратчайшего пути до каждой<br />

вершины 270<br />

16.5. Как найти минимальный путь 271<br />

16.5.1. Требуется ли полный перебор путей 271<br />

16.5.2. Организация перебора путей 271<br />

16.6. Пример поиска минимального пути в графе 285<br />

16.7. Печать информации о наилучшем пути 286<br />

17. ПОИСК 288<br />

17.1. Постановка задачи внутреннего поиска 288<br />

17.2. Последовательный поиск 290<br />

17.3. Логарифмический (бинарный) поиск 305<br />

17.4. Поиск с использованием перемешанной<br />

таблицы (хэш-таблицы) 308<br />

18. ОТВЕТЫ И РЕШЕНИЯ К УПРАЖНЕНИЯМ<br />

ДЛЯ САМОПРОВЕРКИ 312<br />

18.1. Для подраздела 2.4.4 312<br />

18.2. Для подраздела 3.8 314<br />

18.3. Для подраздела 3.9.3 315<br />

18.4. Для подраздела 3.9.6 317<br />

18.5. Для подраздела 4.12 319<br />

18.6. Для подраздела 6.9 321<br />

18.7. Для подраздела 8.16 321<br />

ПРИЛОЖЕНИЯ 327<br />

Приложение П. 1. Тесты и программные проекты.<br />

Варианты заданий 327<br />

П. 1.1. Тесты (контрольные работы) 327<br />

П. 1.1.1. Программирование на ПМ-ассемблере.<br />

Варианты тестов 327<br />

П.1.1.2. Ввод в языках Си/С++. Варианты тестов 330<br />

П. 1.1.3. Вывод в языках Си/С++. Варианты тестов 337<br />

447


п. 1.1.4. Простейшие ветвления. Варианты тестов 342<br />

П.1.1.5. Циклы. Варианты тестов 345<br />

П. 1.1.6. Структуры. Варианты тестов 350<br />

П.1.1. 7. Функции. Варианты тестов 359<br />

П. 1.1.8. Области действия определений. Варианты тестов 361<br />

П. 1.1.9. Массивы и указатели. Варианты тестов 373<br />

П. 1.1.10. Операции над линейным списком. Работа<br />

с динамической памятью. Варианты тестов 379<br />

П.1.1.11. Препроцессор, перечисления, функции с умалчиваемыми<br />

значениями аргументов, перегрузка функций, шаблоны<br />

функций, перегрузка операций. Варианты тестов 388<br />

П.1.2. Программные проекты 392<br />

П. 1.2.1. Программирование на ПМ-ассемблере. Варианты<br />

программных проектов 393<br />

П. 1.2.2. Структурное программирование средствами языков<br />

Си/С-^+ . Варианты программных проектов 395<br />

П. 1.2.3. Средства модульного программирования в языке C++.<br />

Варианты программных проектов 402<br />

П.1.3. Экзаменационное тестирование 405<br />

Приложение П.2. Создание программного проекта 408<br />

П.2.1. IDE MS Visual Studio C++ 6.0.<br />

Создание программного проекта 408<br />

П.2.2. IDE Borland C++ 3.1.<br />

Создание программного проекта 411<br />

Приложение П.З. Рекомендации по структуре<br />

однофайловой программы с одной функцией и пример<br />

оформления исходного текста 412<br />

Приложение П.4. Методика отладки программы 414<br />

П.4.1. Компиляция и компоновка программного<br />

проекта. Устранение синтаксических огиибок 415<br />

П.4.2. Отладка программного проекта. Устранение<br />

логических (алгоритмических) огиибок 417<br />

77.4.3. Тестирование программного проекта 420<br />

Приложение П. 5. Рекомендации по созданию<br />

многофайлового программного проекта с несколькими<br />

функциями и пример оформления исходного текста 421<br />

77.5.7. Спецификация программного проекта 421<br />

П. 5.2. Пример оформления исходного текста<br />

программы 428<br />

Приложение П.6. Примерная программа дисциплины<br />

"Программирование и основы алгоритмизации" 434<br />

Приложение П.7. Прилагаемый компакт-диск 441<br />

ЛИТЕРАТУРА 442<br />

448

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!