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, ¶m ) != 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 "¶m;<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