Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

449

Transcript of Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Page 1: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)
Page 2: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

В.Г. Давыдов

Программирование и основы

алгоритмизации

Рекомендовано УМО по образованию в области радиотехники, электроники и биомедицинской техники и автоматизации

в качестве учебного пособия для студентов высших учебных заведений, обучающихся по специальности

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

Москва «Высшая школа» 2003 '

Page 3: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

УДК 004.4 ББК 32.965

Д 13 Рецензенты:

кафедра «Систем управления и информатики» Санкт-Петербургского государст­венного института точной механики и оптики (технического университета) (зав. ка­федрой, д-р техн. наук, проф. В.В. Григорьев);

канд. техн. наук, доц. Санкт-Петербургского электротехнического университета «ЛЭТИ» СВ. Власенко

Давыдов, В.Г. Д 13 Программирование и основы алгоритмизации: Учеб. посо-

бие/В.Г. Давыдов. — М.: Высш. шк., 2003. — 447 е.: ил. ISBN 5-06-004432-7 Учебное пособие написано в соответствии с разработанной с участием автора

примерной программой курса «Программирование и основы алгоритмизации», утвер­жденной Министерством образования Российской Федерации для подготовки бакалав­ров и специалистов по направлениям 5502 и 6519 «Автоматизация и управление».

Его цель состоит в поэтапном формировании у студентов следующих слоев зна­ний и умений — знание основных понятий программирования (слой 1), знание базо­вого языка программирования C++ (слой 2) и умение решать задачи на ЭВМ (слой 3).

Для удобства преподавателей и студентов приведено по 20 вариантов контроль­ных заданий по основным разделам курса, заданий на выполнение программных проектов и приведены тестовые экзаменационные вопросы. Прилагаемый к учебно­му пособию компакт-диск содержит описание ПМ-ассемблера, его интегрированную среду программирования, полные тексты демонстрационных программ автора и др.

Для студентов высших учебных заведений, обучающихся по специальности 210100 — «Управление и информатика в технических системах».

УДК 004.4 ББК 32.965

Учебное издание Давыдов Владимир Григорьевич

ПРОГРАММИРОВАНИЕ И ОСНОВЫ АЛГОРИТМИЗАЦИИ Редактор ТВ. Рысева, Художник ТС. Лошаков

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

Печать офсетная. Объем 27,44 усл. печ. л., 27,94 усл. кр.-отг.. Тираж 3000 экз. Заказ № 3184. ФГУП «Издательство «Высшая школа», 127994, Москва, ГСП-4, Неглинная ул., 29/14.

Тел.: (095) 200-04-56. E-mail: [email protected] http://www.v-shkola.ru Отдел реализации: (095) 200-07-69, 200-59-39, факс: (095) 200-03-01. E-mail: [email protected]

Отдел «книга-почтой»: (095) 200-33-36. E-mail: [email protected] Отпечатано в ФГУП ордена «Знак Почета» Смоленской областной типографии им. В.И. Смирнова.

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

I S B N 5 - 0 6 - 0 0 4 4 3 2 - 7 © Ф Г У П «Издательство «Высшая школа», 2003

Оригинал-макет данного издания является собственностью издательства «Высшая школа», и его репродуцирование (воспроизведение) любым способом без согласия издатель­ства запрещается.

Page 4: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ПРЕДИСЛОВИЕ

Учебное пособие обеспечивает курс "Программирование и ос­новы алгоритмизации" и соответствует разработанной с участием автора примерной программе этого курса, рекомендованной Мини­стерством образования для подготовки бакалавров и специалистов по направлениям 5502 и 6519 "Автоматизация и управление". Посо­бие ориентировано на студентов, начинающих изучение курса про­граммирования "с нуля", но может быть полезным и преподавателям высших учебных заведений.

Учебное пособие состоит из введения, двух частей и приложе­ний. Во введении приводятся сведения о системах счисления, дается классификация языков программирования и их краткая характери­стика. В первой части пособия в качестве базового языка програм­мирования изучается язык C++ (за исключением его средств объект­но-ориентированного программирования и стандартных библиотек) и рассматривается технология программирования. Вторая часть посвящена решению классических задач прикладного программиро­вания, таких как сортировка массивов, транспортная задача (задача коммивояжера) и поиск в таблице. Кроме утилитарного значения, рассмотрение решения этих задач предметно знакомит с методоло­гией нисходящего иерархического проектирования программ, мо­дульного программирования, рекурсией, элементами теории графов и т.п. В прилоэюениях приведены следующие полезные сведения: • варианты тестов и программных проектов; • сведения о создании программного проекта в различных интег­

рированных средах программирования; • рекомендации по структуре программы и пример оформления ее

исходного текста; • методика отладки программы; • примерная программа дисциплины "Программирование и основы

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

Для удобства преподавателей и студентов пособие содержит по 20 вариантов контрольных заданий по основным разделам курса, заданий на выполнение программных проектов и пример тестовых экзаменационных вопросов. В пособие включены, снабженные отве­тами, упражнения для самопроверки, что позволяет использовать его и для самостоятельного изучения материала. По желанию, вме­сте с учебным пособием можно приобрести компакт-диск, содержа­щий описание ПМ-ассемблера, его интегрированную среду про­граммирования, полные тексты демонстрационных программ автора

Page 5: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

и др. в соответствии с возможностями учебного плана предусмат­

риваются следующие три траектории изучения материала: 1. Траектория, рассчитанная на 130 академических часов заня­

тий в рамках подготовки бакалавров (направление 5502 "Автомати­зация и управление") и специалистов (направление 6519). Это мак­симальная траектория, охватывающая весь изложенный в учебном пособии материал.

2. Минимальная траектория, рассчитанная на 65 академиче­ских часов занятий. В рамках такого варианта: • не изучается материал, изложенный в подразд. 1.2, 6.6, 6.8, в

разд. 7, 8 (кроме подразд. 8.1), 10, 11, 12 (кроме подразд. 12.1-12.6), 15 (кроме подразд. 15.8 и 15.9), 16 и 17 (кроме подразд. 17.4);

• не выполняются программные проекты, описанные в приложени­ях П.1.2.1 и П.1.2.3.

3. Промежуточная траектория, рассчитанная на 100 академи­ческих часов занятий. В рамках такой траектории, по усмотрению преподавателя, не изучается только часть материала, пропущенного в предыдущей траектории.

Ваши отзывы об учебном пособии, конструктивные замечания и критику направляйте по адресу

[email protected].

Автор

Page 6: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1. ВВЕДЕНИЕ

Электронная вычислительная машина (ЭВМ) представляет со­бой устройство, способное хранить и выполнять программы. Про­граммы - это алгоритмы и структуры данных. Известна следующая формула Никлауса Вирта - разработчика языка Паскаль:

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

Структуры данных представляют исходные данные, промежу­точные и конечные результаты.

Алгоритмы - указания о том, какие действия и в какой после­довательности необходимо применять к данным для получения тре­буемого конечного результата.

1.1. Системы счисления

в ЭВМ программы представлены с использованием двоичной системы счисления. Причина этого кроется в следующем.

Основным элементом ЭВМ является электронный ключ, имею­щий два состояния - "включено" или "выключено". Это хорошо соответствует двоичной системе счисления, в которой используются две цифры: "О" и " 1 " , обозначающие один двоичный разряд - бит.

Рассмотрим системы счисления более подробно и, в частности, системы счисления, применяемые в ЭВМ.

Система счисления - совокупность приемов и правил для запи­си чисел цифровыми знаками. Различают непозиционные и позици­онные системы счисления.

В непозиционной системе счисления значение знака (символа) не зависит от его положения в числе. Пример - римская система счисления.

Позиционная система счисления - система, в которой значение цифры числа определяется ее положением (позицией) в числе. Лю­бая позиционная система счисления характеризуется основанием. Основание "^" позиционной системы счисления - количество цифр, используемых при изображении числа в данной системе.

Для позиционной системы счисления справедливо равенство, \idi3b\bdiQMOQ развернутой формой записи числа:

Page 7: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

где A^^j^ - произвольное число, записанное в системе счисления с ос­нованием 'V"; / - коэффициенты ряда или значения разрядов числа (цифры системы счисления); п + 1 т - количество целых и дробных разрядов числа.

На практике, для краткости, используют сокращенную запись чисел:

Ая) = ^п<^п-х • ••а,а^,а_,а_2...а_,„ ( 2 )

Пример. Для десятичного числа 349,17 развернутая форма за­писи

3-10'+4-10'+9-10VM0-'+7 10-', а сокращенная

349.17 В вычислительной технике, в основном, используются двоич­

ная, восьмеричная и илестнадцатеричная системы счисления (вось­меричная и шестнадцатеричная - для более компактной записи дво­ичных кодов). В нашем обиходе используется десятичная система счисления. По этой причине необходимо уметь переводить числа из систем счисления с основаниями, равными целым степеням 2, в де­сятичную систему счисления и наоборот.

В двоичной системе счисления для записи числа в сокращен­ной форме используются цифры О и 1, в восьмеричной - О, 1,2, ..., 7, в десятичной - О, 1,2, ..., 9 и в шестнадцатеричной - О, 1,2, ..., 9, А, В, С, D, Е, F.

Перевод чисел из двоичной, восьмеричной или илестнадцате-ричной систем счисления в десятичную систему легко выполняется с помощью развернутой формы записи числа (1). Например, двоич­ное число

42) =1101,001(2) соответствует десятичному числу

^ = Ь2'+1-2'ч-1-2'+1-2-' =13,125 Аналогично выполняется перевод из восьмеричной и шестна-

дца.теричной систем в десятичную систему счисления. Рекомендуем самостоятельно составить соответствующие примеры перевода в де­сятичную систему. Обратите внимание, что при выполнении пере­вода в десятичную систему вычисления ведутся в десятичной сис­теме.

Перевод чисел из десятичной системы в двоичную систему счисления поясним примером.

Пусть требуется перевести десятичное число 13,125 в двоич­ную систему счисления. Целая и дробная части десятичного числа переводятся по разному: целая - делением на основание q — 2^ г.

Page 8: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

дробная - умножением на q = 2. Vi деление, и умножение выполня­ются в десятичной системе счисления.

При делении 13 на 2 получаем частное 6 и остаток 1. При де­лении 6 на 2 получаем частное 3 и остаток 0. При делении 3 на 2 получаем остаток 1 и частное 1. Поскольку частное меньше двух, то на этом деление заканчивается.

При умножении 0,125 на два получаем 0,250 {целая часть произведения 0). При умножении 0,250 на 2 получаем 0,500 {целая часть произведения 0). При умножении 0,500 на 2 получаем 1,000 {целая часть произведения 7). Поскольку дробная часть теперь ну­левая, то умножение на этом заканчивается.

Остатки от деления, взятые в обратном порядке, и последнее частное дают целую часть числа в двоичной системе счисления, а целые части от умножения, взятые в прямом порядке, дают дробную часть числа. В результате получаем число в двоичной системе счисления

1101,001(2)

Обратите внимание, что перевод дробной части числа завер­шается при получении после точки всех нулей или при получении требуемого числа разрядов дробной части (требуемой точности).

Особенно просто выполняется перевод чисел из двоичной сис­темы счисления в системы счисления с основанием, равным степе­ням 2, и наоборот. При указанном переводе удобно пользоваться табл. 1.

Табл. 1. Перевод чисел из двоичной системы в восьмеричную или шестнадцатеричную системы счисления и наоборот

Восьмеричная цифра 0 1 2 3 4 5 6 7

Двоичная триада 000 001 010 011 100 101 по 111

Шестнадцатеричная цифра 0 1 2 3 4 5 6 7 8 9 А В С D Е F

Двоичная тетрада 0000 0001 0010 ООН 0100 0101 оно 0111 1000 1001 1010 1011 1100 1101 1110 1111

Page 9: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

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

1. 42)=1 101,1 1001(,) ^ 8 ) = ? Для перевода двоичного числа в восьмеричную систему снача­

ла двигаемся от точки влево, выделяя д в о и ч н ы е триады. Если ос­тавшаяся последняя группа битов не образует триады, то д о п о л н я е м ее до т р и а д ы нулями слева. Затем двигаемся от точки вправо , также выделяя триады. П о с л е д н ю ю , н е п о л н у ю группу битов , д о п о л н я е м до триады добавлением нулей справа. В результате двоичное число за­писывается в виде

001 101 , 1 1 0 0 1 0 В соответствии с табл . 1 з аменяем полученные триады восьме­

р и ч н ы м и ц и ф р а м и и получаем

2. 42)=И01Д 1001(2) 4 .6)=? П е р е в о д числа выполняется аналогично предыдущему , но вме­

сто триад выделяются д в о и ч н ы е т е т р а д ы : 1101 , 1100 1000

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

^ 1 6 ) = - ^ ' ^ ^ ( 1 6 )

3 . 4,6)-^^1,^^06) 4 2 ) = ? в соответствии с табл . 1 з аменяем шестнадцатеричные ц и ф р ы

тетрадами 1010 1111 0001 , 1 1 1 1 1110

и получаем ^2) =101011110001,1111111(2)

4. 48)=710,526(,,) 4 2 ) = ? Этот пример предлагаем выполнить самостоятельно . В о з м о ж е н быстрый и простой перевод чисел из восьмеричной

системы в шестнадцатеричную систему счисления и обратно . Такой перевод м о ж н о выполнить в два этапа - сначала из исходной систе­мы счисления в д в о и ч н у ю систему и затем из двоичной в новую сис­тему счисления . П р и м е р ы такого рода также предлагаем в ы п о л н и т ь самостоятельно .

1.2. Классификация языков программирования и их краткая характеристика

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

Page 10: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1. Машинно-зависимые языки, которые можно применять на одной ЭВМ или на ограниченном подмножестве машин с одинако­вой архитектурой.

2. Машинно-независимые языки - их можно использовать на любой ЭВМ. Языки этой группы называют универсальными языка­ми.

Машинно-зависимые языки^ в зависимости от их близости к машинным языкам, делятся на три группы: • машинные языки (языки нулевого уровня); • ассемблерные языки (языки первого уровня или языки типа 1:1,

последнее означает, что одна ассемблерная команда после транс­ляции порождает ровно одну машинную команду);

• макроассемблеры (языки второго уровня или языки типа \\п). Аналогично, маилинно-независимые языки включают сле­

дующие группы языков: • Процедурные языки (третий уровень): Си, C+-I-, Паскаль,

ФОРТРАН, БЭЙСИК и др. Процедурные языки требуют детальной разработки алгоритма решения и, по существу, являются языками для записи алгоритмов решения задач.

• Проблемные языки (четвертый уровень) или языки типа "заполни бланк". Это языки описания задач, специализированные языки. Используя подобный язык программирования, пользователь со­общает только, какую задачу надо решить и с какими данными. Как решить задачу - "знает" язык. В качестве примера проблемно­го языка можно назвать язык ПРОСПО, разработанный фирмой IBM для программирования систем управления производствен­ными процессами.

• Универсальные языки (пятый уровень): ПЛ/1, АЛГОЛ-68, Ада и др. При создании универсальных языков в их состав включили все лучшее, что имелось на момент создания в процедурных языках.

1.2.1. Машинные языки

Рассмотрим машинные языки на примере простого машинного языка (ПМ), разработанного для студентов кафедры "Автоматика и вычислительная техника" Санкт-Петербургского государственного политехнического университета проф. Лекаревым М.Ф. Язык ПМ подробно рассмотрен в [1] и его описание имеется на прилагаемом компакт-диске. Здесь, если вместе с учебным пособием Вы приоб­рели еще и компакт-диск, рекомендуем рассмотреть имеющийся в [1] материал, включая структурную организацию и функционирова­ние простой ЭВМ.

Page 11: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Пример. З апрограммируем на м а ш и н н о м языке П М решение следующей задачи: ввести исходные данные

А,В,С,

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

Е=АВ + С/\,5 Система команд м а ш и н ы П М содержит несколько десятков

различных одноадресных команду из которых в табл. 2 приводятся лишь те, к о т о р ы е будут необходимы для решения указанной задачи.

Табл. 2. Таблица кодов операций (КОП) Выполняемое действие (обозначение команды) КОП

Передача слова из ОЗУ в регистр А арифметико-логического устройства АЛУ (ЧТЕНИЕ)

01

02 03

Передача слова из регистра А АЛУ в ОЗУ (ЗАПИСЬ) Слолсение содержимого регистра А АЛУ с операндом, заданным в коман-де. Результат помещается в регистр А АЛУ (+)

04 05 06 07 08

Вычитание ( - ). Операция выполняется аналогично сложению Умножение ( * ).Операция выполняется аналогично сложению Деление ( / ).Операция выполняется аналогично сложению Ввод слова с устройства ввода в ОЗУ (ВВОД) Вывод слова из ОЗУ в устройство вывода (ВЫВОД)

При составлении программы решения задачи на м а ш и н н о м языке требуется р е ш и т ь следующие вопросы:

1. Распределить память для данных , обрабатываемых про­граммой, например , в начале памяти (табл. 3)

Имя переменной

А В С

"1,5" Е

Табл. 3. Таблица символов (ТС) Адрес в ОЗУ (для удобства используется десятичный адрес

вместо двоичного адреса) 00000 00001 00002 00003 00004

2. Распределить память для команд программы. Н а п р и м е р , ус­ловимся размещать программу, начиная с адреса 01000 (для удобст­ва используется десятичный адрес вместо двоичного адреса) .

3. Составить собственно программу, т.е. записать коды ма­шинных команд с указанием места их размещения в ОЗУ. Напоми­наем, что реально в Э В М используются двоичные коды команд и

10

Page 12: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

адресов, но мы для собственного удобства воспользуемся и здесь десятичными кодами. Машинная программа приведена в табл. 4.

Проанализировав содержимое табл. 3, оценим возможности и особенности программирования на машинных языках.

В оперативном запоминающем устройстве (ОЗУ) между ме­стом размещения данных и команд программы осталась "дырка" -ячейки с адресами 00005...00999, которую для полного использова­ния памяти нужно ликвидировать. Однако заранее оценить размер памяти, необходимой для размещения данных и команд, нельзя или, по крайней мере, очень трудно.

1 Адрес команды

01000 01001 01002 01003 01004 01005 01006

01007

01008

01009

01010

01011

01012

01013

КОП

07 08 07 08 07 08 01

06

02

01

05

03

02

08

Табл. Адрес

операнда 00000 00000 00001 00001 00002 00002 00002

00003

00004

00000

00001

00004

00004

00004

4. Машинная программа Действие команды

УВв -> ОЗУ по адресу 000000 (А) Содержимое ячейки ОЗУ с адресом 00000 (А) - ^ УВыв УВв -> ОЗУ по адресу 000001 (В) Содержимое ячейки ОЗУ с адресом 00001 (В) —> УВыв УВв -> ОЗУ по адресу 000002 (С) Содержимое ячейки ОЗУ с адресом 00002 (С) —> УВыв Содержимое ячейки ОЗУ с адресом 00002 (С) -^ регистр А АЛУ Содержимое регистра А АЛУ (С) разделить на содержимое ячейки ОЗУ с адресом 00003 (1.5) и результат поместить в регистр А АЛУ Содержимое регистра А АЛУ (С/1.5) - ^ ОЗУ по адресу 00004 (Е) Содержимое ячейки ОЗУ с адресом 00000 (А) —> регистр А АЛУ Содержимое регистра А АЛУ (А) умножить на содержимое ячейки ОЗУ с адресом 00001 (В) и результат поместить в регистр А АЛУ Содержимое регистра А АЛУ (А*В) сложить с содержимым ячейки ОЗУ с адресом 00004 (С/1.5) и результат поместить в регистр А АЛУ Содержимое регистра А АЛУ (А*В+С/1.5) -^ ОЗУ по адресу 00004 (Е) Содержимое ячейки ОЗУ с адресом 00004 (А*В+С/1.5) -^ УВыв 1

При внесении изменений в программу в процессе ее отладки эти размеры меняются. Поэтому действия, указанные выше в пп. 1 -3, приходится повторять.

Другие недостатки программирования с использованием ма­шинного языка заключаются в следующем.

1. Действия, указанные в пп. 1 - 2 нетворческие, поэтому при их выполнении программист делает много ошибок. Вместе с тем их можно автоматизировать с помош;ью ЭВМ.

11

Page 13: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

2. Реальные системы команд ЭВМ громоздки (сотни различ­ных команд), а использование двоичных кодов команд программы трудоемко и ненаглядно. Машинную программу трудно восприни­мать (см. табл. 4 без левого и правого столбцов).

Из-за отмеченных недостатков машинные программы давно перестали писать. Вместе с тем отметим достоинство программ, на­писанных с использованием машинных языков, - они имеют наи­большее быстродействие и требуют минимальных затрат памяти. Но это может быть достигнуто только ценой больших затрат времени квалифицированного программиста.

Многие из названных выше недостатков машинных программ устраняются при программировании на ассемблерных языках.

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

Ассемблерные языки отличаются от соответствующих машин­ных языков следующими особенностями:

1. Вместо двоичных, восьмеричных, шестнадцатеричных и де­сятичных записей кодов операций и адресов используется их на­глядная символическая запись. Перевод символических записей в двоичные коды, воспринимаемые машиной, выполняется автомати­чески, с помощью ЭВМ.

2. Размещение в ОЗУ данных и команд также автоматически выполняет ЭВМ, причем без "дырок". Действия, указанные в пп. 1 -2 выполняет специальная системная программа-переводчик, назы­ваемая транслятором (компилятором).

3. Оптимальность программы по быстродействию и занятой памяти практически сохраняется (проигрыш не более 5... 10 %). Это делает ассемблерные языки практически основными для системных программистов.

Пример программы на языке ПМ-ассемблер, подобный приме­ру из подразд. i .2.1, приведен в [1] в разд. 7 (см. прилагаемый ком­пакт-диск).

Перевод (перекодировка) ассемблерной программы на язык машинных команд производится транслятором по следующей схеме (рис. 1). Трансляция выполняется в два прохода.

Проход 1. Составляется таблица символов (ТС). Проход 2. Выполняется перекодировка команд в двоичные ко­

ды (используются ТК и ТС).

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

Макроассемблерные языки сейчас являются самыми мощными. Объясняется это тем, что макроассемблер обладает всеми возмож-

12

Page 14: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ностями ассемблера и, дополнительно, мощным аппаратом макро­вызовов и макроопределений. Поясним возможности указанного ап­парата примером.

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

R^{X^+Y^)/{X'Y)

Текст программы на ассемблере

Таблица кодов операций (ТК)

Таблица символов (ТС)

Текст программы на машинном

языке Рис. 1. Схема трансляции в два прохода

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

[Г к 2 10 МЕТКА

11 20 ОПЕРАЦИЯ

к Стандартное начало К придумывает прог

MACRO ЧТЕНИЕ * ЗАПИСЬ ЧТЕНИЕ * + / / ЗАПИСЬ MEND

21 ОПЕРАНД , MACRO -раммист SUB(X, Y, X X Х2 Y Y Х2 X Y R

40

ключевое

R)

КОММЕНТАРИЙ слоъо, SUB(X, Y, R) -

Тело

макроопределения Стандартный конец

Пусть, например, необходимо вычислить С = (А^+В')/(А-В) и G-={E^+F^)/(E-F)

Это можно сделать в программе с помощью однострочных макровызовов:

13

Page 15: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

2 10 МЕТКА

11 20 ОПЕРАЦИЯ

21 40 ОПЕРАНД

41 КОММЕНТАРИЙ

S U B ( A , В , С) S U B ( E , F , G)

Здесь X, Y, R - формальные, а А, В, С и Е, F, G - фактические параметры. Вызов "SUB(A, В, С) " заменяется фактически модифици­рованным телом макроопределения, в котором формальные пара­метры X, Y, R заменены соответственно фактическими параметрами Л, В, С.

Макроассемблер - основной язык системных программистов. В частности, программист может, при необходимости, разработать свой набор макроопределений - это эквивалентно разработке в рам­ках макроассемблера "своего", специализированного языка с опера­торами - макровызовами. При этом сохраняется высокая эффектив­ность программ, написанных на макроассемблере. Проигрыш по бы­стродействию и занятой памяти не превышает 10... 15 %.

1.2.4. Машинно-независимые языки. Процедурные и универсальные языки

Чтобы сопоставить машинно-независимые языки с процедур­ными и универсальными языками, запишем тот же пример на проце­дурных и универсальных языках высокого уровня:

/ '*' пример на языке Си, Вычисляем D := А * В + С / 2 . 0 * / ^include <stdio.h> ±nt main ( void ) {

float Л, B, C, D; scanf( " %f %f %f", &A, printf( " %f %f %f". A, D=A*B+C/2.0; printf( "\n %f", D ) ; return 0;

&B, &C ) ; B, С ) ;

PROGRAM PEMPAS ( INPUT, OUTPUT ) ; { Пример на языке ПАСКАЛЬ. Вычисляем D : = A * B + C / 2 . 0 } | VAR

А , Б , С , D: REAL; BEGIN { Для PRMPAS

READ( А, В, С ) ; WRITE( А, В, С ) ; D := А ^ В + С / 2.0; WRITE( D )

END. { Для PRMPAS

14

Page 16: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

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

REJLL А, В, С , D READ *г ^f Br С PRINT *r А, В, С D = А ^ В + С / 2.0 PRINT *, D STOP

END

100 REM Пример на БЭИСИКе. Вычисляем D : = A * B + C / 2 . 0 110 INPUT А, В, С 120 PRINT А, В, С 130 PRINT А * В + С / 2 . 0 140 END

\ / ^ Пример на языке PL/1. Вычисляем D := А * В + С / 2.0 */ PRMPL1: PROCEDXmE OPTIONS ( MAIN ) ;

DECLARE ( A, Br Cr D ) REAL FLOAT DEC ( 6 ) ; GET LIST( Ar Br С ) ; PUT LIST( Ar Br С ) ; D = A ^ В + С / 2.0 PUT LIST ( D ) ;

END PRMPLl;

Из приведенных примеров видны удобства работы на машин­но-независимых языках. По этой причине ясно, что языки програм­мирования высокого уровня - основные языки проблемных програм­мистов. Эффективность программ, написанных на языках высокого уровня, понижена в 2 - 4 раза. Для программ на языках Си/С++ по­нижение эффективности составляет лишь 1,5 раза и объясняется это тем, что этот язык включает в себя многие средства ассемблерных языков.

Page 17: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ЧАСТЬ 1. БАЗОВЫЙ ЯЗЫК ПРОГРАММИРОВАНИЯ

2. ЯЗЫК ПРОГРАММИРОВАНИЯ ВЫСОКОГО УРОВНЯ C++

Язык программирования Си был разработан в 1972 году Д. Ритчи в фирме Bell Laboratories (США) как универсальный язык системного программирования в связи с созданием популярной опе­рационной системы UNIX. Эта операционная система (ОС) была, в основном, написана на языке Си, что обеспечило ее переносимость на любые ЭВМ. Действительно, для каждой архитектуры ЭВМ дос­таточно написать только транслятор с языка Си и с его использова­нием ОС UNIX легко переносится на новую ЭВМ, принадлежащую соответствующей архитектурной линии.

При разработке языка Си был принят компромисс мелсду низ­ким уровнем языка ассемблера и высоким уровнем таких языков, как Паскаль, ФОРТРАН, БЭЙСИК, ПЛ/1 и др. Многие и многие опера­ции языка Си (манипулирования строками, ввода-вывода и др.) вы­несены за пределы языка и реализованы как подпрограммы, которые могут быть вызваны из Си-программ. Такое решение обеспечило высокую эффективность языка Си (высокое быстродействие и ма­лые затраты памяти).

Язык Си - современный язык, включающий управляющие кон­струкции, рекомендуемые теоретическим и практическим програм­мированием. Такими конструкциями являются следование, ветвле­ние, циклы; модули, называемые функциями. Язык Си побуждает программиста использовать в своей работе нисходящее проектиро­вание, структурное программирование и пошаговую разработку мо­дулей. В дополнение к сказанному, язык 0++, являющийся даль­нейшим развитием языка Си, содержит такой важный инструмент, как средства объектно-ориентированного программирования (ООП).

Таким образом, если есть желание работать в среде програм­мотехники, то один из первых вопросов, на который нужно ответить "Да" - это вопрос "Умеете ли Вы программировать на языках Си/С++?".

16

Page 18: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

2.1. Введение. Структурное и модульное программирование

Суть структурного программирования иллюстрирует рис. 2. Необходимо подробно проанализировать этот графический кон­спект-рисунок, потому, что, во-первых, он первый, и, во-вторых, на нем изложена суть той технологии программирования, которой мы в дальнейшем будем пользоваться - это технология структурного программ ирован ия.

Что такое АЛГОРИТМ? Аль Хорезми

Структурное программирование

Ветвления Следование if - then - else switch

Как его записать?

Дейкстра

Циклы while do - while for

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

Если посмотреть на верхнюю часть графического конспекта-рисунка, то внимание сразу привлекает слово АЛГОРИТМ.

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

АЛГОРИТМ - одно из основных понятий современной матема­тики и программирования. Само слово происходит от имени узбек­ского математика IX в. Мухаммеда, уроженца Хорезма (по-арабски "Аль Хорезми"). Его работы по арифметике и алгебре были переве­дены на латинский язык в XII в. и оказали большое влияние на раз­витие математики в Европе. Сформулированные ученым правила выполнения четырех арифметических действий получили название "алгоризм", дальнейшая трансформация — "алгоритмус", "алгорифм" и "алгоритм". Интуитивное понятие алгоритма можно выразить сле­дующим образом.

Алгоритм - строгая и четкая система правил, которая опреде­ляет последовательность действий над некоторыми объектами и по-

17

Page 19: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

еле конечного числа шагов приводит к достижению поставленной цели.

Примерами алгоритмов могут являться: • рецепты приготовления блюд; • пояснения, "как пройти", "как проехать"; • правило умножения целых чисел "столбиком" и т.п.

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

Способы записи алгоритмов. Один и тот же алгоритм может быть записан (описан) различными способами. Наибольшее распро­странение в практике программирования получили [2]: • текстуальная (словесная) запись алгоритма; • запись алгоритма с помощью схемы (разновидность визуального

способа - формализма); • запись алгоритма с использованием диаграммы Нэсси-

Шнейдермана (разновидность визуального формализма); • запись алгоритма с использованием Р-схемы (разновидность ви­

зуального формализма); • запись алгоритма с помощью псевдокода^ • запись алгоритма в терминах языка программирования.

Эти способы записи алгоритмов не исключают друг друга, а используются последовательно, на различных этапах решения зада­чи. Только три способа записи алгоритма — с использованием схемы, диаграммы Нэсси-Шнейдермана и Р-схемы - являются альтернатив­ными на соответствующем этапе решения задачи.

Дадим краткую характеристику перечисленных выше способов записи алгоритмов. С этой целью один и тот же алгоритм запишем различными способами.

Текстуальная запись алгоритма. Имеется следующий алго­ритм, записанный в текстуальной форме:

1. Начало алгоритма. 2. Выполнить некоторое действие (оператор) s i . 3. Если выполнено условие "Усл1", то выполнить операторы

s2, s3 и перети к п. 4. Иначе - перейти к пп. 3.1. 3.1. Пока выполняется условие "Усл2", выполнять пп. 3.2

и 3.3. Иначе - перейти к п. 4. 3.2. Если выполнено условие "УслЗ", то выполнить опера­

тор s4, иначе — выполнить оператор s5. 3.3. Выполнить оператор s6.

4. Пока выполняется условие "Усл4", выполнять оператор s7. Иначе — перейти к п. 5.

5. Выполнить оператор s8. 6. Конец алгоритма.

18

Page 20: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Запись алгоритма с помощью схемы (ГОСТ 19.701 - 90, со­вместим с меэюдународным стандартом). Запись того же самого алгоритма в виде схемы иллюстрирует рис. 3.

В случае простой схемы сравнительно несложно обеспечить ее бесспорную наглядность. Но по мере роста сложности отображаемо­го фрагмента алгоритма (программы), его логическая структура на­чинает "тонуть" в "клубке спагетти", в который постепенно превра­щается схема алгоритма [2]. Поэтому практика использования схем алгоритмов уже давно считается устаревшей и программисты при­меняют ее как инструмент разработки только эпизодически. Там, где стандарты организации требуют наличия схем алгоритмов, они поч­ти неизменно рисуются после написания программы.

Запись алгоритма с помощью диаграммы Нэсси-Шнейдермана (рис. 4). Диаграмма Нэсси-Шнейдермана была предложена в сочета­нии со структурным программированием как средство борьбы с проблемой "клубка спагетти", присущей схемам алгоритмов. Эта диаграмма, бесспорно, заменяет одномерное представление вложен­ных операторов двумерным (см. рис. 4). Тем не менее, по мере роста сложности программного кода появляются проблемы отображения, поскольку элементы диаграммы быстро становятся все меньше и меньше. На рис. 4 приведена диаграмма Нэсси-Шнейдермана для того же самого алгоритма, что и ранее.

Запись алгоритма с помощью Р-схемы (рис. 5). Используемая при этом Р-технология программирования разработана в Институте Кибернетики АН УССР. Согласно Р-технологии программа должна быть представлена в форме нагруженного по дугам структурного графа (Р-схемы). Р-схема состоит из подграфов, каждый из которых имеет один вход и один выход. Вершины графа называются состоя­ниями Р-схемы. Переходы из одного состояния в другое представ­лены помеченными дугами, причем каждая дуга может быть поме­чена условием перехода и действием, выполняемым в процессе вы­полнения перехода. На рис. 5 приведена Р-схема для того же самого алгоритма.

Запись алгоритма с помощью псевдокода. Довольно широкое распространение получил еще один способ записи алгоритма с по­мощью псевдокода. Этот способ" записи является промежуточным и используется перед записью алгоритма в терминах выбранного язы­ка программирования. Псевдокод представляет собой удобный для практики промежуточный язык. Это и не естественный язык, и не язык программирования, а их симбиоз. Псевдокод похож на язык программирования тем, что может использовать его некоторые ин­струкции, но, с другой стороны, допускает и словесную, и формуль­ную записи там, где сразу сложно воспользоваться языком програм­мирования.

19

Page 21: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

а) Точка входа или выхода

Операционный блок (функциональный узел)

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

Циклическая конструкция

б) Поток управления

Цикл «Уcл4>^ Не «Усл4»

s7

Цикл «Усл4» \ /

s8

f Конец J

( )

ГЦикл «Усл2» Не «Усл2»

< ^ У с л З ^

s4

1 s6

1 Цикл «Усл2»

Нет

Да

s5

Рис. 3. Схема алгоритма: а) основные обозначения; б) пример использования

Page 22: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

а) Программа

Простая конструкция

Условная конструкция

Циклическая конструкция

Заголовок программы Тело программы

Оператор

Условие Тело цикла

б)

т

Усл4

s2

S3

Пример программы s1

Усл1

Усл2

s7

s8

т \ s4

F

УслЗ / ^

s5

s6

Рис. 4. Диаграмма Нэсси-Шнейдермана: а) графические элементы; б) пример использования

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

Применительно к решению задачи на ЭВМ, можно сформули­ровать, что алгоритм, или программа для вычислительной машины состоит их двух важных разделов: описания действий, которые не­обходимо выполнить, и описания данных, с которыми оперируют упомянутые действия. Действия описываются с помощью операто­ров, а данные - с помощью определений или объявлений.

В 1965 г. профессор Эйндховенского университета Дейкстра (Нидерланды) начал пропагандировать стиль программирования, получивший название "программирование без оператора goto (без­условного перехода)", первоначально принятый большинством про­граммистов негативно. Однако, в течение нескольких последующих лет этот же стиль, получивший название структурного программи-

21

Page 23: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

рования, нашел широкое применение. Здесь также будем придержи­ваться этого подхода. По мере изучения программирования будут рассмотрены положительные моменты структурного подхода к про­граммированию, а также основные принципы и требования струк­турного программирования.

а) Состояние Р-схемы

Совмещение двух состояний в одно (цикл)

Дуга Р-схемы Условие

Действие

б)

Усл1 склгыэ Усл4

s7

Рис. 5. Р-схема: а) графические элементы; б) пример использования

Основными управляющими конструкциями структурного про­граммирования являются: • СЛЕДОВАНИЕ - если в записи алгоритма (программы) подряд

написаны несколько действий (операторов) друг за другом, то они будут выполняться последовательно в таком же порядке.

• ЕСЛИ-ТО-ИНАЧЕ - условная конструкция, определяющая раз­ветвление в порядке выполнения действий (операторов). Дослов­ный перевод этой конструкции if-then-els е.

• ЦИКЛЫ. В структурном программировании предусмотрены цик­лические конструкции трех видов:

1. Цикл с предусловием ПОКА-ДЕЛАЙ: пока истинно некото­рое условие, делай то-то и то-то (дословный английский перевод этой конструкции while).

2. Цикл с постусловием ПОВТОРЯЙ-ПОКА (do-while). Отли­чается от предыдущего цикла тем, что тело цикла повторяется не менее одного раза.

22

Page 24: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

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

Этих трех элементарных конструкций, называемых базовыми конструкциями структурного программирования, достаточно, чтобы управлять порядком выполнения действий в любом алгоритме. От­метим также, что каждая из названных конструкций имеет только один вход и один выход, что делает их использование очень удоб­ным.

Ветвление (условная конструкция) 1) Общий случай (рис. 6 <з)

"ЕСЛИ" УСЛОВИЕ "ТО"ВЕТВЬ'ТО "ИНАЧЕ" ВЕТВЬ-ИНАЧЕ "ВСЕ"

а) («ЕСЛИ»)

(«ИНАЧЕ») Нет -4-

ВЕТВЬ-ИНАЧЕ

б)

ВЕТВЬ-ТО

(«ВСЕ»)

... («ЕСЛИ»)

(«ИНАЧЕ») Нет

ВЕТВЬ-ТО

... («ВСЕ») Рис. 6. Ветвление (условная конструкция):

а) общий случай; б) частный случай

/ / C++ реализация ±f( А > В )

D = Е; // ВЕТВЬ-ТО

23

Page 25: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / . . . else {

С = F/ // ВЕТВЬ-ИНАЧЕ / / . . . }

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

"ЕСЛИ" УСЛОВИЕ "ТО" ВЕТВЬ-ТО "ВСЕ"

// C+-h реализация ±f( А > В ) {

D = Е; // ВЕТВЬ-ТО / / . . . ;

Циклы 1) С предусловием (рис. 7, 8). Пример

SUMMA = Y,I

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

// C++: ЦИКЛ С ПРЕДУСЛОВИЕМ SUMMA = 0 / 1 = 1 / // ПОДГОТОВКА ЦИКЛА while ( I <= 20 ) {

SUMMA += I/ // Эквивалентно SUMMA = SUMMA + I; I += 1/

}

// C++: ЦИКЛ FOR (CM, РИС, 8) for( 1=1/ I <= 20/ I++ ) // I++ эквивалентно 1 = 1 + 1 / {

SUMMA += I/ }

2) С постусловием (рис. 9). 20

Пример: SUMMA = ^I

"ПОВТОРЯЙ" ТЕЛО-ЦИКЛА "ПОКА" УСЛОВИЕ

// C++: ЦИКЛ С ПОСТУСЛОВИЕМ SUMMA = 0 / 1 = 1 / // ПОДГОТОВКА ЦИКЛА

24

Page 26: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

do {

}

SUMMA -h= I; I += 1;

while( I <= 20 ) ;

// Эквивалентно SUMMA = SUMMA + J ,

SUMMA = 1 + 2 + ... + 20

SUMMA:=0; I := 1;

SUMMA := SUMMA + I; I : = ! + 1;

ТЕЛО-ЦИКЛА («ДЕЛАТЬ»)

(«ПОКА»)

УСЛОВИЕ

Нет («ВСЕ»)

Вариант по ГОСТ

SUMMA:=0; I := 1;

Цикл «I» I > 2 0

SUMMA := S U M M A + 1 ; I := 1 + 1;

Цикл «I»

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

Наряду с методологией структурного программирования, хо­роший стиль программирования рекомендует также обязательное использование методологии модульного программирования. Мо­дульное программирование предполагает последовательную деком­позицию (разбиение) исходной задачи на функционально закончен-

25

Page 27: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ные подзадачи, оформленные в виде отдельных модулей, которые в языках Си/С-ь+ называются функциями.

Вариант по ГОСТ

SUMMA := 0;

Цикл «I» I > 2 0

SUMMA := SUMMA + I

Цикл «I»

1, 20, +1 - начальное, конечное значения "1" и шаг изменения "1"

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

Для определения рационального размера функции и количест­ва ее параметров можно использовать "правило семь ± два". Смысл этого правила заключается в том, что человек хорошо вос­принимает до семи некоторых элементов - параметров функции, операторов языка программирования и т.п. Таким образом, при хо­рошо выполненной декомпозиции размер функции не превосходит обычно 2 5 - 8 1 строк текста, а количество параметров не превышает 5 - 9 . Размер функции 2 5 - 8 1 строк текста получается, если в ее бло­ке содержится не более 5 - 9 элементарных конструкций, каждая из которых занимает не более 5 - 9 строк. Модули (функции Си) можно хранить в отдельных файлах, отлаживать параллельно, что способ­ствует сокращению сроков проектирования программных проектов и привлечению к работе над проектами коллективов программистов.

Модульное программирование, получившее также название нисходящего программирования, для сложных программных проек­тов может носить иерархический характер, т.е. полученные вначале программные модули, в свою очередь, при необходимости, также декомпозируются, с тем, чтобы достичь указанных выше показате­лей, соответствующих хорошему стилю программирования.

26

Page 28: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант по ГОСТ

SUMMA= 1 + 2 + ... + 20

SUMMA:=0; I := 1;

SUMMA := SUMMA + I; I := I + 1;

SUMMA:=0; I := 1;

(«ПОВТОРЯЙ»)

ТЕЛО-ЦИКЛА

Цикл «I»

SUMMA := SUMMA + I I := I + 1;

(«ПОКА»)

УСЛОВИЕ I > 2 0

Цикл «I»

Нет

Рис. 9. Цикл с постусловием

2.2. Язык программирования и его описание (на примере языков Си/С++)

Совокупность понятий, относящихся к указанной в заголовке теме, иллюстрирует рис. 10. На нем сразу же обращают на себя вни­мание слова "ЯЗЫК ПРОГРАММИРОВАНИЯ", "АЛФАВИТ", "СИНТАКСИС + СЕМАНТИКА" и "цветок с лепестками". Обсудим эти понятия подробнее.

Алгоритмический язык (язык программирования), как средство записи алгоритмов, является формализованным средством, предна­значенным не столько для общения между людьми, сколько для об­щения между человеком и ЭВМ. Это определяет следующие разум­ные требования к языку программирования.

1. Он должен быть достаточно простым^ чтобы быть доступ­ным широкому кругу людей.

2. Язык должен допускать однозначное истолкование алгорит­ма, чего нельзя сказать об обычном разговорном языке.

3. Алгоритм, записанный на некотором языке, должен быть сначала переведен на машинный язык ЭВМ. Переводом занимается специальная программа - транслятор. Язык должен быть простым и в том плане, чтобы не было особых сложностей при создании и функционировании транслятора.

27

Page 29: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Этим требованиям в достаточной степени удовлетворяют язы­ки Си/С++.

Языки Си/С++, как и любые другие языки программирования, полностью определяются заданием их алфавита (словаря исходных символов), точным описанием их синтаксиса (грамматики) и се­мантики (смысла) - "СИНТАКСИС + СЕМАНТИКА" (см. рис. 10).

ж - напоминает о том, что, например, буквы греческого алфавита

использовать нельзя

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

J..

Строч­ные и

пропис­ные ла­тинские буквы

/* */ \ \п \г \t \" \'

и др. / * + - ; % » « < > < = > = == !=

& I - ' ! . - > { } ( ) && : = + = - = *= /= %= » = « =

&= 1= ' = [ ] ++ - ,

Только в языке C++ :: // .* ->*

и др.

Специальные символы

АЛФАВИТ (ЛИТЕРЫ) СИНТАКСИС + СЕМАНТИКА

Простота Однозначность

ЯЗЫК ПРОГРАММИРОВАНИЯ Рис. 10. Язык программирования и его описание

Алфавит языка - набор основных символов (литер), исполь­зуемых для записи алгоритма. На рис. 10 изображены литеры языков Си/С++ - три "лепестка цветка". Следует отметить, что некоторые литеры алфавита являются составными, изображаются двумя или тремя символами, но рассматриваются как неделимые (например, "+=" или " » = " ) .

Рис. 1 1 содержит информацию о способах описания синтакси­са языка, где обращают на себя внимание две фамилии — Д. Бэкус и Н. Вирт и приведена информация, касающаяся способов описания

28

Page 30: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

синтаксиса языка, предложенных Д. Бэкусом и Н. Виртом в проти­вопоставлении друг другу (две параллельных колонки).

Из допустимых символов языка, указанных на рис. 10, можно писать программу на языке Си/С++, но не в произвольном виде, а в соответствии с синтаксисом языка. Удобными способами описания синтаксиса языка являются следующие способы.

1. Использование металингвистических формул (предложены Д. Бэкусом, автором языка АЛГОЛ-60).

2. Синтаксические диаграммы (предложены Н. Виртом, авто­ром языка Паскаль).

На рис. 11 приведены определения одних и тех же понятий как через металингвистические формулы, так и через синтаксические диаграммы.

Металингвистическая формула позволяет определить некото­рое понятие путем перечисления всех его значений. Она использует следующие обозначения:

"::=" - знак, который читается как "это есть по определению"; <Определяемое_понятие> - пишется слева от "::="; I - обозначает "ИЛИ"; ( ) — круглые скобки, обозначают "И"; { } — фигурные скобки, обозначают неограниченное повторение

ноль, один, два и т.д. раз, заключенной в них конструкции; [ ] — квадратные скобки, обозначают необязательность конст­

рукции, заключенной в эти скобки. Из рис. 11 следует, что в языке Си два имени, имеющие совпа­

дающие восемь первых символов, будут восприниматься одинако­выми. Вместе с тем отметим, что в интегрированных средах про­граммирования на языке C++ различимая длина идентификаторов может задаваться программистом с помощью соответствующей оп­ции. Прописные и строчные буквы идентификаторов различимы. Так, в частности, ALPHA и alpha - разные идентификаторы.

Использование синтаксических диаграмм поясняет правый столбец на рис. 11. Синтаксическая диаграмма - это схема, состав­ленная из линий со стрелками, прямоугольников и овалов. В прямо­угольник заключают объект, определенный в другом месте, а в ова­лы - литеры или составные символы языка. Сопоставляя определе­ния понятий обоими способами легко понять смысл и особенности применения синтаксических диаграмм. Из сопоставления можно за­ключить, что метод синтаксических диаграмм проще и нагляднее. Его, в основном, и рекомендуется использовать.

29

Page 31: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

д. Бэкус Металингвистические

формулы

<Прописная_буква> ::= A|B|C|...|Z <Строчная_буква> ::= a|b|c|...|z

-Буква> ::= ( <Прописная_буква> | <Строчная_буква> )

-Ненул_восьм_цифра> 1|2|...|7

-Восьмеричная_цифра> ::= ( <Ненул_восьм_цифра>|0 )

<Ненул__дес_цифра> ::= ( <Ненул_восьм_цифра>|8|9 )

<Десятичная_цифра> :;= ( <Ненул^ес_цифра>|0 )

<Идентификатор> ::= ( <Буква>|_ ) { ( <Буква>|

<Десятичная_цифра>|_)}

!!! ДЛИНАИДЕнтификатора !!! Эквивалентно

Ж ДЛИНАИДЕ 8! > К .

Н. В и р т Синтаксические

диаграммы Прописная_буква Строчная_буква

Буква ^ ^ Прописнаябуква

Строчнаябуква • — J ^

Ненул_восьм_цифра

Восьмеричная_цифра

1 Ненул_восьм

_цифра I

Ненул_дес_цифра

X Ненул_восьм

_цифра

Десятичная_цифра

Ненул_дес_ цифра

I Идентификатор

Буква Буква

Дес_цифра

о-Рис. 1 1. Способы описания синтаксиса языка

Семантика определяет смысл предложений (операторов), за­писанных на языке, как каждого в отдельности, так и их совокупно­сти. В большинстве случаев смысл предложений будет представ-

30

Page 32: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ляться некоторыми пояснениями на обычном языке или эквивалент­ными совокупностями других предложений языков Си/С++.

2.3. Структура и конструкция программы на Си/С++

Базовыми элементами языков Си/С++ являются: • комментарии; • идентификаторы;

.• служебные (зарезервированные) слова; • константы; • операторы; • разделители.

Из базовых элементов строится программа. Рассмотрим снача­ла базовые элементы, а затем и структуру программы.

2.3.1. Комментарии

Синтаксическая диаграмма комментария к фрагменту Си-программы приведена на рис. 12.

Комментарий

>Ci*

Печатный символ

Рис. 12. Определение комментария в языке Си

В Си-программе комментарии используются для документиро­вания и могут начинаться и заканчиваться в любом месте програм­мы, где может находиться символ "пробел", и могут содержать лю­бое количество строк:

/'*' Это однострочный комментарий */

/-^ Компилятор языка Си рассматривает эти строки как комментарий

"-/

Обратите внимание, что вложенные комментарии наподобие показанного ниже не допускаются стандартом ANSI и большинст­вом компиляторов:

31

Page 33: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Эта часть комментария правильная /'*• Начало этого комментария игнорируется. * / Эта строка теперь находится вне комментария! Ошибка!

Этот пример показывает, что внутренняя пара символов "/*" игнорируется, а первая же пара символов "*/" завершит коммента­рий. Тем самым, предпоследняя строка и последняя пара символов "*/" окажутся вне комментария и при попытке их компиляции будет выдано сообщение об ошибке.

Наряду с рассмотренными вариантами в языке C++ имеется и другая форма записи комментария:

/ / Это однострочный комментарий

Комментарии подобного вида удобно использовать как ло­кальные комментарии для пояснений к определению некоторого объекта или пояснений к отдельному оператору.

/* Коммен- // Такое вложение возможно! -тарий */ // Коммен- /* И так тоже можно! */ -тарий

Двусмысленность!

X = Y//* Это деление */Z;

Надо так:

X = Y/ /->" Это деление ^/ Z;

2.3.2. Идентификаторы

Идентификаторы были рассмотрены выше (см. рис. 11). Иден­тификатор представляет собой имя некоторого объекта программы. Подробнее об объектах программы говорится ниже в разд. 3.

2.3.3. Служебные слова

Служебные слова представляют собой идентификаторы, имею­щие специальное значение для компиляторов языков Си/С++. Их нельзя использовать как имя переменной. Ниже приведен список служебных слов языка C++:

asm case const delete dynamic cast

auto catch const cast do else

bool char continue double enum

break class default

explicit

32

Page 34: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

export for Inline namespa.ce protected return static template try vmion void.

extern friend int new public short static_cast this typedef unsigned volatile

false goto long operator register signed struct throw typeid using wchar t

float if xmitable private reinterpret^cast sizeof switch true typename virtual while

Трансляторы языков Cu/C++, соответствующие требованиям стандарта ANSI, воспринимают только слуэюебные слова, записан­ные строчными буквами. Функции служебных слов будут рассмат­риваться ниже по мере изучения материала.

Напоминаем, что не следует использовать имена объектов (идентификаторы), совпадающие со служебными словами.

2.3.4. Константы

Определение константы с помощью синтаксической диаграм­мы приведено на рис. 13.

Константы Целая_костанта

#i Символьная константа

Строковая_константа

Константа с пл. точкой

Рис. 13. Определение константы

Константы, в отличие от переменных, являются фиксирован­ными значениями^ которые можно вводить и использовать на языках Си/С++.

Целые константы. Целые константы (рис. 14) не имеют дробной части и не содержат десятичной точки. В отличие от кон­стант с плавающей точкой они точно представляют изображаемое значение. Наиболее часто используются десятичные константы. Ше-стнадцатеричные и восьмеричные константы полезны, когда прихо-

33

Page 35: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

дится иметь дело с данными, представляющими комбинации битов (получаются более короткие записи). Определение десятичной, восьмеричной и шестнадцатеричной констант приведено на рис. 15 -17.

Целые константы могут быть обычной длины или длинные. Длинные целые константы оканчиваются буквой "/" или "L"

Размер целых констант обычной длины зависит от реализации, (для шестнадцатиразрядного процессора — 2, для тридцатидвухраз­рядного — 4 байта). Длинная целая константа всегда занимает 4 бай­та. Таким образом, на тридцатидвухразрядном процессоре эквива­лентны длинная целая константа и целая константа обычной длины.

Целая_константа

Десятичная_константа

Восьмер._константа

#J Шестнад._константа —••

Рис. 14. Определение целой константы

Десятичная_константа

Ненул ._десят._цифра

11 -1028 57944L Десятичная_цифра

Рис. 15. Определение десятичной константы

Восьмеричная_константа

ч2>-013 02000 0160000L

Восьм._цифра

Рис. 16. Определение восьмеричной константы

34

Page 36: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Шестнадцатеричная_константа

М о \ ^ ) ^

] — •

Шестнадцатерич-ная_цифра

щ

Шестнадцатеричная цифра

Десят._цифра

0X400 ОхЬ OxEOOL

Рис. 17. Определение шестнадцатеричной константы

Внутреннее представление константы целого типа в ЭВМ — целое число в двоичном коде. При использовании десятичной целой константы старший бит числа интерпретируется как знаковый (О — положительное число, 1 — отрицательное). Для восьмеричных и ше-стнадцатеричных целых констант возможно представление только положительных чисел и нуля, поскольку старший разряд рассматри­вается как часть кода числа, а не как его знак. Более подробное об­суждение внутреннего представления в ЭВМ целых констант выхо­дит за рамки данной книги и будет рассмотрено при изучении арифметических основ построения ЭВМ.

Диапазон значений десятичных констант обычной длины для шестнадцатиразрядного процессора - от -32768 до +32767, для три-дцатидвухразрядного процессора - ^ ^ ...-г^^ i;; Диапазон значе­ний восьмеричных и шестнадцатеричных констант обычной длины для шестнадцатиразрядного процессора - 0...(2'^-1), для тридцатид­вухразрядного процессора - 0...(2^2 _])

Диапазон значений длинных десятичных констант не зависит от разрядности процессора и составляет ""- •+(2 -1)) Диапазон значений длинных восьмеричных и шестнадцатеричных констант также не зависит от разрядности процессора и составляет 0...(232 _])

Константы с плавающей точкой. Определение константы с плавающей точкой приведено на рис. 18. Внутреннее представление в ЭВМ констант с плавающей точкой состоит из двух частей — ман­тиссы и порядка. При этом константы с плавающей точкой типа float занимают 4 байта, из которых один двоичный разряд отводится под знак мантиссы, 8 разрядов под порядок и 23 под мантиссу. Ман­тисса - число большее 1,0, но меньшее 2,0. Поскольку старшая циф-

35

Page 37: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

pa мантиссы всегда равна 1, то она не хранится. Для констант с пла­вающей точкой типа double, занимающих 8 байт, под порядок и мантиссу отводятся соответственно 11 и 52 разряда. Длина мантис­сы определяет точность числа, а длина порядка — диапазон числа. Для констант с плавающей точкой типа long double под число отво­дится 10 байт. Также заметим, что более подробное обсуждение внутреннего представления fe ЭВМ констант с плавающей точкой выходит за рамки данной книги и будет рассмотрено при изучении арифметических основ построения ЭВМ.

В языке C++, когда в конце константы с плавающей точкой от­сутствуют б у к в ы / F, /, L, константа имеет тип double (8 байтов или 64 бита с диапазоном значений ±l,7•10~^°^..±l,7 •Ю '* ). Если же кон­станта заканчивается буквой /или F, то она имеет тип float, занима­ет 4 байта и диапазон значений ±3,4•10'^^..±3,4•10^^^. Аналогичным образом, при завершении константы буквами / или L константа име­ет тип long double, занимает 10 байт с диапазоном значений

Константа_с_плавающей_точкой

•СУ

F-константа

F-константа F-константа

е H h F-константа

F-константа

10. Ю.Гэкв. 10.F 0.0054 0/00541 экв. 0.0054L .0054 5.5е-3

<7> - • Десятичная_константа

Рис. 18. Определение константы с плавающей точкой

36

Page 38: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Символьные константы. Для кодирования одного символа используется байт (восемь битов). Благодаря этому набор символов содержит 256 символов, образующих две группы: • печатные символы; • непечатные символы.

Непечатным символам соответствуют специальные управляю­щие коды, которые служат для управления внешними устройствами или для других видов управления. В качестве примера непечатного символа назовем символ перехода к новой странице, управляющий, например, работой принтера.

Символьная константа в языках Си/С+-ь состоит либо из одно­го печатного символа, заключенного в апострофы, либо управляю­щего кода, заключенного в апострофы. Управляющие коды пред­ставляют непечатные символы (табл. 5). Символьная константа рас­сматривается как символьный беззнаковый тип данных с диапазо­ном значений от О до 255. Константа ' \0 ' называется нулевым сим­волом или нулевым байтом.

Примеры: 'д:' ' Г Лп'

Табл. 5. Управляющие символы (коды) Управляющий код

\п V \/ \v \b Y \\ \» \' \шестнадцатеричная_константа или \восьмеричная_константа

Назначение Переход к новой строке Возврат каретки Горизонтальная табуляция Вертикальная табуляция Возврат на одну позицию Переход к новой странице Обратная косая черта Кавычка Апостроф

Строковые константы. Строковая константа содержит по­следовательность из нуля или более символов, заключенную в ка­вычки. Для запоминания строковых констант используется по одно­му байту на каждый символ строки и автоматически добавляется к ней признак конца строки, которым служит нулевой байт. Нулевой байт является ограничителем строки.

Для составления строковых констант можно использовать лю­бые печатные символы или управляющие коды, перечисленные выгие. На рис. 19 показан пример размещения строки в оперативной памя­ти ЭВМ.

Приведем еще несколько примеров строковых констант:

37

Page 39: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

"Эта строка содержит символ табуляции \ t " "В строке указан символ,, вызывающий звуковой сигнал: \07'

Последняя строка пустая, в ней нет ни одного символа, однако для ее хранения используется один байт - завершающий нулевой байт.

"СтрокаЛп"

С т Р о к а \п \0 Байты памяти, содержащие коды от О до 255

Нулевой байт

Рис. 19. Размещение строки в оперативной памяти

2.3.5. Структура Си-программы

Си-программа - совокупность одного или нескольких модулей. Модулем является самостоятельно транслируемый файл. Такой файл обычно содержит одну или несколько функций. Функция состоит из операторов языка Си. Структуру Си-программы иллюстрирует рис. 20.

Си-программа

Модуль (файл с определениями данных и операторами) Внешние определения данных

Функция Внутренние определения данных

Операторы

Функция Внутренние определения данных

Операторы

Модуль (файл с определениями данных и операторами) Внешние определения данных

Функция Внутренние определения данных

Операторы

Функция Внутренние определения данных

Операторы

Рис. 20. Структура Си-программы

38

Page 40: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Термин "функция" в языках Си/С+-*- охватывает понятия "под­программа", "процедура" и "функция", используемые в других язы­ках программирования. Как следует из рис. 20, Си/С++-программа может содержать одну функцию (главная функция main) или любое количество функций. Выполнение программы начинается с главной функции. Приведем простой пример подобной программы.

Си++. Программа с одним модулем (файлом) и двумя функция ми. Чтение с клавиатуры одной строки символов, заканчивающей­ся символом '\п ' . Каждый символ печатается вместе с его деся­тичным, восьмеричным и шестнадцатеричным кодами V

^include <stdio.h> // Для функций ввода-вывода // В результате выполненмя директивы включения на место // предыдущей строки помещается содержимое файла stdio.h

// Прототип функции: используется компилятором для проверки // правильности записи заголовка в определении функции и // правильности вызова функции void convert( ±nt ) ;

// Выполнение программы начинается с выполнения следующей // ниже главной функции int main ( void ) / / Возвращает О при успехе {

±пЬ ch; // Прочитанный символ // На экран выводятся две строки, являющееся аргументами // функции экранного вывода printf print f ( "\п Программа изображает символы и их "

"коды. \п" ) ; printf( "\п Наберите строку символов и нажмите клавишу "

"Enter. \п" ) / ch = getchar( ) ; // Подождать ввода символа while ( ch != '\п' ) // '\п' вводится после нажатия Enter {

// Вызов функции печати символа ch и его десятичного, // восьмеричного и 16-ричного кодов. Компилятор // контролирует правильность вызова функции, // используя ее прототип convert ( ch ) ; ch = getchar( ) ; // Ввод следующего символа

} printf( "\п Обработка закончена. \п" ) ;

return 0;

// Определение функции, печатающей символ и его коды

39

Page 41: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

void convert ( ±nt ch ) // Изображаемый символ

{ printf( "Символ 10 код 8 код 16 код \п" ) ; // Непечатные символы имеют десятичные коды О.. 31, а // десятичный код символа ' ' равен 32 ± f ( c h < ' ' )

printf( "Управляющий (непечатный) символ: \п" ) ; // Обратите внимание, что один и тот же символ печатается // вначале в символьном формате %с, а затем // соответственно в форматах десятичного %d, // восьмеричного %о и 16-ричного %к чисел. Число // форматов и количество следующих за управляющей // строкой аргументов совпадают print f( "%с %d %о %х \п",

ch, ch, ch, ch ) ;

return; }

Заметим, что данный пример, не только показывает структуру программы, но и иллюстрирует, как нужно оформлять программу, использовать комментарии и т.п.

Проанализируем этот пример и подведем некоторые итоги. Каждая Си-программа должна иметь одну и только одну главную функцию с именем main. С этой функции начинается исполнение программы. Другие функции могут быть вызваны из функции main или из какой-либо другой функции в процессе выполнения про­граммы. Эти функции могут находиться в том же модуле (файле), что и функция main, или в других модулях.

Функция может иметь нудь или более аргументов. Аргументы являются переменными, которые используются для передачи дан­ных между функциями (main не имеет аргументов, а функция convert имеет один аргумент - переменную ch).

Каждая функция после своего заголовка содержит блок, кото­рый начинается с "{" и заканчивается " } " . Блок содержит определе­ния данных, за которыми следуют операторы функции. Определения данных создают переменные, которые будут использованы в функ­ции. Операторы задают действия, которые должны быть выполнены над переменными.

Все элементы данных должны быть определены перед их ис­пользованием. Определения данных всегда завершаются точкой с запятой. Операторы также завершаются точкой с запятой.

40

Page 42: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

2.4. Простой ввод-вывод в языках Си/С++

Языки Си/С++ не содерэюат встроенных средств ввода-вывода. Для реализации ввода-вывода в составе системы програм­мирования Си/С+-ь поставляется библиотека стандартных функций, содержащая наряду с другими функциями функции ввода-вывода. Функции ввода-вывода библиотеки позволяют читать данные из файлов на магнитных дисках и с устройств и писать данные в файлы и на устройства. Библиотека Си поддерживает три уровня ввода-вывода: • ввод-вывод потока; • ввод-вывод нижнего уровня; • ввод-вывод для консоли и порта.

Здесь мы рассмотрим только ввод-вывод потока.

2.4.1. Ввод-вывод потока

При вводе-выводе потока все данные рассматриваются как по­ток отдельных байтов - это либо файл на магнитном диске, либо фи­зическое устройство (дисплей, печатающее устройство и т.п.). Та­ким образом, операции ввода-вывода потока означают работу с фай­лами или устройствами.

Ввод-вывод потока позволяет. 1. Открывать и закрывать потоки. 2. Читать и записывать символ. 3. Читать и записывать целые данные. 4. Читать и записывать строки. 5. Читать и записывать форматированные данные любого типа. 6. Анализировать ошибки ввода-вывода потока и достижение

конца потока (конца файла). Для использования функций ввода-вывода потока в программе

необходимо директивой include включить в состав текста програм­мы файл stdio.h:

^include <stdio.h>

Обработка данной директивы состоит в замене строки ди­рективы содерэюимым текстового файла stdio.h. Этот файл содер­жит объявления и определения функций ввода-вывода, а также оп­ределения констант, типов и структур, используемых функциями ввода-вывода потока.

41

Page 43: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Открытие потока. Перед выполнением операций ввода-вывода для потока его нужно открыть. Для этой цели служит функ­ция уЬрег7(^ , описание которой имеет вид:

^include <stdlo.h> FILE * fopen ( // Возвращает указатель на открытый

// файл // Указатель на имя открываемого // файла

const cha.i: *type ) ; // Указатель на вид доступа к файлу

const char *path.

Функция открывает файл path в режиме доступа type. Сим­вольная строка type задает вид доступа к файлу в соответствии с табл. 6. Функция/open возвращает указатель на открытый файл. Ну­левой указатель (NULL) означает ошибку. Многочисленные приме­ры открытия файлов с контролем ошибок приведены ниже.

Вид доступа type "г"

'V'

"л"

•V+"

"w+"

"а+"

Табл. 6. Виды доступа к файлу Назначение

Открывается файл для чтения. Если файл не существует или не может быть найден, то возникает ошибка Открывается пустой файл для записи. Если файл уже существует, то его содержимое будет уничтожено Открывается файл для записи в конец (добавления). Если файл не существует, то он сначала будет создан Открывается файл для чтения и для записи (файл должен существовать, иначе - ошибка) Открывается пустой файл для чтения р для записи. Если файл уже существует, то его содержимое будет уничтожено Открывается файл для чтения и для записи в конец (добавления). Если файл не существует, то он сначала будет создан

Закрытие потока. Для закрытия потока служит функция fclose(), которую следует вызвать сразу же после окончания работы с потоком:

/ / Возвращает О при успехе и EOF при // ошибке

stream ) ; / / Закрываемый поток

ilnclude <stdio.h>

±nt fclose(

FILE

Примеры закрытия файлов с контролем ошибок приведены ниже.

Предопределенные указатели потока. В начале выполнения Си-программы автоматически открывается пять потоков:

42

Page 44: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• стандартный ввод (предопределенный указатель stdin); • стандартный вывод (предопределенный указатель stdout); • стандартный вывод сообщений об ошибках (предопределенный

указатель stderr); • стандартный дополнительный поток (предопределенный указа­

тель stdaux); • стандартная печать (предопределенный указатель stdprn).

По умолчанию stdin соответствует клавиатуре терминала, stdout и stderr - экрану терминала, stdaux - дополнительному порту и stdprn - печатающему устройству.

Предопределенные указатели пяти перечисленных стандарт­ных потоков можно использовать в любой функции ввода-вывода, которая в качестве аргумента требует указатель потока.

Функции чтения из потока и записи в поток. Функции чте­ния из потока и записи в поток, имеющиеся в языке Си, перечисле­ны в табл. 7.

Табл. 7. Функции чтения из потока и записи в поток Объект

операции

Серия бай­тов Символ

Данное int Строка Формат, данные

Чтение из stdin

getc getchar

gets scan/

Чтение из любого потока

fread

fgetc fgetchar

getw /gets fscanf

Чтение из стро­

ки Си

sscanf

Запись в stdout

put putchar ungetc

puts print/ vprintf

Запись в любой поток

fwrite

fputc fputchar

putw /puts /print/ v/print/

Запись в строку Си

sprint/ vsprint/ 1

На данном этапе среди функций, перечисленных в таблице, рассмотрим лишь универсальные функции для ввода scan/-/scan/ и для вывода printf-fprint/

2.4.2. Ввод с использованием функций scanf-fscanf

Вначале рассмотрим и проанализируем несколько примеров. Общей особенностью приведенных ниже программ-примеров явля­ется их оформление в виде, предусматривающем возможность их выполнения на ЭВМ.

/* Программа-пример 1 (начало) .

43

Page 45: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ввод в из файла "

ЯЗЫКЕ Си exl

же значения: ±пЬ

float long char

Написать "exl. dat") -/

• написать фрагмент Си-программы^ которая dat" на магнитном диске прочитает указанные ни-

1 г а. 12; f; 1; ch. str[ вид

// // // // // //

20 ];//

OxFA -22 074 1.57 -125874 'Z '

"Нам Тхань" читаемых данных (вид строк в файле

^Include <stdlo.h>

int main ( void ) {

// Для функций ввода-вывода

// Возвращает О при успехе

int i, 11^ 12; float f; long 1; char ch, str[ 20 ]; FILE *f_ln; // Указатель на структуру со

// сведениями о файле для ввода int ret code; // Возвращаемое значение для fscanf

// Открываем файл exl.dat для чтения f_ln - fopen( "exl.dat", "г" ) ; if( f__ln -= NULL ) { // Ошибка открытия файла

printf( "\n Файл exl.dat для чтения не открыт. " ) ; return 1;

}

// Читаем данные из файла exl.dat retcode = fscanf( f_ln,

" 1 = %х ll=%d 12=%о f=^%f l = %ld ch^%c str=%s%c%s", &1, Sell, &12, &f, &1, &ch, str, &str[3], &str[4] ) ;

if( retcode != 9 ) {

print f ( "\n Данные в fscanf прочитаны с ошибками." ) ; return 2;

return О; } // Конец примера 1

Вид строк исходных данных в файле exl.dat: l=OxFA 11=-22 12=074

f==1.57 1 = -125874 ch=z str=HaM Тхань

44

Page 46: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Как работает функция fscanf? Вначале слева направо просматривается управляющая строка

"...". Если очередным символом является символ "пробельной груп­пы" (пробел или '\Г' или \п'), то в исходных данных (во входном по­токе) пропускаются все подряд идущие символы пробельной груп­пы, пока не встретится другой символ.

Если в управляющей строке встретится формат, который на­чинается с символа "%", то из входного потока читается последова­тельность символов до пробельного символа. Она преобразуется в кодовый формат в соответствии с типом формата и записывается по адресу, заданному в соответствующем аргументе (запись &/ означа­ет адрес, по которому в оперативной памяти размещается перемен­ная /). Если до пробельного символа раньше встретится символ, не допустимый в записи читаемого значения, то ввод по текущему формату остановится на этом символе. Символ управляющей стро­ки, следующий за символом "%", указывает способ преобразования символов из входного потока в кодовый формат (табл. 8).

!!! Число форматов и число аргументов в функции/уса«/обя­зательно должно быть одинаковым.

Если в управляющей строке встретился символ, отличный от символа пробельной группы и от символа "%", то функция fscanf считывает очередной символ из входного потока. При несоответст­вии прочитанного символа символу, указанному в управляющей строке, функция/л'сал?/прерывает работу и конфликтный символ ос­тается во входном потоке. В случае соответствия прочитанный сим­вол пропускается и функция продолжает работу. Отмеченная осо­бенность позволяет организовать так называемый "не слепой" ввод. Это означает, что во входном потоке (в файле исходных данных) с помощью лидирующих символов можно указать, к какой перемен­ной относится вводимое значение (см. текст примера выше).

Рассмотрим еще один пример, являющийся логическим про­должением предыдущего примера.

/* Программа -ВВОД В

-пример 2 ЯЗЫКЕ Си:

(начало) .

1. Написать фрагмент Си-программы, которая из "exl.dat" ния:

±пЬ short ±nt float long-char

на магнитном диске прочитает указанные ниже

1; 11/ 12; f; 1; ch.

// Ох FA // -22 // 074 // 1.57 // -125 // 'z'

файла значе-

45

Page 47: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

str[ 20 ];// "Нам Тхань" 2, Написать вид читаемых данных

"exl.dat") */

(вид строк в файле

1 Сим­вол за%

d D о О

х,Х

i

I

и

и \e,E,f,

с

S

%

/ ? •

Р

Табл. 8. Способы преобразования символов при вводе Тип, ожидаемый при вводе

Десятичное целое Десятичное целое Восьмеричное целое Восьмеричное целое Шестнадцатеричное целое без префиксов Ох или ОХ Десятичное, шестнадцатеричное или вось­меричное целое Десятичное, шестнадцатеричное или вось­меричное целое Десятичное целое без знака Десятичное целое без знака

Величина с плавающей точкой из мантис­сы и порядка Символ. Пробельные символы, которые обычно пропускаются, считываются, если указано "с". Чтобы прочесть из потока сле­дующий не пробельный символ, исполь­зуйте формат Vols Символьная строка

Символ %

Из потока ничего не читается

Величина в виде XXXX:YYYY^ где цифры X и Y являются шестнадцатеричными циф­рами верхнего регистра

Тип аргумента

Указатель на int Указатель на long int Указатель на int Указатель па long int Указатель на int

Указатель на int

Указатель на long int

Указатель на unsigned int Указатель на unsigned long int Указатель из.float

Указатель на char

Указатель на символьный массив, достаточно боль-пюй, чтобы разместить вводимое поле и завер­шающий нуль-символ '\0', добавляемый автоматиче­ски Не преобразуется, участвует во вводе как символ '%' Указатель на переменную типа int, в которую записы­вается количество симво­лов, считанных из потока вплоть до этой точки при текущем вызове функции Указатель на объект (far* или near*). Формат %р вы­полняет преобразование указателя к требуемому указателю используемой модели памяти |

46

Page 48: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdio.h> • // Для функций ввода-вывода

int main ( void ) // Возвращает 0 при успехе {

Int i , 12; short 11; float f; long 1; char ch, str[ 20 ]; FILE *f_ln/ // Указатель на структуру со

// сведениями о файле для ввода ±nt ret code/ // Возвращаемое значение для

// fscanf

// Открываем файл exl.dat для чтения f_ln = fopenC "exl.dat", "г" ) ; ±f( f__ln == NULL ) {

printf( "\n Файл exl.dat для чтения не открыт. " ) ; return 1;

}

// Читаем данные из файла exl.dat retcode = fscanf( f_ln,

" 1 = %х ll = %hd 12^%о f=^%f l = %41d874 ch = %c str=%s%c%s", &1, &11, &12, Scf, Sclr &chr str, &str[3], &str[4] ) ;

±f( retcode != 9 ) {

prlntf( "\n Данные в fscanf прочитаны с ошибками." ) ; return 2;

}

return 0; } // Конец примера 2

Вид строк исходных данных в файле exl.dat: l^OxFA 11=-22 12=074

f=1.57 1=-125874 ch=z str=HaM Тхань V

Из рассмотренного примера следует, что в общем случае структура формата имеет вид:

% ['^] [ширина ] [префикс] тип

Звездочка (*), следующая за знаком процента, подавляет запо­минание следующего вводимого поля. Поле считывается в соответ-

47

Page 49: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ствии с форматом, но преобразованная величина никуда не записы­вается. "Ширина" - положительное десятичное целое, задающее максимальное число символов при вводе. Если "ширина" избыточ­ная, то чтение, как и ранее, выполняется до пробельного символа. Если "ширина" меньше, чем число символов до пробельного, то чи­таются и преобразуются только символы числом не более "ширина", (см. пример 2).

Префиксами могут быть: N - используется для печати адресов near (формат %Np); F - используется для печати адресов far (формат УоГр); h - для ввода коротких целых с типом short (см. пример 2); / - для ввода длинных целых и вещественных с типом long (см.

пример 2). Рассмотрим еще один, более сложный, иллюстрирующий при­

мер.

V7* Программа -ВВОД В

-пример 3 (начало) . ЯЗЫКЕ Си:

1. Написать фрагмент "exl.dat" ни я:

±пЬ

float long-char

на Си-программы, которая из

магнитном диске прочитает указанные ниже

±. // 11, // 12; // f; // 1; // ch, // str[ 20 ];//

OxFA или 250 74 18 1,57 -125874

"Нам Тхань" 2. Написать вид читаемых данных (вид строк в

"exl.dat") V

файла значе-

файле

^include <stdlo.h>

±nt main ( void ) {

// Для функций ввода-вывода

// Возвращает О при успехе

Int 1, 11, 12; float f; long 1; сЪаг ch, str[ 20 ]; FILE *f_ln; // Указатель на структуру со

// сведениями о файле для ввода ±nt ret code; // Возвращаемое значение для

// fscanf

// Открываем файл exl.dat для чтения f_ln = fopen( "exl.dat", "г" ) ; ±f( f_ln == NULL ) {

48

Page 50: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

printf ( "\n Файл exl.dat для чтения не открыт. " ) ;

}

// Читаем данные из файла exl.dat retcode = fscanf( f_in,

" %х %d %о %f %ld %c %s%c%s"r &±f &il, &i2, &f, &1, &ch, str, &str[3], &str[4] ) ;

±f( retcode 1=9) {

printf( "\n Данные в fscanf прочитаны с ошибками." ) ; r-etuirn 2;

}

// За крыва ем файл retcode = fclose( f_in ) ; ±f( retcode == EOF ) {

printf( "\n Файл exl.dat не закрыт." ) ; return 3;

}

jzebvLzm 0; } // Конец примера 3

Вид строк исходных данных в файле ех1.dat: OxFA 074

22 1.57 -125874 z Нам Тхань V

В этом примере /1 получает значение 74, так как читается по формату Vod (десятичный формат). Аналогично, /2 получает деся­тичное значение 18, так как читается по формату Voo (восьмеричный формат - восьмеричный код 22 соответствует десятичному коду 18).

В заключение отметим, что функция 5са«/идентична функции fscanf^ но вместо входного потока, заданного первым аргументом, она по умолчанию использует предопределенный входной поток stdin. По этой причине в вызове функции scan/ CUWCOK аргументов начинается сразу с управляющей строки.

2.4.3. Вывод с использованием функций printf-fprintf

Вначале рассмотрим и проанализируем пример. Особенностью приведенной ниже программы-примера также является ее оформле­ние в виде, предусматривающем возможность выполнения на ЭВМ.

49

Page 51: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Программа-пример 4 (начало) . ВЫВОД В ЯЗЫКЕ Си/С++: Укажите вид строк печати в файле fl,out на магнитном диске

после выполнения приведенной ниже программы '7 ^include <stdio.h> // Для функций ввода-вывода

±nt main ( void. ) // Возвращает О при успехе {

// Данные для печати float f = 1.5е2; long- double

Id = 2.0e-3L; ±пЬ i = 7/ long- ±nb 11 = 121; short ±nt si = 5; FILE *f_out/ // Указатель на структуру со

// сведениями о файле для вывода int retcode; // Возвращаемое значение для fсlose

// Открываем файл fl.out для записи f_out = fopen( "fl.out", "w" ); ±f( f_out == NULL ) {

print f ( "\n Файл fl.out для записи не открыт. " ); return 1;

}

// Записываем в файл fl.out fprlntfi f__out, " %30s\n f=%f %5s l = %10d\n",

fprlntfi f_out, " ld=%-Lf f=%15f f=%15.2f f=%+15.2f\n". Id, frf. f );

fprlntf( f_out, " l = %10.5d f=^%E ll = %ld sl = %hl\n", i / ff 11, si );

// Закрываем файл fl.out retcode = fclose( f_out ); ±f( retcode == EOF ) {

printf( "\n Файл fl.out не закрыт." ); return 2;

}

return 0/ } // Конец примера 4

Как работает д^уякияя fprintjl Первый аргумент в вызове функции (foui) указывает поток, в

который производится запись (вывод). Работа функции начинается с

50

Page 52: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

просмотра слева направо управляющей строки "...". Если в управ­ляющей строке нет ни одного формата, то после нее аргументов то­же не будет. Символы в управляющей строке "..." могут быть трех видов.

1. Управляющие символы (в кодовой таблице первые 32 сим­вола), примерами управляющих символов являются '\п\ \t\ '\0х7' и т.д. Если встречается управляющий символ, то он выполняет пред­писанные ему действия. Например, '\п' вызовет переход в потоке ( в нашем случае в файле /Lout) на следующую строку, '\^' - выполнит печать пробелов в соответствии с используемым значением табуля­тора (табулятору может соответствовать 2 - 1 6 пробелов, обычно че­тыре или восемь) и т.д.

2. Форматы, которые начинаются с символа "%". Если встре­тился формат, то из списка аргументов берется соответствующий ему аргумент, значение которого преобразуется в соответствии с типом формата и выводится в поток (в нашем случае в файл fJ.out).

!!! Число форматов в управляющей строке должно быть равно числу аргументов !!!

3. Остальные символы, которые называются печатными и вы­водятся в выходной поток в том виде, как они изображены.

Работа функции заканчивается после просмотра управляющей строки до конца или при возникновении ошибки.

Формат имеет следующую структуру (в квадратных скобках указаны поля формата, которые могут отсутствовать):

% [флаг] [ширина__поля_вывода ] [ . точность] [префикс] тип

Допустимые значения полей "флаг", ".точность", "тип" и дей­ствия, выполняемые перечисленными полями, приведены в табл. 9 -11. Примеры их использования даны выше. Результаты действия форматов приведены в файле результатов/7.оwr, где условно с по­мощью символа "^" показано расположение пробелов.

Поле "флаг" управляет выводом в поток ( табл. 9). В формате может быть указано несколько флагов одновременно, если они не противоречат друг другу.

Поле "ширина_поля_вывода" задает минимальное число выво­димых символов. Это неотрицательное целое десятичное число. Ес­ли "ширина" излишняя, то слева или справа, в зависимости от флага "-", поле вывода дополняется пробелами. Если ширина недостаточ­на, то поле вывода увеличивается до требуемой длины, т.е. усечения выводимого данного не будет!

51

Page 53: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Флаг

-

+

Пробел

#

Табл. 9. Действие флагов форматирования Смысл

Выравнивание результата по левому краю заданного поля Вывод величины с указанием знака "+", если величина принадлежит к типу со знаком

Вывод пробела перед величиной, если это по­ложительное число со знаком Для форматов о, х или Х выводит перед числом префикс 0, Ох или ОХ соответственно. Для форматов е, Е или /выводит число с десятич­ной точкой. Для форматов g, G выводит число с десятич­ной точкой и предотвращает усечение лишних нулей.

Значение по умолча­нию

Выравнивание по пра­вому краю Знак выводится толь­ко для отрицательных величин Пробел не выводится

Префикс в указанных случаях не выводится. Десятичная точка выводится, если за ней следует цифра. Лишние нули усекаются.

"Точность"' - неотрицательное десятичное число, перед кото­рым ставится точка (табл. 10). Обратите внимание, что "точность", в отличие от "ширины", может вызвать усечение выводимой величи­ны или ее округление (для переменной с плавающей точкой).

Тип d, i, и, о, X, X

Е, e,f

g^G

с S

Табл. 10. Действие поля формата ".точность" Смысл

Указывает минимальное число выводи­мых цифр. Если точность меньше, чем надо, то число не усекается. Если точ­ность больше, чем надо, то число допол­няется слева нулями. Указывает число цифр, выводимых после десятичной точки (в случае усечения по­с л е д и ^ цифра округляется).

Указывает максимальное число значащих цифр выводимого числа. Ни на что не влияет. Точность указывает максимальное число выводимых символов. Лишние символы строки не выводятся.

Умолчание Если точность равна нулю, или вообще опущена, или стоит просто точка, то в ка­честве точности берется еди­ница. Точность равна шести. Если она равна нулю или стоит просто точка, то десятичная точка не выводится. Выводятся все значащие цифры. Выводится символ. Символы выводятся до тех пор, пока не будет достигнут нуль-символ.

"Префиксы h, I, L, N, F". Префикс "/г" (sHort) для типов d, i, о, х, А" (табл. 11) указывает

на тип аргумента short int, а для типа и - на тип аргумента unsigned short int.

Префикс "/" для типов d, i, о, х, Jf указывает на тип аргумента long int, для типа и - на тип аргумента unsigned long int, а для типов е, Е, f, g, G - на тип double.

52

Page 54: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Табл. 11. Символы типа \d, i

и 0

X или \х /

\Е, е

G'S

с S

п

р

int int int int

float

float

float

int Строка

Указатель на целое

Указатель ти­па void far *

Десятичное целое со знаком Десятичное целое без знака Восьмеричное целое без знака Шестнадцатеричное целое без знака с использованием цифр "abcdef' или "ABCDEF" Величина со знаком вида [-]dd.dd, где d - десятичная циф­ра. Число цифр до точки определяется величиной числа, а после - точностью. Величина со знаком вида [-]d.ddddde[3HaK]ddd или [-ld.ddddd£[3HaK]ddd Величина со знаком, выводимая в формате /или е, Е, в зависимости от того, что компактнее при заданной точно­сти Отдельный символ i Символы выводятся или до достижения нуль-символа, или после вывода количества символов, заданного в поле "точность" Выводится число символов, успешно записанных к дан­ному м о м е т у . Эта величина присваивается переменной inl с адресом в аргументе. Выводит адрес, на который указывает аргумент, в виде ХХХХ : УУУУ (сегмент : смещение) с использованием шестнадцатеричных цифр верхнего регистра

Префикс "Z," для типов е, Е, f, g, G указывает на тип аргумента long double.

Префикс "F" для типов р, s, п указывает на тип аргумента дальний указатель, а префикс 'W" для тех же типов - на тип аргумен­та ближний указатель.

Файл результатов работы программы/7.ow/' имеет следующий вид (символ ^ обозначает пробел):

^i=''^'^^^00007^f=l. 500000Ei-02^1±=12^si=5

Обратите внимание, что функция print/ идентична функции* /print/, но вместо выходного потока, заданного первым аргументом, она по умолчанию использует предопределенный входной поток stdout. По этой причине в вызове функции/7гш(/'список аргументов начинается сразу с управляющей строки.

Функции ргш^/х^^гш^/'возвращают число выведенных символов при успешном завершении или EOF при ошибке. Однако это воз­вращаемое значение обычно не контролируют.

53

Page 55: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

2.4.4. Упражнения для самопроверки

1. Имеется следующий фрагмент Си-программы:

float ±nt cha.r ±nt

a, Ь; i J/ cl , c2^ c3 retcode;

retcode = fscanfi stdin, " %i %3d %c %c %c %f %f'\ &i, &j, &cl, &c2r &c3r Sea, &b ) ;

Строки исходных данных в файле с указателем stdin имеют следующий вид:

17 123456 2.4еЗ 112 14,5

Какие значения получат переменные retcode, а, Ь, i, J, cl, с2, с31

2. Имеется следующий фрагмент Си-программы:

float ±nt char ±nt char

a; ^f jr cl, c2, c3; retcode; c4, c5, s[20];

Написать фрагмент программы, обеспечивающий чтение из файла f.dat, на магнитном диске следующих значений:

а = 1.5 i = 21 j = -12 cl = 'в' с2 = 'е' сЗ = 'с' с4 = 'а' с5 = 'н' S => "Прочита иная-строка"

Как при этом будут выглядеть строки исходных данных в фай-n^f.datl

Предусмотреть контроль корректности значений, возвращае­мых функциями библиотеки Си.

3. В программе имеются следующие переменные:

±nt d = 254; float f = 1234.56; char *str = "Строка символов";

Используя, по возможности, только эти данные написать про-

54

Page 56: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

грамму, выводящую в файл результатов/ile.out следующие строки (в них символ ^ обозначает местоположение пробела):

/•-/-254 " "- "- - -"7 " " f " " ^2547 (^^^^^1234,5600) " " (1234. 5 600''^^^^) /Стр/^-^/м/

Ответы и решения для этих и последующих упражнений для самопроверки можно проверить в разд. 18.

Page 57: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3. т и п ы ДАННЫХ и и х АТРИБУТЫ

Вспомним еще раз определение программы по Н. Вирту:

"Программа = структуры данных + алгоритм'

В соответствии с приведенным определением начнем рассмот­рение программы с данных. Данные характеризуются следующими атрибутами: • именами; • типами; • областями действия; • временем жизни.

Данные можно также инициализировать, то есть определять их начальные значения одновременно с их размещением в памяти. Так как в языках Си/С++ функции могут получать и возвращать значе­ния, то важно также обсудить параметры/аргументы функций и воз­вращаемые функциями значения.

3.1. Имена

В языках Си/С++ любая область памяти компьютера, которая может быть использована программой, называется объектом. Любое выражение, представляющее собой ссылку на объект, называется адресным выраэюением или, короче, адресом^ именем.

Например, в рассмотренной выше программе объект "с/г" был определен следующим образом:

i n t main ( sroldL ) {

±nt ch; // Объект с именем (адресом, // адресным выражением) ch и типом // ±nt

retuxm 0;

Имена объектов (например, ch) являются просто идентифика­торами. Служебное слово int (INTeger, целый) указывает тип значе­ния, которое будет содержать данный объект.

56

Page 58: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3.2. Типы данных

Тип данного указывает компилятору языка C++, сколько памя­ти надо выделить для размещения объекта. Кроме того, он указыва­ет компилятору каким образом надо интерпретировать значение, со­держащееся в объекте. Тип объекта указывается в определении объ­екта с помощью служебного слова (слов) - спецификации типа. Пре­дусмотрено следующие основные (стандартные) типы данных (табл. 12).

Табл. 12. Основные (стандартные) типы данных Служебное слово

char wchart

int bool

float

Размер в байтах 1 2

Зависит от реализации 1

4

Назначение Для символа (-128 ... +127) Для символа из расширенного набора (-32768 ... +32767) Для целого значения Для логического значения (falsey true) Для значения с плавающей точкой (по абсолютной величине от 3.4Е-38до3.4Е+38)

Происхождение и перевод служебных слов:

char (CHARacter: буква, симвом); wchar_t (wide character type: расширенный символьный тип); int (INTeger: целое число); float (число с плавающей точкой).

Существуют четыре спецификатора типа (табл. 13), уточняю­щих внутреннее представление и диапазон значений стандартных типов: unsigned (без знака), signed (со знаком), short (короткий), long (длинный).

Символьный тип (char). Под величину символьного типа от­водится один байт, что позволяет хранить в нем любой символ из 256-символьного набора ASCII. Величины типа char применяются также для хранения целых чисел из диапазона +127 ... -128. По умолчанию тип char эквивалентен типу signed char. При использо­вании типа unsigned char значения могут находиться в диапазоне О ... 255. Величины типа unsigned char применяются также для хране­ния целых чисел из диапазона О ... 255.

Расигиренный символьный тип (wchart). Этот тип предна­значен для работы с набором символов, для кодировки которого не­достаточно одного байта (например, для набора Unicode). Символь­ные и строковые константы с типом wchart записываются с пре­фиксом L, например:

57

Page 59: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

iinclude <stdio.h> int main ( void ) {

wpr±ntf( L"%s\n%c\n", L"string", L'A' ) ;

reburn 0; }

В результате выполнения этой программы на экран выводятся следующие строки:

string А

Табл. 13. Уточняющие спецификаторы типа Служебное слово

unsigned char

unsigned или unsigned int short или short int unsigned short или unsigned short int long или long int

unsigned long или unsigned long int double или long float long double

Размер в байтах 1

Зависит от реали­зации 2

2

4

4

8

10

Назначение Байт с неотрицательным целым значением (0 ... 255) Для неотрицательногЬ целого значения Целое значение (от -32768 до +32767) Беззнаковое короткое целое (0 ... 65535) Целое длинное (-231...+ (2^1 -1) ) 1 Беззнаковое целое длинное (0. . . (232-1)) Вещ. с двойной точностью ±1,7 .10- '^ . .±1 ,7 .10^ ' ° ' Вещ. с повыщенной точностью ±3,4•10-'^•'^..±3,4.10"'^' '

Целые типы. Размер типа int зависит от реализации, (для ше­стнадцатиразрядного процессора - 2, для тридцатидвухразрядного -4 байта). Диапазон значений для шестнадцатиразрядного процессора - от -32768 до +32767, для тридцатидвухразрядного процессора -(-231...+ (231 _1))

Спецификатор short перед именем типа указывает компилято­ру, что под число требуется отвести два байта, независимо от раз­рядности процессора. Спецификатор long означает, что целая вели­чина занимает четыре байта. Таким образом, на шестнадцатиразряд­ном процессоре эквивалентны типы int и short int, а на тридцатид­вухразрядном - int и long int.

Внутреннее представление величины целого типа — целое чис­ло в двоичном коде. При использовании спецификатора signed

58

Page 60: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

старший бит числа интерпретируется как знаковый (О - положи­тельное число, 1 — отрицательное). Спецификатор unsigned позволя­ет представлять только положительные числа, поскольку старший разряд рассматривается как часть кода числа.

По умолчанию, все целочисленные типы считаются знаковы­ми, то есть спецификатор signed можно опускать.

Логический тип (bool). Величины логического типа могут принимать только значения false и true, которые являются служеб­ными словами. Внутренняя форма представления значения false — О (нуль). Любое другое значение интерпретируется как true. При пре­образовании к целому типу true имеет значение 1.

Типы с плавающей точкой (floaty double и long double). Внутреннее представление для типов с плавающей точкой состоит из двух частей — мантиссы и порядка. При этом величины типа float занимают четыре байта, из которых один двоичный разряд отводит­ся под знак мантиссы, 8 разрядов под порядок и 23 под мантиссу. Мантисса — число большее 1,0, но меньшее 2,0. Поскольку старшая цифра мантиссы всегда равна 1, то она не хранится.

Для величин типа double, занимающих восемь байт, под поря­док и мантиссу отводятся соответственно 11 и 52 разряда. Длина мантиссы определяет точность числа, а длина порядка — диапазон числа.

Спецификатор long перед именем типа double указывает, что под число отводится 10 байт.

Чтобы проверить размер памяти, выделяемой для объекта дан­ного типа, можно написать программу, использующую операцию ''sizeof {size - размер). Значением этой операции является размер любого объекта или спецификации типа, выраженный в восьмиби­товых байтах:

/ / См++. Программа печатает размеры объектов основных и // производных типов

^include <stdio.h> // Для функций ввода-вывода

int main ( void ) // Возвращает О при успехе {

printf ( "Тип объекта Его размер в байтах \п\п" ) ; prlntfC "char %d \п", slzeof( char ) ) ; printf ( "unsigned char %d \ л " , sizeof ( unslgnecL cbar ) ) . printf ( "int %d \ л " , sizeof ( int ) ) ; printf( "unsigned %d \ л " , sizeof( unsigned ) ) / printf( "short %d \n", sizeof( short ) ) ; printf( "unsigned short %d \ n " ,

sizeof( unsigned short ) ) ; printf( "long %d \ n " , sizeof( long ) ) / printf( "unsigned long %d \ л " .

59

Page 61: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

sizeof ( unsigned long- ) ) ; printf( "float %d \n", sizeof ( float ) ) ; printf( "double %d \n", sizeof ( double ) ) ; print f ( "long double %d \n", sizeof ( long double ) ) ,

•retujETZi 0;

}

Tun void. Кроме перечисленных, к основным типам языка от­носится тип void, но множество значений этого типа пусто. Он ис­пользуется для определения функций, которые не возвращают зна­чения, для указания пустого списка аргументов функции, как базо­вый тип указателей и в операции приведения типов. Все это будет рассмотрено далее.

В заключение приведем с использованием синтаксических диа­грамм правила определения объектов в программах на языке Си/С++ (рис. 21). Следует заметить, что наряду с приведенными на этом рисунке разновидностями, есть и другие разновидности специ­фикации типа и определяемого объекта, которые мы рассмотрим позже.

В дополнение к имени и типу объекта существуют еще два ат­рибута: • область действия; • время жизни объекта.

Эти два атрибута определяются классом хранения, который связывается с конкретным объектом.

3.3. Класс хранения: область действия и время жизни

Областью действия объекта (данного) называется та часть программы, в которой можно пользоваться этим объектом. В част­ности, областью действия может быть: • блок операторов ( { . . . } ); • модуль (файл); • вся программа в целом.

Временем жизни данного называется отрезок времени, в тече­ние которого значение этого данного доступно в некоторой части программы. Время жизни данного может быть столь коротким, как время исполнения операторов блока, или столь же длинным, как время выполнения всей программы.

В языках Си/С++ область действия и время жизни объекта оп­ределяются его классом хранения, в качестве которого можно ис­пользовать следующие классы:

60

Page 62: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• внешний; • внешний статический; • внутренний статический; • автоматический; • регистровый.

Список_опред._объектов

*н Спецификация_типа —г-н Определяемый_объект

Специф._типа

unsigned >

и short У

и long У

•( char У

•Г long V ^

•Г float \

> double

Определяемый_объект

Идентификатор

Рис. 21. Правила определения объектов

3.4. Внешние и внешние статические данные

Дадим более полное определение модуля на рис. 22. Отсюда следует, что можно объявлять и определять данные в модуле до то­го, как будут указаны определения функций. В этом и состоит "внешнее объявление и определение данных".

61

Page 63: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Рассмотрим далее несложный иллюстрирующий пример.

Модуль (файл с объявлениями и определениями данных и операторами)

Объявления и определения внешних данных Функция

Внутренние определения данных Операторы

Функция Внутренние определения данных

Операторы

Рис. 22. Обобш[ение определения модуля

Файл Р2.СРР Программа с одним модулем и двумя функциями. Программа яв­

ляется примером и конкретизацией информации, представленной на рис. 19

#include <stdio.h> // Для функций ввода-вывода

// Прототип функции void, save ( void. ) ;

// Определение внешних данных: область действия - программа, // время жизни - программа int i l , ±2;

// Выполнение программы начинается с выполнения' следующей // ниже главной функции int main ( void ) // Возвращает О при успехе {

save ( ) / // Вызов функции: определяет И, i2 printf( "\п 11 = %d 12 = %d"r 11, 12 ) ;

re turn 0; }

// Определение, функции, задающей значения 11, 12 void save( void ) {

11 = 10; 12 = 15;

return;

В этом примере определены внешние данные /1 и /2. Область действия внешних данных (в примере /1 и /2) распространяется на

62

Page 64: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

весь модуль, а время их эюизни совпадает со временем выполнения программы. Любая функция, находящаяся в этом файле (модуле) может иметь доступ к внешним данным. Таким образом, внешние данные файла являются общими данными для всех функций того же файла.

В общем случае область действия внешних данных можно рас­пространять и за пределы файла (модуля), используя служебное сло­во extern {EXTERNal - внешний).

В качестве иллюстрации перепишем программу для предыду­щего примера с использованием двух файлов.

Файл РЗ.СРР Двухфаиловый программный проект с двумя функциями. Пример

иллюстрирует область действия и время жизни данных, имеющих внешний класс хранения. V

^include <stdio.h> // Для функций ввода-вывода

// Прототип функции: хотя определение этой функции находится // в другом файле данного программного проекта (SAVE.CPP), // в данном файле прототип также нужен - он используется // для контроля правильности вызова функции void save ( void. ) /

// Объявление внешних данных: дополнительной памяти // объявление не занимает, а лишь говорит о том, что // соответствующие данные, определены в другом файле extern Int 11, 12;

// Возвращает О при успехе

// Вызов функции: определяет 11, 12 %d 12 = %d", 11, 12 ) ;

int {

}

main ( void )

save( ) ; prlntf( "\n

return 0;

11

Файл SAVE.CPP Используется в программном проекте, главная функция кото-

\рого имеется в файле РЗ.СРР. V

// Прототип функции: в принципе, в этом файле прототип не // нужен, так как файл содержит определение этой функции. // Мы оставляем здесь прототип только для унификации void save ( void ) ;

// Определение внешних данных: занимает память, располагая в // ней соответствуюище данные

63

Page 65: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt ilr ±2;

-void save ( void. )

il 10; ±2 15;

jretuim/ ;

в приведенном примере внешние данные /1 и /2 определены в файле SAVE.CPP. Такое определение должно присутствовать толь­ко в одном файле многофайлового программного проекта. Чтобы воспользоваться этими данными вне файла, где они определены (на­пример, в файле РЗ.СРР), их следует объявить с использованием служебного слова extern. Таким образом, определение создает дан­ное (см. файл SAVE.CPP), а объявление (см. файл РЗ.СРР)- только ссылается на данное, определенное в другом файле (рис. 23). Обра­тите внимание, что объявление внешних данных, в отличие от их определения, может присутствовать в нескольких файлах программ­ного проекта.

ОПРЕДЕЛЕНИЕ Определить данное типа int

, Имя нового данного

int 11; ОБЪЯВЛЕНИЕ

Указывает, что данное определено в другом месте (в другом файле) Указывает тип данного Имя существующего данного

extern int i1; Рис. 23. Определение и объявление внешних данных

В определении данного перед спецификацией его типа можно использовать служебное слово static (статический). При этом об­ласть действия определяемого данного ограничивается только тем файлом, где данное определено, а время смсизни, как и ранее, совпа­дает со временем выполнения программы.

Приведем и для этого случая иллюстрирующий пример. _ _

Файл Р4.СРР Двухфайловый программный проект с двумя функциями. Пример

иллюстрирует область действия и время жизни данных^ имеющих внешний и внешний статический классы хранения

64

Page 66: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdlo,h> // Для функций ввода-вывода

// Прототип функции: хотя определение этой функции находится // в другом файле данного программного проекта (SAVE1.СРР) , // в данном файле прототип также нужен - он используется // для контроля правильности вызова функции void savel ( void. ) ;

// Объявление внешних данных: дополнительной памяти // объявление не занимает, а лишь говорит о том, что // соответствующее данные определены в другом файле // Gxtejcn int 11; Такое объявление ошибочно! extejcn int 12;

int main ( void ) {

savel( ) ;

prlntf( "\n 12

return 0;

// Возвращает 0 при успехе

// Вызов функции: определяет // значение 12

%d", 12 ) ;

Файл SAVE1.CPP Используется в программном проекте, главная функция кото­

рого имеется в файле Р4.СРР V // Прототип функции: в принципе, в этом файле прототип не // нужен, так как файл содержит определение этой функции. // Мы оставляем здесь прототип только для унификации void savel( void ) ;

// Определение внешнего и внешнего статического данных: // занимает память, располагая в ней соответствуюш^^е данные static int 11; // Доступно только в этом файле int 12; // Доступно в этом файле и файле

// Р4,СРР

void savel( void ) {

11 = 10; 12 = 15;

return; }

Обратите внимание, что в этом примере в файле Р4.СРР объ­явление

extern int 11.

65

Page 67: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

было бы ошибочным, так как областью действия /1 является только файл SAVE1.CPP.

Подводя итоги сказанному, выполним некоторые обобщения. В общем случае, программный проект является многофайловым. В рассуждениях, относящихся к объектам с описателем класса хране­ния "внешний", будем считать, что программный проект содержит три файла (рис. 24 а).

Гипотетический программный проект f l . cpp f2.cpp fS.cpp

1 m i l l ^ ^ ^ ^ ^ ^ И н 1 extern int i 1 ; ^ ^ H 1 o v f o r n И - ^ ^ ^ H

{

float i 1 ;

)

a) Гипотетический программный проект

f l . cpp f2.cpp fS.cpp

• " s t a t i c l ^ ^ H 1 static char ch; ^ H 1 ctatin i9- ^ ^ ^ ^ H

{ char i2;

)

6) Рис. 24. Внешние объекты:

a) с описателем класса хранения внешний; б) с описателем класса хранения внешний статический

На этом рисунке область действия объекта /1 с описателем класса хранения extern содержит весь файл fl.cpp, часть файла f2.cpp и часть файла f3.cpp (выделена заливкой). Отметим, что на­чальная часть файла f2.cpp не входит в область действия объекта /1 потому, что в этом файле объявление объекта /1 помещено в сере­дину файла. Аналогично, в файле f3.cpp вложенный блок не входит

66

Page 68: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в область действия, так как в нем переопределяется объект с иден­тификатором / 1 . Область действия объекта / 1 с описателем класса хранения extern можно сделать максимальной — все файлы про­граммного проекта. Для этого достаточно объявление объекта /1 в файлах fZ.cpp и О.срр поместить в их начало, а вложенные блоки не должны содержать переопределение объекта / 1 . Еще раз напомним, что определение объекта с описателем класса хранения extern долэюно быть только в одном файле программного проекта (лю­бом), а в остальных файлах долэюно использоваться только объяв­ление объекта.

Рис. 24 б иллюстрирует области действия объектов с описате­лем класса хранения "внешний статический". Так, областью дейст­вия объекта/является весь файл П.срр (и только он), областью дей­ствия объекта ch является залитая часть файла f2.cpp, а областью действия объекта /2 является залитая часть файла О.срр.

Теперь можно сформулировать ряд важных уточнений, отно­сящихся к областям действия и времени жизни объектов с описате­лями класса хранения "внешний" и "внешний статический":

\. Временем эюизни внешних данных является интервал време­ни, в течение которого программа выполняется. Это верно как для внешних, так и для внешних статических данных. Следовательно, если внешней переменной будет присвоено значение, то оно будет сохраняться в течение всего времени выполнения программы и не будет утрачено между вызовами функций.

2. Областью действия внешнего данного в общем случае явля­ется вся программа за исключением влоэюенных блоков, в которых содерэюатся переопределения данного с тем эюе именем. Вложен­ным блоком называется конструкция вида { ... }, в которой между фигурными скобками могут находиться определения данных и опе­раторы.

3. Областью действия внешнего статического данного явля­ется файл, где это данное определено, за исключением влоэюенных в этот файл блоков, в которых содерэюатся переопределения данного с тем эюе именем.

Обратите особое внимание на два последних уточнения' отно­сительно областей действия внешних и внешних статических дан­ных - это очень важно!

Остальные классы хранения данных - автоматический, внут­ренний статический и регистровый - гораздо уже по области дейст­вия и, за исключением внутренних статических данных, по времени жизни. Данные этих классов привязаны к отдельным функциям или блокам. Поэтому перед рассмотрением областей действия и времени

67

Page 69: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

жизни этих данных познакомимся подробнее с определениями, объ­явлениями (прототипами) функций и их вызовами.

3.5. Функции

Выше типы данных и области действия рассматривались при­менительно только к объектам данных. В языках Си/С++ эти атри­буты могут быть связаны и с функциями. Рассмотрим определения и объявления функций.

Общий вид определения функции представлен на рис. 25.

Класс: внешний (extern, по умолчанию) или статический (static).

Будем пользоваться умолчанием - опускать extern,

Тип возвращаемого значения (int - по умолчанию, void - отсутствует)

Имя функции

static extern

float power( int number, float exponent) {

} Имена параметров

Типы параметров

Рис. 25. Определение функции

Прежде всего, следует заметить, что функцию можно сделать статической, указав перед ее именем и типом слово static. В языках Си/С++, по умолчанию, все функции трактуются как внешние, если только перед типом функции не указано служебное слово static. Это означает, что областью действия внешней функции является вся программа. Определение функции как статической сужает область ее действия на оставшуюся часть файла, в котором она определена.

Из рис. 25 следует также, что функции можно приписать тип. Тем самым будет определен тип данного, возвращаемого функцией в качестве результата. Если тип возвращаемого функцией результата отличается от int, то об этом следует сообщать компилятору, как в месте ее определения, так и в любом месте ее внешнего объявления в других файлах. Показанный на рис. 25 пример иллюстрирует так­же способ присваивания имен параметрам функции и способ опре­деления их типов.

68

Page 70: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Объявление функции в языках Си/С++ называется прототи­пом функции. Вид его аналогичен заголовку определения функции, за которым вместо блока функции { ... } следует символ ";". Другое отличие списка параметров в прототипе заключается в том, что либо разрешается указание всех имен параметров, как в заголовке опре­деления функции, либо все имена параметров можно опустить.

Давайте теперь рассмотрим иллюстрирующий пример — запи­шем прототип, определение и пример вызова функции, определяю­щей наибольшее и наименьшее значение из двух аргументов. При проектировании функции обычно вначале составляют специфика­цию функции, которую можно рассматривать как графическую фор­му записи прототипа функции. Спецификация (прототип) функции определяет ее интерфейсные свойства. Это означает, что на данном этапе функция рассматривается как "черный ящик" и определяются только ее интерфейсные свойства — входные и выходные данные. Спецификация функции для рассматриваемого примера представле­на на рис. 26.

double Arg1

double Arg1 MaxMin

double &Max

double &Min

Исходные данные Процесс (передаются по значению)

Рис. 26. Спецификация функции

Результаты (передаются по ссылке)

Теперь запишем исходный текст в виде законченной програм­мы, содержащий записи прототипа, вызова функции и ее определе­ния.

Файл MAXMIN. СРР Однофайловый программный проект с двумя функциями. Пример

иллюстрирует работу с функцией: объявление (прототип) функ­ции^ определение функции, вызов функции без возвращаемого значения и оба варианта передачи параметров функции - по зна- \ чению и по ссылке V

^include <stdlo.h> // Для функций ввода-вывода

// Прототип функции: Argl, Агд2, Мах, М1п - параметры функции // В данном случае прототип функции является обязательным, // так как вызов функции выполняется раньше, чем функция // определена void MaxMin ( double Argl, double Arg2, double &Max,

69

Page 71: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

double &Min ) ;

±nt main ( void ) // Возвращает 0 при успехе (

double al = 1.5, a2 - -17.1, Mx, Mn;

// Вызов функции без возвращаемого значения: al, а2, Мх, // Мп - аргументы функции MaxMln ( al, а2, Мх, Мп ) ;

prlntf( "\п al = %1д, а2 = %1д, Мх = %1д, Мп = %1д \п", al, а2, Мх, Мп ) ;

jretujrn О; }

// Определение функции, вычисляющей Мах:=наиб. (Argl,Агд2) и // М1п:^наим. (Argl,Агд2). Функция не имеет возвращаемого // значения void MaxMin (

double Argl, // Исходное данное ~ передается по // значению

double Агд2, // Исходное данное - передается по // значению

double &Мах, // Ответ - передается по ссылке double ScMin ) // Ответ - передается по ссылке

{ ±f( Argl>Arg2 ) {

Max = Argl; Mln = Arg2; } else {

Max = Arg2; Min = Argl; }

return; }

Еще раз напомним, зачем нуэюен прототип (объявление) функ­ции. Прототип функции используется для контроля правильности вызова функции. В рассмотренном выше примере прототип функции MaxMin применяется компилятором при вызове этой функции Здесь компилятор сравнивает: • возвращаемое значение функции в прототипе (void - отсутствует)

со способом вызова функции (вызов должен начинаться с имени функции);

• сравнивает количество параметров в прототипе и их типы с коли­чеством аргументов в вызове функции и типами аргументов.

70

Page 72: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в нашем примере имеет место их полное соответствие, что свидетельствует об отсутствии ошибок в вызове функции. Попутно заметим, что в языке Си прототип функции не обязателен. При его отсутствии компилятор выдает лишь предупреждение о невозмож­ности проверить правильность вызова такой функции, что не пре­пятствует выполнению программы. Это является недостатком языка Си. В языке же С-ь+, напротив, прототип является обязательным и это хорошо.

Если в файле программного проекта, где находится вызов функции, имеется определение этой функции, причем определение функции предшествует ее вызову, то наличие прототипа не является обязательным. При его отсутствии для контроля правильности вызо­ва функции компилятор использует заголовок функции из определе­ния функции.

Рассмотрим процесс передачи аргументов а\ и а2 функции MaxMin в приведенной выше программе. Что же функция MaxMin получает в действительности - копии значений аргументов а\ и а2 или значения этих аргументов? В данном случае функция получает копии значений аргументов al и а2. Передача функции копий зна­чений аргументов, в противоположность передаче функции значе­ний самих аргументов, называется передачей аргументов по значе­нию. При таком способе передачи значения аргументов a l и а2 ко­пируются, на время работы функции, в дополнительную область па­мяти и используется в функции в качестве параметров ArgX и Arg2. По завершении работы функции указанная область памяти освобож­дается и может быть повторно использована. При этом сами аргу­менты al и а2 остаются неизменными (даже, если в теле функции значения параметров будут изменены). Такой способ передачи ар­гумента в функцию, по существу, означает "упрятывание" информа­ции в функции. Следовательно, он хорош для передачи в функцию исходных данных, которые после завершения функции должны со­хранить прежние значения.

В языке C++, в отличие от языка Си, существует и другой спо­соб передачи аргумента в функцию - передача аргумента по ссылке. В нашем примере такими аргументами являются Мх и Мп. При пе­редаче аргументов Мх и Мп по ссылке в качестве параметров Мах и Min используется сами аргументы Мх и Мп. По завершении работы функции аргументы Мх и Мп останутся такими, какими они были перед завершением функции (в нашем случае Мх получает наиболь­шее, а Мп — наименьшее значение из a l и а2). Такой способ переда­чи аргумента в функцию хорош для получения из функции ответа.

71

Page 73: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в рассмотренном нами примере функция не имела возвращае­мого значения. Но ведь существуют и функции, имеющие возвра­щаемое значение. Когда же их следует применять? Ответ на этот во­прос прост — если из функции получаем единственный ответ. В этом случае удобнее его получать как значение, возвращаемое функцией. Рассмотрим пример, иллюстрирующий такой способ получения от­вета. В качестве решаемой задачи рассмотрим более простую зада­чу, являющуюся частью только что рассмотренной задачи - запишем прототип, определение и пример вызова функции, определяющей наибольшее значение из двух аргументов. Спецификация соответст­вующей функции приведена на рис. 27.

double Arg1

double Arg1 Max double

Процесс Наибольшее из Arg1 и Arg2 получаем как возвращаемое значение

Рис. 27. Спецификация функции с возвращаемым значением

Исходные данные (передаются по значению)

Теперь запишем исходный текст в виде законченной програм­мы, содержащий записи прототипа, вызова функции и ее определе­ния.

/* Файл МАХ.СВР Однофайловый программный проект с двумя

иллюстрирует работу с функцией: ции, определение чением

функции и вызов объявление функции с

функциями. (прототип)

Пример функ-

возвращаемым зна-

^include <stdio.h> // Для функций ввода-вывода

// Прототип функции: Argl^ Агд2 - параметры функции, функция // имеет возвращаемое значение. В данном случае прототип // функции является обязательным, так как вызов функции // выполняется раньше, чем функция определена dovLble Мах ( double Argl, double Arg2 ) ;

±пЬ main ( void ) {

double al

// Возвращает 0 при успехе

1.5, a2 = -17.1, Мх;

// Вызов функции с возвращаемым значением: al, а2 -// аргументы функции

72

Page 74: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Мх = Max( al, a2 ) ;

printf ( " \ л al = %lg, a2 = %lg, Mx = %lg \n", air a2, Mx ) ;

z-etuni 0; }

// Определение функции double Max ( // Возвращает наиб. (Argl ,Агд2)

double Arglr // Исходное данное - передается по // значению

double Агд2 ) // Исходное данное - передается по // значению

( ±£( Argl>Arg2 ) {

retuim Argl; }

re bum A r g2 ; }

В рассмотренном примере функция Max имеет возвращаемое значение. Поэтому вызов этой функции должен быть записан в фор­ме выражения присваивания. В этом выражении слева от знака опе­рации '=' должно указываться имя переменной с типом как тип зна­чения, возвращаемого функцией, а справа от знака '=' должно следо­вать имя функции. В нашем примере в вызове функции Мах имеет место точное соответствие прототипу этой функции и в части воз­вращаемого значения, что также говорит об отсутствии ошибок в использовании функции. В заключение отметим, что класс хранения таких объектов, как параметры функций, передаваемые по значе­нию, называется автоматическим (другие названия - локальный, рабочий). Такое название означает, что область действия парамет­ра, передаваемого по значению, ограничивается текущей функцией, точнее блоком функции за исключением тех вложенных в блок функции блоков, в которых содержится переопределение имени па­раметра. Область действия параметра, передаваемого в функцию по ссылке, определяется соответствующим аргументом в вызове функ­ции.

Временем эюизни параметра, передаваемого в функцию по значению, является продолжительность исполнения функции. Как только функция завершит работу, значения ее параметров, передан­ных по значению, будут утеряны. Время же жизни параметра, пере­даваемого в функцию по ссылке, также определяется соответствую­щим аргументом в вызове функции. Автоматический класс хранения могут иметь не только параметры функций, но и другие объекты.

73

Page 75: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3.6. Автоматические, регистровые и внутренние статические данные

Автоматические, регистровые и внутренние статические дан­ные можно определить внутри любого блока операторов языков Си/С++. Общий синтаксис блока представлен на рис. 28.

{ Начало блока

Внутренние определения данных

Операторы

Конец блока

Рис. 28. Общий синтаксис блока

Отметим разницу в синтаксисе блока языков Си/С++. В языке Си внутренние определения данных блока должны обязательно предшествовать операторам блока, а в языке C++ внутренние опре­деления данных и операторы блока могут быть перемешаны. Но при этом необходимо, чтобы использованию внутреннего данного в опе­раторе блока обязательно предшествовало его определение.

В качестве примеров блоков, известных нам на данном этапе, можно назвать блоки в операторе //, блоки в циклических операто­рах, блоки функций и обычные блоки. Существуют и другие разно­видности блоков, которые будут рассмотрены далее.

Данные можно определить внутри блока как имеющие либо автоматический auto {AUTOmatic), либо статический static, либо ре­гистровый register классы хранения (рис. 29). По умолчанию, когда описатель класса хранения опущен, предполагается автоматиче­ский класс хранения!!!

74

Page 76: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Определение_внутренних_данных

^

auto

static

Специф._типа

register >

Идентификатор

Рис. 29. Определение внутренних данных

Областью действия внутренних данных с классами хранения автоматический, регистровый и внутренний статический является блок, где они определены, включая те вложенные в него блоки, в которых не содержатся переопределения тех же самых имен.

Время эюизни внутренних данных с классом хранения auto и register совпадает со временем выполнения блока. Следовательно, они создаются (размещаются в памяти) в момент входа в блок и уничтожаются при выходе их него. При этом внутренние данные с классом хранения register (они могут иметь только целый тип int) хранятся в быстродействующих машинных регистрах, если это воз­можно, или они эквивалентны данным с классом хранения auto в противном случае.

Время снсизни внутренних статических данных с классом хра­нения static совпадает со временем выполнения всей программы. Они создаются однократно и сохраняют значения между повторны-ми входами в блок, в котором они определены.

Рассмотрим ряд иллюстрирующих примеров.

Файл Р7.СРР Двухфайловый программный проект (файлы Р7.СРР и SAVE4.СРР)

с тремя функциями. Пример иллюстрирует время жизни и область действия параметров функции и внутренних автоматических дан­ных V

^include <stdio.h>

// Прототипы функций voxd save4 ( float ) ; float get ( void ) ;

int main ( void. ) {

// Для функций ввода-вывода

// Возвращает О при успехе

75

Page 77: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / Определение внутренних автоматических данных. Можно и // в такой эквивалентной форме: float mv^ pi; auto float /nv, pi;

pi = 22. Of / 7; sa\re4 ( pi ) ; mv = get ( ) ; print f ( "\n mv ^ %f pi = %f"^ mv, pi ) ;

retvLm 0; }

/* Файл SAVE4.CPP Используется в программном проекте, главная функция кото­

рого имеется в файле Р7.СРР

// Прототипы функций void save4( float ) ; float get( void ) ;

static float fv;

void save4( float mv ) {

fv = mv; return;

}

float get( void ) ( •

return fv; }

Внутренняя автоматическая переменная wv, определенная в функции main, и параметр mv в функции save4 размещены в разных областях памяти и не влияют друг на друга. Первая из них имеет об­ластью действия блок функции main и время жизни, равное времени выполнения main. Параметр mv функции save4 имеет в качестве об­ласти действия блок этой функции и время жизни - время выполне­ния save4.

Автоматические, регистровые внутренние данные и внутрен­ние статические данные можно переопределять при вложении бло­ков друг в друга, что иллюстрирует следующий пример.

/* Файл Р8.СРР Однофайловый программный проект с одной главной функцией.

Пример иллюстрирует переопределение данных во вложенных бло­ках */

76

Page 78: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdio.h> // Для функций ввода-вывода

int main ( void. ) // Возвращает О при успехе {

// Определение внутренних автоматических переменных int counter^ // Действует в блоке main и во

// вложенном блоке while i; // Действует в блоке main и не

// действует во вложенном блоке // while

counter = О; 1 = 10; while ( counter < i ) {

// Переопределение внутренней автоматической // переменной во вложенном блоке - она // располагается в другой области памяти // и действует в блоке while int i; i ^ 0; counter++; printf( "\n counter ^ %d i = %d", counter, i ) /

} print f ( "\n\n counter = %ci i = %d \л", counter, i ) ;

retuizn 0; }

Результаты выполнения этой программы имеют следующий вид:

counter = 1 i = О counter ^ 2 i = О counter = 3 i = О counter = 4 i == О counter = 5 i = О counter = 6 i = 0 counter = 7 i ^ 0 counter = 8 i = 0 counter =91=0 counter = 10 i = 0

counter^ = 10 i = 10

Как указывалось выше, внутренние статические данные имеют ту же область действия, что и арифметические и регистровые дан­ные, но время их жизни максимально и равно времени выполнения программы.

/ / Определение функции void, save ( float mv ) {

// Определение внутреннего статического данного с его // инициализацией при трансляции static int counter == 0;

11

Page 79: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

counter++,

return/ }

Значение counter будет сохраняться между вызовами функции save и, следовательно^ по нему можно судить сколько раз вызыва­лась эта функция.

3.7. Инициализация данных

в языках Си/С-1-+ большинство данных может быть явно или неявно инициализировано в момент их определения. Инициализаци­ей называется присваивание переменной начального значения.

Сводные данные об областях действия, времени жизни и ини-циализируемости объектов Си-программ приведены в табл. 14.

Табл. 14. Области действия, время жизни и инициализация объектов

1 Класс хранения

Область действия

Время жизни

Инициали-зируе-мость

объектов [ Момент

инициали­зации

Инициали­зация по умолча­

нию

Внешний

Програм­ма

Програм­ма

Все

При ком­пиляции

Нулем

Внешний стати­ческий Файл

Програм­ма

Все

При ком­пиляции

Нулем

Параметр функции

Функция

функция

Нет

Нет

Нет

Автома­тический

Блок

Блок

Все

При каж­дом входе

в блок Не

определе­но

Регист­ровый

Блок

Блок

Все

При каж­дом вхо­де в блок

Не опреде­

лено

Внутренний статичес­

кий Блок

Программа

Все

При компи­ляции

Нулем

При отсутствии явных указаний данным с классами хранения extern и static присваиваются нулевые начальные значения. Пере­численные данные и большинство других данных могут быть явно инициализированы в момент определения с помощью указания по­сле их имени знака '=' и константного выраэюения:

static ±nt counter = 0; // Константное выражение не содержит переменных long max_size = 512 * 200L;

78

Page 80: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Данные с классами хранения extern и static инициализируются однократно в момент компиляции. Автоматические и регистровые данные инициализируются в процессе выполнения программы при каждом входе в блок, в котором они определены.

3.8. Упражнения для самопроверки 1. Что напечатает следующая программа?

^include <stdlo.h>

// Прототипы функций int next ( void ) ; tub reset ( void ) ; int last ( void ) ; int nw ( ±nb ) ;

int i = 1;

int main ( void ) {

auto Int 1, j ;

1 = reset( ) ; fox:( j = 3; j <= 3; j++ ) {

prlntf( "1 = %1 j = %l\n", i , J ) ; print f ( "next ( )=%l\n"^ next ( ) ) ; prlntf( "last( )=%l\n"r last( ) ) ; print f( "nw (1+j ) =%l\n", nw(l+j) ) ;

}

jretujrn 0;

static Int 1=10;

int next ( void ) {

T&txuon 1 += 1; }

int last ( void ) {

return 1 -= 1; }

int nw ( Int 1 )

79

Page 81: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

{

static int j = 5;

rGtuJcn 1 = j += 1;

/ 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 /

extern Int i /

±nt reset ( void. ) {

jretuxn i /

;

2. Что напечатает следующая программа?

^include <std±o.h> // Прото типы функций int next ( int ); int reset ( void. ) ; int last ( int ) ;

int i = 2;

int main ( void ) {

auto int i, j ;

i = reset ( ); £or( j = 1; j <= 2; j++ ) J

printf( "\ni = %d j = %d\n"r i, j ); print f( "next ( i ) = %d\n", next ( i ) ); printf( "last( i ) = %d\n'\ last ( i ) );

}

return 0; } / / 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 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 k k k

int reset( void ) {

return ++i; } / / 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 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 k k k

int next ( int j ) {

return j = i++; } / / 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 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 k k k

int last ( int j ) {

static int i = 10;

80

Page 82: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

return j = i - - / i

Как уже указывалось, ответы для этих упражнений можно про­верить в разд. 18.

3.9 Производные типы данных

в языках Си/С++ предусмотрены несколько производных ти­пов данных, среди которых основными являются: • массивы; • структуры; • объединения.

3.9.1. Массивы

Структура данных, называемая массивом, позволяет опреде­лить непрерывный (по отношению к расположению в памяти) набор однотипных объектов данных.

Приведем пример определения массива:

Этот массив символьного типа способен хранить 15 символов. Начальные значения элементов не определены: определение мас­сива дано без инициализации V char kaf__name [ 15 ];

Индивидуальный доступ к отдельным элементам массива осу­ществляется с помощью индексированных имен:

kaf_name[ О ] . . . kaf_name [ 14 ]

Обратите внимание, что индексы элементов массива изменя­ются в диапазоне О ... 14, а не в диапазоне 1 ... 15.

Массивы могут иметь любой класс хранения, кроме register. Области действия и времена жизни массивов такие же, как и у про­стых данных. Массивы моэюно инициализировать (рис. 30):

chstr kaf_name[ 15 ] = { 'К', 'а', 'ф\ ' е \ 'д'г 'Р\ ' а ' , ' Ч 'АЧ ^B^, ' Г ' , ^\0^ } /

По принятому соглашению массивы символов, содержащие строку, после конца строки обязательно должны содержать нулевой байт.

81

Page 83: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

По этой причине массив kafjname может хранить в виде стро­ки название кафедры, состоящее не более чем из 14 символов!

0

к 1 а

2

Ф 3 е

4

Д

5

Р

6 а

7 8 А

9 В

10 Т

11 \0

12 ?

13 ?

14 ? kaf_name |

Рис. 30. Инициализация массива

Символьный массив можно инициализировать и более удоб­ным способом:

char kaf_name[ 15 ] = "Кафедра АВТ";

Это определение символьного массива с инициализацией пол­ностью эквивалентно предыдущему. Вместе с тем, приведенное ни­же определение символьного массива с инициализацией дает не­сколько иной результат:

cJiax- kaf_name [ ] = "Кафедра АВТ"/

В последнем случае в памяти резервируется 12 байтов - ровно столько, сколько требуется для хранения инициализирующей стро­ки.

Можно аналогичным образом определять массивы с любым типом элементов с инициализацией или без нее:

/ / Определение массива из 9 элементов с типом double без // инициаЛИЗации double а[ 9 ]; // Определение массива из 4 элементов с типом double с // инициализацией double b[ 4 ] = { 1.0, 2.0, -3.1, 4.5 } ;

Рассмотрим практически значимый пример, в котором исполь­зуется массив значений с плавающей точкой. Предварительно опре­делим понятие стек, широко применяемое в программировании и вычислительной технике.

Стек - непрерывная область оперативной памяти, в которой хранятся объекты заданного типа, и работа с которой организована по правилу "последним записан - первым прочитан". По этой при­чине стек часто называют очередью типа LIFO (Last Input First Output).

Файл P9.CPP Двухфайловый программный проект (файлы Р9.СРР и STACK.СРР)

с тремя функциями: главной функцией и функциями занесения в стек и извлечения из стека. Стек организован на базе внешнего

82

Page 84: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

статического массива, состоящего из элементов с типом float. Стек доступен в функциях не за счет передачи через список па­раметров^ а за счет своей области действия и времени жизни.

^include <stdio.h> // Для функций ввода-вывода

// Прототипы функций void push ( float ) ; void, pop ( float &r int & ) /

int main ( void ) // Возвращает 0 при успехе {

int flag; // 0 - извлечение из стека не // выполнено г иначе - выполнено

float out_value;// Значение, полученное из стека

push ( 2.4f ) ; // Занести в стек 2.4f push ( -17. 4f ) ; // Занести в стек -17.4f for( int 1 = 0; i < 3; ±++ ) {

// Извлечение из стека pop ( out_value, flag ) ; // Анализ полученного результата if( flag ) {

printf( "\n Результат извлечения: %f ", out_value ) ;

} else {

printf( "\n Стек пуст" ) ; }

}

return 0; }

Файл STACK.CPP Содержит функции для занесения элемента и извлечения эле­

мента из стека. Используется в программном проекте, главная функция которого имеется в файле Р9.СРР V ^include <stdio.h> // Для функций ввода-вывода

// Прототипы функций (в принципе - прототипы здесь не нужны, // мы оставляем их в этом файле только для унификации void push ( float ) ; void pop ( float &, int & ) ;

^define N10 // Размер стека // Стек: массив из 10 элементов - доступен только в этом

83

Page 85: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// файле, время жизни - программа static float s[ N ] ;

// Указатель вершины стека: вначале стек пуст static unsigned, int

top;

// Занесение в стек void push(

float V ) // Заносимое значение {

if( top < N ) {

s[ top++ ] = V/ } else {

print f ( "\n Стек полон - занесение не выполнено" ) ; }

return; }

// Извлечение из стека void pop (

// Извлеченное значение - передается по ссылке float &out, // О - извлечение не выполнено (стек пуст) int &f )

{ if( top > О ) {

out = s[ --top ]; f = 1; } else (

f = 0; }

return;

Результаты выполнения данной программы имеют вид:

Результат извлечения: -17.400000 Результат извлечения: 2.400000 Стек пуст

Рассмотренные выше примеры использовали так называемые одномерные массивы, обращение к элементам которых выполняется с помощью одного индекса (индексного выражения). Указанное ин­дексное выражение (например, /+/) должно иметь целый тип без

84

Page 86: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

знака, так как индексы элементов массива начинаются со значения индекса, равного нулю.

Наряду с такими массивами можно использовать двухмерные массивы и массивы большей размерности. Рассмотрим еще один ил­люстрирующий пример.

Файл Р10.СРР Однофайловый программный проект с одной главной функцией.

Пример иллюстрирует работу с двумерными массивами из элемен­тов символьного типа V

^include <stdlo.h> // Для функций ввода-вывода

^define N 8 // Строковый размер массива ^define М 16 // Столбцовый размер массива

// Определение двухмерного символьного массива с // инициализацией: N строк, каждая строка из М символов char arr__kaf_name [ N ] [ М] = {

"Состав ФТК:", / / Инициализация первой строки "1. Кафедра АВТ", "2. Кафедра ТК", "3. Кафедра САУ", "4. Кафедра МУС", " 3 . Кафедра ИИТ", "6. Кафедра САПР", " 7 . Кафедра СУД"

} ;

int main ( void ) // Возвращает О при успехе {

a u t o ±nt Index; // Индекс строки двухмерного массива

fori Index = 0; Index < N; lndex++ ) {

print f ( "\n %s "r arr_kaf_name [ Index ] ) . }

return 0;

Результаты выполнения данной программы имеют вид:

Состав ФТК: 1. Кафедра АВТ 2. Ка федра ТК 3. Кафедра САУ 4. Кафедра ИУС 5. Кафедра ИИТ

85

Page 87: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

6, Кафедра САПР 7. Кафедра СУД

Используемый в программе двухмерный массив arrkafname хранит информацию, представленную в табл. 15.

Строки/ столбцы

0 1 2

6 7

0

С 1 2

6 7

1

о

2

с

Т а б л . 3

т К К

К К

4

а а а

а а

1Ь. 5

в Ф Ф

Ф Ф

С т р у к т у р а 6

е е

е е

7

Ф Д

д

д д

8

Г р р

р р

м а с с и в а 9

К а а

а а

10

\0

11

? А Т

С с

12

9

В к А У

13

?

т \0

п Д

14

? \0 ?

р \0

15

? 7 ?

\0 ?

В этой таблице элемент массива аггjkaf_name[ 6][ 7 ] хранит код символа 'д'. Элементы этого массива в оперативной памяти ком­пьютера располагаются в подряд идущих байтах по строкам:

arr_kaf_name [ О ][ О ] arr_kaf_name [ О ] [ 15 J arr_kaf_name [ 1 ][ О ] агг kaf_name[ 1 ][ 15 ]

arr_kaf__name[ О ][

агг kaf namef 1 ] [

arr_kaf__name [ 7 ] [ О ] arr_kaf__name [ 7 ][ 15

arr_kaf_name [ 7 ] [

3.9.2. Массивы - как аргументы функций

Массив, в отличие от других видов данных, рассмотренных нами, всегда передается в функцию по ссылке, а не по значению. По­чему?

Потому, что массив может занимать много места в памяти и копировать его, как это делается при передаче аргумента по значе­нию, расточительно.

Так как массив занимает смежные ячейки памяти, то при ис­пользовании имени массива в качестве аргумента языки Си/Сн-+ обеспечивают передачу функции адреса первого элемента этого массива. Модифицируем последний пример.

Файл Р11,СРР Одно файловый программный проект с двумя функциями. Пример

иллюстрирует передачу массива в функцию

^include <stdlo.h> // Для функций ввода-вывода

86

Page 88: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^define N8 // Строковый размер массива idefine М 16 // Столбцовый размер массива

// Определение двухмерного символьного массива с // инициализацией: N строк^ каждая строка из М символов static char arr_kaf_name [ N ] [ М] = {

"Состав ФТК:", // Инициализация первой строки "1 . Кафедра АВТ", "2. Кафедра ТК", "3, Кафедра САУ", "4. Кафедра МУС", "5. Кафедра ИИТ", "6. Кафедра САПР", "7. Кафедра СУД"

) ;

// Прототип функции: обратите внимание как записывается // параметр - двумерный массив. Здесь прототип также не // обязателен, так как файл содержит определение функции void display ( cha.r arr_kaf_name [ N] [ М ] ) ;

int main ( void ) // Возвращает 0 при успехе {

// Вызов функции, печатающей строки массива: обратите // внимание, как записывается аргумент-массив display ( arr__kaf_name ) ;

return 0; }

// Печать строк двухмерного массива void display (

// Массив для печати: передается по ссылке, т.е. не // копируется char arr kaf name [ N] [ М ] )

{ auto int index; // Индекс строки двухмерного массива

fori index = О; index < N; index++ ) {

printf( "\n %s ", arr_kaf_name[ index ] ) ; }

return; }

Результаты выполнения этой программы выглядят так же, как и у предыдущей программы.

В дополнение к сказанному выше приведем пример инициали­зации двумерного массива с типом, отличным от символьного типа:

87

Page 89: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Определение внешнего статического массива с // инициализацией. Массив содержит элементы целого типа: // N строк, каждая строка из М элементов stable ±zit а[ 3 ] [ 4 ] = {

{ 1 , 3 , 5 , 1 } , // Инициализация первой строки { 2, 4, 6, 8 } , { 3, 5, 7, 9 }

} ;

Резюмируя сказанное, можно заключить, что массив представ­ляет собой объект, состоящий из некоторого количества взаимосвя­занных однотипных элементов.

В противоположность массиву, может потребоваться объект, состоящий из некоторого количества взаимосвязанных разнотипных элементов. Такое данное называют в языке Си структурой. Сразу же отметим, что объект с типом "структура" в языке C++ имеет бо­лее широкий смысл, чем в языке Си. Ниже мы вначале рассмотрим структуру с точки зрения языка Си.

3.9.3. Упражнения для самопроверки

Написать прототип, определение функции и пример вызова функции для решения следующей задачи: • вычислить сумму элементов одномерного массива х[ 7V ] {N ~ 50) це­

лого типа имеющих нечетные индексы; • получить одномерный массив z[N] (N = 40) из двух заданных масси­

вов целого типа jc[ Л ], >'[ Л ] по правилу:

z[ i ] := тах{ х[ 1 ], у[ i ] }

Возможный вариант ответа можно посмотреть в разд. 18.

3.9.4. Структуры

Различают объявление структуры и определение структурного объекта. Объявление структуры и определение структурного объек­та можно выполнять в Си-программе по отдельности или же совме­стно. Поясним сказанное примерами.

/* Объявление структуры, содержащей сведения о студенте. Об­

ратите внимание, что данное объявление не размещает никакого объекта в оперативной памяти, а лишь вводит новый структурный тип

3timet STUDENT__INFO

88

Page 90: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

};

// Фа культет char fak_name[ 30 ]; char fio[ 30 ];// ФИО // Номер группы char group_name[ 7 ]; char date[ 9 ];// Дата поступления студента в ВУЗ float stip/ // Размер стипендии

Здесь использованы следующие соглашения:

struct - начало объявления и/или определения; STUDENT_INFO - имя (тэг) структуры; {

... - список элементов (полей) структуры } ;

Обратите внимание, что, тэг структуры принято записывать с использованием прописных букв. Элементы структуры могут иметь любой тип, допустимый в языке Си, например, тип массива, струк­турный и т.п. Как уже указывалось, приведенное объявление создает новый тип с именем STUDENTINFO.

Для создания же структурированного объекта надо использо­вать его определение:

// Данное определение размещает объект current в оперативной // памяти компьютера и использует ранее сделанное // объявление типа STDENT_INFO struct STUDENT_INFO

current; // Для Си или C++

ИЛИ

/ / Только для C+ + , если нет объекта с другим типом и именем // STUDENT__INFO STUDENT_INFO current;

Приведенное выше объявление структуры STUDENT INFO и определение структурированного объекта current можно объеди­нить, причем это даст такой же результат:

/'^ Комбинация объявления структурного типа и определения

структурного объекта V struct STUDENT_INFO // Тэг структуры {

// Факультет char fak name[ 30 ];

89

Page 91: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

char fio[ 30 ];// ФИО // Номер группы char group_name[ 7 ]; char date[ 9 ];// Дата поступления студента в ВУЗ float stip; // Размер стипендии

} current; // Имя структурного объекта

Как и массивам^ структурам может быть приписан любой класс хранения, за исключением класса register. Тем самым будут определены область действия и время жизни структурированного объекта. Структуры, как и массивы, можно инициализировать.

Дополнительно отметим, что для элемента структуры статиче­ский класс хранения в языке Си использовать нельзя. Такая возмож­ность предусмотрена только в языке C++. При этом в языке C++ для нескольких объектов одного и того же структурного типа в памяти размещается только один элемент структуры со статическим клас­сом хранения, а не несколько.

Для ссылки на элементы структурного объекта current следует использовать операцию "точка". Эту операцию называют квалифи­кацией элемента.

/ / Символ, начинающий название факультета current.fak_name[ О ] current.stip // Размер стипендии студента

Сколько байтов памяти занимает current? Точный размер этого объекта можно определить с помощью операции sizeof:

s±zeo£( current ) или sizeof ( struct STUDENT__INFO ) ИЛИ sizeof( STUDENT_INFO )

Последний вариант допустим только в языке C++. Имена элементов структуры не конфликтуют с такими же име­

нами других объектов, имеющих отличающиеся типы:

Int stip; // правильно struct STUDENT__INFO

currentl; // Тоже правильно

Объекты stip и currentl.stip располагаются в разных областях памяти и имеют разный смысл: с ними можно работать независимо.

Как уже указывалось, структурные объекты можно инициали­зировать по тем же синтаксическим правилам, что и массивы:

/* Объявление структурного типа и определение структурирован­

ного объекта с его инициализацией

90

Page 92: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

struct STUDENT_INFO // Тэг структуры {

// Факультет сЬяг fak_name[ 30 ]; char fio[ 30 ];// ФИО // Номер группы cbatr group__name [ 7 ] ; char date[ 9 ];// Дата поступления студента в ВУЗ float stip; // Размер стипендии

} current = // Имя структурного объекта {

"ФТК", "Иванов И. И. " , "1081/4", "01-09-97" , 100 000,Of

} ;

// Можно создавать массив структур, например: struct STUDENT_INFO

group[ 12 ]; group[ 5],stip // Стипендия шестого студента группы

3.9.5. Структуры в качестве аргументов функций

Структура может быть передана в функцию целиком. При этом могут быть использованы оба способа передачи - по значению и по адресу (по ссылке). Как указывалось ранее, передача структуры в функцию по значению предполагает ее копирование в памяти, что при больших размерах структуры нецелесообразно. По этой причине следует в качестве основного способа использовать передачу струк­туры в функцию по ссылке. Приведем два иллюстрирующих приме­ра:

Файл Р14.СРР Однофайловый программный проект с двумя функциями. Пример иллюстрирует передачу структуры в функцию по значению (следует заметить, что этот способ не рекомендуется) V

iinclude <stdlo.h> // Для функций ввода-вывода

/* Объявление структурного типа, определение и инициализация

структурированного объекта V

struct STUDENT_INFO // Сведения о студенте {

// Факультет char fak__name[ 30 ];

91

Page 93: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

char // Группа char char float

current =

fiol 20 ];// ФИО

group^^name [ 7 ]; date[ 9 ];// Дата поступления в университет stlp;

{

};

// Размер стипендии // Определение объекта

"ФТК:", "Иванов И. И. "1081/4", "01-09-97", 100000,Of

// Прототип: обратите внимание, как записывается параметр // структура при его передаче по значению. В принципе, // здесь прототип не обязателен, так как файл содержит // определение функции void dlsplay_s ( struct STUDENT_INFO ) ;

int main ( void ) {

// Возвращает 0 при успехе

// Вызов функции, печатающей строки массива: обратите // внимание, как записывается аргумент-структура d±splay_s ( current ) ;

retvLrn О; }

// Печать элементов структуры void display_s (

struct STUDENT_INFO // Структура для печати: передается S ) // по значению, т.е. копируется

{

printf( "\п Факультет: %s S.fak name ) ; printf( "\n ФИО: %s ", s.fio ) ; printf ( "\n Номер группы: %s ", s.group_name ) . printf( "\n Дата поступления: %s ", s.date ) ; printf( "\n Размер стипендии: %f ", s.stip ) ;

return;

/* Файл P15.CPP Однофайловый программный проект с двумя функциями. Пример

иллюстрирует передачу структуры в функцию по ссылке. Такой способ передачи структуры в функцию рекомендуется в качестве основного */

^include <stdio.h> // Для функций ввода-вывода

92

Page 94: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Объявление структурного типа, структурного объекта V struct STUDENT INFO

определение и инициализация

// Сведения о студенте {

// Факультет cha,r char // Группа сЬа.г char float

current =

fak__name[ 30 ]; fio[ 20 ];// ФИО

group__name [ 7 ]; date[ 9 ];// Дата поступления stip; // Размер стипендии

// Определение объекта

в университет

{

};

"ФТК:", "Иванов И. И. "1081/4", "01-09-97", 100000,Of

// Прототип: обратите внимание, как записывается параметр // структура при его передаче по ссылке. void display_s ( struct STUDENT_INFO & ) ;

int main ( void ) {

// Возвращает 0 при успехе

// Вызов функции, печатающей строки массива: обратите // внимание, как записывается аргумент-структура display_s( current ) ;

return 0; }

// Печать элементов структуры void display__s (

struct STUDENT__INFO // Структура для печати: передается &S ) // по ссылке, т.е. не копируется

printf( "\п Факультет: %s ", s.fak_name ) ; printf( "\n ФИО: %s ", s.fio ) ; print f ( "\n Номер группы: %s ", s.group_name ) ; printf( "\n Дата поступления: %s ", s.date ) ; printf( "\n Размер стипендии: %f ", s.stip ) ;

return; }

3.9.6 Упражнения для самопроверки

1. В текстовом файле "ctrl4.dat" имеется 15 строк, каждая из кото­рых имеет следующий формат:

93

Page 95: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

число_ 1 число_2

Здесь "число_Г' определяет вид геометрической фигуры (1 - квадрат, 2 -круг), а "число_2" - параметр фигуры (при "число 1" — 1 - длина сторо­ны, а при "число_2" = 2 - радиус).

1.1. Написать определение массива структур для хранения ука­занных сведений о геометрических фигурах. Каждый элемент массива должен иметь следующие поля: • имя фигуры; • длина стороны или радиус; • площадь фигуры.

1.2. Написать фрагмент программы для чтения из файла на маг­нитном диске "ctrl4.dat" информации о геометрических фигурах.

1.3. Написать фрагмент программы, вычисляющий площади геометрических фигур.

1.4. Написать фрагмент программы, печатающий в файл "ctrl4.out" параметры геометрических фигур. Сведения об отдельных фигурах располагаются в отдельной строке и имеют вид:

круг: радиус= . . . , площадь^ . . .

квадрат: длина стороны= . . . , площадь= . . .

Предусмотреть контроль корректности значений, возвращаемых функциями библиотеки Си, указать какие включаемые файлы требует представленный фрагмент.

Page 96: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

4. ОПЕРАТОРЫ И УПРАВЛЕНИЕ ИХ ИСПОЛНЕНИЕМ

В теории программирования доказано, что любая программа может быть закодирована (записана) с помощью комбинаций трех элементарных конструкций, каждая из которых имеет только один вход и только один выход. Как уже упоминалось, такими элемен­тарными конструкциями являются следующие конструкции: • последовательность операторов (следование); • выбор (ветвление); • итерация (цикл).

Вы уже знакомы с несколькими различными операторами язы­ка Си, которые реализуют эти конструкции. К ним относятся опера­торы z/, while, do-while и for.

Однако кроме элементарных конструкций в языках Си/С-н+ существуют и другие операторы, которые помогают облегчить про­граммирование. Перечень операторов языков Си/С++ в форме син­таксической диаграммы показан на рис. 31.

4.1. Пустой оператор

Пустой оператор состоит из одного символа — ";". Он не вы­полняет никаких действий. Тогда возникает вопрос - а зачем он ну­жен?

Пустой оператор используется как заполнитель в других, более сложных операторах, например, в операторах z/, for, while. По этой причине пустой оператор будет обсуждаться при рассмотрении ука­занных операторов и им подобных.

4.2. Операторы-выражения

Операторы-выражения представляют собой просто выражения, за которыми следуют точка с запятой, или запятая, или некоторый другой контекст. Многие операции, а, следовательно, и виды выражений, еще не были рассматрены, здесь ограничимся необходимым минимумом, изложенным выше.

95

Page 97: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Оператор

->( return

->^ break

->r continue

- ^ goto J

' ^ •

V ^

' ^ L ^

< < >

Выражение

Выражение

A

4'> ^ ^

ь ^ Идентификатор

jf

Блок

while

L ^ V ' У ^

^

^

switch

do-while

for

u i ic ; |^a 1 Kjyj

Рис. 31. Перечень операторов

4.3. Операторы break и continue

Эти операторы (break - прервать, continue - продолжить), по­добно пустому оператору, используются в составе других операто­ров: switch, while, do-while и for. Как и в случае пустого оператора будем касаться их по мере обсуждения тех операторов, в состав ко­торых они могут входить.

4.4. Блок операторов

Как уже указывалось выше, блок (иногда называемый состав­ным оператором), состоит из определений объектов (данных), за ко­торыми следует последовательность операторов. Блок заключается в

96

Page 98: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

фигурные скобки. Повторно отметим, что в языке C++ определе­ния объектов и операторы могут чередоваться, но определения объектов долэюны всегда предшествовать их использованию.

Обратите также внимание на то, что в описании синтаксиса языков Си/С++ всюду, где указан "оператор", в качестве последнего можно использовать блок операторов.

4.5. Оператор return

Этот оператор имеет следующие две формы:

return ( выражение ) ; ИЛИ эквивалентно return, выражение;

Первая форма обеспечивает передачу управления из текущей функции, не имеющей возвращаемого значения, на оператор, непо­средственно следующий за вызовом функции. Вторая форма опера­тора return обеспечивает не только указанную передачу управления, но еще и возвращает значение в место вызова. Следовательно, эта форма применяется в блоке функции, имеющей возвращаемое зна­чение. При этом тип выражения в операторе return и тип возвра­щаемого функцией значения должны совпадать.

Обратите внимание, что хотя оператор return\ может отсутст­вовать (в этом случае компилятор его вставляет сам перед закры­вающей фигурной скобкой, завершающей функцию), хороший стиль программирования предполагает явную запись этого оператора. К сожалению, в существующей литературе эта рекомендация не всегда выполняется.

4.6. Оператор if

Этим оператором уже ранее пользовались и достаточно интен­сивно. Это объясняется тем, что / / один из основных структуриро­ванных операторов языка Си. Синтаксическая диаграмма этого опе­ратора представлена на рис. 32.

Работа этого оператора состоит в том, что вначале вычисляет­ся значение заключенного в скобки "выражения". Если его значение отлично от нуля (!0, "истина"), то выполняется "оператор_1". Если использовано служебное слово else (иначе) и значение "выражения" равно нулю (=0, "ложь"), то выполняется "оператор_2", указанный после служебного слова else. После выполнения "оператора 1" или

97

Page 99: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

"оператора_2" управление передается следующему за if оператору программы.

!=0

dXIH Выражение Оператор_1

= 0

•Г else V ^ Оператор_2

Рис. 32. Синтаксическая диаграмма оператора if

Если значение "выражения" равно нулю, но служебное слово else отсутствует, то управление сразу же передается следующему оператору программы. Обратите внимание, что если вычисление значения "выражения" дает нецелый тип, то полученное значение перед анализом преобразуется к целому типу. Как обычно, в качест­ве операторов "оператор_1" или "операторе" можно использовать блоки операторов:

±£( а > Ь ) так = а;

else max = b;

Обратите также внимание на местоположение символов ';'. На­личие этих символов необходимо потому, что в качестве "операто-ра_Г' и "оператора_2" используются операторы-выражения, кото­рые должны заканчиваться ';' (см. рис. 31).

/ / Эквивалентная запись ±f(a>b) {

так = а; } else {

max = b; )

В соответствии с синтаксической диаграммой для операторов, приведенной на рис. 31 , после операторов-блоков символ ";" не ста­вится. По этой причине символ "точка с запятой" отсутствует после символов " } " , завершающих блоки.

Поскольку в качестве "оператора 1" и "оператора_2" можно, в частном случае, использовать и оператор if то операторы if могут быть вложенными:

98

Page 100: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

char ch;

±f( ch -= 'a ' ; index = 1;

else ±f( ch == 'b ' ; index =2;

else ±f( ch == 'y' ) index = 25;

else ±f(ch== ' z ' ) index = 26;

else index = 0;

В этом примере при значении ch, отличном от одной из строч­ных букв латинского алфавита, переменной index будет присвоено нулевое значение. Обратите внимание на местоположение вложен­ных операторов if. Хороший стиль программирования рекомендует помещать вложенный if в охватывающий г^после else\

// Пример с "подводными камнями"

dovble а = 77. 7; int i - 5; if(i<3)

±f( i ^= 2 ) a = 1.1;

else a = 2.2;

// Здесь имеем a = 7 7 .7

/ / Другой пример double a = 77. 7/ int i = 5; if(i<3) {

±f( i -= 2 ) a = 1.1;

I else

a = 2.2; // Здесь имеем a =2.2

Почему получены такие результаты? Потому, что языки Си/С++ следуют обычному правилу, согласно которому служебное слово else связывается с ближайшим предыдущим //, с которым еще не было связано else. Иной порядок, как следует из рассмотренного примера, может быть установлен с помощью фигурных скобок.

99

Page 101: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в заключение отметим, что при ветвлении на два направления достаточно использовать один оператор if. При ветвлении на три и более направлений можно использовать вложенные операторы if В последнем случае возможна и другая альтернатива, которая рас­сматривается далее.

4.7. Оператор switch

Вернемся к рассмотренному выше в конце подразд. 4.6 приме­ру. Этот пример трудно читать и еще труднее сопровождать. В подобной ситуации, когда требуется передавать управление одному из нескольких операторов в зависимости от значения выражения, можно использовать оператор switch (проверяемое выражение при этом не может иметь вещественный тип). Приведенный выше фрагмент при использовании оператора switch приобретает следующий более наглядный и удобный вид:

char ch;

switch ( ch ) {

case 'a': 1ndex = 1; break;

case 'Ь': index = 2; break/

case 'у': Index = 25; break;

case 'z ': index = 26; break;

default: index = 0;

}

Синтаксис оператора switch (переключатель) поясняется диа­граммой, приведенной на рис. 33.

Выполнение оператора switch начинается с вычисления заклю­ченного в скобки "выражения", которое долэюно давать результат целого или символьного типа. Затем просматриваются друг за дру­гом префиксы case (случай), вычисляются указанные после служеб­ного слова case "константныевыражения" и полученные значения сравниваются со значением выражения, указанного после служебно­го слова switch. Если эти результаты совпали, то управление переда­ется оператору, следующему за соответствующим служебным сло­вом case. Если ни одного совпадения не произошло и при этом ука­зано необязательное служебное слово default (по умолчанию), то

100

Page 102: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

управление передается оператору, следующему за default. Если ни одного совпадения не произошло, а служебное слово default отсут­ствует, то управление передается оператору, непосредственно сле­дующему за последней фигурной скобкой оператора switch.

OnepaTop_switch

( switch V ^ T ( V H Выражение V->( ) V w OnepaTop_case

Оператор case

case ' N I Константное I ^ / ' ^ T ^ [ J \ _выражение | I V ' У I f I

Ц^ default y

Оператор V-ffi }

Рис. 33. Синтаксическая диаграмма переключателя

После передачи управления в блок операторов case исполне­ние указанных в нем операторов производится до конца блока (от одной альтернативы к другой), если только последовательность вы­полнения операторов не будет изменена операторами break или goto. Действие оператора break сводится к передаче управления оператору, следующему за последней закрывающей фигурной скоб­кой switch (рис. 34). Следует также отметить, что порядок следова­ния case в блоке безразличен.

Подведем итоги всему сказанному. Для программирования ветвлений на три или более направлений имеются две альтернативы - использование вложенных операторов / /или использование опера­тора switch. Из приведенных примеров видно, что использование оператора switch является более наглядным и простым. Однако этим оператором нельзя воспользоваться, если проверяемое выражение имеет вещественный тип или если проверяется комбинация не­скольких условий. В этом случае приходится использовать вложен­ные операторы if

4,8. Оператор while

Этот оператор уже рассматривался нами. Он является операто­ром цикла с предусловием (рис. 35):

101

Page 103: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

switch( выражение ) {

case конст._выр,_1: Операторы break; —

// Выражение не может быть // вещественного типа

case конст._выр,_2: Операторы break;

Рис. 34. Переключатель

Оператор while

-Н while (^ while ")-КГО~Л Выражение Оператор

Рис. 35. Синтаксическая диаграмма оператора while

Работа оператора while заключается в следующем. 1. Вычисляется значение "выражения" и, если его тип отличен

от целого, то полученное значение приводится к целому типу. 2. Если значение "выражения" отлично от нуля ("истина"), то

выполняется "оператор" и осуществляется переход к п. 1. 3. Если значение "выражения" равно нулю ("ложь"), то выпол­

нение цикла завершается и управление передается оператору, сле­дующему за оператором while.

Таким образом, оператор while эквивалентен следующей по­следовательности операторов:

cycle: ±f( выражение ) {

}

опера тор // Оператор передает управление на оператор^ // следующий за меткой cycle (см. подробнее об // операторе goto ниже) goto cycle;

"Выражение", используемое в операторе while, называется ус­ловием повторения цикла^ В общем случае, конечно же, одним из результатов выполнения "оператора", составляющего тело цикла, должно быть изменение значений переменных, входящих в "выра­жение". В противном случае оператор while выполнялся бы беско­нечно.

102

Page 104: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Обычно тело цикла представляет собой блок операторов. Это позволяет обеспечить циклическое выполнение группы операторов. Но в частном случае, если "выражение" имеет нулевое значение, те­ло цикла не будет выполняться ни разу. Это является отличительной особенностью циклов с предусловием.

Рассмотрим и проанализируем ряд специальных примеров.

/ / пример 1 while ( fun( ) == 1 ) ; // В качестве тела цикла использован

// пустой оператор

В этом примере вся работа осуществляется за счет выполнения условия цикла и не требуется никаких других операторов (fun() -функция, возвращающая некоторое целое значение; как только она вернет значение, отличающееся от единицы - выполнение цикла за­кончится). В подобных случаях щ\л удовлетворения синтаксических правил следует в качестве тела цикла указать точку с запятой, кото­рая обозначает пустой оператор.

/ / пример 2 while ( 1 ) {

bxrea-k ;

}

Этот пример демонстрирует преднамеренное создание "беско­нечного" цикла. Так как "выражение" в этом цикле всегда имеет значение 1 ("истина"), то цикл может исполняться неограниченное число раз. Одним из способов завершения выполнения такого цикла является использование в его теле оператора break. Как только это произойдет, управление будет передано оператору программы, сле­дующему за оператором while. Оператор break подобным же обра­зом действует в теле цикла и других циклических операторов, кото­рые будут рассмотрены ниже.

Циклические операторы, так же как и условные операторы, можно вкладывать друг в друга. В подобных случаях оператор break обеспечивает выход только из того цикла, в теле которого он на­ходится.

В языке предусмотрен оператор continue^ который позволяет пропускать оставшуюся после него часть тела цикла и начать новую итерацию, т.е. новое выполнение тела цикла сначала. Таким обра­зом, по своему действию операторы break и continue являются опе­раторами с ограниченным диапазоном передачи управления. Дейст­вие их показано на рис. 36.

103

Page 105: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

while( выражение) {

while( выражение ) {

break;

}

continue;

}

Рис. 36. Использование операторов break и continue в цикле while

4.9. Оператор do-while

Оператор do-while, часто называемый оператор do, является циклом с постусловием и имеет синтаксическую диаграмму, пред­ставленную на рис. 37 (обратите внимание на наличие в конце опе­ратора точки с запятой).

Оператор do-while

do - ^ / d o j - H Оператор V-U while V ^ / ( V H Выражение Н->Г ) \>( \ )—•

Рис. 37. Синтаксическая диаграмма оператора do-while

Работа оператора поясняется следующей эквивалентной по­следовательностью операторов:

cycle: оператор ±£( выражение ) goto cycle;

Из эквивалентного представления следует, в частности, что в отличие от цикла while, тело цикла do-while, независимо от значения "выражения", будет выполнено не менее одного раза. Для изменения хода выполнения операторов, составляющих тело цикла можно вос­пользоваться операторами break и continue (рис. 38).

do {

Ьгяак"

} while( выражение );

do {

continue*

} while( выражение);

Рис. 38. Операторы break и continue в цикле do-while

104

Page 106: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Остальные особенности цикла while (использование пустого оператора, "бесконечный" цикл, вложенность циклов и т.п.) в рав­ной степени относятся и к циклу do-while.

4.10- Оператор for

Этот оператор также является циклом с предусловием и имеет синтаксическую диаграмму, представленную на рис. 39. Работа опе­ратора for поясняется следующей эквивалентной последовательно­стью операторов:

выражение! cycle: if( выражение2 ) {

опера тор выражение 3 goto cycle;

}

В качестве "выражения!" и "выраженияЗ" можно использовать списки выражений, в которых выражения разде^лены запятыми.

Оператор for

>j Выражение_1 о

Выражение_2

Выражен ие_3 • о Оператор

Рис. 39. Синтаксическая диаграмма оператора/Ьг

/ / пример char

int

namel 20 ] = "Кафедра АВТ"^ kaf__name [ 20 ]; 1;

// Копирование пате в kaf_name с использованием цикла for fori i = 0; пате[ i ] != '\0'; i + + ) {

kaf_name [ i ] = name [ i ]/ } kaf name[ i ] = '\0' /

105

Page 107: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Копирование name в kaf_name с использованием цикла while // Инициализация управляющей переменной цикла 1 - О/ while ( пате[ i ] != '\0' ) {

kaf__name[ i ] = name [ i ]; // Модификация управляющей переменной цикла ±++;

} kaf_name[ i ] == '\0'/

// В качестве упражнения предлагается этот же фрагмент // записать с использованием цикла do-whlle

// В заключение выполним копирование строк с использованием // строковой функции St г еру (подробнее о строковых функциях // будет сказано ниже) ^include <string.h> // Для строковых функций • strcpy ( kaf__name, name ) ;

Из рассмотренного примера следует, что в подобных случаях оператор уЬг удобнее и легче для восприятия, чем операторы while и do'while. Это обусловлено тем, что все три выражения, связанные с организацией цикла (инициализация, проверка и модификация усло­вия цикла) собраны вместе. За счет этого не приходится просматри­вать исходный код в поисках выражений, обеспечивающих инициализацию и модификацию, как пришлось бы делать при применении операторов while и do-while.

Из примера также следует, что управляющая переменная циклических операторов после завершения соответствующих циклов сохраняет свое значение и значением этой переменной, при необходимости, можно пользоваться, как это было сделано в нашем примере.

Из синтаксической диаграммы цикла for следует также, что пустой оператор может быть использован для пропуска любого из выражений, входящих в состав этого оператора:

for( ; ; ) оператор // Бесконечный цикл

Как и для циклов while и do-while, для цикла for последова­тельность передачи управления в теле цикла может быть изменена с помощью операторов break и continue (рис. 40).

106

Page 108: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

for( выр1; выр2; вырЗ ) for( выр1; выр2; вырЗ ) { {

break; continue;

} Передача управления на «выражениеЗ» - см. эквивалентное представление

Рис. 40. Использование операторов break и continue в цикле/Ьг

Пример. Одномерный массив вещественного типа напечатать по четыре элемента в строку по 15 позиций на элемент

JV

^define N100 // Размер массива

^include <stdlo.h> // Для функций ввода-вывода

{ float а[ N ]/ // Массив для печати

printf( "\п" ) ; // Начать печать с новой строки for( int i = О; 1 < N; i-f-f ; {

±f( ( 1 % 4 ) == 0 ) printf( "\n" ) ; pr±ntf( "%15g", a[ i ] ) ;

}

}

В качестве упражнения рекрмендуем Вам запрограммировать эту же задачу с использованием циклов while и do-while.

Повторно напоминаем, что в языке C++ в блоке можно чередо­вать определения объектов и операторы, но определение объекта обязательно должно предшествовать его использованию в операто­ре. В рассмотренном примере таким объектом является /. Область действия и время жизни / - от точки определения (заголовок цикла) и до конца блока (объект с автоматическим классом хранения).

Завершая рассмотрение циклических операторов, отметим, что при программировании цикла есть три возможности: • использовать цикл/Ьг; • использовать цикл while; • использовать цикл do-while.

Возникает вопрос: какой из этих альтернатив следует восполь­зоваться в конкретном случае? Ответ прост - лучше всего, как было показано выше, использовать цикл for, а это всегда можно сделать,

107

Page 109: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

если заранее известно число повторений цикла. В остальных случа­ях используются циклы while и do-while, причем цикл do-while сле­дует применять, если требуется тело цикла выполнить не менее од­ного раза.

4.11. Оператор goto и метки операторов

Операторы break и continue определялись выше как операторы с ограниченным диапазоном передачи управления. В дополнение к ним языки Си/С++ предоставляют программисту и нашумевший оператор перехода ^о/'о:

доЬо идентификатор;

Здесь "идентификатор" является именем метки, которая запи­сывается в виде

идентификатор:

Строго говоря, при написании программ применение операто­ра goto не является необходимым, так как любая программа может быть написана с помощью только трех элементарных конструкций, каждая из которых имеет только один вход и один выход: • следование; • ветвление; • цикл.

Однако существуют ситуации (их немного), когда goto удобен, и поэтому его включили в язык Си. Из числа подобных ситуаций на­зовем две.

1. Обычно оператор goto служит для передачи управления в конец функции в случае обнаружения ошибки, особенно если ошиб­ки могут возникать во многих местах функции. В конце функции, куда выполняется переход по goto, выполняется обработка ошибок и возвращается значение, соответствующее наличию ошибки.

2. Другим примером целесообразного применения goto может служить выход из многократно вложенных циклов, поскольку опе­ратор break осуществляет выход только из того цикла, где он ис­пользован (рис. 41). В остальных случаях использовать goto не сле­дует.

4.12. Упражнения для самопроверки 1. Изобразить фрагмент схемы программы, соответствующий сле­

дующему фрагменту Си-программы:

108

Page 110: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

if( с =^ 1 ) а + + / else ±f( с else ±£( с =^ 3 ) а += 1/

2 ) а-

while( выражение ) {

while( выражение ) {

lf( /* Ошибка */ . . . ) goto loop_end;

(oop_end: < Рис. 41. Использование оператора go/ о для выхода

из гнезда циклов

2. Записать фрагмент программы, соответствующий следующему фрагменту схемы программы (выполнить действие, противоположное предыдущему):

<^а <= Ь ^

1 Да к:=п; г:= 1;

Нет г:=3; i к

3. с помощью операторов ветвлений и присваивания записать на языке Си фрагмент программы, вычисляющий величину

[ п+1 п ^ [ а+Ь

[ а-Ь

при 1=4, при i='l, 1, 9, в остальных случаях

4. Пусть определена переменная

int к;

Укажите, что напечатает следующий фрагмент программы:

printf( "\п %5s \ л " , '"*-" ) ; for( к = 1; к >= -5; к-- )

printf( " %li %3s ", к, "--" ) ;

109

Page 111: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

5. Пусть определен массив

±nt а[ 25 ];

Напишите фрагмент Си-программы, который напечатает с новой строки значения элементов массива "а" по пять элементов в строке и по десять позиций на элемент. Решить задачу с помощью цикла while.

6. При каких исходных значениях "А:" приведенный ниже цикл бу­дет выполняться бесконечно?

±zit к; wh±lG( к < 5 ) к+ + ;

Page 112: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

5. ВЫРАЖЕНИЯ И ОПЕРАЦИИ

В языках Си/С++ предусмотрен богатый набор операций. В дополнение к традиционным арифметическим, логическим операци­ям, операциям отношения и присваивания предусмотрены сокра­щенные версии этих операций, побитовые операции и операции над адресами (указателями). Рассмотрим операции и выражения, ис­пользующие их, кроме операций над адресами и побитовых опера­ций, которые будут рассмотрены ниже.

Можно выделить шесть категорий операций: • ссылки; • унарные операции; • бинарные операции; • тернарные операции; • присваивания; • операция "запятая".

Операции ссылки используются, в основном, для доступа к элементам массивов и структур.

Унарные операции воздействуют на одно значение или выра­жение.

В бинарных операциях участвуют два выражения, а в тернар­ных - три.

Приоритеты операций в порядке их убывания и порядок вы­полнения операций с одинаковым приоритетом указаны в табл. 16. Из приведенной таблицы следует, что в особый класс бинарных операций выделены операции присваивания.

Табл. 16. Приоритеты и порядок выполнения операций Наименование

операций

Разрешение области видимости Ссылочные

Знаки операций

[ ] - доступ по индексу: адрес_начала[ выражение ] или выражение[ адрес_начала ]) ( ) — управление порядком выполнения операций в выражении; вызов функции: имя_функции( списокпараметров ); конструирование значения: тип(списокпараметров) . — выбор члена класса посредством объекта: объект, членкласса -> - выбор члена класса посредством указателя: указатель->член класса

Порядок выпол­нения

Нет

Слева -направо

111

Page 113: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

продолжение табл. 16 Наименование

операций Знаки операций Порядок

выпол­нения

Унарные ++ - постфиксный инкремент: lvalue++ — постфиксный декремент: lvalue-new - динамически создать объект (выделить динамическую память): new type или new type( списоквыражений) delete — уничтожить объект (освободить динамическую память): delete указа-гел ь н а о б ъ е к т delete[ ] - уничтожить массив объектов: delete указательнамассивобъектов ++ - префиксный инкремент: -ь+lvalue — префиксный декремент: —lvalue * - разадресация (разименование): * выражение & - получение адреса объекта: & lvalue + - унарный плюс: + выражение — - унарный минус: - выражение ! - логическое отрицание (not): !выражение '- - поразрядное дополнение: -выражение sizeof - размер в байтах: 812еоГ(объект) или sizeof(THn) typeidO - идентификация типа времени выполнения: typeid(type) (type) - приведение типа: (type)выpaжeниe -приведение типа выражения к типу в скобках (старый стиль), выполняется справа-налево COnst_cast — константное преобразование типа: const_cast<type>(выpaжeниe) dynamic_cast - преобразование типа с проверкой во время выполнения: dynamiccast <1уре>(выражение) reinterpret__cast - преобразование типа без проверки: reinteфret_cast<type>(выpaжeниe) static__cast — преобразование типа с проверкой во время компиляции: static cast<type>(выpaжeниe) .* - выбор члена класса посредством объекта: объект. *указатель_на_член_класса, выполняется слева-направо ->* - выбор члена класса посредством указателя на объект: указатель на объект ->* указатель на член класса, выполняется слева-направо

Нет

Мультипликативные бинарные

* - умножение: выражение * выражение Слева -/ - деление: выражение / выражение | направо % - остаток от деления (деление по модулю): выражение % выражение

Аддитивные бинарные + - сложение: выражение - - - вычитание: выражение -

выражение выражение

Слева -направо

Сдвига бинарные « - сдвиг влево: выражение « выражение » - сдвиг вправо: выражение » выражение

Слева -направо

112

Page 114: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Продолжение табл. 16 1 Наименование

операций

Отношения бинарные

Отношения бинарные

Поразрядная "И" бинарная

Поразрядная "ИСКЛЮЧАЮЩЕЕ

ИЛИ" бинарная Поразрядная

"ВКЛЮЧАЮЩЕЕ ИЛИ" бинарная Логическая "И" бинарная(and)

1 Логическая "ИЛИ" бинарная (or)

Условная тернарР1ая

Простое присваивание

Совмещенное присваивание

Генерация исключения Запятая

(последовательность)

Знаки операций

< - меньше: выражение < выражение > - больше: выражение > выражение <= - меньше или равно: выражение <= выражение >= - больше или равно: выражение >= выражение == - равно: выражение == выражение != - не равно: выражение != выражение & - поразрядное умножение: выражение & выражение ^ - выражение '^ выражение

1 - выражение | выражение

&& - логическое умножение: выражение && выражение ii - логическое умножение: выражение || выражение

?: - выражение ? выражение : выражение

= - lvalue = выражение

*= - выражение *= выражение /= - выражение /= выражение %= - выражение %= выражение += - выражение += выражение -= - выражение -= выражение « = - выражение « = выражение » = - выражение » = выражение &= - выражение &= выражение 1= - выражение |= выражение ^= - выражение ^= выражение throw - throw выражение , - выражение , выражение

Порядок выпол­нения

Слева — направо

Слева — направо | Слева -направо Слева -направо

Слева -направо

Слева -направо Слева -направо Справа-

налево Справа —

налево Справа —

налево

Нет Слева -направо |

5.1. Операции ссылки

Порядок выполнения операций (раньше - позже) определяется их приоритетом. Операции с одинаковым приоритетом могут вы­полняться в порядке их появления в выражении слева - направо или справа - налево. В этом плане операции ссылки после операции раз­решения области видимости имеют наивысший приоритет и выпол­няются слева - направо (табл. 16).

Имеются следующие разновидности операций ссылки: • ссылка на элемент массива [ ];

113

Page 115: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• ссылка на элемент структуры .; • ссылка на элемент структуры с помощью указателя ->.

Особое место в этой группе занимает операция "()", служащая для управления порядком выполнения операций в выражении. Объ­ясняется это тем, что данная операция, как и другие операции ссыл­ки, имеет наивысший приоритет.

Ссылка на элемент массива. Операция предназначена для выделения конкретного элемента массива. Чтобы использовать эту операцию, выражение, называемое индексным и имеющее целое значение, заключают в квадратные скобки и записывают после име­ни массива:

^define N100 // Размер массива

±nt arr[ N ]; // Определение массива целого типа // из N элементов

. . . агг[ i+2 J // Ссылка на элемент массива

. . . arrf 12 ] // Ссылка на элемент массива

Повторно напомним, что у массива, содержащего N элементов, значения индекса изменяются в диапазоне от О до Л^-1. Имя массива, записанное без операции ссылки, означает адрес первого элемента массива:

а г г эквивалентно <&агг/" О ]

Алгоритм выполнения ссылки на элемент массива следующий (например, для агг[ i+2 ]):

1. Вычислить значение выражения, которое служит индексом. Пусть, например, результат s = i+2.

2. Преобразовать s в сдвиг в массиве элемента с индексом /+2 относительно начала массива. Для этого следует умножить s на раз­мер отдельного элемента массива:

shift = S '*' s±zeof( ±nt )

3. Вычислить адрес элемента массива по формуле:

adr = arr+shift = arr+s*slzeof (int) = &arr [ 0] -hs'^slzeof (int)

Эта функция получила название функции индексации. В ней агг = 8сагг[ О ] означает адрес первого элемента массива (адрес на­чала массива).

4. Извлечь значение, находящееся по этому адресу. Нетрудно заметить, что для доступа к элементам одномерного

массива нужно выполнить довольно много работы - одно умножение

114

Page 116: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

и одно сложение. Поэтому работа с массивом по сравнению со c i^-лярными объектами происходит существенно медленнее и это сле­дует иметь в виду.

Получим функцию индексации для двумерного массива. Дву­мерный массив (в математике его называют матрицей) располагает­ся в оперативной памяти по строкам - сначала располагаются эле­менты первой строки матрицы в порядке возрастания индексов и адресов оперативной памяти, затем - второй строки и т.д.

#define N10 // Строчный размер матрицы (массива) #define М 9 // Столбцовый размер массива

// Определение двумерного массива short ±nt two_arr[ N ][ М ]/

// Функция индексации для элемента массива two__arr[i] [j] : // 1. Вычисляем значения индексных выражений, указанных в // квадратных скобках (в данном случае этого не требуется) . // 2. Вычисляем сдвиг указанного элемента относительно начала // массива shift = (i*M+j) *s±zeo£(shoirb int) . // 3, Тогда функция индексации, дающая адрес оперативной // памяти, по которому находится элемент массива // two__aгг [i ] [j ] , имеет следующий вид: // adr = two_arr-hshift = 8ctwo_arr[0] [0] + (i*M+j) // *3±zeof( short i n t ; .

Для доступа к элементам двумерного массива вычислительной работы еще больше - на каждый элемент два умножения и два сло­жения. Поэтому для повышения быстродействия надо избегать ис­пользования массивов вообще и особенно массивов высокой раз­мерности.

Ссылка на элемент структуры. Ссылка на элемент структу­ры осуществляется с помощью операции, обозначаемой точкой. Вы­ражение

current .stip (см. подразд. 3.9.3 о структурах)

представляет значение элемента stip структуры current. Так как структуры могут быть вложенными, то вложенность распространя­ется и на ссылку на элемент структуры. Например,

bigstr.smallstr.elem

представляет элемент elem структуры smallstr^ которая, в свою оче­редь, является элементом структуры bigstr.

Операция ссылки на элемент структуры с помощью указателя выглядит следующим образом:

115

Page 117: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

STUDENT__INFO s_data, // Структура *ps_data = &s__data;

// Указатель на эту же структуру

. . . s_data.stlp /* эквивалентно */ ps__data->stlp

5.2. Унарные операции

Приоритет унарных операций ниже, чем операций ссылок и выполняются они справа налево (см. табл. 16). Унарные операции представлены в табл. 17.

Табл.17. Унарные операции -f

++ — sizeo/{ имя типа )

(спецификация типа)выражение ~

& *

Инвертирование знака Логическое отрицание Увеличение, уменьшение Размер в байтах Преобразование типа Поразрядная инверсия (обсуждается ниже в разделе "Поля битов и побитовые операции") Адресация, разадресация (обсуждается ниже в разделе "Указатели")

Унарный минус. Является обычной арифметической операци­ей изменения знака и может использоваться в выражении любого арифметического типа.

При использовании операции "-" тип результирующего значе­ния тот же, что и тип операнда, над которым выполняется операция.

Логическое отрицание. Операция отрицания "!" изменяет значение "истина" на значение "ложь", а значение "ложь" - на значе­ние "истина". Напомним, что значению "истина" соответствует не­нулевое целое значение, а значению "ложь" - нуль.

Увеличение и уменьшение. Операции "++" и "—" могут пред­шествовать операнду (префиксные операции) или следовать за ним (постфиксные операции). Эти операции соответственно добавляют или вычитают единицу из значения операнда и присваивают ему по­лученный результат:

X = X -h 1; у = у - 1;

X = X + 1; а = а г г [ X ];

/' эквивалентно /* эквивалентно

/* эквивалентны

*•/ ++х; V --у;

= arrf -h-i-x 7/

116

Page 118: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

а = arr [ X ]; /* эквивалентны */ а = arr [ к + + ]; к = к + 1;

При использовании "++" и "—" тип результирующего значения тот же, что и тип операнда, над которым выполняется операция.

Преобразование типа. В языке предусмотрено средство для явного изменения типа выражения:

( спецификация_типа )выражение

"Спецификациятипа" может быть любым служебным словом, задающим спецификацию типа, например, int, short, long, float и т.д.

int 1 = 11000, w = 4; long a;

// Если целое занимает 2 байта (разрядность процессора 16 // бит), то здесь возникает ошибка при умножении: // 44000 > 32767. Как быть? а = 1 "^ w; а = ( long ) ( 1 * W ) ; // И здесь возникает ошибка при

// умножении: 44000 > 32 767 а = ( long )1 * w; // Так правильно!

Другим распространенным примером использования автома­тического преобразования типа является преобразование типов ар­гументов при вызове библиотечных функций языка Си. И еще одно важное замечание. В вызовах таких функций аргументы с типом float автоматически преобразуются к типу double, а аргументы с ти­пом char преобразуются к типу int.

Операция sizeof Операция выполняется на этапе компиляции программы и дает константу, которая равна числу байтов, требуе­мых для хранения в памяти данного объекта. Объектом может быть имя переменной, массива, структуры или просто спецификация ти­па. Применение этой операции демонстрировалось выше.

Пример.

±пЬ count, iarrayl 10 ]; for( count = 0; count < slzeof( larray ) / sizeof ( ±nt ) ;

count++ ) printf( "iarray[%d] = %d\n", count, iarrayl count ] ) ;

Применение операции sizeof всюду, rjxQ это возможно, счита­ется хорошим стилем программирования.

117

Page 119: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

5.3. Бинарные операции

п р и о р и т е т и порядок выполнения бинарных операций пред­ставлены в табл. 16. Бинарные операции воздействуют на два выра­жения:

выражение Ыпор выражение

Здесь Ыпор - одна из б и н а р н ы х операций , приведенных в табл . 18.

Табл . 18. Б и н а р н ы е операции , /, % Умножение, деление, взятие остатка от деления целого на целое

Сложение, вычитание

» Операции сдвига (обсуждаются в разделе "Поля битов и побитовые операции") Больше, меньше Больше или равно Меньше или равно Равно Не равно

&, м Поразрядные логические операции (обсуждаются в разделе "Поля битов и побитовые операции")

&&, Логическое И, логическое ИЛИ

Арифметические операции: "*", "/", "%", "+" и "-". Эти опе­рации задают о б ы ч н ы е действия над операндами арифметического типа. Операция "%" означает получение остатка от деления одного г^елого числа на другое :

1 % j дает значение i - ( i/j ) * j

Примеры. 12 % 6 дает О, 13 % 6 дает 1, 3 % 6 дает 3 и т .д. Если арифметическая операция или операция о т н о ш е н и я со­

держит операнды различных типов , то компилятор выполняет авто­матическое преобразование их типов , если замена типов явно не указана. Такое преобразование производится путем " п р о д в и ж е н и я " значений операндов к "наибольшему" типу:

long

unsigned. ciiax- или

double (наибольший тип) double float

unsigned long int long int

unsigned int int

char или unsigned short int short int (наименьший тип)

118

Page 120: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Алгоритм выполнения очередной бинарной арифметической операции ("*", "/", "+", "-") или операции отношения ("<", ">", ">=", "<=", "==", "!=") состоит в следующем.

1. Если один операнд имеет тип long double, то и второй опе­ранд преобразуется к типу long double и выполняется операция. Иначе производится переход к п. 2.

2. Все операнды с типом float преобразуются к типу double и производится переход к п. 3.

3. Если один операнд имеет тип double, то и второй операнд преобразуется к этому же типу и выполняется операция. Иначе вы­полняется п. 4.

4. Если один операнд имеет тип unsigned long int, то и второй операнд преобразуется к этому же типу и выполняется операция. Иначе выполняется п. 5.

5. Если один операнд имеет тип long int, то и второй операнд преобразуется к этому же типу и выполняется операция. Иначе вы­полняется п. 6.

6. Если один операнд имеет тип unsigned int, то и второй опе­ранд преобразуется к этому же типу и выполняется операция. Иначе выполняется п. 7.

7. Если операнды имеют тип unsigned char и/или unsigned short int, то они преобразуются к типу unsigned int и выполняется опера­ция. Иначе выполняется п. 8.

8. Если операнды имеют тип char и/или short int, то они пре­образуются к типу int и выполняется операция.

Далее по такому же алгоритму выполняется следующая из пе­речисленных выше арифметических операций и операций отноше­ний.

Результат арифметической операции имеет такой же тип, как и тип операндов после преобразования. Результат операции отноше­ния всегда имеет тип int (О - "ложь", "нет" и 1 - "истина", "да").

И еще раз повторим важное замечание. В вызовах функций ар­гументы с типом float автоматически преобразуются к типу double, а аргументы с типом char преобразуются к типу int.

Операции отношения. Приоритеты и порядок выполнения операций отношения "<", ">", "<=", ">=", == и "!=** указаны в табл. 16. Операция "==" выполняет проверку на равенство, а операция "!=" - проверку на неравенство. Смысл остальных операций отноше­ния очевиден.

Запись операции отношения имеет вид:

операнд! операция__отношения опера нд2

119

Page 121: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Как указывалось выше, результатом вычисления отношения может быть или ненулевое целое значение ("истина") или нулевое значение ("ложь"). Таким образом, после выполнения присваивания

X = count < slzeof( la гray ) / 2

переменной "х" будет присвоено значение либо единицы, либо нуля. С помощью операций отношения можно сравнивать и указате­

ли, что будет рассмотрено ниже в разд. 6.

Логические операции (**&&" и "||"Л Приоритет и порядок вы­полнения логических операций также представлены в табл. 16, а их смысл описан в табл. 19, называемой таблицей истинности.

Табл. 19. Таблица истинности для логических операций Операнд1

НеО НеО

0 0

Операнд2 НеО

0 НеО

0

Операнд1 && Операнд2 1 0 0 0

Операнд1 |j Операнд2 1 1 1 0

Вычисление выражения с логическими операциями прекраща­ется, как только результат становится однозначно определенным:

О && ( А > в ) 2 \\ ( ( А+В ) < 4 )

В этих выражениях правый операнд не будет вычисляться. Пример. Логическая функция задана следующей таблицей ис­

тинности (табл. 20).

Табл. 20.Задание Параметр 1 ( р1 )

НеО НеО

0 0

логической функции таблицей истинности Параметр 2 ( р2 )

НеО 0

НеО 0

Значение функции 0 0

НеО 0

// Прототип ±nt lf( ±nt pi, ±nt p2 )

// Определение функции ±nt If(

±nt ±nt

( ±f( (

P2 )

!pl ) && p2 )

// Возвращает значение функции // Параметр функции // Параметр функции

120

Page 122: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

retuxm 1,

jretux-n 0;

// Пример вызова функции ±nt value = lf( 4, 0 ) ;

5.4. Тернарная операция

Тернарная операция применяется для конструирования услов­ных выражений:

выраж__1 ? выраж_2 : выраж__3

Данное выражение интерпретируется следующим образом. Вначале вычисляется "выраж 1". Если его значение отлично от нуля ("истина"), то вычисляется "выраж_2", следующее за знаком "?". Ес­ли же "выраж 1" имеет нулевое значение ("ложь"), то за значение ус^ловного выражения принимается значение "выражЗ" .

Пример.

max = ( a>b ) ? а : b; // Эквивалентно ±f( a>b )

max = a; else

max = b;

5.5. Операции присваивания

Приоритеты и порядок выполнения операций присваивания указаны в табл. 16.

Простая операция присваивания используется следующим образом:

lvalue = выражение/

Приведенная запись получила название выраэюение присваива­ния. Так как результатом выражения присваивания служит значение "выражения", стоящего справа от операции присваивания, которое присваивается объекту '4value", то выражения присваивания могут быть вложенными:

max = min = 0;

121

Page 123: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в приведенном примере вначале будет выполнено присваива­ние min = О, в результате которого ''min'' станет равно нулю, а затем это же значение будет присвоено "max",

Операцию присваивания можно записывать в сокращенной форме, что широко используется:

Обычна я ф орма count = count + 2; offset = offset / 2; s = s * ( f-h2 ) ; a[ i+2 ] = a[ i+2 ] - 10;

Сокращенная форма count += 2; offset /= 2; s *= f + 2; a[ i+2 ] -= 10;

Операции присваивания "+=", "/=", "*="^ "%=" и подобные им (см. табл. 16) называют составными операциями присваивания. Операция присваивания выполняется следующим образом: • вначале вычисляется значение выражения, стоящего справа от

операции присваивания; • полученное значение автоматически приводится к типу объекта,

указанного слева от операции присваивания (! такое преобразо­вание лучше делать явно с помощью операции приведения типа);

• преобразованное значение присваивается объекту, указанному слева от операции присваивания.

5.6. Операция "запятая"

Приоритет и порядок выполнения операций "запятая" указаны в табл. 16. Отметим, что эта операция имеет самый низкий приори­тет.

Операция может быть использована для разделения несколь­ких выражений. Эта операция, чаще всего, применяется в операторе for для того, чтобы выполнялось более одного выражения в управ­ляющей части этого оператора:

/ / Суммирование первых десяти ненулевых целых значений £ог( ±пЬ 1=1, sum = 1; 1 < 10; i++, sum += i ) ; // To же самое, в более длинной форме ±nt sum = 0; for( ±nt i = 1; 1 <= 10; i + + ) (

Page 124: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

6. УКАЗАТЕЛИ

Как известно, оперативная память ЭВМ и, в частности, память IBM PC делится на восьмибитовые байты. Каждый байт пронумеро­ван, нумерация начинается с нуля. Номер байта называется адресом. Говорят, что адрес указывает на определенный байт. Таким образом, указатель является просто адресом байта памяти.

6.1. Зачем нужны указатели?

в большинстве прикладных программ можно обойтись без яв­ного применения указателей. К числу таких программ относятся программы, написанные на языках высокого уровня, кроме про­грамм, написанных на языках Си/С++.

Зачем эюе нуэюны указатели! 1. Применение указателей во многих случаях позволяет упро­

стить программу и/или повысить ее эффективность. 2. Для управления памятью ЭВМ. Так в состав Си-библиотек

входят функции резервирования и освобождения блоков оператив­ной памяти в любой момент в процессе выполнения программы. В языке же C++ для этой цели предусмотрены операторы new и delete. Эти операторы C++ управляют динамической памятью. Управление динамической памятью позволяет эффективно увеличивать размеры программ при тех же ресурсах компьютера и приспосабливать их к изменяющимся размерам данных. Как программа "узнает", где рас­положен вновь занятый блок памяти? С помощью указателя!

6.2. Указатели и их связь с массивами и строками

Рассмотрим несколько примеров.

/ / iarray - адрес iarrayf О ] int Iarrayf 6 ]; // Напечатаем 16-ричный адрес iarray[ О ] print f ( "iarray = %х \ л " , iarray ) ;

В Си имеется операция "&" взятия адреса, в результате приме­нения которой к имени данного получается адрес этого данного. Например, вызов

printf( "iarray = %х \ л " , &iarray[ О ] ) ;

123

Page 125: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

дает точно такой же результат, как и предыдущий вызов. iarray = выражение/ // Ошибка: имя массива является указателем на место // расположения массива в памяти^ ему нельзя присваивать // новое значение "Это строка \п" // Значением строки является

// указатель на первый символ // строки^ т.е. адрес символа "Э" // в памяти

6.3. Определение и применение указателей

Определение указателей. Синтаксис определения указателя имеет вид:

описатель_класса_хранения спецификация^ типа *идентификатор;

"Спецификациятипа" может относиться к любому основному или производному типу данных. Символ "*" означает "указатель на".

Примеры:

/ / Определяет iptr как указатель на // целое // Объявляет cptr как указатель на // символ // Определяет fptr как указатель на // статическое данное с плавающей // точкой

±nt

ex t em chctr

static float

*iptr;

*cptr;

*fptr;

Операции над указателями. Один из способов получения ад­реса данного состоит в применении унарной операции взятия адре­са &, приоритет и порядок выполнения которой указаны выше в табл. 16:

±xit main ( void ) {

int Xr // Целое *px; // Указатель на целое: пользоваться

// указателем пока нельзя^ так как // его значение еще не определено

}

X = 2; рх = &х;

*рх = 3;

return 0;

// Теперь указателем рх пользоваться // можно: *рх равно 2

// Теперь *рх равно х равно 3

Page 126: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Единственное ограничение применения операции взятия адре­са & состоит в том, что ее нельзя применить к объекту с классом хранения register, а также к полям битов (поля битов обсудим позже в разделе "Поля битов и битовые операции").

Из приведенного выше примера следует, что к переменной-указателю, как только ей присвоен допустимый адрес, можно при­менить операцию разадресации, обозначаемую "*". Приоритет и по­рядок выполнения этих операций также указаны выше в табл. 16.

/ / Пример без разадресации int main ( void ) {

int X, у /

X = 2; у = x;

return 0; } // Эквивалентный пример с разадресацией int main( void ) (

int X, у г *рх;

X = 2; рх = &х; у = *рх;

return О; } // Здесь *рх означает извлечь значение^ находящееся по адресу // рх: косвенная адресация

Из приведенного выше первого примера следует также, что операцию разадресации можно использовать и в левой части выра­жения присваивания.

Инициализация указателей. Указатели, как и объекты других типов, можно определять, совмещая определение с инициализацией. Приведем несколько примеров такого рода.

/ / Использование указателей в инициализирующих выражениях // (рх - адрес х) Lnt X, *рх = &х; char line [ 80 ], "upline = line;

// pline - адрес line[ 0 ] int *page = 0xB800;

// Указатель на начало видеобуфера // цветного дисплея

125

Page 127: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Арифметические операции над указателями. В связи с арифметическими операциями над указателями рассмотрим не­сколько примеров.

Пример 1.

±пЬ i [ 6 ] г *pi = i / / / i и pi - адрес i [ О ]

Будем считать, что первый байт массива / имеет адрес 10 000. Тогда для первого элемента массива (его индекс равен нулю): • адрес равен 10 000, или &/[ О ], или /, или/?/; • значение элемента равно *10 000, или /[ О ], или */, или */>/.

Для элемента массива с индексом/ (J = О, 1,2, 3, 4, 5): • адрес равен 10000 + / * sizeofi int ), или &/ [ / ], или / + / , или pi +

У; • значение элемента равно *( 10 000 + / * sizeoJ{ int ) ), или / [ / ],

или *( / + / ), или *(/?/ + / ).

Пример 2.

#define N 3 // Строчный размер массива ^define М 4 // Столбцовый размер массива int а[ N ] [ М ] , *ра == а ;

Будем считать, что первый байт массива "<я" имеет адрес 20000. Тогда для элемента массива, принадлежащего строке с ин­дексом О и столбцу с индексом 0: • адрес равен 20 000, или 8са[ О ][ О ], или а, илира', • значение элемента равно *20 000, или «[ О ][ О ], или *а, или */7а.

Для элемента массива, принадлежащего строке с индексом / (/ = О, 1, ..., iV-1) и столбцу с индексом 0: • адрес равен 20 000 + ( / * М ) * sizeof{ int ), или 8са[ / ][ О ],

или *( а + / ), или а[ / ], или &.ра[ / * М ] ; • значение элемента равно *( 20 000 + ( / * М ) * sizeof{ int ) ),

или а{ / ][ О ], или *( *( flf + / ) ), или *<з[ / ], или ра{ / * М ] . Для элемента массива, принадлежащего строке с индексом

/ (/ = О, 1, ..., 7V-1) и столбцу с индексом/ (j — О, 1, ..., М-1): • адрес равен 20 000 - ) - ( /* М + / ) * sizeof{ int ), или &а[ / ] [ / ],

или *( а + / ) -f/, или а[ / ] +у, или &ра[ / * М + j ]; • значение элемента равно *( 20 000 + ( / * М +j ) * sizeoJ{ int ) ),

или а[ / ] [ / ], или *(*( а + / ) + / ), или *( а[ / ] +/ ), или ра[ / * M-^J ] .

126

Page 128: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Пример 3.

/ / Программа добавляет строку s к концу строки t

^include <stdlo.h> // Для функций ввода-вывода

int main ( void ) // Возвращает О при успехе {

char t[ 24 ] == "Персональная " , "^pt = t , / / pt - адрес начала t s[ ] = "ЭВМ IBM PC", "^ps = s; // ps - адрес начала s

while ( *pt != '\0' ) pt-h + ; // Здесь pt - адрес завершающего символа строки t , т.е. // адрес '\0' // Посимвольно копируем строку s в "хвост" строки t, пока // не будет скопирован нуль-символ while ( ( *pt = *ps ) != '\0' ) {

pt++; ps++; } printf( "\n Сцепление строк (конкатенация): %s", t ) ;

return 0; }

Хотя на практике это требуется не часто, можно определять объекты, которые будут указателями на указатели и использовать несколько уровней косвенной адресации:

/ / Указатель на указатель на целое, т.е. адрес адреса целого / / (уровень косвенной адресации 2) int '<" *ppi ;

**ppi . . . / / Это значение целого

В приведенных выше примерах рассматривались только ариф­метические операции "н-+" и "+", но наряду с ними могут использо­ваться и такие арифметические операции, как "-" и "—".

Сравнение указателей. Указатели можно сравнивать с помо­щью операций отношения:

<, >, <=, >=, ==, !=

! Обратите внимание на очень важное замечание. В сравнении должны участвовать указатели на данные с одним и тем эюе типом, относящиеся к одному и тому эюе объекту (массиву, структуре и т.п.).

127

Page 129: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Пример. // Сравнение указателей: копирование одной строки в другую

^include <stdlo.h> // Для функций ввода-вывода

±nt main ( void ) // Возвращает О при успехе {

cbstr al [ 7 7 , // Строка -приемник *р1 = al, // р1 - адрес начала al а2 [ ] = "Пример",

// Строка-источник *р2 = а2; // р2 - адрес начала а2

// Копировать а2 в al while ( р2 < ( а2 + sizeof( а2 ) ) ) {

*р1 = *р2; р1+ + ; р2+ + ; } pr±ntf( "\п Копия: %s", al ) ;

jretujrn О;

6.4. Указатели на структуры

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

/ / Объявление структуры, содержащей сведения о студенте struct STUDENT_INFO {

// Факультет char fak_name[ 30 ]; char fio[ 30 ];// ФИО // Номер группы char group_name [ 7 ]; char date[ 9 ]/// Дата поступления студента float stip; // Размер стипендии

} ;

// Указатель на структуру STUDENT_INFO s_data, // Определение структуры

sl_data уг // Определение еще одной структуры *ps_data; // Указатель на структуру данного

// типа ps_data = &s_data; // Указатель на s_data: адрес

// первого байта s_data

// Доступ к элементу структуры: обе приведенные ниже формы

128

Page 130: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / эквивалентны . . . s__data . stip . . . ps_data->stip . . .

Приоритеты и порядок выполнения операций "." и "->" приведены выше в табл. 16.

/ / Определение адреса элемента структуры: обе приведенные // ниже формы эквивалентны

. . . &s__data , stip . . . &ps_data->stip . . . / / Операция присваивания над структурами: оба операнда должны // быть объектами с одинаковыми типами (тегами) sl_data = s_data;

К указателям на структуры можно также применять арифмети­ческие операции. Например,

ps__data-i- +; // Эквивалентно ps_data += s±zeof( stxract STUDENT_INFO ) ;

В языке Си (но не в языке C-I-+) обычно указатель на структуру используется для передачи адреса структуры в функцию. Это позво­ляет получить из функции в качестве результата модифицированное значение структуры. Заметим, что для этой цели в языке C++ лучше использовать передачу структуры по ссылке.

6.5. Использование указателей в качестве аргументов функций

Ранее была рассмотрена программа добавления одной строки в конец другой. Слияние (конкатенация) строк является достаточно распространенной операцией. Поэтому, почему бы не превратить эту программу в функцию? Приведем текст такой функции. Функ­цию, как типовую, поместим в отдельный файл:

Файл STRCAT.CPP Добавление строки с указателем ps к концу строки с указа­

телем pt V

/ / Определение функции void Streat(

char *pt, // Указатель на строку-приемник char *ps ) // Указатель на строку-источник

{ while ( *pt ) pt + + / // Здесь pt - адрес завершающего символа строки с

129

Page 131: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// указателем pt, т.е. адрес '\0' // Посимвольно копируем строку с указателем ps в "хвост" // строки с указателем pt, пока не будет скопирован // нуль -символ

while ( ( ±nt ) ( *pt = *ps ) ) {

pt++/ ps++; }

return;

/^ Файл P24.CPP Главная функция,

*/ использующая функцию strcat^

^Include <stdio.h> // Для функций ввода-вывода

// Прототип void strcat ( char *, char * ) ;

±Tib main ( void. ) // Возвращает 0 при успехе {

// Строка-приемник char t[ 24 ] = "Персональная ";

strcat ( t , "ЭВМ IBM PC" ) ; printf( "\n Конкатенация строк: %s", t ) ;

return 0; }

В связи с рассмотренным примером напомним, что имя масси­ва эквивалентно указателю на первый байт этого массива, а значе­нием строки является указатель на ее первый символ. Эти указатели, как исходные данные, передаются функции strcat по значению, т.е. функции передаются их копии, и поэтому изменение копий указате­лей в strcat не повлияет на значения указателей в главной функции.

Напомним также, что в отличие от массивов, которые всегда передаются в функцию по ссы-пке, структуры могут передаваться в функцию, как по значению, так и по ссылке (по адресу). Примеры, иллюстрирующие оба названных способа передачи структур в функ­цию, рассмотрены выше в подразд. 3.5. Передачу структуры в функцию по значению не следует использовать при большом разме­ре структуры даже, если структура передается в функцию как ис­ходное данное. Объясняется это тем, что при выполнении функции при передаче структуры по значению в функции используется копия структуры, размещаемая в дополнительной памятки.

130

Page 132: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Рассмотрим еще несколько примеров, целью которых является показать необходимость использования в списке параметров функ­ций языка Си адресов объектов для получения из функций результа­тов (сказанное относится только к языку Си).

/* Файл Р25.СРР Однофайловый программный проект с двумя функциями. Пример

иллюстрирует возможность появления ошибки из-за отсутствия в Си передачи в функцию параметра по адресу (ссылке) V

^include <stdio.h> // Для функций ввода-вывода

void swap ( int, int ) ; // Прототип

int main ( void ) // Возвраш,ает 0 при успехе {

int к = 10, у = 20;

swap( X, у ) ; printf( "\п X = %d, у = %d", Xr у ) ; // Будет напечатано х = 10, у = 20

геЬлт О; }

// Перестановка значений не будет выполнена, так как // параметры функции передаются по значению void swap (

int X, int у ) // X <--> у

{ int temp = x; x = y; у = temp/

jcetvLm; }

Обращаем внимание, что будут напечатаны значения л: = 10 и у = 20. Почему? Потому, что в функцию swap "х" и ' У передаются по значению, т.е. копии "х" и " у . Следовательно, изменятся только копии, а после выхода из swap они будут потеряны.

Чтобы избежать этой ошибки в языке Си (это не относится к языку C++), нужно в функцию swap передавать адреса "х" и "jv" или, что то же самое, указатели на эти объекты:

Файл Р26,СРР Однофайловый программный проект с двумя функциями. Пример

иллюстрирует использование в функции Си параметра-адреса объекта для получения из нее измененного значения оаъекта

131

Page 133: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdio.h> // Для функций ввода-вывода

// Прототип void swap ( int *, int * ) ;

int main ( void. ) // Возвращает 0 при успехе {

int X = 10, у = 20;

swap ( &x, &y ) ; // В функцию передаются указатели print f ( "\n X = %d, у = %d"r ^r У )f // Теперь будет напечатано x = 20, у = 10

re turn Of }

// Перестановка значений будет выполнена, так как в качестве // параметров в функцию передаются адреса объектов void swap (

int *рх, int *ру ) // Указатели

{ int temp = *рх; *рх = *ру; *ру = temp;

return; }

Как уже указывалось ранее, в языке С+4-, в отличие от языка Си, для получения результатов из функции используется передача соответствующих параметров по ссылке (по адресу). При этом в функции работа производится не с копией аргумента, а с самим ар­гументом, что иллюстрирует приводимый ниже пример:

Файл Р2 7.СРР Однофайловый программный проект с двумя функциями. Пример

иллюстрирует использование в функции языка C++ параметров, передаваемых по ссылке, для получения из нее измененных зна­чений объектов _V ^include <stdio.h> // Для функций ввода-вывода

// Прототип void swap ( int &, int & ) ;

int main ( void ) // Возвращает 0 при успехе {

int X = 10, у = 20;

swap ( X, у ) ;

132

Page 134: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

printf( "\n X = %d, у = %d"r K, у ) ; // Будет напечатано x = 20, у = 10

retujcn 0; }

// Перестановка значений будет выполнена, так как параметры в // функцию передаются по ссылке void swap (

±nt &х, ±nt &у )

{ int temp = x; X = у; у = temp;

return;

6.6. Указатель как значение, возвращаемое функцией

Иногда удобно получать от функции в качестве возвращаемого значения указатель, в частности при операциях над строками. В ка­честве примера рассмотрим функцию копирования, возвращающую указатель на конец строки-приемника. Последовательный вызов этой функции обеспечит конкатенацию (сцепление) строк:

Файл Р28.СРР Одно файловый программный проект с двумя функциями. Пример

иллюстрирует сцепление нескольких строк с помощью функции ко­пирования строки, использующей указатели V

^include <stdio.h> // Для функций ввода-вывода

// Прототип char * strcpy( char *, char * ) ;

±nt main ( void. ) // Возвращает 0 при успехе {

char t[ 24 ], // Строка-приемник *pt; // Указатель на конец строки-

// приемника

pt = strcpy ( t, "Персональная" ) ; // pt = t + 12

//pt = t + 16 pt = strcpy ( pt, " ЭВМ" ) ; // pt = t + 23 pt = strcpy ( pt, " IBM PC" ) ; print f ( "\n Конкатенация строк: %s \n", t ) ;

133

Page 135: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

return О; }

/ • "

Копирует строку-источник с начальным адресом ps в строку-приемник с начальным адресом pt и возвращает указатель на по­следний символ строки-приемника */ char * St г еру (

char *pt^ // Указатель на строку-приемник char *ps ) // Указатель на строку-источник

{ while ( ( ±nt ) ( *pt = *ps ) ) (

pt++/ ps++; }

return pt;

6.7. Массивы указателей

Одним из распространенных производных типов данных в Си является массив указателей. Приведем несколько иллюстрирующих примеров.

/ / Пример 1: пять указателей на целые значения - массив // указателей Int *piarray[ 5 ] ;

Данное определение трактуется следующим образом: [] - наивысший приоритет - массив; * - приоритет ниже [] - указателей; int - связывается в последнюю очередь - на целые значения

// Пример 2: указатель на массив из пяти целых значений int ( *p±array ) [ 5 ];

Данное определение трактуется следующим образом: О - наивысший приоритет, как у [], но выполняется слева

направо - указателъ; [] - наивысший приоритет как у () , но выполняется слева

направо - на массив; int - связывается с идентификатором в последнюю очередь

- целых значений

// Пример 3: инициализация массмва указателей на строки // символов char *stud_info[ ] = {

134

Page 136: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

"ФТК" , "Иванов И. И. "1081/3", "01-09-97"

}.

В памяти эти данные будут располагаться так, как это указано на рис. 42 (значения адресов байтов - условные). Нетрудно заметить, что при таком определении каждая строка будет занимать мини­мально необходимую память.

char *studJnfo[ ] Сегмент данных

studJnfo[ 0 ] studJnfo[ 1 ] studJnfo[ 2 ] studJnfo[ 3 ]

Адрес

15000 1-15004 L 15016 V

1—•

Адрес

15000 15001 15002 15003

15004 15005 15006 15007 15008 15009 15010 15011 15012 15013 15014 15015

Содержимое

'Ф' •т 'К' ЛО' 'И' 1 'в' 'а' 'н' 'о' 'в'

'И'

'И'

'\0

1 Рис. 42. Размещение в памяти

Как организовать доступ к массиву строк?

/ / Печать сформированного в памяти массива строк ^include <stdio,h> printf( " Факультет: %s \п", stud__lnfo[ О ] ) ; printf( " ФИО: %s \п", stud_lnfo[ 1 ] ) ;

// Второй символ фамилии заменим на 'р' *( stud_info[l ] + 1 ) = 'р';

Аргументы командной строки. По-видимому, наиболее рас­пространенным приложением массива указателей является передача данных программе через командную строку операционной системы.

135

Page 137: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

предположим, что имеется программа prog.exe, которая должна чи­тать исходные данные из файла prog.dat и писать результаты своей работы в файл prog.res.

Тогда программу можно запустить из среды MS DOS с помо­щью следующей командной строки:

prog.ехе prog,dat prog.res [Enter]

Компилятор языка разбивает командную строку на слова, разделенные пробелами (в данном примере prog.exe, prog.dat и prog.res). Затем передает функции main в качестве аргументов число слов в командной строке (в примере 3) и массив указателей на каж­дое слово (в первом элементе массива указателей находится адрес "prog.exe'\ во втором — ''prog.daf\ в третьем - "prog.res'\ а значение четвертого - NULL).

Файл Р29.СРР Однофайловый программный проект с одной функцией. Пример

иллюстрирует передачу в программу аргументов командной стро­ки. Программа печатает количество слов в командной строке и сами слова */

#include <stdio.h> // Для функций ввода-вывода

Izit main ( // Возвращает О при успехе ±nt argCr // ARGument Counter: число слов в

// командной строке сЬа.г *argv[ ] ) / / ARGument Value: массив указателей

// на аргументы командной строки {

printf ( "\п Число слов в командной строке: %d \п", а где ) ;

print f ( "\п Передаваемые аргументы: \п" ) ; unsigned int

i = 0; while( argv[ i ] ) {

printf( "%s \n", argvf i + + ] ) ; } // Другой вариант print f ( "\n Переда в a емые аргументы: \n" ) ; while( *argv ) {

printf( "%s \n", *argv++ ) ; }

j re t ixm 0;

136

Page 138: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

6.8. Замена типов указателей

Основное применение замены типов указателей связано с устранением предупреждений в выражениях присваивания. Рассмотрим следующий иллюстрирующий пример.

Файл РЗО.СРР Однофайловый программный проект с одной функцией. Пример

иллюстрирует замену типов указателей и производит побайтовое копирование одной структуры в другую V

struct STUDENT_INFO // Сведения о студенте { // Факультет

char fak_name[ 30 ]; char fio[ 20 ];// ФИО

// Группа char group__name[ 7 ]; char date[ 9 ];// Дата поступления в университет float stip/ // Размер стипендии

} s2 = / / Определение объекта: источник {

} .

"ФТК:", "Иванов И, И, "1081/4", "01-09-98", 100000.Of

int main ( void ) // Возвращает О при успехе {

STUDENT_INFO si; // Приемник

// Адреса структур si и s2 char *psl = ( char '*' )&sl,

*ps2= (char *) &s2;

// Побайтовое копирование структуры s2 в si fori unsigned 1 = 0; 1 < slzeof ( STUDENT_INFO ) ; i + + ) {

*psl = *ps2/ psl++; ps2++; } // Вообще-то следует иметь в виду, что возможно выражение // присваивания над структурами: si = s2;

return 0;

137

Page 139: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Операция замены типа {char *) указывает компилятору, что перед применением надо интерпретировать адрес структуры &s\ как указатель на символ.

Рассмотрим еще один пример, демонстрирующий мощь и изя­щество указателей.

/* Файл Р31.СРР Однофаиловыи программный проект с

иллюстрирует "хитросплетение ссылок". программа?

одной функцией. Что напечатает

Пример данная

^include <std±o.h> // Для функций ввода-вывода chai: *с[ ] = // Массив указателей на строки {

"ENTER", "МЕР", "POINT", "FIRST"

} ; // Массив указателей на элементы массива указателей на строки char **ср[ ] = { с+3, с+2, с+1, с } ; char ***срр = ср; // См. рис. 43 а

±nt main ( void ) // Возвращает О при успехе {

printf( "\n%s"r **++срр ) ; // См. рис. 43 б

printf ( "%s ", *--*-i- + cpp+3 ) ; // См. рис. 43 в

printf ( "%s", *срр[-2]+3 ) ; // См. рис. 43 г

printf ( "%s\n", срр[-1] [-!]+! ) ; // См. рис. 43 д

return О; }

Перечислим операции, используемые в программе, в порядке убывания приоритетов:

"[ ]" - выполняются слева направо; "++", "—", "*" - выполняются справа налево; "+" - выполняются слева-направо.

138

Page 140: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

а)

срр I У:

Г Р |

[т1 "Т1

ПГ" ГТ" FR" Гз" ГТ]

б;

срр [ / )

\0

\0

i О

N

\0 \0

4 и

*( *( ++СРР ) )

1

2

Операции одинакового приоритера, выполняются справа налево.

Будет напечатано: POINT

( *( - ( *( ++СРР ) ) ) ) + 3

1

2

4 5-

Операции одинакового приоритета, выпол­няются справа налево.

Будет напечатано: POINTER

Рис. 43. "Хитросплетение ссылок"

Рассмотрим еще один пример.

Файл Р32.СРР Однофайловый программный проект с одной функцией. Пример

иллюстрирует работу с массивом с использованием указателей. Что напечатает данная программа? V 1

^include <stdlo.h>

int

// Для функций ввода-вывода

а[ 3 ] [ 3 ] == { { 1, 2, 3 } , ( 4, 5, 6 Ь { 7 , 8 , 9 } } ,

139

Page 141: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

int main ( void. ) {

// Возвращает 0 при успехе

£оз:( ±nt i = О; i < 2; i + ч- ) {

pr±ntf( "\n%d %d %d"r a[i][2-i], *a[i], *(*(a + i)+±) ) ;

jretux-22 0;

d)

\0

" p l " Q | "Tl "NI T l

гт~ гг ГЦ ГЦ ГТ"

( *( срр[ -2 ] ) ) + 3 I I

2 3

Приоритет [ ] выше, чем *.

Будет напечатано с учетом предыдущей печати: POINTER ST

Прод. рис. 43

Так как a[i] означает адрес первого элемента строки / массива "(з", то *л[/] есть значение этого элемента. Аналогично, a+i эквива­лентно &а[/], *{a+i) эквивалентно a[i], *(a-^i)+i эквивалентно a[i]-^i и

ср

с

срр

1 ^ 'W

н^^^^ /

/ ( < | \ \ Е N Т Е R \0

(

N Е Р \0

Р О 1

N Т

1 \о

г_ F 1 R S Т

1 \о 1 ( срр[ -1 ] ) [ -1 ] ) 1 1 1

1

+

г.

1 3

Операции одинаков

1

ого приоритета, выпол­няются справа налево.

Будет напечатано с учетом предыдущей печати: Р OIN" ГЕК STEF 3

140

Page 142: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

эквивалентно &а[/][/]. Следовательно, *(*(а+/)-н/) эквивалентно a[im.

Таким образом, программа напечатает:

3 11 5 4 5

6.9. Упражнения для самопроверки

1. Что напечатает следующая программа?

^include <stdio.h>

±nt main ( void ) {

int a[ ] = { 10, 11, 12, 13, 14, 15, 16 }; ±nt i, *p;

fo2:( p = a, i = 0; p + 2*1 <= a + 6; p+ +, i++ ) prlntf( " %3d", *( p + 2*1 ) );

printf ( "\n" ) ;

fojci p = a + 5; p >= a + 1; p -= 2 ) printf ( " %3d", *p ) ;

printf ( " \л" ; /

return 0;

2. Что будет напечатано? ^include <stdio.h>

int main ( voxd ) {

int a[ ] = { 10, 11, 12, 13, 14 }; int *p[] = { a , a + 1, a + 2 , a + 3, a-i-4}, int **pp = p/

pp = pp + 4; printf ( "pp-p =%3d *pp-a =%3d **pp =%3d\n", pp-p,

*pp-a, **pp ) ;

*pp-~; printf ( "pp-p =%3d *pp-a =%3d **pp =%3d\n", pp-p,

*pp-a, **pp ) ;

*++pp; printf( "pp-p ==%3d *pp-a =%3d **pp =%3d\n", pp-p,

*pp-a, **pp );

141

Page 143: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

--^рр; printf( "рр-р =%3d *рр-а =%3d **рр =%3d\n", рр-р,

*рр-а, **рр ) ;

j ce tu im О; )

Ответы можно посмотреть в разд. 18.

Page 144: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

7. ПОЛЯ БИТОВ И ПОБИТОВЫЕ ОПЕРАЦИИ

7.1. Поля битов

в отличие от других языков высокого уровня в языке C++, как и в ассемблерных языках, имеется развитый набор средств манипу­лирования битами. На рис. 44 показано представление символа эк­рана в видеопамяти:

7 6 5 4

Цвет фона

3 Номера битов в байтах

2 1 0 7 6 5 4 3 2 1 0 Цвет

символа Код символа

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 О Номера битов в слове

Старший байт Младший байт

Интенсивность символа Признак мерцания

Рис. 44. Представление символа экрана в видеопамяти

Поля битов объявляются как элементы структуры по правилу:

Спецификация_типа идентификатор: размер поля

В качестве "спецификации типа" задается обычно unsigned int (для шестнадцати- или тридцатидвухразрядного процессора слово из 16 или 32 битов), а размер поля - целая константа в диапазоне от О до 16 или 32. Ниже будет рассматриваться случай с 16-разрядным процессором.

/ / Представление слова видеопамяти^ представляющего символ на // экране, stxract WORD {

unsigned int unsigned, int unsigned int unsigned int unsigned int

} sd;

в виде структуры с битовыми полями

blink: 1; // Мерцание bkgrd: 3; // Цвет фона in tens: 1; // Интенсивность символа forgrd: 3; // Цвет символа ch : 8; // Код символа

// Symbol Display

В структуре поля битов можно смешивать с другими элемен­тами, не являющимися полями битов. Если это происходит, то пер-

143

Page 145: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

вый же элемент структуры, не являющийся полем битов, но сле­дующий за битовым полем, размещается со следующего слова памя­ти из 16 битов. При этом в предыдущем слове часть битов может оказаться неиспользованной.

/ / Доступ к отдельным полям битов в структуре и присваивание // им значений sd.blink = 1; // Установить мерцание // Установить цвет символа - три единичных бита sd,forgrd = 1; sd.ch = 'А'; // Буква «А» прописная

WORD *psd = &sd;

psd->bkgrd = 3; // Установить цвет фона

Допускаются два специальных объявления поля битов. Можно объявлять безымянные поля для того, чтобы следующее поле заняло заданные биты слова:

/ / Использование безымянных полей битов stiracb CONTROL {

unsigned int flagl: 1; unsigned, int s__s : 4;

: 2; // Два неиспользуемых бита unsigned int flag2: 1;

} ;

Можно также объявлять поля битов нулевой длины. При этом размещение следующего поля битов начнется с нового слова из 16 битов и, тем самым, в текущем слове будут автоматически оставле­ны неиспользованные биты. Так можно разделить промежутком два поля битов.

И еще повторно сделаем важное замечание. В языках Си/С+-1-не допускаются указатели на поля битов и на массивы полей битов.

7.2. Побитовые операции

Побитовые операции можно применять только к объектам це­лого и символьного типа. С их помощью можно проверять и моди­фицировать биты в данных целого и символьного типа. Побитовые операции перечислены в табл. 21.

Операции Иу ИЛИ, исключающее ИЛИ. Эти операции дейст­вуют на каждый бит соответствующего операнда (операндов) так, как это показано в табл. 22. При этом если операнды имеют различ-

144

Page 146: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ные типы, то они перед выполнением операции приводятся к одина­ковому (старшему) типу по правилам, аналогичным указанным в подразд. 5.3. С помощью операции "&" удобно проверять и обну­лять биты, операции "|" - устанавливать биты, а операции "^" - про­верять несовпадение битов.

&-1 А

« » ~

Табл. 21. Операция

бинарная - бинарная - бинарная - бинарная - бинарная - унарная

Побитовые операции Назначение

И ИЛИ j Исключающее ИЛИ Сдвиг влево Сдвиг вправо Дополнение до единицы

Табл. 22. Определение операций " & " , "|", ' Бит левого

операнда (Ь/) 0 1 0 1

Бит правого операнда (Ьг)

0 0 1 1

Ы&Ьг

0 0 0 1

Ы\Ьг

0 1 1 1

Л "

Ы" Ьг

0 1 1 0 i

Операция \ : 0001001101100011 0100001100100001

0101001101100011

Операция ^: 0001001101100011 0100001100100001

0101000001000010

Операция &: 0001001101100011 0000000000000001

0000000000000001

Операция сдвига влево. Формат операции:

операнд « выражение

В результате биты "операнда" будут сдвинуты влево на число битов, задаваемое значением "выражения". Освобождающиеся спра­ва биты заполняются нулями. Допустимые значения "выражения" изменяются в диапазоне от О до 8*5'/zeoy( операнд ).

/* Файл РЗЗ. СРР Однофаиловый

иляюстрируе т V

программный действие

проект с операции сдвига

одной влево

функцие (ВС+ + 3.

й. 1)

Пример

iinclude <stdlo.h>

±nt main ( void. ) {

// Для функций ввода-вывода

// Возвращает О при успехе

±nt к == 1;

145

Page 147: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

print f( "\n%dr %d, %d, %d, %d, %d, %d, %d", x « I , x«2, x«3, x«0, x«30r х«-327б8, x«-32161, x«-32766 ) ;

те turn G; }

// Будет напечатано 2, 4 , 8 , 1 , О, 1, 2, 4

Нетрудно заметить, что сдвиг битов "операнда" влево на одну позицию эквивалентен умножению на два. Заметим также, что отри­цательные значения "выражения" или значения, равные или превы­шающие число битов в операнде, в общем случае недопустимы и дают неопределенное значение, зависящее от реализации. Приве­денный выше пример иллюстрирует особенности реализации языка Borland C++ версии 3.1 для подобной ситуации.

Операция сдвига вправо. Формат операции:

операнд » выражение

В результате биты "операнда" будут сдвинуты вправо на число битов, задаваемое значением "выражения". Допустимые значения "выражения" изменяются в диапазоне от О до S'^sizeofl операнд ). Операция сдвига вправо выполняется аналогично сдвигу влево, но отличие состоит в способе заполнения освобождающихся битов: • если "операнд" беззнаковый, то освобождающиеся биты запол­

няются нулями; • иначе, если "операнд" знаковый, то освобождающиеся биты за­

полняются знаковым разрядом (нулями для положительного и единицами для отрицательного "операнда").

Файл Р34.СРР Однофайловый программный проект с одной функцией. Пример

иллюстрирует действие операции сдвига вправо (ВС++ 3.1)

^include <stdio.h> // Для функций ввода-вывода

int main ( void ) // Возвращает О при успехе {

int X = 64;

printf( "\n%d, %d, %dr %d, %dr %dr %d, %d"r x » l , x»2, x»3, x»Or x»30r x»-32768r x»-32767, x>>-32 766 ) ;

return 0;

146

Page 148: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

}

// Будет напечатано 32, 16, 8, 64, О, 64, 32, 16

Аналогично предыдущему случаю заметим, что сдвиг битов "операнда" вправо на одну позицию эквивалентен делению на два. Обратите внимание также, что отрицательные значения "выраже­ния" или значения, равные или превышающие число битов в опе­ранде, в общем случае недопустимы и дают неопределенное значе­ние, зависящее от реализации. Приведенный выше пример показы­вает особенности реализации языка Borland С+-ь версии 3.1 для по­добной ситуации.

Дополнение до единицы. Операция изменяет значения всех битов операнда на противоположные значения:

-выражение

// Рассмотрим пример char с, d; с = с & 'OxlF'; // Обнулить (маскировать) старший

/ / бит с = с & (- ' 0x7F) '; // Маскировать все биты, кроме

// старшего

Операции присваивания. Если бинарные побитовые операции используются в операторах присваивания вида

value = value побитовая_операция выражение

то можно использовать сокращенную запись (табл. 23):

value побитовая_операция= выражение

// Обычная форма Сокраш,енная форма с = с & '0x7F'; с &= '0x7F'/

Приоритеты побитовых операций и порядок их выполнения рассмотрены в табл. 16.

&= 1= Л=:

~= « = » =

Табл. 23. Сокращенная Операция

запись побитового присваивания Назначение

Операция "И" и присваивание Операция "ИЛИ" и присваивание Операция "^" и присваивание i Дополнение до единицы и присваивание Сдвиг влево и присваивание Сдвиг вправо и присваивание

Page 149: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

8. ДИНАМИЧЕСКОЕ РАЗМЕЩЕНИЕ ОБЪЕКТОВ В ПАМЯТИ.

ОДНОНАПРАВЛЕННЫЙ НЕКОЛЬЦЕВОЙ ЛИНЕЙНЫЙ СПИСОК И ОПЕРАЦИИ С НИМ

8.1. Понятие об однонаправленном линейном списке. Динамическое размещение объектов в памяти

Сущность однонаправленного линейного списка (ЛС), предна­значенного для хранения символов, представлена на рис. 45.

Т'

W

'е'

W

'к'

W

'с'

W

'т'

NULL

I start Рис. 45. Однонаправленный линейный список

Каждый элемент ЛС содержит две части (два поля): • основное поле, в котором хранится содержательная информация

(в нашем примере - символ); • вспомогательное поле, в котором хранится указатель на следую­

щий элемент линейного списка. Список содержит два элемента, которые являются особенны­

ми, отличными от других. Это первый (головной) элемент ЛС - его особенность состоит в том, что он снабжен указателем (на рис. 45 таким указателем является Start), Без этого указателя нельзя рабо­тать с линейным списком. Последний элемент ЛС также является особенным, так как в его вспомогательном поле хранится указатель NULL, означающий, что следующего элемента нет.

Из сказанного следует, что элемент ЛС в терминах языка С++ можно определить в виде структуры:

stxract EL ЕМ {

char ELEM

} ;

ELEM

ch; *neKt;

*pe;

// Основное поле: символ // Указатель на следующий элемент

// Указатель на структуру

148

Page 150: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в языках Си/С++ имеется возможность динамического разме­щения некоторого объекта в оперативной памяти (функция malloc{ ) и др. в библиотеке языка Си, оператор new языка СН-+) или освобо­ждения занятой ранее динамической памяти (функция free{ ) в биб­лиотеке языка Си, оператор delete языка C++):

Пример размещения элемента памяти. Среда языка Си

линейного списка в динамической

^include <malloc .h> /* Для функции та Нос */ ^include <stdio.h> /* Для функций ввода-вывода */ ^include <stdlib.h> /'*' Для функции exit */

ре = ( struct ELEM * ) malloc ( sizGof ( struct ELEM ) ) ; ±f( pe == NULL ) /* Обработка результата размещения"^/ {

printf ( "\n Размещение элемента в динамической памяти не" " выполнено " ) ;

exit( 1 ) ; } . . . pe->ch . . . /'*' Значение поля данных структуры */ . . . pe->next . . . /* Указатель на следующий элемент '^/

/* линейного списка */

Пример размещения элемента линейного списка в динамической памяти. Среда языка C++ */

^include <stdio.h> // Для функций ввода-вывода #include <stdlib.h> // Для функции exit

ре = new ELEM; ±f( ре == NULL ) // Обработка результата размещения {

printf ( "\п Размещение элемента в динамической памяти " "не выполнено " ) ;

exit ( 1 ) / } . . . pe->ch . . . / / Значение поля данных структуры . . . pe->next . . . / / Указатель на следующий элемент

// линейного списка

/* Пример освобождения динамической памяти^

линейного списка. Среда языка Си • " /

занятой элементом

finclude <malloc.h> /* Для функции free */

149

Page 151: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±f( ре != NULL ) {

free ре; ре == NULL; I

/* Пример освобождения динамической памяти.

линейного списка. Среда языка С++ */

занятой элементом

±f( ре != NULL ) {

del&te ре; ре = NULL; }

Рассмотрим еще один практически значимый пример разме­щения в динамической памяти двумерного массива (матрицы) и ос­вобождения занятой памяти. /*

*/

Файл DynMem, Демонстра ция

срр работы с ма трицеи в динамической памяти

^include <stdio.h> // Для ввода-вывода ^include <stdlib.h> // Для exit ( )

// Ввод размеров матрицы, размещение матрицы в динамической // памяти и заполнение ее * srold ReadMatrix (

chetr *pFileInp,// Указатель на файл данных unsigned &RowSize, // Строчный размер unsigned &ColSize, // Столбцовый размер int **&рМх ) // Указатель на матрицу

{ // Указатель на структуру со сведениями о файле данных FILE *pStructInp;

// Открытие файла данных для чтения if( ( pStructlnp = fopeni pFilelnp, "г" ) ) == NULL ) {

printf( "\n Ошибка 10, Файл %s для чтения не " "открыт \п", pFilelnp );

jretuarn/

// Чтение размеров матрицы int retcode = fscanf( pStructlnp, "%u %u",

ScRowSize, &ColSize ) ; if( retcode != 2 ) {

printf( "\n Ошибка 20. Ошибка чтения размеров"

150

Page 152: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

;

" матрицы \п" ) / return;

}

// Размещение в ДП массива указателей на строки матрицы рМх = new ±nt * [ RowSize ]; ±£( !рМх ) {

printf ( "\п Ошибка 30. Ошибка размещения в ДП " "массива указателей на строки матрицы \п" ) /

return; }

// Размещение в ДП строк матрицы £ог( unsigned int 1=0; l<RowSize; 1++ ) {

рМх [ 1 ] = new int [ Col Size ]; i£( !pMx[ 1 ] ) {

printf( "\n Ошибка 40. Ошибка размещения в ДП" " строки матрицы \п" ) ;

return; }

}

// Заполнение матрицы £ог( 1=0; KRowSlze; 1 + + ) {

£ог ( unsigned int j = 0; j<ColSlze; j++ ) {

retcode = fscanf( pStructlnp^ " %1" r &pMx[l ] [ j ] ) ;

i£( retcode != 1 ) {

printf( "\n Ошибка 50. Ошибка чтения " "элемента ма трицы \п" ) ;

return; )

} }

fclose ( pStructlnp ) ; / / Закрытие файла данных

return;

int maln( void ) // Возвращает О при успехе {

int "^^рМх; // Указатель на матрицу unsigned г, // Число строк

с; // Число столбцов

// Ввод матрицы ReadMatrlx ( "DynMem. Inp", г, с, рМх ) ;

151

Page 153: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / Здесь проверяем г, с , рМх

/ / . . . // Освобождение динамической памяти, занятой матрицей

// Освобождение ДП, занятой строками матрицы for( unsigned ±nt i=0; Кг; i++ ) {

delete [ ] pMx[ i ]; } // Освобождение ДП, занятой массивом указателей на строки // матрицы delete [ ] рМх;

ret-arn 0; }

Для работы с ЛС используются следующие основные опера­ции: • инициализация; • добавление элемента в начало ЛС; • добавление элемента в конец ЛС; • создание ЛС таким образом, чтобы первый занесенный элемент

оказался в начале списка; • создание ЛС таким образом, чтобы последний занесенный эле­

мент оказался в начале списка; • удаление элемента из начала ЛС; • удаление элемента из конца JIC\ • разрушение ЛС с освобождением занятой им динамической памя­

ти; • печать содержимого ЛС; • добавление или удаление элемента после каждого элемента ЛС,

содержащего заданное значение; • добавление или удаление элемента перед каждым элементом ЛС,

содержащим заданное значение. Реализация перечисленных операций неоднозначна. Рассмот­

рим один из возможных вариантов программирования операций над ЛС, который представляется более удачным.

8.2. Инициализация линейного списка Эта операция является тривиальной и заключается в присваи­

вании указателю на начало линейного списка значения NULL^ озна­чающего, что ЛС пуст. Инициализация списка выполняется самой

152

Page 154: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

первой. Прототип функции Initls^ выполняющей инициализацию ЛС, ее определение и пример вызова приведены ниже. На данном этапе рекомендуем из примера рассмотреть только часть програм­мы, предшествующую главной функции, и все, что относится к функции Initls, Остальной материал будет рассмотрен далее.

Файл LS.CPP Работа с динамической памятью. Однонаправленный линейный

список и операции с ним

^include <stdio.h> // Для функций ввода-вывода ^include <stdlib.h> // Для функции exit

struct EL // Структура для элемента списка {

char ch; // Данные (символ) EL *next; // Указатель на следующий элемент

} ;

// Указатель на начало списка: класс хранения внешний // (область действия и время жизни - вся программа), // используется во всех функциях программного проекта и // через список параметров функций не передается EL * start;

// Прототипы функций void Init_ls ( void. ) ; void Dest^^ls ( void ) ; void Add_end ( cha.r с ) ; void Add__beg ( cbstr с ) ; void Del_end ( void ) ; void Del_beg ( void ) ; void Create_end ( void ) ; void Create__beg( void ) ; void Print_ls ( void ) ; void After_Add( char find,, сЪа.г add ) ; void Before__Add ( char find, char add ) ; void After_Del ( char find ) ; void Before_Del ( char find ) ;

int main( void ) // Возвращает 0 при успехе {

Init_ls ( ) ; // Инициализация списка // Заполнение линейного списка символами из файла LS.DAT: // первый прочитанный символ - в начале списка Сгеаte_beg( ) ; Print__ls ( ) ; // Вывод содержимого списка на экран Dest__ls ( ) ; // Разрушение списка After_Del ( '3'); // Удаление после '3' Print Is( ) ;

153

Page 155: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Add end( ' С ) , // Добавление в конец // элемента 'С' // Удаление после '3'

списка

After_Del ( '3'); Pr±nt_ls ( ) ; Dest_ls ( ) ; // Разрушение списка // Заполнение линейного списка символами из файла LS.DAT: // последний прочитанный символ - в конце списка Create_end( ) ; Print Is( ) ; Dest_ls ( Add__end (

Add_end (

Add beg( Add_beg ( Print Is ( Del_end(

Del_beg(

) ; 'C 'D' ^B^ 'A'

r ) ; ) ;

) ;

) ;

) ;

) ; ) ;

// // // // // // // // // //

Разрушение списка Добавление в конец списка

элемента 'С' Добавление в конец списка

элемента 'D' Добавление в начало элемента Добавление в начало элемента

Удаление последнего элемента списка

Удаление первого элемента спи

'В 'А

СК(

Print_ls ( ) ; Dest_ls ( ) / // Заполнение for( Int i = 1;

Add__end( '2' Print_ls ( ) ; // Добавление '1 Before_Add( '2', Print_ls ( ) ; // Добавление '3 After_Add( '2', Print_ls ( ) ; // Добавление '1 Before_Add( ' 1 ' , Print_ls ( ) ; // Добавление '2 After_Add( '2', Print_ls( ) ; // Добавление '2 After_Add( '2', Print_ls( ) / // Добавление '3 After__Add( '3', Print_ls ( ) ; After_Del ( '3'); Print_ls ( ) ; Before_Del( '1') Print_ls( ) ; Dest_ls ( ) ;

return 0;

// Разрушение списка списка пятью двойками

i-^+ ) i <= 5; ) ;

' перед Ч' ) ;

' после '3' ) ;

' перед Ч' ) /

' после '2' ) ;

' после '2' ) ;

' после '3' ) /

//

//

//

Удаление после

Удаление перед ' 1

Разрушение списка

// Инициализация списка

154

Page 156: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

void Init_ls ( void ) {

start = NULL; // Вначале список пуст

rebvLrn; }

// Paзрушение списка void Dest_ls ( void ) {

if( start == NULL ) {

printf( "\n Список пуст. Удалять нечего" ) ; retuxm/

}

while ( start /- NULL ) Del__beg ( ) ; // Удаление первого элемента списка

return/ }

// Добавление элемента в конец списка void Add_end (

char с ) // Данные добавляемого элемента {

// Указатель на новый (добавляемый) элемент списка EL *temp,

*сиг; // Указатель на текущий элемент

temp = new EL; // 1: динамическое размещение // элемента

if( temp == NULL ) {

printf( "\n Элемент списка не размещен" ) ; exit ( 1 ) ;

} temp->ch = с; // 2: занесение данного temp->next = NULL; // 3: новый элемент является

// последним if( start == NULL ) // Новый список (пустой)

start = temp; // 4а: указатель на начало списка else {

// 46: проходим весь список от начала, пока текущий // элемент не станет последним сиг = start; while( cur->next != NULL )

// Продвижение по списку сиг = cur->next;

// 4в: ссылка последнего элемента на новый, // добавляемый в конец списка

155

Page 157: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

cur~>next = temp; }

return/ }

// Добавление элемента в начало списка void Add_beg(

char с ) // Данные добавляемого элемента {

// Указатель на новый (добавляемый) элемент списка EL *temp;

temp = new EL; // 1: динамическое размещение // элемента

±f( temp == NULL ) (

printf( "\n Элемент списка не размещен" ) ; exit ( 2 ) ;

} temp->ch = с/ // 2: занесение данного // 3: новый элемент ссылается на начало списка temp->next = start; start = temp; // 4: новый элемент становится

/ / первым

return; }

// Заполнение линейного списка символами из файла LS.DAT: // первый прочитанный символ - в начале списка void Create^beg( void ) {

// Данное для элемента, добавляемого в конец списка сЪаг с; // Указатель на структуру со сведениями о файле для // чтения FILE *f__in;

// Открываем файл для чтения ±£( ( f_in = fopeni "Is.dat", "г" ) ) == NULL ) {

printf( "\n Файл Is.dat для чтения не открыт " ) ; exit ( 3 ) ;

} // Создаем список while ( ( с = ( сНаг )fgetc( f_in ) ) != EOF ) {

Add_end( с ) ; } // Закрываем файл i£( ( fclose( f in ) ) == EOF )

156

Page 158: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

{ printf ( "\n Файл Is.dat не закрыт " ) ; e x i t ( 4 ) ;

}

return; }

// Заполнение линейного списка символами из файла LS.DAT: // последний прочитанный символ - в начале списка void Create_end ( void, ) {

// Данное для элемента^ добавляемого в конец списка сЬа.г с; // Указатель на структуру со сведениями о файле для // чтения FILE *f_in;

// Открываем файл для чтения ±£( ( f__in = fopen( "Is.dat", "г" ; ; == NULL ) {

print f ( "\n Файл Is.dat для чтения не открыт " ) ; exit(5);

}

// Создаем список while ( ( с = ( char )fgetc( f_in ) ) != EOF ) {

Add_beg ( с ) ; }

// Закрываем файл ±f( ( fclose( f_in ) ) == EOF ) {

printf( "\n Файл Is.dat не закрыт " ) ; exit(6);

}

rebum; }

/ / Удаление последнего элемента списка void Del_end ( void ) {

EL *prev, // Указатель на предпоследний // элемент

*end/ // Указатель на последний элемент

i£( start - = NULL ) {

printf ( "\n Список пуст. Удалять нечего" ) ; retjim; } // 1: поиск последнего (и предпоследнего) элементов

157

Page 159: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

prev = NULL; end = start; while( end->next /= NULL ) {

prev = end; end = end->next; // Продвижение no списку

} d&lete end; // 2: удаление последнего элемента ±f( prev != NULL ) {

// 3: бывший предпоследний элемент становится // последним prev~>next = NULL;

} else (

start = NULL; // 3: в списке был один элемент }

return; }

// Удаление первого элемента списка void Del_beg ( void. ) {

EL *del; // Указатель на удаляемый элемент

if( start == NULL ) {

printf( "\n Список пуст. Удалять нечего" ) ; return;

} // 1: подготовка первого элемента для удаления del = start; start = del->next; // 2: start сдвигается на второй

// элемент delete del; // 1: удаление первого элемента

return; }

// Печать содержимого списка на экран void Print_ls( void ) {

EL *prn; // Указатель на печатаемый элемент

i£( start == NULL ) {

printf( "\n Список пуст. Распечатывать нечего" ) ; return;

}

prn = start; // Указатель на начало списка print f ( "\п" ) ;

158

Page 160: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

while ( prn != NULL ) // До конца списка {

// Печать данных (символа) элемента printf( "%с" , prn->ch ) ; prn = prn~>next; // Продвижение по списку

}

return/ }

// Добавление элемента add после элемента find void After_Add ( char find, char add ) {

EL *temp, // Указатель на добавляемый элемент *cur; // Указатель на текущий элемент

if( start == NULL ) {

printf( "\n Список пуст. Нельзя найти нужный " "элемент " ) /

return/ } // Поиск элементов, содержащих символ find, с добавлением // после них элемента с символом add сиг = start/ while ( cur != NULL ) {

if( cur->ch == find ) {

// Нужный элемент найден (он является текущим) // 1: динамическое размещение элемента temp = new EL/ if( temp == NULL ) {

printf( "\n Элемент списка не размещен" ) / exit ( 7 ) /

} // 2: занесение данного temp->ch = add/ // 3: ссылка на элемент, который стоял за текущим temp->next = cur->next/ // 4: текущий элемент указывает на новый cur->next = temp/ // 5: новый элемент становится текущим сиг = temp/

) сиг = cur->next/ // Продвижение по списку

}

return/

/ у i^:h*iir-^-k-^irTlf*^Th-^-^9r-^ir*i^-k*-*c***Thilc-^*-ki^-k-k*-!h***T^

159

Page 161: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Добавление элемента add перед элементом find void Before_Add ( char find, cba.r add ) {

EL *temp, // Указатель на добавляемый элемент *cur, // Указатель на текущий элемент "^prev; // Указатель на элемент стоящий

// перед текущим)

±f( start =- NULL ) {

printf( "\n Список пуст. Нельзя найти нужный " "элемент " ) ;

jretujrn/ ; // Поиск элементов, содержащих символ find, с добавлением // перед ними элемента с символом add сиг = start; while ( cur /- NULL ) {

// Нужный элемент найден (он является текущим) ±f( cur->ch == find ) {

// 1: динамическое размещение элемента temp = new EL; if( temp == NULL ) {

printf( "\n Элемент списка не размещен" ) ; exit ( 8 ) ;

} // 2: занесение данных temp->ch = add; // 3: новый элемент указывает на элемент с // символом find temp->next = cur; // 4: если элемент с символом find был первым, // то start смещается влево (на новый элемент) ±£( сиг == start )

start = temp; else

// 4: элемент, стоящий перед сиг указывает на // новый prev->next = temp;

} prev = cur; // Продвижение текущего и

// предыдущего элементов сиг = cur->next; // по списку

}

} return;

// Удаление элемента после элемента find void After Del( char find )

160

Page 162: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

EL *del, // Указатель на удаляемый элемент *сиг; // Указатель на текущий элемент

±£( start == NULL ) {

printf( "\n Список пуст. Нельзя найти нужный " "элемент " ) ;

jr&bVLJCZi;

}

±f( start->next == NULL ) {

printf ( "\n В списке только один элемент. Нельзя " "выполнить данную операцию" ) ;

return/ } // Поиск элементов,, содержащих символ find,, с удалением // элементов г следуюшр1Х за найденными сиг = start; do {

'±f( cur->ch == find ) ( // Нужный элемент найден (он является текущем)

// 1 : указатель на элемент для удаления del = cur->next; // 2: связь текущего элемента с элементом, // следуюш;им за удаляемым cur->next = del->next; delete del; // 3: удаление элемента // 4: является ли теперь текущей элемент // последним? Если "да" - выход из цикла и // функции ±f( cur->next == NULL )

геЬит; } cur = cur->next; // Продвижение по списку

} while( cur->next != NULL ) ;

return; }

// Удаление элемента перед элементом find void. Before_Del ( chstr find ) {

// Указатель на предпредыдуш:ий элемент по отношению к // заданному EL '*'pprev,

'*'dei, / / Указатель на удаляемый элемент *сиг; // Указатель на текущий элемент

lf( start == NULL ) {

161

Page 163: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

printf( "\n Список пуст. Нельзя найти нужный " "элемент " ) ;

} ±£( start->next == NULL ) {

printf( "\n в списке только один элемент. Нельзя " "выполнить данную операцию" ) ;

return/ } // Поиск элементов, содержащих символ find, с удалением // элементов, предшествующих найденным pprev = NULL/ cur = start->next/ while( cur != NULL ) (

±f( cur->ch == find ) { // Нужный элемент найден (он является

// текущим) // Найденный элемент является вторым в ЛС ±f( pprev == NULL ) {

// 1: будем удалять головной элемент del = start/ // 2: первым элементом списка теперь будет // бывший второй start = start->next/

}

else {

// 1: указатель на удаляемый элемент del = pprev->next/ // 2: связь предпредыдущего и текущего // элементов pprev->next = del->next/

}

delete del/ // Удаление элемента } else { // Продвижение указателя pprev требуется, если на

// очередном шаге не было удаления элемента ±f( pprev == NULL )

pprev = start/ else

pprev = pprev->next / } cur = cur->next/ // Продвижение этого указателя

// требуется всегда } retujm/

}

Файл LS.DAT, из которого программа читает исходные дан­ные, имеет следующий вид:

162

Page 164: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1234567890

Результаты выполнения программы выдаются на экран и име­ют вид:

1234567890 Список пуст. Нельзя найти нужный элемент Список пуст. Распечатывать нечего В списке только один элемент. Нельзя выполнить данную операцию

С 0987654321 ABCD ВС 22222 1212121212 123123123123123 11231123112311231123 1122311223112231122311223 11222231122223112222311222231122223 1122223311222233112222331122223311222233 11222231122223112222311222231122223 12222122221222212222122223

8.3. Добавление элемента в начало списка

Эта операция имеет не только самостоятельное значение, но и может быть использована для создания линейного списка, когда по­следний занесенный элемент будет находиться в начале ЛС.

Смысл операции иллюстрирует рис. 46. Прототип функции Add beg, выполняющей добавление элемента в начало списка, ее определение и пример вызова содержатся в вышеприведенном при­мере. На данном этапе также рекомендуем из примера рассмотреть только часть программы, которая относится к функции Addjbeg. Ос­тальной материал рассмотрим далее, применяя указанный подход.

8.4. Добавление элемента в конец списка

Эта операция, как и предыдущая, имеет, как самостоятельное значение, так и может быть использована для создания линейного списка, когда первый занесенный элемент будет находиться в нача­ле ЛС.

Смысл операции иллюстрирует рис. 47. Прототип функции Add_end, выполняющей добавление элемента в конец списка, ее оп­ределение и пример вызова содержатся в примере, приведенном ра­нее.

163

Page 165: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

До выполнения операции Общий случай Особый случай

• h> • W NULL Start = NULL;

t Start После выполнения операции

2: занесение данного «с» 2 занесение данного «с»

•—Н NULL NULL 3: temp->next =

Start;

t 1: динамическое размещение

temp = new EL;

t 4: Start = temp;

t 1. динамическое размещение

temp = new EL;

t 4: Start = temp;

Рис. 46. Добавление элемента в начало линейного списка

8.5. Создание ЛС с первым занесенным элементом в начале

Для создания ЛС с первым занесенным элементом в начале списка достаточно в теле цикла вызывать функцию занесения одно­го элемента в конец списка с аргументом, представляющим собой очередное прочитанное число. Прототип функции Create_beg, вы­полняющей создание списка с первым прочитанным элементом в его начале, определение функции и пример вызова содержатся в приме­ре, приведенном выше.

8.6. Создание ЛС с первым занесенным элементом в конце списка

Эта операция аналогична предыдущей. Для создания ЛС с пер­вым занесенным элементом в конце списка достаточно в теле цикла вызывать функцию занесения одного элемента в начало списка с ар­гументом, представляющим собой очередное прочитанное число. Прототип функции Create end, выполняющей создание списка с первым прочитанным элементом в его конце, определение функции и пример вызова содержатся в том же примере.

164

Page 166: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• — h >

До выполнения операции Общий случай Особый случай

Start = NULL,

t Start

t Start

NULL

После выполнения операции 2: 2: занесение данного « о

# • 4в:

NULL NULL 3: temp->next = NULL;

t t 1- 1-

динамическое размещение temp = new EL;

t t 46: cur 4B: cur->next = temp; 4a: Start = temp;

Рис. 47. Добавление элемента в конец списка

8.7. Удаление элемента из начала списка Данная операция, также как и операции добавления элемента в

начало или конец линейного списка, позволяет решить две задачи: • удаление одного элемента из начала ЛС; • разрушение линейного списка с освобождением занятой им ди­

намической памяти путем циклического выполнения операции удаления элемента из начала списка.

Реализация операции, представленная в графической форме, дана на рис. 48. Прототип функции Del beg, выполняющей удаление первого элемента списка, ее определение и пример вызова содер­жатся в программном проекте, приведенном в подразд. 8.1.

165

Page 167: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

До выполнения операции Общий случай Особые случаи:

а) в ЛС один элемент б)

• — — • ! • — ^ > I I

• • ^ NULL NULL Start = NULL;

Start t Start

После выполнения операции

• ^ NULL

Вывод сообщения и возврат из

функции

1: del = Start;

t 2: Start = del->next;

3: delete del;

t 1: del = Start;

t 2: Start = del->next = NULL; 3- delete del;

Рис. 48. Удаление элемента из начала списка

8.8. Удаление элемента из конца списка Реализация операции, представленная в графической форме,

дана на рис. 49. Прототип функции Delend, выполняющей удаление последнего элемента списка, ее определение и пример вызова со­держатся в программном проекте, приведенном в подразд. 8.1.

8.9. Разрушение ЛС с освобождением занятой им динамической памяти

Разрушение линейного списка с освобождением занятой им динамической памяти может быть выполнено путем циклического выполнения операции удаления элемента из начала списка.

Прототип функции Destls, выполняющей разрушение списка, ее определение и пример вызова содержатся в программном проек­те, приведенном в подразд. 8.1.

166

Page 168: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

До выполнения операции Общий случай

t Start

L__

• — и • — — •

t start

Особые случаи: а) в ЛС один элемент б)

NULL 1 I NULL

t Start

После выполнения операции

Start = NULL;

NULL

Вывод сообщения и возврат из

функции

t 1: prev

t t end 1: prev = NULL;

end 2: delete end; 2: delete end;

3 prev->next = NULL; 3: Start = NULL; Рис. 49 . Разрушение линейного списка

8.10. Печать содержимого ЛС

Печать содержимого линейного списка может быть выполнена путем циклического выполнения печати содержимого текущего элемента списка и продвижения по списку от начала до конца. Опе­рация тривиальна и не требует особых пояснений.

Прототип функции P r i n t l s , выполняющей печать содержимого линейного списка на экран, ее определение и пример вызова содер­жатся в программном проекте, приведенном в подразд. 8.1.

8.11. Добавление элемента после каждого элемента ЛС, содержащего заданное значение

Реализация операции, представленная в графической форме, дана на рис. 50.

Прототип функции AfterAdd, выполняющей добавление эле­мента с данным add после каждого элемента ЛС, содержащего за­данное значение y?«(i, ее определение и пример вызова содержатся в программном проекте, приведенном в подразд. 8.1.

167

Page 169: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

До выполнения операции Общий случай

t Start

^

find

w ,, . NULL

После выполнения операции 2:

find

4: W

add 3:

• w NULL

Особые случаи

Start = NULL,

Вывод сообщения и

возврат из функции

t t t Start cur 1: temp = new EL;

2: temp->ch = add; 3: temp->next = cur->next, 4. cur->next = temp; t 5: cur = temp;

Рис. 50. Добавление элемента после каждого элемента ЯС, содержащего заданное значение

8.12. Добавление элемента перед каждым элементом ЛС, содержащим заданное значение

Реализация операции, представленная в графической форме, дана на рис. 51.

Прототип функции BeforAdd, выполняющей добавление эле­мента с данным add перед каждым элементом ЛС, содержащим за­данное значение find, ее определение и пример вызова содержатся в программном проекте, приведенном в подразд. 8.1.

8.13. Удаление элемента после каждого элемента ЛС, содержащего заданное значение

Реализация операции, представленная в графической форме, дана на рис. 52.

168

Page 170: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

До выполнения операции Общий случай

•—ь>

find

NULL

t Start После выполнения операции

2:

• \-> 4:

•• W

add 3:

' W

find

—• NULL

t start

Особые случаи

a) Start = NULL; 6) cur = Start;

a) Вывод сообщения и

возврат из функции

б) См. реализацию

шага 4 prev t cur 1: temp = new EL; 2: temp->ch = add; 3: temp->next = cur; T 4: Start = temp при cur = Start

или prev->next = temp в остальных случаях

Рис. 51. Добавление элемента перед каждым элементом ЛС, содержащим заданное значение

Прототип функции After_Del, выполняющей удаление элемен­та после каждого элемента ЛС, содержащего заданное значение find, ее определение и пример вызова содержатся в программном проек­те, приведенном в подразд. 8.1.

При реализации операции удаления производится просмотр элементов линейного списка, начиная с первого элемента до пред­последнего элемента списка.

Отличительной особенностью операции является то, что если после удаления элемента текущий элемент является последним, то необходимо выйти из цикла просмотра элементов и из данной функ­ции. Тем самым предотвращается ошибка, связанная с выполнением продвижения в ЯС на следующий элемент сиг — cur->next и после­дующей проверкой условия повтора цикла cur->next != NULL.

169

Page 171: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

8.14. Удаление элемента перед каждым элементом ЛС, содержащим заданное значение

Реализация операции, представленная в графической форме, дана на рис. 53.

До выполнения операции Общий случай

1 t start

w

find

w w ^ NULL

После выполнения операции

find

2: w w NULL

t start

Особые случаи

а) Start = NULL, б) Start->next =

NULL,

а) Вывод сообщения и

возврат из функции б) Вывод

сообщения и возврат из функции

t t cur 1 • del = cur->next; 2: cur->next = del->next;

3. delete del; 4: если после удаления текущий элемент -

последний, то выполняется выход из функции

Рис. 52. Удаление элемента после каждого элемента ЛС, содержащего заданное значение

Прототип функции BeforDel , выполняющей удаление элемен­та перед каждым элементом ЛС, содержащим заданное значение find, ее определение и пример вызова содержатся в программном проекте, приведенном в подразд. 8.1,

При реализации операции удаления производится просмотр элементов линейного списка, начиная со второго элемента до по­следнего элемента списка.

Отличительной особенностью операции является то, что если удаление элемента выполнено, то продвижение указателя на пред-предыдущий элемент не требуется. Указатель же на текущий эле­мент должен продвигаться всегда.

Другой особенностью операции является то, что реализация шагов 1: и 2: в случае, когда текущий элемент является вторым в ЛС, отличаются (см. рис. 53).

170

Page 172: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

8.15. Зачем нужен линейный список Для хранения и обработки информации наряду с ЛС можно

использовать и массивы. Например, вместо ЛС, приведенного на рис. 45, можно использовать символьный массив из пяти элементов, причем это сэкономило бы оперативную память. Значит ли это, что ЛС не следует использовать? Конечно же, нет!

До выполнения операции Общий случай

t Start

" •

w

... w

find

, NULL

После выполнения операции

2: w

find

NULL

t start

t t t pprev cur

1 • del = pprev->next или del = Start, если текущий элемент в ЛС второй

2: pprev->next = del->next или Start = Start->next, если текущий элемент в ЛС второй

3: delete del,

Особые случаи

а) Start = NULL, б) Start->next =

NULL,

а) Вывод сообщения и

возврат из функции б) Вывод

сообщения и возврат из функции

Рис. 53. Удаление элемента перед каждым элементом ЛС, содержащим заданное значение

Например, использование ЛС обеспечивает следующие пре­имущества: • вставка или удаление элементов в ЛС происходит проще и быст­

рее (вставка или удаление элемента в начальную часть массива большого размера требует выполнения значительного объема ра­боты);

• при работе с ЛС не требуется знать его максимальный размер (при размещении же массива надо заранее знать его требуемый размер или размещать массив максимального размера в расчете

171

Page 173: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

на наихудший случай).

8.16. Упражнения для самопроверки Определены следующие данные:

stjract ELEM // Структура для элемента списка {

±nt dat; // Данное struct ELEM

*next; // Указатель на следующий элемент } *сиг, // Указатель на текущий элемент

// списка *start/ // Указатель на начало списка

Во входном файле Is.dat содержится некоторое количество це­лых чисел, разделенных символами пробельной группы ( ' ', '\^', '\«' ).

1. Написать прототип, определение и пример вызова функции для ввода из входного файла имеющихся там чисел, представив вве­денную информацию линейным списком, в котором каждый узел (динамически размещенная структура) содержит две компоненты. Первая компонента хранит данное (введенное число), а вторая -указывает адрес следующей структуры. При этом первое прочитан­ное число Должно находиться в начале линейного списка. Исходные данные и результаты работы функции следует передавать через спи­сок параметров.

С целью обработки ошибок предусмотреть контроль значений, возвращаемых функциями библиотеки языков Си/С++.

2. Дополнительно написать прототип, определение и пример вызова функции, которая в процессе просмотра списка выводит данные (числа) в файл на магнитном диске is.out. Требования к оформлению функции и обработке ошибок аналогичны указанным в п. 1 требованиям.

3. Дополнительно написать прототип, определение и пример вызова функции, которая разрушает линейный список. Требования к оформлению функции и обработке ошибок аналогичны указанным в пункте 1 требованиям.

Page 174: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

9. ПРЕПРОЦЕССОР ЯЗЫКА Си/С++

Перед собственно компиляцией программы к ней применяется процедура предварительной обработки. Она выполняется програм­мой, называемой препроцессором:

ПРЕдварительный ПРОЦЕССОР

Препроцессор расширяет возможности языка следующим. 1. Подстановкой имен (заменой символических аббревиатур

на соответствующие значения), т.е. наличием макроопределений. 2. Включением файлов. 3. Условной компиляцией. Препроцессор обеспечивает и некоторые другие, гораздо реже

используемые возможности. По этой причине их рассматривать не будем.

Наличие препроцессора сокращает затраты времени на разра­ботку программ, обеспечивает создание переносимых, более читае­мых и удобных для сопровождения и отладки программ.

9.1. Директивы препроцессора

Указания препроцессору вставляются в программу в виде ди­ректив. Директивой служит строка, в первой позиции которой ука­зан символ диеза "#". Допускается, хотя и не рекомендуется, нали­чие предшествующих символу диеза пробелов и табуляторов. За символом диеза следует название директивы. Между ними допуска­ется, хотя и не рекомендуется, произвольное число пробелов и/или табуляторов.

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

9.2. Подстановка имен

Подстановка имени (макроопределение) представляет собой символическое имя, которое присваивается фрагменту программы (строке элементов языка). Когда впоследствии препроцессор обна-

173

Page 175: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

руживает это символическое имя в программе, то он заменяет имя соответствующей строкой.

Для подстановки имен предусмотрены две директивы препро­цессора: • создать макроопределение (Ude/ine); • удалить макроопределение, т.е. сделать его неопределенным

{Uundef). Пример использования директивы Udefine приведен на рис. 54.

Признак директивы - его рекомендуется размещать в начале строки. Допускаются, но не рекомендуются, предшествующие пробелы и/или табуляторы

Служебное слово препроцессора.

По крайней мере один пробел или табулятор.

#define NULL ЛО'

I — Конец строки - завершает макроопределение (макроопределение должно размещаться в одной строке).

Текст макроопределения - любое число символов в пределах одной строки.

По крайней мере один пробел или табулятор

Имя макроопределения - любой идентификатор Для более легкого «узнавания» макроопределения рекомендуется использовать в нем только прописные буквы.

Между знаком диеза и служебным словом препроцессора допускаются, но не рекомендуются, пробелы и табуляторы.

Рис. 54. Структура директивы на примере директивы Udeflne

Приведем несколько примеров: #define SUCCES 1 ^define NULL '\0' ^define MAX_SIZE 50 ^define UNIX // Пустое макроопределение ^define printf myprintf

Приведенные примеры показывают, что, как указывалось вы­ше, запись имени макроопределения прописными буквами не обяза-

174

Page 176: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

тельна. Однако использование в именах макроопределения только прописных букв позволяет легко отличить макроопределения от других элементов программы.

Обратите внимание на то, что директивы включения препро­цессора не требуют завершающей точки с запятой. При ее наличии точка с запятой войдет в текст макроопределения, замещающий имя макроопределения. Это может стать причиной ошибки.

Следующей, достаточно распространенной, ошибкой является включение пробелов в имя макроопределения, так как идентифика­тор не может содержать пробелов. Следует помнить, что для пре­процессора имя макроопределения заканчивается на первом же про­беле или табуляторе. Все символы, следующие за ним, рассматри­ваются как замещающая строка.

После определения имени макроопределения всякое его вхоэю-дение в текст программы (за исключением вхождения в символьные и строковые константы) будет замещаться связанной с ним стро­кой символов.

Примеры замещения макроопределений, созданных приведен­ными выше директивами ^define., содержатся в табл. 24.

Табл. 24. Примеры замещения макроопределений До препроцессора

if (scanner 0 = =SUCCES)pnntf("Cmon\n ");

struct INDEXJNFO index[MAX_SIZE];

ifOine[pos]=-^NULL) printf("3mo NULLW):

После препроцессора

if (scanner 0==l)myprintf("Cmon \n ");

struct INDEX JNFO index[50];

if(line[pos]=='\0') myprintf("3mo NULL\n"):

Обратите внимание, что строка NULL внутри строковой кон­станты не подверглась замене.

Для отмены макроопределения можно воспользоваться директивой Uundef, действие которой иллюстрирует табл. 25.

Отмена подстановки имени макроопределения с помощью ди­рективы i^undef остается в силе до конца файла, в котором оно встретилось, если только это имя не будет заново определено но­вой директивой i^define.

При использовании директивы Udefine можно указывать пара­метры при имени макроопределения:

^include <stdio.h>

^define AREA (г) (3 . 14* (г) * (г) )

175

Page 177: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Таблица площадей кругов ±nt main ( void. )

float radius = 1.Of;

printf ( "\n Радиус while( radius < 10.5f ) {

printf( "%f radius -h= 1 . Of ;

Площадь \n" ) ;

%f \ л " , radius, AREA( radius ) ) .

геЬмхпл 0;

Табл. 25. Отмена макроопределения директивой i^undef До препроцессора

include <stdio h> Ые/гпе print/myprintf int main( void )

\{ printf( "Введите дату: ");

^undefprintf printf( "Введите время: "), return 0,

J

После препроцессора

Текст файла stdio.h после обработки его препроцессором ini main( void) {

myprintf( "Введите дату: "),

printf( "Введите время- "); return 0;

}

В этом примере второй аргумент AREA( radius ) в вызове функ­ции ргш^/'заменяется на (Ъ.\4*(radius)*(radius)). Обратите внимание также, что в макроопределении не только параметр г, но и весь текст макроопределения заключены в круглые скобки. Эти скобки позволяют избежать ошибок из-за возможных побочных эффектов, связанных с приоритетами выполнения операций:

^include <stdio.h>

^define AREA (г) 3.14*r*r

2.Of/AREA(radius) заменяется на 2.0f/3.14*radius*radius, ошибка AREA(start-end) заменяется на 3.14*start-end*start-end, также ошибка

Директива Udefine моэюет содер^юатъ в круглых скобках не только один, но и любое число параметров, разделенных запятыми.

176

Page 178: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

9.3. Включение файлов

Иногда один и тот же фрагмент программы встречается в не­скольких файлах, образующих программу. Включение такого обще­го фрагмента в несколько исходных файлов программы выполняется с помощью директивы

^include "путь_к_файлу"

или

^include <путь_к_файлу>

Здесь путь_к_файлу означает корректную запись вида

диск: \ путь_по__ка талогам\ имя__файла_с_ра сширением

Для включаемых файлов принято использовать расширение .И или .hpp.

Если указанный в директиве файл будет найден, то строка с директивой Uinclude будет заменена содержимым этого файла. По­иск включаемого файла выполняется в каталоге, указанном в дирек­тиве ^include. Если

диск: \путь_по_каталогам\

отсутствует, то при использовании записи в форме: • " п у т ь к ф а й л у " поиск ведется сначала в текущем каталоге, а за­

тем в каталогах включаемых файлов, определенных в интегриро­ванной среде программирования;

• <путь к_файлу> поиск ведется сразу в каталогах, определенных в интегрированной среде программирования.

Между названием директивы и путем к файлу может нахо­диться любое число пробелов и табуляторов, в том числе и не одно­го, но рекомендуется использовать между ними один пробел.

Во включаемые файлы помещают директивы Uinclude; прото­типы функций; определения встроенных {inline) функций; объявле­ния {extern) данных, определенных в другом файле; определения {const) констант; перечисления {епит), директивы условной транс­ляции {#ifndef, Uendif и др.), макроопределения {^define), имено­ванные пространства имен {namespace), определения типов {class, struct), объявления и определения шаблонов {template), общие для нескольких исходных файлов, составляющих одну программу.

177

Page 179: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Хотя многие из перечисленных средств нами еще не рассматрива­лись, было целесообразно привести здесь указанные сведения.

9.4. Условная компиляция

Нередко, например, при отладке, требуется иметь возможность включать или исключать некоторые части программы на этапе ком­пиляции. Именно для этих целей и предназначена условная компи­ляция.

В зависимости от конкретного "условия" препроцессор может включать или исключать строки из программы. Для этих целей ис­пользуется пять директив, указанных в табл. 26.

Табл. 26. Директивы условной компиляции Директива

Ш/<константное выраэюение>

Mfdef идентификатор

Mfndef идентификатор

Uelse

Uendif

Функция Компилировать строки, следующие за директивой, если <константное выражение> отлично от нуля ("истина") Компилировать строки, следующие за директивой, если "идентификатор" определен с помощью Udefine Компилировать строки, следующие за директивой, если "идентификатор" не определен с помощью директивы define или определение отменено с помощью Uundef Используется в сочетании с директивами Ш/, Uifdef, Uifndef как отрицание условия Заверщает область действия директив #if, m/def, m/ndef, Uelse

Эти директивы подобны традиционной конструкции if-then-else. Иллюстрирующий пример приведен в табл. 27.

Если в этом примере удалить директиву

#define TRACE

ТО после обработки препроцессором будем иметь текст файла в сле­дующем виде:

void getline ( sroid ) /

±nt main ( void ) {

get line( ) ;

return 0;

178

Page 180: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

void, get line ( void. ) (

return;

Табл, 27. Использование директив условной компиляции До препроцессора

#include <stdio.h> Mefine TRACE void gedine( void ) ; int main( void) и mfdef TRACE

printf( "Main \n"); Uendif

getline(); return 0;

} void getlinef void) { mfdefTRACE

printf( "Getline \n"); ^endif

return; }

После препроцессора Текст файла stdio.h после обработки его препроцессором void getline( void); int main( void ) {

printfC'Main \n");

getline(); return 0;

} -void getline( void ) {

printf( "Getline \n");

return; }

Очень часто директивы условной трансляции используются для предотвращения многократного включения заголовочных фай­лов:

/-"

V

stdlo h Definitions for stream Inpu t/OL itput.

#lfndef STDIO_H ^define STDIO_H // Текст включаемого файла

#en'dlf

Как указывалось выше, существуют и другие, менее употреби­тельные директивы препроцессора. Например, в Visual C++ б име­ются также следующие директивы:

• #е///'(относится к директивам условной компиляции); • #Нпе (позволяет включать номера строк исходного кода заим­

ствованных файлов);

179

Page 181: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• i^error (обычно включается между директивами #(/'- if^endif для проверки какого-либо условия на этапе компиляции; при выполне-ниии такого условия компилятор выводит сообщение, указанное в terror и останавливается);

• i^pragma (позволяет настраивать компилятор с учетом специ­фических особенностей конкретной машины или операционной системы - указанные особенности индивидуальны для каждого компилятора);

• import (имеет отношение к включению библиотек типов в cow-технологии).

Их обсуждение выходит за рамки данного пособия.

9.5. Указания по работе с препроцессором

Всякий раз, когда модифицируется включаемый файл имя. И, требуется заново компилировать каждый файл, в который файл за­головка включен с помощью директивы Uinclude (среда программи­рования не всегда контролирует изменение включаемого файла, хо­тя в последних версиях это предусмотрено).

Препроцессор можно использовать для того, чтобы изменить внешний вид программы. Например, блок операторов можно офор­мить в стиле языка Паскаль следующим образом:

^define begin { ^define end }

i n t main ( void. ) begin

rGturn 0; end

Иногда случается, что текст макроопределения не помещается на одной строке. В подобных случаях признаком продолжения стро­ки с текстом макроопределения является символ "\", например:

ifdefine SUM_ZERO_ARRAY( array, size, sum ) { int i^O;

sum = 0; while( i < size ) {

sum += array[ I ]; array[ i J = 0; i + +;

} }

180

Page 182: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Большинство компиляторов языков Си/С++ поставляется вме­сте с набором заголовочных файлов. Одним из примеров такого ро­да является файл stdio,h. При использовании стандартных заголо­вочных файлов следует посмотреть их содержимое, чтобы случайно не переопределить стандартное имя. Стандартные заголовочные файлы разработаны квалифицированными программистами и по этой причине их также целесообразно посмотреть.

Page 183: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

10. РЕДКО ИСПОЛЬЗУЕМЫЕ СРЕДСТВА ЯЗЫКОВ СИ/С++

10.1. Объявление имени типа typedef

с помощью typedef ыожпо приписать имя существующему ти­пу данных. Примеры использования объявления имени типа приве­дены в табл. 28.

Табл. 28. Объявление имени типа Объявление имени типа

typedef int INTEGER, typedef int SHORT, typedef long LONG, typedef char * STRPTR; typedef struct

{ double r; double i;

} COMPLEX:

Пример применения INTEGER a, b, SHORT c,d, LONGe,f STRPTR ^, h, COMPLEX k.

Значение int a, b; int c, d; longe,f. char *g, *h, struct {

double r; double i.

Из приведенной таблицы следует, что объявление имени типа в общем виде записывается следующим образом:

typedef <type definition> <identifier>;

Заметим, что в объявлении имени типа <type definition> и <identifier> можно поменять местами, хотя делать это не рекоменду­ется. Чтобы можно было легче обнаружить в программе введенное имя типа лучше использовать в identifier прописные буквы, как это сделано в вышеприведенной таблице.

Из табл. 28 также следует, что простейшая форма typedef по­хожа на директиву препроцессора ^define. Отличие заключается в том, что typedef обрабатывается компилятором, а директива Udefine - препроцессором. При этом компилятору доступны дополнитель­ные проверки, обеспечивающие более глубокий уровень выявления ошибок.

Имя, объявленное в typedef можно использовать в том же кон­тексте, что для спецификации типа, например, как аргумент опера­ции sizeof.

Основными целями использования (ype<ie/являются:

182

Page 184: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• повышение удобства чтения программы; • повышение мобильности программы.

Если typedef находится внутри функции, то его область дейст­вия локальна и ограничена этой функцией. Если же объявление име­ни типа расположено вне функции, то его область действия гло­бальна. В последнем случае typedef часто помещают во включаемые файлы.

10.2. Объекты перечислимого типа

Объект перечислимого типа представляет собой объект, значе­ния которого выбираются из фиксированного множества идентифи­каторов, называемых константами перечислимого типа. Синтаксис определения объекта перечислимого типа представлен на рис. 55 в виде синтаксической диаграммы.

Определение объекта перечислимого типа

—•fenum V w Идентификатор перечислимого

типа

{ > *

о Константа

перечисли­мого типа

^ }

Идентификатор объекта перечислимого типа

Константа перечислимого типа

< : >

Идентификатор

Константное выражение с целочисленным значением

Рис. 55. Определение объектов перечислимого типа

Пример определения объекта перечислимого типа:

епгпа languages { с , разе, ada ^ modula2^ forth } master;

или В эквивалентной форме

183

Page 185: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

enum languages{ с , разе, ada, modula2, forth } ; languages master;

Здесь languages - новый перечислимый тип, a master - объект типа languages.

Значением master может быть один из идентификаторов:

с, pasc, adaг modula2, forth

Например, можно написать:

master = с; ±f( master == с )

printf( "\п Я знаю язык См" ) ; switch( master ) {

сазе с:

break;

сазе forth: break;

cie£aul t:

}

Используя идентификатор перечислимого типа можно опреде­лить дополнительные объекты, например:

languages о1, о2;

Теперь имена o l , о2 обозначают объекты типа languages. Внутренним представлением каждой константы перечислимо­

го типа служит целое значение (типа int). При объявлении перечис­лимого типа

envaa languages { с , pasc^ ada, modula2, forth } ;

его константам (слева направо) автоматически присваиваются воз­растающие целые значения О, 1,2, 3, 4.

Как следует из рис. 55, при объявлении перечислимого типа можно задать явное присваивание его константам целых значений, например:

enum languages { с = -1, разе = 4, ada, modula2, forth = 4);

Тем константам, значения которых явно не задано, присваива­ется значение предшествующей константы, увеличенное на единицу.

184

Page 186: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Таким образом, константе ada соответствует значение 5, а константе modula2 - значение 6. Разным константам перечислимого типа мо­жет соответствовать одно и то же значение {pasc^ forth).

Рассмотрим следующий пример:

#include <stdlo.h>

±nt mam ( vaid ) {

enxna t{ c==-l^ pasc=4^ ada, modula2, forth=4 } ; t m, ml;

m = ada; // Следующее присваивание не вполне корректно -// компилятор формирует предупреждение, но программа // выполняется ml = 5 / printf ( "\п т = %d, ml = %d", т, ml ) ; // Данные присваивания также не вполне корректны -// компилятор формирует предупреждение, но программа // выполняется т = О; ml = 6; printf( " \ л т = %d, ml = %d, ada = %d", m, ml, ada ) ;

return 0;

Результаты выполнения программы имеют вид:

т =^ 5, ml = 5 т = О, ml = б, ada = 5

Объекты перечислимого типа, которым присвоены значения констант перечислимого типа, также как и константы перечислимо­го типа, можно использовать в любом выражении вместо целых кон­стант. Вместо них подставляются соответствующие им целые значе­ния. Сказанное подтверждает и приведенный выше пример.

Объектам перечислимого типа можно присваивать любой класс хранения, кроме register. Область действия и время жизни для них определяются таким же образом, как и для рассмотренных ранее объектов других типов.

Приведем еще несколько примеров перечислимых типов из файла .. \include \graphics. h:

епит COLORS {

BLACK, /* dark colors */ BLUE, GREEN,

185

Page 187: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW, WHITE

/* light colors */

enum graphics_errors {

grOk grNoIni t Graph grNotDetected grFileNotFound grInvalidDriver grNo Loa dMem grNoScanMem grNoFlооdMem gr Font Not Found grNoFon tMem grInvalidMode

grError grIOerror grInvalidFont grinvalidFontNum grInvalidVersion

= = = - = = = = = = = = = = = =

0, -i. -2, -3, -4, -5, -6, -7, -8, -9, -10,

-11. -12, -13, -14, -18

graphresult error return codes */

generic error */

10.3. Объединения Подобно структуре, объединение представляет собой агрегати-

рованный тип данных. Синтаксис объявления объединения иденти­чен синтаксису объявления структуры, только вместо служебного слова struct используется служебное слово union. Различие между структурой и объединением состоит в том, что каждому элементу объединения выделяется одна и та эюе область памяти^ а не раз­личные области, как в структуре.

Синтаксис объединения поясняется следующей записью: vLn±oTi [<идентификатор типа объединения>] {

<тмп> <идентифмкатор>;

186

Page 188: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

} [<список объектов-объединений>]/

Примеры:

union INT__OR_LONG {

int 1; long- 1;

} а_питЬег, b_number;

или в эквивалентной форме

union INT__OR_LONG // Объявление типа объединения (

int 1; long 1;

} ; // Определение объектов-объединений с типом INT_OR_LONG INT_OR_LONG a_number, Ь_питЬег;

Для объекта-объединения апитЬег или b_number можно легко выполнить преобразование целого значения в длинное целое или наоборот. Для преобразования целого значения в длинное целое достаточно выполнить следующие действия:

а_питЬег. 1 = 7/ / / Теперь а_питЬег. 1 имеет // значение 11

Наряду с преобразованием типов с помощью объекта-объединения можно получить доступ к отдельным байтам объекта, как это показано в следующем примере.

union {

long lvalue; dovibl e dvalue; char chvalue; char cvaluel 8 ];

} value;

Определен объект с именем value, размер которого равен 8 байтам (наибольший из размеров для типов long, double, char, char[ 8]).

/ / Доступ к 4 байтам как к объекту типа long value.lvalue // Доступ к 8 байтам как к объекту типа double value.dvalue value.chvalue // Доступ к байту как к объекту типа

// char

187

Page 189: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

к каждому из этих байтов можно осуществить доступ по от­дельности, используя массив символов value.cvalue:

value, cvalue[ 3 ] // Доступ к 4 байту

Наряду с объектами-объединениями можно работать и с указа­телями на эти объекты, как показано ниже:

union (

long lvalue; dovLble dva lue; сЬлг chvalue; сЬа.з: cvalue[ 8 ];

} valuef upvalue = &value;

pvalue->lvalue /* эквивалентно */ value.lvalue

Объектам с типом объединения можно присваивать любой класс хранения, кроме register. Область действия и время жизни для них определяются таким же образом, как и для рассмотренных ранее объектов других типов.

Page 190: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

11. МОДЕЛИ ПАМЯТИ

Материал данного раздела в основном освещает вопросы ис­пользования оперативной памяти процессора, относящиеся к при­ложениям для шестнадцатибитной среды DOS и WINDOWS с уче­том особенностей процессоров INTEL 80x86.

Модель памяти для программ на языках Си/С++, работающих в шестнадцатибитной среде, определяет, как программа использует память компьютера. Модель памяти связана с архитектурой процес­сора.

Процессоры INTEL 80x86 используют сегментную организа­цию памяти, позволяющую адресовать 1 Мбайт памяти. Так как все регистры процессора шестнадцатиразрядные, то прямой доступ имеют только 64 Кбайта памяти (диапазон шестнадцатиразрядных беззнаковых адресов О, 1, 2, ..., 2^^-1). Эти 64 Кбайта памяти назы­ваются сегментом. Для того чтобы адресовать 1 Мбайт памяти, тре­буется двадцатибитовый адрес. Поэтому для представления двадца­тибитового адреса используются два регистра (32 бита). Один ре­гистр содержит адрес сегмента (регистр CS - указатель сегмента ко­да, DS - указатель сегмента данных, SS - указатель сегмента стека, ES - указатель дополнительного сегмента), а второй регистр содер­жит смещение в сегменте.

Полный двадцатибитовый адрес, адресующий все адресное пространство процессора, вычисляется следующим образом (рис. 56).

ЮРА : 01С2

16-ричный код смещения 16-ричный код сегмента

Сегментный регистр

Сегментный регистр после сдвига

Смещение

0001 0000 1111 1010 (ЮРА)

0001 0000 1111 1010 0000 (ЮРАО)

0000 0001 1100 0010 (01С2)

0001 0001 0001 0110 0010 (11162) Рис. 56. Получение полного двадцатиразрядного адреса

189

Page 191: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Значение сегментного регистра сдвигается влево на четыре разряда (на одну шестнадцатиричную цифру) и к полученному зна­чению добавляется смещение. Как следует из рис. 56, начальный ад­рес сегмента всегда является двадцатибитовым числом, а так как сегментный регистр имеет только шестнадцать бит, то недостающие младшие четыре бита всегда подразумеваются равными нулю. Это означает, что сегменты всегда начинаются на границе шестнадцати байт или параграфа (отрезок памяти из смежных шестнадцати байт называется параграфом).

Сегменты памяти могут быть смежными, разделенными, пере­крываться полностью или частично. Так как сегменты могут пере­крываться, то одна и та же ячейка памяти может быть адресована более чем одним адресом. Например,

10F0 : 0262 И 10Е0 : 0362

указывают на один и тот же адрес памяти. Когда программа загружается в основную память, ее код и

данные загружаются в отдельные сегменты памяти. Эти два сегмен­та называются сегментами по умолчанию.

11.1. Адресация near, far и huge

Специальные ключевые слова

near - ближний г tan: - дальний, hugre - огромный

используются в программах на языках Си/С+н- для модификации оп­ределений переменных и функций и определяют их размещение в памяти, отличное от стандартного размещения.

Когда эти ключевые слова используются с указателями, то они изменяют размер указателя, который определяется выбранной моде­лью памяти. Имеется три типа указателей (три типа адресации): near (16 бит), far (32 бита) и huge (32 бита).

Адрес near. Доступ внутри сегмента по умолчанию возможен через шестнадцатибитовое смещение, так как адрес сегмента по умолчанию всегда известен. Например, адрес объекта в сегменте данных по умолчанию получается сложением содержимого шестна­дцатибитовой величины указателя на объект (смещения) с содержи­мым регистра сегмента данных DS, сдвинутым влево на четыре би­та. Это шестнадцатибитовое смещение называется адресом near.

190

Page 192: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Аналогично формируется адрес команды в сегменте команд по умолчанию (вместо регистра DS используется регистр CS).

Доступ к данным или командам из сегментов по умолчанию в языках Си/С++ осуществляется через указатели near:

тип near *near_pointer/

Например,

int near *^_Р^'

Так как для доступа к данным или командам через адрес near требуется только шестнадцатибитовая арифметика, то ссылки near наиболее эффективны.

Адрес far. Когда данные или код программы выходят за преде­лы сегментов по умолчанию, адрес должен состоять из двух частей: адреса сегмента и адреса смещения. Такие адреса называются адре­сами ^аг. Доступ вне сегментов по умолчанию осуществляется через указатели Уаг:

тип far *far_pointer;

Например,

±nt far '^f_p;

Указатели y fr позволяют адресовать всю память, но имеют сле­дующие особенности.

1. Пусть имеются три указателя/аг - ptrl ^ ptr2, ptr3 - на одну и ту же ячейку памяти:

ptrl -ptr2 -ptr3 -

- 5F20 ; - 5F21 ; - 5F41 :

: 0210, : 0200, : 0000.

Над указателями допустимы операции сравнения и правомер­ны следующие выражения:

ptrl === ptr2 ptrl == ptr3 ptr2 == ptr3

Однако результатом всех трех сравнений будет значение "ложь", так как операции "==" и "!=" над указателями у гг использу­ют все 32 бита указателя как unsigned long int, а не как фактический адрес памяти.

С другой стороны, операции сравнения "<", "<=", ">", ">=" при сравнении указателей у^г используют только 16 бит смещения и для

191

Page 193: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

указателей far также не гарантируется правильность выполнения этих операций. Например, вычисление выражений

ptrl > ptr2 ptrl > ptr3 ptr2 > ptr3

Приводит к неожиданному результату: значением выражений будет "истина", хотя в действительности все три указателя адресуют одну и ту же ячейку памяти.

2. Если добавить единицу к указателю У^г 1000:FFFF, то ре­зультатом будет 1000:0000, а не 2000:0000. Если вычесть единицу из указателя 1000:0000, то результатом будет 1000:FFFF, а не OFFF:OOOF. Таким образом, при увеличении или уменьшении указа­теля/аг изменяется только смещение. Следовательно, указателемУаг нельзя адресовать данные или код программы, размер которых пре­вышает 64 Кбайта.

Адрес huge. Адрес huge, так же как и адрес/аг, состоит из ад­реса сегмента и смещения и занимает 32 бита. Адрес huge в языках Си/С++ задается указателем huge:

тип huge *huge_pointer;

Указатель huge имеет два отличия от указателя Уаг. 1. Указатель huge нормализован и содержит максимально до­

пустимое значение адреса сегмента для определяемого им адреса. Так как сегмент всегда начинается на границе, кратной 16 байтам, то значение смещения для нормализованного указателя будет в пре­делах от О до F. Например, нормализованной формой указателя 35D2:1253 (определяемый адрес 36F73) будет 36F7:0003. Операции сравнения с указателями huge оперируют со всеми 32 битами и дают правильный результат.

2. Для указателей huge нет ограничений на изменение значения указателя. Если при изменении указателя huge происходит переход через границу 16 байт, то изменяется адрес сегмента.

Например, увеличение на единицу указателя 25B0:000F дает 25В 1:0000 и, наоборот, уменьшение на единицу указателя 2531:0000 дает 25B0:000F. Эта особенность указателя huge позволяет адресо­вать данные, размер которых превышает 64 Кбайта (занимают более одного сегмента). В языках Си/С++ указатели huge применяют для адресации массивов размером более 64 Кбайт.

192

Page 194: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

11.2.Стандартные модели памяти для шестнадцатибитной среды DOS

Системы программирования Си/С++ для 16-битной среды DOS предоставляют пять стандартных моделей памяти: • крошечную (tiny); • малую (small); • среднюю (medium); • компактную (compact); • большую (large); • сверхбольшую (huge).

Метод стандартных моделей памяти является наиболее про­стым способом управления доступом к коду и данным в основной памяти. В этом случае управление памятью осуществляется через режимы (опции) компилятора.

Крошечная модель памяти. Данные, стек, динамическая па­мять и код программы располагаются в одном и том же сегменте по умолчанию и занимают суммарно не более 64 Кбайт памяти. Для ад­ресации кода, данных, стека и динамической памяти используются только адреса near , что убыстряет выполнение программы. Исполь­зуется для построения .сот файлов.

Малая модель памяти. Используется по умолчанию для большинства обычных программ на языках Си/С++. Программа с малой моделью памяти занимает только два сегмента по умолчанию: до 64 Кбайт для кода программы и до 64 Кбайт для данных, стека и динамической памяти программы. Для адресации кода, данных, сте­ка и динамической памяти используются только адреса near, что убыстряет выполнение программы.

Средняя модель памяти. Используется в программах с боль­шим объемом кода программы (более 64 Кбайт) и небольшим объе­мом данных, стека и динамической памяти (не более 64 Кбайт). Средняя модель памяти обеспечивает один сегмент для данных, сте­ка и динамической памяти программы и отдельный сегмент для ка­ждого исходного модуля (файла) программы. Это значит, что про­грамма может занимать до 1 Мбайта л^я кода и до 64 Кбайт для данных, стека и динамической памяти. Поэтому в программах со средней моделью памяти для адресации кода используются адреса far, а для адресации данных - адреса near.

193

Page 195: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Компактная модель памяти. Используется в программах с большим объемом данных и стека программы (более 64 Кбайт до 1 Мбайта) и небольшим объемом кода (не более 64 Кбайт). Компакт­ная модель памяти обеспечивает один сегмент для кода программы и несколько сегментов для данных и стека программы. Поэтому в программах с компактной моделью памяти для адресации кода ис­пользуются адреса near, а для адресации данных - адреса far.

Большая модель памяти. Используется в программах с боль­шим объемом кода, данных и стека программы. Обеспечивает не­сколько сегментов для кода, данных и стека программы. Это гаран­тирует до 1 Мбайта суммарной памяти. При этом отдельный элемент данных не может превышать 64 Кбайта. Используются только адре-са far.

Сверхбольшая модель памяти. Модель аналогична большой модели памяти за исключением того, что в сверхбольшой модели памяти снято ограничение на размер отдельного элемента данных. Для адресации кода адреса far, а для адресации данных - адреса huge.

11.3. Изменение размера указателей в стандартных моделях памяти для шестнадцатибитной среды DOS

Одним из недостатков концепции стандартных моделей памя­ти является то, что при изменении модели памяти меняются размеры адресов данных и кода. Однако можно подавить задаваемый по умолчанию способ адресации для конкретной модели, используя служебные слова near, far, huge.

Данные можно определять с ключевыми словами near, far, huge. При этом модифицируется либо размещение данных, либо размер указателей на данные.

Функции можно объявлять и определять только с ключевыми словами near и far (ключевое слово huge нельзя применять к функ­циям). Если ключевое слово near или far предшествует имени функ­ции, оно определяет, будет ли функция размещаться как near (в сег­менте кода по умолчанию) или как far (за пределами кода по умол­чанию).

Если ключевое слово near или far предшествует указателю на функцию, то оно определяет, будет ли для вызова функции исполь­зоваться адрес near (16 бит) или адрес far (32 бита).

Для определения массивов размером более 64 Кбайт следует использовать ключевое слово huge:

194

Page 196: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdio.h>

// Массив huge из 70000 байтов char hugre h_arr[ 10000 ] ;

Использование операции sizeof для массивов huge имеет осо­бенности.

printf( "\п Размер массива h_arr: %ld " , sizeof ( h_arr ) ) ;

Напечатается:

Размер массива h_arr: 44 64

(неверный ответ, так как ^/z^o/'возвращает unsigned int в диапазоне 0...65535, а у нас 70000).

Правильный вариант:

printf( "\п Размер массива h_arr: %ld ", (unsigned, long inb) sizeof ( h_arr ) ) ;

11.4. Макроопределения для работы с указателями в шестнадцатиразрядной среде DOS

Заголовочный файл DOS.H определяет три макроса, облег­чающих работу с указателями: • FP_OFF(fp) - возвращает смещение указателя^ ; • FP_SEG(fp) - возвращает сегмент указателя^ ; • MK_SEG( S, о ) ~ возвращает длинный указатель, составленный из

сегмента s и смещения о , переданных в качестве аргументов.

В качестве а р г у м е н т о в ^ в приведенных выше макросах мож­но использовать не только указатели, но и адреса переменных.

/ / Применение макросов FP__OFF и FP_SEG ^include <stdlo,h> ^include <dos.h>

int mam ( -void. ) {

int 1;

print f ( "Адрес локальной переменной: %p \ л " , &i ) ; printf( "Адрес локального значения: %04X:%04X \n"^

FP_SEG( &i ; , FP_OFF( &1 ) ) ;

195

Page 197: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

return О;

11.5. Работа с памятью для среды WINDOWS

Приложения для шестнадцатибитной среды Windows (EXE) и Windows (DLL) при компиляции вместо шести могут использовать только одну из следующих четырех стандартных моделей памяти: • малую {smaH)\ • среднюю {medium)\ • компактную {compact)', • большую {large).

Отличием стандартных моделей памяти для шестнадцатибит­ной среды Windows (DLL) от среды Windows (EXE) является то, что для данных и динамической памяти используется адресация far во всех моделях памяти.

Другой отличительной особенностью всех приложений Windows для шестнадцатибитной среды является то, что сегмент не содержит реальный адрес памяти. Вместо этого сегмент содержит индекс (селектор), указывающий на строку в таблице (таблице де­скрипторов), где этот адрес хранится. Для шестнадцатиразрядной же среды DOS процессор аппаратно суммирует значение сегментно­го регистра с указанным смещением, чтобы получить линейный ад­рес в оперативной памяти.

Работа с памятью в тридцатидвухбитной среде WINDOWS. В тридцатидвухразрядных программах всегда использу­ется сплошная (непрерывная) память. Управление этой памятью осуществляют интегрированная среда программирования и опера­ционная система.

Page 198: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

12. НОВЫЕ в о з м о ж н о с т и ЯЗЫКА C+-i-, НЕ СВЯЗАННЫЕ С ОБЪЕКТНО-

ОРИЕНТИРОВАННЫМ ПРОГРАММИРОВАНИЕМ [3,4]

Язык C++ отличается от языка Си, в первую очередь, под­держкой объектно-ориентированного программирования (ООП). Однако, по сравнению с предшествующим языком Си, в нем есть еще ряд очень полезных нововведений, которые мы и рассмотрим в данном разделе.

Комментарии. Как уже указывалось ранее, в языке C++ мож­но использовать два вида комментариев: обычные, оформленные по правилам языка Си, и однострочные, начинающиеся с символов // и продолжающиеся до конца строки. Многочисленные примеры их применения были рассмотрены выше.

Размещение определений данных внутри блока. Напоминаем, что в языке Си все определения локальных данных внутри блока помещаются перед первым исполняемым оператором. В языке же C++ можно (и часто это оказывается более удобным) определять данные в любой точке блока перед их использованием:

/' ми

Файл Р36. программ

блока V

СРР на

(расширение C+-h) .

.СРР принято Ра змещение

для файлов определении

с данных

текста-внутри

^include <stdio.h>

int mam ( void ) // Возвращает 0 при успехе {

// В языке C-h-h "модно" таким образом определять и // присваивать начальное значение управляющей // переменной цикла for fori xnt counterl = 0; counterl < 2; counterl++ ) // Переменная counterl "видна"^ начиная с этой строки и // до конца та±п, а не только внутри блока for. Ей // присваивается значение О перед входом в цикл {

// Автоматической переменной 1 присваивается значение // О при каждом проходе тела цикла, ±пЬ i =- О; // а внутренняя статическая переменная j

197

Page 199: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / инициализируется нулем static ±nt

J = О/ for( Int counter2 = 0; counter2 < 5; counter2++ )

pr±ntf( "\n ± = %d j = %d", i-h+r J++ ) ; } // counter2 "существует" до предыдущей фигурной скобки char quit_message[ J = "\n До свидания! \n"; printf ( "%s", quit_message ) ;

ret-am 0; }

В качестве упражнения рекомендуем определить, что напеча­тает данная программа, и проверить результаты Вашего анализа с помощью ЭВМ.

12.1. Прототипы функций. Аргументы по умолчанию

в языке Си наличие прототипов функций необязательно. Такая "снисходительность" часто порождает массу трудно обнаруживае­мых ошибок, поскольку компилятор не может проверить, соответст­вуют ли при вызове функций типы передаваемых аргументов и тип возвращаемого значения определению данной функции. Язык Сн-+ более строг: он требует, чтобы в файле, где происходит обращение к функции, причем обязательно до обращения к функции, присутство­вало либо определение этой функции, либо ее объявление с указани­ем типов передаваемых аргументов и возвращаемого значения, или, по терминологии языков Си/С+н-, прототип. В последнем случае оп­ределение функции может находиться в другом файле. Обычно про­тотипы функций помещают в заголовочный файл, который включа­ется в компилируемый файл директивой ^include.

В языке C++ в прототипах функций моэюно задавать значе­ния аргументов по умолчанию. Предположим, что написана функция DrawCircle, которая рисует на экране окружность заданного радиуса с центром в данной точке, и задан ее прототип:

void DrawCircle( ±nt х=100, Int у=100, Int radius=100 ) ;

Тогда вызовы этой функции будут проинтерпретированы, в за­висимости от количества передаваемых аргументов, следующим образом:

/ / Рисуется окружность с центром в точке (100, 100) и // радиусом 100 DrawCircle ( ) ;

198

Page 200: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / Рисуется окружность с центром в точке (200, 100) и // радиусом 100 DrawCircle ( 200 ); // Рисуется окружность с центром в точке (200, 300) и // радиусом 100 DrawCircle( 200, 300 ); // Рисуется окружность с центром в точке (200, 300) и // радиусом 4 00 DrawCircle( 200, 300, 400 ); // Ошибка: аргументы можно опускать только справа DrawCircle ( , , 400 );

Значения аргументов по умолчанию можно задавать не для всех аргументов, но начинать надо обязательно "справа":

/ / ОшиОочный прототип void DrawCircle( Int х, int у=100, Int гad ); // Ниже даны правильные варианты void DrawCircle( int к, int у=100, int radius=100 ); void DrawCircle( int к, int y, int radius^lOO );

12.2. Доступ к глобальным переменным, скрытым локальными переменными с тем же именем

Оператор разрешения области видимости "::" позволяет вос­пользоваться глобальной переменной в случае, если она скрыта ло­кальной переменной с тем же именем:

^include <stdio.h>

int i = 2;

int mam ( void ) {

float i = 5.3f; {

cJiax- *i = "Hello!"; printf( "i-строка = %s i-целое = %d \n", i, ::i );

}

zr&tuxm 0; }

В результате выполнения программы получим:

1-строка = Hello! i-целое = 2

199

Page 201: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

12.3. Модификаторы const и volatile

Модификатор const, как и в языке Си, запрещает изменение значений данных. Разумеется, константа должна быть инициализи­рована при описании, ведь в дальнейшем ей ничего нельзя присваи­вать. Кроме того, в языке C+-I- данные, определенные как const, ста­новятся недоступными в других файлах программного проекта, по­добно статическим переменным:

Файл Р37,СРР Модификатор const делает данное недоступным в других фай­

лах программного проекта. Состав проекта: Р37.СРР

CONST,СРР */

^include <stdio,h>

ехЬезпл floatt PI;

±nt main ( void ) // Возвращает 0 при успехе {

printfi "\n PI=%f'\ PI ) ;

return 0; }

Файл CONST. CPP. Используется в программном проекте P3 7.PRJ

const float PI = 3.14159;

Раздельная компиляция файлов из приведенного примера пройдет успешно, но компоновщик сообщит, что в файле Р37.СРР имеется не разрешенная внешняя ссылка.

В большинстве случаев компилятор языка C++ трактует описанное как const данное, не локальное ни в одном блоке (областью действия его является файл), точно так же, как и макроопределение, созданное директивой препроцессора Udefine, т.е. просто подставляет в соответствующих местах величину, которой данное инициализировано. Однако const обладает тем преимуществом перед Udefine, что обеспечивает контроль типов, поэтому его использование может уберечь от многих ошибок.

Модификатор volatile, напротив, сообщает компилятору, что значение данного может быть изменено каким-либо фоновым про­цессом - например, при обработке прерывания. С точки зрения ком-

200

Page 202: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

пилятора это означает, что, вычисляя значение выражения, в кото­рое вход;ит такое данное, он должен брать его значение только из памяти (а не использовать копию, находящуюся в регистре, что до­пустимо в других случаях).

12.4. Ссылки

в большинстве языков программирования параметры переда­ются в подпрограмму (функцию) либо по ссылке, либо по значению. В первом случае подпрограмма (функция) работает непосредственно с аргументом, переданным ей, а во втором случае - с копией аргу­мента. Различие здесь очевидно: аргумент, переданный по ссылке, подпрограмма (функция) может модифицировать, а переданный по значению - нет.

Как уже отмечалось выше, в языке Си аргументы передаются в функцию только по значению и общепринятый способ обеспечить функции непосредственный доступ к какому-либо данному из вы­звавшей программы состоит в том, что вместо самого данного в ка­честве аргумента передается его адрес. При работе на языке C++ нет необходимости прибегать к таким ухищрениям - в языке C++ реали­зованы оба способа передачи параметров. Многочисленные приме­ры, иллюстрирующие сказанное рассмотрены выше.

Ссылки в языке C++ можно использовать не только для пере­дачи параметров в функции, но и для создания псевдонимов данных:

/ / Ссылка хг становится псевдонимом // к // Все равно, что к = 2; // Все равно, что х++;

int int

xr = 2; ХГ+ + ;

X ^ 1; &xr = X

Однако, int X = 1; // Так как типы х и хг не совпадают, то компилятор создает // переменную типа char, для которой хг будет псевдонимом, // и присваивает ей (char)х char &ХГ = х; хг = 2; // Значение х не изменяется

201

Page 203: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

12.5. Подставляемые функции

в языке Си для уменьшения расходов на вызовы небольших, часто вызываемых функций, принято использовать макроопределе­ния с параметрами. Однако их применение сильно запутывает про­грамму и служит неиссякаемым источником трудноуловимых оши­бок.

Что же предлагает взамен язык C++? Достаточно описать функцию как inline и компилятор, если это возможно, будет под­ставлять в соответствующих местах тело функции, вместо того, что­бы осуществлять ее вызов. Конечно же, определение подставляемой функции должно находиться перед ее первым вызовом:

inline int InlineFunctionCube( int x ) {

return x*x*x/ }

int b = InlineFunctionCube( a ) ; int с = InlineFunctionCube( a++ ) /

Вот теперь можно повысить эффективность программы, поль­зуясь при этом всеми преимуществами контроля типов и не опасаясь побочных эффектов. Невозможна подстановка функций, содержа­щих операторы case, for, while, do-while, goto. Если для данной функции, определенной как inline, компилятор не может осущест­вить подстановку, то он трактует такую функцию как статическую, выдавая, как правило, соответствующее предупреждение.

12.6. Операции динамического распределения памяти

Так как занятие и освобождение блоков памяти является очень распространенной операцией, в языке C++ введены два "интеллек­туальных" оператора new и delete, освобождающих программиста от необходимости явно использовать библиотечные функции malloc, calloc и free. Примеры использования этих операторов приведены выше. Остается добавить, что в программе, использующей new и delete, не запрещается применять также функции библиотеки языка Си malloc, calloc, free и им подобные.

202

Page 204: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

12.7. Перегрузка функций

Предположим, что по ходу программы часто необходимо печа­тать значения типа ш/, double и char *. Почему бы не создать для этой цели специальные функции?

/ / в языке Си для вывода значений разного типа каждой из // функций придется дать особое имя void. print_int ( ±xit 1 )

printf( "%d", i ; / z-etuxn/

voxd print_double ( dovible x )

printf( "%lg"r X ) ; ) ; x - e tum/

•sroxdL print_str±ng ( chsir "^s )

printf( "%s", s ) ; ) ; x-etixm/

±nt j =5/

print_lnt ( J ) ; print_double( 3,14159 ) ; print_string( "Hello" ) ;

В стандартном языке Си потребовалось дать этим трем функ­циям различные имена, а вот в языке C++ можно написать "умную" функцию print, существующую как бы в трех ипостасях:

i^include <stdio. h>

void, print ( Int i )

pr±ntf( "%d", i ) ; return;

void print ( double x )

printf( "%lg", X ) ; return;

void print ( сЪа.г *s )

printf( "%s", s ) ; retuim;

int mam ( void )

203

Page 205: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt j = 5;

print( J ); print( 3.14159 ) ; print( "Hello" ) ;

iretuxTi 0; }

Компилятор сам выбирает, какую из трех перегруженных функций с именем print вызвать в каждом случае. Критерием выбора служат количество и типы аргументов, с которыми функция вызыва­ется, причем, если не удается найти точного совпадения, компиля­тор выбирает ту функцию, при вызове которой "наиболее легко" вы­полнить для аргументов преобразование типа.

Обратите внимание на два существенных обстоятельства. • Перегруженные функции не могут различаться только по типу

возвращаемого значения:

void. f( ±nt^ int ); izit f( int^ int ); // Ошибка!

• Перегрузка функций не должна приводить к конфликту с аргу­ментами, заданными по умолчанию:

void f ( int = о ) ; void f( void ) ;

f ( ) ; // Какую функцию вызвать?

Компилятор языка C++ позволяет давать различным функциям одинаковые имена. Поэтому, помещая имена функций в объектный файл - результат компиляции, он должен их каким-то образом мо­дифицировать, чтобы сделать уникальными. Модифицированные компилятором имена содержат информацию о количестве и типе па­раметров, так как именно по этому признаку перегруженные функ­ции различаются между собой. Такая модификация получила назва­ние "декорирование имен".

В некоторых ситуациях, например, при необходимости ском­поновать программу на языке C++ с объектными файлами или биб­лиотеками, созданными "обычным" Си-компилятором, декорирова­ние имен нежелательно. Чтобы сообщить компилятору языка C++, что имена тех или иных функций не должны декорироваться, их следует объявить как extern "С":

/ / Отдельная функция

204

Page 206: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

exteim "С" ±nt fund ( ±nt ) ; extern "C" // Несколько функций {

void. func2 ( ±nt ) ; ±nt funcS ( void. ) ; double fun с 4( double ) ;

}

Модификатор extern "C" можно использовать не только при объявлении, но и при описании функций. Естественно, что функции с модификатором extern "С" не могут быть перегруженными.

12.8. Шаблоны функций

При написании программ на языке C++ часто приходится соз­давать множество почти одинаковых функций для обработки дан­ных разных типов. Используя служебное слово template (шаблон), можно задать компилятору образец, по которому он сам сгенерирует код, необходимый для конкретных типов:

1 ^ Файл Р38.СРР, ""/

Шаблоны функций

^include <stdio.h> iinclude <string.h>

// Замена местами переменных "а <~> Ь". Компилятор создаст // подходящую функцию, когда "узнает", какой тип аргументов // Т подходит в конкретном случае template < class Т > void swap( Т &а, Т &Ь ) (

Т с; // Для обмена

с = Ь; b = а; а = с/

return/ }

int mam ( void ) // Возвращает О при успехе {

Int i = О, J = 1; double X = 0.0, у = 1.0/ char *sl = "Строка!", *s2 = "Строка2" /

print f ( "\n Перед обменом: \n i = %d j = %d \n x=%lg " "y==%lg \n sl=%s s2=%s", 1, J, X, y, si, s2 ) /

swap ( i , J ) / swap ( x, у ) / swap ( si, s2 ) / printf ( "\n После обмена: \n i = %d j = %d \n x=%lg "

205

Page 207: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

"у=%1д \п sl = %s s2^%s", i , j , x , y , si, s2 ) ;

rebvim 0; }

Аргументы, помещаемые в угловые скобки после служебного слова template, называют параметрами настройки шаблона. Пара­метры настройки шаблонов функций обязательно должны быть именами типов.

12.9. Перегрузка операций

Если в языке C++ можно самому определять новые типы дан­ных, например, структуры, то почему бы не заставить привычные операторы выполнять те же действия над определенными нами ти­пами, которые мы хотим? И такая возможность есть.

Пусть @ есть некоторый оператор языка C++, кроме следую­щих операторов:

, . * :: ?: sizeof

Тогда достаточно определить функцию с именем operator@ с требуемым числом и типами аргументов так, чтобы эта функция вы­полняла необходимые действия:

Файл Р39.СРР. Перегрузка операторов

^include <stdlo.h> ^include <string.h>

// Максимальная длина строки +1 const ±nt MAX_STR_LEN = 80;

stiract STRING // Структурный тип для строки {

chsr s[ MAX_STR_LEN ]; // Строка

±nt str_len; // Текущая длина строки } ;

// Переопределение ("перегрузка") оператора сложения для // строк - выполняет сцепление (конкатенацию) строк STRING орега,Ьог+ ( // Возвращает конкатенацию строк

STRING &sl, // Первый операнд STRING &s2) // Второй операнд

{

STRING TmpStr; // Для временного хранения

206

Page 208: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Длина строки результата равна сумме длин складываемых // строк. Позаботимся также о том, чтобы не выйти за // границу массива-суммы ±f( ( TmpStr. str_len = si . str_len + s2. str__len ) >=

MAX_STR_LEN ) {

TmpStr, s[ 0 ] = '\xO'/ TmpStr. Str__len = 0; re turn TmpStr;

}

// Выполним конкатенацию (сложение) строк strcpy( TmpStr.sг sl.s ) ; strcat ( TmpStr.s, s2.s ) ;

rebvLrn TmpStr; }

int main ( void ) // Возвращает 0 при успехе {

STRING strl, str2, str3;

strcpy ( strl.Sr "Перегрузка операторов - " ) ; strl.str_len = strlen ( strl.s ) ; strcpy( str2.Sr "это очень здорово!" ) ; str2. str__len = strlen ( str2.s ) ; printf( "\n Первая строка: длинa=%d^ coдepжимoe=%s",

strl . str__len ^ strl.s ) ; printf( "\n Вторая строка: длинa=%d, coдepжимoe=%s",

str2. str_len,^ str2.s ) ; str3 = strl + str2; print f( "\n Конкатенация строк: длина = %d,. содержимое^%s ",

str3. str__len, strJ.s ) ;

rebvLrii. 0;

Page 209: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

13. ТЕХНОЛОГИЯ СОЗДАНИЯ ПРОГРАММ [5]

К настоящему моменту рассмотрен весь спектр средств языка С+-ь, кроме технологии объектно-ориентированного программиро­вания (ООП) и стандартной библиотеки языка C++. Рассмотрим те­перь, какими же принципами нужно руководствоваться, чтобы соз­дать красивую, понятную и надежную программу.

13.1. Кодирование и документирование программы

С течением времени в процессе работы каждый программист вырабатывает собственные правила и стиль программирования. При этом полезно учиться не только на собственном опыте, но и разумно следовать приведенным ниже рекомендациям, основанным на дос­тижениях ведущих программистов, которые, де-факто, стали не­гласным стандартом программирования. Это поможет избежать многих распространенных ошибок и неоправданно больших затрат времени на проектирование программных продуктов. Вместе с тем отметим, что, конечно же, что на все случаи жизни советы дать не­возможно - ведь не зря программирование, особенно на заре его развития, считалось искусством.

Главная цель, к которой нуэюно стремиться, - получить легко читаемую программу возможно более простой структуры [5]. В конечном итоге, все технологии программирования направлены на достижение именно этой цели, поскольку только таким путем можно добиться надежности и простоты модификации программы. В соот­ветствии со сказанным, предпочтение при программировании следу­ет отдавать не наиболее компактному и даже не наиболее эффектив­ному способу программирования, а такому способу, который легче для понимания. Особенно важно это в случае, когда программу пи­шут одни программисты, а сопровождают другие, что является ши­роко распространенной практикой [5].

Первый шаг в написании программь/ - запись ее в так назы­ваемой текстуальной форме, возможно, с применением блок-схем. Текстуальная форма должна показать, что именно и как программа должна делать. Если же не можете записать алгоритм решения зада­чи в текстуальной форме, то велика вероятность того, что алгоритм плохо продуман. Текстуальная запись алгоритма полезна по не­скольким причинам — она позволяет детально продумать алгоритм, обнаружить на самой ранней стадии некоторые ошибки, разбить программу на логическую последовательность функционально за-

208

Page 210: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

конченных фрагментов, а также обеспечить комментарии к про­грамме.

Каждый функционально законченный фрагмент алгоритма в соответствии с технологией модульного программирования следует оформить в виде функции. Каждая функция должна решать только одну задачу (не надо объединять два коротких независимых фраг­мента в одну функцию). Предельные параметры функции (количест­во строк исходного текста и число параметров) определяются рас­смотренным ранее правилом "семь плюс-минус два".

Если некоторые действия встречаются в программе хотя бы дважды, их также нужно оформить в виде функции. Однотипные действия оформляются в виде перегруженных функций или функций с параметрами. Короткие, простые функции следует оформлять как подставляемые функции.

Необходимо тщательно выбирать имена объектов (пере­менных, функций и т.п.). Рационально выбранные имена могут сде­лать программу в некоторой степени самодокументированной. Не­удачные имена, наоборот, служат источником проблем. Не увлекай­тесь сокращениями - они ухудшают читаемость текста. Общая тен­денция состоит в том, что чем больше область видимости объекта, тем более длинным именем его надо снабжать. Перед таким именем часто ставится префикс типа (одна или несколько букв, по которым можно определить тип объекта). Для управляющих переменных ко­ротких циклов, напротив, лучше использовать однобуквенными именами типа /, у, или к. Имена макросов предпочтительнее записы­вать прописными буквами, чтобы отличать их от других объектов программ. Не рекомендуется использовать имена, начинающиеся с одного или двух символов подчеркивания, имена типов, оканчи­вающиеся на "_/" и т.п.

Переменные желательно инициализировать при их опреде­лении^ а определять как можно ближе к месту их непосредственного использования. Но нет правил без исключений. Поэтому, с другой стороны, все определения локальных переменных блока лучше рас­полагать в начале блока, чтобы их легко можно было найти.

Локальные переменные предпочтительнее глобальных. Если глабальная переменная все же необходима, то лучше определить ее статической. Это ограничит область действия такой переменной од­ним исходным файлом.

Всю необходимую функции информацию нужно стремиться передавать через список параметров, а не через глобальные пере­менные, изменение которых трудно отследить.

Входные параметры функции, которые не дол:и€ны в ней изменяться, следует передавать по ссылке с модификатором const, а не по значению. Кроме улучшения читаемости программы и

209

Page 211: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

уменьшения случайных ошибок, этот способ позволяет экономить память при передаче сложных объектов. Исключение составляют параметры, размер которых меньше размера указателя - их лучше передавать по значению.

Нельзя возвращать из функции ссылку на локальную пере­менную, потому что она автоматически уничтожается при выходе из функции, которая является ее областью действия. Не рекомендуется также возвращать ссылку на объект, созданный внутри функции с помощью функции malloc{ ) или операции new, так как это приводит к трудно контролируемым утечкам динамической памяти.

Следует избегать использования в программе чисел в явном виде. Константы должны иметь осмысленные имена, заданные через const или епит (последнее предпочтительнее, так как память под перечисление не выделяется). Символическое имя делает программу более понятной. Кроме того, при необходимости изменить значение константы это можно сделать всего лишь в одном месте программы.

Для записи каждого фрагмента программы необходимо ис­пользовать наиболее подходящие языковые средства. Любой цикл можно, в принципе, реализовать с помощью операторов if и goto, но это было бы нелепо, поскольку с помощью операторов цикла те же действия легче читаются, а компилятор генерирует более эффектив­ный код. Ветвление на три или более направлений предпочтитель­нее программировать с использованием оператора switch, а не с по­мощью гнезда операторов if.

Следует избегать лишних проверок условий. Например, вме­сто операторов

±f( strstr( a,b ) > О ) { . . . } else ±f( strstr( a,b ) < 0 ) { . . . ; else ( . . . }

лучше записать

±nt ls_equal = strstr ( a,b ) ; ±f( is_equal > 0 ) { . . . } else ±f( is_equal < 0 ) { . . . } else { . . . }

Бессмысленно использовать проверку на неравенство нулю (или, что еще хуже, на равенство true или false):

bool is_busy;

±f( is_busy == true ) { } // Лучше ±f( is_busy ) { } ±f( is_busy == false ) { } // Лучше ±f( !is_busy ) { } while ( a == 0 ) { } // Лучше while ( !a ) { }

210

Page 212: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Если одна из ветвей условного оператора короче, чем другая, то более короткую ветвь условного оператора лучше поместить сверху, иначе вся управляющая структура может не поместиться на экране, что затруднит отладку.

В некоторых случаях тернарная операция лучше условного оператора:

if( Z ) 2.-J; else i=k; // i = z ? j : k;

При использовании циклов надо стремиться объединять инициализацию^ проверку условия повторения и приращение в одном месте. Сказанное означает, что лучше использовать цикл/or.

Необходимо проверять коды возврата для выявления оши­бок и предусматривать печать сообщений в тех точках про­граммы, куда управление при нормальной работе программы пе­редаваться не доллсно. Например, в переключателе должен исполь­зоваться вариант default с обработкой ситуации по умолчанию, осо­бенно, если в переключателе перечислены все возможные варианты.

Сообщение об ошибке дол:н€но быть информативным и под­сказывать пользователю, как ее исправить. Например, при вводе не­верного значения в сообщении должен быть указан допустимый диапазон.

Операции выделения и освобо^исдения динамической памяти следует помещать в одну и ту Jtce функцию. Утечки памяти, когда ее выделили, а освободить забыли, создают большие проблемы в программах, продолжительность работы которых велика или не ог­раничена (на серверах баз данных, в операционных системах и т.п. программах).

После написания программу надо тщательно отредакти­ровать — убрать ненужные фрагменты, сгруппировать описания, оп­тимизировать проверки условий и циклы, проверить, оптимальное ли разбиение на функции и т.п. Подходить к написанию программы надо так, чтобы ее в любой момент можно было передать другому программисту.

Комментарии имеют очень важное значение, поскольку программист чаще выступает как читатель, а не как писатель. Даже в том случае, если программу сопровождает автор программы, раз­бираться через год в плохо документированном тексте удовольствие сомнительное.

По использованию комментариев и использованию форма­тирования текста мо:>§€но дать следующие рекомендации (они иллюстрируются многочисленными примерами исходных текстов программ, приведенными в этой книге).

• Программа, если она используется, живет не один год, по-

211

Page 213: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

требность в каких-то ее новых свойствах появляется сразу же после ввода программы в эксплуатацию и сопровождение программы за­нимает гораздо больше времени, чем ее разработка. По этой причи­не основная часть документации долэюна находиться в тексте про­граммы. Хорошие комментарии написать почти так же сложно, как и хорошую программу.

а Комментарии долэюны представлять собой правильные предложения без сокращений и со знаками препинания и не должны подтверждать очевидное. В данной книге иногда есть отступы от этого в учебных целях.

а Если комментарий к фрагменту программы занимает не­сколько строк, его лучиге разместить до фрагмента, чем справа от него, и выровнять по вертикали. Абзацный отступ комментария долэюен соответствовать отступу комментируемого блока.

• Для разделения функций и других логически законченных фрагментов пользуйтесь пустыми строками и комментариями вида:

//* ii^ic-kic*i(i<irk*i(iciri(:*iricici(*i(**i^ic-kic*icif9c*i(-k^ic**ic-kiricici<*-k-^iri(i(icici^*ic*if*

• Вложенные блоки долэюны иметь отступ в 3 - 4 символа (лучше для создания отступов использовать табулятор), причем бло­ки одного уровня вложенности должны быть выровнены по верти­кали. Желательно, чтобы закрывающая фигурная скобка находилась строго под открывающей скобкой. Форматируйте текст по столбцам везде, где это возможно.

• Помечайте комментарием конец длинного составного опе­ратора.

• Не следует размещать в одной строке много операторов. и В комментариях после знаков препинания используйте

пробелы. Настоятельно рекомендуем внимательно рассмотреть приве­

денные в книге программные тексты, обратив внимание на оформ­ление комментариев и форматирование текста. Это будет способст­вовать формированию хорошего стиля программирования.

13.2. Проектирование и тестирование программы [5]

Вначале рассмотрим, как не следует проектировать и тестиро­вать программы. Начинающие программисты, особенно студенты, часто, получив задание, сразу садятся за компьютер и начинают ко­дировать те части алгоритма, которые им удается придумать сходу. Объектам программы даются первые попавшиеся имена и т.п. Когда компьютер "зависает" или получаются "загадочные" результаты, по-

212

Page 214: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

еле некоторого перерыва написанные фрагменты стираются и все повторяется заново.

В процессе работы неоднократно изменяются структуры дан­ных, разбиение на модули (функции) делается только тогда, когда просматривать текст программы становится неудобно и утомитель­но. При этом комментарии к программе не пишутся, а ее текст не форматируется. Из-за многочисленных неудач периодически выска­зываются сомнения в правильности работы компилятора, компьюте­ра и операционной системы.

Когда программа впервые доходит до стадии выполнения (а это случается не скоро), в нее вводятся произвольные исходные данные, после чего экран компьютера и файл результатов становят­ся объектами пристального удивленного изучения. "Работает" такая программа обычно только на одном наборе исходных данных, а вне­сение в них даже небольших изменений приводит автора к потере веры в себя и портит его настроение.

Задача настоящего программиста состоит в том, чтобы нау­читься подходить к программированию профессионально. Профес­сионал отличается тем, что может достаточно точно оценить, сколь­ко времени займет разработка программы, которая будет работать в точном соответствии с поставленной задачей. Для достижения по­добных результатов требуется склонность к программированию, опыт, а также знание основных принципов, выработанных програм­мистами в течение более чем полувека развития этой дисциплины. Даже к написанию самых простых программ нужно подходить по­следовательно, проходя в соответствии со структурным подходом ряд последовательных этапов.

Структурный подход к программированию охватывает все стадии разработки проекта — спецификацию, проектирование, соб­ственно программирование (кодирование) и тестирование. При этом стремятся к уменьшению числа возможных ошибок, их более ран­нему обнаружению и упрощению процесса исправления ошибок. В рамках структурного подхода используются нисходящая разработка, структурное и модульное программирование и нисходящее тестиро­вание.

Рассмотрим этапы создания программ, рассчитанные на дос­таточно большие программные проекты, разрабатываемые коллек­тивом программистов [5]. Для неболыиих программ каждый из та­ких этапов упрощается, но содержание и последовательность эта­пов не изменяются,

13.2.1. Этап 1: постановка задачи

Создание любой программы начинается с постановки задачи,

213

Page 215: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Изначально задача ставится в терминах некоторой предметной об­ласти и необходимо перевести ее в термины, более близкие к про­граммированию. Поскольку программист редко досконально разби­рается в предметной области, а заказчик - в программировании, то постановка задачи может стать весьма непростым итерационным процессом. Отметим, что здесь весьма полезным является использо­вание объектно-ориентированного подхода, средства реализации которого в языке C++ будут рассмотрены во второй части книги.

Постановка задачи заканчивается созданием технического за­дания, а затем и внешней спецификации программы, которая вклю­чает в себя.

а Описание исходных данных и результатов (виды, представление, точность, ограничения и т.п.).

• Описание задачи, реализуемой программой, а Способ обращения к программе. • Описание возможных особых и аварийных ситуаций и оши­

бок пользователя. На этом этапе программа рассматривается как "черный ящик",

для которого определена выполняемая им функция, входные и выходные данные.

13.2.2. Этап 2: разработка внутренних структур данных

Большинство алгоритмов решения задач зависит от того, ка­ким образом организованы данные. Из этого следует, что начинать проектирование программы надо не с алгоритмов, а с разработки структур входных, промежуточных и выходных данных. При этом принимаются во внимание такие факторы, как ограничения на раз­мер данных, необходимая точность, взаимосвязь данных между со­бой, требования к быстродействию программы и т.п. Структуры данных могут располагаться в статической или динамической памя­ти. В последнем случае обеспечивается более экономное использо­вание оперативной памяти.

13.2.3. Этап 3: проектирование структуры программы и взаимодействия модулей

На этом этапе применяется технология нисходящего проек­тирования, основная идея которой заключается в разбиении задачи на подзадачи меньшей сложности, которые можно разрабатывать раздельно. При этом используется метод пошаговой детализации. При этом программа сначала пишется на языке некоторой гипотети­ческой машины, которая способна понимать самые обобщенные действия, а затем каждое из таких действий описывается на более

214

Page 216: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

низком уровне абстракции и т.д. Очень важной на этом этапе явля­ется спецификация интерфейсов, т.е. способов взаимодействия подзадач.

Для каждой подзадачи создается внешняя спецификация, ана­логичная указанной для этапа 1. Здесь же решаются вопросы раз­биения программы на модули. Декомпозиция выполняется таким образом, чтобы минимизировать взаимодействие модулей. В резуль­тате может оказаться так, что одна задача реализуется с помощью нескольких модулей и, наоборот, в одном модуле может решаться несколько задач. На более низкий уровень проектирования перехо­дят только после окончания проектирования верхнего уровня. Алго­ритм записывают в обобщенной форме — например, текстуальной, в виде обобщенных блок-схем или другими способами.

На этапе проектирования следует учитывать возможность бу­дущих модификаций программы и стремиться проектировать про­грамму таким образом, чтобы вносить изменения было возможно проще. Процесс проектирования является итерационным, поскольку в программе реального размера трудно продумать все детали с пер­вого раза.

13.2.4. Этап 4: структурное программирование

Процесс программирования (кодирования) также организуется по принципу "сверху вниз": вначале кодируются модули самого верхнего уровня и составляются тестовые примеры для их отладки. При этом на месте еще не написанных модулей следующего уровня ставятся "заглушки" - временные программы. "Заглушка" в про­стейшем случае просто выдает сообщение о том, что ей передано управление, а затем возвращает его в вызывающий модуль. В других случаях "заглушка" может выдавать значения, заданные заранее или вычисленные по упрощенному алгоритму. Таким образом, сначала создается логический скелет программы, который затем обрастает "плотью" кода. Такая технология программирования получила на­звание нисходящей технологии программирования, В литературе [5] показано, что эта технология по сравнению с восходящей техно­логией программирования имеет целый ряд преимуществ, что и обу­словило ее широкое распространение.

Рекомендации по записи алгоритмов в терминологии языка C++ приведены в подразд. 13.1. Ввиду важности, напомним еще раз, что главные цели — читаемость и простота структуры программы в целом и любой из составляющих ее функций.

При программировании следует отделять интерфейс (функции, модуля, класса) от его реализации и ограничивать доступ к ненулсной информации. Небрежное, даже в мелочах, про-

215

Page 217: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

граммирование может привести к огромным затратам на поиск оши­бок на этапе отладки.

Этапы проектирования и программирования совмещены во времени: в идеале сначала проектируется и кодируется верхний уро­вень, затем следующий за ним и т.д. Такая стратегия хороша тем, что в процессе кодирования может возникнуть необходимость вре-сти изменения, отражающиеся на модулях нижнего уровня.

13.2.5. Этап 5: нисходящее тестирование

Хотя этот этап рассматривается последним, это не значит, что тестирование не должно проводиться на предыдущих этапах. Про­ектирование и программирование должны обязательно сопровож­даться написанием набора тестов — проверочных исходных дан­ных и соответствующих им наборов эталонных реакций.

Необходимо различать процессы тестирования и отладки про­граммы. Тестирование представляет собой процесс, посредством которого проверяется правильность функционирования программы и соответствие всем проектным спецификациям. Таким образом, тестирование носит позитивный характер. Отладка - процесс ис­правления ошибок в программе. При отладке, в частности, исправ­ляют ошибки, обнаруженные при тестировании. Следует заметить, что процесс обнаружения ошибок подчиняется экспоненциальному закону, т.е. большинство ошибок обнаруживается на ранних стадиях тестирования и, чем меньше в программе осталось ошибок, тем дольше придется искать каждую из них.

Для исчерпывающего тестирования программы необходимо проверить каждую из ветвей алгоритма. Общее число ветвей оп­ределяется числом комбинаций всех альтернатив на последователь­ных участках алгоритма. Это конечное число, но оно может быть очень большим. Поэтому при тестировании программа разбивается на фрагменты, после исчерпывающего тестирования которых они рассматриваются как элементарные узлы более длинных ветвей. Тесты, в числе прочих возможностей, должны содержать проверку граничных условий (например, переход по условию л:>10 должен проверяться для значений 9, 10 и 11). Отдельно проверяется реакция программы на оигибочные исходные данные.

Идея нисходящего тестирования предполагает, что к тестиро­ванию программы приступают еще до того, как завершено ее проек­тирование и кодирование. Это позволяет раньше опробовать основ­ные межмодульные интерфейсы, а также убедиться в том, что про­грамма в основном удовлетворяет требованиям пользователя. Толь­ко после того, как логическое ядро испытано настолько, что появля­ется уверенность в правильности реализации основных интерфей-

216

Page 218: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

сов, приступают к кодированию следующего уровня программы. Естественно, что полное тестирование программы, пока она

представлена в виде скелета, невозможно, однако добавление каж­дого следующего уровня позволяет постепенно расширять область тестирования.

Этап комплексной отладки на уровне системы при нисходя­щем проектировании занимает меньше времени, чем при восходя­щем, поскольку вероятность появления серьезных ошибок, затраги­вающих большую часть системы, гораздо ниже. Кроме того, для ка­ждого следующего, подключаемого к системе модуля уже создано его окружение и выходные данные отлаженных модулей можно ис­пользовать как входные для тестирования других. Но это, конечно, не значит, что модуль можно подключать к системе совсем "сырым" - бывает удобным провести часть тестирования автономно, по­скольку сгенерировать на входе системы все варианты, необходи­мые для тестирования отдельного модуля, трудно.

В приложении П.4 рассмотрена более подробно методика от­ладки программы применительно к возможностям двух популярных разновидностей интегрированных сред проектирования программ.

Page 219: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ЧАСТЬ 2. ПРИКЛАДНОЕ ПРОГРАММИРОВАНИЕ

В число классических задач прикладного программирования принято включать следующие задачи, составляющие золотой багаж любого программиста:

• сортировка (сортировка массивов, сортировка файлов); • транспортная задача (задача коммивояжера); • поиск в таблице; • обработка списков; • работа с очередями; • численный анализ (вычислительная математика).

Численный анализ изучается в отдельном курсе, а остальные задачи прикладного программирования рассматриваются ниже.

Перечисленные задачи прикладного программирования рас­сматриваются для овладения основами науки программирования и профессиональным стилем программирования. Умение решать эти и им подобные сложные задачи составляет основу профессиональной квалификации программиста.

Прежде, чем перейти к рассмотрению некоторого подмножест­ва классических задач прикладного программирования кратко обу-дим очень важный вопрос — структуры данных и, в частности, дина­мические структуры данных.

14. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ [5]

Любая программа предназначена для обработки данных. По этой причине алгоритмы обработки данных существенно зависят от способа организации данных и выбор структур данных должен предшествовать созданию алгоритмов. Стандартными способами организации данных в языках Си/С++ являются основные и состав­ные типы. Очень часто в программах используются массивы, струк­туры и их сочетания — структуры или массивы структур, полями ко­торых, в свою очередь, являются массивы и структуры.

Оперативную память под данные можно выделять статически или динамически, причем в обоих случаях выделяется непрерывный участок памяти. Статическое распределение памяти для данных производится при компиляции. В этом случае требуемый объем

218

Page 220: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

оперативной памяти должен быть известен до начала выполнения программы и задан в виде константы. Заметим, что это возможно не всегда и тогда используют динамическое размещение данных в опе­ративной памяти. Динамическое размещение данных в памяти про­исходит во время выполнения программы с помощью операции new (только для языка С-+"Ь) или функции malloc (языки Си/С++). При этом необходимый объем требуемой памяти должен быть известен лишь к моменту динамического распределения памяти, а не до нача­ла выполнения программы.

Как уже было сказано, если до начала работы с данными не­возможно определить, сколько памяти потребуется для их хранения,, то память выделяется по мере необходимости отдельными блоками, связанными друг с другом с помощью указателей. Такой способ ор­ганизации получил название динамических структур данных, по­скольку их размер изменяется во время выполнения программы. В качестве таких структур в программах часто используются линейные списки (см. разд. 8), бинарные деревья и очереди, частным случаем которых являются стеки. Они отличаются способами связи отдель­ных элементов друг с другом и допустимыми операциями. Отметим, что динамическая структура данных может занимать несмежные блоки оперативной памяти.

Динамические структуры данных часто применяют и для бо­лее эффективной работы с данными, размер которых известен. К такого рода случаям можно отнести решение задач сортировки и поиска элементов. При сортировке упорядочивание динамических структур не требует перестановки элементов, а сводится к измене­нию указателей на эти элементы. Это особенно эффективно, если сортируемые элементы большого размера. При решении задачи по­иска элемента в тех случаях, когда важна скорость, данные лучше всего представлять в виде бинарного дерева.

Элемент любой динамической структуры данных представляет собой структуру {struct), содержащую, по крайней мере, два поля -для хранения данных и для указателя (см. разд. 8). В общем случае полей данных и указателей может быть несколько. Поля данных мо­гут быть любого типа: стандартного (основного), составного или типа указатель.

14.1. Линейные списки

Самый простой способ связать множество элементов - сделать так, чтобы каждый элемент содержал ссылку на следующий. Такой список называется однонаправленным (односвязным). Исчерпываю­щий пример такого рода рассмотрен в разд. 8. Если добавить в каж-

219

Page 221: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

дыи элемент вторую ссылку — на предыдущий элемент, то получится двунаправленный список (двусвязный). Если же последний элемент связать указателем с первым, получится кольцевой список.

Каждый элемент списка содержит ключ, идентифицирующий этот элемент. Ключ часто бывает целым числом или строкой и явля­ется частью поля данных. В качестве ключа в процессе работы со списком могут выступать различные части поля данных (например, фамилия, возраст, стаж работы и др.). Ключи разных элементов спи­ска, в частном случае, могут совпадать.

Над списками можно выполнять различные операции. Список операций над однонаправленным линейным списком и их реализа­ция были рассмотрены в разд. 8.

14.2. Бинарные деревья

Бинарное дерево представляет собой динамическую структуру данных, состоящую из узлов (вершин), каждый из которых содер­жит, кроме данных, не более двух ссылок на различные бинарные деревья. На каждый узел имеется ровно одна ссылка. Начальный узел называется корнем дерева.

На рис. 57 приведен алгоритм построения и пример бинарного дерева (его корень обычно располагается сверху).

Узел, не имеющий поддеревьев, называется листом. Исходя­щие узлы называются предками, входящие — потомками. Высота дерева определяется количеством уровней, на которых располага­ются его узлы. Использование двоичного дерева для сортировки массива рассмотрено в разд. 15.

Если дерево организовано таким образом, что для каждого уз­ла все ключи его левого поддерева меньше ключа этого узла, а все ключи его правого поддерева - больше, то оно называется деревом поиска. В дереве поиска можно найти элемент по ключу, двигаясь от корня и переходя на левое или правое поддерево в зависимости от значения ключа в каждом узле. При этом время поиска определяется высотой дерева, которая пропорциональна двоичному логарифму количества узлов. Пример использования дерева поиска рассматри­вается в разд. 17.

Дерево является рекурсивной структурой данных, поскольку каждое поддерево также является деревом. Действия с такими структурами лучше всего описываются с помощью рекурсивных ал­горитмов. Например, обход всех вершин дерева можно выполнить с помощью рекурсивной функции. Пример такой функции имеется в разд. 15.

220

Page 222: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Корень

J1=2*i

_Z X J 8 10

2, 3, 4, 5 - вершины 6, 7, 8, 9, 10-листья

Пример двоичного дерева для size =10 Рис. 57. Алгоритм построения и пример бинарного дерева

14.3. Очереди и их частные разновидности

Универсальную очередь можно рассматривать как частный случай однонаправленного или двунаправленного линейного списка, когда определены только операции добавления или выборки эле­ментов с любого из двух концов и, может быть, просмотра элемен­тов очереди. Иные операции с очередью не определены. При выбор­ке элемент исключается из очереди. В качестве применений очереди можно назвать моделирование систем массового обслуживания, со­ставной частью которых являются очереди, диспетчеризация задач операционной системой, буферизация ввода-вывода и др.

На практике часто используются более простые, частные слу­чаи универсальной очереди - очереди типа FIFO и L1FO (стек). Оче­редь типа FIFO (First Input - First Output : первым занесен ~ первым извлечен) получается, если операция добавления элемента в очередь разрешена только для одного конца очереди, а операция выборки элемента - только для другого конца очереди.

Стек - это частный случай однонаправленного линейного спи­ска, добавление элементов в который и выборка выполняются только с одного конца, называемого вершиной стека. Говорят, что стек реализует дисциплину обслуживания LIFO (Last Input - First Output : последним занесен — первым извлечен). Стеки широко при­меняются в программировании, компиляторах, рекурсивных алго­ритмах и т.п.

221

Page 223: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

14.4. Реализация динамических структур с помощью массивов

Операции динамического выделения и освобождения памяти -дорогостоящее удовольствие. Поэтому, если максимальный размер данных можно определить до начала их использования и в процессе работы программы он не изменяется (например, при сортировке массива), то более эффективным может оказаться однократное вы­деление непрерывной области динамической памяти. Связи элемен­тов при этом реализуются не через указатели, а через вспомогатель­ные переменные или вспомогательные массивы, в которых хранятся номера (индексы) элементов массива. Такого рода приемы рассмат­риваются в разд. 15 и 17.

Проще всего реализовать таким образом стек (см. подразд. 3.9.1). Кроме массива элементов, соответствующих типу данных стека, достаточно иметь одну переменную целого типа для хранения индекса элемента массива, являющегося вершиной стека. При по­мещении элемента в стек индекс увеличивается на единицу, а при выборке - уменьшается.

Для реализации очереди требуются две переменных целого ти­па - для хранения индексов элементов массива, являющихся нача­лом и концом очереди.

Для реализации линейного списка на базе массива требуется вспомогательный массив целых чисел и еще одна переменная (мас­сив данных, вспомогательный массив и вспомогательную перемен­ную можно оформить в виде полей структуры), например:

- массив данных - вспомогательный массив - индекс первого элемента в списке

/-ЫЙ элемент вспомогательного массива содержит для каждого /-го элемента массива данных индекс следующего за ним элемента. От­рицательное число используется как признак конца списка. Тот же массив после сортировки будет иметь следующий вид:

- массив данных - вспомогательный массив ~ индекс первого элемента в списке

Аналогичным образом, для создания бинарного дерева можно использовать два вспомогательных массива с таким же размером, как и массив данных (содержат индексы вершин его левого и право­го поддерева). Отрицательное число в этих массивах означает

отсутствие соответствующего поддерева. Как и в предыдущем случае, массив данных и вспомогательные массивы можно офор-

222

10 1 0

25 2

20 3

6 4

21 5

8 6

1 7

30 -1

10 2 6

25 7

20 4

6 5

21 1

8 0

1 3

30 -1

Page 224: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

мить как поля структуры. В качестве упражнения можно предлага­ется самостоятельно и для этого случая составить иллюстрирующий пример.

Обращаем Ваше внимание на то, что при работе с такими структурами необходимо контролировать возможный выход индек­сов за границы массива (нарушение индексации).

И, наконец, важное заключительное замечание. Рассмотрен­ный способ реализации позволяет использовать преимущества ди­намических структур (например, сортировать структуры из гро­моздких элементов данных без их физического перемещения в па­мяти) и при этом не расходовать время на выделение и освобожде­ние динамической памяти для каэюдого элемента данных.

Page 225: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

15. СОРТИРОВКА

Под сортировкой понимают процесс перестановки объектов данного множества в определенном порядке. Цель сортировки - об­легчить последующий поиск элементов в отсортированном множе­стве. В этом смысле элементы сортировки присутствуют во многих задачах прикладного программирования.

Зависимость выбора алгоритмов решения задачи от структуры данных - явление довольно частое. В случае сортировки эта зависи­мость настолько сильна, что методы сортировки обычно разделяют на две категории:

• сортировка массивов; • сортировка последовательных файлов.

Эти две разновидности сортировок часто называют соответст­венно внутренней (сортировка массивов) и внешней (сортировка файлов) сортировками. Это объясняется тем, что массивы распола­гаются во "внутренней" (оперативной) памяти ЭВМ и для нее харак­терен быстрый произвольный доступ (прямой доступ). Файлы же хранятся в более медленной, но более вместительной "внешней" па­мяти, т.е. на запоминающих устройствах с механическим передви­жением (магнитных дисках и лентах). Указанное существенное раз­личие можно наглядно продемонстрировать на примере сортировки пронумерованных карточек.

1. Представление карточек в виде массива с прямым доступом (рис. 58) означает, что все карточки одновременно видны и равно­доступны.

0

2

8

6

9

4

1

5

Рис. 58. Произвольный (прямой) доступ

2. Представление карточек в виде последовательного файла (рис. 59) предполагает, что видна и доступна только верхняя кар­точка. Чтобы добраться до остальных карточек необходимо, напри­мер, перекладывать карточки в колоде по одной спереди назад.

Очевидно, что такое ограничение приведет к существенному изменению методов сортировки.

224

Page 226: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ir Рис. 59. Последовательный доступ

Терминология и обозначения. Будем считать, что нам даны элементы

Сортировка означает перестановку этих элементов в таком порядке

что при заданной функции упорядочения f справедливо отношение

f(a,,)<f(a,,)<...<f(a,J

В частном случае, функция упорядочения может не вычис­ляться по какому-либо специальному правилу, а может содержаться в каждом элементе в виде явной компоненты (поля). Ее значение на­зывается ключом элемента. Следовательно, для представления эле­мента а,, особенно хорошо подходит структура.

В соответствии со сказанным, определим структурный тип ELEMENT, который будем использовать в последующих алгоритмах сортировки, следующим образом:

/ / Структурный тип для элемента массива struct ELEMENT

Int key; // Ключ сортировки // Описание других компонентов элемента

};

Здесь "другие компоненты" - это все существенные данные об эле­менте, а поле key - ключ служит лишь для идентификации элементов при сортировке.

Таким образом, в алгоритмах сортировки ключ - единственная учитываемая компонента и при учебном рассмотрении алгоритмов нет необходимости определять остальные поля элемента массива. Следует иметь в виду, что выбор в качестве типа ключа целого типа произволен. Ясно, что точно также можно использовать и любой другой тип, на котором задано отношение всеобщего порядка (упо-

225

Page 227: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

рядоченный тип).

Метод сортировки называется устойчивым, если относитель­ный порядок элементов с одинаковыми ключами не меняется при сортировке. Устойчивость сортировки часто бывает желательной, если элементы упорядочены по каким-то вторичным ключам, т.е. по свойствам, не отраженным в первичном ключе.

15.1. Сортировка массивов

Основное требование к методам сортировки массивов - эко­номное использование памяти. В этом смысле говорят, что сорти­ровку нужно выполнять in site (на том же месте) и другие методы, использующие копирование массива, для нас не представляют инте­реса.

Удобной мерой эффективности алгоритмов сортировки "на месте" является число

с (Compare)

необходимых сравнений ключей и число М (Move)

необходимых пересылок элементов. Хотя эффективные алгоритмы сортировки требуют порядка

сравнений, где N - число элементов сортируемого массива, все же сначала обсудим несколько более простых методов сортировки, ко­торые требуют порядка

С-TV'

сравнений, по следующим трем причинам. 1. Простые методы особенно хорошо подходят для разъясне­

ния свойств большинства принципов сортировки. 2. Программы, основанные на этих методах, легки для пони­

мания и коротки (следует помнить, что программы также занимают память!).

3. Хотя сложные алгоритмы требуют меньшего числа опера­ций, но эти операции более сложны. Поэтому при достаточно ма­лых значениях Л простые методы работают также быстро, но их не следует использовать при больших N,

Методы сортировки массивов "на месте" можно разбить на три основных класса:

226

Page 228: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• сортировка выбором; • сортировка вставками; • сортировка обменом.

Рассматриваемые программы будут работать с указателем агг на массив, компоненты которого нужно отсортировать "на месте", и структурным типом ELEMENT^ определенным выше.

Указатель агг на массив можно определить так:

±пЬ / / Размер массива // Указатель на сортируемый массив ELEMENT *агг = new ELEMENT[ s } ;

В простейшем случае элементы массива агг[ О ]г arrf 1 ], ..., arr[ s-1 ]

будем располагать в порядке не убывания их ключей:

arr[k^],key < arr[k2^.key < ... < arr[k^]Леу

Сортировка выбором. Выбирается элемент с наибольшим значением ключа и меняется местами с последним. Затем то же са­мое повторяется для 5-1 первого элемента, найденный элемент с наибольшим значением ключа меняется местами с предпоследним элементом и т.д. (рис. 60).

max s-1

max s-2 s-1 и т.д.

Рис. 60. Сортировка простым выбором

Сортировка включениями. Элементы разделяются на уже готовую последовательность (упорядоченную) и неупорядоченную (рис. 61). В начале упорядоченная часть содержит только один эле­мент. Очередной элемент из начала неупорядоченной части вставля­ется на подходящее место в упорядоченную часть. При этом упоря­доченная часть удлиняется на один элемент, а неупорядоченная часть — укорачивается. Сортировка заканчивается при исчезновении неупорядоченной части.

227

Page 229: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1 1-1 — \ \

S-1 1 г i= 1,2, ..., s-1

Вставим на подходящее место или оставим на месте

Рис. 61. Сортировка простыми включениями

Сортировка обменом. Основная характеристика процесса -обмен местами двух соседних элементов (перестановка), если они расположены не так, как требует отсортированный массив (рис. 62). На приведенном рисунке изображен только один шаг (просмотр). Сортировка массива гарантируется после s-\ просмотра.

S-2 S-1 и т.д.

Рис. 62. Сортировка простым обменом

15.2. Сортировка массива простым выбором

Метод основан на следующем правиле. 1. Выбирается элемент с наибольшим значением ключа. 2. Он меняется местами с последним элементом агг[ 5-1 ]. Эти

операции затем повторяются с оставшимися первыми ^-1 элемента­ми, затем - с s-2 первыми элементами и т.д. до тех пор, пока не ос­танется только один первый элемент - наименьший. Пример сорти­ровки массива простым выбором приведен на рис. 63, в соответст­вии с которым, программу можно представить следующим образом:

/ / Просмотр неотсортированных начальных сегментов массива // (вначале - весь массив, затем - сегмент из первых slze-1 // элементов и т,д.) £о1т( L -= size-1; L >= 1; L— ; {

// Присвоить indmax и max индекс и значение элемента // массива с наибольшим значением ключа из // агг[ О ]..arrf L ] // . . . // Поменять местами агг[ indmax ] и агг[ L ] arrf indmax ] = arrf L ]; arrf L ] = max;

228

Page 230: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Начальные значения ключей элементов массива

44

44 44

44 м— Об

06

06

06

55

55 55 ^ 18

18

18

12

12

12 12

12

12

."1 18

18

42

42 42

42

42 1 Ф 42

42

42

94

67 м 06

06 1 44

44

44

44

18

18

«"1 55

55

55

55

55

06

06 1 67

67

67

67

67

67

67

94 94

94

94

94

94

94 Рис. 63. Сортировка массива простым выбором

Полный текст программы, иллюстрирующей все разработан­ные к настоящему времени методы сортировок массивов, приводит­ся ниже. На данном этапе в этой программе не следует рассматри­вать функции, осуществляющие методы сортировки, отличные от сортировки массива простым выбором. Всю остальную часть про­граммы следует внимательно изучить.

/* Файл TestSortArr.срр. Тестирование сортировки динамически

размещенных массивов с использованием различных методов. Определение методов сортировки приведено в файле

SortArr.срр. Определение Функций размещения массива в динамической па­

мяти и освобождения занятой динамической памяти находится в файле А1 ocFreeDM. срр,

Определение функций ввода и печати значений элементов мас­сива дано в файле SortlnOut.срр.

Заголовочный файл программного проекта дан в файле Sor­tArr. h.

Давыдов В.Г. Консольное приложение. Visual C++ 6 Ч

// Включаемый файл программного проекта для сортировки // массивов ^include "SortArr.h"

int main ( // Возвращает О при успехе ±nt ArgCr // ARGument Counter: число

// аргументов в командной строке // ARGument Value: массив указателей на аргументы // командной строки (ArgV[ О ] - .ехе файл, в // интегрированной среде программирования известен и не

229

Page 231: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// задается; ArgV[ 1 ] - файл ввода; ArgVf 2 ] - файл // вывода) сЪаг *ArgV[ ] )

{ // Проверка числа аргументов командной строки ±f( ArдС /= 3 ; {

printf( "\п Ошибка 5. В командной строке должно быть три аргумента: " " \л Имя_проекта. ехе имя_файла_ввода имя_файла__вывода \п" ) ;

ех11 ( 5 ) ; }

// Размещение массивов в динамической памяти: // а, al - указатели на начало массивов в динамической // памяти; S, 9 - размеры массивов AllocArrDMf аггг 8 ) ; // Для сортировки простыми включениями AllocArrDMl( arrl, 9 ) ;

// Сортировка массива простым выбором ReadArr ( arr, ArgV[ 1 ] ) ; WriteArr ( arrг "\n Сортировка массива простым "

"выбором. Массив до сортировки \п '\ ArgV[ 2 ], "w" ) ;

SelectSort ( arr ) ; WriteArr ( arr, "\n Массив после "

"сортировки \n ", ArgVf 2 ], "a" ) ;

// Сортировка массива простыми включениями ReadArrl ( arrl, ArgV[ 1 ] ) ; WriteArrl ( arrl, "\n\n Сортировка массива простыми "

"включениями. Массив до сортировки \п ", ArgV[ 2 ], "а " ) ;

InsertSort ( arrl ) ; WriteArrl ( arrl, "\n Массив после "

"сортировки \п ", ArgV[ 2 ], "а" ) ;

// Сортировка массива методом "пузырька" ReadArr ( arr, ArgV [ 1 ] ) ; WriteArr ( arr, "\n\n Сортировка массива методом "

"\"пузырька\". Массив до сортировки \п ", ArgV[ 2 ], "а " ) ;

BubbleSort( arr ) ; WriteArr( arr, "\п Массив после "

"сортировки \п ", ArgV[ 2 ], "а" ) ;

// Сортировка массива сложным выбором ReadArr ( arr, ArgV [ 1 ] ) ; WriteArr( arr, "\n\n Сортировка массива сложным "

230

Page 232: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

;

"выбором. Массив до сортировки \п ", ArgVf 2 ], "а " ) ;

TreeSort( arr ) ; WriteArr( arr, "\n Массив после "

"сортировки \п ", ArgV[ 2 ], "а" ) ;

// Сортировка массива методом Шелла ReadArr( arr, ArgV[ 1 ] ) ; WriteArr ( arr, "\n\n Сортировка массива методом "

"Шелла. Массив до сортировки \п ", ArgV[ 2 ], "а" ) ; ShellSort ( arr ) ; WriteArr ( arr, "\n Массив после "

"сортировки \п ", ArgV[ 2 ], "а" ) ;

// Сортировка массива методом Хоора (не рекурсивный // вариант) ReadArr ( arr, ArgV[ 1 ] ) ; WriteArr ( arr, "\n\n Сортировка массива методом "

"Хоора. Массив до сортировки \п ", ArgV[ 2 ], "а" ) ; Quicksort( arr ) ; WriteArr ( arr, "\n Массив после "

"сортировки \п ", ArgVl 2 ], "а" ) ;

// Сортировка массива методом Хоора (рекурсивный вариант) ReadArr ( arr, ArgV[ 1 ] ) ; WriteArr ( arr, "\n\n Рекурсивная сортировка "

"Хоора. Массив до сортировки \п ", ArgV[ 2 ], "а" ) ; QuickSortl ( ) ; WriteArr ( arr, "\п Массив после "

"сортировки \п ", ArgV[ 2 ], "а" ) ;

//ккккккккккккккккккккккккккккккккккккккккккккккккккккккк // Освобождение динамической памяти, занятой массивами FreeArrDM( arr ) ; FreeArrDM( arrI ; ;

xetujrn 0;

/* Файл SortArr.h. Подключение стандартных заголовочных фай­

лов, объявление объектов с описателями класса хранения "внеш­ний", типов элементов сортируемого массива, стека и прототи­пов функций. Используется как заголовочный файл в программном проекте для сортировки массивов.

Давыдов В.Г. Консольное приложение. Visual C++ 6 V // Предотвращение возможности многократного подключения #ifndef SORTARR__H

^define SORTARR Н

231

Page 233: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdio.h> // Для ввода-вывода ^include <stdlib.h> // Для exit ( )

const ±nt M = 16; // Размер стека отложенных // сегментов: >= (1од2(size)+1)

// Структурный тип для элемента массива struct ELEMENT {

±nt key; // Ключ сортировки // Описание других компонент элемента

} ;

// Объявления внешних объектов extern ELEMENT

*arr; // Указатель на сортируемый массив extern ±nt size; // Размер сортируемого массива // Указатель на сортируемый массив для сортировки // простыми вставками extern ELEMENT

*arrl; extern ±nt sizel; // Увеличенный на единицу размер

// сортируемого массива

// Структурный тип для элемента стека struct STACK {

±nt 1; // Левая граница сегмента ±nt г; // Правая граница сегмента

} ;

// Прототипы функций (имена параметров в прототипе не // используются и, поэтому, мы их не записываем void AllocArrDM( ELEMENT *&, int ); void. AllocArrDMl ( ELEMENT *&, int ); void ReadArr( ELEMENT [ ], char * ); void ReadArrl ( ELEMENT [ ] , char * ) ; void SelectSort( ELEMENT [ ] ) Г void WriteArr ( ELEMENT [ ], char *, char *, char * ); void WriteArrl( ELEMENT [ ], char *, char *, char * ); void InsertSort ( ELEMENT [ ] ) ; void BubbleSort( ELEMENT [ ] ); void TreeSort ( ELEMENT [ ] ); void Sift( int, int, ELEMENT [ ] ); void ShellSort ( ELEMENT [ ] ); void Quicksort ( ELEMENT [ J ); void Push ( int, int, STACK [ ], int & ); void Pop ( int Sc, int &, STACK [ ], int & ); void QuickSortl ( void ) ; void Split ( i n t , int ); void FreeArrDM( ELEMENT *& );

232

Page 234: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

iendlf _

Файл AlocFreeDM. cpp. Размещение одномерного массива в ди­намической памяти и освобождение динамической памяти, занятой массивом.

Используется в программном проекте для сортировки масси­вов.

Давыдов В.Г. Консольное приложение. Visual C-h-h 6 V // Включаемый файл программного проекта Unciude "SortArr.h"

// Размещение сортируемого массива в динамической памяти void AllocArrDMi

ELEMENT *&arr, // Указатель на начало массива в // динамической памяти (передаем // по ссылке - это ответ)

±пЬ S ) // Число элементов массива {

// Контроль корректности размера массива ±f( S < 2 ) {

printf ( "\п Предупреждение 10. Массив должен " "содержать более двух элементов \п (задан размер, '' " равный %d) . Принимается размер массива, равный'^ " 2. \п Выполнение программы продолжается ", s ) /

S = 2/ } // Размещение массива в динамической памяти агг = new ELEMENT[ s ] ; ±£( arr -= NULL ) {

printf( "\n Ошибка 20. Размещение массива в " "динамической памяти не выполнено ");

exit ( 20 ) ; }

// Инициализация массива нулевыми значениями £ог ( int 1 = 0; i<s; i4-+ )

arr [ 1 ] . key = 0;

// Инициализация размера массива size = s;

2retuz*n/ }

// Размещение массива в динамической памяти для сортировки // простым выбором

233

Page 235: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

void AllocArrDMl ( ELEMENT *&arrl, // Указатель на начало массива в

// динамической памяти (передаем // по ссылке - это ответ)

±nt S ) // Число элементов массива {

// Контроль корректности размера массива ±£( S < 3 ) {

print f ( "\п Предупреждение 10. Массив должен " "содержать более трех элементов \п (задан " "размер, равный %d) . Принимается размер " "массива г равный 3,\п Выполнение программы "продолжается " ) ;

S = 3; }

// Размещение массива в динамической памяти arrl = new ELEMENT[ s ]; ±f( arrl == NULL ) (

printf( "\n Ошибка 20. Размещение массива в " "динамической памяти не выполнено " ) ;

exit ( 20 ) ; }

// Инициализация массива нулевыми значениями for( int i = 0; i<s; i-h-h )

arrl[ i ] . key = 0;

// Инициализация размера массива sizel = s;

return; }

// Освобождение динамической памяти, занятой массивом void FreeArrDM (

ELEMENT *&arr ) // Указатель на начало массива в // динамической памяти (передаем // по ссылке - это и ответ)

{ ±f( arr != NULL ) {

delete [ ] arr; arr NULL;

retvLrn;

Файл SortlnOut.срр. Функции чтения значений элементов мас­сива из файла данных и печати их значений в файл результатов.

234

Page 236: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Используется в программном проекте для сортировки масси­вов.

Давыдов В.Г. Консольное приложение^ Visual C++ 6 V // Включаемый файл программного проекта § include "SortArr.h"

// Чтение значений элементов массива из файла данных void ReadArr (

ELEMENT arr[ ] , // Сортируемый массив // Указатель на имя файла с исходными данными cha.r *pInpFile )

{ FILE *pStrInp; // Указатель на структуру со

// сведениями о файле ввода ±nt i, // Индекс элемента массива

RetCode, // Возвращаемые значения fscanf( ) // или их сумма

RetCodel;// Возвращаемое значение fclose( )

// Открытие файла ввода данных pStrlnp = fopen( pInpFiler "г" ) ; ±£( pStrlnp -= NULL ) {

printf( "\n Ошибка 30. Файл %s для чтения не открыт" " \п", pInpFile ) ;

exit ( 30 ) ; }

// Ввод значений элементов массива Ret Code --= О; for( i = 0; 1 < size; i++ ) {

RetCode += fscanf( pStrlnp, " %d", &( arr[ i ].key ) ) ;

} ±f( RetCode < size ) {

printf( "\n Ошибка 40. Ошибка чтения данных из файла" " %s \п", pInpFile ) ;

exit ( 40 ) ; }

// Закрытие файла ввода RetCodel = fclose( pStrlnp ) ; ±f( RetCodel == EOF ) {

printf( "\n Ошибка 50. Файл %s не закрыт \n ", pInpFile ) ;

exit ( 50 ) ; }

235

Page 237: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

return/ }

// Чтение значений элементов массива из файла данных для // сортировки простыми включениями void. ReadArrl (

ELEMENT arrl [ ], // Сортируемый массив // Указатель на имя файла с исходными данными char *pInpFile )

{ FILE *pStrInp; // Указатель на структуру со

// сведениями о файле ввода int i, // Индекс элемента массива

RetCode^ // Возвращаемые значения fscanf( ) // или их сумма

RetCodel; // Возвраш.аемое значение f close ( ) \

// Открытие файла ввода данных pStrlnp = fopen( pInpFile, "г" ) ; ±f( pStrlnp -= NULL ) {

printf( "\n Ошибка 30. Файл %s для чтения не " "открыт \л", pInpFile ) ;

exit ( 30 ) ; }

// Ввод значений элементов массива ~ обратите внимание на // то, что первый элемент массива (служебный -// "барьер") не заполняется Ret Code = О; fori i = 0; 1 < size; i++ ) {

RetCode += fscanf( pStrlnp, " %d", &( arrl[ i+1 J.key ) ) /

}

±f( RetCode < size ) {

prmtf ( "\n Ошибка 40. Ошибка чтения данных из " "файла %s \п", pInpFile ) ;

exit ( 4 0 ) ; }

// Закрытие файла ввода RetCodel = fclose ( pStrlnp ) ; ±f( RetCodel =- EOF ) {

prmtf( "\n Ошибка 50. Файл %s не закрыт \n ", pInpFile ) ;

exit ( 50 ) ; }

return/

236

Page 238: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Печать значений элементов массива в файл результатов void. Wri teArr (

ELEMENT arr[ ]^ // Сортируемый массив char '^'pMSG^ // Указатель на строку-сообщение о

// печатаемом массиве *pOutFile^// Указатель на имя.расширение

// файла результатов *Mode ) // Указатель на режим открытия файла

{

char

char

FILE

int

*pStrOut; // Указатель на структуру со // сведениями о файле результатов

2, // Индекс элемента массива RetCodel; // Возвращаемое значение fclose( )

}

// Открытие файла вывода pStrOut = fopen ( pOutFlle, Mode ) ; ±£( pStrOut = - NULL ) {

printf( "\n Ошибка 60. Файл %s для вывода не ' "открыт \л", pOutFile ) /

exit( 60 ) ; }

// Печать значений элементов массива с заголовком fprintf( pStrOut, pMSG ) ; tor( 1 = 0; i < size/ i++ ) { // Элементы выводятся по 6 в каждой строке из

// расчета по 10 позиций на каждый элемент fprintf( pStrOut, "%10d", arr[ i ].key ) ; xf( ( ( i-hl ) % 6 ) == 0 ) {

fprintf( pStrOut, "\n " ) ; }

}

// Закрытие файла вывода RetCodel = fclose( pStrOut ) ; ±f( RetCodel == EOF ) {

printf( "\n Ошибка 70. Файл %s не закрыт \n pOutFile ) ;

exit ( 70 ) /

return;

// Печать значений элементов массива в файл результатов для // сортировки простыми включениями void WriteArrl(

ELEMENT arrl [ ] г // Сортируемый массив

237

Page 239: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

}

char *pMSG, // Указатель на строку-сообщение о // печатаемом массиве

cha.2: *pOutFile, // Указатель на имя .расширение файла // результатов

cbajT *Mode ) // Указатель на режим открытия файла

FILE *pStrOut; // Указатель на структуру со // сведениями о файле результатов

±пЬ i, // Индекс элемента массива RetCodel; // Возвращаемое значение fclose( )

// Открытие файла вывода pStrOut = fopen ( pOutFile, Mode ) ; ±f( pStrOut == NULL ) {

printf( "\n Ошибка 60. Файл %s для вывода не " "открыт \л", pOutFlle ) /

exit ( 60 ) ; }

// Печать значений элементов массива с заголовком fprintf( pStrOut, pMSG ) ; for( i = 1; i < sizel; i-h-h ) { // Элементы выводятся по 6 в каждой строке из расчета

// по 10 позиций на каждый элемент fprintf( pStrOut, "%10d", arrlf i ].key ) ; ±f( ( 1 % 6 ) == 0 ) {

fprintf ( pStrOut, "\л " ) / }

}

// Закрытие файла вывода RetCodel = fclose( pStrOut ) ; ±f( RetCodel == EOF ) {

printf( "\n Ошибка 10. Файл %s не закрыт \n ", pOutFile ) ;

exit ( 70 ) ; }

return/

Файл SortArr.cpp. Функции сортировки динамически размещенного массива по не­

убыванию: * простая сортировка массива выбором; * простая сортировка массива обменом; * простая сортировка массива вставками; * сортировка массива с помощью двоичного дерева; * сортировка Шелла; * не рекурсивная сортировка Хоора;

238

Page 240: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

'*' рекурсивная сортировка Хоора. Используется в программном проекте для сортировки масси­

вов. Давыдов В.Г. Консольное приложение, Visual C++ 6

V // Включаемый файл программного проекта ^include "SortArr.h"

// Определения объектов с описателем класса хранения внешний. // Их объявление имеется в заголовочном файле проекта и эти // объекты доступны в других файлах проекта ELEMENT ±nt ELEMENT

int

*arr/ size; *arrl;

sizel;

// Указатель на сортируемый массив // Размер сортируемого массива // Указатель на сортируемый массив // для простой сортировки // вставками // Увеличенный на единицу размер // //

сортируемого массива для простой сортировки вставками

// Эти объекты определяем в данном месте, чтобы при // рекурсивных вызовах // создавались заново ELEMENT сору,

median/

int 1 ,

J/

в сортировке Хоора они не

// Копия элемента массива // Медиана разделяемого сегмента в // сортировке Хоора // Индекс кандидата на обмен слева // Индекс кандидата на обмен справа // в сортировке Хоора

// Сортировка массива простым выбором - по неубыванию void SelectSort (

ELEMENT arrf ] ) // Сортируемый массив {

// Индекс последнего элемента из // пока неупорядоченных // Индекс наибольшего элемента среди // 1..L

// Индекс анализируемого элемента // Для наибольшего элемента среди // 1. .L

JLXm

ELEMENT

•LJr

indmax.

к; max;

// Просмотр неотсортированных начальных сегментов массива // (вначале - весь массив, затем - сегмент из первых // size-1 элементов и т.д.) £ог( L ^ size-1; L >= 1; L-- ) {

// Присвоить indmax индекс элемента массива // наибольшим значением ключа из агг[ О ]. indmax = О; max = arr[ О ]; for( к = 1; к <= L; к++ )

.агг[ L ]

239

Page 241: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±f( arr[ к ] , key > max. key ) (

indmax = k; max = arrf к ]; }

}

// Поменять местами arr[ indmax ] и arrf L ] arrf indmax ] = arrf L ]; arrf L ] = max;

}

return; )

// Сортировка массива простыми включениями - по неубыванию void InsertSort(

ELEMENT arrlf ] ) // Сортируемый массив {

// Используются следующие глобальные объекты: // i - индекс вставляемого элемента; // J - индекс элемента в упорядоченном сегменте; // сору = arrf i ]

// Перебор вставляемых элементов £ог( i=2; 1 <= size; i++ ) {

copy = arrlf 1 ]; arrlf 0 ] = copy;// Установка "барьера"

// Вставка copy на нужное место j = i-1 ; while( copy.key < arrlf j J.key ) {

// Сдвиг arrlf j+1 ] = arrlf j ]; j - - ;

}

arrlf j+1 ] = copy; }

return; }

// Сортировка массива простым обменом - по неубыванию (метод // "пузырька") void. BubbleSort (

ELEMENT arrf ] ) // Сортируемый массив {

int к; // Индекс анализируемого элемента ELEMENT temp; // Для перестановки элементов int sorted^ // 1=0 (отсортирован)

change; // !=0 (были перестановки)

240

Page 242: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

sorted = О; // Цикл проходов while ( !sorted ) {

change = О; fori к = 1; к < size; к++ ) {

±f( arr[ k-1 ].key > arr[ к ].key ) {

temp = arr[ к ]; arr[ к J = arr[ k-1 J; ar r / " k-1 ] = temp; change = 1;

} } sorted = !change;

}

retvum; }

// Сортировка массива сложным выбором с использованием // пирамиды - двоичного дерева

// Просеивание void Sift(

Int rooty // Корень дерева или поддерева Int last у // Последняя вершина в дереве ELEMENT arr[ ] ) // Сортируемый массив

{ // 1 - позиция "дырки" (объект определен на внешнем // уровне) Int j l , // j1 = 2*1 -- следующая вершина

// снизу и слева для i j2; // j2 = 2*1 + 1 - следующая вершина

// снизу и справа для 1 // j - претендент из jl и j2 на заполнение "дыры" // (объект определен на внешнем уровне) // сору - просеиваемый элемент (объект определен на // внешнем уровне) Int found; // 1 (нашли место для вставки сору)

// Подготовка сору = агг[ root-1 ]; 1 = root; found = 0; while ( .'found ) {

// Определение jl и j2 для зафиксированного i jl = 2*i; j2 - jl-hl;

// Анализ вариантов заполнения "дыры" lf( jl > last ) { // Следующего уровня внизу нет

found = 1; }

241

Page 243: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

etlse { // Следующий внизу уровень есть

±£( jl == last ) {

J = Jl/ } else {

j = ( arrf J1-1 J.key >= arrf j2-l ] . key ) ? jl : j2;

}

// Выяснение, кто заполняет "дыру" ±f( arr[ j-1 ],key <= copy.key ) {

found = 1; }

else {

arr[ i-1 ] = arr[ j-1 ]; i=j; }

} } arrf i-1 ] = copy; return;

}

// Сортировка void TreeSort (

ELEMENT arrf ]) // Сортируемый массив {

±nt temproot, // Индекс корня частичного поддерева templast; // Последний элемент в

// неупорядоченном поддереве ELEMENT tempcopy; // Для перестановки элементов

// Начальная подготовка дерева for( temproot = size/2; temproot > 0; temproot-- ) {

Sift( temproot, size, arr ) ; , // Сортировка for( templast = size; templast >= 2; templast-- ) {

// Переставить максимум из корня дерева на // окончательное место tempcopy = arrf О ]; arrf О ] = arrf templast-1 ]; arrf templast-1 ] = tempcopy;

// Просеять новый корень на место - восстановить // дерево Sift( 1, templast-1, arr ) ;

242

Page 244: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

}

xretuxm/ }

// Сложная сортировка массива вставками (метод Шелла) void ShellSort (

ELEMENT arr[ ] ) // Сортируемый массив {

izit d, // Дистанция Шелла fillpos; // Местоположение "дыры"

// i - индекс анализируемого элемента (объект определен // на внешнем уровне) // J - индекс претендента слева на заполнение "дыры" // (объект определен на внешнем уровне) // сору = arrfi] (объект определен на внешнем уровне) int found; // 1 (нашли место для вставки сору)

d = size; while ( d > 1 ) {

d = d/2;

// Отсортировать вставками при текущем d £or( 1 = d; i < size; i++ ) (

copy = arr[ 1 ]; fillpos = i ;

// Найти место вставки copy found = 0; do { .

j = fillpos - d; ±f( j < 0 ) { // Претендента слева нет

found = 1; } else { // Претендент слева больше - сдвиг

if( arr[ j ].key <= copy,key ) {

found = 1; } else {

arr[ fillpos ] = arr[ j ]; fillpos ^ j ;

} }

} while( !found ) ; // Вставка copy arrf fillpos ] = copy;

243

Page 245: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

retuxm/ }

// Быстрая сортировка Хоора - нерекурсивный вариант

// Занесение в стек сегментов void. Push (

±пЬ left^ // Левая граница сегмента int rights // Правая граница сегмента STACK s[ 7/ // Стек границ сегментов int &sp ) // sp - указатель вершины стека

{ // В стек заносятся только сегменты из двух или более // элементов ±f( ( right-left ) >= 1 ) {

sp+ + / s[ sp ],1 = left; s[ sp J.r = right/ }

return; }

// // Извлчение сегмента из стека void Pop (

int &i, // Указатель на левую границу // сегмента

int &r, // Указатель на правую границу // сегмента

STACK s[ ], // Стек границ сегментов int &sp ) // Указатель вершины стека

( 1 = s[ sp J.l; г = s[ sp ].r; sp--;

re trim; } // // Быстрая сортировка массива - нерекурсивный вариант void Quicksort(

ELEMENT arr[ ] ) // Сортируемый массив {

int left, // Левая граница разделяемого // сегмента

right; // Правая граница разделяемого // сегмента

// i - индекс кандидата на обмен слева - направо (объект // определен на внешнем уровне) // j - индекс кандидата на обмен справа - налево (объект // определен на внешнем уровне) // median - медиана разделяемого сегмента (объект

244

Page 246: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// определен на внешнем уровне) // сору - для перестановки кандидатов (объект определен // на внешнем уровне) STACK s[ М ]; // Стек границ сегментов ±Tib sp; // Указатель вершины стека

sp = -1 ; // Вначале стек пуст Push ( О, size-lr S, sp ); while ( sp >= О ) {

// Подготовка верхнего сегмента из стека для // разделения Pop ( left, right г s , sp ); median = arr[ ( left+rlght )/2 ]; i = left; J = right;

// Разделение текущего сегмента while ( 1 <= J ) {

// Найти кандидата на обмен слева while ( arr[ i ].key < median.key ) i + + ; // Найти кандидата на обмен справа while ( median.key < arr[ j ],key ) j - - ;

// Обмен, если кандидаты находятся в разных // подсегментах if( 1 <= j ) {

copy = arr[ 1 ]; arr[ i ] = arr[ j ]; arr[ j ] = copy; 1++; j - - ;

I }

// Поместить в стек сначала белее длинный подсегмент, // а затем - более короткий if( ( j-left ) < ( right-1 ) ) { // Леваый подсегмент - короче

Push ( 1, right, s , sp ); Push ( left, J, s , sp );

} else { // Правый подсегмент - короче

Push ( left, J, s , sp ); Push ( 1, right, s , sp );

} }

return; }

// Быстрая сортировка Хоора - рекурсивный вариант

void Quicksort 1 ( void )

245

Page 247: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

( Split ( Or size-1 );

return; } // // Функция разделения сегментов void Split (

±nt leftr // Левая граница сегмента int right ) // Правая граница сегмента

{ xf( ( right-left ) <= О ) (

return; } else (

// Подготовка median = arr[ ( left + right )/2 ]; i = left; j = ri gh t;

// Разделение while( i <= j ) f

// Найти кандидата на обмен слева while ( arr[ i ].key < median.key ) i + + ; // Найти кандидата на обмен справа while ( median.key < arrf j J.key ) j - - ;

// Обменr если кандидаты находятся в разных // подсегментах if( i <= J ) I

copy = arrf 1 J; arrf i ] = arrf j ]; arrf J ] = copy; i++; j - - ;

} }

// Финал - разделить сначала более короткий сегмент if( ( j-left ) < ( rlght-1 ) ) { // Леваый сегмент - короче

Split ( leftr J ); Split ( 1, right ); } else I // Правый сегмент - короче

Split ( 1, right ); Split ( left, j ); }

}

return;

Для файла данных, приведенного ниже.

246

Page 248: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

44 55 12 42 94 18 6 61

файл результатов имеет следующий вид:

Сортировка 44

6

6 61

массива простым выбором. Массив до сортировки 55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94

Сортировка, массива простыми включениями. Массив до сортировки 44

6

6 61

55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94

Сортировка массива методом "пузырька". Массив до сортировки 44

6

6 61

Сортировка 44

6

6 61

Сортировка 44

6

6 61

Сортировка 44

6

6 61

55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94

массива сложным выбором. Массив до сортировки 55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94

массива методом Шелла. Массив до сортировки 55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94

массива методом Хоора. Массив до сортировки 55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94

Рекурсивная сортировка Хоора. Массив до сортировки \ 44

6

6 61

55 12 42 94 18 61

Массив после сортировки 12 18 42 44 55 94 1

В приведенной программе на данном этапе заслуживают также внимания следующие решения.

1. Размещение сортируемого массива в динамической памяти и освобождение динамической памяти.

2. Механизм передачи сортируемого массива в функции сор­тировок.

247

Page 249: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3. Оформление включаемого файла программного проекта.

Эффективность сортировки простым выбором. Число сравнений ключей не зависит от начального порядка ключей. Опе­рация сравнения выполняется в теле цикла с управляющей перемен­ной к и средним числом повторений size/2. Этот цикл, в свою оче­редь, находится в теле цикла с управляющей переменной L и числом повторений size-\. Таким образом, число сравнений

С = {size -1) • size 12

Число пересылок, напротив, зависит от начального порядка ключей. Если принять, что операция сравнения в теле цикла по к да­ет результат "истина" в половине случаев, то среднее число пересы­лок в этом цикле равно size!А. Цикл по L, как указывалось выше, вы­полняется sizeA раз и в теле цикла выполняется три пересылки и цикл по к. С учетом этого число пересылок

М = (3 + size 14) • {size -1)

Получаем, что при сортировке простым выбором и число сравнений, и число пересылок пропорционально size'^.

15.3. Сортировка массива простыми включениями

Идея алгоритма пояснена на рис. 61, а пример сортировки мас­сива данным методом иллюстрирует рис. 64. Алгоритм сортировки простыми включениями выглядит следующим образом:

for ( 2=2/ JL < sizel/ l+-h ) {

copy = arr [ i ]; // Вставка copy на нужное место среди отсортированных // элементов массива агг[ О ], . . . , arrf i-l ]

}

При поиске подходящего места удобно чередовать сравнения и пересылки, т.е. как бы "просеивать" сору, сравнивая его с очеред­ным элементом arr[J ] и, либо вставляя сору, либо пересылая arr[j ] направо и передвигаясь налево. Заметим, что "просеивание" может закончиться при двух различных условиях.

1. Найден элемент arr[j ] с ключом, меньшим, чем у сору. 2. Достигнут левый конец упорядоченного сегмента и, следо­

вательно, сору нужно вставить в левый конец упорядоченного сег­мента.

248

Page 250: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ключей элементов массива

44 1 44

12

12

12

12

06

55 ^ 1 55 1

44 ^ 42 42

18

12

12

12 1 55 1 44

44

42

18

42

42

42 1 55 1 55

44

42

94

94

94

94

94 1 55

44

18

18

18

18

18

94 1 55

06

06

06

06

06

06

941

67

67

67

67

67

67

67

i = 2

1 = 3

i = 4

1 = 5

1 = 6

i = 7

i = 8

Массив отсортирован 06 12 18 42 44 55 67 94

Рис. 64. Пример сортировки массива простыми включениями

Это типичный пример цикла с двумя условиями окончания. При записи подобных циклов можно использовать известный прием фиктивного элемента ("барьера"), установив "барьер" слева в упоря­доченной части массива агг[ О ] = сору (рис. 65).

0 1 2 ... sJze-1 I г 1 1 ~

агг "Г

«Барьер» Сортируемый массив

Рис. 65. Использование "барьера" при сортировке массива ростыми включениями

Прототип функции сортировки массива простыми включения­ми, ее определение и пример вызова даны в примере, приведенном в подразд. 15.2. Теперь наступила пора познакомиться с ними.

Обратите внимание на то, что при использовании метода "ле­вого барьера" размер массива, подлежащего сортировке, увеличен на один элемент. При этом элемент массива с нулевым индексом яв­ляется вспомогательным и не сортируется. Таким образом, в масси­ве сортируются элементы с индексами 1, 2, ..., size 1-1. По этой при­чине для сортировки простым выбором используются функции раз­мещения сортируемого массива в динамической памяти, заполнения его значениями из файла и печати значений элементов массива в файл, отличающиеся от аналогичных функций для других методов.

Эффективность сортировки. Число С,, сравнений ключей

249

Page 251: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

при i-oM просеивании составляет самое большее /, а самое меньшее - 1. Число М, пересылок (присваиваний) элементов при /-ом просеивании равно

(С,-1) + 3 = С,+2

Это объясняется тем, что тело цикла while выполняется на один раз меньше, чем число проверок условия повтора цикла. Три других пересылки при /-ом просеивании есть:

сору = агг1[ 1 ]; arrl [ О ] = сору/ arrl [ j+1 ] = copy;

Поэтому обш^ее число сравнений и пересылок есть

где (size-2) - число повторов цикла по /, v/rc ' - l

МАХ ~ 2 1 ' ~ (size +1) • (size - 2) / 2,

QpEfl = (CM,N + С ^ ) / 2 = ((size - 2) + (size +1) • (size - 2) / 2) / 2, uze-\

^MiN = 3 • (size - 2), MMAX = X (' " ^) = ^^^^^ " ^) * ^^^^^ " 2) / 2,

Л^сРЕд=(Л^м1м+^мАх)/2 = (3-(^/2е-2) + С9/2еч-5)-(^/^^-2)/2)/2. C iM и Л/ д, , имеют место, если элементы массива с самого на­

чала упорядочены, а С дх и Л/ АХ встречаются, если элементы масси­ва расположены в обратном порядке.

15.4. Сортировка массива простым обменом (метод "пузырька")

Данный алгоритм основан на принципе сравнения и обмена пары соседних элементов до тех пор, пока не будут отсортированы все элементы массива. Пример сортировки массива методом "пу­зырька" приведен на рис. 66.

Очевидно, что в наихудшем случае, когда минимальное значе­ние ключа элемента имеется у самого правого элемента, число про­смотров равно size-\.

Прототип функции сортировки массива простым обменом, ее определение и пример вызова даны в примере, приведенном в под-разд. 15.2. Внимательно изучите их.

250

Page 252: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Начальные значения ключей 44 элементов массива (size = 8)

Конец первого просмотра 44 12 42 55 18 Обратите внимание как «пузырек» 94 «всплыл» вправо!

< • м •

55

12

12

55

42

42

55

94

18

18

94

06

06

94 <—

6

— • 06

Конец второго просмотра

12

12

44

Конец третьего просмотра 12

Конец четвертого просмотра 12

42 42

42 18

18

44 44

18

18 -• 42

18

18

55 06 06

44 06 06

44 44 I 55

06 06

42 42 ( 44

Конец пятого просмотра 12 4-

06 06

18 18 I 42 44 55

67 I 94

55 55 I 67

55 67

06 12 Конец шестого просмотра 06 12 | 18 42 44 55 67 И конец сортировки, так как больше перестановок нет!

Рис. 66. Пример сортировки массива простым обменом

94

67 94

94

67 94

94

Эффективность сортировки. За один проход среднее число сравнений С СРЕД равно size/2 (на первом проходе - size-\, а на по­следнем - 1). При этом среднее число возможных пересылок А/ксРЕд =l-5*QcpEA (в предположении, что проверяемое условие вы­полняется в половине случаев). Минимальное количество проходов равно 1, максимальное - size-l, а среднее - size/2. Следовательно,

СсРЕд =size^ /4, Л/сРЕд =1.5-5/ze^ / 4

15.5. Выводы по простым методам сортировки

1. в простых методах сортировки массивов время сортировки пропорционально size^.

2. Более точные оценки производительности простых методов

251

Page 253: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

сортировки массивов показывают, что наиболее быстрой является сортировка вставками, а наиболее медленной - сортировка обме­ном.

3. Несмотря на плохое быстродействие, простые алгоритмы сортировки следует применять при малых значениях size.

4. Наряду с простыми алгоритмами сортировки массивов су­ществуют сложные алгоритмы сортировки, обеспечивающие время сортировки, пропорциональное не size^, а size-logjisize). При больших значениях size они обеспечивают существенный выигрыш. К их рас­смотрению мы и переходим.

15.6. Сортировка массива сложным выбором (с помощью двоичного дерева)

Данная сортировка, по сравнению с сортировкой простым вы­бором, обладает большим быстродействием за счет сокращения за­трат времени на выбор максимального элемента.

/ . Идея алгоритма. Исходное состояние двоичного дерева (ключи исходного массива структур агг с числом элементов size) показано на рис. 67.

а Вначале двоичное дерево подготавливаем таким образом, чтобы элемент массива с максимальным значением ключа (94) находился в корне дерева.

• Выбираем элемент с максимальным значением ключа, меня­ем его местами с последним элементом массива и затем восстанав­ливаем двоичное дерево (рис. 68). Вновь найденный элемент с мак­симальным значением ключа (67), который после восстановления двоичного дерева оказывается опять в его корне, меняется местами с предпоследним элементом массива и т.д. (всего size-\ раз).

Таким образом, получаем общее число сравнений

С = {size -1) • log 2 {size)

и Какие в связи с этим возникают проблемы? Как построить двоичное дерево (пирамиду) без дополнитель­

ных затрат памяти с тем, чтобы обеспечить сортировку "на месте", т.е. обойтись в дереве size вершинами вместо (5/z^*2-l) вершин?

Как организовать дерево в самом начале работы?

252

Page 254: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Всего ( 2*size-1 ) вершин

94

.55^ / \

44 55

. 4 2 , / \

12 42

, 9 4 / \

94 18

67 / \

06 67

Рис. 67. Исходное состояние двоичного дерева

Вместо выбранного элемента с максимальным значением ключа (94) появились «дырки»

На восстановление двоичного дерева потребовалось 1од2( size ) сравнений претендентов на заполнение «дырок»: 1, 2, 3

Рис. 68. Двоичное дерево после первой замены и восстановления

2. Построение пирамиды (двоичного дерева) **на месте'* и ее начальное заполнение,

2.1. Представление двоичного дерева из size элементов в од­номерном массиве из size элементов. Из вершины двоичного дерева, в обш;ем случае, идут две дуги вниз (см. выше рис. 57), отсюда тер­мин - двоичное дерево.

В двоичном дереве могут встретиться следующие случаи: • у 1 > size - вершина / есть лист дерева; • j \ ~ size - нет7*2; • yi < size - есть у 1 иу2.

Таким образом, для представления массива из size элементов

253

Page 255: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

требуется дерево с size вершин. У каждой вершины может быть О, 1 или 2 дуги вниз (О -лист, 1 или 2 дуги вниз - промежуточная верши­на или корень).

Для рассмотренного выше примера мы получаем следующее дерево из size вершин (рис. 69).

44

/ 67

42

А

^ ^ 55

о \..^^ 94

с:

12

18 06

Рис. 69. Двоичное дерево из size вершин

В отличие от первого дерева из (2*^/ze-l)=15 вершин ключ 94 встречается один, а не четыре раза. Ключ 55 встречается один, а не три раза и т.д. Выигрыш налицо!

2.2. Как подготовить дерево в самом начале, когда в массиве царит беспорядок?

В первую очередь рассмотрим поддеревья-листья 5, 6, 7, 8 (нижний слой на рис. 69). Каждое из поддеревьев-листьев из одной вершины и, следовательно, они упорядочены.

Вершина с максимальным номером, у которой есть следующие снизу вершины, имеет номер size/2 (при size=^ такая вершина имеет номер 4). Поэтому подготовку дерева надо начинать с вершины с номером size/2, потом - продолжать с вершины size/2'l и т.д., закон­чив вершиной 1 (см. рис. 70-73).

Рассмотренный на рисунках процесс будем называть "просеи­ванием дыры" (sift).

254

Page 256: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Size/2=4 В корне соответствующего поддерева образуем «дыру» путем копирования соответствующего корню элемента в сору. Ищем подходящее место для вставки сору и помещаем в него сору.

«Дыра»

67

42 4

1) Г — ; -• j 1

сору W 67 4

1 42 j ^ ^ сору 3) ^ 42

67 4

8 8 Рис, 70. Подготовка двоичного дерева для вершины 4

Size/2 - 1 = 3

/ 18

12 3

1) Г 1 -• 1 1 \ 06

сору ^ 18 <; 06 w

3

3)

12 сору

18

12 6 6

Рис. 71. Подготовка двоичного дерева для вершины 3

06

55 1) Г :

X 2 \ ^ сору _д 67 94 W

Size/2 - 2 2

= 2 сору 1 55 1

X 2)^\Ч 3) 67 94 ^

94

/4"^ 67 55

Рис. 72. Подготовка двоичного дерева для вершины 2

В ходе первоначальной подготовки дерева и в ходе восстанов­ления дерева после выбора элемента с максимальным значением ключа из корня выполнялась одна и та же операция, которую мы на­звали "просеивание". После выбора максимального ключа за каждый шаг просеивания "дырка" перемещается на один уровень вниз, а число уровней есть \og2{size). Поскольку выбор элемента с макси­мальным значением ключа и последующее восстановление дерева проводятся {size-\) раз, то, как уже указывалось выше, общее коли­чество сравнений ключей элементов массива пропорционально {size -1) • log 2 {size).

255

Page 257: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3. функция просеивания 3.1. Идея функции (см. рис. 57)

±ль root^ // Корень дерева ими поддерева last; // Номер последней вершины дерева

// или поддерева

Возможны следующие ситуации: а у 1 > last - претендент на заполнение дыры есть сору\ а yi = last - j2 нет и претендент на заполнение дыры есть

тах{ сору.key., arr[J\ ].кеу }; • yi < last - претендент на заполнение дыры есть

тах{ с ору. key, arr[j\ ].key, arr[j2 ].key }.

/ 42

44

4

67

2 55

fS

Size/2 - 3 = = 1 a) 6) в)

94

44

1

1) Г - - 1

18 copy к

94 1

copy ( i

i 44 i

18 ^

94

copy

1 44 1

• ^ " ^ l " ^ " " ^

18 1 1 1 1 1 1 1 1 ^)ш\ L 1 1

2 3 2 3 - ^ 2 ^ ; ^ 3

с)) в итоге nonv ЧИМ 67 55

Рис. 73. Подготовка двоичного дерева для вершины 1

256

Page 258: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3.2. Функции просеивания и сортировки сложным выбором. Прототипы функций просеивания (Sift), сортировки сложным

выбором с помощью двоичного дерева (TreeSort), их определения и примеры вызовов содержатся в примере программы, приведенном выше в подразд. 15.2. Обратите внимание на то, что функция про­сеивания используется только для внутренних целей и, таким обра­зом, не является интерфейсной (вызывается из функции сортировки сложным выбором). Функция же сортировки сложным выбором, на­против, является интерфейсной. Это означает, что она вызывается пользователем для сортировки массива.

Эффективность сортировки. Ранее было показано, что чис­ло сравнений пропорционально {size-\)'\og2isize). Проанализируем эффективность сортировки более детально.

В функции просеивания Sift цикл while в среднем выполняется {logjisize))/2 раз, содержит одну пересылку в теле цикла и две пере­сылки за пределами цикла. В функции TreeSort сортировки сложным выбором в теле цикла по templast делается три пересылки и пере­сылки в функции sift, а сам цикл выполняется (size-\) раз. В функ­ции TreeSort в теле цикла по temproot выполняется функция sift, а тело цикла повторяется (size/2) раз. Таким образом, число пересы­лок

Л/ = (3 + 2 + (log2 (size)) 12) - (size -1) + (2 + (logj (size))/2) • size/2

пропорционально size • logj (size). В функции просеивания Sift в цикле while производится в сред­

нем 2'(\og2(size))/2 = \og2(size) сравнений. Поэтому общее число сравне­ний

С = (log 2 (size))' (size -1) + (log 2 (size)) • size 12,

что также пропорционально size*\og2(^ize).

15.7. Сложная сортировка вставками (сортировка Шелла)

Идея алгоритма, 1. Используется несколько проходов. 2. На первом проходе отдельно группируются и сортируются

вставками элементы, отстоящие друг от друга на (i = size/2 позиций. 3. На втором проходе аналогично группируются и сортируют­

ся вставками элементы, отстоящие друг от друга иг. d = d/2 позиций. 4. Аналогично выполняются последующие проходы и сорти-

257

Page 259: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ровка заканчивается последним проходом при d - I (как при про­стой сортировке вставками).

Иллюстрирующий пример дан на рис. 74. Следует подчеркнуть, что ускорение сортировки происходит

на первых этапах, когда сортировка вставками производится среди элементов, отстоящих друг от друга на болыиие расстояния. По этой причине на втором и последующих этапах перестановки эле­ментов почти отсутствуют.

Текст функции. Прототип функции ShellSort сложной сорти­ровки вставками, ее определение и пример вызова приведены выше в программе, текст которой дан подразд. 15.2.

О выборе последовательности значений d. У нас в примере использовалась последовательность значений d, равная 1, 2, 4, ..., d<size (в обратном порядке) и число этапов составляло \og^{size).

Начальные значения ключей 44 55 12 42 94 18 06 67 элементов массива Первый этап при d=size/2=4

18 55

06 12

В результате получаем 44 18 06 42 94 55 12 67 Второй этап при d = d/2 = 2 • •

06 44

06 12 44 94

В результате получаем 06 18 12 42 44 55 94 67 Третий этап при d = d/2 = 1 (последний): сортировка производится как

простая сортировка вставками В результате получаем 06 12 18 42 44 55 67 94

Рис. 74. Иллюстрирующий пример для сортировки Шелла

Вместе с тем выявлено, что лучшие результаты получаются, когда последовательные значения d не кратны друг другу. По этой причине Д. Кнут (Кнут Д. Искусство программирования для ЭВМ. Т. 3. - М.: Мир, 1978. С. 342) указывает, что дистанцию нужно вы­бирать так:

258

Page 260: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^ , = 1 , J, =3 -^ ,_ ,+1 (A: = 2,3,...) 1,4, 13, 40, 121, ... (в обратном порядке)

Анализ показывает, что число сравнений при этом пропорцио­нально size^'^, а не size^'^ ^ как в нашем варианте. И то, и другое при больших size хуже, чем size-Xogj^size),

15.8. Сложная сортировка обменом (сортировка Хоора)

/ . Идея алгоритма (рис. 75). Исходный массив разбивается на два сегмента - левый, элементы которого агг[ i ].кеу <= median.key, и правый сегмент с элементами arr[j [.key >= median.key. Далее, каж­дый из полученных сегментов можно сортировать автономно, ана­логично предыдущему, до получения сегментов единичной длины. Тогда массив в целом будет отсортирован.

Исходные значения ключей элементов массива агг[ i ].кеу ( i = О, 1, 2, . ., size-1 ), size = 8

left 44 55 12 1 42 1 94 18 06

hpht 67

п median

median = arr[ (left+right )/2 ] Рис. 75. Идея алгоритма сортировки Хоора

2. Как выполнить разделение на сегменты? Решение этой задачи представлено на рис. 76.

Анализ рисунка показывает, что за size сравнений {size - число элементов массива) исходный массив разделяется на два сегмента, которые можно сортировать автономно. Всего потребуется Xo^^i^ize) таких разделений, в результате которых получатся сегменты еди­ничной длины. Следовательно, общее число сравнений пропорцио­нально size • 1о§2 {size).

259

Page 261: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

left

a) i = left, while( arr[ i ].key < median.key ) i++;

Всегда ли закончится цикл? Да, всегда, поскольку, как минимум, справа есть median. После выхода из цикла получим агг[ I ] key >= median.key п

median Q) j = right; while( arr[ j ] key > median.key ) j — ; right

По тем же причинам, цикл будет заканчиваться всегда и после его завершения получим агг[ j ].кеу <= median key

в) п

median Если i <= j , то divrl i ] и arr[ j ] меняем местами: copy = arr[ i ]; arr[ i ] = arr[ j ];

arr[ j ] = copy; i++; j - ;

г) Перейти к a), если i <= j

Рис. 76. Разделение исходного сегмента на подсегменты

J. Примеры, Пример 1 для общего случая представлен на рис. 77.

Примеры 2, 3 и 4 (частные случаи) приведены на рис. 78. Их анализ предлагается выполнить самостоятельно.

Page 262: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

left Исходные значения ключей элементов массива arr[i] key (1 = 0 , 1 , .., size-1), size = 8

right

44 55 12 42 94 18 06 67 4 5 6 7

median=arr[(left+right)/2]

a) Разбиение исходного массива 1=0, j=7,6; arr[0] меняем местами с arr[6], i++; j - - , i<=j (продолжаем обмен) 1=1, j=5; arr[1] меняем местами с arr[5], i++, j - - , i<=j (продолжаем обмен) 1=2,3, j=4,3; arr[3] меняем местами с arr[3]; i++, j - - ; i>j (разбиение на сегменты закончено) В результате получаем:

' " right

Сегменты.

left 06 18 12 42 94 55 44 67

4 5 6 7 median=arr[(left+right)/2]

J левый left J и правый » nght

(большего размера) б) Каждый из полученных сегментов также разбиваем на сегменты, причем это выгоднее начать с меньшего сегмента (см. обоснование ниже) Поэтому больший из полученных сегментов запоминаем в стеке для последующего разбиения

left j i g h t \=o,^^, j=2; arr[1] меняем местами с агг[2], 06 18 12

0 1 2 Н Н nned

left right 06 12

0 1

1

18

2 med

lan t

lan

•++; J--; i>J (разбиение на подсегменты закончено) В результате получаем

агг[|].кеу

агг[|].кеу

Левый сегмент left j и правый i .right (меньшего размера) Рис. 77. Пример сортировки Хоора для общего случая

4. В каком порядке сортировать полученные сегменты? Сразу оба полученных сегмента сортировать нельзя - какой-то из них нужно отложить на более позднее время, например, запомнить его в стеке.

struct STACK (

±пЬ 1; ±пЬ г;

} s[ М ]; int sp;

// Тип элемента стека

// Левая граница сегмента // Правая граница сегмента

// Указатель вершины стека

261

Page 263: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в) Больший из полученных сегментов (левый) запоминаем в стеке для последующего разбиения. Работа с правым (меньшим) сегментом закончена, так как в нем один элемент. Извлекаем из стека и разбиваем очередной сегмент

left right arr[j] key 06 12 i=0; j=1,0; arr[0] меняем местами с arr[0]; i++, j -

i>j (разбиение на сегменты закончено). Оба полученных сегмента единичной или нулевой

median А^'^чь! и работа с ними закончена

г) Извлекаем из стека и разбиваем очередной сегмент left right

агг[1].кеу 94 55 44 67

left

6 7 median

right arr[i].key 44 55 94 67

6 7 median

i=4; j=7,6; arr[4] меняем местами с агг[6]; i++; j — ; i<=j (продолжаем обмен) i=5; j=5; агг[5] меняем местами с arr[5]; i++; j — ; i>j (разбиение на сегменты закончено). Получаем левый left..j и правый 1..right (большего размера) сегменты. Больший сегмент запоминаем в стеке для последующего разбиения, а работа с меньшим сегментом закончена (в нем один элемент).

arr[i].key 94 67

д) Извлекаем из стека и разбиваем очередной сегмент left right

i=6; j=7; агг[6] меняем местами с агг[7]; i++; j — ; i>j (разбиение на сегменты закончено). Оба полученных сегмента имеют единичную длину и работа с ними закончена. Так как в стеке нет больше сегментов для разбиения,то и сортировка закончена:

7 median

06 12 18 42 44 55 67 94 0 1 2 3 4 5 6 7

Прод. рис. 77 •

Какой должна быть глубина стека М? Величина М зависит от порядка разбиения полученных сегментов. Н. Вирт показал, что ес­ли в стек помещать более длинный из получившихся сегментов, а с коротким сегментом сразу "расправляться", то

М = \0g2isize),

где size - размер сортируемого массива.

262

Page 264: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Пример 2

Исходные значения ключей элементов массива агг[ i ].кеу (i = О, 1, .. , size-1), size =

left nght 3 2 - 7 5 4 О

Пример 3

Исходные значения ключей элементов массива агг[ i ].кеу (i = О, 1, ..., size-1), size =

median=arr[(left+right)/2]

left right 3 2 5 - 7 4

1

Пример 4

Исходные значения ключей элементов массива агг[ 1 ].кеу (1 = О, 1, ..., slze-1), size =

median=arr[(left+right)/2]

left right 3 2 4 - 7 5 0 1

, median=arr[(left+right)/2] Рис. 78. Частные случаи для сортировки Хоора

Если, в целях унификации, границы исходного массива также заносить в стек, то

Л/ = 1 + \og2(size).

При занесении в стек сделаем так, что операция занесения не будет выполняться, если длина заносимого сегмента меньше или равна единице.

Прототипы функций для занесения границ сегментов в стек (Push) и извлечения границ сегментов из стека (Pop), определения функций и примеры их вызова даны в примере программы из под-разд. 15.2. Эти функции являются служебными для нерекурсивной функции Quicksort. Подобные функции, как указывалось выше, на­зывают неинтерфейсными функциями.

5. Нерекурсивная сортировка Хоора, Прототип функции Quicksort, ее определение и пример вызова даны в примере про­граммы из подразд. 15.2. Эта функция является интерфейсной функ­цией и может использоваться для сортировки массива.

6. Рекурсивная сортировка Хоора.

Напомним ваэюнейшие особенности рекурсии: 1. При рекурсивных вызовах функции создаются поколения

263

Page 265: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

вызовов-функций. Это означает, что имеются вложенные друг в дру­га активные экземпляры рекурсивной функции.

2. Каждый рекурсивный вызов помещает в системный стек: копии параметров рекурсивной функции, передаваемых по

значению; адреса аргументов из вызова рекурсивной функции, соответст­

вующих параметрам, передаваемым по ссылке; ее автоматические переменные; адрес возврата из функции; возвращаемое значение, если оно имеется.

По этой причине в рекурсивной функции лучше иметь меньше па­раметров и автоматических переменных (помните, что поколений активных экземпляров рекурсивной функции может быть много).

3. На рекурсивный вызов тратится больше времени, но зато программа получается нагляднее и проще.

С учетом этих особенностей и была спроектирована рекурсив­ная сортировка Хоора. Прототип функции QuickSortl для рекурсив­ной сортировки Хоора, ее определение и пример вызова даны в примере программы из подразд. 15.2. Эта функция является интер­фейсной и предназначена для сортировки массивов. Для разделения исходного сегмента на подсегменты функция Quicksort 1 использует служебную рекурсивную функцию Split. Ее прототип, определение и пример вызова даны также в примере программы из подразд. 15.2. Функция Split является рекурсивной и именно при ее разработке бы­ли учтены перечисленные выше важные особенности рекурсивных функций.

15.9. Сравнительные показатели производительности различных методов сортировки массивов

Приводимые ниже в табл. 29 данные получены для программы, написанной на языке Паскаль (ЭВМ SDS6400) для неупорядоченных массивов.

Из приведенных в таблице данных следует, в частности, что даже для массива относительно небольшого размера из 512 элемен­тов:

1. Худшая по производительности из простых сортировок (простая сортировка обменом) работает в 35 раз медленнее быстрой сортировки Хоора.

2. Самая быстрая из простых сортировок (простая сортировка вставками) работает медленнее в 4,2 раза, чем самая худшая по про-

264

Page 266: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

изводительности из сложных сортировок (сортировка Шелла). При увеличении размеров массива, указанные в пп. 1 и 2

фекты проявляются еще в большей степени. эф-

Табл. 29. Сравнительные показатели производительности различных методов сортировки массивов

Метод сортировки

Вставками

Выбором

Обменом

Обменом (Хоора)

Выбором (с помощью двоичного дерева)

Вставками (Шелла)

Простые методы сортировки Время

сортировки для

size==256, милисекунд

356

509

1026

Время сорти­ровки

для size=512, ми­

лисекунд 1444

1956

4054

Соотношение методов по производительности (относительное время

сортировки)

1

1.3

3

Сложные методы сортировки

60

Н О

127

116

241

349

1 1 1.7

2.1

Page 267: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

16. ГРАФЫ- ТРАНСПОРТНАЯ ЗАДАЧА (ЗАДАЧА КОММИВОЯЖЕРА)

16,1. Терминология

Граф - это пара (К, /?), где V - конечное непустое множество вершин, а R -множество неупорядоченных пар <а, Ь> вершин из множества К, называемых ребрами. Говорят, что ребро г — <а, Ь> соединяет вершины "а" и "6". Ребро 'V" и вершина "<з", ребро 'V" и вершина "Z?" называются инцидентными. Вершины "а" и "6" являют­ся смеэюными. Ребра, инцидентные одной и той же вершине, также называют смеэюными. Степень вершины равна числу ребер, инци­дентных ей.

Для простоты будем ограничиваться классом графов без пе­тель, т.е. без таких ребер <а, 6>, что а = Ь.

Пример графа._ Города и связывающие их дороги можно пред­ставить с помощью графа, показанного на рис. 79.

Рис. 79. Пример графа, представляющего города и дороги между ними

В данном примере граф задает объекты (города) и отношения между объектами (дороги). В приведенном графе содержатся пять вершин и семь ребер. Степень вершин 1, 2, 3, 4 равна трем, а вершины 5 -двум.

В ориентированном или направленном графе каждое ребро имеет направление (например, дорога с односторонним движением, рис. 80 а). В направленном графе отношения не симметричны.

В неориентированном графе отсутствует ориентация ребер (рис. 80 б), т.е.

266

Page 268: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

{a,b) G R тогда и только тогда, когда {Ь,а) е R

а b а b

О Ю о о а - предшественник вершины "Ь" b - преемник вершины "а"

а) б) Рис. 80. Граф:

а) ориентированный (направленный); б) неориентированный

Путь в неориентированном графе, соединяющий вершины "а" и "Z?", - это последовательность вершин Vo,v,,...,i/„(«>0) такая, что v/Q=a,v„=6, а для любого /(0</<«-1) вершины v, и v,^,, соединены ptGpoM. Длина пути Vo,v,,...,v„ равна количеству его ребер, т.е. п. Для примера, показанного на рис. 76, путь 1-4-3-2 между вершинами 1 и 2 имеет длину 3.

Путь замкнут, если v^ =v„. Путь называется простым, если все его вершины различны. Замкнутый путь, в котором все ребра раз­личны, называется циклом. Простой цикл - это замкнутый путь, все вершины которого, кроме вершин VQ И V„, попарно различны.

Расстояние между двумя вершинами - это длина кратчайшего пути, соединяющего эти вершины. Например, на рис. 79 расстояние между вершинами 1 и 2 равно единице, а между вершинами 3 и 5 -двум.

Обод - это граф, вершины которого Vo,v,,...,v/„ при п>2 можно занумеровать так, что для всех i{\<i<n-\) вершина v, соединена ребрами с v,_, и v,^,, вершина VQ С V„, а других ребер нет.

Граф называется связным, если лля любой пары вершин суще­ствует соединяющий их путь.

Во взвешенном графе, в дополнение к графу, задана функция W = f{R), определяющая вес или длину ребра в графе. Обычно взве­шенные графы являются неориентированными, а веса ребер - поло­жительными. Пример взвешенного неориентированного графа при­веден на рис. 81.

В этом примере в качестве веса ребра можно выбрать расстоя­ние между городами. Из примера со всей очевидностью следует, что при поиске пути с суммарным минимальным весом не обязательно, что минимальный вес имеет путь с минимальным числом ребер (в примере лучшим путем от start ло finish является путь по трем доро­гам 1-4-5-2 с суммарным весом 30.0).

267

Page 269: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

100 10.0

10.0 Рис. 81. Пример взвешенного неориентированного графа

16.2. Формы задания графа

Используются две основные формы: 1. С помощью матрицы инциденций (соединений): а) для неориентированного взвешенного графа (рис. 82 а) мггт-

риц|^ инциденций имеет число строк и столбцов, равное числу вер­шин, и симметрична;

б) для ориентированного взвешенного графа матрица соедине­ний не симметрична (рис. 82 б).

а

о 20 b

-О а

о 20

0

0

20

0

а О 20 а

ь | 2 0 I О I b а b а

а) б) Рис. 82. Способы задания графа

b

±nt

2. С помощью списка ребер:

\сЬ А

±nt ±nt float

Num Top , NumArc;

first; last / weight/

// Число вершин // Число ребер

// Ребро графа

// 1-я вершина ребра // 2-я вершина ребра // Вес ребра

};

268

Page 270: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / Адрес первого элемента массива структур с информацией о // ребрах графа А *рАгс;

Задание графа на основе списка ребер удобйо свести в одну структуру:

/ / Структурный тип для графа struct GRAPH {

±nt NumTop; // Число вершин Izit NumArc; // Число ребер А *рАгс; // Указатель на начало массива ребер

// в динамической памяти } ;

Сопоставление указанных форм задания графа позволяет заключить следующее.

1. Использование матрицы соединений требует хранения в па­мяти NumTop*NumTop элементов.

2. Использование списка ребер требует хранения в памяти Ъ"^NumArc элементов.

3. При Ъ'^NumArc < NumTop"^NumTop эффективнее использо­вать задание графа с помощью списка ребер.

4. Использование списка ребер алгоритмичнее. Это означает, что алгоритмы решения задач с использованием графов, заданных списком ребер, проще и эффективнее.

Для примера, приведенного на рис. 81, информация для списка ребер имеет следующий вид:

1г 2, i , 4г 2 , 3 , 2г 5 , 3 , 4, 4, 5 ,

80. 10. 20. 10. 20. 10.

.0

.0 ,0 .0 О 0

Здесь в первой строке 1 и 2 - номера вершин, а 80.0 - вес соединяю­щего их ребра.

16.3. Почему для решения задачи подходит рекурсивный алгоритм?

в общем случае путей из вершины start до вершины finish мо­жет быть несколько, но только один путь будет наилучшим (в част­ном случае, путь может вообще не существовать, например, в несвя­занном графе, или может быть несколько наилучших, эквивалент-

269

Page 271: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ных путей). Нас при поиске оптимального пути интересует на каж­дом этапе только один путь, а не все сразу, т.е. требуется последова­тельный перебор путей. Из информации на рис. 83 следует, что для перебора путей хорошо подходит рекурсивный алгоритм (аналогия с вычислением факториала).

Путь

р,

Вершина

Ребро •

Рис. 83. Рекурсивный перебор путей

16.4. Представление кратчайшего пути до каждой вершины

Сведения о любом пути должны содержать следующую ин­формацию.

1. Имеется ли путь до вершины графа? 2. Суммарный вес пути, начиная от заданной начальной вер­

шины (start)? Но только этих сведений недостаточно, так как нет указаний,

откуда и как двигаться. Для задания недостающих сведений можно организовать линейный список, который для графа, представленно­го выше на рис. 81, будет иметь вид, показанный на рис. 84 а. Такой линейный список можно организовать на базе массива структур (рис. 84 б).

stmjct W {

int ±nt

float } ;

// Путь до одной вершины

exist; // (!= 0) - путь имеется ref; // Предыдущая вершина^ через

// которую проходит путь SumDlst; // Суммарная длина минимального пути

// Адрес первого элемента массива с информацией о минимальном // пути между заданными вершинами W *pMinWay/

270

Page 272: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

start finish start finish 1

1

0.0

0

4 1

10.0

1

5 1

20.0

4

2 1

30.0

5

exist

SumDist

ref

1 1

0.0

0

2 1

30.0

5

3 1

30.0

4

4 1

10.0

1

5 1

20 0

4

J t i t (REFerence - ссылка): 0 - конец списка

a) 6) Рис. 84. Представление кратчайшего пути между вершинами:

а) с помощью линейного списка; б) на базе массива структур

16.5. Как найти минимальный путь

16.5.1. Требуется ли полный перебор путей

При поиске минимального пути часть путей можно отбросить (рис. 85). Попытки, представленные на этом рис., неуместны, если, допустим, существует

pMinWayf 18 ].SumDist = 50.0 и pMinWayf 18 ].exist != О

Текущий путь длиной 200.0

•в;.-; finish

• о

Рис. 85. Поиск минимального пути

16.5.2. Организация перебора путей

Как уже указывалось, для этой цели хорошо подходит рекур­сивный алгоритм. Рассмотрим, как можно пройти отрезок пути от достигнутой промежуточной вершины (intermediate) до финиша (fin­ish) - рис. 86.

271

Page 273: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

start а) intermediate = finish

б) intermediate != finish intermediate

О • intermediate

О finish

Вершина Конец

•>\ Ребро Путь

Рис. 86. Прохождение пути от достигнутой вершины до финиша

Попытку шага вперед из достигнутой вершины по заданному ребру будем делать с помощью функции ForStep, а прохождение пу­ти (если он есть) от достигнутой вершины до вершины finish - с по­мощью функции Pass Way (взаимно рекурсивный вызов PassWay -For Step).

Для решения транспортной задачи спроектируем программу и в ней разработаем функции PassWay -ForStep. Спецификация функ­ции, выполняющей шаг вперед, представлена на рис. 87. Обратите внимание, что в список параметров этой функции включены только три параметра - topl^ IndArc, top2. Это важно, так как для рекурсивных функций, как это было показано выше, число параметров следует минимизировать. Поэтому Gr, pMinWay опре­делены как глобальные объекты. Исходный текст программы для решения транспортной задачи, включающий определение функции ForStep., приведен ниже. На данном этапе в этом тексте рекомендуем рассмотреть только введенные типы, данные и определения всех функций, кроме solution и PassWay. Указанные в конце функции бу­дут рассмотрены позже.

Достигнутая вершина int top1 -Индекс ребра, по которому шагаем int IndArc-Вершина на конце ребра int top2 -Граф GRAPH Gr-

input

ForStep W *pMin\/Vay

process

Массив с инфор­мацией о наилуч­шем пути

output Рис. 87. Спецификация функции, выполняющей шаг вперед по ребру

Обратите также внимание на то, что функция ForStep является служебной функцией и вызывается из функции PassWay, которая, в свою очередь, вызывается из функции solution.

Ill

Page 274: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Файл TestGr.cpp. Тестирование решения транспортной задачи с размещением данных в динамической памяти.

Определение функций^ используемых при решении транспортной задачи, приведено в файле Graph.срр.

Определение Функций размеш,ения данных в динамической памя­ти и освобождения занятой динамической памяти находится в файле GrAlocFree.срр.

Включаемый файл программного проекта находится в файле GrHead.h.

Давыдов В.Г. Консольное приложение, Visual C-f-f- 6 V

/ / Включаемый файл программного проекта для решения // транспортной задачи ^include "GrHead.h"

±nt main ( // Возвраш,ает О при успехе ±пЬ АгдС, // Число аргументов в командной

// строке cha.r *ArgV[ ] ) / / Массив указателей на аргументы

// командной строки {

// Проверка числа аргументов командной строки ±f( ArgC /= 3 ) {

printf( "\п Ошибка 5. В командной строке должно быть три аргумента: "\п Имя__проекта. ехе имя_файла_ввода имя_файла_вывода \п" ) ,

exit ( 5 ) ; }

// Чтение информации о графе ReadGraph ( ArgV[ 1 ] ) ;

// Печать информации о графе WriteGraph( ArgV[ 2 ], "w" ) ;

solution ( ) ; // Решение транспортной задачи

// Вывод результатов решения OutRes ( ArgV[ 2 ], "а" ) ;

•returri Or }

Файл GrHead.h. Подключение стандартных заголовочных фай­лов, объявление используемых структурных типов, объявление объектов с описателями класса хранения "внешний" и прототипов функций. Используется как заголовочный файл в программном проекте для решения транспортной задачи (задачи коммивояже­ра) .

Давыдов В.Г. Консольное приложение. Visual C++ 6

273

Page 275: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

_v // Предотвращение возможности многократного подключения iifndef GRHEAD_H

^define GRHEAD Н

^include <stdio.h> ^include <stdlib.h>

// Для ввода-вывода // Для exit ( )

// Структурный тип для ребра графа stjnzct А {

±nt first/ ±nt last; £1олt weight;

} ;

// 1-я вершина ребра // 2-я вершина ребра // Вес ребра

// Структурный тип для графа strvLcb GRAPH {

±nt NumTop; ±nt NumArc; A *pArc;

// Число вершин // Число ребер // Указатель на начало массива ребер // в динамической памяти

};

// Структурный тип пути до одной вершины struct W {

int

xnt

}.

exist;

ref;

// //

(!=0) в графе имеется путь до вершины

// Предыдущая вершина, через которую // проходит путь до данной вершины

£loa,t SumDlst; // Суммарная длина минимального пути // до данной вершины

// Объявления внешних объектов extern GRAPH

Gr; // Граф // Указатель на массив структур для хранения информации о // минимальном пути от start до finish extern W *pMlnWay; extern int finish; // Вершина - финиш пути extern int start; // Вершина - старт пути

// Прототипы функций void GrAllocDM( void, ) ; void GrFreeDM( void ) ; void ForStep ( int, int, int ) ; void PassWay ( int ) ; void solution ( void ) ; void OutRes ( char* *, char *pMode ) ; void ReadGraph ( char *pFlleInp ) ;

274

Page 276: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

void WriteGraph ( char ^pFileOut, char *pMode ) ;

^endif

Файл GrInpOut.срр. Функции файлового чтения данных о графе и печати их.

Используется в программном проекте для решения транспорт­ной задачи (задачи коммивояжера) .

Давыдов В.Г, Консольное приложение. Visual C++ 6 V // Включаемый файл программного проекта для решения // транспортной задачи (задачи коммивояжера) #include "GrHead,h"

// Чтение данных о графе void ReadGraph (

// Указатель на файл данных char "^pFilelnp )

{ FILE *pStrInp; // Указатель на структуру со

// сведениями о файле ввода ±пЬ i , // Индекс ребра

RetCode, // Возвращаемые значения fscanf( ) // или их сумма

RetCodel; // Возвращаемое значение fclose( )

// Открытие файла ввода pStrlnp = fopen( pFilelnpr "г" ) ; ±£( pStrlnp == NULL ) {

printf( "\n Ошибка 50, Файл %s для чтения не " "открыт \п", pFilelnp ) ;

exit ( 50 ) ; }

// Чтение количества вершин и количества ребер RetCode = fscanf( pStrlnp, " %d", &Gr,NumTop ) ; ±f( RetCode != 1 ) (

printf ( "\n Ошибка 60, Ошибка при чтении числа " "верш^^н графа \п" ) ;

exit ( 60 ) ; } RetCode = fscanf ( pStrlnp, " %d", &Gr,NumArc ) ; ±f( RetCode 1=1) {

printf ( "\n Ошибка 70, Ошибка при чтении числа " "ребер графа \п" ) ;

exit ( 70 ) ; }

275

Page 277: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Размещение структур данных графа в динамической памяти GrAllocDM( ) ;

// Заполнение массива ребер графа Ret Code = 0; £ою ( i = 0; 1 < Gr.NumArc; i + -h ) {

RetCode += fscanf( pStrlnp, " %d %d %g", &Gr.pArc[ i ].first, &Gr.pArc[ 1 J.last, &Gr.pArc[ 1 ].weight ) ;

±f( ( Gr.pArcl i ].first < 0 ) \\ ( Gr.pArcl i ].first >= Gr.NumTop ) | | ( Gr.pArc[ i ].last < 0 ) \\ ( Gr.pArcf 1 ].last >= Gr.NumTop ) )

{ printf( "\n Ошибка 75. Индексы вершин д.б. в "

"диапазоне О. . %d \ л " , Gr.NumTop~l ) ; exi t ( 75 ) /

} } ±£( RetCode < 3*Gr.NumArc ) {

printf( "\n Ошибка 80. Ошибка чтения элементов " "массива ребер \п" ) ;

exit ( 80 ) ; }

// Чтение информации о вершинах - старте и финише пути RetCode = fscanf ( pStrlnp, " %d", &start ) ; ±£( RetCode /= 1 ) {

printf( "\n Ошибка 90. Ошибка при чтении начальной" " вершины пути \п" ) ;

exit ( 90 ) ; } RetCode = fscanf ( pStrlnp, " %d", & finish ) ; ±£( RetCode != 1 ) (

printf( "\n Ошибка 100. Ошмбка при чтении конечной" " вершины пути \п" ) ;

exit ( 100 ) ; }

// Закрытие файла ввода RetCodel = fclose( pStrlnp ) ; ±f( RetCodel == EOF ) {

printf( "\n Ошибка 110. Ошибка закрытия файла %s \п", pFilelnp ) ;

exit ( 110 ) ; }

xretuxm/

276

Page 278: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Печать данных о графе void. WriteGraph (

char *pFileOut,// Указатель на файл результатов char *pMode ) // Указатель на режим открытия файла

{ FILE *pStrOut/ // Указатель на структуру со

// сведениями о файле результатов Int i, // Индекс элемента массива ребер

RetCodel; // Возвращаемое значение fclose( )

// Открытие файла вывода pStrOut = fopen( pFileOut, pMode ); ±f( pStrOut == NULL ) {

printf( "\n Ошибка 120, Файл %s для вывода не " "открыт \л", pFlleOut );

exit ( 120 ) ; }

// Печать информации о графе fprlntf ( pStrOutг "\п Число вершин графа: %d,"

" число ребер: %d \п"г Gr.NumTop, Gr.NumArc ) ; fprlntf ( pStrOutr

" \j^* * * * * -^ * * * * * * * * * -^ * * * * * * * * * * * * * * * * * * "^ * * * "^^ * * * * * * * * * * * * *

"\n Индекс ребра 1-я вершина 2-я вершина Вес ребра" "\п********************^**************************^*********" "\п" ) ;

tori 1 = о; 1 < Gr.NumArc/ i + -i- ) {

fprlntf( pStrOut, "%10d %14d %14d %17f \л", i, Gr.pArcf 1 ].first, Gr.pArc[ 1 ],lastr Gr.pArcf i ].weight );

}

// Закрытие файла вывода RetCodel = fclose( pStrOut ); ±f( RetCodel -= EOF ) {

printf( "\n Ошибка 150. Файл %s не закрыт \n ", pFileOut );

exit ( 150 ); }

return; }

Файл GrAlocFree. cpp. Размеш,ение массива с информацией о ребрах графа в динамической памяти и освобождение динамиче­ской памяти, занятой этим массивом.

Используется в программном проекте для решения транспорт­ной задачи.

277

Page 279: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Давыдов В.Г. Консольное приложение^ Visual C++ 6 _V j / / Включаемый файл программного проекта для решения // транспортной задачи (задачи коммивояжера) #include "GrHead.h"

// Размещение массива с информацией о ребрах графа в // динамичнеской памяти void. GrAllocDM( void ) {

// Gr.NumTop - число вершин графа (определяется на // внешнем уровне) // Gr. NumArc - число ребер графа (определяется на внешнем // уровне) // Gr.pArc - указатель на начало массива с информацией о // ребрах^ размещенного в динамической памяти // (определяется на внешнем уровне) // pMinWay - указатель на начало массива с информацией о // наилучшем пути, размещенного в динамической // памяти (определяется на внешнем уровне)

// Контроль корректности количества вершин графа i£( Gr.NumTop < 2 ) {

printf( "\п Предупреждение 10. Число вершин графа должно быть более" " одной вершины" "\п (задано число вершин, равное %d) . Принимается число" " вершин, равное 2." "\п Выполнение программы продолжается ", Gr.NumTop ) ;

Gr.NumTop = 2; }

// Контроль корректности количества ребер графа if( Gr. NumArc < 1 ) {

printf( "\n Предупреждение 20. Число ребер графа должно быть не" " менее одного ребра" "\п (задано число ребер, равное %d) . Принимается число" " ребер, равное 1. "

Gr.NumArc = 1; }

// Размещение массива ребер в динамической памяти Gr.pArc = new А[ Gr. NumArc ] ; if( Gr.pArc == NULL ) {

printf( "\n Ошибка 30. Массив ребер в динамической" " памяти не размещен " ) ;

exit ( 30 ) ; }

11^

Page 280: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Размещение массива структур с информацией о наилучшем // пути в динамической памяти pMlnWay = new W[ Gr. NumTop ] ; ±f( pMinWay == NULL ) {

printf( "\n Ошибка 40. Массив структур с информацией о наилучшем" " пути в \п" "\п динамической памяти не размещен " ) ;

exit ( 4 0 ) ; }

// Инициализация массивов, размещенных в динамической // памяти, нулевыми значениями for( Int 1 = О; 1 < Gr.NumArc; i++ ) {

Gr.pArcf i J.firSt = 0; Gr.pArcf i ],last = 0; Gr.pArc[ 1 ].weight = O.Of;

} £or( 1 = 0; 1 < Gr. NumTop/ 1 + + ) {

pMlnWayf 1 ]. exist = 0; pMinWayf i ].ref = 0/ pMinWay[ i ] . SumDist = 0.0;

}

return; }

// Освобождение динамическом памяти, занятой массивами ребер // и структур с информацией о наилучшем пути void. GrFreeDM( void ) {

i£( Gr.pArc /= NULL ) {

delete [ ] Gr.pArc; Gr.pArc = NULL; }

if( pMinWay != NULL ) {

delete [ ] pMinWay; pMinWay = NULL; }

return; }

Файл Graph. cpp. Функции решения транспортной задачи:

* шаг вперед из достигнутой вершины по заданному ребру; * прохождение пути от достигнутой вершины InterMediate, если он есть, до вершины finish; * поиск пути минимального веса в неориентированном взвешенном

279

Page 281: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

графе; * печать информации о наилучшем пути от start до finish.

Используется в программном проекте для решения транспорт­ной задачи (задачи коммивояжера). |

Давыдов В.Г, Консольное приложение^ Visual C++ 6 '/ I // Включаемый файл программного проекта для решения транс­портной задачи (задачи коммивояжера) #include "GrHead.h"

// Определения объектов с описателем класса хранения внешний. // Их объявление имеется в заголовочном файле проекта и // доступно в других файлах проекта Inb start; // Вершина - старт пути

// Эти объекты определяем в данном месте^ чтобы при // взаимнорекурсивных вызовах функций PassWay( ) и // ForStep ( ) они не создавались заново GRAPH Or; // Граф W *pMinWay; // Указатель на массив структур с

// информацией о наилучшем пути из // вершины start в finish

Int finish, // Вершина - финиш пути one, // 1-я вершина текущей дуги two/ // 2-я вершина текущей дуги

// Шаг вперед по ребру с индексом IndArc из вершины topi в // вершину top2 void. ForStep (

±пЬ topi, // Достигнутая вершина, из которой // шагаем вперед

int IndArc, // Индекс ребра, по которому // делается шаг вперед

int top2 ) // Вершина на конце ребра {

£2.аа.Ь NewDist; // Расстояние до top2 по пути через // topi

NewDist = pMinWayl topi ] . SumDist + Or.pArc[ IndArc ].weight;

±f( !pMinWay[ top2 ]. exist ) { // Пока пути до top2 нет

pMinWayl top2 ].exist = 1; pMinWay[ top2 ]. SumDist = NewDist/ pMinWayf top2 J.ref = topi/ PassWay ( top2 ) /

} else { // Путь до top2 уже существует

if( pMinWay[ top2 ]. SumDist > NewDist ) { // Новый путь короче

pMinWayl top2 ].SumDist = NewDist/

280

Page 282: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

pMinWayl top2 J.ref = topi; PassWay( top2 ) ; }

}

return/ }

// Прохождение пути от достигнутой вершины InterMedlate, если // он есть г до вершины finish void. PassWay (

// Достигнутая вершина - отправная точка пути Int InterMedlate )

{ ±nt к; // Индекс текущей дуги графа

±f( InterMedlate == finish ) { // ! ! ! Выход из рекурсии

return/ } else {

// Перебор ребер графа £ог( к == О/ к < Gr.NumArc/ к++ ) {

one = Gr.pArc[ к ]. first/ two = Gr.pArc[ к J.last/ // Определения направления шага по ребру и // выполнение шага в найденном направлении ±f( one == InterMedlate ) {

ForStep( one г к, two ) / }

else ±f( two == InterMedlate ) (

ForStep ( two, k, one ) ; }

} return/ // !!! Альтернативный вариант выхода

// из рекурсии } }

// Поиск пути минимального веса в неориентированном // взвешенном графе void, solution ( void ) {

int j / // Индекс вершины

// Начальная подготовка массива структур с информацией о // наилучшем пути fori j = О/ j < Gr.NumTop/ j++ ) {

281

Page 283: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

pMinWayf j ], exist = 0; } pMinWay [ start ]. exist = 1; pMinWay[ start ] . SumDist = O.Of; pMinWay[ start J.ref = -1;

// Рекурсивное определение требуемого пути PassWay( start );

return/ }

// Печать информации о наилучшем пути от start до finish void. OutRes (

char *pFileOut, // Указатель на файл вывода char *pMode ) // Указатель на режим вывода в файл

{ FILE *pStrOut; // Указатель на структуру со

// сведениями о файле результатов ±пЬ TempTopf // Текуш,ая вершина пути

RetCodel; // Возвраш;аемое значение fclose ( )

// Открытие файла вывода pStrOut = fopen( pFileOut^ pMode ); ±f( pStrOut == NULL ) {

printf( "\n Ошибка 140. Файл %s для вывода не " "открыт \л" , pFileOut );

exit ( 140 ) ; )

// Печать информации о найденном пути ±f( !pMinWay[ fini'Sh ],exist ) {

printf ( "\n Искомого пути не существует \п" ) ;

} else {

// Печать оптимального пути ТетрТор = finish/ fprintf( pStrOut,

"\п Вершина-финиш: %d, вершина - старт: %d " "\л Значение минимального пути: %д \л", finish,

start, pMinWay[ finish ].SumDist )/ fprintf ( pStrOut, "\n Список вершин, образуюш:их"

" этот путь (от finish до start): \п" )/ while ( ТетрТор != -1 ) {

fprintf( pStrOut, " %4d ", ТетрТор )/ Temp Top = pMi nWay [ Temp Top ] . ref/

} }

282

Page 284: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Закрытие файла результатов RetCodel = fclose( pStrOut ) ; ±f( RetCodel == EOF ) {

printf( "\n Ошибка 150. Файл %s не закрыт \n", pFileOut ) ;

exit ( 150 ) ; }

// Освобождение динамической памяти GrFreeDM( ) ;

retujcn/

При файле исходных данных

5 6 О 1 80 0 3 10 1 2 20 1 4 10 2 3 20 3 4 10 О 1

получаем решение транспортной задачи в файле результатов в следующем виде:

Число вершин графа: 5 , число ребер: 6

Индекс ребра 1-я вершина 2-я вершина Вес ребра 0 О 1 О 2 1 3 1 4 2 5 3

В ерши на - финиш: 1, в ерши на - с тар т: О Значение минимального пути: 30

Список вершин, образуюш;их этот путь (от finish до start) : 1 , 4 3 О

Спецификация функции прохождения пути от достигнутой вершины до finish, если он есть, представлена на рис. 88.

1 3 2 4 3 4

80. 10. 20. 10. 20. 10.

,000000 ,000000 ,000000 ,000000 ,000000 ,000000

283

Page 285: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Достигнутая вершина - отправная точка пути int InterMediate

Финиш пути Граф

int finish GRAPH Gr

PassWay

input process output

Рис. 88. Спецификация функции прохождения пути от достигнутой вершины j\o finish

Необходимо обратить внимание, что в список параметров этой функции включен только один параметр - InterMediate- так как Gr, finish определены на внешнем уровне (повторяем, что для рекурсив­ных функций число параметров нуэюно минимизировать). Текст функции PassWay приведен выше. На данном этапе рекомендуем рассмотреть только функцию PassWay. Остальные функции будут рассмотрены позже. Данная функция, как и функция ForStep, явля­ется вспомогательной и вызывается из функции solution.

Из приведенной программы следует, что взаимно-рекурсивный вызов функций выглядит следующим образом (рис. 89).

Pass Way ( start);

Выход (InterMediate == finish или обработаны все ребра графа) Рис. 89. Взаимно-рекурсивный вызов функций Pass Way-ForStep

Спецификация функции solution для решения задачи в целом представлена на рис. 90.

Вершина - старт пути int start

Граф GRAPH Gr

input

solution Массив с информацией о

- • наилучшем пути W *pMinWay

process output Рис. 90. Решение задачи в целом

Список параметров этой функции пуст. Объясняется это тем.

284

Page 286: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

что start, Gr, pMinWay являются глобальными объектами (определены на внешнем уровне). Текст программы, включающий определение функции solution, приведен выше. Данная функция, в отличие от предыдущих функций Pass Way и ForStep, является ин­терфейсной функцией и вызывается для решения транспортной за­дачи.

16.6. Пример поиска минимального пути в графе

Схема графа приведена на рис. 91.

first last weight

0 1 2 3 4 5

0 0 1 1 2 3

1 3 2 4 3 4

80.0 10.0 20.0 10.0 20.0 10.0

Внимание! Нумерация вершин и ребер начинается с нуля, так как минимальный индекс элемента массива с языках Си/С++ равен нулю.

Рис. 91. Пример схемы графа

Состояние массиваpMinWay после подготовки в функции solu­tion перед вызовом функции PassWay{ start ) показано на рис. 92.

pMinWay 1 1

0.0

-1

0

0.0

0

0

0.0

0

0

0.0

0

0

0.0

0

Индексы вершин exist SumDist

ref (REFerence - ссылка): -1 означает конец списка start finish

Рис. 92. Состояние массива,pMinWay после начальной подготовки

В качестве задания для самостоятельной работы предлагается проанализировать работу программы и убедиться, что в результате в массиве pMin Way получится информация, показанная на рис. 93.

Важное замечание! В данном частном случае массивpMinWay указывает наилучшие пути от start до всех остальных вершин. Но в общем случае это не гарантируется. Гарантируется лишь оптималь-

285

Page 287: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ность пути из start ъ finish.

pMJnWay 1

0.0

-1

1

30.0

4

1

30.0

3

1

10.0

0

1

20.0

3 start finish

i'

Индексы вершин exist

SumDist

ref (REFerence - ссылка): -1 означает конец списка

Рис. 93. Состояние массива/?МшЖду после решения транспортной задачи

16.7. Печать информации о наилучшем пути

в рассмотренном примере получена информация о наилучшем пути между вершинами start \\ finish в обратном порядке (рис. 94).

Рис. 94. Информация о наилучшем пути между вершинами start и finish

Прототип и определение функции OutRes^ в которой произво­дится печать информации о найденном оптимальном пути, приведе­ны в подразд. 16.5.2. Там же приведен текст функции, выполняющей тестирование спроектированного класса, и результаты тестирования.

Советуем внимательно изучить этот пример и поэксперимен­тировать с ним.

При этом рекомендуем обратить внимание на следуюш^ие осо­бенности рассмотренного программного проекта:

1. Взаимно-рекурсивный вызов функций Pass fVay-ForStep (ва­рианты завершения рекурсии в методе Pass Way; минимизация коли­чество параметров и внутренних данных в этих методах; алгоритми­ческое решение, обеспечивающее получение решения транспортной задачи при неполном переборе путей между заданными вершинами).

2. Структуру спроектированной программы. 3. Оформление исходных текстов

286

Page 288: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

4. Терминологию при работе с графами. 5. Практическую значимость решения транспортной задачи

(получение оптимального пути между городами, связанными раз­ветвленной системой дорог; определение оптимального маршрута между заданными пунктами в крупном городе и т.п.).

Page 289: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

17. поиск

Поиск, как и сортировка, может быть двух видов. 1. Внутренний поиск - поиск в оперативной памяти, в таблице

(т.е. в массиве). 2. Внегиний поиск- поиск на внешней памяти (на магнитном

диске или магнитной ленте). Рассмотрим широко распространенные задачи внутреннего

поиска.

17.1. Постановка задачи внутреннего поиска Таблица данных располагается в оперативной памяти и содер­

жит некоторое количество строк, вид которых представлен на рис. 95.

8 байт 62 байта (LDATA-1), данное - другие сведения (LKEY-1, LengthKEY). ключ для поиска

Рис. 95. Структура строки таблицы

Строка таблицы может занимать, например, 70 байт памяти (см. рис. 95). Байт хранит код некоторого символа.

Приведем несколько примеров таблиц такого рода. 1. Русско-английский словарь. Ключ - русское слово, данное -

соответствующее английское слово и другие сведения. 2. Англо-русский словарь. Аналогично. 3. Таблица домашних адресов: ключ - фамилия, другие сведе­

ния - домашний адрес и телефон.

Для дальнейшего примем допущение, что ключ поиска может содержать только строчные буквы латинского алфавита, цифры и символ пробела, начинается с буквы, а символ пробела (символы пробелов) может (могут) быть только завершающим (завершающи­ми).

В кодовых таблицах буквы латинского алфавита упорядочены, заглавные (прописные) буквы предшествуют строчным, цифры предшествуют заглавным буквам, а пробел - цифрам (в смысле ал-

288

Page 290: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

фавита). Русские буквы в кодовых таблицах - в общем случае неупо-рядочены. Отсюда вывод - сравнение ключей поиска, содержащих латинские буквы, можно проводить непосредственно (например, с помощью строковой функции strcmp, как в нашем случае). И наобо­рот, если ключ содержит русские буквы, то для сравнения ключей следует использовать специально написанную для этой цели функ­цию.

Данные для поиска в таблице могут иметь следующий вид:

const ±nt LKEY = 9; / / Длина ключа в строке таблицы // LKEY~1

// Длина данного в строке таблицы LDATA-1 const xnt LDATA = 63;

// Тип для строки таблицы struct STRTAB (

// Ключ char кеу[ LKEY ]; // Данные сЬаг data[ LDATA ];

} ;

// приведенные ниже объекты будут использованы практически во // веек функциях поиска в таблице и их целесообразно // определить с описателем класса хранения внешний - это // сделает указанные объекты доступными в других файлах // проекта и всех функциях STRTAB *рТаЫе; // Адрес первой строки таблицы в

// динамической памяти ±nt size; // Размер таблицы

Спецификация функции, выполняющей поиск в таблице, пред­ставлена на рис. 96. Из нее следует, что хотя общее число исходных данных {input) и результатов, получаемых из функции поиска {out­put), равно пяти, все же в список параметров функции следует включить только три из них, так как два исходных данных - table и size — следует определить на внешнем уровне как глобальные объек­ты.

STRTAB *рТаЬ1е И • Int &found int size м search char KeyWord[LKEY] И • int &line

input process output Рис. 96. Спецификация функции поиска в таблице

Решение задачи поиска заключается в том, что в таблице рТаЫе надо найти строку с полем ключа, совпадающим со словом

289

Page 291: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Key Word (если строка найдена, то ее индекс line, а флаг результата поиска found=\) или получить ответ, что такой строки в таблице нет (found=0).

Основными способами поиска в таблице являются. / . Последовательный поиск. Эффективность поиска (среднее

число обращений к таблице для нахождения искомой строки) равна size/2.

2, Логарифмический поиск (бинарный, с помощью двоичного дерева). Число обращений к таблице равно \Q>%^{size).

J. ПоисКу использующий прямой доступ к таблице. Число обращений к таблице равно единице.

4. Поиск с использованием перемеиганной, слабо заполнен­ной таблицы (хэт-таблицы). Число обращений к таблице близко к единице.

Рассмотрим перечисленные способы поиска, кроме малоупот-ребимого поиска с прямым доступом, рассмотренного в [6].

17.2. Последовательный поиск

Пример таблицы, заполненной для последовательного поиска, показан на рис. 97.

П Р о с м о т

| р | ключ данное -* Рис. 97. Пример таблицы для последовательного поиска

size-1

lesson

type

word

work

лекция

тип

слово

работа

Поиск выполняется в полностью заполненной таблице. Про­смотр таблицы выполняется последовательно, в соответствии с рос­том индексов строк таблицы. Если в какой-то строке таблицы поле ключа совпадает с KeyWord^ то поиск окончен с результатом "на­шли". Если этого не произошло, а конец таблицы достигнут - поиск окончен с результатом "не нашли".

Для таблицы, показанной на рис. 97, для ключевого слова word результатом поиска 6yjXQT found = 1; line = 2. Аналогично, для клю­чевого слова a«<i результатом поиска 6yjxQT found = 0.

Как указано выше, эффективность поиска определяется чис­лом обращений к таблице. Для последовательного поиска эффек-

290

Page 292: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

тивность поиска составляет в среднем size/2 обращений. Программный проект, в котором содержатся определения

функций для поиска в таблице и пример их использования, приво­дится ниже. В примере на данном этапе следует рассмотреть только данные и те фрагменты проекта, которые относятся к функции Se-quentialSearch для последовательного поиска в таблице. К числу та­ких фрагментов относятся, в том числе, функции AllocTableDM (размещение таблицы в динамической памяти), FreeTableDM (осво­бождение занятой таблицей динамической памяти), SeqlnpTab (за­полнение таблицы), PrintTab (печать содержимого таблицы) и Print-Search (вывод результатов поиска).

/* Файл TestSearch.срр. Тестирование поиска в таблице. Определение методов поиска в таблице приведено в файле

Sea rch Tab! е. срр. Заголовочный файл проекта - файл SearchTable. h. Для откытия и закрытия файлов используются универсальные

функции^ определенные в файлах OpenCloseFile.h и OpenCloseFile.срр.

Давыдов В.Г. Консольное приложение. Visual C++ 6 */

// Включаемый файл программного проекта для поиска в таблице ^include "SearchTable.h"

±nt main ( // Возвращает О при успехе ±nt ArgC, // Число аргументов в командной

// строке dbai: *ArgV[ ] ) / / Массив указателей на аргументы

// командной строки (ArgV[ О ] -// .ехе файл, в интегрированной // среде программирования известен // и не задается/ ArgV[ 1 ] - файл // ввода/ ArgV[ 2 ] - файл вывода)

{ // Проверка числа аргументов командной строки ±f( ArgC != 3 ) {

printf( "\n Ошибка 5. В командной строке должно быть три аргумента: " "\п Имя_проекта. ехе имя_файла_ввода имя_файла_вывода \п" ) /

exi t ( 5 ) / }

// Создаем и инициализируем таблицу из 4 строк AllocTableDM( 4 ) /

±пЬ found, // 1 - нашли ключевое слово line/ // Индекс найденной строки

291

Page 293: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Заполняем таблицу для последовательного поиска и // печатаем ее SeqInpTab ( ArgV[ 1 ] ) ; PrintTab( ArgV[ 2 7/ "^"г

" Состояние таблицы:" ) ;

// Тестирование последовательного поиска в таблице FILE *pStructFlleOut = OpenFile( ArgV[ 2 ], "a",

170 ) ; fprintf( pStructFileOutr

"\n\n Тестирование последовательного поиска \л" ) ; CloseFile( pStructFileOut, ArgV[ 2 7, 180 ) ; SequentialSearch ( "and", founds line ) ; PrintSearchi ArgV[ 2 ], "a", found, line, "and" ) ; SequentialSearch( "word", found, line ) ; PrintSearch( ArgV[ 2 ], "a", found, line, "word" ) ;

// Заполняем таблицу для логарифмического поиска и // печатаем ее InpTabLog( ArgV[ 1 ] ) ; PrintTab( ArgVf 2 ], "a",

" Состояние таблицы:" ) ;

// Тестирование логарифмического поиска в таблице pStructFileOut = OpenFile( ArgV[ 2 ], "а", 190 ) ; fprintf( pStructFileOut,

"\n\n Тестирование логарифмического поиска \п" ) ; CloseFile( pStructFileOut, ArgV[ 2 ], 200 ) ; LogariphmSearch ( "and", found, line ) ; PrintSearch ( ArgVf 2 ], "a", found, line, "and" ) ; LogariphmSearch ( "word", found, line ) ; PrintSearch( ArgV[ 2 ], "a", found, line, "word" ) ;

// Заполняем таблицу для хэш-поиска и печатаем ее BeginTable( ArgV[ 1 ], 2 ) ; PrintTab( ArgV[ 2 ], "a",

" Состояние таблицы:" ) ;

// Тестирование кэш-поиска в таблице pStructFileOut = OpenFile( ArgV[ 2 ], "а", 210 ) ; fprintf( pStructFileOut,

"\n\n Тестирование хэш-поиска \n" ) ; CloseFile( pStructFileOut, ArgVf 2 ], 220 ) ; HashSearch ( "work", found, line ) ; PrintSearchi ArgV[ 2 ], "a", found, line, "work" ) ; HashSearch( "type", found, line ) ; PrintSearch ( ArgVf 2 ], "a", found, line, "type" ) ;

// Освобождение динамической памяти, занятой таблицей FreeTableDMi ) ;

292

Page 294: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

retuxm 0; }

-_ Файл SearchTable.h. Включаемый файл для поиска в таблице. Давыдов В. Г. Консольное приложение^ Visual C-h+ 6

V // Предотвращение возможности многократного подключения // данного файла Hfndef SEARCHTABLE_H

^define SEARCHTABLE_H

^include <string.h> // Для строковых функций

// Для открытия-закрытия файлов #Include "OpenCloseFile.h"

const ±nt LKEY = 9; // Длина ключа в строке таблицы // LKEY-1

// Длина данного в строке таблицы LDATA-1 const ±nt LDATA = 63;

// Тип для строки таблицы stmict STRTAB {

// Ключ char кеу[ LKEY ]; // Данные char data[ LDATA ];

} ;

// Объявление объектов с описателем класса хранения // внешний. Они доступны в других файлах проекта^ в // которых подключен данный файл extern STRTAB

*рТаЫе; // Адрес первой строки таблицы в // динамической памяти

extern ±nt size; // Размер таблицы // Указатель на структуру со сведениями о файле ввода extern FILE

*pStructFlleInp;

// Прототипы функций void AllocTableDM( int ); void. FreeTableDM( void ); void SeqInpTab ( char * ) ; void PrlntTab ( char *pFlleOut, chstr *, char *pHead ); void SequentlalSearch ( char [ ], int <5, int & ); void PrlntSearch(,char *, char *, int, int, char [ ] ); void Round( int ); void InpTabLog ( char * ); void LogarlphmSearch ( char [ ], int &, int & ); int Kod( char );

293

Page 295: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt Hash ( chstr [ ] ) ; void. BeglnTable ( сЬлг *, ±nt ) ; void. HashSearch ( сЪах: [ ], inb &, inb & ) ,

§endif

Файл OpenCloseFile.h. Включаемый файл для функций открытия и закрытия файлов.

Используются в любых программных проектах. Давыдов В.Г. Консольное приложение,. Visual Сч-+ 6

// Предотвращение возможности многократного подключения // данного файла i^lfndef OPENCLOSEFILE_H

^define OPENCLOSEFILE_H

^Include <stdlo.h> // Для ввода-вывода ilnclude <stdllb.h> // Для функции exit ( )

// Прототипы функций FILE * OpenFlle ( char *, char *, inb ) ; void CloseFlle( FILE *, char *, int WarnNum ) ;

#endlf

Файл OpenCloseFile.cpp. Универсальные функции открытия и 3акрытия файлов.

Используются в любых программных проектах. Давыдов В.Г. Консольное приложение. Visual C++ 6

*/

// Включаемый файл ilnclude "OpenCloseFile.h"

// Открытие файла FILE * OpenFlle ( // Возвращает указатель на структуру

// со сведениями об открытом файле // Указатель на имя .расширение открываемого файла сЬаг *pFl 1 eNam е , cha.r *pMode, // Указатель на режим открытия файла // Номер ошибки или предупреждения int ErrWarnNum )

{ // Указатель на структуру со сведениями об открытом файле FILE *pStructFlle/

// Открытие файла pStructFlle = fopen ( pFlleName, pMode ) ; if( IpStructFlle ) {

294

Page 296: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

printf( "\n Ошибка %d. Ошибка открытия файла %s в режиме \"%s\"\п",

ErrWarnNum, pFileName, pMode ) ; exit ( ErrWarnNum )/

}

jc&tum pStructFile; )

// Закрытие файла void. CloseFile (

// Указатель на структуру со сведениями о закрываемом FILE *pStructFile, // Указатель на имя.расширение закрываемого файла char *pFileNaine^ Int WarnNum ) // Номер предупреждения

{ // Закрытие файла х£( fclose( pStructFile ) == EOF ) {

printf( "\n Предупреждение %d. Файл %s не закрыт. \n" "\n Выполнение программы продолжается \n",

WarnNum^ pFileName ); }

геЬгит; }

Файл SearchTable.cpp. Функции поиска в таблице:

* размещение таблицы в динамической памяти и ее инициа ЛИЗ а ция ; * освобождение динамической памяти, занятой таблицей/ * заполнение массива значениями, читаемыми из файла на магнитном диске (для последовательного поиска); * заполнение таблицы значениями, читаемыми из файла на магнитном диске (для логарифмического поиска); * вывод содержимого таблицы в файл на магнитном диске/ * последовательный поиск в таблице; * вывод результатов поиска в таблице в файл на магнитном диске; * обход вершин дерева с целью формирования словаря для бинарного (логарифмического) поиска; * бинарный (логарифмический) поиск в таблице, подготовленной в форме алфавитно-упорядоченного двоичного дерева/ * преобразование символа ключа ~ строчная латинская буква, цифра или пробел - в его порядковый номер (целое число) ; * хэш-функция ключа "KeyWord" из "LKEY-1" символа (символ -строчная латинская буква, цифра или пробел) для таблицы из size строк; * начальная подготовка хэш-таблицы;

295

Page 297: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

* поиск в хэш-таблице. Используется в программном проекте для поиска в таблице. Давыдов В.Г. Консольное приложение^ Visual C-h+ 6 I

JV i // Включаемый файл программного проекта для поиска в таблице ^include "SearchTable.h"

// Определения объектов с описателем класса хранения внешний. // Их объявление имеется в заголовочном файле проекта и // доступно в других файлах проекта STRTAB *рТаЫе; // Адрес первой строки таблицы в

// динамической памяти ±nt size; // Размер таблицы // Указатель на структуру со сведениями о файле ввода FILE *pStructFileInp;

// Размеш.ение таблицы в динамической памяти и ее // инициализация void AllocTableDM(

±nt s ) // Число строк таблицы {

// Проверяем г подходит ли размер таблицы? ±£( S < 1 ) {

printf( "\п Предупреждение 10. Таблица должна содержать не менее" " двух строк" "\п (задано %d строк) . Принимается размер таблицы 2. " "\п Выполнение программы продолжается. " ) ;

S = 2; }

// Размещаем таблицу в динамической памяти рТаЫе = new STRTAB[ s ] ; ±f( !рТаЫе ) {

printf( "\n Ошибка 20. Таблица не была размеш,ена " "в динамической памяти " ) ;

exit( 20 ) ; }

// Инициализация таблицы fori ±nt i = О; i < s; 1ч-+ ) {

pTablef i ] . key[ 0 ] = '\0'/ pTablel i ].data[ 0 ] = '\0';

} size = s;

jretujrn/

296

Page 298: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Освобождение динамической памяти, занятой таблицей void FreeTableDM( void ) {

xf( рТаЫе ) {

delete [ ] рТаЫе; рТаЫе = NULL; }

}

// Заполнение массива значениями, читаемыми из файла на // магнитном диске (для последовательного поиска) void SeqInpTab (

// Указатель на файл ввода сЬаг *pFlleInp )

{ // Открытие файла для чтения FILE *pStructFileInp = OpenFile ( pFilelnp,

"г", 30 ) ;

// Заполнение массива £ою ( ±nt 1=0; Ksize; i + + ) {

±f( fscanfi pStructFilelnp, " %s %s", pTablef i ].key, pTable[ i ].data ) != 2 )

{ printf( "\n Ошибка 40. Ошибка чтения строки^

" таблицы с индексом %d ", i ) ; exit( 40 ) ;

} }

// Закрытие файла ввода CloseFile( pStructFilelnp, pFilelnp, 50 ) ;

return/ }

// Заполнение таблицы значениями, читаемыми из файла на // магнитном диске (для логарифмического поиска) void InpТаbLog (

// Указатель на файл ввода сЬаг *pFileInp )

{ // Открытие файла для чтения

pStructFilelnp = OpenFile( pFilelnp, "г", 60 ) ;

Round( 1 ) ; // Рекурсивное заполнение таблицы

// Закрытие файла ввода

297

Page 299: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

CloseFile( pStructFilelnp, pFllelnp, 70 )

retvLrn; }

// Вывод содержимого таблицы в файл на магнитном диске void PrintTab(

char *pFlleOut,// Указатель на файл вывода char- *pMode^ // Указатель на режим открытия файла char "^pHead ) // Указатель на заголовок для печати

{ // Открытие файла для записи FILE *pStructFileOut = OpenFile ( pFileOut, pMode,

80 ) ;

// Печать таблицы с заголовком fprintf( pStructFileOut, "\п %s \п", pHead ) ; tor( ±nt 1=0; l<size; i++ ) {

fprintfC pStructFlleOutr "\n %-8s%-62s", pTable [ i ].key, pTable [ i J.data ) ;

}

// Закрытие файла вывода CloseFile( pStructFlleOut, pFileOut, 90 ) ;

return; }

// Последовательный поиск в таблице void SequentlalSearch (

// Ключ для поиска строки в таблице char Keyword [ ], ±nt &foundr // 1 - нашли ±пЬ &line ) // Индекс найденной строки

{ ±nt 1, // Индекс анализируемой строки

EndTab; // 1 - конец заполненной части // та блицы

// Подготовка к поиску found = О; EndTab = 0;

// Поиск 1 = 0; while ( .'found && /EndTab ) {

±£( Istrcmpi Keyword^ pTable [ 1 ] . key ) ) Щ { // Нашли

found = 1; line = 1; } else

298

Page 300: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Шаг вперед по таблице if( i == size-1 ) {

EndTab = 1/ ; else {

i + + / } }

}

retuim; }

// Вывод результатов поиска в таблице в файл на магнитном // диске void PrintSearch(

char *pFileOutг// Указатель на файл вывода char *pMode, // Указатель на режим открытия файла ±nt found, // 1 - нашли в таблице ±iib line г // Индекс строки в таблице // Ключевое слово для поиска char Keyword[ ] )

{ // Открытие файла для записи FILE *pStructFlleOut = OpenFile ( pFileOut, pMode,

100 ) ;

fprintf( pStructFlleOutr "\n Результаты поиска для" " ключевого слова: %s\n'\ KeyWord ) ;

±f( found ) (

fprintf( pStructFileOutr "Индекс строки в таблице: %d. Найденная строка: \ л " , line ) ;

fprintf( pStructFileOut, "%-8s%-62s \ л " , рТаЫе [ line J.key, рТаЫе [ line ] .data ) ;

} else {

fprintf( pStructFileOut, "Строка с ключом \"%s\" в" " таблице не найдена, \п", Keyword ) ;

}

// Закрытие файла вывода CloseFile( pStructFileOutг pFileOut, 110 ) ;

return/ }

// Обход вершин дерева с целью формирования словаря для // бинарного (логарифмического) поиска, !!! Читаемые данные

299

Page 301: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// должны быть ал фа витно-упорядоченными по ключам void Round(

±nt root ) // Корень дерева {

±£( size < root ) { // !!! Выход из рекурсии

}

Round( 2*root ); // Обойти вершины левого поддерева

// Приписать корню очередную по алфавиту строку таблицы ±f( fscanf( pStructFilelnp, "%8s%62s",

рТаЫе[ root-1 ].key, рТаЫе[ root-1 ],data ) ! =2 ) {

printf( "\n Ошибка 120. Ошибка чтения строки таблицы \п" );

exit ( 120 ); }

Round( 2*root+l ); // Обойти вершины правого поддерева

return/

// Бинарный (логарифмический) поиск в таблице, подготовленной // в форме алфавитно-упорядоченного двоичного дерева void. LogariphmSearch (

// Ключ для поиска строки в таблице сЪаг Keyword [ ] , ±nt &found, // 1 - нашли ±Tib Scline ) // Индекс найденной строки

{ int i, // Индекс вершины дерева

EndTab; // 1 - достигнут конец таблицы

// Подготовка к поиску found = 0; EndTab = 0;

// Поиск i = 1; while ( ! found && .'EndTab ) {

±f( !strcmp( Keyword, pTable[ i-1 ] . key ) ) { //Нашли

found = 1; line = i-1; } else { //Шаг вперед по таблице

±f( strcmp( Keyword, pTable [ i-1 ] , key ) < 0 ) {

i = 2*i/ }

300

Page 302: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

else {

} EndTab = (i > size ) ,

1 = 2*i-i-l/

return;

// Преобразование символа ключа - строчная латинская буква, // цифра или пробел - в его порядковый номер (целое число)

// Порядковый номер символа // Преобразуемый символ

±п\

(

t Kod( char

switch ( S3 {

case case case case case case case case case case case case case case case case case case case case case case case case case case case case case case case case case case

symbol )

/mbol )

' a ': 'b' : 'c' : d' :

'e' : f : g' : h'; 'i ': J'.• ;c' : 1 ' : m ': n ': o' : p': q': r' : s' : t': u': V* : w' : X' : y ' : z ': 0' : 1 ': 2': 3' : 4 ' : 5' : 6' : 7':

return return return return return return return return return return return return return return return return return return return return return return return return return return return return return return return return return return

// //

0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33.

301

Page 303: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

case '8*: return 34; case '9': return 35; case ' ': return 36;

dLefaul t: printf(

"\n Ошибка 130. Ключ поиска содержит недопустимый символ. \п" "\п Ключ может содержать толька строчные латинские буквы, " " цифры и пробел \п" ) ;

exit ( 130 ) ; }

return -1; // Этот оператор не будет // выполняться

}

// Хэш-функция ключа "KeyWord" из "LKEY-1" символа (символ -// строчная латинская буква, цифра или пробел) для таблицы // из size строк int Hash( // Возвращает индекс строки таблицы

// Ключ char Keyword [ ] )

{ unsigned. ±nt

I Key; // Индекс символа в ключе ±nt ih = 0; // Значение хэш-функции

// Вычисление индекса строки таблицы for( IKey = 0; I Key < strlen ( KeyWord ) ; IKey-h-h ) {

ih = ih * 37 -f Kod( KeyWordf IKey ] ) ; ih = ih % size;

}

return ih; }

// Начальная подготовка хэш-таблицы void BeginTable (

char *pFileInp,// Указатель на файл ввода int ТаЫеЬеп ) / / Число вводимых строк

{ int i , // Индекс строки таблицы

line, // Номер текуш,ей строки found; // 1 - найдена позиция вставки

// Заносимое слово char Keyword [ LKEY ];

// Инициализация таблицы нуль-символ а ми £ог( i = О; i < size; i++ ) {

£or( int il = 0; il < LKEY; il++ )

302

Page 304: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

рТаЫе[ i ] . key [ 11 ] = '\0'; fox:( int 12 = 0; 12 < LDATA; 12 + + )

pTablef 1 ] .data[ 12 ] = '\0'; }

// Отметка строк таблицы как свободных for( 1 = О; 1 < size; 1++ ) (

pTablef 1 J.keyf О ] ^ ' '/ }

// Открытие файла для чтения pStructFllelnp == OpenFlle( pFllelnpr "г", 140 ) ;

// Занесение в таблицу исходных строк for( line = 0; line < TableLen; llne++ ) { // Цикл чтения исходных строк

±f( fscanf( pStructFllelnp, " %s", Keyword ) != 1 ) {

printf( "\n Ошибка 150. Ошибка чтения ключа из" " строки с индексом %d \п", line ) ;

}

// Поиск индекса '1' строки таблицы для ее заполнения found = О; // Пока индекс не найден while( /found ) {

±f( pTablef 1 ].key[ 0 ] == ' ' ) { // Индекс найден

found - 1; } else { // Конфликт - шаг по таблице

1++; 1 = ( 1 > ( slze-1 ) ? 0: 1 ) ; }

}

// Чтение данного ±f( fscanf( pStructFllelnp, " %s",

pTablef 1 ] .data ) != 1 ) {

printf( "\n Ошибка 160. Ошибка чтения ключа из" " строки с индексом %d \л", line ) /

exlt ( 160 ) ; }

// Занесение ключа в строку "1" таблицы strcpyi pTablef 1 ].key, KeyWord ) /

// Закрытие файла ввода CloseFlle( pStructFllelnp, pFllelnp, 170 ) ;

геЬмтсп;

303

Page 305: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

}

// Поиск В хэш-таблице void HashSearch(

// Ключевое слово для поиска char Keyword [ ], ±nt бе founds // 1 - нашли ±nt &11пе ) // Индекс найденной строки в таблице

{ ±nt 1, // Индекс строки таблицы

EndTah; // 1 - достигли свободной строки

// Подготовка к поиску found = О; EndTab = О; i = Hash( KeyWord ) ;

// Поиск в таблице while ( ( .'found ) && ( .'EndTab ) ) {

±f( pTablel 1 J.keyf 0 ] == ' ' ) { // Достигли свободной строки

EndTab = 1; }

else {

±£( ! strcmp ( pTablef 1 ] . key, KeyWord ) ) { // Нашли

found = 1; line = i; } else { // Шаг no таблице

i++; i = ( i > ( size-1 ) ? 0: i ) ; }

} }

retujni/

Для файла исходных данных, имеющего вид

call вызов type тип word слово work работа

был получен следующий файл результатов:

Состояние та блицы:

call type word work

вызов тип слово работа

304

Page 306: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Тестирование последовательного поиска

Результаты поиска для ключевого слова: and Строка с ключом "and" в таблице не найдена.

Результаты поиска для ключевого слова: word Индекс строки в таблице: 2. Найденная строка: word слово

Состояние та блицы:

word слово type тип work работа call вызов

Тестирование логарифмического поиска

Результаты поиска для ключевого слова: and Строка с ключом "and" в таблице не найдена.

.Результаты поиска для ключевого слова: word Индекс строки в таблице: О. Найденная строка: word слово

Со стояние та блицы:

call вызов

type тип

Тестирование хэш-поиска

Результаты поиска для ключевого слова: work Строка с ключом "work" в таблице не найдена.

Результаты поиска для ключевого слова: type Индекс строки в таблице: 2. Найденная строка: type тип

17.3. Логарифмический (бинарный) поиск

Кардинальное повышение эффективности поиска в таблице достигается полным пересмотром алгоритма поиска аналогично то­му, как это было ранее в сложных алгоритмах сортировки массивов.

Пример. В реальных словарях, например в англо-русском сло­варе, человек быстро находит нужные сведения, используя их упо­рядоченность по алфавиту. Указанный подход и использован при

305

Page 307: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

логарифмическом (с помощью двоичного дерева) поиске в таблице. Если исходную таблицу (словарь) предварительно подготовить

в форме двоичного дерева так, чтобы ключи левого поддерева были раньше по алфавиту, чем ключ корня, а ключи правого поддерева -позже, то число обращений к таблице для сравнения с заданным ключевым словом не может превышать log^isize). При этом, после каждого обращения к таблице, область поиска сокращается в общем случае примерно в два раза.

Начальная подготовка таблицы в форме двоичного дерева. Исходные данные при заполнении таблицы перед чтением должны быть упорядочены по алфавиту для ключевых слов. Это легко обес­печить, используя рассмотренные выше способы сортировки масси­вов.

Пусть, например, читаемые данные содержат в каждой отдель­ной строке информацию для заполнения одной строки таблицы, причем они отсортированы по ключам по не убыванию (size=\0):

А В С D Е F G Н I J

Data Data Data Data Data Data Data Data Data Data

for for for for for for for for for for

string string string string string string string string string string

0 1 2 3 4 5 6 7 8 9 (size-1)

Двоичное дерево строится, как это было ранее рассмотрено, с использованием рекуррентного подхода (см. рис. 57). Для нашего примера после начальной подготовки двоичное дерево должно иметь вид, показанный на рис. 98.

Соответственно этому алгоритму можно записать рекуррент­ную функцию Round и использующую ее функцию InpTabLog для начального заполнения и подготовки таблицы, прототипы и опреде­ления которых содержатся в вышеприведенной программе. Обрати­те внимание на то, что функция Round является вспомогательной для функции InpTabLog.

Бинарный (логарифмический) поиск в таблице, подготов­ленной в форме двоичного дерева. Идея поиска состоит в следую­щем.

1. Исходный ключ сравнивается с ключом, соответствующим корню дерева (номер соответствующей вершины дерева / = 1, а ин­декс элемента массива - (/ - 1) ). Если при этом ключи совпадают, то нужная строка найдена {found = 1), ее индекс line = i - 1 и поиск за-

306

Page 308: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

вершен.

/ А

} В

Л-с

D

2 ]\

F

-V Е

10

/ н

/ 1

3

J

Последние слова по алфавиту (H-I-J) присваиваются правому поддереву Первые слова по

алфавиту (A-B-C-D-E-F) присваиваются левому поддереву

Среднее слово по алфавиту (G) присваивается корню.

Аналогично заполняются левые и правые поддеревья для частичных корней

Рис. 98. Двоичное дерево после начального заполнения

2. Если поиск не завершен, то определяется поддерево для продолжения поиска путем сравнения KeyWord < рТаЫо[ /-1 ].кеу. При положительном итоге необходимо вести поиск в левом подде­реве и номер следующей вершины / = /*2. При выполнении же про­тивоположного условия KeyWord > рТаЫе[ /-1 ].кеу, поиск следует вести в правом поддереве и номер следующей вершины дерева / = /*2+1.

3. При выполнении условия i > size (вершину / дерево не со­держит) поиск следует прекратить, так как строка с ключевым сло­вом KeyWord отсутствует {EndTab = 1 w found = 0). Иначе - выполня­ется переход к п. 1 с новым значением /, соответствующим корню левого или правого поддерева.

Легко заметить, что после каждого сравнения KeyWord с клю­чом рТаЫе[ /-1 ].кеу область поиска сокращается примерно в два раза и среднее число обращений к таблице (средняя длина поиска) составляет 1^^.,, =\og^{size), что существенно эффективнее, чем при по­следовательном поиске.

307

Page 309: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в соответствии со сказанным, прототип, определение функции LogariphmSearch и пример ее вызова имеют вид, показанный в про­грамме из подразд. 17.2.

17.4. Поиск с использованием перемешанной таблицы (хэш-таблицы)

при поиске с использованием хэш-таблицы используется ор­ганизация данных в виде массива. Основная идея поиска состоит в преобразовании заданного ключа Key Word в индекс Hash( Key Word ) соответствующей строки в таблице. Поэтому такой способ поиска иногда называют поиском с преобразованием ключей (рис. 99).

рТаЫе

Keyword

Исходный ключ

Hash(KeyWord) Индекс строки в таблице с key = Keyword

Size-1

Ключ (key) Данное (data) Рис. 99. Хэш-поиск в таблице

Основная трудность преобразования ключей состоит в том, что множество возможных значений ключей намного обширнее, чем множество индексов строк в таблице. Так, например, если ключ со­держит восемь символов, в качестве которых используются строч­ные латинские буквы, цифры и пробел (всего 37 возможных значе­ний каждого символа в ключе), то всего имеется 37^ возможных значений ключей, что, естественно, во много раз превышает реаль­ный размер таблицы size. Из сказанного следует, что функция Hash является отображением "много в один".

Идея поиска с использованием хэил-таблицы состоит в сле­дующем. Первый этап в операции поиска - вычисление соответст­вующего индекса Hash{ KeyWord ) в таблице, а второй - очевидно необходимый этап - проверка, действительно ли элемент с ключом KeyWord находится в таблице в строке с индексом Hash( KeyWord ).

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

308

Page 310: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1. Какую функцию Hash( KeyWord ) следует использовать? 2. Как поступать в ситуации, когда функция Hash{ KeyWord )

не дает местонахождения нужного элемента (! много ключей дают одинаковый индекс)?

Ответ на второй вопрос заключается в том, что нужно исполь­зовать какой-то метод для получения нового индекса в таблице, а если и там нет нужного элемента, то следующего индекса и т.д. По­добный случай, когда в строке Hash{ KeyWord ) находится другой ключ, а не ключ KeyWord^ называется конфликтом, а задача получе­ния альтернативных индексов li^зыв2iQTCЯ разрешением конфликтов.

Выбор функции преобразования. Основное требование к хо­рошей функции преобразования Hash{ KeyWord ) состоит в том, чтобы она распределяла ключи как можно более равномерно по шкале значений индексов. Разумеется, она должна также эффектив­но вычисляться, т.е. состоять из очень небольшого числа основных арифметических действий.

Пусть ih определяет порядковый номер ключевого слова Key-Word во множестве всех возможных значений ключей и вычисляется следующим образом:

unsigned. ±nt I Key; // Индекс символа в ключе

Int ih = О; // Значение хэш-функции

// Вычисление индекса строки таблицы for( IKey = 0; I Key < strlen ( KeyWord ) ; IKey-h-h ) {

ih = ih * 37 + Kod( KeyWord[ IKey ] ) ; } ih = ih % size;

В результате вычислений ih получает значение из диапазона 0-36. К сожалению, величина ih существенно превышает максимально допустимое целое значение (2'^-1 или 2^'~1). По этой причине функцию Hash{ KeyWord ) следует построить несколько иначе — вы­числение

ih = ih % size;

перенести в блок цикла. Прототип полученной таким образом хэш-функции и ее определение приведены в примере программы в под-разд. 17.2. Функция Hash{ KeyWord ) также является вспомогатель­ной и используется при хэш-поиске. Эта функция обладает тем свойством, что значения ключей равномерно распределяются во всем интервале индексов строк таблицы. Исследованиями показано, что для большей равномерности распределения желательно, чтобы

309

Page 311: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

size было простым числом (см. Вирт Н., Алгоритмы + структуры данных = программы: Пер. с англ. М.: Мир, 1985. С. 305).

Разрешение конфликтов. Если строка в таблице рТаЫе, соот­ветствующая заданному ключу Key Word, не содержит нужный эле­мент, то имеет место конфликт. Это означает, что два или более элементов таблицы имеют ключи, отображающиеся в один и тот же хэш-индекс строки таблицы. Для разрешения конфликтов такого ро­да существуют различные методы получения вторичных индексов.

Один из методов разрешения конфликтов состоит в просмотре одного за другим различных элементов таблицы, начиная со строки с индексом Hash( Key Word ), пока не будет найден нужный элемент или не встретится свободное, не заполненное место таблицы. По­следнее означает отсутствие в таблице строки с заданным ключом. Этот метод называется открытой адресацией. Разумеется, что шаг просмотра элементов таблицы при вторичных пробах должен быть постоянным. Одним из таких методов является метод линейного ап­робирования с открытой адресацией. Реализация этого метода со­держится в определении функции HashSearch.

Отметка в таблице свободных мест. Для этой цели можно, например, в первый символ ключа (байт) свободной строки таблицы записать символ пробела:

/ / Отметка строк таблицы как свободных fox:( 1 = О/ i < size/ i-h+ ) (

рТаЫе[ i ] . key [ О ] = ' '; I

Начальная подготовка хэш-таблицы. При начальном запол­нении хэш-таблицы также может иметь место конфликт. В связи с этим сделаем валсное замечание. При хэш-поиске и при начальной подготовке таблицы для разрешения конфликта следует использо­вать один и тот эюе метод. В нашем примере таким методом явля­ется метод линейного апробирования с открытой адресацией. Про­тотип функции BeginTable, используемой для начального заполне­ния хэш-таблицы, и ее определение имеются в примере, приведен­ном в подразд. 17.2. Функция BeginTable является интерфейсной функцией.

Функция для поиска в хэш-таблице. Прототип функции HashSearch и ее определение имеются в примере, приведенном вы­ше в подразд. 17.2. Функция HashSearch также является интерфейс­ной функцией. Обратите внимание на то, что функции BeginTable и

310

Page 312: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

HashSearch очень похожи друг на друга. Пример тестирования хэш-поиска в таблице имеется в под-

разд. 17.2 (см. функцию main).

Эффективность хэш-поиска. Проведенный для линейного апробирования анализ показал, что среднее значение числа проб при поиске (длина поиска)

WP ~ \-al2

\-а '

где a = TabLen/size есть коэффициент заполненности таблицы (табл. 30).

а L,,,

Табл. 0.1

1.056

30. Эффективность хэш 0.2

1.125

0.3

1.214

-поиска 0.5

1.5

0.9

5.5

Из таблицы следует, что хэш-поиск имеет весьма высокую эф­фективность. Но при этом важно понимать и недостатки данного метода.

1. Существенное повышение эффективности поиска достига­ется только при большой избыточности таблицы.

2. Сложность удаления элемента из таблицы.

В заключение отметим, что из перечисленных выше классиче­ских задач прикладного программирования^ составляющих золотой багаж любого программиста - сортировка массивов, транспортная задача (задача коммивояжера), поиск в таблице, обработка списков, работа с очередями; сортировка файлов ) — мы рассмотрели решение первых трех классов задач прикладного программирования. Осталь­ные задачи будут рассмотрены в следующем учебном пособии "Тех­нология программирования" в связи с изучением и освоейием дру­гих технологий программирования, таких как объектно-ориентированное программирование, программирование с исполь­зованием библиотеки стандартных классов языка C++ и др. Учебное пособие "Технология программирования" предназначено для обес­печения одноименной дисциплины, изучаемой в следующем, треть­ем семестре в рамках подготовки бакалавров (направление 5502) и специалистов (направление 6519).

ЗП

Page 313: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

18- ОТВЕТЫ И РЕШЕНИЯ К УПРАЖНЕНИЯМ ДЛЯ САМОПРОВЕРКИ

18.1. Для подраздела 2.4.4

Ответ к упражнению 1.

retcode^l 1=17 j=123 с1=4 с2=5 сЗ=6 а=2400,000000 Ъ=172,000000

Ответ к упражнению 2.

Файл 2_4_4_2.СРР

2. Имеется следующий фрагмент Си-программы:

float ±nt cbSLr ±nt ch^jc

a; i^ jr cl, c2r c3; retcode; c4, c5r s[20]

Написать фрагмент программыг обеспечивающий чтение из файлаf.dat на магнитном диске следующих значений:

а = 1,5 1 = 21 j = -12 с1 = 'в' с2 = ' е ' сЗ = ' с ' с4 ^ 'а' с5 = 'н ' S = "Прочитанная-строка"

Как при этом будут выглядеть строки исходных данных в файле f.dat?

Предусмотреть контроль корректности значений^ возвращае­мых функциями библиотеки Си.

В. Давыдов^ консольное приложение (Microsoft Visual Studio C++ 6.О) */

#include <stdio.h> // STanDart Input Output - для // стандартных функций ввода-// вывода

±nt main ( void ) // Возвращает О при успехе {

float а; Int i г j / chctr cl, c2, c3; char c4, c5^ s[20];

312

Page 314: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

FILE *f_in; // Указатель на файл для ввода ±пЬ ret code; // Возвращаемое значение для функции

// fscanf

// Открываем файл f.dat для чтения f_±n = fopen( "f.dat", "г" ); ±f( f__in == NULL ) {

printf( "\n Файл f.dat для чтения не открыт. " ); jzebvucn 1;

}

// Чтение данных из файла f.dat retcode = fscanf( f_±n, " a = %f 1 = %d j = %d "

"cl = \'%c\' c2 = \'%c\' c3 = \'%c\' " "c4 ^ \'%c\' c5 = \*%c\^ S = \"%s\" ", &a, &i, &j, &cl, &c2, &c3, &c4, &c5, s );

if( retcode != 9 ) {

printf( "\n Данные прочитаны с ошибками." ); retvim 2;

}

// Закрываем файл ввода fclose ( f_in ) ;

z-etiLm О;

Ответ к упражнению 3.

Файл 2_4_4_З.СРР

3. В программе имеются следующие переменные:

±nt d = 254/ float f = 1234.56; cha.r *str = "Строка символов"/

Используя, по возможности, только эти данные написать программу, выводящую в файл результатов file.out следующие строки (в них символ ^ обозначает местоположение пробела) :

1+254^''^^^''^]^'^[^^''''^254] (^^^^^1234.5600) ^^ (1234.5600^^'^^^) /Стр/^^/м/

В. Давыдов, консольное приложение (Microsoft Visual Studio C++ 6. 0)

^Include <stdlo.h> // STanDart Input Output - для // стандартных функций ввода-

313

Page 315: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// вывода

±nt main ( void ) // Возвращает О при успехе {

int d = 254; float f = 1234.56f; cha,r *str = "Строка символов"/ FILE *f_out; // Указатель на файл для взвода

// Открываем файл file,out для записи f_out = fopen( "file.out"г "w" ) ; ±f( f_out == NULL ) {

printf ( "\n Файл file, out для записи не открыт. " ) . return 1;

}

// Вывод данных в файл file.out fprintf( f_out, "[%+-lld],%2c[%8d] \n (%14. 4f) %2c("

"%-14.4f)\n/%.3s/%2c/%c/\n ", d, str[ 6 ], d, f, str[ 6 ; , f, str, strf 6 ]r str[ 9 ] ) ;

// Закрываем файл взвода fclose ( f_out ) ;

return 0;

18.2. Для подраздела 3.8

Ответ к упражнению 1.

Будет напечатано:

i=l j=3 next ( )=11 last ( )=0 nw(i+j) =9

Ответ к упражнению 2.

Будет напечатано:

i == 3 j = 1 next ( i ) = 3 last ( i ) =10

i = 3 j ^ 2 next ( i ) = 4 last ( i ) = 9

314

Page 316: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

18.3. Для подраздела 3.9.3

Ответ купрамснению 1,

Фа ил 3_ 9_3_1. срр

Написать прототип^ определение функции и пример вызова функции для вычисления суммы элементов одномерного массива х[ N ] (N = 50) целого типа ^ имеющих нечетные индексы

В. Давыдов, Консольное приложение (Microsoft Visual Studio C++ 6.О) V ^include <stdio.h> // Для ввода-вывода

^define N50 // Размер массива

// Прототип ±nt SumUneven( ±nt ar[ ] ) ;

±nt main ( void ) // Возвращает 0 при успехе {

Int a[ N ] ;

// Инициализация массива toj: ( ±zib i=0/ i<N; i + + )

a[ i ] ^ 1;

// Вызов функции ±nt s = SumUneven( a ) ;

// Печать найденной суммы printf( " Сумма значений элементов массива с нечетными "

"индексами = %d \л", s ) ;

x-etujm 0; }

// Вычисление суммы значений элементов вектора с нечетными // индексами Int SumUneven ( // Возвращает сумму значений

// элементов с нечетными // индексами

±пЬ аг[ ] ) // Обрабатываемый массив {

int Sum = 0; // Искомая сумма

// Поиск суммы £ог( Int i=l; i<N; i+=2 )

315

Page 317: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Sum += ar[ i ]/

return Sum/ }

Ответ к упражнению 2.

Файл 3_9_3_2. срр

Написать прототип, определение функции и пример вызова функции для получения одномерного массива z[ N ] (N = 40) из двух заданных массивов целого типа х[ N ], у[ N ] по правилу:

z[ i ] := тах{ х[ ± ], у[ ± ] }

В. Давыдов. Консольное приложение (Microsoft Visual Studio C+-h 6, О)

*/

^include <stdio.h> // Для ввода-вывода

^define N40 // Размер массивов

// Прототип void. CreateArr ( Int x[ ], int y[ 7, Int z[ ] ) ; int main ( void ) // Возвращает 0 при успехе {

±nt x[N],y[N],z[N];

// Инициализация исходных массивов £or( int i=--0; i<N; i + + ) {

x[ i ] = 1; y[ i ] = 0; }

// Вызов функции CreateArr( x, y, z ) ;

retujm 0; }

// Формирование массива void CreateArr(

int X [ 7, // Исходные int y[ ], // массивы int z[ ] ) // Формируемый массив

{ // Формирование массива for( int i=0; i<N; i+=2 ) {

if( x[ i ]>y[ i 7 ; z[ i ] = x[ i ];

316

Page 318: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

else z[ i ] = y[ ± ];

}

18.4. Для подраздела 3.9.6

Ответ к упражнению 1,

/* Файл 3_9_3_2, срр

1. В текстовом файле "ctrl4, dat" имеется 15 строк, каждая из которых имеет следующий формат:

число_ 1 число_ 2 Здесь "число_1" определяет вид геометрической фигуры (1 -

квадрат, 2 - круг) , а "число_2" - параметр фигуры (при "чис-ло_1" = 1 ~ длина стороны, а при "число_2" = 2 - радиус) .

1.1. Написать определение массива структур для хранения указанных сведений о геометрических фигурах. Каждый элемент массива должен иметь следующие поля: * имя фигуры; * длина стороны или радиус; * площадь фигуры.

1.2. Написать фрагмент программы для чтения из файла на магнитном диске "ctг14.dat" информации о геометрических фигу­рах.

1.3. Написать фрагмент программы, вычисляющий площади геометрических фигур.

1.4. Написать фрагмент программы, печатающий в файл "ctrl4.out" параметры геометрических фигур. Сведения об от­дельных фигурах располагаются в отдельной строке и имеют вид:

круг: радиус= . . . , площадь= . . . или

квадрат: длина стороны= . . . , площадь= . . . Предусмотреть контроль корректности значений, возвращае­

мых функциями библиотеки Си, указать какие включаемые файлы требует представленный фрагмент.

В. Давыдов. Консольное приложение (Microsoft Visual Studio C++ 6.0) V ^include <stdlo.h> // Для ввода-вывода ^include <string.h> // Для строковых функций

#define N15 // Размер массива структур

int main ( void ) // Возвращает О при успехе (

317

Page 319: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Определение массива фигур sbiract GeomFigure {

cbai: name [ 8 ];// Название фигуры double pa ram; // Параметр фигуры: длина стороны

// или радиус double square/ // Площадь фигуры

) агг[ N ]; // Массив геометрических фигур

// Заполнение массива структур со сведениями о // геометрических фигурах и вычисление их площадей

FILE *f__ln; // Указатель на файл для ввода // Открываем файл ctrl4,dat для чтения f__in = fopen( "ctrl4,dat", "г" ) ; ±£( f_±n == NULL ) {

print f ( "\n Файл ctrl4. dat для чтения не открыт. " ) ; jretuxn 1;

}

±zib Tag; // 1 - квадрат^ 2 - круг double pa ram; // Параметр фигуры for( ±nt 1=0; KN; 1 ++) {

±f( fscanf( f_ln, " %d %lf", &Tag, &param ) != 2 ) {

printf( "\n Ошибка чтения " ) ; return 2;

} switch ( Tag ) { ca.se 1 :

strcpy ( arr[ i J.name, "Квадрат" ) ; arr[ 1 ] .pa ram = pa ram; arr[ 1 ]. square = pa ram "&param; break;

case 2: strcpy( arr[ 1 J.name^ "Круг" ) ; arr[ 1 ].pa ram = pa ram; arr[ 1 ].square = 3.141592*param*param; break;

default: return 3;

} } // Закрываем файл чтения fclose ( f_in ) ; // Печать сведений о геометрических фигурах

FILE *f_out; // Указатель на файл для вывода // Открываем файл ctrl4.out для записи f_out = fopen( "ctrl4.out", "w" ) ; lf( f out == NULL )

318

Page 320: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

prlntf ( "\n Файл ctrl4. out для записи не открыт. " ) ; jretujrn 4;

} fo3z( 1=0/ KN/ 1++) {

±£( !strcmp ( arrf 1 ] . name, "Квадрат" ) ) {

fprlntf( f_out, "\n Квадрат: длина стороны=%1д, " "площадь = %1д " , arr[ 1 ] ,param, arrf 1 ]. square ) ;

} else {

fprlntf( f_out, "\n круг: радиус=%1д, " "площадь=%1д " , arr[ 1 ] .param, arr[ 1 ]. square ) ;

// Закрываем файл вывода fclose ( f_out ) ;

retuim 0;

18.5. Для подраздела 4.12

Ответ к упражнению 1 (рис. 100).

Нет

Нет

Рис. 100. Ответ к упражнению 1

Ответ к упралснению 2.

±f( a<=b )

к=п;

319

Page 321: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

r=l; } else

r=3;

Ответ к упражнению 3.

swi tab( 1 ) { case 4:

n-h+ ; break;

case 1: case 7; case 9: n=a+b; break;

de£ault: n=a-b;

Ответ к упражнению 4.

Будет напечатано: •к

-- О - - -1 — -2 -- -3 - - -4

Ответ к упражнению 5. /*

Файл 4__12_5. срр

5. Пусть определен массив int а[ 25 ];

Напишите фрагмент Си-программы, который напечатает с но­вой строки значения элементов "а" по 5 элементов в строке и по 10 позиций на элемент. Решить задачу с помош^ю цикла while.

В. Давыдов. Консольное приложение (Microsoft Visual Stu­dio C++ 6. 0) */

^include <stdio.h> // Для ввода-вывода

Int main ( vo±dL ) // Возвращает 0 при успехе {

int a[ 25 ];

// Инициализация массива for( int i=0; i<25; i + + )

320

Page 322: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

{ а[ i ] = 1;

}

// Печать массива ± = О; while( i<25 ) {

±f( !( 1%5 ) ) printf ( "\п" ) ;

printf( "%10d", a[ ± ] ) ; i + + ;

} printf ( "\n" ) ;

z-etux-n 0;

Ответ к упражнению 6.

При любых исходных значениях "А:" цикл будет выполняться конечное число раз.

18.6. Для подраздела 6.9

Ответ к упражнению 7.

Будет напечатано:

10 13 16 15 13 11

Ответ купрамснению 2.

Будет напечатано:

рр-р рр-р рр-р рр-р

= = - • =

=

4 3 4 4

*рр-а -*рр-а = *рр-а = *рр-а =

4 3 4 3

**рр **рр **рр **рр

= 14 = 13 = 14 = 13

18.7. Для подраздела 8.16

Ответ к упражнениям 1-3.

Файл LS.CPP

321

Page 323: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

struct ELEM {

Int ELEM

}

dat; *next; *start

Определены следующие данные:

// Структура для элемента списка

// Данное // Указатель на следующей элемент // Указатель на начало списка

Во входном файле Is,dat содержится некоторое количество целых чисел г разделенных символами пробельной группы ( ' ', '\t', '\л ' ; .

1. Написать прототип, определение и пример вызова функции для ввода из входного файла имеющихся там чисел, представив введенную информацию линейным списком, в котором каждый узел (динамически размещенная структура) содержит две компоненты.

Первая компонента хранит данное (введенное число), а вторая -указывает адрес следующей структуры. При этом первое прочи­танное число должно находиться в начале линейного списка. Ис­ходные данные и результаты работы функции следует передавать через список параметров,

С целью обработки ошибок предусмотреть контроль значений, возвращаемых функциями библиотеки языков Си/C++,

2. Дополнительно написать прототип, определение и пример вызова функции для печати в файл ks.out на магнитном диске содержимого линейного списка, Требования к оформлению функции и обработке ошибок аналогичны указанным выше требованиям,

3. Дополнительно написать прототип, определение и пример вызова функции, которая разрушает линейный список. Требования к оформлению функции и обработке ошибок аналогичны указанным в пункте 1 требованиям,

В. Давыдов, Консольное приложение (Microsoft Visual Studio C++ 6,0) */

^Include <stdlo.h> // Для функций ввода-вывода ^Include <stdllb,h> // Для функции exit

stmict EL // Структура для элемента списка {

int dat; // Данные (целое) EL *next; // Указатель на следующий элемент

} ;

// Прототипы функций void Create__beg ( EL *&, char * ) ; void Add_end( EL *&, int ) ; \ void Prlnt_ls ( EL *, char *, char * ) / void Dest_ls( EL *& ) ; void Del_beg( EL *& ) ;

int main ( void ) // Возвращает 0 при успехе {

// Указатель на начало списка

322

Page 324: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

EL *start;

start = NULL/ // Инициализация списка // Заполнение линейного списка символами из файла LS.DAT: // первый прочитанный символ - в начале списка Create_beg( starts "LS.DAT" ); // Вывод содержимого списка в файл Print_ls( start, "LS.OUT"r "w"); Dest_ls ( start ) ; // Разрушение списка

jretixzm 0; }

// Заполнение линейного списка символами из файла LS.DAT: // первый прочитанный символ - в начале списка void. Crea te_beg (

EL *&start, // Указатель на начало списка // Указатель на файл ввода cbstr *pFlleInp )

{ // Данное для элемента, добавляемого в конец списка Int 1 / // Указатель на структуру со сведениями о файле для // чтения FILE *f_±n;

// Открываем файл для чтения ±£( ( f_in = fopen( pFllelnp, "г" ) ) == NULL ) {

printf ( "\n Файл %s для чтения не открыт \ л " , pFilelnp ) ;

e x i t ( l ) ; } ±nt re;

// Указатель на файл ввода // Создаем список wb±le( ( ГС = fscanfC f_in, " %d ", &i ) ) == 1 ) {

Add_end( start, i ); } // Закрываем файл ±£( ( fclose( f_in ) ) =- EOF ) {

printf( "\n Файл %s не закрыт \ л " , pFilelnp ); e x i t ( 2 ) ;

}

jretixzm/ ;

// Добавление элемента в конец списка void Add_end(

EL *&start, // указатель на начало списка

323

Page 325: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

int i) // Данные добавляемого элемента

// Указатель на новый (добавляемый) элемент списка EL *temp,

*сиг; // Указатель на текущий элемент

temp = new EL; // 1: динамическое размещение // элемента

±f( temp == NULL ) {

printf( "\n Элемент списка не размещен \п" ) / exit ( 3 ) ;

} temp->dat = 1; //2: занесение данного temp->next = NULL; // 3: новый элемент является

// последним ±£( start == NULL ) // Новый список (пустой)

start = temp; // 4а: указатель на начало списка else {

// 46: проходим весь список от начала^ пока текущий // элемент не станет последним сиг = start; wb±le( cur->next != NULL )

// Продвижение по списку сиг = cur->next;

// 4в: ссылка последнего элемента на новый^ // добавляемый в конец списка cur->next = temp;

}

геЬизпл; }

// Печать содержимого списка на экран void Prlnt_ls (

EL *start, // Указатель на начало списка сЬа2Г *pFileOut,// Указатель на файл вывода char *pMode ) // Указатель на режим открытия файла

( EL *ргп; // Указатель на печатаемый элемент

±£( start == NULL ) {

printf ( "\п Список пуст. Распечатывать нечего \п" ) ; retu2m;

}

// Открываем файл вывода FILE *f_out = fopen( pFileOut, pMode ) ; ±f( !f__out ) {

printf ( "\n Ошибка открытия файла вывода &s \ л " , pFlleOut ) ;

324

Page 326: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

exit( 4 ) ; } prn = start; // Указатель на начало списка fprintf( f_out, "\л Состояние линейного списка: \п" ) / int i ^ О; while ( prn ! = NULL ) // До конца списка

{ if( ! ( i%4 ) )

fprintfi f_out, "\n" ) ; // Печать данных элемента fprintfi f_out, "%15d", prn->dat ) ; prn = prn->next; // Продвижение no списку

;

/ / Закрываем файл вывода fclose( f_out ) /

rBturzi/

}

//***********************************************************

/ / Разрушение списка void Dest_ls (

EL *&start ) // Указатель на начало списка

{ ±f( start == NULL ) {

printf( "\n Список пуст. Удалять нечего" ) ; return;

} while( start /= NULL )

Del_beg( start ) ; / / Удаление первого элемента списка

return;

}

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

325

Page 327: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Удаление первого элемента списка void Del_beg(

EL *&start ) // Указатель на начало списка {

EL *del; // Указатель на удаляемый элемент

±f( start == NULL ) {

printf ( "\n Список пуст. Удалять нечего" ) ; return/

} // 1: подготовка первого элемента для удаления del - start; start = del->next; // 2: start сдвигается на второй

// элемент delete del; // 1: удаление первого элемента

return;

Page 328: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ПРИЛОЖЕНИЯ

Приложение П.1. Тесты и программные проекты. Варианты заданий

П.1.1. Тесты (контрольные работы)

На практических занятиях по основным разделам курса целе­сообразно провести тестирование. Такими разделами являются:

• программирование на ПМ-ассемблере; Q ввод; о вывод; • простейшие ветвления; а циклы; а структуры; Q функции; о области действия определений; о массивы и указатели; о работа с динамической памятью и операции с линейным списком; а препроцессор, перечисления, функции с умалчиваемыми значениями ар-

гументов, перегрузка функций, шаблоны функций, перегрузка операций.

П.1.1.1. Программирование на ПМ-ассемблере. Варианты тестов

Изобразить схему программы и написать законченную программу на языке ПМ для решения заданной задачи. Для ввода и вывода использовать файлы MS DOS. Для обеспечения нагляд-ности вывода использовать строковые данные.

Вариант 1. Ввести и напечатать значения элементов массива целого типа с фиксированным размером 10 (для упрощения про­граммы размер массива вводить не нужно). Вычислить и напечатать среднее значение для множества отрицательных элементов массива. Постарайтесь не потерять в вычисленном среднем значении дроб­ную часть. Если массив не содержит элементов с отрицательными значениями, то в качестве ответа напечатать "В массиве нет отрица­тельных элементов".

Вариант 2. Ввести и напечатать значения элементов массива целого типа с фиксированным размером 8 (для упрощения размер массива вводить не нужно). Вычислить и напечатать значение наи-

327

Page 329: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

большего элемента массива.

Вариант 3. Ввести и напечатать значения элементов массива вещественного типа с размером 10. Вычислить и напечатать количе­ство отрицательных элементов массива.

Вариант 4, Ввести и напечатать значения элементов массива вещественного типа с размером 20. Вычислить и напечатать индекс наименьшего элемента массива.

Вариант 5. Ввести и напечатать значения элементов массива целого типа с размером 20. Вычислить и напечатать среднее ариф­метическое для элементов массива. Постарайтесь, чтобы дробная часть в результате не потерялась.

Вариант 6, Ввести и напечатать значение переменной х веще­ственного типа. Вычислить для нее восьмую степень и напечатать вычисленное значение. Решить задачу с использованием только трех умножений.

Вариант 7. Ввести и напечатать значения переменных а, 6, с вещественного типа. Определить наибольшее значение среди них, присвоить его переменной d и напечатать. Решить задачу с исполь­зованием только двух сравнений.

Вариант 8, Ввести и напечатать значения переменных а, Ь, с вещественного типа. Определить и напечатать, сколько среди них отличных от нуля.

Вариант 9, Ввести и напечатать значения переменных а, b це­лого типа. Определить, равны ли они друг другу, и напечатать ответ.

Вариант 10. Ввести и напечатать значения переменных а, b вещественного типа. Определить количество положительных значе­ний среди заданных и напечатать ответ.

Вариант 11, Ввести и напечатать значения переменной х ве­щественного типа. Вычислить и напечатать значение функции у := \х\.

Вариант 12. Ввести и напечатать значения переменных а, Ь, с, d вещественного типа. Определить и напечатать z := max( min( а, b ), max( с, d ) ) .

328

Page 330: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 13, Ввести и напечатать значения переменных х, у и Z вещественного типа. Вычислить и напечатать значения переменных и := т а х ( х, у, z ) , / : = min( х, у, z ).

Вариант 14. Ввести и напечатать значения переменных а, Ь, с, d вещественного типа. Сделать такую перестановку значений этих переменных, чтобы а приняло значение 6, b приняло значение с, с приняло значение а. Значения этих переменных после перестановки также напечатать.

Вариант 15. Ввести и напечатать значения переменных хну вещественного типа. Вычислить и напечатать значения перемен­ных и := т а х ( д:, у ) , / : = min( д:, у ).

Вариант 16. Ввести и напечатать значения переменных х, у^ z вещественного типа. Вычислить и напечатать целое/7 по правилу:

Р : = 1 при к = min { X^ у^ Z ) , 2 при у = min { X ^ у г Z ) , 3 при Z = m i n ( X, у г z )

Вариант 17. Ввести и напечатать значения переменных а, 6, с вещественного типа. Присвоить переменной а максимальное, а пе­ременной Ъ - минимальное из указанных значений. После этого на­печатать их значения.

Вариант 18. Ввести и напечатать значение х вещественного типа. Вычислить и напечатать значение у:

У : = + 1 О - 1

при X > О ^ при X = О, при X < О

Вариант 19. Ввести и напечатать значения переменных а, Ь, с, d вещественного типа. Определить и напечатать количество нулевых значений среди заданных.

Вариант 20. Ввести и напечатать значения переменных а, 6, с, d вещественного типа, причем два из них одинаковы. Найти и напе­чатать значение, отличное от этих двух.

329

Page 331: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

п. 1.1.2. Ввод в языках Си/С++. Варианты тестов

Ниже приведены варианты фрагмента программного кода, содер­жащего определения некоторых переменных. В комментариях к опреде­лениям переменных указано, какие значения переменных нужно ввести.

Написать фрагмент программы, обеспечивающий: • открытие файла (потока Си) '4npuf^ для работы с файлом операцион­

ной системы "Test2.m'^; • ввод из этого файла (потока Си) значений переменных, указанных в

комментариях к программному фрагменту соответствующего вари­анта;

• закрытие файла (потока Си). Указать, как при этом будут выглядеть строки исходных дан­

ных в файле операционной системы ^^Test2Jn^^ (сделайте это обяза­тельно, иначе Ваш ответ нельзя будет проверить).

Предусмотреть контроль корректности значений, возвращае­мых функциями библиотеки Си ^^foperC\ ^^fscanf\ Подключить не-обходимые стандартные заголовочные файлы.

/ / / / / / / /

4. 7 "Ой 31 12

Вариант 7.

double d; char s[ 3 ]; unsigned long uli; short si; char c; // 'r' xnt 1; // -21

Вариант^ 2.

long double b; //4.7 char s[ 3 ]; // "Я" long i; // -1 short j ; //12

Вариант 3.

long double b; // 4.7e2 char s[ 20 ]; // "4" int i; // 12 unsigned j ; // 0x21

Вариант 4.

double b; //4.7 char s[ 20 ]; // "Отлично' long int i; // -21

330

Page 332: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

unsxgned. long-

Вариант

float

±nt

char

Вариант

float ±nt unstgnedL cbeir

Вариант

double float

Int unsigned char

Вариант

long double float long ±nt unsigned char

Вариант

float int

char

J/

5. <a. b ;

if J/ Clr c2. c3 , c4. s[20];

6.

b; J/ u; c4. s[20];

7. d; a. b; i/ J/ Clr c2. c3 , c4. s[20] ;

8.

d; a; ±; j r Clr s[20] ;

9.

a; i r

J/ Clr c2r

//

// // // // // // // // //

// // // // //

// // // // // // // // // //

// // // // // //

// // // // //

0x12

1.5 14. 7 -21 12 'y' 'P' 'a ' ' / ' "Прочитa иная-строка

14.0 12 21 'P' "Зимний-вeчер"

2.0 1.5 12.21 -21 0x12 '1 ' '2' '3' '4' n n It

1.5 1.5 -1 13 '4' ngn

1.5 21 -12 'в' 'e'

^ 1

Page 333: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

s[20]; // "Прочмтанная-строка'

Варит

float

long^ ±nt char

чт 10.

br k; a; Clr s[20] ;

// 5.0 // 15,123 // 27 // ' B ' / / "Строка

Вариант 11, Имеется следующий фрагмент Си-программы:

float ±nt char

а , Ь;

clr s[20]

Строки исходных данных в файле (потоке Си)- ''stdin'' имеют следующий вид (каждая клетка содержит один символ):

+ + + + + + + + + + + + + + + I I I - I 2 I I I 1 1 1 . 1 5 1 1 . 1 1 1 \п1 + + + + + + + + + + + + + + + | Э | т | о | | с ! г | р | о | к | а | I | \ л | + + + + + + + + + + + + + + 1 1 1 2 1 I I I \ л | + + + + + + + Написать фрагмент программы, обеспечивающий чтение из

файла с указателем ''stdin^\ следующих значений:

а : 1.5 (должно быть прочитано значение 1.5) b : 14.7 i : -21 j : 12 cl : ' . ' S : "строка"

Предусмотреть контроль корректности значений, возвращае­мых функцией библиотеки Си ^^scanf\ Подключить необходимые стандартные заголовочные файлы.

Вариант 12, Имеется следующий фрагмент Си-программы:

float ±nt char ±nt

a г b; ir j ; clr c2r c3; RetCode;

RetCode = fscanf( stdirir " %i %3d %c %c %c %f %f " , &ir &jr &clr &c2r Scc3r &br &a ) ;

Строки исходных данных в файле (потоке Си) "stdin" имеют следующий вид (каждая клетка содержит один символ):

332

Page 334: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

+ + + + + + 4- + + + + + + + + I I 1 - I 7 I 7 I | - 1 2 | 4 | 3 1 5 | 5 | 7 | \ л | + + + + + + + + + -|. + + + -I- + I \ 2 \ . \ 4 \ e \ 3 \ I I i I 4 I . I 7 I \ л | + + + + + + + + + + + + + + I I I 7 I 2 I I I \ л | + + + + + + +

К а к и е значения получат переменные RetCode, а, b, /, j , cl, c2, c3?

Вариант 13, Имеется следующий фрагмент С и - п р о г р а м м ы :

float а , Ь/ ±nt i, j ; chsLx: cl, c2, c3; ±nt RetCode; RetCode = fscanf( stdin, " %o 2%ld %c 5%c %c %f %f " ,

&i, &jr &cl, &c2, &c3, &b, &a ) ;

С т р о к и исходных д а н н ы х в файле (потоке Си) ''stdin" имеют с л е д у ю щ и й вид (каждая клетка содержит один символ) :

I I I 1 7 1 7 1 I I 2 I 4 I 3 I 5 I 5 I 7 I \ л | + + + + + + + + + + + + + -I- + I \ 2 \ . \ 4 \ е \ 3 \ I \ 1 \ 4 \ . \ 7 \ \п\ + + + + + + + + + + + + + + I I I 7 I 2 I I I \ л | + + + + + + + Какие значения получат переменные RetCode, а, Ь, i, j , с 1, с2,

сЗ?

Вариант 14, Имеется следующий фрагмент С и - п р о г р а м м ы :

float ±nt chstr

а г Ь; i / J / clr s[ 20 ];

Написать фрагмент п р о г р а м м ы , о б е с п е ч и в а ю щ и й : открытие файла (потока Си) ''Input" для работы с файлом операцион­ной системы "Test2.in"; ввод из этого файла (потока Си) следующих значений указанных ни­же переменных:

а 1 с1

• 1.5 • -21

t г

Ь . J • S .

4. 7 12 "String

• закрытие файла (потока Си). При этом строки исходных д а н н ы х в файле операционной сис­

темы "Test2Jn" имеют следующий вид (каждая клетка содержит

333

Page 335: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

один символ): + + + + + + + + + + + + + + + I I \ S \ t \ r \ i \ n \ g \ \ 1 \ 2 \ 1 1 \ л |

I J I . I 5 I I \ - \ 2 \ 1 \ I 4 I . I 7 I \ л | + + + + + + + + + + + + + + I I - I . I - I I \ л | + + + + + + +

Предусмотреть контроль корректности значений, возвра­щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подклю­чить необходимые стандартные заголовочные файлы.

Вариант 15. Имеется следующий фрагмент Си-программы:

flo&t double ±nt unsigned

a; Ь; i; J r

Написать фрагмент программы, обеспечивающий: • открытие файла (потока Си) '4npuf для работы с файлом операцион­

ной системы ''Test2.w''\ • ввод из этого файла (потока Си) следующих значений указанных ни­

же переменных:

а : 1.5 b : 4.7 i : -21 j : 0x12

• закрытие файла (потока Си). При этом строки исходных данных в файле операционной сис­

темы ''Test2An'' имеют следующий вид (каждая клетка содержит один символ):

+ + + + + + + + + + + + + + + I I I I I I I \ о \ к \ 1 \ 2 \ I \ \п\ + + + + + + + + + + + + + + + \ 1 \ . \ 5 \ I \ - \ 2 \ 1 \ I 4 I . I 7 I \ л |

-I- + 4- + + + + + + + + • + + + Предусмотреть контроль корректности значений, возвра­

щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подклю­чить необходимые стандартные заголовочные файлы.

Вариант 16. Имеется следующий фрагмент Си-программы:

flostt а; double Ь; long int i; unsigned long j ;

Написать фрагмент программы, обеспечивающий:

334

Page 336: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• открытие файла (потока Си) "Input'' для работы с файлом операцион­ной системы "Test2Jn'';

• ввод из этого файла (потока Си) следующих значений указанных ни­же переменных:

а: 1,5 Ь : 4.7 ±: -21 j : 0x12

• закрытие файла (потока Си). При этом строки исходных данных в файле операционной систе­

мы "Test2Jn'' имеют следующий вид (каждая клетка содержит один сим­вол):

-j. + + + + -j- + + + -I- + + + -I- + I I I I I I I 1 0 1 x 1 1 1 2 1 I I \ л |

I i 1 . I 3 I I 1 - I 2 I I I I 4 I . I 7 I \ л | + + + + + + + + + + + + + +

Предусмотреть контроль корректности значений, возвра­щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подклю­чить необходимые стандартные заголовочные файлы.

Вариант 17, Имеется следующий фрагмент Си-программы:

float а г Ь; ±nt i, j , ret code/ chcir cl, c2 r c3; RetCode = fscanf( stdin, " %i %4d %c %c %c %f %f ",

&i, &j, &cl, &c2, &c3, &ar &b ) ;

При этом строки исходных данных в потоке stdin имеют сле­дующий вид (каждая клетка содержит один символ):

+ + + + + + + + + + + + + + + I I 1 1 1 7 1 \ 1 \ 2 \ 3 \ 4 \ 5 \ 6 \ 1 \ \ \п\ + + + + + + + + + + + 4- + + + \ 2 \ . \ 4 \ е \ 2 \ 1 1 I 7 I 2 I | 1 | . | 5 | \ л | + + + + + + + + + + + + + + +

Какие значения получат переменные RetCode, а, Ь, i, j , cl, с2, сЗ?

Вариант 18. Имеется следующий фрагмент Си-программы:

float ±nt cha.r

а, b; ^r J / cl, s[ 20 ];

Написать фрагмент программы, обеспечивающий: открытие файла (потока Си) '4npuf' для работы с файлом операцион­ной системы ''Test2dn''\

335

Page 337: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• ввод из этого файла (потока Си) следующих значений указанных ни­же переменных:

а : 1.5 b : 14.7 1 : 1 j : 12 cl: ' .' s : "Это хорошо"

• закрытие файла (потока Си). При этом строки исходных данных в файле операционной систе­

мы "Test2.iii" имеют следующий вид (каждая клетка содержит один символ):

+ + + + + + + -1- + + -I- + + + + \ Э \ т \ о \ \ к \ о \ р \ о \ ш \ о \ I | 1 1 \ п |

1 1 1 . 1 5 1 I 1 I 2 I I I 1 . 1 I I \ л | + + + + + + + + + + + + + + \ 1 \ 4 \ . \ 7 \ I I I I I ! I I \ л | + + + + + + + + + + + + + +

Предусмотреть контроль корректности значений, возвра­щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подклю­чить необходимые стандартные заголовочные файлы.

Вариант 19, Имеется следующий фрагмент Си-программы:

float ±nt unsigned. char

b; J, retcode; u; c4, s[ 20 ]

Написать фрагмент программы, обеспечивающий: • открытие файла (потока Си) "Input'' для работы с файлом операцион­

ной системы "Test2»in"; • ввод из этого файла (потока Си) следующих значений указанных ни­

же переменных:

и : 21 b : 14.7 j : 12 с4: 'р' S : "Зима-вечер"

• закрытие файла (потока Си). При этом строки исходных данных в файле операционной систе­

мы "Test2.in" имеют следующий вид (каждая клетка содержит один сим­вол):

336

Page 338: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

+ + + + + + + + + + + + + + + I S l M l M l a l - l B l e l ^ J l e l p I \ p \ \ \n\ + + - - - + + + + + + + + + + + + + I I 1 I 1 1 1 2 1 I I \ 2 \ 1 \ \ \n\ + + + -f + + + -I- -f + 4- -I- 4- + I i I 4 I . I 7 I I I I 1 I I I I \ л | H + + + + + + H + + + + + +

Предусмотреть контроль корректности значений, возвра­щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подклю­чить необходимые стандартные заголовочные файлы.

Вариант 20. Имеется следующий фрагмент Си-программы:

float a n t unsigned. ci iajT

Ь; jr retcode; и; c4, s[ 20 ];

Написать фрагмент программы, обеспечивающий: • открытие файла (потока Си) ''Input" для работы с файлом операцион­

ной системы "Test2Jn"; • ввод из этого файла (потока Си) следующих значений указанных ни­

же переменных:

и : 1 • с4:

21 12 'Р'

Ь .

S .

14. 7

"Ура-вечер"

• закрытие файла (потока Си). При этом строки исходных данных в файле операционной систе­

мы "Test2Jn'' имеют следующий вид (каждая клетка содержит один сим­вол):

+ + 4- + + + + + + + + + + + + 1 " | 3 ^ 1 р | а | - | в ! е | ч | е | р | " | | | \ л | + + + + + + + + + + + + + + + I ' I р I ' I I I I 2 I I I I 2 I I I I \ л | + + -|. 4- + + + + + + Ч- + + + I 1 1 4 I . I 7 I I I I I I I I I \ л |

-I- + ^ + 4- + + + + + + + + + Предусмотреть контроль корректности значений, возвра­

щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Подклю­чить необходимые стандартные заголовочные файлы.

П. 1.1.3. Вывод в языках Си/С++. Варианты тестов

Ниже приведены варианты фрагмента программного кода, со­держащего вывод в файл (поток Си) ''stdouf\ Укажите, как будут выглядеть строки вывода в файл (поток Си) ''stdouf после выполне­ния заданного фрагмента. Для удобства в приведенных вариантах

337

Page 339: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

фрагментов программного кода символ "^" обозначает пробельный символ.

Вариант 1.

floett г; ±пЬ 1 = 17; г = l,5f * 2.0elf; fprlntf ( stdout, "*r=%5.2e^%s'^*l = %—i'd\n*%-3s\n".

Вариант 2.

float r; ±nb 1 = 17/ r = 1.5 * 2,Of­fprint f( stdout, "*r^%5.2f^%5s^*l=%-+10d\n*%-30s\n",

r, " _ " , 1, "*", );

Вариант 3.

double r; int 1 = 17; r = 1.543 * 2. 0; fprlntf( stdoutr "*r=%5.21f'^%-4s^-^l = %- + 10d\n*%-8s\n",

Вариант^ 4.

float r = 3.0; int 1 = 17; fprlntf ( stdoutr "*r=%5.2f''%5s^*l = %- + 10d\n*%-30s\n",

T^ II n ,• II Tic " ) .

Вариант 5.

float r = 1.5e2; ±nt 1 = 7 ; fprlntf( stdout, "^%30s\n^r=%f^%5s^l=%10d\n", "*", r ,

""", 1 );

Вариант 6.

float r = 1.5e2; int 1 = 5 ; fprlntf( stdout, "^r=%f'^%-5s^l = %+10d\n'^%2.3s",

Гг "*", 1, "строка" );

Ниже приведены варианты фрагментов программного кода, содержащие определения данных и их инициализацию.

338

Page 340: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Написать фрагмент программы, обеспечивающий: • Опфытие файла (потока Си) ^^Outpuf^ для работы с файлом операци­

онной системы ^^ Tests. ouf\ • Вывод в открытый поток ''Outpuf строк заданного вида. Указание. При выводе максимально использовать указанные в вариан­тах данные и возможности форматированного вывода. • Закрытие файла (потока Си).

Предусмотреть контроль корректности значений, возвращае­мых функциями библиотеки Qnfopen п/close. Подключить необхо-димые стандартные заголовочные файлы.

Вариант 7. Фрагмент Си-программы:

long d = 254; double f = 1234.56; cha.r str[ ] = "Строка 1";

Вид выводимых строк (ниже знак ^ обозначает пробел): [+254^^]-^^ [-^^254] (+1234.6^) (1.234560E-h03^^)

Вариант 8. Фрагмент Си-программы:

±nt d = 254; float f = 1234.56; chstr str[ ] = "Строка";

Вид выводимых строк (ниже знак ^ обозначает пробел): [^'^•f-254]'^[254] (+1234.6'^) (1.234560Е+03)

Вариант 9. Фрагмент Си-программы:

±nt float t cha.r

d = 254; f = 1234.56; str[ ] = "Строка

Вид выводимых строк (ниже знак ' обозначает пробел): [^'^+254]^[254] (+1234.6^) (1.234560Е+03) /^^^-^^^-^^^^Стр/

Вариант 10. Фрагмент Си-программы:

±nt d = 254; flosLt f = 1234.56; сЬлг *str = "Строка символов";

339

Page 341: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вид выводимых строк (ниже знак ^ обозначает пробел): [+254] '^^[''^^^^254] (^^1234, б) ^-^ (1.234560Е+03)

/^^'-^^^'^^^^Стр/^^ /м/

Вариант 77. Фрагмент Си-программы:

i n t d = 254; float f = 1234.56; cha.xr "^str = "Строка символов";

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел):

(-^'-^^^1234,5600) ^^ (1234.5600^^^^'') /Стр/^^/м/

Вариант 12. Фрагмент Си-программы:

±пЬ d = 113; float f = 12.34; char *str = "Строка символов";

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел): [ + 113^^^^^]^^[^^^'-^254] (--^+12.3)

/Строка^^^^^/^^/ил/

Вариант, 13, Фрагмент Си-программы:

±nt d = 254; float f = 1234.56; сЪат *str = "Строка символов";

Вид выводимых строк (ниже знак ^ обозначает пробел): ^^^^^254]'^^[^^254^^''^^^^'^^^] (+1234. 6) ^'^ (^^^^^1.23Е+03)

/ ^ ^ ^ ^ ^Строка/ "^ ^ / ^ "^лов/

Вариант 14. Фрагмент Си-программы:

±nt d - 254; float f = 1234.56; char *str = "Строка символов";

Вид выводимых строк (ниже знак ^ обозначает пробел): Y ^ ^ ^ ^ ^+254]^"[254 - ^ - - - ; (1234. 5 - - - - - ; ^ ^ ( ^ 1 . 2 3 4 Е + 0 3 )

/Стр - - ' - - - / ' - ^/мв/

340

Page 342: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 15. Фрагмент Си-программы:

±пЬ d = 254; float, f = 1234.5 6; char *str = "Строка символов";

Вид выводимых строк (ниже знак ^ обозначает пробел): [-254^^^^^]^^[^^^^^+254] (^^^+1234.5600) /Строка симв^^^^/^^/т/

Вариант 16. Фрагмент Си-программы:

xnt d = 123; float f = 1234.56; char *str = "Прочитанная строка";

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел): [^-^^^^^123]^^[1234. 6-^^^^^] (123-----) /^^^^^^^^^^Просто/

Вариант 17. Фрагмент Си-программы:

±Tit d = 123; float f = 1234.56; char *str = "Прочитанная строка";

Вид выводимых строк (ниже знак ^ обозначает пробел): [----- + 123]--[ + 1234. 6-----J (-----123.)

Вариант 18. Фрагмент Си-программы:

±nt d = 254; float f = 1234.56; char *str = "Строка символов";

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел): [+254]--[-----254] (--1234. 6) -- (1.234560Е+03) /^^^^^-^^^^^Стр/-- /м/

Вариант 19. Фрагмент Си-программы:

int d = 123; float f = 1234.56; char *str = "Прочитанная строка";

341

Page 343: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вид выводимых строк (ниже знак ^ обозначает пробел):

(123 ;

Вариант 20. Фрагмент Си-программы:

±пЬ d = 123; float f = 1234.56; сЪ.а.х: *str = "Прочитанная строка";

Вид ВЫВОДИМЫХ строк (ниже знак ^ обозначает пробел): [^^^^-^ + 123] ^^[ + 1234. 6^^^^^] (^^^^^123,)

* П.1.1.4. Простейшие ветвления. Варианты тестов

Вариант 1. С помощью операторов ветвлений и присваивания записать фрагмент программы, вычисляющий значение переменной п по следующему правилу:

[ л+1 при 1=1 или 1=5^ п := [ а+Ь при 1=7 или 1=12,

[ а-Ь в остальных случаях

Вариант 2. С помощью операторов ветвлений и присваивания записать фрагмент программы, вычисляющий значение переменной п по следующему правилу:

[ п+1 при а>0 и Ь=0 , п := [ а-^Ь при а<=0 и Ь=0 ,

[ а-Ь в остальных случаях

Вариант 3. Изобразить фрагмент схемы алгоритма, соответст­вующий следующему фрагменту программы:

±f ( с < 3 ) ±f ( с == 2 ) а++; else b-h-h; а += 1;

Вариант 4. Изобразить фрагмент схемы алгоритма, соответст­вующий следующему фрагменту программы:

±f ( с < 3 ) ±£ ( с -== 2 ) а + + ; Ь++; а += 1;

Вариант 5. С помощью операторов ветвлений и присваивания записать фрагмент программы, вычисляющий значение переменной п по следующему правилу:

342

Page 344: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

[ 1 при i=l или 2 или 1, л := [ 2 при 1=10,

[ О в остальных случаях

Вариант 6. Изобразить фрагмент схемы алгоритма, соответст­вующий следующему фрагменту программы:

±f( с == 1 ) а + + ; else ±f( с == 2 ) а-~; else ±f( с === 3 ) а -h= 1/

Вариант 7. С помощью операторов ветвлений и присваивания записать фрагмент программы, вычисляющий значение переменной п по следующему правилу:

[ л+1 при 1=4, л := [ а+Ь при 1=1 или 7 или 9,

[ а-Ь в остальных случаях

Вариант 8. С помощью операторов ветвлений и присваивания записать фрагмент программы, вычисляющий значение переменной Z по следующему правилу:

[ х+5 при а>2 и Ь=0, Z := [ а+Ь при а<0,

[ X в остальных случаях

Вариант 9. Изобразить фрагмент схемы алгоритма, соответст­вующий следующему фрагменту программы:

±f( с < 3 ) ±£( с == 2 ) а + + / else b++/ ±f( с < 2 ) C+ +; а += 1;

Вариант 10. Записать фрагмент программы, соответствующий следующему фрагменту схемы программы (рис. 101):

: > ^ Да у Нет

R:=X; P:=Y;

R:=Y; Р:=Х: i к

Q:=1;

Рис. 101. Фрагмент схемы программы

Вариант 11. Записать фрагмент программы, соответствующий следующему фрагменту схемы программы (рис. 102):

343

Page 345: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

X>Y Да Q:=1;

Нет R:=Y; P:=X;

Рис. 102. Фрагмент схемы программы

Вариант 12. Изобразить фрагмент схемы алгоритма, соответ­ствующий следующему фрагменту программы:

±£( с < 3 ) ±f( с == 2 ) a-h + ; else b+-h; ±f( с < 2 ) c+-h/ else a +=^ 1; { C+ + / b+ +; }

Вариант 13. Изобразить фрагмент схемы алгоритма, соответ­ствующий следующему фрагменту программы:

[ х+5 при 1 = 1 , 3 , 5 ; Z := [ а-\-Ь при 1 = 2 , 4 , 6 ;

[ к в остальных случаях

Вариант 14. Записать фрагмент программы, соответствующий следующему фрагменту схемы программы (рис. 103):

^а<=Ь^ Да

Нет R:=X; P:=Y;

a:=d;

Рис. 103. Фрагмент схемы программы

Вариант 15. Изобразить фрагмент схемы алгоритма, соответ­ствующий следующему фрагменту программы:

±f( с <= 1 ) а + + ; else ±f( с == 5 ) а--; else а *= 2;

Вариант 16. Записать фрагмент программы, соответствующий следующему фрагменту схемы программы (рис. 104):

V""" у Да

a:=d;

R:=X; P:=Y; i L

Рис. 104. Фрагмент схемы программы

344

Page 346: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 17, С помощью операторов ветвлений и присваива­ния записать фрагмент программы, вычисляющий величину i ( i >= О ) по следующему правилу:

[ л+1 при 1=0, л := [ а+Ь при 1=2,4,6,8,10 и т.д.

[ а-Ь в остальных случаях

Вариант 18. Изобразить фрагмент схемы алгоритма, соответ­ствующий следующему фрагменту программы:

±£( с < 5 ) ±f( с == 1 ) а + + ; Ь- -= 1;

Вариант 19. С помощью операторов ветвлений и присваива­ния записать фрагмент программы, вычисляющий значение пере­менной Z по следующему правилу:

[ х-ь5 при а>2 и Ь=0 , Z := [ а+Ь при а < = 2 ,

[ X в остальных случаях

Вариант 20. Записать фрагмент программы, соответствующий следующему фрагменту схемы программы (рис. 105):

V ^ у Нет

R:=X; P:=Y;

• i i

Рис. 105. Фрагмент схемы программы

П.1.1.5. Циклы. Варианты тестов

Вариант 1. Задан массив:

float а[ 34 ];

Написать фрагмент программы, который напечатает с новой строки значения элементов массива по пять элементов в строке и по пятнадцать позиций на элемент. Печатаемые значения прижимать к левой границе поля вывода, а положительные значения печатать со знаком плюс. Решить задачу с помощью цикла/Ьг.

345

Page 347: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 2. Задан массив:

double а[ 104 ];

Написать фрагмент программы, который напечатает с новой строки значения элементов массива по пять элементов в строке и по пятнадцать позиций на элемент. Печатаемые значения прижимать к правой границе поля вывода, а положительные значения печатать со знаком плюс. Решить задачу с помощью цикла do-while.

Вариант J. Задан массив:

dLovible а[ 43 ] ;

Написать фрагмент программы, который напечатает с новой строки значения элементов массива по четыре элемента в строке и по девятнадцать позиций на элемент. Печатаемые значения прижи­мать к левой границе поля вывода, положительные значения печа­тать со знаком плюс, а в дробной части печатать три цифры. Решить задачу с помощью цикла while.

Вариант 4, Задан массив:

воллЫе а[ 50 ];

Написать фрагмент программы, который напечатает с новой строки значения элементов массива по четыре элемента в строке и по двадцать позиций на элемент. Печатаемые значения прижимать к правой границе поля вывода, а в дробной части печатать 6 цифр. Решить задачу с помощью цикла do..while.

Вариант 5. Задан массив:

double а[ 50 ];

Написать фрагмент программы, который напечатает в уже от­крытый поток ''Output с новой строки значения элементов массива по четыре элемента в строке и по двадцать позиций на элемент. Печатаемые значения прижимать к правой границе поля вывода, а в дробной части печатать шесть цифр. Решить задачу с помощью цик­ла do..while. Использовать только один цикл.

Подключить необходимые стандартные заголовочные файлы.

Вариант 6, Дано следующее определение:

±пЬ к;

346

Page 348: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

При каких исходных значениях к приведенный ниже цикл бу­дет выполняться бесконечно?

wb±le ( к < 5 ) k+'h;

Возможные варианты ответов: при к <= ..., или при к >= ..., или таких к не существует.

Вариант 7. Пусть определены переменные

±пЬ к, п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

prlntf( "\n\n^%-2s-\n"r " " ) ; for( к = 7; к >= 5; к— ; I

printf( "\п\п" ) ; п = 6 - к; printf( "%i--%-h3d-%5s--'\ к, л , " - " ) ;

}

Вариант 8. Дано следующее определение:

int к;

При каких исходных значениях к приведенный ниже цикл бу­дет выполняться бесконечно?

Willie ( к >^ 15 ) к+ + ;

Возможные варианты ответов: при А: <= ..., или при к >— ..., или таких к не существует.

Вариант 9. Пусть определены переменные:

int к, п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

printf( "\n\n\t^%-2s-\n"r "12345" ) ; for( к = 5; к > 5; к++ ) {

printf( " \ л \ л " ; / п = б - к/ pr±ntf( "%±-^%4d^%2s^^", кг п, "-" ) ;

}

Вариант 10. Дано следующее определение:

347

Page 349: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt к;

При каких исходных значениях к приведенный ниже цикл бу­дет выполняться бесконечно?

do {

к+ + ; } while ( к > 10 ) ;

Возможные варианты ответов: при к <= ..., или при к >= ..., или таких к не существует.

Вариант 11, Пусть определены переменные:

±пЬ к, п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

printf( "\n%-3.2s\n", "*****" ) ; foi:( к = 5; к > 5; к-- ) {

п ^ 6 - к; printf( "%i--%04d-%2s--", к, л , " - " ) ; }

Вариант 12. Дано следующее определение:

int к;

При каких исходных значениях к приведенный ниже цикл бу­дет выполняться бесконечно?

do {

к++ ; } while ( к > -5 ) ;

Возможные варианты ответов: при к <= ..., или при к >= ..., или таких к не существует.

Вариант 13. Пусть определены переменные:

int к, п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

printf( "\n\n-%-5s-\n", " " ; /

348

Page 350: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

fojc( к ^ 5; к >= 5; к— ; (

printf( "\п\п" ) ; п = 6 - к; printf( "%l--%4d-%2s--", к, л , " - " ) ;

}

Вариант 14. Дано следующее определение:

±nt к;

При каких исходных значениях к приведенный ниже цикл бу­дет выполняться бесконечно?

while( к < 12 ) к++;

Возможные варианты ответов: при к <= ..., или при к >= ..., или таких к не существует.

Вариант 15. Пусть определены переменные:

±пЬ кг п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

printf( "\n%3s\n", " - " ) ; for( к = 5; к > 5; к-- )

{ п = б - к; printf( "%i--%4d-%2s--", к, л , " - " ) ;

}

Вариант 16. Сколько раз будет выполнено тело приведенного ниже цикла?

for( ±nt к=4; к<17; к+=3 ) /

Возможные варианты ответов: тело цикла будет выполнено ... раз или цикл будет выполняться бесконечно.

Вариант 17. Пусть определены переменные:

±пЬ к, п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

printf( "\n%3s\n", "-12345" ) ; for( к = 5; к >= 1; к— ; (

349

Page 351: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

п = 8 - к/ pr±ntf( "%i--%4d-%2s--", к, л , "-12" ) ; }

Вариант 18. Сколько раз будет выполнено тело приведенного ниже цикла?

±пЬ с = 3; foxi ±nt к=4/ к<17; к+=3, с+=2 ) ;

Какое значение будет иметь с после выхода из цикла?

Вариант 19. Пусть определены переменные:

±пЬ к;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

prlntf( "\n-%-5s%s-\n", "*", "+" ) ; for( к = 1; к >= -3; к— ;

pr±ntf( "-%5d-%3s-"r к, "--" ) ;

Вариант 20. Пусть определены переменные:

±nt к^ п;

Укажите, что напечатает следующий фрагмент программы (ни­же знак ^ обозначает пробел):

printf( "\n%6s\n", "-" ) / toxi к = 5/ к >= 1; к-- ) {

п = 6 - к; pr±ntf( "%i--%4d-%2s--", к, л , "***" ) ; }

П.1.1.6. Структуры. Варианты тестов

В ответах на приведенные ниже варианты тестов необходимо выполнить следующее.

Закрыть открытые файлы, как только они станут не нужны. Предусмотреть контроль корректности значений, возвращае­

мых функциями библиотеки Си ^^fopeti'^ ^fscanf\ Указать, какие включаемые файлы требует представленный фрагмент.

Вариант 1. В файле операционной системы "Task4Jn'' хранит­ся в текстовой форме ведомость сдачи экзаменов студентами неко­торой группы. Каждая строка этого файла содержит сведения об од­ном студенте, представленные в следующем формате: позиции 1..,2 -

350

Page 352: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

порядковый номер студента в группе; позиция 3 - пробельная лите­ра; позиции 4...22 - фамилия студента длиной не более 18 символов, в произвольном месте поля; позиция 23 - пробельная литера; пози­ция 24.- четыре оценки по четырем предметам, разделенные не ме­нее чем одной пробельной литерой. Количество студентов в группе равно 16. Пример строк указанного файла:

01 Андреев 5 4 5 5 02 Быков 5 5 5 5

16 Яковлев 4 4 5 4

Написать: 1) определение массива структур для хранения ука­занной ведомости; 2) фрагмент программы, который заполнит экза­менационную ведомость данными, вводимыми из файла операцион­ной системы "Task4Jn" (ввод данных должен осуществляться в тек­стовом режиме; 3) фрагмент программы, который вычисляет сред­нюю экзаменационную оценку по всем предметам и студентам (т.е. среднюю оценку из 64 оценок), а затем выводит значение этого по­казателя в файл операционной системы "Task4,out",

Вариант 2. В файле операционной системы "f.in" имеется 10 строк, каждая из которых содержит длины сторон прямоугольников (значения длин задаются в формате с плавающей точкой и разделе­ны пробелами).

Написать: 1) определение массива структур для хранения ука­занных длин сторон прямоугольников, их площадей и периметров; 2) фрагмент программы для чтения длин сторон прямоугольников из файла операционной системы "/.ш"; 3) фрагмент программы, вычис­ляющий и печатающий площади и периметры прямоугольников в файл операционной системы ''f.ouf\

Вариант 3. Имеется следующий фрагмент программы:

stiract ExamReport // Строка экз. ведомости { // Фамилия студента

char Name [ 15 ] ; u n s i g n e d Mark; // Экзаменационная оценка

} ; / / MA ТНета tics : в едомость по ма тема тике ExamReport Math [ 16 ];

Написать фрагмент программы, который заполнит экзамена­ционную ведомость ^^Math^^ данными, вводимыми из файла опера­ционной системы "Task4.in". Ввод данных должен осуществляться в текстовом режиме. В каждой строке файла "Task4.in" содержатся

351

Page 353: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

следующие поля данных: фамилия студента длиной не более 13 символов, начинающаяся с позиции 1; экзаменационная оценка (в позиции 16). Между последней литерой фамилии и оценкой распо­ложены пробельные литеры.

Вариант 4, В файле операционной системы ''Task4.m" хранит­ся в текстовой форме ведомость сдачи экзаменов студентами неко­торой группы. Каждая строка этого файла содержит сведения об од­ном студенте, представленные в следующем формате: позиции 1...2 -порядковый номер студента в группе; позиция 3 - пробельная лите­ра; позиции 4... 15 - фамилия студента длиной не более 11 символов, в произвольном месте поля; позиция 16 - пробельная литера; пози­ция 17 - три оценки по трем предметам, разделенные не менее чем одной пробельной литерой. Количество студентов в группе равно 16. Пример строк указанного файла:

01 Андреев 5 4 5 02 Быков 5 5 5

16 Яковлев 4 5 4

Написать: 1) определение массива структур для хранения ука­занной ведомости, причем в связи с каждым студентом необходимо хранить только фамилию и три оценки, а порядковый номер студен­та должен быть представлен неявно, индексом элемента массива структур; 2) фрагмент программы, который заполнит экзаменацион­ную ведомость данными, вводимыми из файла операционной систе­мы ''Task4Jn" (ввод данных должен осуществляться в текстовом ре­жиме); 3) фрагмент программы, который вычисляет среднюю экза­менационную оценку по всем предметам и студентам (т.е. среднюю оценку из 48 оценок), а затем выводит значение этого показателя в файл операционной системы "Task4.out'\

Замечание, Очевидно, каждая строка исходных данных со­держит лишние сведения: порядковый номер студента в группе (в начале строки). При вводе эти номера следует игнорировать (каким-либо способом).

Вариант 5. Имеется следующий фрагмент программы:

struct ExamReport // Строка экз. ведомости {

// Фамилия студента cJiar- Name [ 15 ]; unsigned. Mark; // Экзаменационная оценка

} ; // MATHematlcs: экзаменационная ведомость ,по математике ExamReport Math[ 16 ];

352

Page 354: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в каждой строке файла "Task4.in" содержатся следующие поля данных: фамилия студента длиной не более 13 символов, начинаю­щаяся с позиции 1; экзаменационная оценка (в позиции 16). Между последней литерой фамилии и оценкой расположены.пробельные литеры.

lianncaTb фрагмент программы, который заполнит экзамена­ционную ведомость ^'Math*^ данными, вводимыми из файла опера­ционной системы "Task4.in" (ввод данных должен осуществляться в текстовом режиме).

Вариант 6» В файле операционной системы ^^Test6.in" имеется пять строк, каждая из которых содержит длины сторон прямоуголь­ников (значения длин разделены двумя пробелами).

Написать: 1) определение массива структур для хранения ука­занных длин сторон прямоугольников, их площадей и периметров; 2) фрагмент программы для чтения длин сторон прямоугольников из файла операционной системы "Test6.in"; 3) фрагмент программы, вычисляющий и печатающий площади и периметры прямоугольни­ков в файл операционной системы "Test6.ouf\

Вариант 7. Имеется следующий фрагмент программы:

sbract ExamReport // Строка экз. ведомости {

// Фамилия студента char Name [ 15 ]; unsigned Markl; // Экзаменационная оценка 1 unsigned. Mark2; // Экзаменационная оценка 2

} Ехат[ 16 ] ;

В каждой строке файла "Task4.in" содержатся следующие поля данных: фамилия студента длиной не более 13 символов, начинаю­щаяся с позиции 1; экзаменационная оценка (в позиции 16); пробел (в позиции 17); экзаменационная оценка (в позиции 18). Между по­следней литерой фамилии и первой оценкой расположены пробель­ные литеры.

Написать фрагмент программы, который заполнит экзамена­ционную ведомость "£xa/?z" данными, вводимыми из файла опера­ционной системы "Task4,in" (ввод данных должен осуществляться в текстовом режиме).

Вариант 8. Имеется следующий фрагмент программы:

struct EXAM_REPORT // Строка экзаменационной ведомости {

353

Page 355: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

chajT fam[ 21 ];// Фамилия экзаменуемого i n t mark; // Экзаменационная оценка

} math[ 16 ]; // Абсолютная успеваемость (процент студентов с // положительными оценками) £1оа.Ь аи; // Качественная успеваемость ( процент студентов, получивших // "4" и "5" ) float ки;

В каждой строке этого файла содержится фамилия студента длиной не более 19 символов, начинающаяся с позиции 1, и экзаме­национная оценка (поз. 22). Между фамилией и оценкой расположе­ны "пробелы".

Написать: 1) фрагмент программы для чтения экзаменацион­ной ведомости из текстового файла "/./«"; 2) фрагмент программы, вычисляющей и печатающей в файл "/.ow/" абсолютную и качест­венную успеваемость группы по математике.

Вариант 9, В файле операционной системы ''Task4,in'' хранит­ся в текстовой форме ведомость со сведениями о продуктах. Каждая строка этого файла содержит сведения об одном виде продукта, представленные в следующем формате: позиции 1...2 - порядковый номер продукта; позиция 3 - пробельная литера; позиции 4... 15 — на­звание продукта длиной не более 11 символов, в произвольном мес­те поля; позиция 16 - пробельная литера; позиции 17... 19 - содержа­ние белка в 100 граммах продукта (целое).

Количество продуктов в ведомости равно 16. Пример строк указанного файла:

01 02

16

минтай щука

сметана

20 21

15

Написать: 1) определение массива структур для хранения ука­занной ведомости и фрагмент программы, который заполнит ведо­мость данными, вводимыми из файла операционной системы "Task4.in'' (ввод данных должен осуществляться в текстовом режи­ме); 2) фрагмент программы для нахождения и печати (в файл "Task4.out") информацию о продукте с наибольшем содержанием белка.

Вариант 10, В текстовом файле ''Task4.in'' содержится список книг библиотеки, имеющий следующий вид:

01 Иванов Программирование 20,500

354

Page 356: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

15 Петров Архиваторы 7.200

Каждая строка списка содержит сведения об одной книге: пер­вые две позиции - порядковый номер книги, третья позиция - "про­бел", с поз. 4 начинается фамилия автора длиной не более 13 симво­лов, поз. 18...34 - название книги (из одного слова), поз. 35 - "про­бел", с поз. 36 - стоимость книги.

Написать: 1) определение массива структур для хранения ука­занного списка и фрагмент программы для чтения списка из файла "Task4Jn''; 2) фрагмент программы, вычисляющей и печатающей среднюю стоимость книг в библиотеке в файл ''Task4.ouf\

Вариант 11, В текстовом файле "Task4.in" содержится ин­формация о квартире, имеющая следующий вид:

01 Комната 15

05 Кухня 5

Каждая строка содержит сведения об одной комнате: первые две позиции - порядковый номер комнаты, третья позиция - "про­бел", с поз. 4 начинается название комнаты длиной не более 15 сим­волов, с поз. 21 - метраж комнаты.

Написать: 1) определение массива структур для хранения ука­занных данных и фрагмент программы для чтения данных о квар­тире из файла "Task4.m"; 2) фрагмент программы для нахождения и печати общего метража данной квартиры в файл "Task4.out".

Вариант 12. В текстовом файле "Task4.i?7" содержится ведо­мость сдачи экзаменов студентами некоторой группы, имеющая сле­дующий вид:

01 Андреев 5 4 5

16 Петров 4 5 4

Каждая строка ведомости содержит сведения об одном студен­те: первые две позиции - порядковый номер студента, третья пози­ция - "пробел", с поз. 4 начинается фамилия студента длиной не бо­лее 11 символов, поз. 16...20 — оценки по трем предметам. Каждой оценке предшествует пробел, а первой оценке может предшество­вать и большее число "пробелов".

Написать: 1) определение массива структур для хранения ука­занной ведомости и фрагмент программы для чтения ведомости из файла ''Task4.in''\ 2) фрагмент программы для нахождения и печати

355

Page 357: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

списка должников (студентов, имеющих хотя бы одну двойку ) в файл "Task4.ouf\

Вариант 13. В текстовом файле "Task4.in" содержится ведо­мость сдачи экзаменов студентами некоторой группы, имеющая сле­дующий вид:

01 Андреев 5 4 5

1 б Петров 4 5 4

Каждая строка ведомости содержит сведения об одном студен­те: первые две позиции - порядковый номер студента, третья пози­ция - "пробел", с поз. 4 начинается фамилия студента длиной не бо­лее 11 символов, поз. 16...20 — оценки по трем предметам (матема­тике, программированию и физике). Каждой оценке предшествует пробел, а первой оценке может предшествовать и большее число "пробелов".

Написать: 1) определение массива структур для хранения ука­занной ведомости и фрагмент программы для чтения ведомости из файла "Task4Jn"; 2) фрагмент программы для нахождения и печати списка должников по программированию в файл "Task4.out".

Вариант 14. В текстовом файле "Task4.in" имеется ведомость сдачи экзаменов студентами некоторой группы:

01 Андреев 5 4 5

1 б Петров 4 5 4

Каждая строка ведомости содержит сведения об одном студен­те: первые две позиции - порядковый номер студента, третья пози­ция - "пробел", с поз. 4 начинается фамилия студента длиной не бо­лее 10 символов, поз. 16...20 — оценки по трем предметам (матема­тике, программированию и физике). Каждой оценке предшествует пробел, а первой оценке может предшествовать и большее число "пробелов".

Написать: 1) определение массива структур для хранения ука­занной ведомости и фрагмент программы для чтения ведомости из файла "Task4Jn"; 2) фрагмент программы для нахождения и печати списка студентов, сдавших физику на "отлично" в файл "Task4.out".

Вариант 15. В текстовом файле "Task4.in" содержатся сведе­ния о предприятиях сферы обслуживания районов города, имеющие следующий вид: ^

356

Page 358: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1. Калининский 10 20 7

10. Выборгский 15 10 9

Каждая строка содержит сведения об одном районе: первые две или три позиции - номер района (с точкой), далее следует один или два "пробела", поз. 5.. 19 — название района длиной не более 14 символов, далее следуют три целых числа, каждому из которых предшествуют один или более пробелов. Первое число задает коли­чество аптек, второе — универсамов, а третье - химчисток.

Написать: 1) определение массива структур для хранения ука­занной информации и фрагмент программы для чтения данных из файла "Task4.in''; 2) фрагмент программы для нахождения и печати в файл "Task4.out" названия района (или районов ), в котором (в которых) находится больше всего аптек.

Вариант 16, В текстовом файле "Task4.m" содержится ин­формация о квартире, имеющая следующий вид:

01 Комната 15

05 Кухня 5

Каждая строка содержит сведения об одной комнате: первые две позиции - порядковый номер комнаты, третья позиция - "про­бел", с поз. 4 начинается название комнаты длиной не более 15 сим­волов, с поз. 21 - метраж комнаты.

Написать: 1) определение массива структур для хранения ука­занных данных и фрагмент программы для чтения данных о квар­тире из файла "Та^-Ы.ш"; 2) фрагмент программы для нахождения и печати в файл "Task4.ouf' метража самой большой по площади ком­наты в квартире.

Вариант 17» Ъ текстовом файле ''Task4.in'' содержится список книг библиотеки, имеющий следующий вид:

01 Иванов Программирование 20.500

15 Петров Архив а торы 7.200

Каждая строка списка содержит сведения об одной книге: пер­вые две позиции - порядковый номер книги, третья позиция - "про­бел", с поз. 4 начинается фамилия автора длиной не более 12 симво­лов, поз. 18.,.34 - название книги (из одного слова), поз. 35 - "про­бел", с поз. 36 - стоимость книги.

Написать: 1) определение массива структур для хранения ука­занного списка и фрагмент программы для чтения списка из файла

357

Page 359: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

''Task4An''\ 2) фрагмент программы, вычисляющей и печатающей полные данные (номер, автор, название и цена) самой дорогой книги в библиотеке в файл "Task4.ouf\

Вариант 18. В текстовом файле "Task4.m" содержится ведо­мость сдачи экзаменов студентами некоторой группы, имеющая сле­дующий вид:

01 Андреев 5 4 5

16 Петров 4 5 4

Каждая строка ведомости содержит сведения об одном студен­те: первые две позиции - порядковый номер студента, третья пози­ция - "пробел", с поз. 4 начинается фамилия студента длиной не бо­лее 11 символов, поз. 16..20 — оценки по трем предметам. Каждой оценке предшествует пробел, а первой оценке может предшество­вать и большее число "пробелов".

Написать: 1) определение массива структур для хранения ука­занной ведомости и фрагмент программы для чтения ведомости из файла "Task4.in"; 2) фрагмент программы для нахождения и печати списка студентов-тоечников (сдавших экзамены на одни тройки) в файл ''Task4.ouf\

Вариант 19. В файле операционной системы " / ш " имеется 10 строк, каждая из которых содержит длины сторон прямоугольников (значения длин задаются в формате с плавающей точкой и разделе­ны пробелами).

Написать: 1) определение массива структур для хранения ука­занных длин сторон прямоугольников, их площадей и периметров; 2) фрагмент программы для чтения длин сторон прямоугольников из файла операционной системы " / ш " ; 3) фрагмент программы, вычис­ляющий и печатающий длины сторон прямоугольников, имеющих максимальные периметр и площадь в файл операционной системы у.оиГ,

Вариант 20. В файле операционной системы "Task4.m" хра­нится в текстовой форме ведомость со сведениями о продуктах. Ка­ждая строка этого файла содержит сведения об одном виде продук­та, представленные в следующем формате: позиции 1...2 - порядко­вый номер продукта; позиция 3 - пробельная литера; позиции 4... 15 - название продукта длиной не более 11 символов, в произвольном месте поля; позиция 16 - пробельная литера; позиции 17... 19 — со­держание белка в 100 граммах продукта (целое); позиция 20 - про­бельная литера; позиции 21...23 — калорийность 100 грамм продукта

358

Page 360: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

(целое). Количество продуктов в ведомости равно 16. Пример строк указанного файла:

01 минтай 02 щука

16 сметана

20 21

15

100 120

150

Написать: 1) определение массива структур для хранения ука­занной ведомости и фрагмент программы, который заполнит экза­менационную ведомость данными, вводимыми из файла операцион­ной системы ''Task4.in^' (ввод данных должен осуществляться в текстовом режиме); 2) фрагмент программы для нахождения и печати (в файл ''Task4.ouf') названия продукта (продуктов) с наибольшей калорийностью.

П.1.1.7. Функции. Варианты тестов

В ответах на приведенные ниже варианты тестов выполнить следующее.

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

Для передачи в функцию исходных данных и получения из нее ответов использовать список параметров. Бдинственный ответ луч> ше получать из функции как возвращаемое значение.

Вариант 1, Вычислить тах:=наиб{а,^,с}. Исходные данные имеют тип с плавающей точкой.

Вариант 2, В массиве целого типа определить количество по­ложительных, отрицательных и нулевых элементов.

Вариант 3. Вычислить тах:=наиб{л,/)} и тш:=наим{а,6}.

Вариант 4. Подсчитать в одномерном массиве целого типа размером 100 элементов наименьшее значение среди положитель­ных элементов.

Вариант 5. Подсчитать в одномерном массиве целого типа размером 100 элементов среднее арифметическое значение. Поста­райтесь не потерять в ответе дробную часть.

Вариант 6. Подсчитать в одномерном массиве целого типа размером 100 элементов индекс и значение последнего из положи­тельных элементов.

359

Page 361: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 7. Подсчитать в одномерном массиве целого типа размером 100 элементов количество нулевых значений.

Вариант 8, Сформировать одномерный массив с элементами

z[ i ] ( О <= i < N ) , N==20

ИЗ двух заданных массивов целого типа х[ i ], у[ i ] по правилу:

z[ 1 ] := mini^ к[ 1 ], у[ i ] } , i = О, 1, . , . , N-1

Вариант 9. Вычислить сумму квадратов элементов двух од­номерных массивов вещественного типа размером по 40 элементов и получить ее из функции как возвращаемое значение

39 Сумма ( х[±] * x[i] -h у[1] ^ у[1] )

1=0

Вариант 10, Найти индекс максимального элемента в массиве целого типа из 30 элеметов. Результат получить из функции как воз­вращаемое значение.

Вариант 11, Написать функцию с двумя параметрами логиче­ского типа, возвращающую значение в соответствии со следующей таблицей истинности:

Параметры Первый false false true true

Второй false true

false true

Возвраща емый результат

false false true

false

Параметры и результат - целого типа: false соответствует ну­левому и true - ненулевому значениям.

Вариант 12. Получить одномерный массив z из двух заданнкх массивов вещественного типа х, у по правилу:

zfi] := ( x[i] -h y[i] ) / 2r ± = О, 1, , . . , 29

Вариант 13. Найти величину и номер первого отрицательного и последнего положительного элементов в массиве вещественного типа заданного размера.

360

Page 362: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 14. Поменять местами первый и последний элемент, второй и предпоследний и т.д. в одномерном массиве вещественного типа заданного размера.

Вариант 15. Вычислть среднее арифметическое для положи­тельных чисел в одномерном массиве целого типа заданного разме­ра. Постарайтесть не потерять дробную часть результата и избежать возможного деления на нуль.

Вариант 16. Написать функцию нахождения минимального элемента среди отрицательных и максимального элемента среди по­ложительных в одномерном массиве целого типа заданного размера.

Вариант 17. Написать функцию нахождения максимального положительного числа кратного пяти в одномерном массиве целого типа заданного размера.

Вариант 18. Найти количество нулевых элементов в одномер­ном массиве целого типа заданного размера и сформировать новый массив из ненулевых элементов исходного массива.

Вариант 19. В одномерном массиве вещественного типа за­данного размера найти сумму элементов, расположенных между максимальным и минимальным элементами. Указанный результат получить из функции как возвращаемое значение.

Вариант 20. Сжать одномерный массив вещественного типа заданного размера. С этой целью удалить из массива все элементы, абсолютное значение которых меньше единицы. Освободившиеся в конце массива элементы заполнить нулями.

П.1.1.8. Области действия определений. Варианты тестов

В ответах на приведенные ниже варианты тестов укажите, как будут выглядеть строки, выведенные на экран в результате выпол­нения программы, приведенной в соответствующем варианте. В от­вете укажите также местоположение пробелов.

Вариант 1. Что напечатает следующая программа?

^include <stdio,h> ±nt i == Or j = 2; int main ( void ) {

auto int i = 0/

361

Page 363: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

printf( "± = %d j^%d \ л " , i , j ) ; {

±nt i =2, j ^ 0; pr±ntf( "± = %d j = %d \n", 1, j ) ; {

Izit j = 10; i += 1; j += 2; printf( "i = %d j=%d \ л " , i , j ) ;

} printf( "i = %d j=%d \ л " , i , j ) ;

} printf( "i = %d j = %d \n", i , j ) ; z-etuzrn 0;

} // end function "main"

Вариант 2, Что напечатает следующая программа?

^include <stdio.h> ±nt i = 10, j = 2; ±nt main ( void ) {

a u t o ±nt i == 8; {

±nt j = 0; printfi "i = %d j = %d \ л " , i , j ) . {

int j = 10; i += 1; j += 2; printf( "i=%d j=%d \n", 1, j ) ;

} j + + ; printfi "i = %d j=^%d \n", i , j ) ;

} printfi "i = %d j = %d \ л " , i , j ) ; return. 0;

}

Вариант 3. Что напечатает следующая программа?

^include <stdio.h> Int i , j = 1; ±nt maini void ) {

±nt i = 5; {

{ ±nt j = 2; j += 3;

} j += 5; printfi "i+l=%d j=%d \n", i+1, j ) ;

} printfi "i = %d j = %d \n", i , j ) ; return 0;

}

Вариант 4. Что напечатает следующая программа?

362

Page 364: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <std±o.h> ±пЬ 1 =^ 1, j = 10; Inb main ( void. ) (

±nt i = 3; {

printf( "i + l=%d j=%d \ л " , i+1, j ) ; {

±nt j = 1; j += 3; } j -f- 5; printf( "l=%d j^%d \ л " , i , j ) ;

} printfi "i = %d j-i-l=%d \ л " , i , j-hl ) ; iretuxn 0;

}

Вариант 5. Что напечатает следующая программа?

^include <stdio.h> int i = 1; j = 10; ±nb main ( void. ) {

int i = 3; I

printf( "i+l^%d j = %d \n"r i+1, j ) ; (

int j = 1; j +=^ 3; } j += 5; printf( "i=%d j=%d \n", i , j ) ;

} printf( "i = %d j + l==%d \n'\ i , j+1 ) ; геЬ\12ПЛ 0;

}

Вариант 6. Что напечатает следующая программа?

^include <stdio.h> int i , j ; int main ( void ) {

auto int i = 3; {

printf( "i + l=%d j=%d \ л " , i + 1, j ) ; {

auto int j = 1; j += 3; printf( "i = %d j = %d \n", i/j ) ;

} j += 5; printf( "i = %d j = %d \n", i , j ) ;

} printf( "i = %d j + l = %d \n", i , j+1 ) ; return 0;

}

363

Page 365: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 7. Что напечатает следующая программа?

^include <stdlo.h> ±nt 1 = 10^ j /

int main ( void, ) {

static int i = 3/ {

printf( '4 = %d j = %d \n", i , j ) ; {

a u t o int j = 10; i += 1; j += 2; prlntf( '4 = %d j = %d \n", 1, j ) ;

} j += 5; prlntf( "l = %d j = %d \n", 1, j ) ;

} prlntfi "l = %d j = %d \n", i , j+1 ) ; return 0;

} Вариант 8. Что напечатает следующая программа?

^Include <stdlo,h> int 1=10, j =2; int main ( void ) {

auto int 1=8; {

int j = 0; prlntf( "l=%d j=%d \n'\ i, j ) {

int j = 10; 1 += 1; j += 2; prlntf( "l = %d j = %d \n", 1, j ) ;

} j++; prlntfi "l = %d j = %d \л", i, j ) ;

} prlntf( '4 = %d j = %d \л", i, j ) ; return 0;

Вариант 9. Что напечатает следующая программа? ^Include <stdlo.h> // Прототипы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw ( int ) ; int 1=1; int main ( void ) {

auto int 1, j ; 1 = reset( ) ; fori j = 1; 3 <= 2; j++ ) {

prlntf( "\nl = %d j = %d\n", 1, j ) ; prlntf( "next( 1 ) = %d\n", next( 1 ) ) ;

364

Page 366: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

printf( "last( i ) - %d\n", last ( i ) ) ; prlntf ( "naw( 1+j ) = %d\n", naw ( 1-hj ) ) ;

} retuxrn 0;

±nt reset ( void. )

return 1/

±nt next ( xnt j )

return ( j = i + + ) ;

int last ( int j )

static int 1 =^ 10; return ( j = l-~ ) ;

int naw ( int i ; auto int j = 10; return( i = j += 1 ) ;

Вариант 10, Что напечатает следующая программа? ^include <stdio.h> // Прототипы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw( int ) ; int 1=1; int main ( void ) {

auto int Ir j ; 1 = reset ( ) ; fori j = 1; j <= 2; j++ ) {

prlntf( "\nl = %d j = %d\n", 1, j ) ; prlntf ( " n e x t Г i ; = %d\n", next ( 1 ) ) ; prlntf ( "last( 1 ) = %d\n'\ last( 1 ) ) ; prlntf ( "naw( 1+j ) - %d\n", naw( 1+j ) ) ;

} retujzn 0;

int reset ( void )

return 1;

int next ( int j )

return ( j = 1-- ) ;

int last ( int j )

static int 1 = 10; return( j = 1++ ) ,

365

Page 367: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt naw ( ±пЬ i ) {

auto ±nb j - 10; return( 1 - j ~= i ) ; }

Вариант 11. Что напечатает следующая программа?

^include <stdio.h> // Прототипы функций ±nt next ( ±zit ) ; int reset ( -void ) ; ±nt last ( ±nt ) / i^t naw( ±nt ) ; Int i = 1; ±nt main ( void ) {

auto ±nt i, j ; i = reset( ) ; fori J, = I; J <= 2; j+-h ) {

prlntf( "\ni = %d j = %d\n", i , j ) ; print f( "next( i ; = %d\n"^ next ( 1 ) ) ; printf( "last( 1 ) = %d\n", last ( 1 ) ) ; printf( "naw( i+j ) --= %d\n", naw ( i+j ) ) ;

) return 0;

int reset( void )

return 1;

±nt next ( int j )

return( j = --1 ) ;

int last ( int j )

static int i =10; return ( j = +4-1 ) ;

int naw( int i )

auto int j =10; return ( i = j -= i + + ) ;

Вариант 12. Что напечатает следующая программа? ^include <stdlo.h> // Прототипы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw( int ) ; int 1=3; int main ( void ) {

auto int i, j ; 1 = reset( ) ; for( j = 4; j <= 5; j++ )

366

Page 368: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

{ print f ( "\nl = %d j = %d\n", i , J ) / print f( "next ( 1 ) = %d\n"^ next ( 1 ) ) ; prlntf( "last( i ) = %d\n"r last ( 1 ) ) ; print f ( "naw( 1+j ) = %d\n" r naw ( 1+j ) ) /

} jcetum 0;

int reset( void )

jretujrn i /

i n t next ( ±nt j )

jcGtuim ( j = - - i ) ;

i n t last ( i n t j )

static int 1; jret-ami j = i-f-/- ) ;

i n t naw ( int 1 )

a u t o int j = 5; retuim ( 1 = j += 1++ ) /

Вариант 13, Что напечатает следующая программа?

^Include <stdlo.h> // Up ототипы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw( int ) ; int 1 = 2; int main ( void ) {

auto int i, j ; 1 = reset ( ) ; for( j = 0; j <= 1; j-h-h ) {

prlntf( "\nl = %d j = %d\n"r ir j ) ; print f( "next ( 1 ) = %d\n"r next ( 1 ) ) ; prlntf( "last( 1 ) = %d\n", last( 1 ) ) ; prlntf ( "naw( 1+j ) = %d\n", naw ( 1+j ) ) ;

} return 0;

} int reset( void ) {

return( 1 + 1 ) ; } int next ( int j ) {

return ( j = 1++ ) ; } int last ( int j )

367

Page 369: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

{

static int i = 4; return ( j = i + + ) ; }

±Tit naw ( ±nt 1 ) {

auto int j = 3; return ( 1 = j += i ) ; }

Вариант 14. Что напечатает следующая программа? ^include <stdio.h> // Прототипы функций int next ( int ) ; void reset( void ) ; int last ( int ) ; int naw( int ) ; int i ; int main ( void ) {

auto int j ; reset( ) ; for( j = 2; j <- 3; j-h+ ) {

printfi "\ni = %d j = %d\n", i , j ) ; print f( "next( i ) = %d\n", next ( i ) ) ; printf( "\ast( i ) = %d\n", last ( i ) ) ; printfi "naw( i-hj ) - %d\n", naw( i-hj ) ) ;

}

return 0; }

void reset ( void ) {

i = 5/ return; } int next ( int j ) {

return ( j = i -h j ) ; }

int last ( int j ) {

sta-tic int i = 2; return ( j += i + + ) ; } int naw( int i ) {

auto int j = 1; return ( i = j+i- ) ; }

Вариант 15. Что напечатает следующая программа?

^include <stdio.h> // Upототипы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw( int ) ; int i = 6; int main ( void ) {

368

Page 370: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

a u t o ±nt j , i; i = reset ( ) ; £or( j = 2; j <= 3; j++ ) {

printfC "\ni = %d j = %d\n", i , j ) ; print f( "next( i ) = %d\n", next ( ± ) ) ; printf( "last( i ; = %d\n"r last ( i ) ) ; printf( "naw( i+j ) = %d\n", naw( i+j ) ) ;

}

x-etux-ii 0; }

±nt reset( void ) I

Int 1=2; зо&Ьихпл i-h-h; } int next ( int j ) {

return ( j = ~-i ) ; }

int last ( int j ) {

static int i = 2; return ( j =- i + + ) ; }

int naw( int i ) {

auto int j = 7; return( i = j -= i ) ; }

Вариант 16, Что напечатает следующая программа? ^include <stdio,h> // Прототипы функций int next ( int ) ; int reset ( int ) ; int last ( int ) / int naw( int ) ; int 1=4; int main ( void ) {

auto int jr 1 = 1; 1 = reset ( 1%4 ) ; £or( j = 1; j < 3 ; j++ ) {

printfi "\ni = %d j = %d\n", i , j ) ; print f( "next( i ) = %d\n", next ( i ) ) ; printfC "last( 1 ) = %d\n"r last ( i ) ) ; printf( "naw( i-hj ) = %d\n", naw( i+j ) ) ;

}

return 0; }

int reset ( int i ) {

return i; ) int next ( int j ) {

return( j = ++i ) ;

369

Page 371: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

; ±nt last ( int j ) {

st&tic int i = 6; jretixm {" j = - - i ) ; }

int naw( int 1 ) {

auto int j = 3; JoetvLTni i = j -= 1 ) ; }

Вариант 17. Что напечатает следующая программа?

^include <stdlo.h> // Прототипы функций int next ( int ) ; int reset ( void. ) ; int last ( int ) ; int naw( int ) ; int 1 = 4/ int main ( void ) {

auto int J, 1/ 1 = reset( ) ; £or( j = 2; j < 4 ; j++ ) {

prlntf( "\nl = %d j = %d\n", reset ( ) , j ) ; print f( "next( 1 ) = %d\n", next ( 1 ) ) ; prlntf( "last( 1 ) = %d\n", last( 1 ) ) ; prlntfi "naw( 1+j ) = %d\n", naw( 1+j ) ) ;

}

return 0;

int reset ( void )

return 1++;

int next ( int j )

return ( j = i-- ) ;

int last ( int j )

static int 1 = 5; return( j = 1++ ) ;

int naw( int 1 )

auto int j = 4; return( 1 = j += 1 ) ;

Вариант 18. Что напечатает следующая программа? ^Include <stdlo.h> // Прототипы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw( int ) ; int 1 = 10;

370

Page 372: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

int main ( void ) {

auto int jr i/ i = reset( ) ; fori j = 2; j < 4; j+ч- ) {

printf( "\ni = %d j = %d\n"r reset ( ) , j ) ; {

static int i = 7/ int j = 10; prlntf( "\ni = %d j = %d\n", i-h+, j ) ;

} print f ( "next ( i ) = %d\n", next ( i ) ) ; printf( "last( i ) = %d\n", last ( i ) ) ; printfC "naw( i-f-j ) = %d\n", naw( i+j ) ) ;

} return 0;

int reset ( void )

return( i + 5 ) ;

int next ( int j )

return( j = i~- ) ;

int last ( int j )

static int 1=^1; return ( j = i-h+ ) /

int naw( int i )

auto int j = 3; return( i = j -= i ) ;

Вариант 19. Что напечатает следующая программа?

^include <stdio.h> // Пр ото типы функций int next ( int ) ; int reset ( void ) ; int last ( int ) ; int naw( int ) ; int i = 10; int main ( void ) {

auto int j , i; i = reset ( ) ; for( 1 = 2; j < 4 ; j-h+ ) {

print f ( "\ni = %d j = %d\n" , r e s e t f ^ , j ) ; printf( "next ( i ) = %d\n", next ( i ) ) ; printf( "last( i ) = %d\n", last ( i ) ) ; printf( "naw( i+j ) = %d\n"r naw( i+j ) ) ;

} return 0;

} int reset ( void )

371

Page 373: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

return( i + 5 ) ;

±nt next ( ±nt j )

return ( j = i-- ) /

±nt last ( ±nt j )

static ±nt i = 1; return ( j = i-h+ ) ;

int naw ( int 1 )

auto int j = 3; return ( 1 = j -= 1 ) ;

Вариант 20. Что напечатает следующая программа? ^Include <stdio.h> // Прото типы функций int next ( int ) ; int reset ( int ) ; int last ( int ) ; int navj ( int ) ; int 1=3; int main ( void ) {

auto int jr 1 = 5; 1 = reset ( 1/2 ) ; £or( j = 6; j < 8 ; j++ ) {

prlntf( "\nl = %d j = %d\n'\ 1, j ) ; prlntf ( "next ( 1 ) = %d\n" r next ( 1 ) ) ; prlntf( "last( 1 ) = %d\n", last( 1 ) ) ; prlntf ( "naw( 1+j ) = %d\n'\ naw( 1+j ) ) /

I return 0;

int reset( int 1 )

retuim. 1;

int next ( int j )

return( j = -~1 ) ;

int last ( int j )

static int 1 = 4; retum( j = l + -h ) ;

int naw ( int 1 )

auto int j = 4; return ( 1 = j ~= 1 ) ;

372

Page 374: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

п.1.1.9. Массивы и указатели. Варианты тестов

В ответах на приведенные ниже варианты тестов укажите, как будут выглядеть строки, выведенные на экран в результате выпол­нения программы, приведенной в соответствующем варианте. В от­вете укажите также местоположение пробелов.

Вариант 7. Что напечатает следующая программа?

^include <stdlo.h> ±nt Array[ ] = { 0 , 4 , 5 , 2 , 3 } ; ±nt main ( void ) {

int Index^ ^Pointer; £or( Index = 0/ Index <= 4; Index+=2 )

pr±ntf( " %3d"r "^ (Аггауч-Index—; ) / printf ( "\n" ) ; Pointer = Array + 1; £or( Index = 0; Index <= 2; )

printf ( " %3d" r Pointer [ -h + Index ] ) ; printf ( "\n" ) ; x-etuxn 0;

}

Вариант 2. Что напечатает следующая программа?

^include <stdio.h> int Array[ ] = { 1, 2, -7, 4, 3 } ; int main ( void ) (

int Index, ^Pointer; fori Index = 0; Index <= 4; Index+=2 ) {

printf( " %3d". Array[ Index] ) ; printf ( "\n" ) ;

} Pointer = Array+1; fojci Index = 0; Index <= 2; ++Index ) {

printf( " %3d". Pointer[ ++Index ] ) ; printf ( " \ л " ; /

} jretuzm 0;

}

Вариант 3. Что напечатает следующая программа?

^include <stdio.h> int Array[ ] = { 1 , 4 , 7 , 2 , 3 } ; int main ( void ) {

373

Page 375: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Int Index ^ "^Pointer; £or( Index = 1; Index <= 4; Index+=1 ) {

pr±ntf( " %3d"r Array[ +4-IndexJ ); printf( "\n" ) ;

}

Pointer = Array; £ox: ( Index = 0; Index <= 2; + + Index ) {

printf( " %3d'\ Pointer[ Index+-h ] ); printf( "\n" ) ;

) return 0;

Вариант 4. Что напечатает следующая программа?

^include <stdio,h> int Array[ ] = { Ir 4r 5, 12, 3 } ; ±nt main ( void ) {

int Index, ^Pointer; fox:( Index = 1; Index <= 4; Index-h=l ) {

printfi " %3d"r * (Array-hlndex-h-h) ); printfC "\n" ) ;

} Pointer = Array + 1; for ( Index = 0; Index <= 2; + + Index )

printfi " %3d"r Pointer[ ++Index ] ); printf( "\n" ) ; retxirn 0;

}

Вариант 5. Что напечатает следующая программа?

^include <stdio.h> int Array[ ] = { 0 , 4 , 5 , 2 , 3 } ; int main ( void ) {

int Index, "^Pointer; for( Index = 0; Index <= 4; Index+=2 )

printf( " %3d", * (Array-hlndex++) ); printf( "\n" ) ; Pointer = Array + 1;

for( Index = 0; Index <= 3; Index++ ) printfi " %3d". Pointer[ ++Index ] );

printfi "\n" ) ; return 0;

374

Page 376: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 6. Что напечатает следующая программа?

^Include <std±o.h> ±nt Array[ ] = { 0 г 4 г 5 г 2 , 3 } ; ±пЬ main ( void ) {

±nt Index, ^Pointer; £ог( Index = 0; Index <= 4; Index-i-=2 )

printf( " %3d"r * (Array+Index-h+) ); print f ( "\n" ) ; Pointer = Array + 1; £ою( Index = 0; Index <= 3; Index + + )

printf( " %3d"r Pointer[ ч-ч-Index ] ) ; printf ( "\n" ) ; ire turn 0;

}

Вариант 7. Что напечатает следующая программа?

^include <stdio.h> ±nt Array[ ] = { 0 , 4 r 5 r 2 , 3 ) ; int main ( void ) {

Int Index, * Pointer; , for( Index = 0; Index <= 2; Index-h=l )

printf( " %3d", *(Array+Index++) ); printf ( "\л" ) ; Pointer = Array; £OJ: ( Index = 0; Index <= 3; Index++ )

printf( " %3d". Pointer[ -h+Index ] ); printf ( "\л" ; ; return 0;

}

Вариант 8, Что напечатает следующая программа?

^include <stdio.h> Int Array[ ] = { 0 , 4 , 5 , 2 r 3 } ; ±nt main ( void ) {

Int Index, ^Pointer; for( Index = 0; Index <= 2; Index-h=2 )

printf ( " %3d", * (Array+Index-h+) ); printf ( "\n" ) ; Pointer - Array; £or( Index = 1; Index <= 2; Index+ч- )

printf( " %3d". Pointer[ ++Index ] ); printf ( "\n" ); jreturn 0;

}

Вариант 9, Что напечатает следующая программа?

375

Page 377: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

^include <stdio.h> Int Array[ J = { 0 , 4 , 5 , 2 , 3 } / ±nt main ( void, ) {

±nt Index, '^'Pointer; £or( Index = 0; Index <= 2; Index+=2 )

prlntf( " %3d"r * (Array-fIndex) ); printf( "\n" ) ; Pointer = Array; for( Index = 1; Index <= 2; Index++ )

printf( " %3d"r Pointer[ Index ] ); printf( "\n" ) ; iretuim 0/

}

Вариант 10. Что напечатает следующая программа?

^include <stdio,h> ±nt main( void ) {

±nt a[ ] = { 10, 11, 12, 13, 14, 15, 16 }, i, *p, for( p = a, i = 0;p + 2*i <= a -h 6; p++, i + + )

printfi " %3d", *( p + 2*i ) ); printf( "\n" ) ; fori p = a + 5; p >== a + 1; p -= 2 )

printf ( " %3d", *p ) ; printf( "\n" ) / retuzm 0;

}

Вариант 11. Что напечатает следующая программа?

^include <stdio.h> ±nt main ( void. ) {

±nt a[ ] = { 10, 11, 12, 13, 14, 15, 16 }, i, *p; for( p = a, i = 0; ++p + i <= a + 5; p++, i-h+ )

printf ( " %3d", *( -h+p + i ) ); printf ( "\n" ) ; fori p = a + 5/ p >= a + 1; p -= 2 )

printfi " %3d", *p++ ); printfi "\n" ) ; return 0;

}

Вариант 12. Что напечатает следуьрщая программа?

^include <stdio.h> ±nt main i void ) {

int a[ ] = { 10, 11, 12, 13, 14, 15 }, i, *p;

376

Page 378: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

£ою ( р = а, ± = О; р + i <= а + 5; р+-/-, ±++ ) pr±ntf( " %3d", *( ++Р + i ) ) ;

printf( "\n" ) ; £o:c ( p ^ a + 5; p >= a + 1; p-- )

printf( " %3d", *p— ) ; printf( "\n" ) ; retvLirn 0;

Вариант 13. Что напечатает следующая программа?

^include <stdio.h> x n t main ( void. ) {

±nt a[ ] = { 10, 11, 12, 13, 14, 15 } , i , *p; for( p = a+2, i = 0; p + i <= a + 5; p+ + , i + + )

print f( " %3d", * ( p -h 1 ) ) / printf ( "\n" ) ; fox:( p==a + 5;p>=a + l; p— ;

printf ( " %3d", *—p ) ; printf ( "\n" ) ; return 0;

I

Вариант 14. Что напечатает следующая программа?

^include <stdio,h> ±пЬ main ( void. ) {

Int a[ ] = { 10, 11, 12, 13, 14, 15 } , i , *p; fori p == a, i = 0 ; p - h i < = a - h 5 - i ; p+ + , i+-h )

printf ( " %3d", *( p + i + + ) ) ; printf ( "\n" ) ; £or ( p = a + 5; p >= a ; p~- )

printf( " %3d", *p— ; / printf ( "\n" ) ; jretujm 0;

}

Вариант 15. Что напечатает следующая программа?

^include <stdio.h> Int main ( void ) {

int a[ ] ^ { 15, 11, 10, 13, 14, 10 } , i , *p; £or ( p = a, i = 0;p-hi<=a + 5 ~ i ; p+ +, i + + )

printf ( " %3d", p[ i ] ) ; printf ( "\n" ) ; tor( p = a + 5; p >= a ; p -= 2 )

printf( " %3d", *p ) ; printf ( "\n" ) ; return 0;

Ъ11

Page 379: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 16, Что напечатает следующая программа?

^include <stdio.h> ±nt main ( void ) {

±nt a[ 3 J[ 3 J ^ { { 1, 2, 3 } , { 4, 5, 6 Ь ( 7, 8, 9 } } ;

±nt *pa[ 3 ] = { a[ 1 J, a[ 2 ], a[ 1 ] } / for( int 1 = 0; 1 < 3 ; i++ )

printf( "%d %d %d\n", a[ 1 ][ 2-i J, *(*(ач-±) + ! ) , * ( pa [ 1 ] ) ) ;

jretujrn 0; }

Вариант 17. Что напечатает следующая программа?

^Include <stdio.h> ±пЬ main( void ) {

±nt a[ 3 ][ 3 ] = { { 1, 2, 3 } , { 4, 5, 6 Ь / 7 Й Я ) } '

int *pa[ 3 ] = { a[ 2 ]\ a] 0 ], a[ 2 ] } ; for( int i = 0; i < 2 ; i++ )

printf( "%d %d %d\n", a[ i ][ 2-i ], *(*(a + i ) + i ) , * ( p a [ i ] ) ) ;

return 0;

Вариант 18, Что напечатает следующая программа?

^include <stdio.h> int main ( void ) {

int a[ 3 ][ 3 ] = { { 1, 2, 3 } , { 4 , 5 , 6 Ь { 7, 8, 9 } } ;

int *pa[ 3 ] = { a[ 2 ], a[ 0 ], a[ 1 ] } ; for( int i = 0; i < 2 ; i++ )

printf( "%d %d %d\n", a[ i ][ 2-i 7 , *(*(a + i ) + i ) , ^ ( p a [ i ] ) ) ;

return 0; }

Вариант 19, Что напечатает следующая программа?

^include <stdio.h> int main ( void ) {

378

Page 380: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt a[ 3 ] [ 3 ] == { { Ir 2, 3 } г { 4, 5, 6 Ь { 7, 8, 9 } } ;

±nt *pa[ 3 ] - { a[ 2 ], a[ 0 ], a[ 1 ] } ; fo2:( int i = 0/ i < 2 ; i++ )

printf( "%d %d %d\n"r a[ 1 ][ 2-i 7 , *(*(a + i ) + l ) , ^ ( p a [ l ] ) ) /

return 0;

Вариант 20. Что напечатает следующая программа?

^Include <std±o.h> int main ( void, ) {

±nt a[ 3 ] [ 3 ] -' { { 1, 2, 3 } , ( 4, 5, 6 ; , { 7, 8, 9 } } ;

int *pa[3] = { a [ 0 ] , a [ l ] , a [ 2 ] } . £or( int i = 2; i > 0 ; i— ;

printf( "%d %d %d\n'\ a[ i ][ 2~i ], *(*(a + i ) + i ) , * ( p a [ i ] ) ) ;

z-etuxn 0;

П. 1.1.10. Операции над линейным списком. Работа с динамической памятью. Варианты тестов

Вариант / . Определен следующий структурный тип:

s t r u c t Node // NODE: узел линейного списка {

Node *рЫпк/ // Pointer LINK: // указатель на очередной узел

floatt Info; // INFOrmation: информация } ;

В текстовом файле операционной системы ''TestSAn'' содер­жится некоторое количество вещественных чисел, разделенных символами пробельной группы ( ' ', '\/', '\«' ).

Написать прототип, определение и пример вызова функции, кото­рая должна ввести из файла ^'TestS.in" содержащиеся в нем веществен­ные числа и запомнить их в узлах линейного списка, в котором каждый узел (динамически размещенная в памяти структура) имеет тип Node. При этом первое прочитанное число должно находиться в последнем от начала узле линейного списка, второе число - в предпоследнем узле и т.д.

Все исходные данные (указатель на **имя. расширение** файла ввода) и все результаты работы функции (указатель на начало линейного списка) должны передаваться через список

379

Page 381: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

параметров. С целью обработки ошибок предусмотреть кон­троль значений, возвращаемых функциями библиотеки Си ^^fopen^\ ^^fscanf^ и операцией new. Подключить необходимые стандартные заголовочные файлы.

Вариант 2, Определен следующий указатель на начало ли­нейного списка:

stJTuct Node {

Node dovible

}

*рЫпк;

In fo ; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrmat ion: инф ормация

Написать прототип, определение и пример вызова функции, которая должна определить, сколько в линейном списке имеется элементов с отрицательными значениями. В частном случае, перед вызовом этой функции линейный список может быть пуст.

Все исходные данные (указатель на начало линейного спи­ска) и все результаты работы функции (количество найденных элементов) должны передаваться через список параметров — это обязательное требование.

Вариант 3. Определен следующий указатель на начало ли­нейного списка:

stxnict Node {

Node

float }

// NODE: узел линейного списка

*pL±nk; // Pointer LINK: // указатель на очередной узел

In fo; // INFOrma tion: информа ция *start/

Написать прототип, определение и пример вызова функции, которая должна определить, сколько в линейном списке имеется элементов, в которых хранится заданное значение add. В частном случае, перед вызовом этой функции линейный список может быть пуст.

Все исходные данные (add^ указатель на начало линейного списка) и все результаты работы функции (количество найден­ных элементов) должны передаваться через список параметров — это обязательное требование.

Вариант 4. Определен следующий /-^азатель на начало ли­нейного списка:

380

Page 382: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

struct Node // NODE: узел линейного списка {

Node *pLlnk; // Pointer LINK: // указатель на очередной узел

±nt Info; // INFOrmation: информация } *start;

Написать прототип, определение и пример вызова функции, которая должна в начало линейного списка добавить еще один эле­мент, в котором будет храниться значение add. В частном случае, перед вызовом этой функции линейный список может быть пуст.

Все исходные данные {add^ указатель на начало линейного списка) и все результаты работы функции (указатель на начало линейного списка) должны передаваться через список парамет­ров. С целью обработки ошибок предусмотреть контроль значе­ния, возвращаемого операцией new.

Вариант 5. Определен следующий указатель на начало ли­нейного списка:

struct Node // NODE: узел линейного списка {

Node *pLink; // Pointer LINK: // указатель на очередной узел

float Info; // INFOrmation: информация } *start;

Написать прототип, определение и пример вызова функции, которая должна в конец линейного списка добавить еще один эле­мент, в котором будет храниться значение add. В частном случае, перед вызовом этой функции линейный список может быть пуст.

Все исходные данные {add, указатель на начало линейного списка) и все результаты работы функции (указатель на начало линейного списка) должны передаваться через список парамет­ров. С целью обработки ошибок предусмотреть контроль значе­ния, возвращаемого операцией new.

Вариант 6. Определен следующий структурный тип:

struct Node // NODE: { // узел линейного списка

Node ^pLink; // Pointer LINK: указатель на // очередной узел списка

in t Info; // INFOrm at ion: // содержательная информация

} ;

В текстовом файле операционной системы "TestS.in" содер-

381

Page 383: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

жится некоторое количество целых чисел, разделенных символами пробельной группы ( ' ', V , '\«' ).

Написать прототип, определение и пример вызова функции, которая должна BBCCTJI ИЗ файла ''TestS.in'' содержащиеся в нём це­лые числа и запомнить их в узлах линейного списка, в котором каж­дый узел (динамически размещенная в памяти структура) имеет тип Node. При этом первое прочитанное число должно находиться в первом от начала узле линейного списка, второе число - во втором узле и т.д.

Все исходные данные (указатель на "имя. расширение** файла ввода) и все результаты работы функции (указатель на начало линейного списка) должны передаваться через список параметров. С целью обработки ошибок предусмотреть кон­троль значений, возвращаемых функциями библиотеки Си ^^fopen^\ ^^fscanf^ и операцией new. Подключить необходимые стандартные заголовочные файлы.

Вариант 7. Определен следующий указатель на начало ли­нейного списка:

зЬгасЬ Node // NODE: узел линейного списка {

Node *рЫпк; // Pointer LINK: // указатель на очередной узел

float Info; // INFOrmation: информация } *start;

Написать прототип, определение и пример вызова функции, которая в процессе просмотра списка выводит данные (числа) в файл на магнитном диске ^'f.ouV\ не разрушая информацию в линей­ном списке. В частном случае, перед вызовом этой функции линей­ный список может быть пуст.

Все исходные данные (указатель на начало линейного спи­ска, указатель на **имя. расширение** файла вывода) должны пе­редаваться через список параметров. С целью обработки ошибок предусмотреть контроль значения, возвращаемого функцией библиотеки Си ^^fopen^\ Подключить необходимые стандартные заголовочные файлы.

Вариант 8. Определен следующий указатель на начало ли­нейного списка:

struct Node // NODE: узел линейного списка {

Node *рЫпк; // Pointer LINK: // указатель на очередной узел

382

Page 384: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

float I

Info; *start;

// INFOrm at ion: информа ция

Написать прототип, определение и пример вызова функции, которая в процессе просмотра списка выводит данные (числа) в файл на магнитном диске "f.ouf\ одновременно освобождая память, занятую линейным списком. В частном случае, перед вызовом этой функции линейный список может быть пуст.

Все исходные данные (указатель на начало линейного спи­ска, указатель на **имя. расширение** файла вывода) и результа­ты работы функции (указатель на начало линейного списка) должны передаваться через список параметров. С целью обра­ботки ошибок предусмотреть контроль значения, возвращаемо­го функцией библиотеки Си ^^fopen^\ Подключить необходимые стандартные заголовочные файлы.

Вариант 9. Определен следующий указатель на начало ли­нейного списка:

stxnjct Node {

Node float

}

*рЫпк;

Info; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrm ation : информа ция

Написать прототип, определение и пример вызова функции для удаления из списка к последних элементов с освобождением занятой ими памяти. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое ко­личество элементов.

Все исходные данные (указатель на начало линейного спи­ска, количество удаляемых элементов) и результаты выполне­ния функции (указатель на начало линейного списка) должны передаваться через список параметров.

Вариант 10. Определен следующий указатель на начало ли­нейного списка:

( stjract Node

Node

float

*рЫпк;

Info; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrm ation: информа ция

Написать прототип, определение и пример вызова функции

383

Page 385: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

для удаления из списка к первых элементов с освобождением заня­той ими памяти. В частном случае, перед вызовом этой функции ли­нейный список может быть пуст или может содержать любое коли­чество элементов.

Все исходные данные (указатель на начало линейного спи­ска, количество удаляемых элементов) и результаты выполне­ния функции (указатель на начало линейного списка) должны передаваться через список параметров.

Вариант 11, Определен следующий указатель на начало ли­нейного списка:

stmict Node {

Node

±nt }

*pLink/

In fo ; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrm ati on : ин форма ция

Написать прототип, определение и пример вызова функции для вставки в линейный список после каждого элемента, в котором хранится значение find, элемента, в котором будет храниться значе­ние add. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные (find^ add^ указатель на начало ли­нейного списка) должны передаваться через список параметров. С целью обработки ошибок предусмотреть контроль значения, возвращаемого операцией new.

Вариант 12. Определен следующий указатель на начало ли­нейного списка:

stmjct Node {

}

Node

±nt

*рЫпк; In fo ; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrm at Ion: информа ция

Написать прототип, определение и пример вызова функции для удаления из линейного списка элемента, следующего после каж­дого элемента, в котором хранится знгченне find. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные (Jind^ указатель на начало линейного списка) должны передаваться через список параметров.

384

Page 386: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 13. Определен следующий указатель на начало ли­нейного списка:

stxract Node {

}

Node

±nt

*pLink;

Info; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrmation: информация

Написать прототип, определение и пример вызова функции для удаления из линейного списка элемента, предшествующего каж­дому элементу, в котором хранится значение y?«(i. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные {find^ указатель на начало линейного списка) и результаты выполнения функции (указатель на нача­ло линейного списка) должны передаваться через список пара­метров.

Вариант 14. Определен следующий указатель на начало ли­нейного списка:

зЬгасЬ Node {

Node

}

*pLink;

Info; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrm at ion: информа ция

Написать прототип, определение и пример вызова функции для вставки в линейный список перед каждым элементом, в котором хранится значение find, элемента, в котором будет храниться значе­ние add. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные (find, add^ указатель на начало ли­нейного списка) и результаты выполнения функции (указатель на начало линейного списка) должны передаваться через список параметров. С целью обработки ошибок предусмотреть кон­троль значения, возвращаемого операцией new.

Вариант 15. Определен следующий указатель на начало ли­нейного списка:

stzTict Node // NODE: узел линейного списка

385

Page 387: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Node

±nt }

*pL±nk;

Info; *start;

// Pointer LINK: // указатель на очередной узел / / INFOrm at ion: мн ф орма ция

Написать прототип, определение и пример вызова функции для удаления из линейного списка второго, четвертого и т.д. эле­ментов. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое количество элементов.

Вариант 16. Определен следующий указатель на начало ли­нейного списка:

struct Node {

Node ±nt

}

*рЫпк;

Info/ *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел / / INFOrm at ion: ин форма ция

Написать прототип, определение и пример вызова функции для модификации каждого из элементов линейного списка, в кото­ром хранится значение find. Модификация подобных элементов за­ключается в хранении в них значения add. В частном случае, перед вызовом этой функции линейный список может быть пуст или мо­жет содержать любое количество элементов.

Все исходные данные (find^ add^ указатель на начало ли­нейного списка) должны передаваться через список параметров.

Вариант 17. Определен следующий указатель на начало ли­нейного списка:

stxract Node {

Node ±nt

}

*pLink;

Info; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrm at ion: информа ция

Написать прототип, определение и пример вызова функции для удаления первого и последнего элементов списка с освобожде­нием занятой ими памяти. В частном случае, перед вызовом этой функции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные (указатель на начало линейного спи­ска) и результаты работы функции (указатель на начало линей-

386

Page 388: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ного списка) должны передаваться через список параметров.

Вариант 18. Определен следующий указатель на начало ли­нейного списка:

stjTuct Node {

Node

}

*pLink;

Infor­ms tart;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrmation: информация

Написать прототип, определение и пример вызова функции для вставки новых элементов и в начало (в него помещается значе­ние addjbeg), и в конец (в него помещается значение add_end) ли­нейного списка. В частном случае, перед вызовом этой функции ли­нейный список может быть пуст или может содержать любое коли­чество элементов.

Все исходные данные {addbeg^ add_end указатель на нача­ло линейного списка) и результаты работы функции (указатель на начало линейного списка) должны передаваться через список параметров. С целью обработки ошибок предусмотреть кон­троль значения, возвращаемого операцией new.

Вариант 19. Определен следующий указатель на начало ли­нейного списка:

stxTict Node {

}

Node

±nt

*pLlnk;

Info; *start;

// NODE: узел линейного списка

// Pointer LINK: // указатель на очередной узел // INFOrmation: информация

Написать прототип, определение и пример вызова функции для вставки в линейный список после каждого элемента, в котором хранится значение/?/7(i, двух элементов, в которых будут храниться значения addl и add2, В частном случае, перед вызовом этой функ­ции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные {find^ addl^ add2^ указатель на начало линейного списка) должны передаваться через список парамет­ров. С целью обработки ошибок предусмотреть контроль значе­ния, возвращаемого операцией new.

Вариант 20. Определен следующий указатель на начало ли­нейного списка:

387

Page 389: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

stmict Node // NODE: узел линейного списка {

Node *pLink; // Pointer LINK: // указатель на очередной узел

xnt Info; // INFOrmation: информация } *start;

Написать прототип, определение и пример вызова функции для вставки в линейный список перед каждым элементом, в котором хранится значение yz«<i, двух элементов, в которых будут храниться значения addl и add2. В частном случае, перед вызовом этой функ­ции линейный список может быть пуст или может содержать любое количество элементов.

Все исходные данные (Jlnd^ addl^ add2^ указатель на начало линейного списка) и результаты выполнения функции (указа­тель на начало линейного списка) должны передаваться через список параметров. С целью обработки ошибок предусмотреть контроль значения, возвращаемого операцией new.

П.1.1.11. Препроцессор, перечисления, функции с умалчиваемыми значениями аргументов, перегрузка функций, шаблоны функций,

перегрузка операций. Варианты тестов

Вариант 1, Директивы препроцессора. Укажите, как следует оформить заголовочный файл, чтобы приведенная ниже запись не приводила к возникновению ошибки:

# include "flle.h'' # include "file.h''

Укажите как будет выглядеть модифицированный заголовоч­ный файл.

Вариант 2. Перечисления. Будет ли корректной приведенная ниже программа:

^include <stdio.h> ±nt main ( jroid ) {

enum t{ c=-l, pasc=4, ada, modula2, forth=4 } ; t m; m = a da; printfi "\n m - %d", m ) ; return 0;

}

Что при этом будет выведено на экран?

388

Page 390: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 3. Функции с умалчиваемыми значениями пара­метров. Имеется следующий фрагмент программного кода:

voxd. DrawCircle ( izit к=100, Int у=100, ±nt radius=100 ) ;

Является ли запись прототипа функции правильной (обоснуйте ответ)? Являются ли правильными приведенные ниже вызовы функ­ции? В случае положительного ответа укажите, с какими значения­ми параметров функция будет выполняться?

DrawCircle( ) ; DrawCircle( 200 ) ; DrawCircle( 200, 300 ) ; DrawCircle( 200, 300, 400 ) ; DrawCircle( , , 400 ) ;

Являются ли правильными приводимые ниже записи прототи­пов функций (обоснуйте ответ)?

void. DrawCircle ( int х , int у=100, Int rad ) ; void. DrawCircle ( int x, int y=100, int radlus=100 ) ; void DrawCircle ( int x, int y, int radlus=100 ) ;

Вариант 4. Шаблоны функций, В одномерном массиве, со­стоящем из п элементов, вычислить сумму отрицательных элемен­тов. Исходные данные и полученные результаты обязательно передавать через список параметров. Написать прототип, опреде­ление шаблона функций и пример ее вызова для типов int, float и double.

Вариант 5. Перегрузка операций для пользовательских ти­пов. Определен следующий пользовательский тип для работы с ком­плексными данными:

struct CMP // CoMPlex: комплексный тип (

dovible г; // Вещественная часть double i; // Мнимая часть

} ;

Написать определение функции, перегружающей операцию суммирования комплексных данных, и пример вызова этой функ­ции. Имейте ввиду, что вещественная часть суммы равна сумме ве­щественных частей операндов. Аналогично — для мнимых частей.

Вариант б. Перегрузка операций для пользовательских ти­пов. Определен следующий пользовательский тип для работы с ком­плексными данными:

389

Page 391: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

struct CMP // CoMPlex: комплексный тип {

double r; // Вещественная часть double ±; // Мнимая часть

} ;

Написать определение функции, перегружающей операцию вычитания комплексных данных, и пример вызова этой функции. Имейте ввиду, что вещественная часть разности равна разности ве­щественных частей операндов. Аналогично - для мнимых частей.

Вариант 7. Шаблоны функций. В одномерном массиве, со­стоящем из п вещественных элементов, вычислить сумму элементов массива с нечетными номерами. Исходные данные и полученные результаты обязательно передавать через список параметров. Написать прототип, определение шаблона функций и пример ее вы­зова для типов intafloat и double.

Вариант 8. Перегрузка операций для пользовательских ти­пов. Определен следующий пользовательский тип:

struct V {

int arr[ 4 ]; // Вектор } ;

Написать определение функции, перегружающей операцию вычитания векторов, и пример вызова этой функции. Имейте ввиду, что разность векторов равна поэлементной разности векторов.

Вариант 9. Перегрузка операций для пользовательских ти­пов. Определен следующий пользовательский тип:

struct V {

double arr[ 4 ]; // Вектор } ;

Написать определение функции, перегружающей операцию суммирования векторов, и пример вызова этой функции. Имейте ввиду, что сумма векторов равна поэлементной сумме векторов.

Вариант 10. Функции с умалчиваемыми значениями пара­метров. Имеется следующий фрагмент программного кода:

void Rect ( float w, tloat 1=1,5 ) ;

390

Page 392: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Является ли запись прототипа функции правильной (обоснуйте Ваш ответ)? Являются ли правильными приведенные ниже вызовы функции? В случае положительного ответа укажите, с какими зна­чениями параметров функция будет выполняться?

Rect ( ) ; Rect ( 2.0 ) ; Rect ( 2.00, 3.00 ) ;

Вариант 11. Шаблоны функций. В одномерном массиве, со­стоящем из п элементов, вычислить наибольшее значение элемента массива. Исходные данные и полученные результаты обязатель­но передавать через список параметров. Написать прототип, оп­ределение шаблона функций и пример ее вызова для типов long, float и double.

Вариант 12. Директивы препроцессора. Опишите: • действия препроцессора по директиве include; • различие форматов ^include <file.h> и include ''file.h'\

Вариант 13. Функции с умалчиваемыми значениями пара­метров. Где следует указывать умалчиваемые значения параметров функции (в прототипе, в заголовке определения функции, в обоих перечисленных местах)?

Вариант 14. Функции с умалчиваемыми значениями пара­метров. Имеется следующий фрагмент программного кода:

void Point ( double х , double у=-1.5 ) ;

Является ли запись прототипа функции правильной (обоснуйте Ваш ответ)? Являются ли правильными приведенные ниже вызовы функции? В случае положительного ответа укажите, с какими зна­чениями параметров функция будет выполняться?

Point ( , ) ; Point ( 2.0, -1.5 ) ; Point ( 2.00, 3.00, 4.7 ) ; Point ( 4.7 ) ;

Вариант 15. Шаблоны функций. В одномерном массиве, со­стоящем из п элементов, вычислить среднее арифметическое значе­ние для отрицательных элементов массива. Постарайтесь не поте­рять дробную часть результата. Исходные данные и полученные результаты обязательно передавать через список параметров. Написать прототип, определение шаблона функций и пример ее вы-

391

Page 393: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

зова для типов int и double.

Вариант 16. Шаблоны функций. В одномерном массиве, со­стоящем из п элементов, вычислить максимальный по модулю отри­цательный элемент массива. Исходные данные и полученные ре­зультаты обязательно передавать через список параметров. На­писать прототип, определение шаблона функций и пример ее вызова для типов int и double.

Вариант 17. Шаблоны функций. В матрице, состоящей из п строк и т столбцов, определить количество строк, не содержащих ни одного нулевого элемента. Исходные данные и полученные ре­зультаты обязательно передавать через список параметров. На­писать прототип, определение шаблона функций и пример ее вызова для типов int и double.

Вариант 18. Шаблоны функций. В матрице, состоящей из п строк и т столбцов, определить максимальное из отрицательных значений элементов матрицы. Исходные данные и полученные ре­зультаты обязательно передавать через список параметров. На­писать прототип, определение шаблона функций и пример ее вызова для типов int и double.

Вариант 19. Директивы препроцессора. Что напечатает дан­ная программа?

^include <stdio.h> ^define AREA (г) 3.14*г*г ±пЬ main ( void ) {

printf( "%f\n'\ AREA( 2.0-1.0 ) ) ; jretujm 0;

}

Вариант 20. Шаблоны функций. В одномерном массиве, со­стоящем из п элементов, вычислить среднее арифметическое значе­ние элементов массива (не потеряйте дробную часть) и индекс наи­большего элемента. Исходные данные и полученные результаты обязательно передавать через список параметров. Написать про­тотип, определение шаблона функций и пример ее вызова для типа int.

П.1.2. Программные проекты

На практических занятиях студенты выполняют три про-

392

Page 394: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

граммных проекта: • решение простой задачи с использованием ПМ-ассемблера (выпол­

няется по усмотрению преподавателя и требует наличия компакт-диска, прилагаемого к данному учебному пособию);

• структурное программирование средствами языков Си/С++; • средства модульного программирования в языке C++.

П. 1.2.1. Программирование на ПМ-ассемблере. Варианты программных проектов

Среда программирования. Интегрированная среда програм­мирования ПМ-ассемблера описана в [1] и имеется на компакт-диске.

Формулировка решаемой задачи. Задача, предложенная для решения, должна предусматривать работу с массивами с использо­ванием косвенной адресации. Варианты программных проектов приведены ниже. Для ввода и вывода использовать файлы MS DOS. Для обеспечения наглядности вывода использовать стро­ковые данные.

Содермсание отчета 1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ - формулировка решаемой зада­

чи, требования к программному проекту, язык программирования. 2. ТЕКСТ ПРОГРАММЫ - назначение программы, листинг с

исходным текстом программы в самодокументируемой форме. Многочисленные примеры оформления исходных текстов ПМ-программ имеются в [1] и на компакт-диске.

3. ОПИСАНИЕ ПРОГРАММЫ - назначение программы; метод решения задачи и основные расчетные соотношения; схема про­граммы с необходимыми пояснениями, выполненная в соответствии с действующими стандартами.

3. ПРОГРАММА И МЕТОДИКА ИСПЫТАНИЙ - разработка контрольного примера (примеров) с их обоснованием и анализом, результаты вычислений по отлаженной программе, выводы.

Варианты 1-5, В качестве первых пяти вариантов можно ис­пользовать приведенные выше варианты 1-5 из подразд. П. 1.1.1.

Вариант 6. Ввести и напечатать значения элементов массива целого типа с заданной размерностью. Вычислить и напечатать сумму элементов массива, расположенных до минимального эле­мента.

393

Page 395: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 7. Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Вычислить и напеча­тать произведение положительных элементов массива. Если массив не содержит элементов с положительными значениями, то в качест­ве ответа напечатать "В массиве нет положительных элементов".

Вариант 8. Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Вычислить и напеча­тать сумму положительных элементов массива, расположенных до максимального элемента. Если массив не содержит элементов с по­ложительными значениями, то в качестве ответа напечатать "В мас­сиве нет положительных элементов".

Вариант 9. Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Вычислить и напеча­тать количество отрицательных элементов массива.

Вариант 10, Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Преобразовать мас­сив таким образом, чтобы вначале располагались все элементы, от­личающиеся от максимального не более, чем на 20%. Модифициро­ванный массив напечатать.

Вариант 1L Ввести и напечатать значения элементов массива целого типа с заданной размерностью. Вычислить и напечатать сумму элементов массива, расположенных после последнего нулево­го элемента. Если массив не содержит нулевых элементов, то в ка­честве ответа напечатать "В массиве нет нулевых элементов".

Вариант 12. Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. В массиве все отри­цательные элементы заменить их квадратами и определить их коли­чество. Модифицированный массив и количество измененных эле­ментов напечатать.

Вариант 13. Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Вычислить и напеча­тать сумму модулей элементов массива, расположенных после мак­симального элемента.

Вариант 14. Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Преобразовать мас­сив таким образом, чтобы вначале располагались все отрицательные элементы. Модифицированный массив напечатать.

394

Page 396: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 15, Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Упорядочить массив по возрастанию значений элементов. Отсортированный массив на­печатать.

Вариант 16, Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Ввести значения гра­ниц диапазона. Вычислить и напечатать количество элементов массива, лежащих в заданном диапазоне, и информацию о заданном диапазоне.

Вариант. 17, Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Ввести значения гра­ниц диапазона. Сжать массив, удалив из него элементы, значения которых находятся в заданном диапазоне. Освободившиеся элемен­ты заполнить нулями. Модифицированный массив и заданный диа­пазон значений напечатать.

Вариант 18, Ввести и напечатать значения элементов массива вещественного типа с заданной размерностью. Преобразовать мас­сив таким образом, чтобы в первой его половине располагались эле­менты, стоявшие в четных позициях, а во второй половине — эле­менты, стоявшие в нечетных позициях. Модифицированный массив напечатать.

Вариант 19, Ввести и напечатать значения элементов массива целого типа с заданной размерностью. Преобразовать массив таким образом, чтобы нулевые элементы располагались в конце массива. Модифицированный массив напечатать.

Вариант 20, Ввести и напечатать значения элементов массива целого типа с заданной размерностью. Преобразовать массив таким образом, чтобы нулевые элементы располагались в начале массива. Модифицированный массив напечатать.

П.1.2.2. Структурное программирование средствами языков Си/С++. Варианты программных проектов

Среда программирования. Любая интегрированная среда про­граммирования языка С+-ь. На начальном этапе обучения можно ре­комендовать использование простой интегрированной среды про­граммирования Borland С+-ь 3.1 с переходом в будущем на более со-

395

Page 397: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

временную и широко распространенную среду программирования Microsoft Visual Studio C++ 6.0.

Формулировка решаемой задачи, С использованием средств структурного программирования языков Си/С++ спроектировать три элементарных программы для решения.

1. Задачи с линейным следованием операторов. Например, вы­числить значение функции

у = arctg( 1 1 4 • 1п(«))

с проверкой области допустимых значений ее аргументов. 2. Задачи с ветвлением (использовать структурированные

операторы if, switch). Например, вычислить значение функции [ а+Ь п р и х<1,

у := [ а*Ь при 1 < = х < = 2 , [ а-Ь в остальных случаях

3. Задачи с циклом (использовать структурированные операто­ры while, do-while, for). Например, вычислить сумму ряда

Набор вариантов программных проектов приводится ниже.

Содерлсание отчета. 1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ - формулировка решаемой задачи,

требования к программам, язык программирования. 2. ТЕКСТ ПРОГРАММЫ - для каждой программы в заголовке-

комментарии указать ее назначение, привести листинг с исход­ным текстом в самодокументируемом виде. Создание программ­ного проекта рассмотрено в приложении П.2. Рекомендации по структуре программы и пример оформления исходного текста программы приведены в приложении П.З.

3. ПРОГРАММА И МЕТОДИКА ИСПЫТАНИЙ - разработка кон­трольных примеров с их обоснованием и анализом, результаты вычислений по отлаженной программе, выводы. Рекомендации по методике отладки разработанной программы приведены в приложении П.4.

Вариант 1. Вычислить значения функций и сумму ряда

у = arctg('';5Lii^-.ln(a)) +10"^. 2,5 19(111( 7))

396

Page 398: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

у =

fa+b } ab Уа-b

при при при

х<\. 1<=:X<=2,

х>2

Вариант 2. Вычислить значения функций и значение факториала

V5^tg^(arcsin(x))^^: ШЫ

у = {" Y U

при при при

jc>3. 1<х<=3,

д:<=1

п\

Вариант 3. Вычислить значения функций и сумму ряда х^ к

е . , ,ч2,33 sin'^Cx+TT/Z) Vtg(in(A:)) j 2 . i g ( x )

11 6 , 7 J C + 9 , 2 X ^ -1 ,02OC^ «рм jc<=0,

• » — ^ при x>0 ax +b-x •sin(A:)

Вариант 4. Вычислить значения функций и сумму ряда

s2 (e l^4 .3

In* (л:) у = дЯ^В -*^»^ У ) .(2.3-^^1)

J =

a-\-bz-\-C'Z < d-\-n-z-¥f-z

g-bh-z+mz

при при

в

х=\. jc-2.

остальных случаях

х-\-\ х+Ъ х+п у = + + ... +

1 3 «

Вариант 5. Вычислить значения функций и сумму ряда

_ s'm(x^+x~^+x^^^) \0~^'k

397

Page 399: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

у =

f а-х+4

Ja(l-e-^) I 0

при при

в

x>4, 0<=х<=4,

остальных случаях

У = Е{(-1Г/(2-а+1)} а=0

Вариант 6, Вычислить значения функций и сумму ряда

2 У =

sin'^(^2,8-e^+x) V- ^^ ^ л х ^ - а "

•9,110^

Д' =

Са+Ь

" [о

при при

в

а>Ь, а<-Ь и

остальных а>0,

случаях

1 V^2,3

а=1 а!

Вариант 7. Вычислить значения функций и сумму ряда

> 75,73-я • г И)-11.7

> = г к1

< \+х 1 sin(x)

при при

в

\х\>\ и |x|<=l и

остальных

а>0. а>0.

случаях

у = Z(2.a-l)-0,5 а=1

а-\

Вариант 8. Вычислить значения функций и сумму ряда

_ (е^^^+Ь/а)'Л: (л-/) У24-23,6

к1 5/ п

У = <

с 9 z^+\g(a+b'C)/x ^/z'X'Sm(a'X)

1

при при

в

х>0. x<=0 W (x*z)>0. остальных случаях

у = 2+ I {Ы)'''^^<-;г^+~^)} а=0 Ъа-\-\ 2'а+2

Вариант 9. Вычислить значения функций и сумму ряда

У = a+b

398 7г/4-\-х' -1/J

Page 400: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

у = (a+b+c

J a+b

[ -

при при

в

\b\<=\a\ и \b\<=\a\ и остальных

\c\<=\b\.

к1>1*|. случаях

Вариант 10. Вычислить значения функций и произведение сомножителей

у = e«(^^>(^ps in(x) -x(^" ' -x ' / ' " ) )10-3

У =

Г(ж/2)-е<-^^'=*д<^)) } Я-/2

[ (^/2)е(*-*>

при при

в

х>0. х=0.

остальных случаях

у = \\+ П(2-а+1)

Вариант 11. Вычислить значения функций и сумму ряда

-4 ^ч sin(x)'10

|^c|-arcsin(x) •(7r/2 + e^^^' (а^-х^)))

у = <

х+2

. 2 - 5

зГ х+ух

при

при

в

jc<3,

3<=jc<6,

остальных случаях

у = Z( - l )"+ ' - l /a

Вариант 12. Вычислить значения функций и сумму ряда

.5

т-л/аЬ

У =

(l/Q+a)^ \ а^-,х

ОМх-а

при при

в

х>=1, х<0.

остальных случаях

У = Z(-l) ' ' - l / (2-f3-a) а=0

399

Page 401: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Вариант 13. Вычислить значения функций и сумму ряда

с b а

У =

(\g{a/{a+b) < sin(a)

I 0

при при

в

(а'Ь+с)>5, 0<=(а'Ь+с)<=5,

остальных случаях

Вариант 14. Вычислить значения функций и сумму ряда

У = ^

X-Z

2 X 'Z

0

при

при

в

х>0 и

х<0 и

остальных

2 2 X >Z ,

2 2 X >Z ,

случаях

у = иК2-а+\Г а=0

Вариант 15. Вычислить значения функций и сумму ряда

у = \ ' 1" при

при в

л:>0. -l<=jc<=0.

остальных случаях

У = Z^^

Вариант 16. Вычислить значения функций и сумму ряда

?'^'^*^\-Ъ'Б\Г?{Х) , , In(a^)

а""}!^ 24,61-10"

У=< \ ' 1" при

при в

jc>0. -1<=л:<=0,

остальных случаях

400

Page 402: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

п

Вариант 17. В ы ч и с л и т ь значения функций и сумму ряда

^ . ( ^ - ) 2 - a . ( , _ 5 ) 3 . s m ( ^ . 1 0 - 5

{sin(x) при х>0,

1 при х=0, ij—x в остальных случаях

п

а=\

Вариант 18. Вычислить значения функций и сумму ряда

е~^'^+] х^ . ^ - 4

^ 1 \а \%{х 1{а-\-Ь)) при (сг+^))>0, +6|-lg(jc) при (а+Ь)<=0

у = Е8-(2.а-1) а=1

Вариант 19. Вычислить значения функций и сумму ряда

^а-х

У = f'' г U

при при при

х>3. 1<х<=3,

л:<=1

й=1 2-0+1

Вариант 20. Вычислить значения функций и сумму ряда

1п*(х)

у =

г a-x+4 \ а (1 -е -^)

1 0

при при

в

х>4. 0<=д:<=4,

остальных случаях

401

Page 403: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

o^l '

Указания no выполнению программных проектов • При вычислении значения функции следует проверить область допустимых значений аргументов функции (например, при вычис­лении х^, где а - вещественное, должно быть д:>0; подкоренное выражение, аргументы логарифмических функций должны быть также положительными; делитель должен быть отличен от нуля; ар­гумент тангенса не должен быть кратен ж/2 и т.п.). • Для получения возможности использования математических функций необходимо подключить соответствующий заголовочный файл:

^include <math.h>

При этом следует иметь ввиду, что большинство математических функций используют аргументы и имеют возвращаемое значение с типом double. Поэтому аргументы функций, вычисляемых в про­граммных проектах 1 и 2 также должны иметь тип double. Исчерпы­вающий перечень и описание стандартных математических и других стандартных функций приведен в [5].

П. 1.2,3. Средства модульного программирования в языке C++. Варианты программных проектов

Среда программирования. Любая интегрированная среда про­граммирования языка C++. Повторяем, что начальном этапе обуче­ния можно рекомендовать использование простой интегрированной среды программирования Borland C++ 3.1 с переходом в будущем на более современную и широко распространенную среду программи­рования Microsoft Visual Studio C++ 6.0 или 7.0 (.NET).

Задание (формулировка решаемой задачи). Задача, предло­женная для решения, может, в частности, предусматривать работу с массивами. Например, с использованием средств структурного и модульного программирования языка C++ спроектировать програм­му для обработки двумерного целочисленного массива. Характери­стикой строки такого массива является сумма элементов строки с положительными четными значениями. Переставляя строки задан­ного массива, расположить их в соответствии с ростом характери­стик. Варианты программных проектов такого рода приводятся ни­же. Отличительной особенностью данного программного проекта

402

Page 404: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

является использование модульного программирования, в рамках которого студент осваивает методологию нисходящего иерархиче­ского программирования, в соответствии с которой обоснованно проектирует файловую и функциональную структуру программного продукта. Другой важной особенностью программного проекта яв­ляется изучение и практическое освоение методики отладки про­граммных проектов.

Рекомендации по созданию программного проекта приведены в приложении П.5.

Содержание отчета. 1. ТЕХНИЧЕСКОЕ ЗАДАНИЕ - формулировка решаемой зада­

чи, требования к программе (в том числе та часть спецификации, которая относится к обработке ошибок и предупреждений), язык программирования.

2. ТЕКСТ ПРОГРАММЫ - для программы в заголовке-комментарии указать ее назначение, привести листинг с исходным текстом в самодокументируемом виде. Пример оформления исход­ного текста программы приведен в приложении П.5.

3. ОПИСАНИЕ ПРОГРАММЫ - описание файловой и функ­циональной структур программного проекта (вторая часть спецификации), краткое описание работы программы и схемы 2-3 функций, выполненные в соответствии с действующими стандарта­ми. 4. ПРОГРАММА И МЕТОДИКА ИСПЫТАНИЙ - описание методики отладки, требования к контрольным примерам, разработка контрольных примеров с их обоснованием и анализом, результаты вычислений по отлаженной программе, выводы.

Указания по выполнению программных проектов, 1. Предусмотреть запуск программного проекта с

использованием командной строки. 2. Использовать файловый ввод-вывод. 3. Массив размещать в динамической памяти (особенности

размещения матрицы в динамической памяти рассмотрены выше в разд. 8).

Вариант 1. Найти максимальное число, встречающееся в за­данном векторе более одного раза.

Вариант 2. Определить норму заданной матрицы, т.е. значе­ние

тах(Х|Ф][У]|) у

Вариант 3. По заданной квадратной матрице размером N-N

403

Page 405: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

построить вектор длиной (2Л^-1), элементы которого - максимумы элементов диагоналей, параллельных главной, включая главную диагональ.

Вариант 4, Характеристикой строки матрицы назовем сумму ее положительных элементов, имеющих четные значения индексов. Переставляя строки заданной матрицы, расположить их в соответст­вии с ростом характеристик.

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

Вариант 6. Говорят, что матрица имеет седловой элемент Ф][уЪ если элемент a[i][j] является минимальным в / -ой строке и максимальным в у-ом столбце. Найти номера строки и столбца ка­кого-либо седлового элемента и его значение.

Вариант 7. Найти значение наибольшего элемента матрицы среди всех элементов тех строк матрицы, которые упорядочены ли­бо по возрастанию, либо по убыванию значений элементов.

Вариант 8, Характеристикой столбца матрицы назовем сумму его отрицательных элементов, имеющих нечетные значения индек­сов. Переставляя столбцы заданной матрицы, расположить их в со­ответствии с убыванием характеристик.

Вариант 9, Элемент матрицы называется локальным миниму­мом, если его значение строго меньше значений всех имеющихся соседей. Подсчитать количество локальных минимумов заданной матрицы и напечатать информацию о каждом из них.

Вариант 10, Составить программу нахождения элемента вектора, имеющего максимальное значение. Элементы, стоящие после максимального, заменить нулями и переставить в начало вектора. Исходный и полученный векторы напечатать.

Вариант 11, Составить программу нахождения максимально­го значения элемента вектора среди отрицательных и минимального значения — среди положительных элементов.

Вариант 12, Написать программу, которая упорядочивала бы элементы вектора по знаку, сначала положительные, а затем — отри-

404

Page 406: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

дательные, в таком же порядке, как в исходном векторе.

Вариант 13. Составить программу, позволяющую найти мак­симальный элемент вектора и, если он не равен нулю, то разделить на него все элементы вектора. Если же максимальный элемент век­тора равен нулю, то вектор не изменять.

Вариант 14. Составить программу поиска элементов, встре­чающихся в векторе более одного раза. Из найденных элементов сформировать новый вектор.

Вариант 15. Составить программу упорядочения по возраста­нию элементов каждой строки матрицы. Сортировка строк должна выполняться на месте, что означает, что вспомогательный вектор не должен использоваться.

Вариант 16. Составить программу вычисления количества положительных элементов в левом нижнем треугольнике квадрат­ной матрицы. Треугольник включает диагональ матрицы.

Вариант 17. Составить программу обмена местами макси­мального элемента главной диагонали квадратной матрицы и мини­мального элемента побочной диагонали.

Вариант 18. Составить программу печати значений элементов той строки матрицы, сумма элементов которой минимальна.

Вариант 19. Составить программу нахождения суммы значе­ний элементов тех строк матрицы, у которых на главной диагонали расположены элементы, имеющие отрицательные значения.

Вариант 20. Составить программу перестановки строк матри­цы по убыванию значения их первого элемента.

П.1.3. Экзаменационное тестирование

Наряду с традиционной формой, экзаменационное тестирова­ние можно проводить в форме тестовых вопросов.

На экзамене каждому студенту может быть предложена ком­плексная проверочная работа, содержащая пять вопросов по некото­рым из перечисленных основных разделов курса:

• программирование на ПМ-ассемблере; • ввод; а вывод;

405

Page 407: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

• простейшие ветвления; а циклы; • структуры; а функции; • области действия определений; • массивы и указатели; • работа с динамической памятью и операции с линейным

списком; • препроцессор, перечисления, функции с умалчиваемыми

значениями аргументов, перегрузка функций, шаблоны функций, перегрузка операций.

Комплексная проверочная работа рассчитана на 1 ч. 15 мин. Ответ на каждый тестовый вопрос, в зависимости от правильности и полноты, оценивается О, 0,25, 0,5, 0,75 или 1 баллом. Таким обра­зом, максимальная сумма баллов может достигнуть 5.

В соответствии с набранными баллами выставляются следую­щие экзаменационные оценки: • "отлично" (4,25-5 баллов); • "хорошо" (3,5-4 балла); • "удовлетворительно" (2,5-3,25 балла); • "неудовлетворительно" (менее 2,5 баллов).

Примеры формулировок тестовых экзаменационных вопросов содержатся в подразд. П.1Л.

КОМПЛЕКСНАЯ ЭКЗАМЕНАЦИОННАЯ РАБОТА Пример варианта

!• Структуры. В файле операционной системы "Task4.in'' хра­нится в текстовой форме ведомость сдачи экзаменов студентами не­которой группы. Каждая строка этого файла содержит сведения об одном студенте, представленные в следующем формате:

позиции 1...2 - порядковый номер студента в группе; позиция 3 - пробельная литера; позиции 4...22 - фамилия студента длиной не более 18 сим­

волов в произвольном месте поля; позиция 23 - пробельная литера; позиция 24 - четыре оценки по четырем предметам, раз­

деленные не менее чем одной пробельной литерой. Количество студентов в группе равно 16. Пример строк ука­

занного файла:

01 Андреев 5 4 5 5 02 Быков 5 5 5 5

16 Яковлев 4 4 5 4

406

Page 408: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1.1. Написать объявление массива структур для хранения ука­занной ведомости.

1.2. Написать фрагмент программы, который заполнит экзаме­национную ведомость данными, вводимыми из файла операционной системы "Task4.in". Ввод данных должен осуществляться в тексто­вом режиме.

1.3. Написать фрагмент программы, который вычисляет сред­нюю экзаменационную оценку по всем предметам и студентам (т.е. среднюю оценку из 64 оценок), а затем выводит значение этого по­казателя в файл операционной системы ''Task4.ouf\

Примечание. Закрыть открытые файлы, как только они станут не нуж­

ны. Предусмотреть контроль корректности значений, возвра­

щаемых функциями библиотеки Си ^^fopen^\ ^^fscanf\ Указать, какие включаемые файлы требует представленный фрагмент.

2. Функции. Написать прототип, определение функции и при­мер вызова функции, которая подсчитывает тах:=наиб{а,6,с}. Исход­ные данные имеют тип с плавающей точкой.

Все исходные данные должны передаваться через список параметров, а найденный максимум следует получить как зна­чение, возвращаемое функцией. Выполнение этого требования является обязательным.

3. Массивы и указатели. Что напечатает следующая про­грамма?

^include <stdlo.h>

xnt Array[ ] = { 0 , 4 , 5 , 2 , 3 } ;

±nt main ( void ) {

±nt Index; ±nt ^Pointer;

for( Index = 0; Index <= 4; Index+=2 ) printf ( " %3d"r * (Array+Index--) );

prlntf ( "\n" ) ;

Pointer = Array + 1; fox:( Index = 0; Index <= 2; )

printf( " %3d". Pointer[ ++Index ] ); printf ( "\n" ) ;

return 0;

407

Page 409: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

4. Операции с линейным списком. Работа с динамической памятью. Определен следующий указатель на начало линейного списка:

stJTuct Node // NODE: узел линейного списка {

Node *pLink; // Pointer LINK: // указатель на очередной узел

double Info; // INFOrmation: информация } * start;

Написать прототип, определение и пример вызова функции, которая должна определить, сколько в линейном списке имеется элементов с отрицательными значениями. В частном случае, перед вызовом этой функции линейный список может быть пуст.

Все исходные данные (указатель на начало линейного спи­ска) и все результаты работы функции (количество найденных элементов) должны передаваться через список параметров — это обязательное требование.

5. Шаблоны функций. В одномерном массиве, состоящем из п элементов, вычислить сумму отрицательных элементов. Написать прототип, определение шаблона функций и пример ее вызова для типов int, float и double.

Приложение П.2. Создание программного проекта

Ниже рассматривается создание программного проекта в двух средах программирования: • в интегрированной среде проектирования программ (IDE - Inte­

grated Development Environment) MS Visual Studio C++ 6.0; • в IDE Borland C++ 3.1.

П.2.1. IDE MS Visual Studio C++ 6.0. Создание программного проекта

Интегрированная среда проектирования программ (IDE) пред­ставляет собой комплект программных инструментов - Tools (рис. 106). Этот комплект инструментов - хороший, инструментов — мно­го, но среда не русифицирована (в ней используется английский язык).

408

Page 410: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Проекты (Projects). Проекты IDE характеризуются следую­щими особенностями.

1. Единицей работы IDE является проект. Проект — это ком­плект файлов.

2. Виды файлов в составе проекта: • исходные файлы, написанные программистом {*.срр — С Plus Plas

— тексты на языке 0++ и *./; — Header — заголовочные файлы), IDE содержит инструменты, которые позволяют автоматизировать со­ставление исходных файлов;

• служебные файлы, которые автоматически создаются IDE, но по инструкциям программиста.

3. Каталог проекта. Служебные файлы обязательно располага­ются в этом каталоге. Исходные файлы хотя и могут располагаться где угодно, но, чтобы не запутаться, их тоже следует поместить в каталог проекта.

4. Проекты IDE и проекты программного обеспечения. Про­стые программы представляют собой просто один проект IDE. Сложное программное обеспечение реализуется в виде некоторого множества проектов IDE.

Компилятор СИ/С++

Компоновщик (Linker)

Редактор текстов (Text

Editor)

Символичес­кий отладчик

(Debugger) Tools

Рис. 106. Интегрированная среда проектирования программ MS Visual Studio С-ь+ 6.0

Создание нового проекта для консольного прило:исения. Для того чтобы создать новое приложение (программу), необходимо соз­дать новый проект. Для этого в IDE выполните команду New... из меню File, в результате чего на экране появится диалоговое окно New.

В этом окне необходимо выполнить следующее: • Выбрать тип создаваемого приложения. В данном случае

следует выбрать опцию Win32 Console Application, поскольку мы создаем консольное приложение, которое является Windows-аналогом старого доброго знакомого - программы для MS DOS.

• Выбрать место расположения нового проекта. Информация о расположении новой рабочей области проекта (диск:\путь\подкаталог) вводится в поле Location (местоположение).

409

Page 411: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Это можно сделать, набрав путь вручную, или воспользовавшись расположенной справа кнопкой Browse.. . (просмотр). Разумеется, что соответствующий подкаталог должен быть предварительно соз­дан.

• Указать утилите Project имя файла проекта. Одновременно с вводом имени проекта в поле Project Name (имя проекта) это же имя автоматически добавляется в качестве подкаталога в поле Loca­tion.

После выполнения указанных действий для создания проекта следует нажать кнопку [ОК], в результате чего на экране появится диалоговое окно мастера создания консольного приложения. В этом окне выбираем переключатель An empty project (пустой проект). При этом создаются только служебные файлы проекта. Для того чтобы наполнить созданный проект, необходимо добавить в него файл(ы), содержащий(ие) текст программы.

Это можно сделать двумя способами. 1. Добавить в проект уже существующий файл(ы), создан-

ный(ые) ранее в текстовом редакторе и имеющий(ие) расширение *.с/?/?. Повторно обращаем внимание на то, что следует предвари­тельно поместить существующий(ие) файл(ы) в каталог проекта (лучше все иметь в одном месте).

2. Создать новый файл и вставить его в проект. Добавление в проект существующего файла. Для этого необ­

ходимо выбрать в меню Project пункты Add to Project (добавить в проект) и Files.... В результате этих действий на экран будет выве­дено диалоговое окно Insert Files into Project (добавление файлов в проект). Здесь следует выбрать те файлы, имеющие расширение .срр, которые хотите включить в проект. Это можно сделать, либо дважды щелкнув кнопкой мыши на имени файла, либо выделив нужные файлы и нажав кнопку [OKJ.

Создание нового файла и включение его в проект. Для созда­ния нового файла необходимо из меню File выполнить команду New... и в появившемся диалоговом окне New выбрать вкладку Files, где представлены все типы файлов, которые можно создавать. Флажок Add to Project (добавить в проект) должен быть установ­лен, чтобы создаваемый файл автоматически был добавлен в проект. В списке Files выберите тип создаваемого файла — C/C++ Header File или C++ Source File, а в поле File name: - имя файла. Осталось нажать кнопку [ОК]. В результате Visual C++ создаст файл и откро­ет пустое поле редактирования текста.

После набора и сохранения всех текстов можно переходить к следующему этапу - отладке программного проекта.

410

Page 412: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Открытие для работы существующего проекта. Для суще­ствующего проекта необходимо из меню File выполнить команду Open Workspace ... и в появившемся диалоговом окне Open Work­space войти в каталог проекта и "кликнуть" по файлу с расширением .dsw. В результате проект загружается в IDE для последующей рабо­ты.

П.2.2. IDE Borland C++ 3.1. Создание программного проекта

Создание нового проекта П08'Прило:>§сения, Для того чтобы создать новое приложение (программу), необходимо создать новый проект. Для нового проекта следует предварительно создать каталог. С помощью встроенного в IDE текстового редактора в каталоге про­екта следует создать файлы проекта с исходным текстом с расшире­ниями *.срр, *.h или *.hpp.

После создания исходных файлов надо создать файл проекта. Для этого надо выбрать в меню Project команду Open Project..., в появившемся окне Open Project File ввести имя файла проекта и нажать кнопку [ОК].

Для включения в проект файлов с расширениями *.с/?р (в ча­стном случае в проекте такой файл может быть единственным) сле­дует на рабочем столе активизировать окно Project, выбрать в меню Project команду Add Item..., в появившемся окне Add to Project List "кликнуть" по каждому из файлов с расширением *.ср/7 и на­жать кнопку [Done]. В результате этого будет создан требуемый программный проект.

После создания программного проекта необходимо проверить и, при необходимости, скорректировать информацию о местополо­жении каталогов стандартных включаемых файлов. С этой целью достаточно в меню Options выполнить команду Directories, в поя­вившемся окне Directories указать расположение каталогов стан­дартных включаемых файлов и нажать кнопку [ОК]. После этого программный проект готов к работе.

Открытие для работы существующего проекта DOS-прилоэн:ения. Для существующего проекта необходимо из меню Project выполнить команду Open Project..., в появившемся окне Open Project File в каталоге проекта выбрать имя файла проекта и нажать кнопку [ОК]. В результате проект загружается в IDE для по­следующей работы.

411

Page 413: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Приложение П.З. Рекомендации по структуре однофайловой программы с одной функцией

и пример оформления исходного текста

приведенный ниже пример оформления исходного текста про­стейшего однофайлового программного проекта с единственной функцией попутно преследует и другую цель — он демонстрирует какой должна быть структура программы:

Файл TASK01.CPP

Проект

Назначение

Состав проекта

ЭВМ

Среда программирования:

Операционная система

Дата создания Дата корректировки

однофайловый с единственной функцией (главной)

пример простой программы г вычисляющей с := а + b

файл проекта TASK01.PRJ/ файл TASK01.СРР (главная функция); файл TASK01.DAT (файл данных); файл TASK01.OUT (файл результатов)

IBM 80386

ВС31 (C++)

DR DOS 6.0

08.11.2000

Иванов И. И,, каф. АВТ, ФТК, гр. 1081/4 Санкт-Петербургским государственный политехнический университет

V

// Для работы с функциями ввода-вывода: STanDard Input Output ^include <stdio.h>

±nt main( void. ) // Возвращает 0 при успехе {

±nt a, Ь, // Аргументы функции с, // Значение функции ret code; // Возвращаемое значение для fscant

FILE *£_±Пг // Указатель на структуру со // сведениями о файле для чтения

*f_out; // Указатель на структуру со // сведениями о файле для записи

// Открываем файл для чтения ±£( ( f_ln = fopen( "task01.dat", "г" ) ) == NULL ) {

printf( "\n Ошибка 10. Файл taskOl.dat для чтения не" " открыт \п" ) ;

412

Page 414: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

retvLm 10; }

// Читаем значения аргументов функции retcode = fscanf( f_±n, " %i %i", &a, &b ) ; ±£( retcode != 2 ) {

printf( "\n Ошибка 20. Произошла ошибка чтения из" " файла taskOl.dat \п" ) ;

jtretujm 20; }

// Закрываем файл для чтения ±f( fclose( f_in ) == EOF ) {

printf( "\n Ошибка 30. Файл task01.dat не " "закрыт \п" ) ;

return 30; }

// Открываем файл для записи з.£( ( f_out = fopen( "task01.out"r "w" ) ) == NULL ) {

printf( "\n Ошибка 40. Файл taskOl.out для записи " "не открыт \п" ) ;

return 40; }

// Печатаем заголовок и аргументы функции fprintf( f_out,

"\п Иванов И. И. г каф. АВТ, ФТК, гр. 1081/4 "\п С.-Петербургский государственный политехнический унверситет " "\п (семестр 1, программный проект 1) \п" "\п с : = а + b \п" "\п Аргументы функции: a=%i b=%i \л", а, b ) ;

// Закрываем файл для записи ±f( fclose( f_out ) -= EOF ) {

printf( "\n Ошибка 50. Файл taskOl.out не закрыт \n" ) ; return 50;

}

// Вычисляем значение функции - в этом месте обычно делается // довольно много работы: проверяется область допустимых // значений прочитанных данных (аргументов функции) и // выполняется решение задачи с = а + Ь;

// Открываем файл для дозаписи ±f( ( f_out = fopen( "taskOl. out ", "a" ) ) == NULL ) {

printf( "\n Ошибка 60. Файл taskOl.out для дозаписи " "не открыт \п" ) ;

return 60; }

// Печатаем значение функции fprintf ( f_outr

413

Page 415: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

"\n Значение функции: с=%1'\ с ) ;

// Закрываем файл для записи ±f( fclose( f_out ) == EOF ) {

printf( "\n Ошибка 10. Файл taskOl.out не закрыт \n" ) ; r&tuxm 10;

}

return 0;

Рекомендуем использовать этот пример как "образец для под­ражания". Обращаем внимание на следующие особенности.

1. В программе используется файловый ввод-вывод. Это наи­более рациональный способ ввода-вывода.

2. Программа имеет следующую структуру: • открытие файла данных для чтения, чтение из него данных и за­

крытие файла (обратите внимание, что файл данных закрывается сразу же после завершения из него чтения, а не в конце работы программы - так выгоднее);

• открытие файла результатов для записи, вывод в него заголовка и прочитанных данных и закрытие файла (обратите внимание, что файл результатов закрывается сразу же после завершения вывода в него значений прочитанных данных, а не в конце работы программы - так также выгоднее);

• проверка области допустимых значений прочитанных данных (это обязательно нужно делать) и содержательное решение зада­чи, которое в большинстве случаев является довольно сложным — поэтому-то на это время держать файл результатов открытым невыгодно;

• открытие файла результатов для дозаписи, вывод в него результа­тов решения задачи и закрытие файла.

3. Для обработки ошибок в программе используются значения, возвращаемые функциями библиотеки Си. Это нужно делать всегда.

4. Чтобы сделать программу наглядной, легко читаемой в про­грамме используется рациональная ступенчатая запись и коммента­рии. Никогда не пренебрегайте подобными элементами оформления и внимательно изучите пример с этой точки зрения.

Приложение П.4. Методика отладки программы

Известное высказывание о том, что после обнаружения по­следней ошибки в программе остается еще хотя бы одна, стало ак­сиомой. Поэтому отладке программы уделялось и уделяется боль-

414

Page 416: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

шое внимание. Как же вести отладку программы? Все обнаруживаемые в программе ошибки можно разделить на

три большие категории. 1. Синтаксические ошибки, которые автоматически выявляют­

ся на этапе компиляции. Уяснить смысл синтаксических ошибок и устранить их достаточно легко, так как здесь в качестве достаточно хорошего помощника выступает компилятор. В зависимости от язы­ка программирования, компилятор лучше или хуже выявляет такие ошибки. В ряде случаев синтаксическая ошибка в программе влечет за собой неадекватную реакцию компилятора. Например, отсутствие скобки часто приводит к тому, что компилятор обнаруживает ошиб­ку через десятки строк кода. В последнем случае можно рекомендо­вать одновременный набор открывающей и закрывающей скобок (например, { }) с последующим вводом текста между ними.

2. Логические (часто их также называют алгоритмическими) ошибки. Их бывает наиболее трудно обнаружить и исправить. Часть из них выявляется на этапе отладки, часть на этапе сопровождения, а некоторые приводят к тяжелым последствиям.

3. Информационные ошибки. В частности, к Появлению ин­формационных ошибок может привести отсутствие обработки оши­бок ввода-вывода, попытки деления на ноль, переполнение разряд­ной сетки компьютера и т.п. Для исключения и/или обработки ин­формационных ошибок в ряде случаев приходится значительную часть исходного кода программы отводить для всевозможных про­верок.

П.4.1. Компиляция и компоновка программного проекта. Устранение синтаксических ошибок

На этапе отладки необходимо устранить все синтаксические и большую часть логических ошибок в программном проекте, допу­щенных на предыдущих этапах создания приложения. Для этого не­обходимо скомпилировать каждый созданный файл. Это можно сде­лать несколькими способами.

• Скомпилировать каждый файл с расширением .срр. Для этой цели в IDE MS Visual Studio C++ 6.0 можно использовать команду Build I Compile имя_файла или комбинацию клавиш <CtrH-F7>, а в IDE Borland C++ 3.1 - команду Compile | Compile имя_файла или комбинацию клавиш <Alt+F9>. Компиляцию отдельного файла удобно использовать в больших проектах, чтобы сосредоточиться на конкретном файле. При этом следует иметь в виду, что ссылки меж­ду файлами не проверяются.

• Скомпилировать и скомпоновать все файлы проекта ("со­брать" или "построить" исполняемый файл), воспользовавшись для

415

Page 417: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

этого в IDE MS Visual Studio C++ 6.0 командами Build | Build имя_файла эквивалентно <F7> или Build | Rebuild All. Единствен­ным отличием этих команд является то, что команда Rebuild All не проверяет даты создания файлов проекта и компилирует все файлы, а не только те, которые были модифицированы после компиляции. Аналогичным образом, в IDE Borland C++ 3.1 можно использовать команды Compile | Маке имя_файла эквивалентно <F9> или Com­pile I Build All. В результате создается исполняемый файл с расши­рением .ехе,

• Можно сразу запустить приложение, выполнив в IDE MS Visual Studio C++ 6.0 команду Build | Execute имя_файла или по комбинации клавиш <Ctrl+F5>, а в IDE Borland C++ 3.1 -команду Run I Run имя_файла или по комбинации клавиш <Ctrl+F9>. Если в программный проект были внесены какие либо изменения, то в IDE MS Visual Studio C++ 6.0 на экране будет высвечено диалоговое окно с запросом на построение исполняемого файла. Для построе­ния указанного файла следует нажать кнопку [Да]. В IDE Borland C++ 3.1 подобный запрос не выполняется.

Если программный проект содержит синтаксические ошибки, то в IDE MS Visual Studio C++ 6.0 при выполнении любой из пред­ставленных команд информация об ошибках автоматически отобра­жается во вкладке Build окна Output, по умолчанию расположенно­го в нижней части окна IDE. Если окно Output было удалено с экра­на, то его можно вывести снова на экран с помощью команды View | Output или по комбинации клавиш <Alt+2>. Каждое сообщение об ошибке или предупреждении начинается с имени файла, где они об­наружены, за которым следует номер строки, где это произошло, а далее идут двоеточие и слово "error" (ошибка) или "warning" (пре­дупреждение) и соответствующий номер. В конце приводится крат­кое описание ошибки или предупреждения. Если дважды щелкнуть левой кнопкой мыши на строке с сообщением или предупреждени­ем, то ошибочная строка будет отмечена стрелкой в левой части в соответствующем окне редактирования. Лучше всего добиться, что­бы в окончательном варианте не было ни того, ни другого, хотя с предупреждениями исполняемый файл создается и может быть за­пущен.

Аналогичным образом, в IDE Borland C++ 3.1 при наличии синтаксических ошибок при выполнении любой из представленных выше команд информация об ошибках автоматически отображается в появившемся окне Message. Каждое сообщение об ошибке или предупреждении начинается со слова "error" (ошибка) или "warning" (предупреждение), за которым следуют имя файла и но­мер ошибочной строки, а далее идут двоеточие и приводится крат­кое описание ошибки или предупреждения. Если дважды щелкнуть

416

Page 418: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

левой кнопкой мыши на строке с сообш^ением или предупреждени­ем, то в соответствуюш[ем окне редактирования в ошибочную строку будет помещен курсор, а текст сообщения будет повторен в нижней части окна редактирования.

После устранения синтаксичесих ошибок следует запустить программу, выполнив команду Build | Execute имя__файла либо по комбинации клавиш <Ctrl+F5> (IDE MS Visual Studio C++ 6.0) или команду Run | Run имя_файла либо по комбинации клавиш <CtrI+F9> (IDE Borland C++ 3.1). При этом также можно получить сообщение об ошибке (или ошибках). Это тот самый случай, когда программный проект не содержит синтаксических ошибок, а при­ложение не работает. Вызвано это так называемыми логическими (алгоритмическими) ошибками, для обнаружения которых можно использовать разные методы (например, закомментировать фраг­менты программы).

Однако лучше всего воспользоваться имеющимся в IDE встроенным отладчиком.

П.4.2. Отладка программного проекта. Устранение логических (алгоритмических) ошибок

Встроенный отладчик предоставляет следующие возможности. • Пошаговое выполнение программы. а Просмотр значений переменных в любом месте программы. Для пошагового выполнения программы отладчик предостав­

ляет несколько возможностей, основные из которых мы и рассмот­рим вначале для IDE MS Visual Studio C++ 6.0, a затем и для IDE Borland C++3 .1 .

Встроенный отладчик IDE MS Visual Studio C++ 6,0. Для за­пуска исполняемого файла в режиме отладки можно выполнить ко­манду Build I Start Debug | Go (выполнить) или эквивалентно на­жать клавишу <F5>. Однако если просто выполнить эту команду, не предпринимая никаких предварительных действий, работа програм­мы не будет отличаться от запуска в обычном режиме, разве что при завершении во вкладке Debug в нижней части окна Output интегри­рованной среды разработки появится информация о параметрах за­вершения работы программы.

Чтобы перейти в режим пошагового выполнения, предвари­тельно перед выполнением команда Go необходимо установить так называемые точки останова {breakpoints), которые можно рассмат­ривать как стоп-сигналы для отладчика. Обычно они устанавлива­ются в местах, которые вызывают сомнение в правильности выпол­нения. При этом предполагается, что все операторы, предшествую-

417

Page 419: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ш ие первой точке останова, выполняются правильно. Самый про­стой способ установки точки останова заключается в следующем. Курсор устанавливается на строку, на которой нужно остановить работу программы, и нажимается клавиша <F9>. Повторное нажатие клавиши <F9> удаляет точку останова. Строка останова в окне ре­дактирования отмечена темно-красным кружком в крайней левой позиции. Если, после задания точки останова, программу запустить по команде Build | Star t Debug | Go, либо нажав клавишу <F5>, то все операторы программы, предшествующие точке останова, будут выполняться в обычном режиме и только перед строкой останова выполнение программы приостановится.

При этом внешний вид интегрированной среды разработки существенно изменится. Во-первых, изменипся состав основного меню. Во-вторых, строка, которая будет выполняться следующей, будет отмечена желтой (по умолчанию) стрелкой. И, наконец, поя­вится два новых окна - Variables (переменные) и Watch (наблюде­ние), которые позволяют просматривать и менять значения пере­менных. Если одно из окон или оба окна на экране отсутствуют, то их можно поместить на экран, используя комбинации клавиш <Alt-4-3> для переменных и/или <Alt+4> для наблюдения.

Для пошагового выполнения в отладчике имеются следующие команды.

• Debug I Step Over (шаг через) или эквивалентно <F10> -выполняет текущий оператор или функцию и переходит к следую­щей строке.

• Debug I Step Into (шаг внутрь) или эквивалентно <F11> -выполняет текущий оператор языка C++ или переходит к первому оператору функции.

а Debug | Step Out (шаг вне) или эквивалентно <Shif t+Fl l> -завершает выполнение текущей функции и переходит к строке, не­посредственно следующей за ее вызовом.

• Debug I Run to Cursor (выполнить до курсора) или эквива­лентно <Ctrl+F10> - выполняет программу до строки, где в текущий момент находится курсор.

В окне Variables (переменные) автоматически отображаются только локальные переменные текущего блока. После каждого шага выполнения программы значения этих переменных обновляются. В строке Context указывается, в какой функции (блоке) в данный мо­мент находимся.

Переменные, которые нужно контролировать или изменять по желанию программиста, можно задать в окне Watch (наблюдение). Для этого в свободной строке столбца Name для контроля значения переменной достаточно набрать идентификатор переменной и на-

418

Page 420: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

жать клавишу [Enter]. Для изменения значения переменной в про­цессе отладки следует выбрать строку с именем этой переменной, с помощью клавиши [Tab] перейти в столбец Value, набрать там но­вое значение и нажать клавишу [Enter]. При дальнейшей отладке, вместо прежнего значения, будет использовано модифицированное таким образом значение переменной.

Для просмотра значения переменной в реэюиме отладки, наря­ду с использованием окон Variables и Watch, можно в окне редак­тирования поставить курсор на имя интересующей нас переменной. Если переменной было присвоено значение, то появится всплываю­щее окно со значением этой переменной Эта возмолсностъ наибо­лее удобна и мы рекомендуем ее использовать как моэюно чаще.

Встроенный отладчик IDE Borland C++ 5.7. Для запуска ис­полняемого файла в режиме отладки следует также предварительно задать точки останова (breakpoints). Предполагаем, что все опера­торы, предшествующие первой точке останова, выполняются пра­вильно. Самый простой способ установки точки останова заключа­ется в следующем. Курсор устанавливается на строку, на которой нужно остановить работу программы, и вводится комбинация кла­виш <Ctrl+F8>. Повторный ввод этой комбинации удаляет точку останова. Строка останова в окне редактирования отмечена красным цветом. Если, после задания точки останова, программу запустить по команде Run | Run, либо введя комбинацию клавиш <Ctrl+F9>, то все операторы программы, предшествующие точке останова, бу­дут выполняться в обычном режиме и только перед строкой остано­ва выполнение программы приостановится. При этом строка остано­ва сохранит подсветку, но изменит цвет подсветки.

Для пошагового выполнения в отладчике имеются следующие команды.

а Run I Trace over (шаг поверх) или эквивалентно <F8> - вы­полняет текущий оператор или функцию и переходит к следующей строке.

а Run I Trace into (шаг внутрь) или эквивалентно <F7> - вы­полняет текущий оператор языка C++ или переходит к первому опе­ратору вызываемой функции.

• Run I Go to cursor (выполнить до курсора) или эквивалент­но <F4> - выполняет программу до строки, где в текущий момент находится курсор.

Переменные, которые нужно контролировать в точке останова можно посмотреть в окне Watch. Чтобы это окно появилось в IDE и отображало значение требуемого объекта, достаточно поместить курсор на идентификатор объекта, ввести комбинацию клавиш <Ctrl+F7> и в появмвшемся окне Add Watch нажать кнопку [ОК].

419

Page 421: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

П.4.3. Тестирование программного проекта

Как и любой другой продукт производства, программа перед использованием должна быть тщательно проверена. Этот этап явля­ется едва ли не самым сложным во всем процессе создания про­граммы - необходимо учесть все варианты ее поведения. Поэтому его нужно начинать не после завершения отладки, а одновременно с разработкой алгоритма.

Одним из путей проверки или тестирования программы явля­ется ее выполнение по одному разу с каждой из возможных комби­наций входных данных — т.е. тестирование с использованием зара­нее подготовленного набора контрольных примеров.

Требования к контрольным примерам. Какие же требования следует предъявить к контрольным примерам?

Таких требований всего два. • Набор контрольных примеров должен быть достаточным,

чтобы показать выполнение всех требований технического задания и обеспечить полную проверку программного проекта — протестиро­вать все ветви, имеющиеся в программе.

• Контрольные примеры должны быть простыми в том смыс­ле, чтобы анализ ожидаемых результатов был несложным (примеры должны быть небольшой размерности со значениями исходных дан­ных, удобными для анализа).

Отметим, что создание достаточного и простого набора кон­трольных примеров является нетривиальной задачей.

Структура контрольного примера. Структура контрольного примера может быть, например, следующей: • цель примера; • исходные данные (для примеров с нормальным завершением

привести ссылку на листинг файла данных) или как моделировать ошибку (для примеров с аварийным завершением);

• анализ ожидаемых результатов (для примеров с нормальным за­вершением - анализ ожидаемых результатов в точках останова);

• полученные результаты, выводы (для примеров с нормальным за­вершением привести ссылку на листинг файла результатов).

Выбор точек останова. При выборе точек останова можно руководствоваться следующими основными правилами: • точки останова следует выбирать после выполнения каждой

420

Page 422: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

функции программного проекта (в них следует проверить результаты работы функции);

• если декомпозиция задачи выполнена не очень удачно и функция получилась большой (более страницы текста), то следует в ее теле выбрать промежуточные точки останова, разбив тело функции на функционально законченные части;

• если функция была отлажена ранее, то после нее точку останова выбирать не следует;

• если функция результаты своей работы выводит в файл на маг­нитном диске, на экран или на принтер, то после такой функции точки останова тоже не нужны.

Методика тестирования программы для контрольных при-меров с нормальным завершением.

При тестировании программы выполняются следующие шаги. • Программа запускается до первой точки останова так, как

это указывалось выше. Если полученные машинные результаты сов­падают с результатами анализа, приведенного в контрольном при­мере, то аналогично программа запускается до следующей точки ос­танова и т.д.

• Если в очередной точке останова машинные результаты от­личаются от ожидаемых, то текущий сеанс отладки с помощью ко­манды Debug I Stop Debugging (IDE MS Visual Studio C++ 6.0) или Run I Program Reset (IDE Borland C++ 3.1) прекращается. Програм­ма повторно запускается до последней точки останова с хорошими результатами и с этого места выполняется по шагам с проверкой по­лученных результатов (выполняется трассировка ошибочного участ­ка). По результатам пошаговой проверки обнаруживается ошибка и текущий сеанс отладки также прекращается. Затем в исходный текст программы вносятся необходимые исправления и трассировка оши­бочного участка повторяется. Этот процесс заканчивается после ис­правления ошибок, о чем будет свидетельствовать получение в оче­редной точке останова ожидаемых результатов.

Приложение П.5. Рекомендации по созданию многофайлового программного проекта

с несколькими функциями и пример оформления исходного текста.

П.5.1. Спецификация программного проекта

Работа над программным проектом начинается с разработки

421

Page 423: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

его спецификации. Спецификация программного проекта включает требования к обработке ошибок и предупрелсдений, а такэюе све­дения о файловом и функциональном составе программного проек­та и о взаимодействии функций проекта друг с другом.

Прежде всего рассмотрим, что же понимается под предупреж­дениями и ошибками. Предупреэюдение необходимо выдавать при наступлении некоторого события, которое требует информирования пользователя, но не препятствует продолжению работы программы. Ошибка возникает при наступлении события, когда дальнейшая ра­бота программы невозможна. При обработке ошибок и предупреж­дений для каждого предупреждения или сообщения об ошибке в на­чале диагностического сообщения следует напечатать номер преду­преждения или сообщения об ошибке, обеспечив нумерацию в воз­растающем порядке. Предупреждения, как правило, следует выда­вать в файл результатов, а сообщения об ошибках - на экран.

Основные особенности использования функций в программ­ных проектах рассмотрены выше в подразд. 2.1 и 3.5. Функциональ­ный типовой состав программного проекта, как минимум, включает главную функцию, из которой последовательно вызываются функ­ции ввода исходных данных, их печати, решения задачи и печати полученных ответов. Для удобства использования функций универ­сальные функции с широкой областью применения целесообразно размещать в отдельных файлах, причем взаимосвязанные универ­сальные функции можно помещать в отдельный общий файл. Спе­циализированные же функции, напротив, размещают обычно в том же файле, где находится главная функция программного проекта.

Приведем пример спецификации программного проекта для решения следующей задачи. Выполнить обработку матрицы, заклю­чающуюся в том, что в каждой строке матрицы ищется максималь­ный элемент. Элементы, стоящие после максимального элемента, следует заменить нулями и поместить в начало строки. Исходную и вновь полученную матрицы напечатать. Предусмотреть запуск про­граммного проекта с использованием командной строки. В файле исходных данных последовательно содержатся строчный размер матрицы, число столбцов матрицы и значения элементов матрицы, разделенные символами пробельной группы (' ', '\^', '\«'). Матрица размещается в статической памяти.

Файловая и функциональная структура программного проекта (рис. 107).

422

Page 424: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Файл Main.cpp

main() Главная функция

Proglnfo() Информация о прог­рамме и командной

строке

ReadMatrix() Чтение матрицы

WriteMatrix() Печать матрицы с

заголовком

SwapUnits() Перестановка

элементов строк

Файл ErWarnW.cpp Файл CheckCS.cpp

ErrorWarningWork() Обработка ошибок и

предупреждений

CheckComString() Контроль командной

строки

OpenFile() Открытие файла

Файл Matrix.срр ^ Вызов функции

Возврат из функции

1, 2, ..., 6 - порядок вы­зова функций

Функция OpenFile() вызывается из функций ErrorWarningWork(), ReadMatrix() и WriteMatrix()

Функция CloseFile() вызывается из функций ErrorWarningWork(), ReadMatrix() и WriteMatrix()

Функция ErrorWarningWork() вызывается из функ­ций ReadMatrix() и WriteMatrix()

CloseFile() Закрытие файла

Файл FileOC.cpp

Рис. 107. Файловая и функциональная структура программного проекта

Желаемый состав и интерфейс функций. Чтобы функции ввода и печати массива стали универсальными, надо их снабдить следующим интерфейсом:

// Прототипы функций void ReadArray(

±nt Arr[ N ], // Вводимый массив // Файл ввода СЪАГ Filelnp[ ] ) ;

void. PrlntArray ( int Arr [ N ] r // Выводимый массив // Файл вывода char FileOutf 7, cha.r Mode [ ] r // Режим открытия файла вывода

423

Page 425: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

/ / Заголовок для печати char Header[ ] ) ;

Перечисленные функции целесообразно разместить в отдель­ном файле как универсальные и взаимосвязанные.

Типовыми универсальными операциями являются операции открытия-закрытия файлов. Поэтому их следует реализовать в виде функций, расположенных в общем отдельном файле:

FILE * OpenFile ( // Возвращает указатель на структуру // со сведениями об открытом файле

// Открываемый файл сЬлг FileName[ ], сЬа.г Mode [ ] , // Режим открытия файла хпЬ ErrCode ) ; / / Код ошибки

void CloseFile( // За крыв аемый файл char FileName [ ], // Указатель на структуру со сведениями о закрываемом // файле FILE *pStrInfoFlle, int ErrCode ) ; / / Код ошибки

Об использовании командной строки при запуске программного проекта. С этой целью можно использовать, например, командную строку следующего вида:

Task02.еке Task02.1пр Task02,out [Enter]

При этом заголовок главной функции может иметь следующий вид:

int main ( // Возвращает О при успехе ±пЬ АгдС, // ARGument Count: число аргументов

// командной строки (в примере 3) char *ArgV[ ] ) / / Argument Value: массив указателей

// на аргументы командной строки // ( в примере ArgV [ 1 ] // эквивалентно "Task02,1пр"^ // ArgVf 2 ] эквивалентно // "Task02.out")

Для обработки ошибок в формате командной строки можно использовать функцию следующего вида (эта функция универсальна и ее целесообразно поместить в отдельный файл):

лго±<1 WorkCS ( int ArgC, // Число аргументов командной строки

424

Page 426: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±nt ErrCode ) // Код ошибки (

±f( ArgC != 3 ) {

printf( "\n Ошибка %d. Непредусмотренный формат командной строки. " " \л Для запуска программы используйте командную строку вида:" "\п Т4сполняемый_файл Файл_ввода Файл_вывода'\ ErrCode ) ;

e x i t ( ErrCode ) ; }

Об обработке ошибок и предупреждений. Чтобы многократно не дублировать аналогичные фрагменты, целесообразно использовать для обработки ошибок и предупреждений универсальную функцию, кото­рую следует поместить в отдельный файл. Эта функция может иметь, например, такой вид:

// Прототип •vo±(X ErrWarnWork ( xnt ErrWarnCode, сЬат Msg[ ] , char Type^

cbar FileOutl ] = "", сЬаг Mode[ ] = "" ) ;

// Определение void ErrWarnWork(

// Код ошибки или предупреждения ±nt ErrWarnCode,

Строка сообщения 'е' - ошибка, сообш,ение выдается

на экран, 'w' - предупреждение, сообш,ение выдается в файл на МД

// Файл вывода char FlleOutf ], cha.r Mode [ ] ) // Режим открытия файла вывода

swibch( Туре ) { ca.se 'е' :

printf( "\п Ошибка %d. %s ", ErrWarnCode, Msg ) ; exit ( ErrWarnCode ) ;

case 'w*:

// Здесь открывается файл FileOut в режиме Mode // (получаем указатель pFileOut с типом FILE *) fprintf ( pFileOut, "\п Предупреждение %d. %s ",

ErrWarnCode, Msg ) ; // Здесь закрывается файл с указателем pFileOut break;

cLefavLl Ь:

printf(

425

char char

Msg[ ], Type,

// // // //

Page 427: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

"\п Ошибка %d. Использован недопустимый тип сообщения:" "\п используйте \'е\' или \'w\' ", ErrWarnCode ) /

exit( ErrWarnCode ) ; }

retvLrn; }

// Пример вызова для предупреждения // Для сообш,ения ch&r buff 200 ]; // Формируем текст предупреждения sprint f ( bufr " . . . Управляюш,ая строка с форматами . . . " ,

список аргументов для форматов ) ; ErrWarnWork( 40, buf, 'W, ArgV[ 2 ], "a" ) ;

// Пример вызова для ошибки // Формируем текст сообщения об ошибке sprint f ( buf, " . . . Управляющая строка с форматами . . .",

список аргументов для форматов ) ; ErrWarnWork( 50, buf, 'е' ) ; // !!! Два последних аргумента не нужны и их не записываем. // Так можно делать, так как 2 последних параметра имеют // значения по умолчанию - см. прототип

Обработка ошибок и предупреждений,

1. Ошибка открытия файла. При возникновении данной ошибки программа прерывает ра­

боту, выдавая на экран следующее сообщение: "Ошибка № XX. Ошибка открытия файла <имя.расширение> для чте­ния/записи/дозаписи. "

Код ошибки и код возврата задаются в вызове функции откры­тия файла,

2. Предупреждение о том, что файл не закрыт. При возникновении данной ситуации программа выдает на эк­

ран следующее сообщение: "Предупреждение № XX. Файл <имя.расширение> не закрыт. Выпол­нение программы продолжено."

Код предупреждения задается в вызове функции закрытия файла.

3. Неверный режим открытия файла. При возникновении данной ошибки программа прерывает ра­

боту, выдавая на экран следующее сообщение: "Ошибка № XX. Использован непредусмотренный режим <режим> от­крытия файла <имя.расширение>. Используйте режимы "г", "rt", "w", "wt", "а" или "at" (на любом регистре) . "

Код ошибки и КОД возврата задаются в вызове функции закры-

426

Page 428: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

тия файла.

3. Неверный тип выдаваемого сообщения. При возникновении данной ситуации программа выдает на эк­

ран^ следующее сообщение: "Предупреждение № XX. Использован непредусмотренный <тип> вы­даваемого сообщени вместо 'е' или 'w'. Применен режим 'w', выполнение программы продолжено, "

Код предупреждения задается в вызове функции обработки предупреждений и сообщений об ошибках.

4. Недопустимое значение количества строк матрицы. При возникновении данной ситуации программа продолжает

работу, выдавая в файл результатов следующее сообщение: "Предупреждение № XX. Из файла <имя.расширение> прочитано не­допустимое значение количества строк матрицы, равное ... (коли­чество строк должно лежать в диапазоне от 2 до ...) . Принимает­ся количество строк 2 , выполнение программы продолжается."

Код предупреждения задается в вызове функции обработки предупреждений и сообщений об ошибках.

5. Недопустимое значение количества столбцов матрицы. При возникновении данной ситуации программа продолжает

работу, выдавая в файл результатов следующее сообщение: "Предупреждение 1? XX. Из файла <имя.расширение> прочитано не­допустимое значение количества столбцов матрицы, равное (количество столбцов должно лежать в диапазоне от 2 до ...) .

Принимается количество столбцов 2, выполнение программы про­должается. "

Код предупреждения задается в вызове функции обработки предупреждений и сообщений об ошибках.

6. Ошибка чтения значения количества строк матрицы. При возникновении данной ошибки программа прерывает ра­

боту, выдавая на экран следующее сообщение: "Ошибка I? XX. Ошибка чтения значения количества строк матрицы из файла <имя.расширение>."

Код ошибки и код возврата задаются в вызове функции обра­ботки предупреждений и сообщений об ошибках.

7. Ошибка чтения значения количества столбцов матрицы. При возникновении данной ошибки программа прерывает ра­

боту, выдавая на экран следующее сообщение: "Ошибка № XX. Ошибка чтения значения количества столбцов мат­

рицы из файла <имя.расширение>." Код ошибки и код возврата задаются в вызове функции обра­

ботки предупреждений и сообщений об ошибках.

427

Page 429: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

8. Ошибка чтения значения элемента матрицы. При возникновении данной ошибки программа прерывает ра­

боту, выдавая на экран следующее сообщение: "Ошибка № XX. Ошибка чтения значения элемента матрицы с номе­

ром строки ... и номером столбца ... из файла <имя.расширение>. " Код ошибки и код возврата задаются в вызове функции обра­

ботки предупреждений и сообщений об ошибках.

9. Преждевременный конец файла исходных данных. При возникновении данной ситуации программа продолжает

работу, выдавая в файл результатов следующее сообщение: "Предупреждение № XX. Файл ввода <имя .расширение> содержит недостаточное количество данных (преждевременный конец фай­ла) . Непрочитанные элементы матрицы инициализируются нулями. Выполнение программы продолжается."

Код предупреждения задается в вызове функции обработки предупреждений и сообщений об ошибках.

10. Избыточное количество данных в файле исходных данных. При возникновении данной ситуации программа продолжает

работу, выдавая в файл результатов следующее сообщение: "Предупреждение № XX. Файл ввода <имя .расширение > содержит лишние данные г которые игнорируются программой. Выполнение программы продолжается."

Код предупреждения задается в вызове функции обработки предупреждений и сообщений об ошибках.

11. Неверное количество аргументов командной строки. При возникновении данной ошибки программа прерывает ра­

боту, выдавая на экран следующее сообщение: "Ошибка № XX. Использован неверный формат командной строки.

Обработка матрицы, заключающаяся в том, что в каждой строке матрицы ищется максимальный элемент. Элементы, стояш^е после максимального элемента, заменяются нулями и помещаются в начало строки. Исходная и вновь полученная матрицы печата­ются. Запуск программы выполняется с использованием командной строки вида: имя_выполняемого_файла . ехе файл__ввода файл_вывода . "

Код ошибки и код возврата задаются в вызове функции обра­ботки командной строки.

П5.2. Пример оформления исходного текста программы

/* Файл TASK02.CPP

Проект : многофайловый с функциями.

428

Page 430: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

расположенными в отдельных файлах

Назначение : вычисление площади садового участка Square := Length * Width

Состав проекта (файл проекта TASK02.PRJ) : файл TASK02.СРР (главная функция

проекта); файл TASK02_1. СРР (ввод длины и ширины садового участка) ; файл TASK02_2.СРР (печать длины и

ширины садового участка) ; файл TASK02_3.СРР (вычисление площади садового участка) ; файл 4. СРР (печать площади садового участка)

ЭВМ : IBM 80386

Среда программирования: ВС31 (C++)

Операционная система : DR DOS 6.О

Дата создания : 02.11.2002 Дата корректировки

Иванов И. И., ФТКг гр. 1081/4 Санкт-Петербургский государственный политехнический университет

_V

// Стандартные включаемые файлы и прототипы функций iinclude "task02.h"

±nt main ( void ) // Возвращает О при успехе {

float Lenght,• // Длина садового участка Width, // Ширина садового участка Square; // Площадь садового участка

// Ввод длины и ширины садового участка ReadData( Lenght, Width ) ;

// Печать длины и ширины садового участка WriteDat( Lenght, Width ) ;

// Вычисление площади садового участка Area ( Lenght, Width, Square ) ;

WriteRes ( Square ) ; // Печать площади садового участка

jcebvLxrn. О; } // Конец файла TASK02.CPP

429

Page 431: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Файл TASK02.H Включаемый файл для проекта TASK02.PRJ:

ные включаемые файлы и прототипы функций. содержит стандарт-

// предотвращение многократного включения данного файла #ifndef TASK02__H

^define TASK02_H

^include <stdio.h> // Для функций ввода-вывода ^include <stdlib.h> // Для функции exit

// Прото типы функций void. ReadData ( float &Length, float &Width ); void WriteDat( float Length, float Width ); void. Area ( float Length, float Width, float &Square ) void WriteRes( float Square );

#endif // Конец файла TASK02.H

Файл TASK02_1.CPP Чтение исходных данных из файла TASK02.DAT. Используется в

поограммном проекте TASK02.PRJ V // Стандартные включаемые файлы и прототипы функций ^include "task02,h"

void ReadData( float &Lenght, // Длина садового участка float &Width ) // Ширина садового участка

{ FILE *f__in; // Указатель на структуру со

II сведениями о файле для чтения

// Открываем файл для ввода ±f( ( f_in = fopen( "task02.dat", "г" ; ; =- NULL ) {

printf( "\n Ошибка 10. Файл task02,dat для чтения не" " открыт \п" ) ;

exit ( 10 ) ; }

// Читаем данные ±f( fscanf( f__in, " %f %f", &Lenght, &Width ) != 2 ) (

printf( "\n Ошибка 20. Произошла ошибка чтения из" " файла task02.dat \п" );

exit ( 20 ); }

// Закрываем файл для чтения

430

Page 432: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

±f( fclose ( f_in ) == EOF ) {

printf( "\n Ошибка 30. Файл task02.dat не " "закрыт \n" ) ;

exit ( 30 ); }

iretuirn/ / / Конец файла TASK02_1.CPP

/* Файл TASK02 2.CPP Печать исходных данных в файл

программном проекте TASK02.PRJ TASK02.RES. Используется в

// Стандартные включаемые файлы и прототипы функций ^Include "task02.h"

void WrlteDat( float Lenght^ // Длина садового участка float Width ) // Ширина садового участка

( FILE *f_out/ // Указатель на структуру со

// сведениями о файле для записи

// Открываем файл для записи и печатаем исходные данные ±f( ( f_out = fopen( "task02.res", "w" ) ) == NULL ) {

printf( "\n Ошибка 40. Файл task02.res для записи не" " открыт \п" );

exit ( 4 0 ); } fprlntf ( f_OUtr

"\n Вычисление площади садового участка \п" "\п Длина садового участка: %д " "\п Ширина садового участка: %д'\ Lenght, Width );

// Закрываем файл для вывода if( fclose( f_out ) == EOF ) {

prlntfi "\n Ошибка 50. Файл task02.res не " "закрыт \n" ) /

exit ( 50 ) ; }

return/ } // Конец файла TASK02 2.GPP

Файл TASK02_3.CPP Вычисление плош,ади прямоугольника.

граммном проекте TASK02.PRJ Используется в про-

431

Page 433: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

// Стандартные включаемые файлы и прототипы функций iinclude "task02.h"

void. Area ( float Lenght, // Длина садового участка float Widths // Ширина садового участка float & Square ) // Площадь садового участка

{

// Вычисление площади садового участка Square = Lenght * Width;

retxum; } // Конец файла TASK02_3.CPP __

Файл TASK02_4.CPP Печать результатов в файл TASK02. RES. Используется в про­

граммном проекте TASK02.PRJ */

/ / Стандартные включаемые файлы и прототипы функций ^include "task02.h"

void WriteRes( float Square ) // Площадь садового участка

{

FILE *f_out; // Указатель на структуру со // сведениями о файле для записи

// Открываем файл для дозаписи ±f( ( f_out = fopen( "task02.res", "a" ) ) == NULL )

' {

printf( "\n Ошибка 60. Файл task02.res для дозаписи" " не открыт \п" ) ;

exit ( 60 ) ; }

// Печать результатов работы программы fprintf ( f_out,

"\п\п Площадь садового участка: %д", Square ) ;

// Закрываем файл ±f( fclose ( f_out ) = - EOF ) {

printf( "\n Ошибка 70. Файл task02.res не" " закрыт \n" ) ;

exit ( 70 ) ; }

return; } // Конец файла TASK02 4.CPP

432

Page 434: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

в приведенном примере рекомендуем. 1. Обратить внимание на оформление заголовочного файла и

его подключение к файлам проекта.

Повторим здесь еще раз, что обычно в заголовочный файл по­мещают директивы ^include', прототипы функций; определения встроенных (inline) функций; объявления (extern) данных, опреде­ленных в другом файле; определения (const) констант; перечисления (епит), директивы условной трансляции (#ifndef, i^endif VL др.), мак­роопределения (Udeflne), именованные пространства имен (name­space), определения типов (class, struct), объявления и определения шаблонов (template).

Заголовочные файлы никогда не должны содержать определе­ния невстроенных функций, определения данных (объектов), опре­деления массивов и неименованные пространства имен.

2. Обратить внимание на оформление функций и, особенно, на оформление заголовка функции.

3. Обратить внимание на оформление заголовочных коммента­риев файлов программного проекта.

Page 435: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Приложение П.б. Примерная программа дисциплины "Программирование и основы алгоритмизации".

МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ

УТВЕРЖДАЮ Начальник Управления образовательных программ и стандартов высшего и среднего профессиональ­ного образования

2000 г.

ПРИМЕРНАЯ ПРОГРАММА д и с ц и п л и н ы

'Программирование и основы алгоритмизации"

Рекомендуется Минобразованием России для подготовки бакалавров по на­правлению 5502 "Автоматизация и управление" (и подготовки специалистов по

направлению 6519 "Автоматизация и управление")

434

Page 436: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

1. Цели и задачи дисциплины Цель дисциплины состоит в поэтапном формировании у студентов следую­

щих слоев знаний и умений, • Слой 1: знание основных понятий программирования. • Слой 2: знание базового языка программирования. • Слой 3: умение решать задачи на вычислительных машинах ВМ.

Формированию отмеченных уровней (слоев) знаний и умений соответствуют разделы дисциплины. Изучение курса предполагает, что студенты знакомы с прин­ципами работы ВМ, десятичной, двоичной, восьмеричной и шестнадцатеричной системами счисления, а также основными понятиями информатики.

2. Требования к уровню освоения содержания дисциплины В результате изучения дисциплины студенты должны:

1. Знать основные понятия программирования. 2. Знать базовый язык программирования. 3. Уметь решать задачи на ВМ.

Примечание. Слой 3 в полном объеме и слой 4 знаний и умений -умение про­ектировать программное обеспечение — формируются у студентов в процессе изуче­ния дополнительной дисциплины "Технология программирования", которая являет­ся органическим продолжением данной.

3. Объем дисциплины и виды учебной работы Вид учебной работы

Общая трудоемкость дисциплины Аудиторные занятия Лекции Практические занятия (упражнения) Курсовая работа Самостоятельная работа Вид итогового контроля (зачет, экзамен)

Всего часов 130 68 34 17 17 62

Экзамен

Семестр 2 2 2 2 2 2 2

4. Содержание дисциплины 4.L Разделы дисциплины и виды занятий

iN» п/п

1 2

3

Раздел дисциплины

Основные понятия профаммрфования Си/С+-1-: базовый язык программирования (алфавит, синтаксис, типы данных, выражения и операции, структурное программирование, операторы ветвлений и циклов, массивы и структуры, модальное програм­мирование, функции, ввод и вывод, описатели класса хранения, области действия и время жизни данных, указатели, препроцессор) Прикладное профаммирование (сортировки массивов, рекурсия и итерация, транспортная задача, элементы обработки списков, работа с динамической памятью)

Лекции

* •

ПЗ (упражнения)

*

ЛР (курс, рабо­

та)

*

*

435

Page 437: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

4,2. Содермсание разделов дисциплины

Раздел 1. Основные понятия программирования (лекции и самостоятельная работа: 16 часов)

1. Алгоритм, данные, программа, структура данных. 2. Регистр: разрядность, графическое изображение. Совокупность регистров как ос­

нова оперативного запоминающего устройства (ОЗУ). Адрес, хранимое слово. Работа ОЗУ в режимах чтения и записи.

3. Простейший набор арифметических операций. Выполнение простейших арифме­тических операций.

4. Работа ВМ при последовательном выполнении команд. Ветвления в программах. Команды условных переходов и безусловной передачи управления.

5. Прямая и косвенная адресация. Зачем нужна косвенная адресация? 6. Классификация и краткая характеристика языков программирования. Машинно-

зависимые язьпси: машинные (О GL - О Generation of the Language), ассемблеры (1 GL) и макроассемблеры (2 GL). Машинно-независимые языки: процедурные (3 GL), проблемные (4 GL) и универсальные (5 GL).

7. Введение в языки ассемблера (1 GL). Операторы языка ассемблера ВМ с "очень простой" архитектурой. Символические команды. Псевдокоманды. Схема трансляции в два прохода.

8. Периферийные устройства ВМ и их разновидности. Накопители на магнитных дисках (НМД) с жёсткими дисками. Прямой и последовательный доступ к ин­формации. Монитор, работа в текстовом и графическом режимах. Клавиатура и мышь. Принтеры. Взаимодействие программ с ПУ.

9. Программные продукты. Основные виды, этапы проектирования и жизненный цикл.

Раздел 2. Си/С++: базовый язык программирования (лекции, практические занятия и самостоятельная работа: 80 часов)

Цель этой части курса - овладение подмножеством языка C++, изобразитель­ные средства которого обеспечиваются большинством языков третьего поколения (3 GL).

\. Программирование на языках высокого уровня (на примере Си/С++). Язык Си: история, первоначальная область применения (системное программирование). Принцип построения: компилируемые конструкции и интерпретируемые средст­ва (библиотека стандартных функций). Раздельная трансляция, компилятор и ре­дактор связей.

2. Алфавит языка. Способы описания синтаксиса языка: металингвистические фор­мулы и синтаксические диаграммы. Определение понятия "идентификатор". Служебные слова. Комментарии.

3. Типы данных. Имена и объявления. Целые типы данных: int, short, long, char. Кодовый формат. Описание данных, литералы. Тип целых констант. Арифмети­ческие операции для целых операндов.

4. Плавающие типы данных: float, double. Кодовый формат. Описание данных, ли­тералы. Тип констант с плавающей точкой. Арифметические операции для веще­ственных операндов. Математические функции стандартной библиотеки Си. На­значение стандартных заголовочных файлов. Компоновка программы из объект­ных модулей и библиотек.

5. Понятие преобразования данных. Зачем нужны преобразования? Примеры пре­образований. Явное преобразование типа. Правила преобразования операндов в

436

Page 438: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

процессе вычислений. 6. Оператор-выражение. Операции уменьшения и увеличения, префиксная и пост­

фиксная форма. Операции простого и составного присваивания. Приоритеты опе­раций.

7. Оператор-выражение. Операции отношения. Результат вычисления отношений. Представление булевских значений "ложь", "истина" в Си. Логические операции: !, II, &&. Операции простого и составного присваивания. Приоритеты операций.

8. Структурное программирование. Операторы ветвления: if и switch. Оператор if, синтаксис, выполнение оператора. Операции отношения. Составной оператор. Сложные условия и логические операции: !, ||, &&. Вложение операторов if, опе­ратор if... else if... else. Оператор switch, выполнение, использование оператора break.

9. Структурное программирование. Операторы цикла с предусловием (while) и по­стусловием (do-while). Примеры применения циклов. Изменение хода выполне­ния цикла с помощью операторов break и continue.

10. Одномерные массивы, пример использования. Связь массивов и указателей. Об­ращение к элементу массива, адресная арифметика. Структурное программиро­вание. Оператор цикла с шагом: for. Строки, кодовый формат, строковые литера­лы. Двумерные массивы. Функция индексации для двумерного массива.

11. Структуры, описание, пример использования. Операция выбора элемента струк­туры. Операции над структурой в целом. Структуры как аргументы функций. Объявление типа: typedef. Размещение структур в памяти.

12. Модульное программирование. Функции. Рациональные размер и количество па­раметров функции. Пример функции. Аргументы и параметры. Передача аргу­ментов по значению и по ссылке. Прототипы функций. Преобразование аргумен­тов в точке вызова. Оператор return.

13. Ввод и вывод (передача) с преобразованиями. Понятие набора данных и файла. Открытие и закрытие потоков. Функции передачи: fscanf и fprintf. Управляющая строка, форматы при вводе и при выводе. Передача без преобразований (в кодо­вых форматах).

14. Время жизни и способ размещения данных. Спецификация класса памяти. Ста­тический способ размещения. Спецификаторы класса памяти extern и static. Ди­намический процесс исполнения программы и концепция памяти auto / register. Данные с управляемым способом размещения, операции new и delete. Инициали­зация данных.

15. Объявления и определения. Область действия описаний. Структура программы на языке Си. Переобъявления во вложенных блоках. Определения и объявления на внешнем уровне. Определения и объявления на внутреннем уровне.

16. Указатели, адресная арифметика, указатели и массивы. Введение в обработку списков. Создание и уничтожение узлов списка, вставка и исключение узлов спи­ска.

17. Препроцессор Си. Директива препроцессора #define. Терминология: макроопре­деление, макрообращение, макрорасширение. Макросы с параметрами, аналогия с функциями. Директива препроцессора #include. Директивы условной компиля­ции.

По материалу разд. 2 в рамках практических занятий и самостоятельной рабо­ты выполняются следующие практические работы.

Проверочные работы по следующим темам (каждая длительностью 20-30 ми­нут): - ввод-вывод с использованием функций fscanf - fprintf;

437

Page 439: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

- ветвления и циклы; - структуры; - функции; - области действия и время жизни объектов; - указатели; - операции с линейным списком.

Простой программный проект, предусматривающий решение трех элементар­ных задач с использованием языка C++ (однофайловые программы с одной функци­ей): - программирование формулы: - программирование ветвления; - программирование цикла. В процессе выполнения программного проекта изучаются программная документа­ция: - единая система программной документации (ЕСПД); - состав ЕСПД, назначение и содержание программных документов (пять из них -"Техническое задание", "Текст программы", "Описание программы", "Программа и методика испытаний" и схема программы - используются при оформлении про­граммного проекта).

Раздел 3, Прикладное программирование (лекции, практические занятия и самостоятельная работа: 34 часа)

Цель этого раздела - овладение основами науки программирования, а также профессиональным стилем программирования на Си/С++.

Выделено шесть часто встречающихся в приложениях типов задач: сортиров­ка массивов, рекурсивные методы, обработка списков, работа с таблицами, работа с файлами, обработка текстов. Решение задач включает алгоритмизацию и програм­мирование. Умение решать эти задачи составляет основу профессиональной квали­фикации любого программиста.

Профессиональный стиль программирования подразумевает разработку про­стых и понятных исходных текстов программ. Важное внимание уделяется выбору наиболее подходящих в каждом случае изобразительных средств языка и оформле­нию программ с учётом особенностей психики человека.

Рассматриваемые ниже задачи демонстрируют использование при проектиро­вании программного продукта иерархической декомпозиции задачи.

1. Сортировка: виды, терминология, обозначения. Простые алгоритмы сортировки (выбором, вставками, обменом). Разработка функций, оценка производительно­сти.

2. Сортировка сложным выбором: с помощью двоичного дерева. Идея, пример ра­боты, разработка функции, оценка производительности.

3. Сортировка сложными вставками: метод Шелла. Идея, пример работы, разработ­ка функции, оценка производительности.

4. Сортировка сложным обменом: быстрая сортировка Хоора (нерекурсивный вари­ант). Идея, пример работы, разработка функции, оценка производительности.

5. Рекурсия и итерация. Рекурсия как метод вычислений. Рекурсивный вариант бы­строй сортировки Хоора. Когда не следует использовать рекурсию? Поиск пути минимального суммарного веса во взвешенном неориентированном графе.

6. Элементы обработки списков. Инвертирование списка ссылок в задаче поиска пути минимального суммарного веса во взвешенном неориентированном графе.

7. Сортировка массива сложным обменом: быстрая сортировка Хоора (рекурсив-

438

Page 440: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ный вариант). По материалу разд. 3 в рамках практических занятий и самостоятельной работы

выполняется более сложный программный проект, реализующий решение некоторой содержательной задачи. Проект содержит несколько файлов с исходным текстом и несколько функций, расположенных в этих файлах. Большое внимание в проекте уделяется методике тестирования спроектированного программного продукта.

Как указывалось выше, для более полной подготовки в области программиро­вания требуется формирование дополнительных знаний и умений, включая даль­нейшее развитие слоя 3 и освоение дополнительного слоя 4: умение проектировать программное обеспечение. Достижению этой цели способствует последующий курс "Технология программирования".

5. Лабораторный практикум (не предусматривается)

6. Учебно-методическое обеспечение дисциплины 6.7. Рекомендуемая литература

Основная литература:

1. Трои Д. Программирование на языке Си для персонального компьютера IBM PC: Пер. с англ. - М.: Радио и связь, 1991.

2. Уэйт М., Прата С, Мартин Д. Язык Си: Пер. с англ. - М.: Мир, 1988. 3. Керниган Б., Ритчи Д., Фъюер А. Язык программирования Си: Задачи по языку

Си: Пер. с англ. - М.: Финансы и статистика, 1985. 4. Давыдов В.Г. Теория и технология программирования. Конспект лекций. Ч. 1. ~-

Санкт-Петербургский государственный технический университет, СПб.: 2000. 5. Давыдов В.Г. Теория и технология программирования. Конспект лекций. Ч. 2. -

Санкт-Петербургский государственный технический университет, СПб.: 2001.

Дополнительная литература:

1. Рассохин Д. От Си к Си++. М.: Издательство "ЭДЕЛЬ", 1993. 2. От Си к Си++ / Е.И. Козелл, Л.М. Романовская, Т.В. Русс и др. М.: Финансы и

статистика, 1993. 3. Эллис М., Строуструп Б. Справочное руководство по языку программирования

C++ с комментариями: Пер. с англ. - М.: Мир, 1992. 4. Бруно Б. Просто и ясно о C++: Пер. с англ. - М.: БИНОМ, 1995. 5. Пол Ирэ. Объектно-ориентированное программирование с использованием C++:

Пер. с англ. / Ирэ Пол. - К.: НИПФ "ДиаСофт Лтд", 1995. 6. Ю. Тихомиров. Visual C++ 6 - СПб.: БХВ - Санкт-Петербург, 1999. 7. Давыдов В.Г., Пекарев М.Ф. Учебный машинно-ориентированный язык (ПМ-

ассемблер): Учебное пособие. Санкт-Петербургский государственный техниче­ский университет, СПб.: 2000.

6.2, Средства обеспечения освоения дисциплины

Любая интегрированная среда программирования языка C++ (желательно бо­лее простая, чтобы сосредоточить внимание на вопросах программирования). Выби­рается по усмотрению вуза.

439

Page 441: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

7. Материально-техническое обеспечение дисциплины Компьютерный класс, оснащенный ПК не ниже Pentium 75 МГц с ОС

Windows 95 и выше или Windows NT.

8. Методические рекомендации по организации изучения дисциплины

Использование специального методического обеспечения не предусмот­рено.

Программа составлена в соответствии с Государственным образователь­ным стандартом высшего профессионального образования по направлению 5502 "Автоматизация и управление" подготовки бакалавров и по направлению 6519 "Автоматизация и управление" подготовки специалистов.

Программу составили:

Лекарев Михаил Федорович, д.т.н., профессор, СПбГТУ (ЛПИ) Давыдов Владимир Григорьевич, к.т.н., доцент, СПбГТУ (ЛПИ)

Программа одобрена на заседании учебно-методического совета по на­правлению 5502 "Автоматизация и управление" 14.11.2000, протокол №

Председатель Совета УМО

Page 442: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

Приложение П.7. Прилагаемый компакт-диск.

На прилагаемом компакт-диске содержатся исходные тексты всех примеров программ. Имеются также и исполняемые файлы этих программ, так что Вам не надо обязательно компилировать за­интересовавшие Вас примеры. Все программные проекты примеров "самодостаточны". Это означает, что ни одному из них не требуются файлы других проектов.

Кроме исходных текстов примеров программ на компакт-диске имеются: • описание ПМ-ассемблера в формате текстового редактора Word

2000; • интегрированная среда программирования ПМ-ассемблера (рабо­

тает как DOS-приложение); • файл с полным текстом приложений к учебному пособию в фор­

мате текстового редактора Word 2000 и др. Более полные сведения о содержимом компакт-диска и работе

с этой информацией имеется в файле ReadMe, расположенном в корневой папке компакт диска.

Обратите внимание, что пользование данной книгой возможно и без компакт-диска, но его наличие обеспечит Вам большие удоб­ства и дополнительный сервис. В частности, без компакт-диска Вы не будете располагать описанием ПМ-ассемблера, специально раз­работанного для использования в учебном процессе, и его интегри­рованной средой программирования.

Page 443: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

ЛИТЕРАТУРА

1. Давыдов В.Г., Пекарев М.Ф. Учебный машинно-ориентированный язык (ПМ-ассемблер): Учебное пособие. - СПб.: Санкт-Петербургский государственный политехнический универси­тет, 2002.

2. Пекарев М.Ф. Модули с двумя выходами в программных проектах. - СПб.: СПбГТУ, 2000.

3. Рассохин Д. От Си к C++. - М.: Издательство "ЭДЕЛЬ", 1993.

4. От Си к C++ / Е.И. Козелл, Л.М. Романовская, Т.В. Русс и др. - М.: Финансы и статистика, 1993.

5. C/C++. Программирование на языке высокого уровня / Т.А. Павловская. - СПб.: Питер, 2001.

6. Давыдов В.Г. Теория и технология программирования: Кон­спект лекций. Ч. 2. СПб: Издательство СПбГПУ, 2001.

Page 444: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

СОДЕРЖАНИЕ ПРЕДИСЛОВИЕ 3 1. ВВЕДЕНИЕ 5

1.1. Системы счисления 5 1.2. Классификация языков программирования и их

краткая характеристика 8 1.2.1. Машинные языки 9 1.2.2. Ассемблерные языки (на примере

ПМ-ассемблера) 12 1.2.3. Макроассемблерные языки 12 1.2.4. Машинно-независимые языки 14

ЧАСТЬ 1. БАЗОВЫЙ ЯЗЫК 16 2. ЯЗЫК ПРОГРАММИРОВАНИЯ ВЫСОКОГО

УРОВНЯ C++ 16 2.1. Введение. Структурное и модульное

программирование 17 2.1.1.Алгоритм и способы его записи 17 2.1.2. Структурное и модульное программирование 21

2.2. Язык программирования и его описание 27 2.3. Структура и конструкция программы на Си/С++ 31

2.3.1. Комментарии 31 2.3.2. Идентификаторы 32 2.3.3. Слуэюебные слова 32 2.3.4. Константы 33 2.3.5. Структура Си-программы 38

2.4. Простой ввод-вывод в языках Си/С++ 41 2.4.1. Ввод-вывод потока 41 2.4.2. Ввод с использованием функций scanf-fscanf 43 2.4.3. Вывод с использованием функций printf-fprintf 49 2.4.4. Упразднения для самопроверки 54

3. ТИПЫ ДАННЫХ И ИХ АТРИБУТЫ 56 3.1. Имена 56 3.2. Типы данных 57 3.3. Класс хранения: область действия и время жизни 60 3.4. Внешние и внешние статические данные 61 3.5. Функции 68 3.6. Автоматические, регистровые и внутренние

статические данные 74 3.7. Инициализация данных 78 3.8. Упражнения для самопроверки 79

443

Page 445: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

3.9. Производные типы данных 81 3.9.1. Массивы 81 3.9.2. Массивы — как аргументы функций 86 3.9.3. Упраэюнения для самопроверки 88 3.9.4. Структуры 88 3.9.5. Структуры в качестве аргументов функций 91 3.9.6. Упраэюнения для самопроверки 93

4. ОПЕРАТОРЫ И УПРАВЛЕНИЕ ИХ ИСПОЛНЕНИЕМ .... 95 4.1. Пустой оператор 95 4.2. Операторы-выражения 95 4.3. Операторы break и continue 96 4.4. Блок операторов 96 4.5. Оператор return 97 4.6. Оператор if 97 4.7. Оператор switch 100 4.8. Оператор while 101 4.9. Оператор do-while 104 4.10. Оператор for 105 4.11. Оператор goto и метки операторов 108 4.12. Упражнения для самопроверки 108

5. ВЫРАЖЕНИЯ И ОПЕРАЦИИ 111 5.1. Операции ссылки 113 5.2. Унарные операции 116 5.3. Бинарные операции 118 5.4. Тернарная операция 121 5.5. Операция присваивания 121 5.6. Операция "запятая" 122

6. УКАЗАТЕЛИ 123 6.1. Зачем нужны указатели? 123 6.2. Указатели и их связь с массивами и строками 123 6.3. Определение и применение указателей 124 6.4. Указатели на структуры 128 6.5. Использование указателей в качестве аргументов

функций 129 6.6. Указатель как значение, возвращаемое функцией 133 6.7. Массивы указателей 134 6.8. Замена типов указателей 137 6.9. Упражнения для самопроверки 141

7. ПОЛЯ БИТОВ И ПОБИТОВЫЕ ОПЕРАЦИИ 143 7.1. Поля битов 143 7.2. Побитовые операции 144

444

Page 446: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

8. ДИНАМИЧЕСКОЕ РАЗМЕЩЕНИЕ ОБЪЕКТОВ В ПАМЯТИ. ОДНОНАПРАВЛЕННЫЙ НЕКОЛЬЦЕВОЙ ЛИНЕЙНЫЙ СПИСОК И ОПЕРАЦИИ С НИМ 148 8.1. Понятие об однонаправленном линейном списке.

Динамическое размещение объектов в памяти 148 8.2. Инициализация линейного списка 152 8.3. Добавление элемента в начало списка 163 8.4. Добавление элемента в конец списка 163 8.5. Создание ЛС с первым занесенным элементом

в начале 164 8.6. Создание ЛС с первым занесенным элементом

в конце списка 164 8.7. Удаление элемента из начала списка 165 8.8. Удаление элемента из конца списка 166 8.9. Разрушение ЛС с освобождением занятой им

динамической памяти 166 8.10. Печать содержимого ЛС 167 8.11. Добавление элемента после каждого элемента ЛС,

содержащего заданное значение 167 8.12. Добавление элемента перед каждым элементом ЛС,

содержащим заданное значение 168 8.13. Удаление элемента после каждого элемента ЛС,

содержащего заданное значение 168 8.14. Удаление элемента перед каждым элементом ЛС,

содержащим заданное значение 170 8.15. Зачем нужен линейный список 171 8.16. Упражнения для самопроверки 172

9. ПРЕПРОЦЕССОР ЯЗЫКА СИ/С++ 173 9.1. Директивы препроцессора 173 9.2. Подстановка имен 173 9.3. Включение файлов 177 9.4. Условная компиляция 178 9.5. Указания по работе с препроцессором 180

10. РЕДКО ИСПОЛЬЗУЕМЫЕ СРЕДСТВА ЯЗЫКОВ СИ/С++ 182 10.1. Объявление имени типа typedef 182 10.2. Объекты перечислимого типа 183 10.3. Объединения 186

11. МОДЕЛИ ПАМЯТИ 189 11.1. Адресация near, far и huge 190 11.2. Стандартные модели памяти для

шестнадцатибитной среды DOS 193

445

Page 447: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

11.3. Изменение размера указателей в стандартных моделях памяти для шестнадцатибитной среды DOS 194

11.4. Макроопределения для работы с указателями 195 11.5. Работа с памятью для среды WINDOWS 196

12. НОВЫЕ ВОЗМОЖНОСТИ ЯЗЫКА C++, НЕ СВЯЗАННЫЕ С ОБЪЕКТНО-ОРИЕНТИРОВАННЫМ ПРОГРАММИРОВАНИЕМ 197 12.1. Прототипы функций. Аргументы по умолчанию 198 12.2. Доступ к глобальным переменным, скрытым

локальными переменными с тем же именем 199 12.3. Модификаторы const и volatile 200 12.4. Ссылки 201 12.5. Подставляемые функции 202 12.6. Операции динамического распределения памяти 202 12.7. Перегрузка функций 203 12.8. Шаблоны функций 205 12.9. Перегрузка операций 206

13. ТЕХНОЛОГИЯ СОЗДАНИЯ ПРОГРАММ [5] 208 13.1. Кодирование и документирование программы 208 13.2. Проектирование и тестирование программы [5] 212

13.2.1. Этап 1: постановка задачи 213 13.2.2. Этап 2: разработка внутренних

структур данных 214 13.2.3. Этап 3: проектирование структуры

программы и взаимодействия модулей 214 13.2.4. Этап 4: структурное программирование 215 13.2.5. Этап 5: нисходящее тестирование 216

ЧАСТЬ 2. ПРИКЛАДНОЕ ПРОГРАММИРОВАНИЕ 218 14. ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ [5] 218

14.1. Линейные списки 219 14.2. Бинарные деревья 220 14.3. Очереди и их частные разновидности 221 14.4. Реализация динамических структур с

помощью массивов 222 15. СОРТИРОВКА 224

15.1. Сортировка массивов 226 15.2. Сортировка массива простым выбором 228 15.3. Сортировка массива простыми включениями 248 15.4. Сортировка массива простым обменом

(метод "пузырька") 250 15.5. Выводы по простым методам сортировки 251

446

Page 448: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

15.6. Сортировка массива сложным выбором (с помощью двоичного дерева) 252

15.7. Сложная сортировка вставками (сортировка Шелла) ... 257 15.8. Сложная сортировка обменом (сортировка Хоора) 259 15.9. Сравнительные показатели производительности

различных методов сортировки массивов 264 16. ГРАФЫ. ТРАНСПОРТНАЯ ЗАДАЧА (ЗАДАЧА

КОММИВОЯЖЕРА) 266 16.1. Терминология 266 16.2. Формы задания графа 268 16.3. Почему для решения задачи подходит

рекурсивный алгоритм 269 16.4. Представление кратчайшего пути до каждой

вершины 270 16.5. Как найти минимальный путь 271

16.5.1. Требуется ли полный перебор путей 271 16.5.2. Организация перебора путей 271

16.6. Пример поиска минимального пути в графе 285 16.7. Печать информации о наилучшем пути 286

17. ПОИСК 288 17.1. Постановка задачи внутреннего поиска 288 17.2. Последовательный поиск 290 17.3. Логарифмический (бинарный) поиск 305 17.4. Поиск с использованием перемешанной

таблицы (хэш-таблицы) 308 18. ОТВЕТЫ И РЕШЕНИЯ К УПРАЖНЕНИЯМ

ДЛЯ САМОПРОВЕРКИ 312 18.1. Для подраздела 2.4.4 312 18.2. Для подраздела 3.8 314 18.3. Для подраздела 3.9.3 315 18.4. Для подраздела 3.9.6 317 18.5. Для подраздела 4.12 319 18.6. Для подраздела 6.9 321 18.7. Для подраздела 8.16 321

ПРИЛОЖЕНИЯ 327 Приложение П. 1. Тесты и программные проекты. Варианты заданий 327

П. 1.1. Тесты (контрольные работы) 327 П. 1.1.1. Программирование на ПМ-ассемблере.

Варианты тестов 327 П.1.1.2. Ввод в языках Си/С++. Варианты тестов 330 П. 1.1.3. Вывод в языках Си/С++. Варианты тестов 337

447

Page 449: Davydov G.v. Programmirovanie i Osnovy Algoritmizacii (2003)

п. 1.1.4. Простейшие ветвления. Варианты тестов 342 П.1.1.5. Циклы. Варианты тестов 345 П. 1.1.6. Структуры. Варианты тестов 350 П.1.1. 7. Функции. Варианты тестов 359 П. 1.1.8. Области действия определений. Варианты тестов 361 П. 1.1.9. Массивы и указатели. Варианты тестов 373 П. 1.1.10. Операции над линейным списком. Работа

с динамической памятью. Варианты тестов 379 П.1.1.11. Препроцессор, перечисления, функции с умалчиваемыми

значениями аргументов, перегрузка функций, шаблоны функций, перегрузка операций. Варианты тестов 388

П.1.2. Программные проекты 392 П. 1.2.1. Программирование на ПМ-ассемблере. Варианты

программных проектов 393 П. 1.2.2. Структурное программирование средствами языков

Си/С-^+ . Варианты программных проектов 395 П. 1.2.3. Средства модульного программирования в языке C++.

Варианты программных проектов 402 П.1.3. Экзаменационное тестирование 405

Приложение П.2. Создание программного проекта 408 П.2.1. IDE MS Visual Studio C++ 6.0.

Создание программного проекта 408 П.2.2. IDE Borland C++ 3.1.

Создание программного проекта 411 Приложение П.З. Рекомендации по структуре однофайловой программы с одной функцией и пример оформления исходного текста 412 Приложение П.4. Методика отладки программы 414

П.4.1. Компиляция и компоновка программного проекта. Устранение синтаксических огиибок 415

П.4.2. Отладка программного проекта. Устранение логических (алгоритмических) огиибок 417

77.4.3. Тестирование программного проекта 420 Приложение П. 5. Рекомендации по созданию многофайлового программного проекта с несколькими функциями и пример оформления исходного текста 421

77.5.7. Спецификация программного проекта 421 П. 5.2. Пример оформления исходного текста

программы 428 Приложение П.6. Примерная программа дисциплины "Программирование и основы алгоритмизации" 434 Приложение П.7. Прилагаемый компакт-диск 441

ЛИТЕРАТУРА 442

448