Вы находитесь на странице: 1из 400

ЯЗЫКИ

учебно-методический комплекс

ПРОГРАММИРОВАНИЯ

Учебная программа дисциплины


Конспект лекций. Язык С
Конспект лекций. Ассемблер
Электронный

Методические указания по лабораторным работам


Методические указания по самостоятельной работе
Банк тестовых заданий в системе UniTest

Красноярск
ИПК СФУ
2008
УДК 004.438
ББК 32.973
Н73
Электронный учебно-методический комплекс по дисциплине «Языки программи-
рования» подготовлен в рамках инновационной образовательной программы «Ин-
форматизация и автоматизированные системы управления», реализованной в ФГОУ
ВПО СФУ в 2007 г.

Рецензенты:
Красноярский краевой фонд науки;
Экспертная комиссия СФУ по подготовке учебно-методических комплексов дис-
циплин

Новиков, Е. А.
Н73 Языки программирования. Язык С. Версия 1.0 [Электронный ресурс] : кон-
спект лекций / Е. А. Новиков, Ю. А. Шитов. – Электрон. дан. (3 Мб). – Красно-
ярск : ИПК СФУ, 2008. – (Языки программирования : УМКД № 147-2007 / рук.
творч. коллектива Ю. А. Шитов). – 1 электрон. опт. диск (DVD). – Систем. тре-
бования : Intel Pentium (или аналогичный процессор других производителей)
1 ГГц ; 512 Мб оперативной памяти ; 3 Мб свободного дискового пространст-
ва ; привод DVD ; операционная система Microsoft Windows 2000 SP 4 /
XP SP 2 / Vista (32 бит) ; Adobe Reader 7.0 (или аналогичный продукт для чте-
ния файлов формата pdf).
ISBN 978-5-7638-1250-3 (комплекса)
ISBN 978-5-7638-1459-0 (конспекта лекций)
Номер гос. регистрации в ФГУП НТЦ «Информрегистр» 0320802545
от 02.12.2008 г. (комплекса)
Настоящее издание является частью электронного учебно-методического ком-
плекса по дисциплине «Языки программирования», включающего учебную програм-
му, конспект лекций «Языки программирования. Ассемблер», методические указания
по лабораторным работам, методические указания по самостоятельной работе, кон-
трольно-измерительные материалы «Языки программирования. Банк тестовых зада-
ний», наглядное пособие «Языки программирования. Презентационные материалы».
Приведены теоретические сведения по языку С, его типам и структурам данных,
рассмотрены типовые задачи программирования.
Предназначен для студентов направления подготовки специалистов 090102.65
«Компьютерная безопасность» укрупненной группы 090000 «Информационная безо-
пасность».

© Сибирский федеральный университет, 2008

Рекомендовано к изданию
Инновационно-методическим управлением СФУ

Редактор Т. И. Тайгина
Разработка и оформление электронного образовательного ресурса: Центр технологий элек-
тронного обучения информационно-аналитического департамента СФУ; лаборатория по разработке
мультимедийных электронных образовательных ресурсов при КрЦНИТ
Содержимое ресурса охраняется законом об авторском праве. Несанкционированное копирование и использование данного про-
дукта запрещается. Встречающиеся названия программного обеспечения, изделий, устройств или систем могут являться зарегистрирован-
ными товарными знаками тех или иных фирм.

Подп. к использованию 01.10.2008


Объем 3 Мб
Красноярск: СФУ, 660041, Красноярск, пр. Свободный, 79
ОГЛАВЛЕНИЕ

ВВЕДЕНИЕ ................................................................. 8
ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ
НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ ........ 9
1. Введение .......................................................................................................................... 9
2. Основные устройства ЭВМ и их назначение .......................................................... 10
3. История развития языков ........................................................................................... 11
ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ
ЯЗЫКОВ ПРОГРАММИРОВАНИЯ. ПРЕПРОЦЕССОР
И МАКРООБРАБОТКА. ЭТАПЫ РЕШЕНИЯ ЗАДАЧ
НА КОМПЬЮТЕРЕ .................................................... 15
1. Общие принципы построения языков программирования ................................. 15
2. Препроцессор и макрообработка. ............................................................................. 18
3. Этапы решения задач на компьютере ...................................................................... 19
ЛЕКЦИЯ 3. СОВРЕМЕННЫЕ ИНТЕГРИРОВАННЫЕ
СРЕДЫ. ВСТРОЕННЫЙ ОТЛАДЧИК.
БИБЛИОТЕКА ПРОГРАММ И КЛАССОВ .................. 21
1. Современные интегрированные среды .................................................................. 21
2. Встроенный отладчик ................................................................................................. 22
3. Библиотеки программ и классов ............................................................................... 23
ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД
ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR()
И GETCH() ................................................................ 26
1. Простейшая программа ............................................................................................... 26
2. Вывод текста на экран ................................................................................................ 28
3. Препроцессор ................................................................................................................ 29
4. Директивы clrscr() и getch() ........................................................................................ 31
ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА
ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT
И FLOAT. ВВОД С КЛАВИАТУРЫ ............................ 35
1. Память ............................................................................................................................ 35
2. Переменные ................................................................................................................... 35
3. Вывод переменных на экран ..................................................................................... 36
4. Запись в переменные типа int и float........................................................................ 37
5. Ввод с клавиатуры....................................................................................................... 40

 Языки программирования. Язык С. Конспект лекций -3-


ОГЛАВЛЕНИЕ

ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ.


МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ .... 44
1. Арифметические операции ........................................................................................ 44
2. Математические выражения и функции .................................................................. 50
ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И
ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ
ОПЕРАТОРЫ ............................................................ 54
1. Операции сравнения и логические операции ......................................................... 54
2. Условные операторы .................................................................................................. 55
ЛЕКЦИЯ 8. ЦИКЛЫ .................................................. 62
1. Цикл for .......................................................................................................................... 62
2. Цикл while ....................................................................................................................... 65
3. Цикл do – while. ............................................................................................................. 67
ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ
ЗАДАЧИ. МАТРИЦЫ ................................................ 70
1. Массивы ......................................................................................................................... 70
2. Некоторые простейшие задачи ................................................................................. 72
3. Матрицы. ........................................................................................................................ 79
4. Перебор элементов матрицы..................................................................................... 81
ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ
С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ .......... 88
1. Указатели........................................................................................................................ 88
2. Способы инициализации указателей ....................................................................... 90
3. Связь указателя с одномерным массивом ............................................................. 94
4. Двумерный массив ...................................................................................................... 95
5. Динамическая память .................................................................................................. 97
ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ ....................... 101
1. Файлы ........................................................................................................................... 101
2. Символы ...................................................................................................................... 103
3. Стандартные программы.......................................................................................... 108
ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК.
ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ ...... 109
1. Строки ........................................................................................................................... 109
2. Стандартные функции для работы со строками ................................................. 113
3. Массив строк ............................................................................................................... 113
4. Доступ к функциональным клавишам ................................................................... 117

 Языки программирования. Язык С. Конспект лекций -4-


ОГЛАВЛЕНИЕ

ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ


НА СТРУКТУРУ ....................................................... 122
1. Структуры .................................................................................................................... 122
2. Указатели на структуру.............................................................................................. 127
ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС.
УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ
РЕЖИМАХ .............................................................. 133
1. Интерфейс пользователя ......................................................................................... 133
2. Графический интерфейс пользователя................................................................. 133
3. Оконный интерфейс .................................................................................................. 133
4. Текстовый режим ....................................................................................................... 134
5. Графический режим ................................................................................................... 135
ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ.
ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ
В ФУНКЦИИ............................................................ 140
1. Функции ........................................................................................................................ 140
2. Локальные и глобальные переменные ................................................................. 142
3. Область действия функции ...................................................................................... 144
4. Передача параметров в функцию ........................................................................... 145
5. Передача массивов в функцию ............................................................................... 148
ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ.
АБСТРАКТНЫЕ ТИПЫ ДАННЫХ.
ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ ............. 152
1. Обработка исключений ............................................................................................. 152
2. Абстрактные типы данных ....................................................................................... 154
3. Инкапсуляция .............................................................................................................. 156
4. Классы и объекты ...................................................................................................... 156
ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ АБСТРАКТНЫХ
ТИПОВ ДАННЫХ .................................................... 160
1. Реализация АТД на примере комплексных чисел ............................................... 160
2. Конструктор класса .................................................................................................... 161
3. Деструктор класса ...................................................................................................... 161
4. Файл реализации ........................................................................................................ 162
5. Файл приложения ....................................................................................................... 163
ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ
И ПАРАМЕТРИЗАЦИЯ ............................................ 166
1. Спецификация ............................................................................................................. 166
2. Параметризация .......................................................................................................... 169

 Языки программирования. Язык С. Конспект лекций -5-


ОГЛАВЛЕНИЕ

ЛЕКЦИЯ 19. ГЕНЕРАТОР КОДА/ПРИЛОЖЕНИЙ ... 172


1. Генератор кодов ......................................................................................................... 172
2. Пример формирования окна .................................................................................... 174
ЛЕКЦИЯ 20. РЕКУРСИЯ ........................................ 177
1. Общие сведения о рекурсии .................................................................................... 177
2. Пример рекурсивной функции ................................................................................. 178
3. Формы рекурсивного обращения ........................................................................... 183
4. Выполнение действий на рекурсивном спуске .................................................... 185
ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)................ 191
1. Выполнение действий на рекурсивном возврате ............................................... 191
2. Выполнение действий на рекурсивном спуске и возврате ............................... 195
ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА .................. 199
1. Быстрая сортировка с использованием рекурсивных функций ...................... 199
2. Быстрая сортировка с использованием циклов.................................................. 210
ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ ................. 213
1. Односвязный список ................................................................................................. 213
2. Формирование списка ............................................................................................... 214
3. Операции над списком .............................................................................................. 220
4. Программа обработки списка................................................................................... 234
ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ.............................. 239
1. Стек................................................................................................................................ 239
2. Операции над стеком ................................................................................................. 239
3. Программа обработки стека ..................................................................................... 239
ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ .................... 246
1. Двусвязные списки .................................................................................................... 246
2. Формирование списка ............................................................................................... 247
3. Операции над списком .............................................................................................. 249
4. Программа обработки списка................................................................................... 253
ЛЕКЦИЯ 26. ДЕРЕВО ............................................ 258
1. Дерево как рекурсивный тип данных..................................................................... 258
2. Алгоритм формирования дерева ............................................................................ 259
ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО ......................... 272
1. Обходы дерева ........................................................................................................... 272
2. Идеально сбалансированное дерево ..................................................................... 281
3. Удаление узла из дерева ........................................................................................... 283
ЛЕКЦИЯ 28. СОРТИРОВКА ................................... 287
План ................................................................................................................................... 287
1. Классы сортировок .................................................................................................... 287
2. Сортировка выбором ................................................................................................ 288

 Языки программирования. Язык С. Конспект лекций -6-


ОГЛАВЛЕНИЕ

3. Сортировка обменом (методом пузырька) ........................................................... 289


4. Сортировка вставками .............................................................................................. 297
5. Пирамидальная сортировка ..................................................................................... 303
ЛЕКЦИЯ 29. АНАЛИЗ АРИФМЕТИЧЕСКИХ
ВЫРАЖЕНИЙ. ИНФИКСНАЯ И ПОСТФИКСНАЯ
ФОРМЫ ЗАПИСИ ................................................... 307
1. Форма записей арифметических выражений ....................................................... 307
2. Преобразование инфиксной формы записи в постфиксную ............................ 309
3. Вычисление выражения в постфиксной форме .................................................. 313
4. Рекурсивно-нисходящий алгоритм разбора выражения.................................... 318
ЛЕКЦИЯ 30. ГРАФЫ. ОБХОД ГРАФА В ГЛУБИНУ
И ШИРИНУ ............................................................. 338
1. Графы ........................................................................................................................... 338
2. Формы представления графа .................................................................................. 339
3. Обходы графа в глубину .......................................................................................... 340
4. Обходы графа в ширину ........................................................................................... 349
ЛЕКЦИЯ 31. ОСТОВНЫЕ ДЕРЕВЬЯ (КАРКАСЫ).
ОСТОВНЫЕ ДЕРЕВЬЯ МИНИМАЛЬНОЙ
СТОИМОСТИ .......................................................... 362
1. Определение каркаса ................................................................................................. 362
2. Определение каркаса при обходе графа в глубину............................................. 364
3. Определение каркаса при обходе графа в ширину ............................................. 366
4. Алгоритм Прима определения каркаса .................................................................. 373
5. Алгоритм Крускала определения каркаса ............................................................. 376
ЛЕКЦИЯ 32. СТАНДАРТНАЯ БИБЛИОТЕКА
ШАБЛОНОВ. КОНТЕЙНЕРЫ И ИТЕРАТОРЫ ........ 379
1. Контейнеры.................................................................................................................. 379
2. Итераторы .................................................................................................................... 382
3. Операции с контейнерами ........................................................................................ 384
ЛЕКЦИЯ 33. ПАРАЛЛЕЛЬНОЕ
ПРОГРАММИРОВАНИЕ .......................................... 388
1. Классификация параллелизма ................................................................................ 388
2. Проектирование программы .................................................................................... 388
3. Реализация параллелизма (MPI) .............................................................................. 389
4. Базовые функции MPI ............................................................................................... 393
ЗАКЛЮЧЕНИЕ ........................................................ 398
БИБЛИОГРАФИЧЕСКИЙ СПИСОК ........................ 399

 Языки программирования. Язык С. Конспект лекций -7-


ВВЕДЕНИЕ

Курс лекций «Языки программирования» соответствует государствен-


ному образовательному стандарту (спец. 075200 – «Компьютерная
безопасность»).
Методически лекции по освоению языка программирования
выстраиваются по следующей схеме:
постановка задачи;
инструменты языка, необходимые для решения задачи;
решение задачи.
Подобная схема исключает из изложения те конструкции языка,
которые не требуются при рассмотрении задачи. Простое перечисление
средств языка без применения их для решения задач малоэффективно с точки
зрения освоения материала.
При изучении материала лекций полезно обращать внимание на
комментарии к директивам программы: в них объясняются трудности,
возникающие при переводе алгоритма решения задачи на язык
программирования. Для более успешного освоения материала полезно
изучить программы, данные в лекциях. Надо понимать, что чем больше из
разных источников будет привлекаться дополнительный материал, тем более
глубокие будут знания и устойчивее приобретенные навыки при
программировании задач.
По убеждениям и опыту авторов освоение конструкций языка
принципиальных теоретических трудностей не представляет. И они в полной
мере могут быть преодолены студентами самостоятельно.

 Языки программирования. Язык С. Конспект лекций -8-


ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ
НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ

План

1. Введение.
2. Основные устройства ЭВМ и их назначение.
3. История развития языков.

1. Введение

Информация в переводе с латинского языка означает разъяснение,


изложение чего-либо или сведения о чем-либо. Понятие обработка
информации появилось совсем недавно. Но информация обрабатывалась с
древних времен. Сначала знания передавалась из поколения в поколение
устно, затем фиксировались в виде наскальных изображений. Возникновение
письменности явилось важным шагом в процессе записи данных и их
дальнейшего осмысливания. Письменность позволила не только хранить
знания, но и породила новые революционные возможности для копирования
информации с целью ее дальнейшего распространения, а также новые
технологии передачи знаний последующим поколениям. Благодаря
изобретению печатного станка появилась возможность массового
тиражирования информации. Это, в свою очередь, позволило вовлекать в
процессы познания широкие круги населения.
Книга является неудобным носителем информации при решении
многих задач. Знания, которые хранятся в книге, не могут влиять на
технологические процессы непосредственно. Человек должен сначала найти
данные, переработать, осмыслить, обобщить прочитанное и затем принять
решение по интересующему вопросу. Сам процесс поиска необходимой
информации очень трудоемок по временным затратам. Да и хранить знания в
виде книг весьма дорого и громоздко.
Все перечисленные выше негативные стороны книги, как носителя
информации, особенно выпукло стали заметны на фоне зародившегося в
первой половине XX века информационного взрыва. Человечество было уже
не в состоянии переработать и осмыслить колоссальные объемы новых
знаний. Появилась потребность в высокоскоростном устройстве
преобразования данных и новых компактных носителях информации.
На вызов, который был брошен стремительным развитием прогресса,
человечество ответило революционным изобретением XX века. Этим
изобретением стала электронная вычислительная машина (ЭВМ).
По мере развития прогресса возникала необходимость проводить
точные и быстрые расчеты, которые были связаны с определением значений
сложных арифметических выражений, логарифмических и тригонометри-

 Языки программирования. Язык С. Конспект лекций -9-


ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ
1. Введение

ческих функций. Проведение таких вычислений без особых устройств было


затруднительным. Заметим, что необходимость в таких устройствах была
уже на ранних стадиях развития цивилизации. Еще в 2600 г. до новой эры
применялось счетное устройство абак (подобие счет). Однако простые
устройства оказывались бесполезными для решения достаточно сложных
задач, которые возникали по мере развития науки и техники. Прошло
несколько тысяч лет, прежде чем были спроектированы и построены новые
счетные устройства. Такая длительная пауза была вызвана тем, что долгое
время для проведения расчетов использовалась римская (не позиционная)
система счисления. Суть римской системы счисления состояла в том, что
значения символов, которые обозначали числа, не менялись от места
(позиции), которое они занимали при записи числа (XV, VX). И только после
того как арабская (позиционная) система счисления вытеснила римскую,
возникли предпосылки для развития счетных устройств.
Одним из самых значительных прорывов в этом направлении было
изобретение Блеза Паскаля (физик, математик, выдающийся ученный).
Б. Паскаль придумал устройство (1640–1645), которое могло производить
четыре арифметических действия над пятизначными числами.
В 1694 г. Лейбниц (математик, механик) создал машину, которая
помимо четырех арифметических операций могла выполнять извлечение
квадратного корня.
В 1887 г. шведский инженер Однер разработал счетное устройство –
арифмометр. Идеи, реализованные при создании арифмометра, легли в
дальнейшем в основу многих других счетных устройств (например,
арифмометр «Феликс»).
Научно-техническая революция XIX в. была связана с необходимостью
увеличения расчетов во всех областях знаний. Это требовало построения
надежных быстродействующих счетных машин, которые могли бы
выполнять действия с большими числами. Бурное развитие электроники в
начале XX в. создало условия для производства вычислительных устройств
на принципиально новой основе. К 1940 г. сошлись потребности в
скоростном вычислении и возможности его реализации. В 1945 г. математик
Джон фон Нейман сформулировал основные принципы построение ЭВМ, а в
следующем году первая электронная вычислительная машина (ЭВМ) начала
действовать. Сейчас подавляющее большинство вычислительных машин
конструируются на основе идей Неймана. В дальнейшем аббревиатуры ЭВМ
и ПК (персональный компьютер) будут использоваться как синонимы.

2. Основные устройства ЭВМ и их назначение

Основными устройствами ЭВМ (в том числе и современного


компьютера) являются:
арифметическое устройство (АУ),
устройство управления,

 Языки программирования. Язык С. Конспект лекций -10-


ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ
2. Основные устройства ЭВМ и их назначение

запоминающие устройства (ЗУ),


внешние устройства ввода и вывода информации.
В современных персональных компьютерах арифметическое
устройство и устройство управления объединены в одно, которое называется
процессором. Обработку (преобразование) информации, управление всеми
устройствами, которые существуют в ПК, и синхронизацию (согласование
порядка) работ всех устройств в компьютере осуществляет процессор.
Запоминающие устройства подразделяются на оперативное
запоминающее устройство (ОЗУ) и внешнюю память (винчестеры, диски,
флэшки). Эти устройства предназначены для записи и хранения информации.
ЭВМ любую информацию переводит в число, которое записано в виде
набора битов. Слово бит <bit> образовано из выражения Binary digIT
(двоичная цифра). Двоичная цифра – это число, которое может принимать
одно из двух значений: 0 или 1. Элементом оперативной памяти является
байт. Байт – это устройство, которое состоит из восьми бит. В байт можно
записать любую комбинацию из 0 и 1. Общее число 0 и 1 в байте должно
равняться восьми. Все байты в памяти нумеруются. Номер байта в
оперативной памяти называется адресом. Существуют следующие единицы
измерения памяти:
Кбайт (один килобайт) = 1024 байт,
Мбайт (один мегабайт) = 1024 Кбайт,
Гбайт (один гигабайт) = 1024 Мбайт.
Устройства ввода (клавиатура, сканер) предназначены для ввода
информации в память ПК. При вводе происходит автоматическое
преобразование информации из обычного формата в тот формат, который
требует компьютер для своей работы.
Устройство вывода (экран монитора, печать) выводит информацию из
памяти компьютера на внешние носители. Формат выводимой информации
определяется пользователем.

3. История развития языков

Управляет работой компьютера программа. Программа – это


последовательность (набор) некоторых директив, которые может выполнить
компьютер. Естественно, эта последовательность должна быть написана с
соблюдением правил и ограничений, которые надо знать. Программу можно
сравнить с выполнением некоторой последовательности команд меню любой
информационной системы. Если порядок выполнения команд меню будет
неправильным или не все команды меню будут выполнены, то поставленная
цель не будет достигнута. Если информационная система настроена на
выполнение команд меню, то ПК настроен на выполнение некоторого набора
директив. Фактически, формат записи директив определяется языком
программирования. Язык программирования – это набор правил, по которым
для ПК формируется директива, которую он обязательно выполнит.

 Языки программирования. Язык С. Конспект лекций -11-


ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ
3. История развития языков

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


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

001 0345 0363 0211.

В представленном коде наборы чисел означают следующее:


001 – операция сложения,
0345 – адрес первого числа,
0363 – адрес второго числа,
0211 – адрес для записи результата.
Последовательность подобных директив и составляла код программы.
Заметим, что в зависимости от задачи таких директив в программе могло
быть от нескольких сотен до нескольких тысяч и даже десятков тысяч.
И программы занимали иногда несколько объемных книг. Оставляем за
скобками технологию запуска подобных задач на счет. Это был довольно
трудозатратный процесс. Ясно, что сопровождать, разбираться, искать
ошибки в таком объемном и непрозрачном с точки зрения информативности
документе трудно.
Ученые попытались изменить ситуацию: 1) упростить процесс
программирования, 2) сделать код программы читаемым и информативным
при его рассмотрении. Достижением в этом направлении стала замена
машинного кода языком Ассемблера. Язык Ассемблер представлял собой
символьную запись машинного кода. В этом языке коды операций
заменялись понятным символьным обозначением, а области памяти для
записи и хранения также обозначались удобным, осмысленным
программистом набором символом. Однако ЭВМ понимает только
машинный код. Поэтому потребовалась особая программа (транслятор),
которая символьную – более понятную запись программы – переводила в
машинный код.
Язык Ассемблер решил далеко не все проблемы, связанные с
программированием на ЭВМ. Одной из главных проблем была следующая.
Для составления программы пользователь обязан был знать все
элементарные операции, которые могла выполнять машина. Это сводилось к
тому, что программист должен был мыслить в терминах аппаратуры
конкретной ЭВМ. Решение задачи, которая не связана с конструкцией ЭВМ,
требовало знание этой конструкции. Более того, в этом случае для разных по
архитектуре ЭВМ программы несовместимы. Другими словами, если
используется Ассемблер, то для ЭВМ разных структур при решении одной и
той же задачи требуется разрабатывать свою программу. Это почти

 Языки программирования. Язык С. Конспект лекций -12-


ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ
3. История развития языков

равносильно тому, что для разного класса автомобилей существовали бы


разные правила уличного движения. Данную проблему, да и много других,
связанных с ассемблером, решили языки программирования (ЯП) высокого
уровня. Эти языки позволили программисту мыслить и писать программы в
терминах задачи, а не в терминах конкретной ЭВМ.
Одним из первых языков высокого уровня был язык Фортран. Он
разрабатывался в период с 1954 по 1957 год. Реализация языка была
опубликована в 1956 году. В это время компьютеры использовались в
основном для научных задач механики, физики, математики и др. Они были
еще маломощными по быстродействию, небольшими и дорогостоящими. Это
все отразилось на концепциях языка Фортран. Однако и сейчас Фортран
является одним из популярных и широко используемых языков
программирования.
Попытка создания универсального языка программирования привела к
появлению языка Алгол 60, который оказал значительное влияние на
последующие языки программирования. По одним данным Алгол 60 имел
колоссальный успех, по другим – был провалом. Долгое время Алгол
фактически был единственным средством для представления алгоритмов в
научной литературе и первым машинно-независимым языком. Структура
языка Алгол в дальнейшем повлияла на архитектуру ЭВМ. Однако Алгол у
пользователей не получил широкого распространения и не стал
доминирующим языком, потому что был труден для понимания, а некоторые
его свойства делали реализацию языка неэффективной.
В 1967 г. был представлен проект языка SIMULA 67, который
представлял собой расширенную версию языка Алгол 60. В языке SIMULA 67
была разработана конструкция класса. Это положило начало понятию
абстракции данных. В дальнейшем эту концепцию использовал Бьерн
Страуструп при создании языка C++. И хотя сам язык SIMULA 67 не
получил широкого распространения, его концепция классов и производных
классов оказала принципиальное влияние на языки следующего поколения.
Популярным языком в конце 1970-х гг. был язык Бейсик. Его
популярность объясняется простотой в изучении. Большинство конструкций
Бейсик произошли от языка Фортран. Заметим, что язык Бейсик мог быть
реализован на компьютерах с небольшой памятью.
В 1971 г. Николаус Вирт опубликовал исходное описание языка
Паскаль, основой которого можно считать Алгол 60. Язык Паскаль
создавался как язык для обучения программированию. Из-за простоты и
выразительности синтаксиса язык Паскаль быстро завоевал популярность
среди студентов. Паскаль – это типизированный язык. В нем разработана
развитая система структур данных и проводится полная проверка типов.
Язык Delphi происходит от языка Паскаль. Он создавался для возможности
объектно-ориентированной поддержки.
У языка C (Си) – четыре предка: Алгол 68, CPL, BCPL и B. Алгол 68
был разработан в 60-е гг. ХХ в. в Европе. Он оказал сильное влияние на
последующее развитие языков программирования. Но сам язык не получил

 Языки программирования. Язык С. Конспект лекций -13-


ЛЕКЦИЯ 1. ОСНОВНЫЕ УСТРОЙСТВА ЭВМ И ИХ НАЗНАЧЕНИЕ. ИСТОРИЯ РАЗВИТИЯ ЯЗЫКОВ
3. История развития языков

широкого распространения. Считается, что это произошло потому, что он


был труден для изучения. Язык CPL был разработан в Лондоне с целью
приспособить Алгол 68 к существующим ЭВМ. Язык BCPL был близок к
CPL. Разработчики этого языка пытались сохранить лучшие черты CPL и
упростить его. Язык B разрабатывался для создания операционной системы
UNIX на базе языка BCPL. Работа велась над упрощением языка BCPL. Но
простата BCPL и B привела к тому, что при помощи этих языков можно было
решать ограниченный класс задач. По этой причине и ряду других в 1971 г.
началась работа над языком С – наследником языка B. Автором этого языка
был Дэнис Ритчи. Он завершил свою работу в 1972 г. Дэнис Ритчи был
известен работами по системному программному обеспечению,
операционным системам, языкам программирования. И, по-видимому, как
специалист по системному программированию, Ритчи создал язык, близкий к
аппаратуре. Поэтому программист мог не очень вникать в архитектуру
компьютера.
Язык С++ (C++) является расширенной версией языка С, в ней
сохранены все его сильные стороны. Язык был создан для поддержки
объектно-ориентированного программирования (ООП). Язык С++
представляет собой развитие и совершенствование лучших свойств
предшествующих языков программирования. Язык С++ разрабатывал Бьерн
Страуструп. Первая публикация о С++ была сделана в 1983 г., но развитие и
модификация языка проводились до конца 1980 годов. Язык С++ стал
популярным языком у программистов. Одной из причин такой популярности
является почти полная совместимость С и С++. Кроме того, хорошие
компиляторы для С++ доступны и недороги. Для языка С++ в настоящее
время разработаны мощные интегрированные среды (Builder C++, Visual
C++ 6), которые поддерживают концепции ООП.

 Языки программирования. Язык С. Конспект лекций -14-


ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ
ЯЗЫКОВ ПРОГРАММИРОВАНИЯ.
ПРЕПРОЦЕССОР И МАКРООБРАБОТКА.
ЭТАПЫ РЕШЕНИЯ ЗАДАЧ НА КОМПЬЮТЕРЕ

План

1. Общие принципы построения языков программирования.


2. Препроцессор и макрообработка.
3. Этапы решения задач на компьютере.

1. Общие принципы построения языков программирования

При проектировании языка программирования необходимы критерии


его оценки. Язык очень сложная система, и выработать единые критерии,
наверное, невозможно. Этот факт подтверждается наличием большого числа
языков программирования. Рассмотрим характеристики, которые, по мнению
некоторых ведущих специалистов, должны присутствовать во всех языках
(Роберт У. Себеста).
Одним из важных критериев является читаемость программ, которые
написаны на этом языке. Напомним, что машинный код и язык Ассемблер
не удовлетворяют этому критерию. Программы, которые легко понять, легче
в эксплуатации. Здесь надо обратить внимание, для решения каких задач
разрабатывался тот или иной язык. Если язык используется для решения
задач, на которые он не был нацелен, то программы могут оказаться
трудными для понимания. Очевидно, чем ближе синтаксис языка к
естественным, применяемым в реальной жизни конструкциям, тем более
понятным становится код программы.
Простота – важнейший критерий языка. Язык, содержащий много
конструкций, изучить сложно. Разные программисты могут пользоваться
разными конструкциями, что может привести к взаимному непониманию
разработанных программ.
Ортогональность – возможность с помощью небольшого числа
базовых конструкций и ограниченным числом способов выразить структуры
данного языка. Ортогональность языка связана с простотой. Отсутствие
ортогональности при использовании разных структур приводит к
увеличению способов организации элементов программы почти при
одинаковых ситуациях. Ортогональность позволяет решить трудную задачу,
используя небольшой набор элементарных конструкций.
Синтаксис языка оказывает влияние на простоту программы и ее
читаемость. Для языка важными являются правила формирования имен
конструкций, которые используются в языке.

 Языки программирования. Язык С. Конспект лекций -15-


ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ
1. Общие принципы построения языков программирования

Большое значение имеют ключевые слова, при помощи которых


формируются управляющие конструкции языка. Чем ближе по смыслу
ключевые слова к принятой терминологии, которая реально используется при
решении задачи, тем программа более понятна и читаема.
Форма и смысл директив должны быть связаны между собой.
Особенно это важно для понимания языка на первом этапе его изучения.
Поддержка абстракций является важной характеристикой на
современном этапе развития языков программирования. Абстракция
позволяет использовать сложные структуры, не вникая в детализацию
формирования этих структур. Поддержка абстракции влияет на легкость
использования языка.
Программа, составленная на языке программирования, должна быть
надежной. На надежность программы влияют:
обработка исключительных ситуаций,
проверка типов,
совмещение имен,
легкость чтения и эксплуатации.
В дальнейшем рассмотрим эти положения подробнее при изучении
конкретного языка программирования.
Стоимость языка программирования зависит от многих его
характеристик. В стоимость языка входят затраты:
на обучение программиста,
создание программы,
компиляцию программы,
выполнение программы,
приобретение языка,
низкую надежность,
эксплуатацию программы (внесение исправлений, модификация,
сопровождение программного обеспечения).
Итак, для простоты представления и читаемости алгоритмов, которые
должны выполняться на компьютере, применяются языки программирования
(ЯП). Программа представляет собой алгоритм решения некоторой задачи.
Она преобразовывает некоторую начальную информацию. Эта начальная
информация характеризуется различными типами данных (числами,
символами, строкам и более сложными структурами). Алгоритм определяет
вычислительные операции, при помощи которых преобразовывается
информация. ЯП должен содержать соглашения о типе информации,
функциях и операторах, при помощи которых происходит преобразование
данных, и быть устроен так, что алгоритм, сформулированный в виде
программы, выполняется на компьютере. И хотя все языки нацелены на
решение одинаковых задач, тем не менее многие из них различаются по
своим концепциям.
ЯП имеют сходство с естественными языками и математическими
формулами. С помощью языков устанавливается способ записи программ.
Одной из проблем при описании языка является то, что пользователи языка

 Языки программирования. Язык С. Конспект лекций -16-


ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ
1. Общие принципы построения языков программирования

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


Разработчики языков программирования должны уметь определять способ
образования выражений, директив и программных конструкций языка.
Изучение языков может быть разделено на исследование синтаксиса и
семантики. Синтаксис языка – это форма, семантика – смысл его директив,
выражений, программных конструкций.
Итак, внешняя форма программ на ЯП устанавливается с помощью
синтаксиса языка, который определяет формальный язык. Синтаксис
описывается с помощью определенных правил или формул, которые задают
множество предложений. Знание синтаксических правил позволяет
установить, принадлежит ли некоторая написанная последовательность слов
предложениям данного языка. Кроме того, эти правила определяют
структуру предложений языка, которые позволяют установить их смысл
(семантику). Следовательно, определения, связанные со структурой, следует
рассматривать еще и как средство распознавания смысла (или средства
определения семантики языка [4]). Значение и способ действия программы на
каком-либо языке уточняются путем задания семантики, то есть
установлением смысла значений языковых элементов. Итак, синтаксис
и семантика языка определяют его структуру.
Синтаксис языка определяется над некоторым множеством элементов,
которые называются символами. Он задает корректную последовательность
символов, которую можно использовать в программе. Существует форма
записи для формального описания синтаксиса языков программирования.
Эту форму записи независимо друг от друга предложили Джон Бэкус и Ноам
Хомски. Формализм, который используется при определении языка, его
синтаксиса, называется бэкус-нуровой формой (БНФ). Форма БНФ
представляет собой способ описания синтаксиса. Существует еще одна
форма описания синтаксиса языка – синтаксическая диаграмма. Есть
правила построения синтаксической диаграммы. Используя эти правила,
можно строить синтаксическую диаграмму любой конструкции языка. Это, в
свою очередь, облегчает проведение синтаксического анализа конструкций.
БНФ и синтаксические диаграммы предназначены для построения программ
грамматического разбора синтаксических структур ЯП. Для описания
семантики выбирают математическую форму описания, то есть
математическим объектам сопоставляют конструкции языка.
Для того чтобы программу, написанную на каком-либо языке,
компьютер выполнил, необходимо переводить ее на язык соответствующей
машины. Процесс перевода программы на машинный язык называется
компиляцией. Роль компиляторов выполняют трансляторы, которые
являются специальными программами, способными анализировать текст
программ и генерировать машинный код. Компьютер выполняет код
программы на машинном языке и выдает результат обработки информации,
согласно реализованному алгоритму. Заметим, что компиляция не
единственный метод реализации языка, но большинство коммерческих
реализаций выполняются с помощью компилятора.

 Языки программирования. Язык С. Конспект лекций -17-


ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ

2. Препроцессор и макрообработка.

Препроцессор – инструмент языка C++, при помощи которого


происходит обработка текста программы перед компиляцией (трансляцией).
Директивы (операторы) препроцессора начинаются с символа #. При каждом
запуске компилятора сначала запускается препроцессор, который находит
инструкции, начинающиеся символом #. Эти инструкции изменяют текст
исходной программы.
В результате появляется новый, временный файл, который пользователю
не виден. Сам компилятор анализирует не исходный файл, а тот, который
был сформирован в результате макрообработки. Директивы препроцессора
перечислены в справочном материале данного комплекса. С назначением
некоторых из них познакомимся позже.
Наиболее часто используются в программах директивы препроцессора
#include и #define. Директива #include дает указание компилятору
присоединить к программе файл, имя которого следует сразу за этой
инструкцией. При помощи директивы #define происходит поиск и замена
одного набора символов на другой. Эту же директиву препроцессора можно
использовать для определения макросов. Макрос представляет собой часть
программы, которая может выглядеть и действовать как функция.
Содержимое макроса, которое задается после #define, подставляется во время
работы препроцессора и создает так называемый встраиваемый код.
Макрос – это символическое имя некоторых операций. В языке C некоторые
библиотечные функции реализованы как макросы.
Макросы имеют большое значение в C, но в C++ они используются
значительно реже. Первое правило о макросах: не используйте их, если нет
необходимости это делать. Практически каждый макрос свидетельствует о
недостатке в языке или программе. Так как макросы изменяют код
программы до обработки его компилятором, то они создают проблемы для
многих инструментов разработки интегрируемой среды. Поэтому при
использовании макросов можно ожидать худшей работы отладчиков,
генераторов списков и т. д. При помощи макросов можно создать свой
собственный язык. Но если попытается сделать что-нибудь нетривиальное
в этом направлении, то это либо невозможно, либо слишком трудоемко.
Большинство компиляторов содержат ряд полезных встроенных
макрокоманд, например, __DATE__, __TIME__, __LINE__ (дата, время,
строка и т. д.). Каждая такая команда окружена двумя знакам подчеркивания.
Встретив один из этих макросов, препроцессор делает соответствующую
замену. Вместо __DATE__ устанавливается дата. Вместо __TIME__ –
текущее время. Директива __LINE__ заменяется номером строки исходного
текста.

 Языки программирования. Язык С. Конспект лекций -18-


ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ
2. Препроцессор и макрообработка.

В языке C++ многие возможности директив препроцессора можно


заменить директивами языка. Более того, автор языка Страуструп выражал
свое намерение освободить язык от препроцессора.
Задание для самостоятельной работы. Изучить директивы #define, #line,
#if, #else, assert().

3. Этапы решения задач на компьютере

Решение любой задачи на компьютере состоит из нескольких этапов:


1) постановка задачи,
2) выбор модели,
3) разработка метода решения,
4) разработка алгоритма решения,
5) составление программы,
6) отладка программы,
7) расчет задачи по программе, обработка и анализ результатов.
Первые два этапа, как правило, выходят за рамки возможностей
студентов и считаются заданными. Фактически формулировка задачи неявно
закрывает эти этапы. Остальные этапы присутствуют, однако на шестой и
седьмой этапы редко акцентируется внимание. Для учебных программ эти
этапы сводятся к простой проверке решения на 1–2 тестовых задачах. Для
реальных задач этапы шесть и семь важны. Что касается обработки и анализа
результата, то это требует от программиста высокого уровня квалификации,
компетенции и глубины знания проблемы. В учебных задачах студент, как
правило, работает в рамках третьего, четвертого и пятого этапов. Очень часто
оказывается, что третий этап для задачи известен, а этапы три и четыре по
своей сути тождественны, то есть решение третьего этапа тождественно
этапу четыре и, наоборот, алгоритм решения фактически является методом
решения. Распределение трудностей этапов три и четыре может быть самым
разнообразным. Здесь возможны любые комбинации трудностей. Если
построить таблицу распределения трудностей этапов три и четыре, она
может выглядеть так.

Распределения трудностей этапов 3 и 4

Этап 3 т т н/т н/т


Этап 4 т н/т т н/т

Важным моментом третьего этапа является выбор структуры данных


для решения поставленной задачи, от этого будет зависеть метод решения
задачи и алгоритм его реализации. Структура данных – это внутренний
вопрос программы. Формат данных, который принят для решения задачи на
ПК, вовсе не обязательно должен соответствовать (или совпадать) виду

 Языки программирования. Язык С. Конспект лекций -19-


ЛЕКЦИЯ 2. ОБЩИЕ ПРИНЦИПЫ ПОСТРОЕНИЯ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ
3. Этапы решения задач на компьютере

начальной информации от источника задачи. Программист сам решает


вопрос о том, в каком виде ему надо представить входную информацию. Это
не значит, что он должен требовать от заказчика изменить форму
представления начальных данных. Форма представления данных должна
иметь естественный, сложившийся вид. Вопрос о переформатирования
начальной информации к удобному для программы виду должен решаться
автоматически в рамках самой программы. Начинающий программист даже
для простых задач, в которых вопрос о структуре данных не столь важен,
должен по возможности всегда продумывать вопрос о представлении
данных. Н. Вирт в книге «Алгоритмы + структуры данных = программы»
подчеркивает важность вопроса о структуре данных при проектировании
программы.
Далее не будем проводить разделение решения задачи на этапы, так как
для учебных задач они часто размыты и не столь четко отделены друг от
друга. Однако иногда будем отмечать, к какому этапу отнести те или иные
рассуждения при анализе ее решения. В учебном курсе, как правило,
трудными задачами являются те, для которых требуется разработать метод
решения. Такие задачи можно разделить на два класса. Первый класс не
требует специальных знаний для разработки метода решения, второй класс
задач такие специальные знания предполагает. Задачи, для которых
требуется разработать метод решения, скорее относятся к математике, чем к
программированию, что и делает их трудными для студентов.

 Языки программирования. Язык С. Конспект лекций -20-


ЛЕКЦИЯ 3. СОВРЕМЕННЫЕ ИНТЕГРИРОВАННЫЕ
СРЕДЫ. ВСТРОЕННЫЙ ОТЛАДЧИК.
БИБЛИОТЕКА ПРОГРАММ И КЛАССОВ
План

1. Современные интегрированные среды.


2. Встроенный отладчик.
3. Библиотеки программ и классов.

1. Современные интегрированные среды

Интегрированная среда (IDE – Integrated Development Environment)


представляет собой некоторый единый комплекс для разработки программ.
Она позволяет легко создавать, открывать, просматривать, редактировать,
сохранять, компилировать и отлаживать любые программы. Управляет
работой IDE набор команд меню. Комплекс современных IDE представляет
собой большой набор инструментальных средств для построения программ.
В этом комплексе объединяются трансляторы, система управления работой
компиляций, редакторы, редакторы ресурсов, стандартные библиотеки,
справочный материал, примеры программ, система отладки программ и
система управления процессом отладки, которые демонстрируют
возможности использования операторов языка. Интегрируемая среда
содержит опции для своей настройки, которая соответствует требованиям
пользователя. Подробное описание IDE содержится в документации.
Изложение всех возможностей IDE потребовало бы объемного пособия. И
изучить все эти возможности IDE – сложная задача из-за большого объема
информации. Но принцип работы с IDE прост, и он присутствует во всех
информационных системах. Все возможности управления и настройки IDE
заключены в командах меню и их опциях. Многие из возможностей IDE
имеют автоматические значения по умолчанию, которые позволяют
среднестатистическому пользователю легко подготавливать программу и
запускать ее. Мощные возможности IDE требуются при разработке больших
информационных систем. Для учебной цели нет особой необходимости знать
на первых шагах все сложные возможности IDE. Так как материал курса
осваивается не на реальных задачах, а на небольших по объему задачах
средней сложности, то для успешного освоения курса надо, чтобы
пользователь умел:
загрузить (запустить) IDE;
пользоваться редактором IDE;
пользоваться файловой системой IDE;
пользоваться системой помощи и подсказок IDE;
пользоваться системой компиляции и запуска программ;
выходить из IDE.

 Языки программирования. Язык С. Конспект лекций -21-


ЛЕКЦИЯ 3. СОВРЕМЕННЫЕ ИНТЕГРИРОВАННЫЕ СРЕДЫ. ВСТРОЕННЫЙ ОТЛАДЧИК
1. Современные интегрированные среды

Здесь мы специально не идентифицируем имена команд меню, которые


выполняют перечисленные задачи. Каждая интегрируемая среда (Borland
C/C++, Visual C++, Visual C++ 6, Builder C++, Delphi) эти задачи выполняет
своим набором команд меню. Команды из любой вышеперечисленной IDE
легко и быстро демонстрируются на компьютере на практических занятиях.
По мере возрастания сложности составляемых программ постепенно
осваиваются более сложные возможности конкретной интегрируемой среды.
При разработке больших программных систем обойтись без знаний более
глубоких возможностей IDE нельзя. Слабое знание IDE может привести к
неоправданной трудоемкости при проектировании программ и недостаткам
конечного продукта. Но подобные системы лучше использовать и изучать
при проектировании больших программ. Необходимость в IDE возникла как
раз при разработке больших и очень больших программ. Поэтому очень
трудно демонстрировать возможности интегрируемой среды на учебных
примерах. Трудность не в демонстрации, а в неэффективности и
громоздкости использования мощного инструментария IDE для простых
модельных задач. Получается стрельба из пушки по воробью. И в этом
случае трудно мотивировать необходимость изучения IDE там, где что-то
можно реализовать проще. Кроме того, при рассмотрении модельных задач
программирования многие возможности интегрируемой среды просто
становятся невостребованными. Здесь возникает еще один методический
аспект. Детальное изучение IDE на самом деле требует полнокровного
учебного курса со своим набором модельных задач, содержание которых
направлено не на составление программ для обработки структур данных, а на
демонстрацию возможностей IDE. Поэтому (по мнению авторов) наиболее
эффективное изучение интегрируемой среды сводится к демонстрации
некоторых принципиальных возможностей IDE на простых примерах,
оставляя студентам право, по мере необходимости, осваивать более глубокие
возможности самостоятельно. Заметим, что понятия встроенный отладчик,
генераторы кода / приложений фактически являются элементами, которые
формируют, наряду с другими возможностями, структуру IDE.

2. Встроенный отладчик

Отладка программы, то есть устранение возможных ошибок либо в


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

 Языки программирования. Язык С. Конспект лекций -22-


ЛЕКЦИЯ 3. СОВРЕМЕННЫЕ ИНТЕГРИРОВАННЫЕ СРЕДЫ. ВСТРОЕННЫЙ ОТЛАДЧИК
2. Встроенный отладчик

выполнять другую программу по одной команде;


останавливать выполняемую программу на заданной команде, которая
называется точкой останова (или контрольной точкой);
просматривать значение переменных в памяти.
До того как стали появляться интегрируемые системы со встроенными
отладчиками, программист отлаживал программу, расставляя в некоторых
контрольных точках вывод информации из интересующих его переменных.
У современных встроенных отладчиков много возможностей. Но для
эффективного поиска ошибки в процессе обработки информации программой
достаточно выполнять программу медленно и следить за значениями
переменных в режиме выполнения программы. Одна из самых мощных
возможностей интегрированного отладчика – вывод содержимого
переменных программы. Это достигается помещением переменных
программы в окно наблюдения (Watches). Для того чтобы поместить какую-
либо переменную в окно наблюдения в интегрируемой среде Borland надо:
установить курсор на имя переменной;
нажать Ctrl+F7;
нажать Enter.
После помещения переменной в окно надо перейти к пошаговому
выполнению программы. Пошаговое выполнение программы происходит при
нажатии клавиши F7 при условии, что курсор находится в окне рабочего
поля языка C++. В окно наблюдения можно поместить несколько
переменных. Пошаговое выполнение программы при желании можно
прекратить. Для этого надо нажать Ctrl+F9 и тем самым перейти в рабочий
режим, то есть в режим непрерывного выполнения директив.
Если программа большая и содержит много строк, то пошаговое
выполнение программы трудоемко по времени. В этом случае можно
использовать точки останова. Чтобы задать точку останова в интегрируемой
среде Borland надо:
установить курсор на строку останова;
нажать клавишу F2 или в команде меню DEBUG выбрать вариант
Breakpoints.
Обратим внимание на то, что в различных IDE порядок действий и
инструменты (клавиши), при помощи которых выполняются эти действия
для поставленных задач, могут быть разными. Для использования
возможностей отладчика в конкретной интегрируемой среде надо
предварительно ознакомиться с его описанием и инструкцией.

3. Библиотеки программ и классов

Составляя программы, мы постоянно будем использовать некоторые


функции. Эти функции (например, clrscr(), getch(), cout, printf() и т. д.) входят
в состав стандартной библиотеки. Современные задачи настолько сложны,
что с ними невозможно справиться, если не привлекать дополнительные

 Языки программирования. Язык С. Конспект лекций -23-


ЛЕКЦИЯ 3. СОВРЕМЕННЫЕ ИНТЕГРИРОВАННЫЕ СРЕДЫ. ВСТРОЕННЫЙ ОТЛАДЧИК
3. Библиотеки программ и классов

инструменты, которые содержат готовые библиотеки. Современная


библиотека играет роль инфраструктуры, которая обеспечивает успешное
решение проблемы. Без хорошего дополнительного окружения, которое
содержится в библиотеке, вряд ли можно надеяться на успешное завершение
поставленной задачи. Стандартная библиотека C++ содержит множество
встроенных функций, которые программисты могут использовать в своих
программах. По существу все эти функции не являются частью языка C++,
но их знает компилятор. При использовании программистом библиотечной
функции компилятор связывает код программы с кодом этой функции.
В C++ библиотека функций содержит стандартную библиотеку
шаблонов (STL). Библиотека шаблонов – это набор классов и функций
общего назначения. При помощи этого набора можно реализовывать
алгоритмы и структуры данных, которые часто используются в процессе
обработки информации. Библиотека шаблонов состоит из трех основных
элементов – контейнеров, алгоритмов и итераторов.
Контейнеры – это объекты, которые содержат другие объекты.
Например, элементами контейнера являются такие АТД (абстрактный тип
данных) как векторы, очереди, списки, стеки. Каждый контейнерный класс
определяет набор функций, которые можно использовать в данном
контейнере, и множество алгоритмов работы над переменными.
Алгоритмы предназначены для обработки содержимого контейнеров.
Они могут совершать поиск нужного элемента, сортировать, определять
максимальный или минимальный элемент и т. д. Все алгоритмы можно
применять к контейнеру любого типа, так как они представляют собой
шаблонные функции. Списки алгоритмов STL, как правило, даются в
таблицах справочного материала в пособиях.
Итераторы – это объекты, которые осуществляют доступ к элементам
контейнера. Фактически итераторы играют роль указателей. Над ними можно
выполнять операции инкремента (++) и декремента (--).
Для плодотворного и успешного использования библиотеки классов
надо, прежде всего, освоить элементы объектно-ориентированного
программирования (ООП) и хорошо ориентироваться в шаблонах. Полное
описание библиотеки STL может занять целую книгу. Возможности средств
данной библиотеки таковы, что можно говорить о STL-программировании.
Формат кодов, которые используют функции STL, естественно, во многом
совпадает с синтаксисом ООП и требует определенных навыков для
корректной записи этих кодов в процессе программирования. В учебных
пособиях описание библиотеки STL, демонстрация ее возможностей и
способы применения, как правило, даются после полного изложения курса
ООП.
Стандартная библиотека должна быть в каждой реализации языка,
чтобы все программисты могли ее использовать. Стандартная библиотека
C++:

 Языки программирования. Язык С. Конспект лекций -24-


ЛЕКЦИЯ 3. СОВРЕМЕННЫЕ ИНТЕГРИРОВАННЫЕ СРЕДЫ. ВСТРОЕННЫЙ ОТЛАДЧИК
3. Библиотеки программ и классов

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


типами переменных, во время работы программы.
Предоставляет информацию о зависящих от реализации аспектах языка
(например, о диапазоне представления переменных).
Предоставляет функции (например, sin(), log() и т. д.), которые не
могут быть написаны оптимально для всех систем на языке C++.
Предоставляет нетривиальные средства, такие как потоки ввода/
вывода.
Предоставляет средства для расширения возможностей, такие как,
поддержка новых типов данных. Обеспечивает ввод/вывод для определяемых
им новых типов в стиле ввода/вывода для встроенных типов.
Служит общим фундаментом для других библиотек.
Предлагаемые библиотекой C++ средства должны удовлетворять
следующим требованиям:
Быть доступными для каждого пользователя, в том числе для
создателей других библиотек.
Быть достаточно эффективными.
Быть независимыми от алгоритмов, которые используются при
программировании.
Быть удобными и достаточно безопасными в большинстве ситуаций.
Быть завершенными в том, что делают.
Быть безопасными по умолчанию с точки зрения типов.
Хорошо дополнять встроенные типы и операции.
Поддерживать общепринятые стили программирования.
Быть способными к расширению, чтобы работать с типами, которые
определяет пользователь.
Средства стандартной библиотеки распределены по файлам, а они
сгруппированы по выполняемым функциям.

 Языки программирования. Язык С. Конспект лекций -25-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА.
ВЫВОД ТЕКСТА НА ЭКРАН.
ДИРЕКТИВЫ CLRSCR() И GETCH()

План
1. Простейшая программа.
2. Вывод текста на экран.
3. Препроцессор.
4. Директивы clrscr() и getch().

1. Простейшая программа

Язык программирования – это некоторый набор символов, директив


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

void main()
{
}

Итак, все программы языка С начинаются с директивы void main(), за


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

void main()
{ }

или

void main () { }

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

 Языки программирования. Язык С. Конспект лекций -26-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
1. Простейшая программа

Замечание 1. Изменять написание директивы в программе по своему


усмотрению нельзя. Любое изменение директивы является ошибкой, и
программа с неточно записанными директивами компьютером не
воспринимается. Приведем примеры неправильных кодов программы-
пустышки.
Void main()
{
}

void main();
{
}

void main
{
}

void main()
{
}.
Поясним, почему коды представленных программ неправильны.
В программе 1 в слове Void используется заглавная буква V, в программе 2
после директивы void main() поставлена точка с запятой, в программе 3 после
слова main нет скобок, в программе 4 после директивы закрывающая
фигурная скобка поставлена точка.
Замечание 2. Все написанные директивы программы выполняются
компьютером по порядку начиная с первой.
Замечание 3. Директивы открывающая фигурная скобка и
закрывающая фигурная скобка могут использоваться в программе
произвольное количество раз. Программу-пустышку, например, можно
записать так:
void main()
{ { { } } }

или
void main()
{ {} {} {} }

При многократном использовании фигурных скобок надо соблюдать


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

 Языки программирования. Язык С. Конспект лекций -27-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
1. Простейшая программа

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


1. Загрузить Borland C.
2. Вызвать редактор языка.
3. Использовав символы клавиатуры, набрать текст программы.
4. Использовав комбинацию клавиш Ctrl+F9, активизировать
выполнение набранной программы.
Язык C поддерживает два экрана:
1) рабочего поля языка С;
2) выдачи результатов расчета.
Переход из одного экрана в другой осуществляется нажатием
комбинацией клавиш ALT+F5.
Экран рабочего поля языка C представляет собой окно с набором
команд меню, которые управляют работой интегрируемой среды Borland C.
Экран выдачи результатов предназначен для информации, которую
необходимо вывести пользователю на экран в процессе работы программы.
Естественно, вывод необходимой информации на экран определяет
программист, который разрабатывал данную программу. Информация на
экране может появляться и в процессе работы программы и при ее
завершении. Возможен случай, когда в прцессе работы программы вывод
информации на экран не предусмотрен. Примером последнего случая может
служить программа-пустышка.
Замечание 4. После завершения работы программы компьютер
возвращается в рабочее поле языка C.

2. Вывод текста на экран

Для вывода текста на экран монитора в языке C можно использовать


две директивы (функции):

cout – функция бесформатного вывода информации на экран;


printf – функция форматного вывода текста на экран

Функция cout. Правила формирования вывода текста на экран.


1. Код вывода текста на экран начинается с директивы cout.
2. Выводимый текст должен следовать за символом << (двойной знак
меньше).
3. Выводимый текст заключается в символ “ “ (двойные кавычки).
4. Если в выводимом тексте встретился символ \n (обратный слеш, n),
то после этого символа последующий текст выдается на экран монитора с
новой строки.
5. Код вывода информации на экран завершается символом ; (точкой с
запятой).

 Языки программирования. Язык С. Конспект лекций -28-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
2. Вывод текста на экран

Пример 1. cout << “Доброе утро студент”;


Функция printf. Правила формирования вывода текста на экран.
1. Код вывода текста на экран начинается с директивы printf.
2. Выводимый текст находится между символом “ “ (двойные кавычки)
и заключается в круглые скобки.
3. Если в выводимом тексте встретился символ \n (обратный слеш, n),
то после этого символа последующий текст выдается на экран монитора с
новой строки.
4. Код вывода информации на экран завершается символом ; (точкой с
запятой).
Пример 1. printf (“Доброе утро студент”);

3. Препроцессор

Препроцессор – это инструкции компилятору. Эти инструкции


называются директивами препроцессора. Все директивы препроцессора
начинаются со знака #.
Функции (директивы) языка C разбиты на логические группы, которые
распределяются по стандартным файлам. Например, функции cout и printf
соответственно содержатся в файлах iostream.h и stdio.h. Эти файлы, как
правило, располагаются в каталоге INCLUDE.
Стандартные файлы присоединяются к программе директивой
#include, относящейся к директивам препроцессора. Все директивы
препроцессора размещаются перед заголовком void main(). Для того чтобы
иметь возможность в какой-нибудь программе использовать директиву cout,
надо файл iostream.h присоединить к этой программе. Полный вид
директивы, которая присоединяет, например, файл iostream.h к составляемой
программе, имеет вид #include <iostream.h>.
Программа 1 «Доброе утро, студент»
#include <iostream.h>
void main()
{
cout << “Доброе утро, студент!!!”; // Выводит на экран текст,
// который заключен в двойных
// кавычках.
}

После работы данной программы на экране выдачи результатов


появится следующая строка:
Доброе утро, студент!!!
Замечание 4. После завершения работы программы компьютер
возвращается в рабочее поле языка. Чтобы ознакомиться с результатом

 Языки программирования. Язык С. Конспект лекций -29-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
3. Препроцессор

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


этого, как говорилось выше, надо нажать клавиши Alt + F5.
Приведем несколько вариантов вывода текстов на экран монитора с
использованием директивы cout. Отметим, что это лишь некоторые
различные возможности, которые можно использовать в процессе написания
программы, если в этом есть необходимость.
1. cout << “Это я, веселый студент, шлю миру горячий привет!!!”;
2. cout << “Это я, веселый” << “ студент,” << “ шлю миру”
<< “ горячий привет!!!”;
3. cout << “Это я,\nстудент, \nшлю миру \nгорячий привет \n!!! \n”;
4. cout << “Это я, веселый студент, шлю миру горячий привет!!! \n”;
5. cout << “Это я, студент, шлю миру горячий привет!!!” << “ \n”;

Приведем результаты выполнения директив в перечисленных


вариантах:
1. Это я, веселый студент, шлю миру горячий привет!!! _
2. Это я, веселый студент, шлю миру горячий привет!!! _
3. Это я,
студент,
шлю миру
горячий привет
!!!
_
4. Это я, веселый студент, шлю миру горячий привет!!!
_
5. Это я, студент, шлю миру горячий привет!!!
_

Символ _ показывает положение курсора после вывода текста на экран


монитора.
Программа «Доброе утро» с использованием функции printf() будет
выглядеть следующим образом.
Программа 2
#include <stdio.h>
void main()
{
printf(“Доброе утро, студент!!!”);
// Выводит на экран текст, который
} // заключен в двойных кавычках.

Замечание 5. Директивы программы, которые расположены между


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

 Языки программирования. Язык С. Конспект лекций -30-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
3. Препроцессор

Замечание 6. В языке C текст, который следует после символа //


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

4. Директивы clrscr() и getch()

Функция getch() ожидает нажатие клавиши. Данную функцию можно


использовать для просмотра экрана выдачи результатов на различных этапах
работы программы. Когда компьютер встречает директиву getch() он
переходит в режим ожидания, при котором на мониторе выдается экран
результатов расчета. И в этом режиме он находится до тех пор, пока
пользователь не нажмет какой-нибудь символ на клавиатуре. Функция getch()
содержится в файле conio.h.
Рассмотрим программу «Доброе утро», в которую включена функция
getch().
Программа 3 «Доброе утро, студент».
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
void main()
{
clrscr(); // Очищает экран монитора.
cout << “Доброе утро, студент!!!”; // Выводит на экран выдачи
// текст, который заключен
// в двойных кавычках.
getch();
}

После запуска программы на экране результатов расчета появится


информация:
Доброе утро, студент!!!
Затем компьютер перейдет к выполнению директивы getch().
В процессе ожидания выполнения этого кода компьютером пользователь
может изучить (или просмотреть, проанализировать, проверить)
информацию на экране выдачи, а затем нажать любую клавишу. Только
тогда компьютер завершит выполнение программы и вернется на рабочее
поле языка C.
На экране выдачи результатов обычно находится информация, которая
остается от предыдущей работы компьютера. Поэтому если программу

 Языки программирования. Язык С. Конспект лекций -31-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
4. Директивы clrscr() и getch()

«Доброе утро» запустить несколько раз (например, пять), то на экране


выдачи результатов будет следующее:
Доброе утро, студент!!!
Доброе утро, студент!!!
Доброе утро, студент!!!
Доброе утро, студент!!!
Доброе утро, студент!!!
На экране монитора желательно сохранять только ту информацию,
которая появилась в процессе работы последней программы. В языке C есть
директива clrscr(), которая удаляет информацию с экрана выдачи результатов
расчета. Эту функцию можно использовать для очистки экрана перед
выдачей новых результатов работы программы. Данная функция содержится
в файле stdio.h.
Рассмотрим программу «Доброе утро», в которую включим директиву
clrscr()
Программа 4 «Доброе утро, студент».
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
void main()
{
clrscr(); // Очищает экран монитора.
cout << “Доброе утро, студент!!!”; // Выводит на экран выдачи текст,
// который заключен в двойных кавычках.
getch();
}

Как только эта программа начнет работать, код clrscr() удалит с экрана
выдачи результатов расчетов всю информацию, которая находилась там до
последнего запуска программы. Поэтому, сколько бы раз программа «Доброе
утро» ни запускалась, на экране выдачи результата будет сохраняться только
одно сообщение:
Доброе утро, студент!!!
То есть будет сохраняться результат последней работы программы.
Любую директиву в программе можно использовать многократно.
Применение той или иной директивы диктуется только условием задачи.
Программа 5
#include <conio.h>
#include <stdio.h>
void main()
{
clrscr();
clrscr();
clrscr();
getch();

 Языки программирования. Язык С. Конспект лекций -32-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
4. Директивы clrscr() и getch()

getch();
clrscr();
clrscr();
getch();
getch();
}

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


информацию с экрана, на котором информация уже отсутствует, и нужно
четыре раза нажать какую-нибудь клавишу, для того чтобы вернуться в среду
языка C.
Поговорим теперь о порядке записей директив в программе. Порядок
записи определяется целью задачи. Для задачи «Доброе утро» возможна,
например, такая последовательность директив:
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{
cout << “Доброе утро, студент!!!”;
clrscr();
getch();
}
В этом случае компьютер будет выполнять действия в следующем
порядке:
1. На экран монитора выводится текст «Доброе утро, студент!!!». Но
на экране при этом может присутствовать информация о работе предыдущей
программы.
2. Удаляется вся информация с экрана.
3. Компьютер ожидает нажатие клавиши.
4. После нажатия клавиши происходит переход в оболочку языка C.
При выполнении директив в таком порядке после завершения работы
программы на экране монитора будет отсутствовать текст послания. При
последовательности директив
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{
cout << “Доброе утро, студент!!!”;
getch();
clrscr();
}

 Языки программирования. Язык С. Конспект лекций -33-


ЛЕКЦИЯ 4. ПРОСТЕЙШАЯ ПРОГРАММА. ВЫВОД ТЕКСТА НА ЭКРАН. ДИРЕКТИВЫ CLRSCR() И GETCH()
4. Директивы clrscr() и getch()

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


1. На экран монитора выводится текст «Доброе утро, студент!!!». Но
на экране при этом может присутствовать информация о работе предыдущей
программы.
2. Компьютер ожидает нажатие клавиши, состояние экрана при
ожидании не меняется.
3. После нажатия любой клавиши удаляется вся информация, которая
была на мониторе, и компьютер переходит в оболочку языка C.
В этом случае после завершения работы программы на экране
терминала тоже не появится нужное сообщение. Отсюда вывод:
предложенный порядок расположения директив в двух последних примерах
не выполняет поставленную задачу, потому что на экране монитора после
работы программ не сохраняется текст «Доброе утро, студент!!!». Заметим,
что в процессе выполнения программ данный текст на экране появлялся. Для
подтверждения этого немного изменим код программы первого примера:
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{ cout << “Доброе утро, студент!!!”;
getch();
clrscr();
getch(); }

Здесь после вывода текста «Доброе утро, студент!!!» компьютер


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

 Языки программирования. Язык С. Конспект лекций -34-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ.
ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ
ТИПА INT И FLOAT. ВВОД С КЛАВИАТУРЫ

План
1. Память.
2. Переменные.
3. Вывод переменных на экран.
4. Запись в переменные типа int и float.
5. Ввод с клавиатуры.

1. Память

Память компьютера состоит из байтов. Байт – это устройство, которое


содержит 8 битов. Схематично байт представляют в виде прямоугольника,
разделенного на 8 одинаковых частей. Предполагается, что в каждой части
можно зафиксировать два устойчивых состояния: 0 или 1 (рисунок).
Емкость памяти машины измеряется в байтах. Каждый байт в памяти
имеет свой уникальный номер, который называется адресом байта.

Рис. 5.1

2. Переменные

Переменная – это именованная область памяти для записи и хранения


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

<тип переменной> <имя переменной>

Здесь <тип переменной> – ключевое слово базового типа данных. Тип


переменной определяет размер выделяемой памяти под информацию.

 Языки программирования. Язык С. Конспект лекций -35-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
2. Переменные

Базовые типы данных


Тип переменных Размер байт Диапазон значений
int 2 –32768÷32767
float 4 3.4E-38÷3.4E+38
long 4 –2147483648÷2147483647
double 8 1.7E-308÷1.7E+308
unsigned long 4 0÷4294967295

Тип определяет операции, которые можно применять для переменных.


Здесь <имя переменной> – идентификатор, т. е. произвольный набор из
символов клавиатуры. Имя переменной определяет составитель программы.
При формировании имени надо соблюдать некоторые ограничения:
нельзя использовать русские буквы и разделители (пробел, запятая,
точка и др.);
имя не должно совпадать со служебными (ключевыми) словами языка C;
прописные и строчные буквы являются разными символами, т. е.
переменные с именами n1 и N1 – разные переменные.

Примеры объявления переменных:

int a, b, c;

В памяти компьютера выделяется три области памяти размером в два


байта под именами a, b, c.

float r1, r2, wer;

В памяти компьютера выделяется три области памяти размером в


четыре байта под именами r1, r2, wer.

3. Вывод переменных на экран

Для вывода информации на экран из переменного можно использовать


директиву cout. При этом надо соблюдать правило – имя переменной, из
которой выводится информация, должно следовать за символом << (двойной
знак меньше). Если определена переменная n1, то код вывода информации из
n1 имеет следующий вид:

cout << “Целое число в n1 = ” << n1 << “\n”;

Представленную директиву компьютер будет выполнять в следующей


последовательности:
На экран результата выдаст текст в кавычках:

 Языки программирования. Язык С. Конспект лекций -36-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
3. Вывод переменных на экран

Целое число в n1 =

А затем – информацию из области памяти с именем n1.


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

Целое число в n1 =

В этом случае код вывода информации из переменной n1 будет


выглядеть так:
cout << n1 << “\n”;

Вывод текста на экран включен в код для пояснения процесса работы


компьютера. В данном случае программа сообщает о виде выводимой
информации.
cout – код бесформатного вывода, т. е. вид выводимой информации
определяется типом переменной. И поэтому в данном случае информацию,
которая хранится в области памяти компьютера с именем n1, компьютер
будет трактовать как целое число.

4. Запись в переменные типа int и float

Для записи информации (чисел) в переменные типа int или float можно
использовать оператор присваивания, который определяется символом =
(равно). Например, если код int k1; резервирует два байта памяти под именем
k1 для записи целых чисел, то код k1 =2; записывает (засылает) в эту область
памяти число 2.
Переменная может быть определена при ее объявлении. Например,
возможен в программе код float r1 = 0.8, we = –3.5; При выполнении
данного кода компьютер выделяет две области памяти с именами r1, we и
одновременно занесет в эту память соответственно числа 0.8 и –3.5.
Программа 1
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{ int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа резервирует память для четырех целых чисел\n”;
cout << “и засылает в переменные a, b и c”
<< “ соответственно числа 2, 5, 7\n”;
a = 2; b = 5; c = 7;
cout << “В память введена следующая информация:\n”;

 Языки программирования. Язык С. Конспект лекций -37-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
4. Запись в переменные типа int и float

cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
getch(); } // Ожидается нажатие клавиши.

В результате работы программы 1 на экране выдачи результатов будет


находиться следующая информация:

Эта программа резервирует память для четырех целых чисел


и засылает в переменные a, b и c соответственно числа 2, 5, 7
В память введена следующая информация:
a=2
b=5
c=7
Замечание 1. При объявлении переменных в выделяемых областях
памяти находятся случайные числа. Этот факт иллюстлируется следующей
программой.
Программа 2. Вывод информации из зарезервированных областей
памяти компьютера.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{ int a, b, c, z; // Резервируем память для четырех целых чисел.
clrscr(); // Очищается экран.
cout << “Эта программа резервирует память для четырех целых чисел. \n”;
cout << “В зарезервированной памяти “
<< “находится следующая информация: \n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c
<< “\nz = “ << z << “\n”;
getch(); } // Ожидается нажатие клавиши.

После выполнения данной программы на экране монитора появится


следующий результат ее работы:

Эта программа резервирует память для четырех целых чисел.


В зарезервированной памяти находится следующая информация:
a = Xa
b = Xb
c = Xc
z = Xz

Значение чисел Xa, Xb, Xc, Xz, которые вывел компьютер, заранее
невозможно определить. Поэтому программист должен сам заботиться об
инициализации заказанной памяти.
Замечание. Переменную можно не инициализировать, если она
используется после того, как ей присвоили какой-либо результат расчета.

 Языки программирования. Язык С. Конспект лекций -38-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
4. Запись в переменные типа int и float

Оператор & (амперсант) определяет адрес (т. е. физическую область


места памяти) объявленной в программе переменной.
Программа 3
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{ int n1, n2, n3, n4; // Резервируется память для четырех целых чисел.
clrscr(); // Очищается экран.
cout << “Эта программа резервирует память для целых чисел. \n”;
cout << “ Имя переменной n1 соответствует адресу – “ << &n1
<< “\n Имя переменной n2 соответствует адресу – “ << &n2
<< “\n Имя переменной n3 соответствует адресу – “ << &n3
<< “\n Имя переменной n4 соответствует адресу – “ << &n4 << “\n“;
getch(); }

После выполнения данной программы на экране монитора появится


следующий результат ее работы:

Эта программа резервирует память для четырех целых чисел.


Имя переменной n1 соответствует адресу – N1
Имя переменной n2 соответствует адресу – N2
Имя переменной n3 соответствует адресу – N3
Имя переменной n4 соответствует адресу – N4

Здесь N1, N2, N3, N4 – номера адресов ячеек памяти, которые компьютер
выделил программе для записи целых чисел.
Если надо выдать адреса в десятичной системе исчисления, следует
перед &n1, &n2, &n3 и &n4 поставить директиву преобразования типа
(unsigned int). Скобки в данном случае являются элементами директивы.
Cоставим программу для простой, но очень важной задачи, – поменять
местами значения переменных x и y.
Следующая программа переставляет числа, которые записаны в
переменных x и y.
Программа 4
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{ float x = -2.5, y = 5.1, z;
cout << “Значение в x= ” << x << “\nЗначение y= “ << y << “\n”;
z = x; // Пересылается информация из x в z.
x = y; // Пересылается информация из y в x.
y = z; // Пересылается информация из z в y.
cout << “\n Результат работы программы: \n\n”;

 Языки программирования. Язык С. Конспект лекций -39-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
4. Запись в переменные типа int и float

cout << “ Значение переменных после перестановки: \n”;


cout << “Значение в x= ” << x << “\nЗначение y= “ << y << “\n”;
getch(); }

5. Ввод с клавиатуры

Для ввода информации в память компьютера с клавиатуры


используются функции (директивы):

cin – бесформатный ввод данных с клавиатуры;


scanf – форматный ввод данных с клавиатуры.
Функции cin и scanf предоставляют возможность взаимодействовать с
программой во время ее работы.
Директива cin находится в стандартном файле iostream.h. Возможные
коды с использованием cin имеют, например, следующий вид:
cin >> x; С клавиатуры вводится информация в переменную x;
cin >> a >> b >> c; С клавиатуры вводится информация в
переменные a, b, c.
Для директивы cin можно сформулировать общие правила ввода
информации в переменную с именем x:
• код ввода информации в память начинается с директивы cin;
• далее следует символ >> (двойной знак больше);
• затем следует имя переменной, в которую надо вводить информацию
(в данном примере имя x);
• директива ввода информации должна заканчиваться символом ;
(точка с запятой).
Итак, надо запомнить, что если для ввода информации с клавиатуры
используется cin, то перед именем каждой переменной, в которую надо
ввести информацию с клавиатуры, должен находиться символ >> (двойной
знак больше).
Код cin >> x; есть директива ввода информации с клавиатуры в
переменную x. Для того чтобы программа выполнила эту директиву, надо,
используя клавиатуру, набрать требуемый набор символов и нажать клавишу
Enter. Директива cin определяет вид вводимой информации автоматически
по типу переменной, который объявляется в программе. Поэтому
рекомендуется перед вводом данных выводить на экран пояснительный текст
по типу вводимых данных.
Приведенная ниже программа демонстрирует ввод целых чисел с
клавиатуры.
Программа 5
void main()
{int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Очищается экран.
cout <<“Введи целое число (набери число и нажми Enter) ”;
cin >> a; // Ввод с клавиатуры числа в переменную a.

 Языки программирования. Язык С. Конспект лекций -40-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
5. Ввод с клавиатуры

cout << “Введи целое число (набери число и нажми Enter) ”;


cin >> b; // Ввод с клавиатуры числа в переменную b.
cout << “Введи целое число (набери число и нажми Enter) ”;
cin >> c; // Ввод с клавиатуры числа в переменную c.
cout << “Вы ввели числа: \n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
getch(); } // Ожидается нажатие клавиши.
При работе данной программы на экран монитора последовательно
будет выводиться информация:

Введи целое число a (набери на клавиатуре число и нажми Enter)


Введи целое число b (набери на клавиатуре число и нажми Enter)
Введи целое число c (набери на клавиатуре число и нажми Enter)

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


выполнять программу до тех пор, пока на клавиатуре не будет набрано число
и не нажата клавиша Enter. Целое число должно соответствовать диапазону
представления переменных типа int. Приведем код программы, которая
демонстрирует ввод трех целых чисел с клавиатуры с применением
директивы cin >> a >> b >> c.
Программа 6
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{ int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Очищается экран.
cout << “Введи с клавиатуры три целых числа a, b и c,\n”
<< “для этого набери три числа через пробелы, и нажми Enter ”;
cin >> a >> b >> c;
cout << “Вы ввели числа: \n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
getch(); }

При работе данной программы, после того как на экране монитора


появится сообщение

Введи с клавиатуры три целых числа a, b и c,


для этого набери три числа, разделенных пробелом, и нажми Enter ”;

надо, используя клавиатуру, через пробелы набрать три целых числа и


нажать клавишу Enter. После этого программа завершит свою работу.

 Языки программирования. Язык С. Конспект лекций -41-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
5. Ввод с клавиатуры

Функция scanf является функцией форматного ввода. Директива scanf


находится в стандартном файле stdio.h. Общий формат кода при
использовании функции scanf() имеет следующий вид:

scanf (<управляющая строка>, <список переменных>).

В управляющей строке указываются форматы вводимых переменных.


Символы, которые используются для формирования управляющей строки,
заключаются в двойные кавычки. В списке переменных через запятую
указываются адреса переменных, в которые надо ввести информацию с
клавиатуры. Возможные коды с использованием scanf имеют, например,
следующий вид:

scanf(“%d”, & x);


scanf(”%d, %f, %g”, &a, &b, &c);

Здесь “%d”, ”%d, %f, %g” являются управляющими строками. В данном


случае в строке указываются спецификаторы преобразования, или коды
форматов для переменных, которые вводятся с клавиатуры. Спецификатор
(формат) %d определяет ввод в переменную x целого числа, а %f и %g
определяют ввод в переменные b и c вещественных чисел. Для того чтобы
программа выполнила код scanf(“%d”, & x); следует, используя клавиатуру,
набрать целое число и нажать клавишу Enter. В отличие от cin, правила
использования scanf несколько сложнее и заключаются в следующем:
• в двойных кавычках размещаются символы, формирующие
инструкцию ввода – управляющую строку, в которой, например,
указываются форматы выводимых данных;
• после управляющей строки через запятые дается список адресов
вводимых переменных. Порядок перечисления списка переменных
определяется инструкциями управляющей строки.
Приведем код программы ввода трех чисел в память машины с
применением директивы scanf.
Программа 7
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
void main()
{int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Очищает экран.
cout << “Введи целое число a (набери число и нажми Enter ”;
scanf(“%d”,&a);
cout << “Введи целое число b (набери число и нажми Enter ”;
scanf(“%d”,&b);
cout << “Введи целое число c (набери число и нажми Enter ”;

 Языки программирования. Язык С. Конспект лекций -42-


ЛЕКЦИЯ 5. ПАМЯТЬ. ПЕРЕМЕННЫЕ. ВЫВОД НА ЭКРАН. ЗАПИСЬ В ПЕРЕМЕННЫЕ ТИПА INT И FLOAT
5. Ввод с клавиатуры

scanf(“%d”,&c);
cout << “Вы ввели числа: \n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
getch(); } // Ожидается нажатие клавиши.
Управляющая строка каждой директивы scanf программы имеет вид
“%d”, что означает ввод целого числа. При работе данной программы будут
происходить запросы на ввод целых чисел. После вывода на экран
очередного сообщения компьютер не будет выполнять программу до тех пор,
пока на клавиатуре не будет набрано число и не нажата клавиша Enter.
Целое число должно соответствовать диапазону представления переменных
типа int.
Приведем код программы, которая демонстрирует ввод трех целых
чисел с клавиатуры с применением директивы scanf(“%d%d%d”, &a,
&b, &c).
Договоримся в коде программ не писать директивы препроцессора,
присутствие которых, вообще говоря, обязательно. Но формат этих директив
во всех программах одинаковый и добавить в случае необходимости эти
директивы к тексту программы не представляет труда.
Следующая программа вводит три целых числа в память компьютера
через клавиатуру.
Программа 8
void main()
{int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Очищается экран.
cout << “Введи с клавиатуры три целых числа a, b и c,\n”
<< “Ннабери три числа, разделенных пробелом, и нажми Enter ”;
scanf(“%d%d%d”, &a, &b, &c);
cout << “Вы ввели числа: \n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
getch(); } // Ожидается нажатие клавиши.

Управляющая строка в данной программе имеет вид ”%d%d%d”. Это


означает, что с клавиатуры нужно ввести три целых числа. Адреса
переменных, куда заносятся данные, перечисляются через запятую после
управляющей строки.

 Языки программирования. Язык С. Конспект лекций -43-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ.
МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ

План

1. Арифметические операции.
2. Математические выражения и функции.

1. Арифметические операции

В языке C (как и в других языках программирования) используются


следующие арифметические операции:
+ сложение
– вычитание
* умножение
/ деление
% остаток от целочисленного деления.
Эти операции можно использовать при составлении программы для
вычисления суммы, разности, произведения или частного двух чисел.
Например, пусть заданы два вещественных числа a и b. Тогда коды
float a=6, b=2, z;
z= a + b; // Число из a сумируется с числом из b
// результат суммы засылается в z.
z= a – b; // Из числа в a вычитается число из b
// результат разности засылается в z.

z= a * b; // Число из a умнажается на число из b


// результат произведения засылается в z.

z= a / b; // Число из a делится на число из b


// результат деления засылается в z.

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


чисел и результат этого вычисления записывается в переменную z.
Приведем код программы, которая использует арифметические
операции.
Задача 1. Вычислить z по формуле z=a+bc, при a=2, b = 5 и c = 7.
Первое, на что мы обратим внимание: параметры a, b и c принимают
целые значения. Отсюда следует вывод, что и z принимает целое значение.
Для того чтобы выполнить эту задачу, мы должны сначала в память
компьютера ввести числа 2, 5 и 7, а затем над этими числами произвести
необходимые операции. Но прежде чем записать в память числа, мы должны
сначала зарезервировать место в памяти для ввода этих чисел. Итак,

 Языки программирования. Язык С. Конспект лекций -44-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
1. Арифметические операции

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


вычисления z надо зарезервировать четыре области памяти для записи и
хранения четырех целых чисел. Запишем код программы, который выполняет
эту часть задачи.
Следует обратить внимание на вопрос о структуре начальных данных,
так как это важная задача во всем процессе проектирования программ (книга
Н. Вирта: «Алгоритм + структура данных = программа»).
Программа 1. Резервирует память для решения задачи.
void main()
{ int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа выделяет память для 4 целых чисел \n”;
getch(); } // Ожидается нажатие клавиши.

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


5 и 7 в переменные a, b и c можно осуществить последовательностью
следующих директив:
a = 2; b = 5; c = 7;
или директивой (задавать значение переменной можно при ее
объявлении)
int a = 2, b = 5, c = 7;

Добавим перечисленные директивы в код программы 1. Тогда получим


следующую программу.
Программа 2. Засылает в память начальную информацию.
void main()
{ int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа резервирует память для четырех целых чисел \n”;
cout << “и засылает в переменные a, b и c”
<< “ соответственно числа 2, 5, 7\n”;
a = 2; // В переменную a засылается целое число 2.
b = 5; // В переменную b засылается целое число 5.
c = 7; // В переменную c засылается целое число 7.
getch(); } // Ожидается нажатие клавиши.

Для того чтобы контролировать работу программы, нужно проверить


содержимое заполненных ячеек. Добавляя в программу 2 директивы вывода
информации, введенные в память, получим следующий промежуточный код
программы.
Программа 3
void main()
{ int a, b, c, z; // Резервируется память для четырех целых чисел.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа резервирует память для четырех целых чисел\n”;

 Языки программирования. Язык С. Конспект лекций -45-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
1. Арифметические операции

cout << “и засылает в переменные a, b и c”


<< “ соответственно числа 2, 5, 7\n”;
a = 2; b = 5; c = 7;
cout << “В память введена следующая информация:\n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
getch(); } // Ожидается нажатие клавиши.

Прокомментируем директиву

cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;

Она будет выполняться слева направо, а именно:


произойдет вывод текста в кавычках “a = “ (на экране появится a =);
произойдет вывод значения числа в переменной a (на экране повится
a = 2);
произойдет перевод курсора на новую строку и вывод текста в
кавычках (на экране появится b =);
произойдет вывод значения числа в переменной b (на экране появится
b = 5);
произойдет перевод курсора на новую строку и вывод текста в
кавычках (на экране появится c =);
произойдет вывод значения числа в переменной с (на экране появится
c = 7);
курсор переводится на новую строку.
В результате работы программы 3 на экране выдачи результатов будет
находиться следующая информация:

Эта программа резервирует память для четырех целых чисел


и засылает в переменные a, b и c соответственно числа 2, 5, 7
В память введена следующая информация:
a=2
b=5
c=7

Теперь для окончательного выполнения задачи 1 в программу 3 после


вывода начальных данных из переменных a, b и c надо добавить директивы:

z = b * c; // Число из b умножается на число из c,


// результат заносится в z.
z = a + z; // Число из a суммируется с числом из z ,
// результат заносится в z.
cout << “Результат расчета a + b * c = “ << z << “\n”;

Написанные директивы неявно указывают на факт, что если в


некоторых переменных x и y находятся числа, то умножение (сложение,

 Языки программирования. Язык С. Конспект лекций -46-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
1. Арифметические операции

деление, вычитание) этих чисел и запись результата в переменную z


происходит при выполнении директивы z = x * y. При этом информация в
переменных x и y сохраняется, а в переменной z происходит удаление той
информации, которая в ней содержалась до записи нового результата.
Заметим, что все сказанное об операциях над числами справедливо для чисел
любого типа.
Теперь окончательно для задачи 1 приведем соответствующий ей код
программы.
Программа 4
void main()
{ int a, b, c, z: // Резервируется память для четырех целых чисел.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа вычисляет значение z = a + b * c “
<< “ при а = 2, b = 5 и c = 7. \n”;
a = 2; // В переменную a засылается число 2.
b = 5; // В переменную b засылается число 5.
c = 7; // В переменную c засылается число 7.
cout << “В память введена следующая информация: \n”;
cout << “a = “ << a << “\nb = “ << b << “\nc = “ << c << “\n”;
z = b * c; // Число из b умножается на число из c,
// результат заносится в z.
z = a + z; // Число из z суммируется с числом из a,
// результат заносится в z .
cout << “Результат расчета a + b * c= “ << z << “\n”;
getch(); } // Ожидается нажатие клавиши.

После выполнения этой программы на экране монитора появится


следующий результат:

Эта программа вычисляет значение z = a + bc при а = 2, b = 5 и c = 7.


В память введена следующая информация:
a=2
b=5
c=7
Результат расчета a + b * c = 37

Дадим другой вариант кода программы для решения задачи 1.

Программа 5
void main()
{ int a = 2, b = 5, c = 7, z; // Резервируется память для 4 целых чисел.
// При определении переменных в память
// засылаются начальные данные.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа вычисляет значение z = a + b * c “

 Языки программирования. Язык С. Конспект лекций -47-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
1. Арифметические операции

<< “ при а = 2, b = 5 и c = 7. \n”;


cout << “В память введена следующая информация: \n”;
cout << “a= “ << a << “\nb= “ << b << “\nc= “ << c << “\n”;
z = b * c; // Число из b умножается на число из c,
// результат заносится в z.
z = a + z; // Число из a суммируется с числом из z,
// результат заносится в z.
cout << “z = “ << a << “+” << b << “*” << c << “= “ << z
<< “\n”;
getch(); } // Ожидается нажатие клавиши.

Этот вариант программы отличается от предыдущего директивой


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

Эта программа вычисляет значение z= a+bc при а = 2, b = 5 и c = 7.


В память введена следующая информация:
a=2
b=5
c=7
z=2 + 5 * 7 = 37

Если величину z=a+bc надо вычислить при разных значениях


переменных, то определять начальные значения a, b и c в коде программы
неудобно. Неудобно потому, что любое изменение начального значения или
a, b или c требует исправления в коде исходного текста программы. Ниже
приведен код программы вычисления z=a+bc для произвольных (не только
целых) начальных значений чисел a, b и c.
Программа 6. Вычисляет значение z=a+bc при любых начальных
значениях чисел a, b, c.
void main()
{ float a, b, c, z; // Резервируется память для 4 вещественных чисел.
clrscr(); // Удаляется с экрана выдачи информация.
cout << “Эта программа вычисляет значение z = a + b c “
<< “ при любых начальных значениях а, b, c \n”;
cout << “Введи с клавиатуры 4 вещественных числа a b c: \n”;
cin >> a >> b >> c;
cout << “В память введены числа: \n”;
cout << “a= “ << a << “\nb= “ << b << “\nc= “ << c << “\n”;
z = a + b * c; // Число из b умножается на число из c,
// к ac прибавляется число из a и результат заносится в z.
cout << “z = “ << a << “+” << b << “*” << c << “= “ << z << “\n”;
getch(); } // Ожидается нажатие клавиши.

 Языки программирования. Язык С. Конспект лекций -48-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
1. Арифметические операции

Заметим, что в представленной программе коды:

z = b * c; // Число из b умножается на число из c,


// результат заносится в z.
z = a + z; // Число из a суммируется с числом из z,.

ранее использовавшиеся для вычисления значения z, заменяются на один


код: z = a + b * c;. Этот код можно, например, записать в следующих
вариантах: a= a + b * c; или b = a + b * c; или c=a+b * c;
Следует обратить внимание слушателей на последовательный процесс
решения задачи. На самом деле этот процесс является моделью
проектирования сложных программ. Приведем еще один код программы
решения задачи 1.
Программа 7. Вычисляет значение z=a+bc при любых начальных
знвчениях чисел a, b, c.
void main()
{ float n1, n2, n3, n4; // Резервируется память для 4 целых чисел.
clrscr(); // Очищается экран.
cout << “Эта программа вычисляет значение z = a + bc “
<< “ при любых начальных значениях а, b, c \n”;
cout << “Введи с клавиатуры 4 вещественных числа a b c: \n”;
cin >> n1 >> n2 >> n3;
cout << “В память введена следующая информация: \n”;
cout << “a = “ << n1 << “\nb = “ << n2 << “\nc = “ << n3 << “\n”;
n4 = n1+ n2 * n3;
cout << “z = “ << n1 << “+” << n2 << “*” << n3 << “= “ << n4
<< “\n”;
getch(); } // Ожидается нажатие клавиши.

На этом примере демонстрируется тот факт, что имена переменных –


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

z = a + b * c;

 Языки программирования. Язык С. Конспект лекций -49-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
1. Арифметические операции

принято объяснять так: к a прибавляется значение b, умноженное на c,


и результат заносится в z. Хотя более правильным было бы следующее
пояснение: к числу из переменной a прибавляется результат умножения
числа из b с числом из c, результат заносится в ячейку с именем z.

2. Математические выражения и функции

Математическое выражение есть комбинация из переменных,


операций, скобок, констант, элементарных математических функций и их
суперпозиций. Набор математических функций в языке С содержится в
стандартном файле math.h. Эти функции можно использовать при
вычислении математических выражений. Дадим неполный перечень этих
функций:

abs(i) – вычисляет абсолютное значение целого переменного i.


fabs(x) – вычисляет абсолютное значение действительного переменного x.
acos(x) – вычисляет угол, косинус которого равен x.
exp(x) – вычисляет значение ех.
sin(x), cos(x), tan(x) – вычисляет значения тригонометрических функций.
pow(x,y) – Вычисляет ху.
log(x) – вычисляет натуральный логарифм ln x.
log10(x) – вычисляет десятичный логарифм lg x.

Программа 8. Определить значение числа e.


void main()
{double e; // Определяется переменная для числа е.
clrscr();
e = exp(1); // Вычисляется e в 1-й степени.
cout << “Число e = “<< e << “\n”;
getch(); }

Программа 9. Вычислить значение sin x. Значение переменной x


задавать в градусах.
#include <math.h>
void main()
{ float x, y;
clrscr();
cout << “Введи угол в градусах для вычисления синуса “;
cin >> x;
cout << “Вы ввели ” << x << “ градусов\n”;
y = x * acos(-1)/180.; // Переводим градусы в радианы.
y = sin(y);
cout << “sin( “ << x << “ градусов) = ” << y << “\n”;
getch(); }

 Языки программирования. Язык С. Конспект лекций -50-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
2. Математические выражения и функции

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


выражения.
Задача 2

⎛ ⎞
⎜ sin 2 x + cos 2 y ⎟
Z =⎜ −e|cos x|+ sin y
⎟ ⋅ ln(| x | +1) − x 2 + 1 .
⎜ sin ⎛ x + y ⎞ + 1,5 ⎟
⎜ ⎜ 2 ⎟ ⎟
⎝ ⎝ ⎠ ⎠

Перед составлением программы надо ответить на стандартный набор


вопросов и проделать стандартный набор действий. Перечислим некоторые
из них. Внимательно изучить задачу. Основным результатом этого этапа
должны быть ответы на вопросы:
решалась ли подобная задача раньше;
можно ли разбить задачу на более простые;
какие данные входят в условие задачи;
какова форма представления этих данных в программе;
что вызывает затруднение при решении (можно упростить или
отбросить часть условий, которые создают трудности);
рассмотреть частный случай задачи;
рассмотреть часть задачи.
Задачу 2 можно разбить на подзадачи, решение которых не пред-
ставляет труда, а именно последовательно вычислить значения: z = х2 + 1,
y = z и т. д. Из условия задачи следует, что переменные x, y и z являются
действительными числами.
Если решение можно разбить на подзадачи, то начинающему
программисту (и не только ему) можно порекомендовать технологию
постепенного приближения к окончательному решению задачи. Такая
технология называется программированием снизу вверх. В реальном режиме
технология приближения к решению заключается в следующем:
к некоторому начальному тексту программы последовательно
добавляются новые директивы;
с каждым новым добавлением производится контроль выполнения этих
новых директив.
Программа 10. Окончательный код программы для решения задачи 2.
void main()
{
float x, y, z;
float z1;
cout << “Введи действительное число x = “;
cin >> x;
cout << “Введи действительное число y = “;
cin >> y;
cout << “Введены числа: \nx = “ << x << “\ny = “ << y << “\n”;

 Языки программирования. Язык С. Конспект лекций -51-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
2. Математические выражения и функции

z = fabs(cos(x)) + sin(y);
z = exp(z);
z1= sin(x) * sin(x) +cos(y) * cos(y);
z1 = z1/(sin((x + y)/2.) + 1.5);
z = z1 - z; // Вычисляем для задачи 2 значение
// выражения в скобках.
z = z * log(fabs(x) + 1) - sqrt(x*x + 1); // Результат решения задачи.
cout << “\nРезультат вычисления z = “ << z << “\n”;
getch();
}

Следует обратить внимание на контроль начальнных данных при


проектировании программы и на контроль промежуточных результатов
расчета.
Приведем более короткий вариант программы для решения задачи 2.
Если студент освоил теоретический материал, то он сразу может предложить
именно такую, более короткую, реализацию.
Программа 11
void main()
{ float x, y, z;
cout << “Введи действительное число x = “;
cin >> x;
cout << “Введи действительное число y = “;
cin >> y;
cout << “Введены числа: \nx = “ << x << “\ny = “ << y << “\n”;
z=( sin(x)*sin(x)+cos(y)*cos(y)) / (sin((x+y)/2.)+1.5) - exp(fabs(cos(x)) + sin(y));
z=z*log(fabs(x)+1)-sqrt(x*x+1);
cout << “\nРезультат вычисления z= “ << z << “\n”;
getch(); }

Замечание 1. Код данного варианта программы короче, но в этом


тексте труднее контролировать промежуточные результаты расчета. Поэтому
лучше переходить к короткому варианту, имея текст первого варианта.
Технически это сделать легко, копируя и объединяя части строк первого
варианта программы.
Замечание 2. При вычислении алгебраических выражений (например,
задача 2) программа выполняет действия, основываясь на приоритете
операторов. Таблицы приоритетов операций даны в каждом учебнике по
языку С. Напомним самые простые правила:
приоритет операций умножения и деления выше, чем приоритет
сложения и вычитания;
круглые скобки изменяют порядок приоритетов.
Из этих правил следует, что машина сначала производит действия в
круглых скобках, а затем остальные операции в соответствии с приоритетом:
сначала выполняются операции умножения и деления в том порядке,

 Языки программирования. Язык С. Конспект лекций -52-


ЛЕКЦИЯ 6. АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ. МАТЕМАТИЧЕСКИЕ ВЫРАЖЕНИЯ И ФУНКЦИИ
2. Математические выражения и функции

в котором они записаны в выражении, а затем операции вычитания и


сложения в том порядке, в котором они записаны.
Пример 1. Директива z = a / b * c означает вычисление по следующей
a
формуле: z = ⋅ c .
b
Пример 2. Директива z = a / (b * c) означает вычисление по следующей
a
формуле: z = .
b⋅c

 Языки программирования. Язык С. Конспект лекций -53-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ
И ЛОГИЧЕСКИЕ ОПЕРАЦИИ.
УСЛОВНЫЕ ОПЕРАТОРЫ

План

1. Операции сравнения и логические операции.


2. Условные операторы.

1. Операции сравнения и логические операции

В языке C существуют следующие операции сравнения (в некоторой


литературе это операции отношения):
> Больше
>= Больше или равно
< Меньше
<= Меньше или равно
!= Не равно
== Равно
Используя операции сравнения, в программах можно определять,
например, истинность следующих выражений: a<b; c != e+f; a+d > k; и т. д.
Приоритет арифметических операций выше операций отношений. Операции
отношения вычисляют значение, равное 1, если отношение истинно, или 0,
если отношение ложно. Результат от вычисления выражений, в которых
используются операции сравнения, можно присваивать переменным.
Программа 1. Пример демонстрирует действия операций сравнения.
void main()
{ int r;
clrscr();
r = 3 > 8;
cout << “Результат операции 3 > 8 = ” << r << “\n\n” ;
r = 3 < 8;
cout << “Результат операции 3 < 8 = ” << r << “\n\n” ;
r = 8 > 8;
cout << “ Результат операции 8 > 8 = ” << r << “\n\n” ;
r = 8 >= 8;
cout << “ Результат операции 8 >= 8 = ” << r << “\n\n” ;
r = 3 == 8;
cout << “Результат операции 3 == 8 = ” << r << “\n\n” ;
r = 3 != 8;
cout << “Результат операции 3 != 8 = ” << r << “\n\n” ;
r = 3 + 6 > 8;

 Языки программирования. Язык С. Конспект лекций -54-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
1. Операции сравнения и логические операции

cout << “Результат операции 3 + 6 > 8= ” << r << “\n\n” ;


getch(); }
После выполнения программы на экране появится следующее:

Результат операции 3 > 8 = 0


Результат операции 3 < 8 = 1
Результат операции 8 > 8 = 0
Результат операции 8 >= 8 = 1
Результат операции 3 == 8 = 0
Результат операции 3 != 8 = 1
Результат операции 3 + 6 > 8 = 1

Логические операции:
операция && – логическое И;
операция || – логическое ИЛИ;
операция ! – логическое НЕ.
При помощи этих операций можно составить выражение, которое
одновременно проверяет несколько условий. Например, если x1, x2, x3, x4 и
x5 – отметки студента по пяти предметам, то студент будет отличником тогда
и только тогда, когда значение выражения

(x1 == 5) && (x2 == 5) && (x3 == 5) && (x4==5) && (x5 ==5)

будет истинным, т. е. равным 1.


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

2. Условные операторы

Условный оператор позволяет компьютеру найти информацию с


требуемым набором признаков, т. е. из большого объёма информации
выбрать ту, которая необходима пользователю. Поэтому очень часто
условный оператор называют оператором выбора. В языке C существуют две
формы условного оператора. Первая форма условного оператора имеет
следующую структуру:

if (A) { …… }; S1,

Здесь if директива условного оператора (ключевое слово языка С); A –


некоторое выражение; { …..} – блок операторов (некоторая последователь-

 Языки программирования. Язык С. Конспект лекций -55-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
2. Условные операторы

ность директив); S1 – первая директива программы, которая следует за


закрывающей фигурной скобкой.
Порядок выполнения условного оператора:
• вычисляется выражение A в круглых скобках;
• если значение выражения A не равно нулю, то выполняется блок
операторов, следующий за оператором if, т. е. последовательно выполняются
все директивы в фигурных скобках;
• если значение выражения A равно нулю, то блок операторов,
который следует за оператором if, пропускается и начинает выполняться
директива S1, которая следует за блоком операторов.
Программа 2. Вычислить цену на товар себестоимостью t руб.
void main()
{ float s, r, r1, r2 = 0; // s – переменная для цены на товар.
// r – переменная для себестоимости товара.
clrscr();
cout << “Введи себестоимость товара в тыс. руб. ”;
cin >> r; // Ввод с клавиатуры стоимости товара.
r1 = r * 0.01;
if (r > 50) // Значение выражения r > 50 будет равно 1, если это
// выражение истинно, и 0, если ложно.
r2 = r * 0.007; // Налог будет вычисляться, если значение r > 50
// будет равно 1.
s = r + r1+ r2;
cout << “Стоимость товара с учетом налога = “ << s << “ \n”;
getch(); }

Вторая форма условного оператора имеет следующую структуру:

if (A) { …… }; else {…….}; S1;

Здесь if … else – директива условного оператора; A – некоторое выражение;


{ …..} – блок операторов; S1 – первая директива программы, которая следует
за условным оператором.
Порядок выполнения условного оператора:
• вычисляется выражение A в круглых скобках;
• если значение выражения A не равно нулю, то выполняется блок
операторов, который следует за оператором if. После этого осуществляется
переход к выполнению директивы S1. Блок операторов, который следует за
служебным словом else, не выполняется;
• если значение выражения A равно нулю, то блок операторов,
который следует за оператором if, пропускается и начинает выполняться блок
операторов, который следует за служебным словом else. Далее выполняется
директива S1.

 Языки программирования. Язык С. Конспект лекций -56-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
2. Условные операторы

Программа 3. Даны два числа a, b. Найти максимальное значение из


двух чисел.
Алгоритм решения данной задачи прост. Если значение выражения
a > b истинно, то максимальным числом является число a. Если же значение
выражения a > b ложно, то максимальным числом является число b.
void main()
{ int a, b;
clrscr();
cout << “Введи два целых числа ”;
cin >>a >>b; // Ввод с клавиатуры двух чисел.
if (a > b)
cout << “max из чисел “ << a << “, ” << b << “= “ << a << “\n”;
else
cout << “max из чисел “ << a << “, ” << b << “= “ << b << “\n”;
getch(); }
В программе возможны следующие конструкции при использовании
условных операторов:
if {
if {… }
else {… }
}
else {
if {… }
else {… }
}
Программа 4. Даны три числа a, b, c. Найти максимальное значение из
трех чисел.
Принцип решения задачи заключается в следующем. Если значение
выражения a > b истинно, то максимальное значение определяем, исходя из
чисел a и c. Если же значение выражения a > b ложно, то максимальное
значение определяем, исходя из чисел b и c.
void main()
{ int a, b, c;
clrscr();
cout << “Введи три целых числа (набери три числа через пробелы и”
<< “ нажми Enter)\n”;
cin >> a >> b >> c; // Ввод с клавиатуры трех чисел.
if (a > b)
{ if (a > c)
cout << “max= “ << a << “\n”;
else
cout << “max= “ << c << “\n”;
}

 Языки программирования. Язык С. Конспект лекций -57-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
2. Условные операторы

else
{ if (b>c)
cout << “max= “ << b << “\n”;
else
cout << “max= “ << c << “\n”; } getch(); }

Программа 5. Найти максимальное значение из 6 чисел.


Если для данной задачи вычислять максимум по аналогии с
приведенными выше примерами, то код программы получится сложным и
запутанным. Поэтому предложим другой, более простой метод вычисления
максимума, который представлен следующим алгоритмом:
Шаг 1. Устанавливаем переменные a, b, c, d, e и f.
Шаг 2. Определяем значение переменной max по формуле max = a.
Шаг 3. Если max < b, значение max вычисляем по формуле max = b.
Повторяем шаг 3 для переменных c, d, e и f.
void main()
{int a, b, c, d, e, f;
int max;
clrscr();
cout << “Введи 6 целых чисел ”;
cin >> a >> b >> c; // Ввод с клавиатуры.
cin >> d >> e >> f;
max = a;
if (max < b)max = b; // Если max < b истинно, то в max засылается b.
if (max < c)max = c; // Если max < с истинно, то в max засылается с.
if (max < d)max = d; // Если max < d истинно, то в max засылается d.
if (max < e)max = e; // Если max < e истинно, то в max засылается e.
if (max < f)max = f; // Если max < f истинно, то в max засылается f.
cout << “max= “ << max << “\n”;
getch(); }

Код программы состоит из последовательности почти одинаковых


директив. Поэтому без труда можно составить программу вычисления
максимума для любого количества переменных.
Программа 6. Найти корни уравнения a ⋅ x 2 + b ⋅ x + c = 0 .
В зависимости от параметров это уравнение может быть:
квадратным, если a ≠ 0; тождеством, если a = b = c = 0;
линейным, если a = 0, b ≠ 0; не иметь смысла, если a = b = 0, а c ≠ 0.

Алгоритм решения задачи можно сформулировать следующим


образом:
Шаг 1. Устанавливаем значения переменных a, b и c.
Шаг 2. Если a ≠ 0, решаем квадратное уравнение.

 Языки программирования. Язык С. Конспект лекций -58-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
2. Условные операторы

Шаг 3. Если а = 0, b ≠ 0, решаем линейное уравнение.


Шаг 4. Если a = b = c = 0, решаем как тождество.
Шаг 5. Если a = b = 0, а c ≠ 0, то констатируем, что решений нет.
void main()
{ float a, b, c, d; // a, b, c – переменные для коэффициентов уравнения.
float x1, x2; // x1, x2 – переменные для корней уравнения.
clrscr();
cout << “Введи коэффициенты уравнения ”;
cin >> a >> b >> c;
if (a != 0) // Если выражение a != 0, то уравнение квадратное.
{ d = b * b - (4 * a * c); // Вычисляем дискриминант.
if (d >= 0) // Если выражение d >= 0 истинно, то
{ d = sqrt(d); // уравнение имеет два корня.
x1= (-b + d)/(2 .* a);
x2 = (-b - d)/(2. * a);
cout <<“Корни :\n x1= “ << x1 << “\nx2= “ << x2 << “\n”; }
else // Если d < 0 ложно, то действительных корней нет.
cout << “Уравнение не имеет действительных корней. \n”;
} // Случай a != 0 рассмотрен.
else // Случай a = 0. Уравнение линейное и имеет вид bx + c = 0
{ if (b != 0) // Случай b не равен нулю.
cout << “Уравнение имеет один корень x1= “ << c/b << “\n”;
else { // Случай b = 0.
if ( c == 0) // Случай a = 0, b = 0, c = 0.
cout << “Любое число является корнем уравнения\n”;
else // Случай a = 0, b = 0, c != 0.
cout << “Уравнение не имеет решений\n”; }
} getch(); }

Оператор выбора switch предназначен для выбора варианта обработки


информации (т. е. данная инструкция, как и условный оператор, управляет
порядком выполнения кодов). Оператор switch имеет следующую общую
форму записи:

switch (<выражение>)
{
case C1:
S1
break;
case C2:
S2
break;

case CK:
SK

 Языки программирования. Язык С. Конспект лекций -59-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
2. Условные операторы

break;
default:
SD
break;
}
SS;

Здесь switch, case, break, default – ключевые слова языка C; <выражение> –


это код, в котором используются переменные целого или символьного типа;
C1, C2, …, CK – константы, тип которых совпадает с типом <выражение>;
S1, S2, …, SK – последовательность директив; SS – первый код, который
следует после оператора выбора.
Порядок выполнения оператора выбора:
вычисляется <выражение>;
если одна из констант C1, C2, …, CK совпадает со значением
выражения, то программа передает управление на соответствующую метку
case и выполняется соответствующая последовательность операторов до
директивы break. Затем выполняется код SS.
Операторы SD, которые следуют за инструкцией default, выполняются
в том случае, если значение выражения не совпало ни с одной из констант
C1, C2, …, CK. Оператор default может отсутствовать.
Программа 7. Шаблон меню.
void main(void)
{ int k;
clrscr();
cout << "\n\nКоманды меню:\n\n";
cout << "1 Команда\n";
cout << "2 Команда\n";
cout << "3 Команда\n";
cout << "4 Команда\n";
cout << "\nВведи команды меню: ";
scanf("%d",&k);
switch (k)
{
case 1 : cout << "Вы выбрали 1-ю команду ";
getch();
break;
case 2 : cout << "Вы выбрали 2-ю команду ";
getch();
break;
case 3 : cout << "Вы выбрали 3-ю команду ";
getch();
break;
case 4 : cout << "Вы выбрали 4-ю команду ";
getch();

 Языки программирования. Язык С. Конспект лекций -60-


ЛЕКЦИЯ 7. ОПЕРАЦИИ СРАВНЕНИЯ И ЛОГИЧЕСКИЕ ОПЕРАЦИИ. УСЛОВНЫЕ ОПЕРАТОРЫ
2. Условные операторы

break;
default : cout << "Команда меню с номером" << k
<< "отсутствует\n";
}}

Оператор безусловного перехода, который имеет вид:

goto L ;

Здесь L – имя метки оператора программы. Оператор безусловного перехода


передает управление на выполнение директивы, которая следует за меткой L:
Другими словами, программа начинает работать с того места, которое
помечено символом L: Метку L: можно ставить только перед началом
директивы.

 Языки программирования. Язык С. Конспект лекций -61-


ЛЕКЦИЯ 8. ЦИКЛЫ

План

1. Цикл for.
2. Цикл while.
3. Цикл do – while.

Операторы цикла предназначены для многократного повторения


последовательности директив. Язык С поддерживает три формы операторов
цикла: for, while, do – while.

1. Цикл for

Форма оператора цикла for имеет следующий вид:

for (выражение 1; выражение 2; выражение 3) { W; }

Выражение 1 определяет начальное значение переменной, которая


называется управляющим параметром цикла.
Выражение 2 определяет условие продолжения (или завершения)
выполнения оператора цикла.
Выражение 3 изменяет значение управляющего параметра цикла.
W – блок операторов. Блок операторов W называется телом цикла.
Замечание. Если тело цикла состоит из одного оператора, то этот
оператор можно в фигурные скобки не заключать. Возможны следующие
варианты оператора цикла for:

for (; выражение 2; выражение 3) {W;}


for (выражение 1; ; выражение 3) {W;}
for (выражение 1; выражение 2;) {W;}
for (; ;) {W;}

Задача 1. Выдать на экран 20 раз сообщение «Доброе утро, студент!».


Программа 1. Выводит сообщение «Доброе утро, студент!» 20 раз.
void main()
{ clrscr();
S; S; S; S; S; S; S; S; S; S;
S; S; S; S; S; S; S; S; S; S;
getch(); }

 Языки программирования. Язык С. Конспект лекций -62-


ЛЕКЦИЯ 8. ЦИКЛЫ
1. Цикл for
В представленной программе каждый символ S надо заменить на
директиву cout << “ Доброе утро, студент!\n“. Оператор цикла позволяет
код программы для данной задаче записать короче.
Программа 2. Выводит сообщение «Доброе утро, студент!» заданное
число раз. В программе использовать оператор цикла.
void main()
{ int i, n;
clrscr();
cout << "Введи число выводов сообщения: ";
cin >> n;
for ( i = 1; i <= n; i++) // Начало цикла for.
cout << "Доброе утро, студент! \n"; // Вывод текста на экран.
getch(); }

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


монитора n строк с текстом «Доброе утро, студент!».
Программа f_1 имеет очевидное преимущество перед программой f_1а:
она более гибкая, число выводимых сообщений можно задавать во время ее
выполнения. Кроме того, число выдаваемых сообщений не влияет на код
программы. Код f_1а настроен на вывод только 20 сообщений. Изменение
числа выводимых сообщений требует изменений кода программы f_1а.
Оператор цикла for в программе f_1 выполняется следующим образом:
управляющему параметру цикла i присваивается 1;
значение i сравнивается с переменной n;
если i меньше или равно n, то выполняется блок операторов,
заключенный между фигурными скобками. После выполнения последнего
оператора из блока, управление передается на начало оператора цикла.
Оператор цикла увеличивает значение переменной i на 1, затем выполняется
шаг 2 и т. д.;
если значение переменной i больше n, то управление передается на
первый оператор, который следует после тела цикла.
Рассмотрим различные варианты применения цикла for на простых
задачах.
Программа 3. Выводит на экран целые числа от 1 до заданного числа n
(моделируется процесс счета).
void main()
{ int i, s, n; // i, n – параметры цикла;
// s – для формирования порядкового числа.
clrscr();
cout << "Введи число, до которого надо выводить n= ";
cin >> n;
s = 0;
for (i = 1; i <= n; i++)
{ s = s + 1; // В s формируется выводимое число.
cout << "Это " << s << "\n";

 Языки программирования. Язык С. Конспект лекций -63-


ЛЕКЦИЯ 8. ЦИКЛЫ
1. Цикл for
}
cout << “ Вывод чисел от 1 до ” << n << “ завершен! \n”;
getch(); }

Приведем результат работы программы для n = 4.

Введи число, до которого надо выводить n = 4


Это 1
Это 2
Это 3
Это 4
Вывод чисел от 1 до 4 завершен!

Программа 4. Выводит на экран целые числа от 1 до n.


void main()
{ int i, n;
clrscr();
cout << "Введи число, до которого надо выводить n= ";
cin >> n;
for (i = 1; i <= n; i++) // В теле цикла используется i.
cout << "Это " << i << "\n";
getch();
}

Программа 5. Выводит на экран целые числа от заданного числа n до 1.


void main()
{ int i, n;
clrscr();
cout << "Введи число, до которого надо выводить n= ";
cin >> n;
for (i = n; i >= 1; i--) // Значение параметра i уменьшается.
cout << "Это " << i << "\n";
getch(); }

Программа 6. Выводит на экран целые числа от n1 до n2 с шагом dt


(n1 <= n2, dt > 0).
void main()
{ int i, n1, n2, dt;
clrscr();
cout << "Введи число, от которого надо выводить n1 = ";
cin >> n1;
cout << "Введи число, до которого надо выводить n2 = ";
cin >> n2;
cout << "Введи шаг для выдачи чисел dt = ";
cin >> dt;

 Языки программирования. Язык С. Конспект лекций -64-


ЛЕКЦИЯ 8. ЦИКЛЫ
1. Цикл for
for (i = n1; i <= n2; i = i + dt) // Значение i меняется с шагом dt.
cout << "Это " << i << "\n";
getch(); }

Программа 7. Для управления циклом используются два параметра.


void main()
{ float i, j;
clrscr();
for (i = -10, j = 0; i + j <= 0; i++ , j++ )
cout << i << " + “ << j << “= " << (i+j) << "\n";
cout << “\tРабота цикла завершена\n”;
getch(); }

Приведем результат работы программы.

-10 + 0 = -10
-9 + 1 = -8
-8 + 2 = -6
-7 + 3 = -4
-6 + 4 = -2
-5 +5 = 0
Работа цикла завершена

2. Цикл while

Оператор цикла while имеет следующий вид:

while ( <условие>) { W; }

Здесь <условие> – любое выражение, допустимое в языке С; W –


последовательность операторов.
<условие> принимает значение ИСТИНА, если значение выражения
не равно нулю. В противном случае <условие> принимает значение ЛОЖЬ.
Тело цикла выполняется, если <условие> принимает значение ИСТИНА.
В противном случае программа выходит из цикла и выполняет первый
оператор, который следует за телом цикла.
Программа 8. Ввод чисел с клавиатуры в режиме диалога.
void main()
{int k;
clrscr();
cout << "Введи число (Ctrl + z Enter – выход из программы): ";
scanf("%d", &k);
while (!feof(stdin) ) // Начало цикла. Цикл выполняется до тех пор,
// пока не будет введен символ Ctrl + z.
{ cout << "\n Вы ввели число " << k

 Языки программирования. Язык С. Конспект лекций -65-


ЛЕКЦИЯ 8. ЦИКЛЫ
2. Цикл while
<< "\nДля продолжения ввода нажми Enter";
getch();
clrscr();
cout << "Введи число (Сtrl + z Enter – выход из программы): ";
scanf("%d", &k);
}}

Программа моделирует работу в режиме диалога с пользователем.


Замечание. Функция feof(stdin) возвращает значение 0, если с
клавиатуры вводится число, и значение, не равное 0, при вводе комбинации
символов Ctrl + z.
Программа 9. Выводит цифры, которые формируют целое число.
void main()
{ long a;
clrscr();
cout << " Введи число a = ";
cin >> a;
clrscr();
cout << “Число “ << a << “Состоит из цифр:”;
while (a != 0 )
{cout << a % 10 << “\n”; // Выводит последнюю цифру числа.
a = a / 10; // Отбрасывает последнюю цифру числа.
}
getch(); }

Программа 10. Вычисляет сумму цифр заданного целого числа.


void main()
{ long a;
int s = 0;
clrscr();
cout << "Введи число a = " ;
cin >> a;
while (a != 0 )
{
s = s + a % 10; // Вычисляет и прибавляет последнюю цифру числа к s.
a = a / 10; } // Отбрасывает последнюю цифру числа.
cout << "сумма s= " << s << "\n";
getch(); }

Программа 11. Шаблон меню.


void main(void)
{ int iv = 0;
int k;
while ( !iv )
{ clrscr();

 Языки программирования. Язык С. Конспект лекций -66-


ЛЕКЦИЯ 8. ЦИКЛЫ
2. Цикл while
cout << "\n\nКоманды меню:\n\n";
cout << "1 Команда\n";
cout << "2 Команда\n";
cout << "3 Команда\n";
cout << "4 Команда\n";
cout << "5 Выход\n";
cout << "\nВведи команды меню: ";
scanf("%d",&k);
switch (k)
{
case 1 : cout << "Вы выбрали 1-ю команду ";
getch();
break;
case 2 : cout << "Вы выбрали 2-ю команду ";
getch();
break;
case 3 : cout << "Вы выбрали 3-ю команду ";
getch();
break;
case 4 : cout << "Вы выбрали 4-ю команду ";
getch();
break;
case 5 : cout << " \n\n Вы вышли из программы!!!.\n ";
cout << " Жми любую клавишу\n ";
iv = 1;
break;
default : cout << "Команда меню с номером" << k
<< "отсутствует\n";
} }
getch(); }

3. Цикл do – while.

Оператор цикла do – while имеет следующий вид:

do
{ W; }
while ( <условие>)
Здесь <условие> – любое выражение, допустимое в языке С; W –
последовательность операторов.
<условие> принимает значение ИСТИНА, если значение выражения
не равно нулю, ЛОЖЬ – в противном случае. Тело цикла выполняется, если
<условие> принимает значение ИСТИНА.

 Языки программирования. Язык С. Конспект лекций -67-


ЛЕКЦИЯ 8. ЦИКЛЫ
3. Цикл do – while.

Задача 2. Для любого x ≥ 0 найти y = m x . Вычислять корень по


1⎛ x ⎞
итерационной формуле yk +1 = ⎜ ( m − 1) y k + ⎟ с точностью ε, где k – шаг
m⎝ ykm−1 ⎠
итерации.
Составлять программу, которая вычисляет значение переменной по
некоторой итерационной формуле y k +1 = f(y k ), – достаточно простая задача.
Для этого необходимо: объявить переменную y; корректно задать начальное
значение; в блоке операторов записать по правилам языка выражение y = f(y).
Продолжительность вычисления в цикле выражения y = f(y) опреде-
ляется заданной точностью ε. Точность вычислений ε контролируется с
помощью неравенства | yk +1 − yk |≤ ε . Здесь y k , y k +1 – значение y, которое
вычисляется на шагах итерации k и k + 1. Общий вид цикла, который
вычисляет выражение с заданной точностью, имеет следующий вид:

y = x;
do
{y1 = y; // В y1 засылается значение y с предыдущего шага.

y = f(y); // На текущем шаге y определяется по формуле.
} while (fabs(y - y1) > ep); // Выполнять цикл до заданной точности.

Здесь x – начальное значение переменной y; y1 – значение переменной y,


которое определено на шаге k; y – значение переменной, которое
вычисляется на шаге k + 1; ep – заданная точность вычисления по
итерационной формуле.
Программа 12. Вычисляет корень степени m из действительного числа
1⎛ x ⎞
x по итерационной формуле yk +1 = ⎜ (m − 1) yk + m−1 ⎟
m⎝ yk ⎠
void main()
{
double x, ep, r, r1, rm;
double yn, yn1, m;
int i;
clrscr();
cout << "Введи показатель корня и основание степени m, x = ";
cin >> m >> x;
cout << " \n Введи точность вычисления ep = ";
cin >> ep;
yn1 = x;
rm = (m - 1) / m;
r1 = x / m;
do { r = r1;

 Языки программирования. Язык С. Конспект лекций -68-


ЛЕКЦИЯ 8. ЦИКЛЫ
3. Цикл do – while.
yn = yn1;
for (i = 1; i < m; i++) // В цикле
r = r / yn; // вычисляется значение корня степени m
// из числа x.
yn1 = rm * yn + r ; // Вычисляется значение y n+1 .
}
while ( fabs(yn1 - yn) > ep );
cout << "Корень " << m << "-й cтепени из " << x
<< "= " << yn1 << "\n";
cout << "\n\n Проверка:\n ";
yn = 1;
for (i=1; i<=m; i++)
yn = yn1* yn;
cout << yn1 << " в степени " << m << "= " << yn << "\n";
getch();
}

 Языки программирования. Язык С. Конспект лекций -69-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ
ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ

План

1. Массивы.
2. Некоторые простейшие задачи.
3. Матрицы.
4. Перебор элементов матрицы.

1. Массивы

Массив есть множество элементов одного типа. Общий формат


объявления массива имеет следующий вид:

<тип> <имя> [<размер>]

Например, код int x[10]; объявляет массив с именем x, который состоит


из 10 элементов. Этот оператор выделяет 10 ячеек памяти размером в два
байта каждая для целых чисел. Области памяти элементов массива имеют
имена x[0], x[1], x[2], …, x[9], т. е. код доступа к любому элементу массива
имеет формат x[k], k < n, где n – размер массива. Индексы массива в языке С
начинаются с нуля!!!
Замечание 1. <размер> – это константа. Размер может быть задан либо
в виде числа, либо в виде выражения. Например, возможна следующая форма
объявления массива:

const n = 10, m= 15;


float mas[n * m + 10];

Замечание 2. В языке С можно присваивать значения элементам массива,


для которых не зарезервирована память. Транслятор пропускает такую
ошибку. Например, можно написать int x[10]; x[25] = 10; Код x[25] = 1000;
является ошибкой, которую транслятор не выявляет.
Цикл предоставляет возможность перебора индексов, а значит, и
перебора элементов массива.

Программа 1. Выводит значения элементов массива на экран.


void main(void)
{ const n = 4; // Определяется размер массива.
int a[n], i; // Объявляется массив a[n].
clrscr();
cout << "Вывод элементов массива на экран \n";

 Языки программирования. Язык С. Конспект лекций -70-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
1. Массивы
for(i = 0; i < n; i++) // Перебор индексов массива.
cout << “a(“ << i << “)= “ << a[i] << "\n"; // Вывод на экран
// значения элемента массива a[i].
cout << "\n";
getch(); }

Программа выдаст на экран монитора следующую информацию:

a(0) = X0
a(1) = X1
a(2) = X2
a(3) = X3

Здесь X0, X1, X2, X3 – какие-то случайные числа. Данная программа демон-
стрирует, что задание начальных значений (инициализация) элементов
массива должна быть предусмотрена в программе. Инициализировать
элементы массива можно при его объявлении, например, используя код int
x[6] = {n1, n2, n3, n4, n5, n6}, здесь n1, n2, …, n6 некоторые числа. Если при
объявлении массива определить его первый элемент (int x[6] = {n1}), то
остальные элементы массива автоматически заполняются нулями.

Программа 2. Выводит адреса памяти элементов массива в десятичной


и шестнадцатеричной системе.
void main(void)
{ const n = 5;
int i, a[n] ;
clrscr();
cout << “Программа выводит адреса элементов массива. \n\n”;
cout << “Шестнадцатеричная система”
<< “Десятичная система: \n”;
for(i = 0; i < n; i++) // Перебор индексов массива.
{ cout << &a[i] << " "; // Вывод адреса i-го элемента
cout << (unsigned long int) &a[i] << " \n"; } // массива.
cout << "\n"; getch(); }

Результат работы программы на экране будет выглядеть так:

Программа выводит адреса элементов массива.

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


0x1a0a0ff2 436867058
0x1a0a0ff4 436867060
0x1a0a0ff6 436867062
0x1a0a0ff8 436867064
0x1a0a0ffa 436867066

 Языки программирования. Язык С. Конспект лекций -71-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
1. Массивы
Программа 3. Ввод значений элементов массива с клавиатуры. Вывод
значений элементов массива на экран.
void main(void)
{ const n = 6 ;
int i, a[n] ;
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i <= n-1; i++) // Начало цикла.
{ printf("a[ %d ]= ", i); // Подсказка для пользователя.
scanf( "%d", &a[i] ); // Ввода элемента массива с клавиатуры.
}
// Вывод массива на экран.
for (i = 0; i <= n-1; i++)
printf("%d ", a[i]); // Вывода элемента массива на экран.
getch(); }

Анализ результата работы программы приводит к выводу, что


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

2. Некоторые простейшие задачи


Задача mas1. Определить, есть ли в массиве элемент, значение
которого равно заданному числу.
Приведем некоторые соображения, позволяющие составить алгоритм
решения данной задачи. Перебирать элементы массива, сравнивать числа мы
умеем. Для того чтобы решить задачу mas1, нужно проверить, найден или
нет элемент массива, равный заданному числу. Это можно установить,
использовав следующие приемы:
Определить некоторый параметр состояния – (флаг). Начальное
значение этого параметра задать равным нулю. Как только элемент массива,
равный заданному числу, найден, присваиваем этому параметру любое
значение, отличное от нуля. Тем самым фиксируем факт, что элемент с
заданным свойством в массиве найден.
Данный подход можно модифицировать: при каждом совпадении
элемента массива с заданным числом увеличивать значение параметра
состояния на единицу. Тогда фиксируется не только факт совпадения, но и
количество таких совпадений.
В предлагаемой ниже программе реализован второй подход к решению
задачи mas1.
Программа 4
void main(void)
{ const n = 4 ;
int i, a[n], r; // r – переменная для числа поиска.

 Языки программирования. Язык С. Конспект лекций -72-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
int ps = 0; // ps – параметр состояния.
clrscr();
cout << "Введи элементы массива с клавиатуры: \n";
for (i = 0; i <= n - 1; i++ )
{ printf("a[ %d ]= ", i); // Вывод поясняющей информации.
scanf( " %d", &a[i]); // Ввод числа с клавиатуры.
} clrscr();
cout << "Введен массив: \n";
for (i = 0; i <= n - 1; i++)
printf("%d ", a[i]); // Вывод элементов массива на экран.
cout << "\n\nВведи число для поиска его в массиве: ";
scanf("%d", &r); // Ввод числа для поиска.
for (i = 0; i <= n - 1; i++)
if (a[i] == r) ps++; // Подсчет совпадений элементов массива a[i]
// с числом r.
if (ps == 0)
cout << "\n\n Элемент массива со значением "
<< r << " не найден. \n";
else
cout << "\n\n Число элементов в массиве, совпадающих с числом "
<< r << " равно " << ps << "\n";
getch(); }

Следует акцентировать внимание слушателей на приеме ввода


параметра состояния (флага) для того, чтобы зафиксировать те моменты при
обработке информации, которые определяются условием задачи.
Задача mas2. Найти k-й отрицательный элемент массива.

Программа 5. Поиск k-го отрицательного элемента массива.


void main()
{ const n = 10;
int i, b, a[n], in = -1, k, kt = 0 ;
clrscr();
cout << "Введи порядковый номер для поиска “
<< “ отрицательного элемента k = ";
cin >> k;
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i <= n-1; i++)
{ cout << "a(" << i << ")= ";
scanf( "%d", &a[i] ); } // Ввод элемента массива.
for (i = 0; i <= n - 1; i++)
cout << a[i] << " "; // Вывод элемента массива a[i] на экран.
for (i = 0; i < n; i++)
if ( (a[i] < 0) && (kt < k)) // Определяет знак элемента массива

 Языки программирования. Язык С. Конспект лекций -73-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
{ // до тех пор, пока число таких элементов меньше k.
in = i; // Фиксирует индекс текущего
// отрицательного элемента массива.
kt++; } // Учет количества отрицательных элементов.
if ( k == kt )
cout << "\n" << “a(“ << in << “)= “ << a[in] << “является ”
<< k << "- м отрицательным элементом массива";
else
cout << "\n" << k << "-го отрицательного элемента нет \n";
getch(); }

В программе 5 применен прием, который ранее не использовался.


Кодом in = i; фиксируются индексы текущих отрицательных элементов
массива. При завершении работы программы значение kt будет равно:
0, если отрицательных элементов в массиве нет;
k, если количество отрицательных элементов в массиве больше или
равняется k;
некоторому значению r < k, если количество отрицательных элементов
в массиве меньше k.
Анализируя значение переменной in, можно вывести на экран
соответствующий комментарий.
Задача mas3. Найти максимальное значение в массиве.
Алгоритм решения задачи mas3 можно сформулировать следующим
образом.
Шаг 1. Установить начальные значения элементов массива a[n].
Шаг 2. Положить max = a[0].
Шаг 3. Цикл по i при i = 1,…, n – 1.
Шаг 4. Если max < a[i], то положить max = a[i].

Программа 6
void main(void)
{
const n = 4 ;
int i, a[n], max; // max – для максимального элемента массива.
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i < n; i++)
{
printf("a[ %d ]= ", i);
scanf( " %d", &a[i] );
}
max = a[0]; // Первый элемент массива объявляем максимальным.
for (i = 1; i < n; i++) // В цикле перебираем индексы элементов
// массива.
if (max < a[i] ) max = a[i]; // Если максимальный меньше текущего

 Языки программирования. Язык С. Конспект лекций -74-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
// элемента массива, то максимальным
// объявляется текущий элемент.
printf(" \n значение max= %d", max); // Вывод максимального
// значения элемента массива.
getch(); }

Задача mas4. Найти индекс элемента массива, который принимает


максимальное значение.
Алгоритм решения задачи mas4 формулируется следующим образом.
Шаг 1. Установить начальные значения элементов массива a[n].
Шаг 2. Положить ima = 0.
Шаг 3. Цикл по i при i = 1,…, n – 1.
Шаг 4. Если a[ima] < a[i], то положить ima = i.

Программа 7. Определяет элемент в массиве, который принимает


максимальное значение.
void main(void)
{
const n = 4 ;
int i, a[n], ima; // ima – для индекса максимального элемента массива.
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i < n; i++)
{
printf("a[ %d ]= ", i);
scanf( " %d", &a[i] );
}
ima = 0; // Устанавливается начальное значение для ima.
for (i = 1; i < n; i++)
if (a[ima] < a[i] ) ima = i; // Если максимальный меньше текущего
// элемента массива, то максимальным
// объявляется текущий элемент.
// Вывод максимального значения элементов массива.
printf(" \n a(%d) принимает максимальное значение= %d", i, a[ima]);
getch(); }

Задача mas5. Отсортировать элементы массива в порядке убывания.


Алгоритм решения данной задачи полностью базируется на поиске
максимального элемента. Последовательно определяются элементы с
максимальным значением для частей массивов
a[k], a[ k + 1], a[ k + 2], …, a[ n – 1], где k = 0, 1, 2, ..., n – 2. Для
каждой части массива элемент с максимальным значением меняется местами
с первым элементом, т. е. с элементом a[k].

 Языки программирования. Язык С. Конспект лекций -75-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
Программа 8
void main(void)
{const n = 4 ;
int i, a[n], ima; // ima – для хранения индекса максимального элемента.
int k, r;
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i < n; i++)
{ printf("a[ %d ]= ", i);
scanf(" %d", &a[i]); }
clrscr();
cout << "\n Массив до сортировки:\n";
for (i = 0; i <= n – 1; i++)
printf("%d ", a[i]);
for (k = 0; k < n–1; k++)
{ ima = k; // Устанавливается начальное значение для ima.
for (i = k + 1; i < n; i++)
if (a[ima] < a[i]) ima = i; // Определяем индекс максимального
// элемента части массива от индекса k до n–1.
// Перестановка первого элемента части массива от индекса k до n–1
// с максимальным.
r = a[k];
a[k] = a[ima];
a[ima] = r;
}
// Вывод массива после сортировки.
cout << "\n\n Массив после сортировки:\n";
for (i = 0; i <= n – 1; i++)
printf("%d ", a[i]);
getch(); }

Задача mas6. Элементы массива сдвинуть циклически на одну


позицию влево.
Сдвинуть массив циклически на одну позицию влево – это значит:
первый элемент массива сделать последним; второй – первым; третий –
вторым и т. д. Для программной реализации этой задачи элемент x[0] следует
запомнить в рабочей ячейке. После этого нужно организовать цикл, в
котором выполняется директива a[i] = a[i+1] для i = 0, 1, ..., n – 2 (элемент с
индексом i + 1 переписывается на место элемента с индексом i). Далее
значение x[0] из рабочей ячейки следует переписать в x[n – 1].

Программа 9
int main(void)
{ const n = 4 ;
int i, b, a[n] ;

 Языки программирования. Язык С. Конспект лекций -76-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i=0; i <= n – 1; i++) // Начало цикла. Перебор индексов массива.
{
printf("a[ %d ] = ", i); // Подсказка для пользователя.
scanf("%d", &a[i]); // Ввод элемента массива с клавиатуры.
}
clrscr();
// Вывод массива на экран.
printf (“\nМассив до преобразования: \n”);
for (i = 0; i <= n – 1; i++)
printf(" %d ", a[i]);
b = a[0]; // Запоминаем первый элемент массива.
for (i = 0; i < n – 1; i++)
a[i] = a[i + 1]; // Сдвигаем элементы на одну позицию влево.
a[n - 1] = b; // Первый элемент массива пересылаем в a[n-1].
// Вывод массива на экран после сдвига на одну позицию влево.
printf (“\nМассив после преобразования: \n”);
for (i = 0; i < n; i++)
printf(" %d ", a[i]);
getch(); }

Задача mas7. Элементы массива сдвинуть циклически на k позиций


влево.
Для решения этой задачи надо k раз повторить циклический сдвиг
массива на одну позицию влево.

Программа 10
void main(void)
{
const n = 7 ;
int j, i, b, k, a[n] ; // i, j – для текущих индексов массива; k – для числа
// сдвигов влево; b – для промежуточных результатов.
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i <= n – 1; i++) // Начало цикла. Перебор индексов массива.
{
printf("a[ %d ]= ", i); // Подсказка для пользователя.
scanf("%d", &a[i]); // Ввод элемента массива с клавиатуры.
}
cout << "Введи число позиций, на которое надо сдвинуть массив влево, k = ";
cin >> k;
for (j = 0; j < k; j++)
{
b = a[0]; // Запоминаем первый элемент массива.

 Языки программирования. Язык С. Конспект лекций -77-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
for (i = 0; i < n - 1; i++)
a[i] = a[i + 1]; // Сдвигаем элементы на одну позицию влево.
a[n - 1] = b; // Первый элемент массива засылаем
// на место последнего.
}
// Вывод массива на экран.
clrscr();
for (i = 0; i < n; i++)
printf("%d ", a[i]);
getch();
}

Задача mas8. Найти наиболее часто встречаемое число в массиве.


В программе используются следующие переменные:
n – число элементов в массиве;
a[] – массив для начальных данных; ma – для элемента, который
встречается чаще всего;
mt – для частоты текущего элемента в массиве. Параметр mt
показывает, сколько раз текущий элемент a[i] встречается в части массива
от индекса i до индекса n – 1;
im – для индекса элемента, который встречается чаще всего в массиве.

Шаг 1. Цикл по i. Для i = 0, 1, ..., n – 1 выполнять шаги 2–8.


Шаг 2. Установить значение переменной mt = 1.
Шаг 3. Цикл по j. Для j = i + 1, i + 2, ..., n – 1 выполнять шаги 4–6.
Шаг 4. Если a[i] = a[j], то шаг 5, иначе – шаг 6.
Шаг 5. Установить mt = mt + 1, im = i.
Шаг 6. Конец цикла по j.
Шаг 7. Если mt > ma, то ma = mt.
Шаг 8. Конец цикла по i.

Программа 11
void main()
{ const n = 10;
FILE *f1;
int i, j, a[n];
int ma = 0, mt, im = 0;
clrscr();
cout << "Введи элементы массива с клавиатуры \n";
for (i = 0; i <= n - 1; i++) // Цикл. Перебор индексов массива.
{ printf("a[ %d ]= ", i); // Подсказка для пользователя.
scanf("%d", &a[i]); // Ввод элемента массива с клавиатуры.
}
cout << "\nВведен массив:\n\n";
for (i = 0; i < n; i++) // Вывод элементов массива на экран.

 Языки программирования. Язык С. Конспект лекций -78-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
2. Некоторые простейшие задачи
cout << a[i] << " ";
// Начало решения задачи.
for (i = 0; i < n - 1; i++)
{ mt = 1;
for (j = i + 1; j < n; j++)
if (a[i] == a[j])
mt++; // Считаем сколько раз a[i] встречается в части
// массива от индекса i до n – 1.
if (mt > ma)
{ im = i ; // Фиксируем индекс элемента, который
// встречается в массиве чаще всего.
ma = mt; // Фиксируем элемент, который
// встречается в массиве чаще всего.
} }
cout << "\nЧисло " << a[im] << " встречается в массиве " << ma
<< " раз. Чаще всего\n";
getch(); }

3. Матрицы.

Матрица есть прямоугольная таблица чисел из n строк и m столбцов.


Если n = m, матрица называется квадратной, а число n – ее порядком.
Матрицу обычно представляют следующим образом:

a 11 a 12 a 13 . . . . . . . . a 1m
a 21 a 22 a 23 . . . . . . . . a 2 m
a 31 a32 a 33 . . . . . . . . .a 3m
....................
a n1 a n 2 a n3 . . . . . . . . a nm

Строка с индексом i, (1 ≤ i ≤ n) – это одномерный массив элементов


матрицы a i1 , a i 2 , a i 3 , ..., a im , у которых первый индекс i (номер строки)
фиксирован, а второй индекс j (номер столбца) меняется от 1 до m. Столбец с
индексом j (1 ≤ j ≤ m) – это одномерный массив элементов матрицы a 1 j , a 2 j ,
..., a ij , ..., a nj , у которых второй индекс j фиксирован, а первый индекс i
меняется от 1 до n.
Главной диагональю квадратной матрицы порядка n называют
множество элементов a 11 , a 22 , ..., a ii , ..., a nn . Если элемент a ij принадлежит
главной диагонали, то справедливо равенство i = j.
Побочной диагональю квадратной матрицы порядка n называют
множество элементов a 1n , a 2 n , ..., a in +1−i , ..., a n1 . Если элемент a ij принадлежит
побочной диагонали, то справедливо равенство i + j = n + 1.

 Языки программирования. Язык С. Конспект лекций -79-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
3. Матрицы.
В языке C матрице соответствует двумерный массив. Далее понятия
«матрица» и «двумерный массив» используются как синонимы. Общий
формат объявления двумерного массива имеет следующий вид:

<тип> <имя> [n][m],

Замечание 3. В задачах по программированию часто квадратную


матрицу для наглядности интерпретируют как квадрат:

Замечание 4. В языке C первый индекс массива всегда равен нулю.


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

Таблица 9.1
Координаты в памяти компьютера

0,0 0,1 0,2 0,3 0,4 0,5


1,0 1,1 1,2 1,3 1,4 1,5
2,0 2,1 2,2 2,3 2,4 2,5
3,0 3,1 3,2 3,3 3,4 3,5
4,0 4,1 4,2 4,3 4,4 4,5
5,0 5,1 5,2 5,3 5,4 5,5

Перебор элементов матрицы продемонстрируем на программе ввода


элементов матрицы из файла в память компьютера. Естественно, элементы
матрицы перебираются в цикле. В этом случае надо использовать два цикла.
Первый цикл перебирает номера строк матрицы, второй – элементы каждой
строки. Детали организации такого перебора демонстрирует нижеследующая
программа. Напомним: чтобы ввести данные из файла в память компьютера,
этот файл надо прежде создать на внешнем носителе. Заносить матрицу в
файл для наглядности и контроля лучше по строкам. Это значит, что каждая
строка матрицы будет занимать одну строку в файле.

Программа 12. Ввод двумерного массива из файла.

 Языки программирования. Язык С. Конспект лекций -80-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
3. Матрицы.
void main(void)
{ const n = 4, m = 4;
FILE *f1;
int i,j, a[n][m];
clrscr();
// Ввод двумерного массива из файла.
f1 = fopen("t2.dat", "r"); // t2.dat – имя файла на диске с элементами
// матрицы.
for (i = 0; i <= n-1; i++) // Перебор индексов строк матрицы.
{ for (j = 0; j <= m-1; j++) // Перебор элементов строки матрицы.
{ fscanf( f1, " %d", &a[i][j]); // Ввод из файла в память машины
// элементов i-й строки матрицы.
printf(" %d ", a[i][j]); // Вывод элементов матрицы на экран
// монитора.
}
printf("\n"); // Перевод курсора экрана на другую строку.
}
fclose(f1);
// Запись двумерного массива в файл.
f1 = fopen("t2_w.dat", "w");
for (i = 0; i <= n - 1; i++)
{ for (j = 0; j <= m - 1; j++)
fprintf(f1,"%d ", a[i][j]); // Запись элемента матрицы в файл.
fprintf(f1,"\n"); // Перевод указателя файла на другую строку файла.
}
fclose(f1);
getch(); }

4. Перебор элементов матрицы

Расмоторим простейшие схемы переборов элементов матрицы. Эти


схемы представлены на рисунке.

а б в г

Рис. 9.1

 Языки программирования. Язык С. Конспект лекций -81-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
4. Перебор элементов матрицы
Программа 13. Перебор элементов по схеме рис. 9.1, а.
void main(void)
{ int n, m; // n, m – для числа строк и столбцов матрицы.
int i,j; // i – для номеров строк; j – для номеров столбцов.
clrscr();
cout << "Введи число строк матрицы n= ";
cin >> n;
cout << "Введи число столбцов матрицы m= ";
cin >> m;
for (i = 0; i <= n-1; i++) // Формируется индекс текущей строки.
{
for (j = 0; j <= m-1; j++) // Формируется индекс текущего столбца.
printf("%d,%d ", i, j); // Вывод на экран монитора индекса
// текущего элемента матрицы.
printf("\n"); // Перевод курсора на следующую строку
// экрана монитора.
}
getch(); }

Замечание 6. В реальных задачах там, где значения элементов матрицы


используются для каких-то вычислений вместо директивы printf("%d,%d ",
i, j), должны стоять операторы, которые выполняют обработку элементов
a[i][j].

Программа 14. Перебор элементов матрицы по схеме рис. 9.1, б.


void main(void)
{
int n, m; // n, m – для числа строк и столбцов матрицы.
int i,j; // i – для номеров строк; j – для номеров столбцов.
clrscr();
cout << "Введи число строк матрицы n= ";
cin >> n;
cout << "Введи число столбцов матрицы m= ";
cin >> m;
for (i = 0; i <= m - 1; i++) // Перебираем индексы столбцов.
{
for ( j = 0; j <= n-1; j++) // Перебираем индексы строк.
printf("%d,%d ",j , i); // Вывод элементов столбцов.
printf("\n"); // Перевод курсора на другую строку экрана.
}
getch(); }

Замечание 7. Если j – порядковый индекс элемента i-й строки от


начала, то (m – 1 – j) – порядковый индекс элемента i-й строки от конца
(j = 0, 1, ..., m – 1).

 Языки программирования. Язык С. Конспект лекций -82-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
4. Перебор элементов матрицы
Программа 15. Перебор элементов матрицы по схеме рис. 9.1, в.
Перебор элементов строки с четным индексом происходит
последовательно от первого элемента строки к последнему элементу.
Перебор элементов строки с нечетным индексом происходит от последнего
элемента к первому.
void main(void)
{ int n, m;
int i,j;
clrscr();
cout << "Введи число строк матрицы n= ";
cin >> n;
cout << "Введи число столбцов матрицы m= ";
cin >> m;
for (i = 0; i <= n-1; i++) // Формируем (фиксируем) текущий
{ // индекс строки.
for (j = 0; j <= m-1; j++) // Формируем текущий индекс столбца.
{
if( i % 2 == 0 ) // Если индекс строки четный, то
printf("%d,%d ", i, j); // вывод от первого элемента строки i.
else // Если индекс строки нечетный,
printf("%d,%d ", i, m – 1 - j); // то вывод от последнего
// элемента строки.
}
printf("\n");
}
getch(); }

Замечание 8. Если i – порядковый номер элемента j-го столбца


от начала, то n – 1 – i – порядковый номер элемента j-го столбца от конца
(i= 0, 1, ..., n – 1).

Программа 16. Перебор элементов матрицы по схеме рис. 9.1, г.


Перебор элементов столбцов с четными индексами происходит
последовательно от первого элемента столбца к последнему. Перебор
элементов столбцов с нечетными индексами происходит от последнего
элемента столбца к первому.
void main(void)
{ const n, m;
int i,j;
clrscr();
cout << "Введи число строк матрицы n= ";
cin >> n;
cout << "Введи число столбцов матрицы m= ";
cin >> m;

 Языки программирования. Язык С. Конспект лекций -83-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
4. Перебор элементов матрицы
for (j = 0; j <= m-1; j++)
{
for (i = 0; i <= n-1; i++)
{ if( j % 2 == 0)
printf("%d,%d ", i, j); // Если индекс четный.
else
printf("%d,%d ", n-1-i, j); // Если индекс нечетный.
}
printf("\n");
}
getch(); }

Программа 17. Перебор элементов матрицы, расположенных выше


главной диагонали.
void main(void)
{ int i, j, n;
clrscr();
cout << "Введи размер матрицы n = ";
cin >> n;
for (i = 0; i < n; i++)
{
for (j = i; j < n; j++) // Перебор элементов строки начинается
// не с индекса 0, а с индекса i.
cout << i << "," << j << " ";
cout << "\n";
}
getch(); }

Программа 18. Перебор элементов матрицы, расположенных ниже


главной диагонали.
void main(void)
{
int i, j, n;
clrscr();
cout << "Введи размер матрицы n= ";
cin >> n;
for (i = 0; i < n; i++)
{
for ( j = 0; j <= i; j++) // Перебор элементов строки заканчивается
// не индексом n, а индексом i.
cout << i << "," << j << " ";
cout << "\n";
}
getch(); }

 Языки программирования. Язык С. Конспект лекций -84-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
4. Перебор элементов матрицы
Программа 19. Перебор элементов матрицы, расположенных выше
побочной диагонали.
void main(void)
{
int i, j, n;
clrscr();
cout << "Введи размер матрицы n = ";
cin >> n;
for (i = 0; i < n; i++)
{
for (j = 0; j < n – i; j++) // Перебор элементов строки заканчивается
// не индексом n, а индексом n – i.
cout << i << "," << j << " ";
cout << "\n";
}
getch(); }

Программа 20. Перебор элементов матрицы, расположенных ниже


побочной диагонали.
void main(void)
{ int i, j, n;
clrscr();
cout << "Введи размер матрицы n = ";
cin >> n;
for (i = 0; i < n; i++)
{ for (j=n-1 - i; j < n; j++) // Перебор элементов строки начинается
// не с индекса 0, а с индекса n - 1 - i.
cout << i << "," << j << " ";
cout << "\n";
}
getch(); }

Программа 21. Переставить строки k и l матрицы.


void main(void)
{ const n = 5, m =5;
FILE *f1;
int i,j, a[n][m];
int l, k, b;
clrscr();
// Ввод элементов матрицы из файла.
f1 = fopen("t3.dat", "r");
for (i = 0; i <= n-1; i++)
for (j = 0; j <= m-1; j++)
fscanf( f1, "%d", &a[i][j] );
fclose(f1);

 Языки программирования. Язык С. Конспект лекций -85-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
4. Перебор элементов матрицы
cout << "Матрица до перестановки строк: \n";
for (i = 0; i <= n-1; i++)
{ for (j = 0; j <= m-1; j++)
printf("%d ", a[i][j]);
printf("\n"); }
cout << "\nПереставь строку l= ";
cin >> l;
cout << "\nсо строкой k= ";
cin >> k;
l--; k--; // Индекс строки и столбца на единицу меньше номера.
for (j = 0; j <m; j++)
{
b= a[l][j]; // Элемент a[l][j] строки l переставляется
a[l][j] = a[k][j]; // с элементом a[k][j] строки k.
a[k][j] = b;
}
cout << "\n\n Матрица после перестановки строк: \n";
for (i = 0; i <= n-1; i++)
{
for (j = 0; j <= m-1; j++)
printf("%d ", a[i][j]);
printf("\n");
}
getch(); }

Программа 22. Перебор элементов верхнего треугольника матрицы.


void main()
{ int i,j, n, k;
clrscr();
cout << "Введи размер матрицы n= ";
cin >> n;
cout << "Элементы верхнего треугольника матрицы: \n";
if (n % 2 == 0)
k = n / 2; // Число строк в матрице, если n четно.
else
k = n / 2 + 1; // Число строк в матрице, если n нечетно.
for (i = 0; i <= k; i++)
{ for (j = i; j <= n - 1 - i; j++)
printf("%d,%d ", i, j); // Вывод индексов элементов
// верхнего треугольника матрицы.
printf("\n");
}
getch(); }

 Языки программирования. Язык С. Конспект лекций -86-


ЛЕКЦИЯ 9. МАССИВЫ. НЕКОТОРЫЕ ПРОСТЕЙШИЕ ЗАДАЧИ. МАТРИЦЫ
4. Перебор элементов матрицы
Программа 23. Перебор элементов нижнего треугольника матрицы.
void main()
{int i, j, k, n;
clrscr();
cout << "Введи размер матрицы n= ";
cin >> n;
cout << "Элементы нижнего треугольника матрицы от вершины: \n";
if (n % 2 == 0) k = n / 2; // Число строк в матрице, если n четно.
else
k = n / 2 – 1; // Число строк в матрице, если n нечетно.
for (i = k; i <= n – 1; i++)
{ for (j = n – 1 – i; j <= i; j++)
printf("%d,%d ", i, j); // Вывод индексов элементов
// нижнего треугольника матрицы.
printf("\n");
}
getch(); }

 Языки программирования. Язык С. Конспект лекций -87-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ
С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ

План

1. Указатели.
2. Способы инициализации указателей.
3. Связь указателя с одномерным массивом.
4. Двумерный массив.
5. Динамическая память.

1. Указатели

Указатель – это переменная для адреса ячейки памяти. Общий формат


объявления указателя имеет вид

<тип> *<имя>.

Например, коды (директивы) int *p; float *r; выделяют две области памяти
p и r размером два байта. При этом переменная p предназначена для записи
адресов переменных типа int, а переменная r предназначена для адресов
переменных типа float.
Программа 1. Объявляет указатель. Выдает случайную информацию
из области памяти, которая выделяется под указатель.
void main()
{ int *p; // Определяется переменная p, которая является указателем.
clrscr ();
cout << "В переменной p содержится случайный адрес: “
<< p << “\n”;
getch(); }

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


следующее сообщение:

В переменной p содержится случайный адрес: 0xNNNNNNNN

При объявлении указателя, например int *p, выделяется память


размером два байта, в котором и находится случайное число A. Это число
компьютер интерпретирует как адрес памяти для записи целого числа. Число
из адреса A, который записан в указателе, доступно для компьютера. Итак,
при объявлении указателя пользователю доступны две области памяти.
Первая область отводится под указатель. Эта область памяти имеет имя p.
Вторая область определяется адресом, который хранится в указателе. Имя

 Языки программирования. Язык С. Конспект лекций -88-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
1. Указатели

второй области памяти – *p. Причем адрес переменной *p равен A, т. е.


справедливо тождество &(*p) ≡ A.
Замечание 1. Использовать указатель, который не инициализируется
(не задается) программой, нельзя!!!
Доступ к информации из адресов, которые хранятся в указателях типа
int *p, float *r и т. д., реализуется, например, директивами:
*p = 6 – в ячейку памяти, адрес которой хранится в указателе p, заслать
число 6;
cout << *p – выдать на экран информацию из ячейки памяти, адрес
которой хранится в указателе p.
Схема памяти до и после выполнения кода *p = 6 дана на рис. 10.1.

A A N
P *P

A A 6
P *P

Рис. 10.1

Для выдачи указателя на экран или для ввода его с клавиатуры


используется формат %p.
Программа 2. Объявляет указатель. Выдает информацию из областей
памяти p и *p, которые доступны программе.
void main()
{ int *p;
clrscr();
printf("В переменной p содержится не контролируемый программой ”
“адрес:\n %p" " – шестнадцатеричная запись адреса в формате, \n"
" который определяет процессор. \n\n",p);
printf("В переменной p содержится не контролируемый программой “
“адрес:\n %ld" " – десятичная запись адреса. \n\n", p);

printf("В адресе %p содержится случайное число %d ", p, *p);


*p = 10; .
printf("\n\nВ адресе %p содержится число %d,"
" которое определяется программой. \n", p, *p);
getch(); }

 Языки программирования. Язык С. Конспект лекций -89-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
1. Указатели

После окончания работы данной программы на экране монитора


появится содержимое переменных p, *p до инициализации *p и после ее
инициализации, т. е. после выполнения кода *p = 10.

2. Способы инициализации указателей

Указателю можно присвоить адрес однотипной переменной, которая


объявлена в программе. Например, после выполнения директив

int *p, x;
p = &x;

в переменную p засылается адрес переменной x.


Указателю можно присвоить значение NULL как признак
неподготовленности указателя.
Указатель можно инициализировать, используя директиву new.
Например, директива p = new int, где p – указатель, выделяет память для
записи целого числа. Адрес этой области памяти заносится в указатель p и
данная память ставится под контроль программы.
В указатель можно вводить информацию с клавиатуры, используя
директиву форматного ввода scanf() со спецификацией %p.
Программа 3. Объявляет указатель. Указателю присваивается адрес
однотипной переменной.
void main()
{ int *p; // Определяется переменная p для записи адресов ячеек,
// в которые можно заносить целые числа.
int x = 222; // Выделяет память для записи целых чисел.
clrscr();
cout << " До инициализации указателя!!!!: \n";
cout << "В адресе " << p << " содержится число *p= "
<< *p << "\n\n";
p = &x; // Инициализации указателя. Указателю присваиваем адрес
// переменной, которая объявлена в программе.
cout << "\n\n После инициализации p= &x!!!: \n";
cout << "Адрес x = " << &x << "\n";
cout << " В указателе p адрес= " << p << “, в котором число= “
<< *p <<" \n";
getch(); }

Схема памяти после объявления переменных дана на рис. 10.2.

 Языки программирования. Язык С. Конспект лекций -90-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
2. Способы инициализации указателей

A &X 222 A N
P X *P

Рис. 10.2

Схема памяти после выполнения кода p = &x приведена на рис. 10.3.

&X &X 222 A N


P X=*P

Рис. 10.3

Программа 4. Объявляет указатель. Для инициализации указателя


используется директива new.
void main()
{ int *p; // Определяется переменная p для записи адресов ячеек,
// в которые можно заносить целые числа.
clrscr();
cout << " Состояние памяти до инициализации!!!: \n\n";
cout << "В неконтролируемом адресе " << p << "
содержится случайное число " << *p << "\n";
*p = -8; // В неконтролируемый адрес засылаем число.
cout << "\nВ неконтролируемом адресе " << p
<< " содержится определенное программой число " << *p << " \n";
cout << "Нажми любую клавишу\n";
getch();
p = new int; // Выделяется память для записи целого числа.
cout << "В контролируемом адресе " << p << " содержится случайное "
<< " число " << *p << " \n";
cout << "Нажми любую клавишу\n";
getch();
*p = -777;
cout << "\nВ контролируемом адресе " << p
<< " содержится определенное программой число " << *p << " \n";
getch(); }

Разберем работу программы. Схема памяти после объявления


переменных дана на рис. 10.4.

A A N
P *P

Рис. 10.4

 Языки программирования. Язык С. Конспект лекций -91-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
2. Способы инициализации указателей

После выполнения кода p = new int компьютер выделяет память с


адресом AN и адрес данной памяти заносится в указатель p. Состояние
памяти в этом случае изображено на рис. 10.5.

AN AN N A 8
P *P

Рис. 10.5

Программа 5. Объявляет указатель. Адрес в указатель вводится с


клавиатуры директивой scanf, в которой используется спецификация %p.
void main()
{
int *p; // Переменная p для записи адресов ячеек.
int x = 2;
clrscr();
cout << " Состояние памяти до инициализации!!!: \n\n";
cout << "В неконтролируемом адресе " << p << "
содержится случайное число " << *p << "\n\n";
printf("Адрес переменной x в формате, \n который определен "
"процессором = %p",&x);
cout << "\n\n Введи адрес переменной x в формате, \n"
<< " который определен процессором (см. предыдущий вывод) ";
scanf("%p",&p);
cout<<"\n\n Состояние памяти после инициализации указателя: \n\n";
printf("В переменной p адрес %p. В этом адресе число %d\n",p,*p);
getch(); }

Данная программа сначала выдаст адрес переменной x в формате


процессора. Затем в этом же формате с клавиатуры в указатель p вводится
адрес переменной x. Далее после ввода в p адреса x выводим содержимое
переменной p (там должен быть адрес x) и *p (т. е. содержимое x = 2).
Указатель на указатель – это переменная для адреса указателя.
Общий формат объявления указателя на указатель имеет вид

<тип> **<имя>

Например, коды (директивы) int **p; float **r; выделяют две области
памяти p и r размером два байта. При этом переменная p предназначена для
записи адресов указателей типа int, а переменная r предназначена для
адресов указателей типа float. Из определения указателя на указатель
следует, что если объявлены переменные p и a как переменные типа int **p
и int *a, то справедлив код p = &a. На схеме рис. 10.6 представлено
состояние памяти после выполнения кода int **p.

 Языки программирования. Язык С. Конспект лекций -92-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
2. Способы инициализации указателей

AY AY A A N
P *P **P

Рис. 10.6

Здесь AY, A, N – случайные числа в соответствующих областях памяти.


Области с адресами AY и A или, что одно и то же, с именами *p и **p
доступны, но не под контролем программы. Поэтому использование этих
областей памяти при работе программы является ошибкой. Заметим, что
справедливы тождества AY ≡ &*p и A = &**p.
Программа 6. Объявляет указатель на указатель. Выдает информацию из
областей памяти, которые доступны программе.
void main()
{ int **p; // Объявляется переменная p для записи адресов указателей.
// При таком определении доступны три области памяти:
// p – для адреса указателя;
// *p – для адреса целого числа;
// **p – для целого числа.
clrscr();
cout << " Три области памяти, которые доступны программе: \n\n";

// Выдает адрес указателя, который содержится в переменной p.


printf("В p содержится не контролируемый программой адрес:"
"\n %p – шестнадцатеричная запись адреса в формате, \n"
" который определяет процессор. \n\n",p);

// Выдает адрес переменной целого типа,


// который содержится в переменной *p.
printf("В *p содержится не контролируемый программой адрес:"
"\n %p – шестнадцатеричная запись адреса в формате, \n"
" который определяет процессор. \n\n",*p);

// Выдается число из переменной с именем **p.


printf("В **p содержится не контролируемое программой "
"\n число: %d\n", **p);

cout << "\n Адреса данных областей памяти:\n\n";


printf("Адрес p = %p\n", &p);
printf("Адрес *p = %p\n", &*p);
printf("Адрес **p = %p\n", &**p);
getch(); }

 Языки программирования. Язык С. Конспект лекций -93-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
2. Способы инициализации указателей

Заметим, что если переменная p определяется, например, кодом int **p,


то при выполнении кода p = new *int выделяется память под указатель и
адрес этой области памяти заносится в p.
В заключение перечислим все операции над указателями, которые
допустимы в языке С:
присваивание для однотипных указателей;
прибавление (вычитание) значения выражения целого типа;
унарные операции ++, --;
сравнение для однотипных указателей;
разность между однотипными указателями. Результатом разности
является число целого типа.

3. Связь указателя с одномерным массивом

В языке С имя массива является указателем на первый элемент. Это


значит, что если, например, определен массив int x[n], то в переменной x
содержится адрес первого элемента массива. Значение переменной x в этом
случае можно присваивать указателю того же типа.
Программа 7. Демонстрирует факт, что имя массива является указателем
на первый элемент.
void main(void)
{
const n = 10;
int a[n], i; // Имя массива a является указателем на элемент a[0].
clrscr();
cout << "В переменной a находится адрес " << a << "\n";
cout << "Адрес 1-го элемента массива " << &a[0] << "\n";
getch(); }

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


появится следующее сообщение:

В переменной a находится адрес 0xN


Адрес 1-го элемента массива 0xN

Данная программа показывает, что значение в переменной a и адрес


первого элемента массива (&a[0]) совпадают по значению и типу. Такая
связь массива с указателем позволяет организовать доступ к элементам
массива через их адреса. А именно:
если i – текущий индекс массива a[], то его адрес соответствует коду
a+ i, т. е. справедливы тождества &a[i] ≡ a + i и *(a + i) ≡ a[i];
если в некотором указателе p, тип которого соответствует типу массива
a[], содержится адрес элемента этого массива, то индекс этого элемента
вычисляется по формуле i = p – a. Справедливо тождество *p ≡ a[i].

 Языки программирования. Язык С. Конспект лекций -94-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
3. Связь указателя с одномерным массивом

Программа 8. Программа демонстрирует способы доступа к элементам


массива через адреса, вычисляет сумму элементов массива.
const n = 5;
void main()
{
int x[10], i, s = 0; // x – указатель на первый элемент массива.
int *rpt; // Определяем указатель rpt и засылаем туда значение
rpt = x; // адреса 1-го элемента массива. В x и rpt хранится адрес 1-го
// элемента. Значение в переменной x изменять НЕЛЬЗЯ!!!
clrscr();
cout << "Введи элементы массива: \n ";
for (; rpt - x < n; rpt++) // В цикле формируем текущий адрес
// элемента массива.
{ i = rpt - x; // Если известно значение указателя, индекс элемента
// определяется формулой: i = rpt – x.
// rpt – текущее значение указателя; x – адрес 1-го элемента.
cout << "x[" << i << "]= ";
scanf("%d", rpt); // Сравните с директивой scanf(%d”, &x[i]).
}
// Вывод массива на экран.
for (rpt = x; rpt - x < n; rpt++ )
cout << *rpt << " "; // Доступ к элементу через указатель.
for (i = 0; i < n; i++)
s = s + *(x + i); // Другая форма доступа к элементу массива
// через указатель.
cout << "\n S= " << s << "\n";
getch(); }

Программа демонстрирует факт, что значения a[i], *(a + i) и *rpt (если


код rpt++ выполняется i раз) определяют одну и ту же область памяти.

4. Двумерный массив
Если задана матрица, например a[n][m], то для нее определен массив
указателей a[n]. В i-м элементе массива a[i] содержится адрес первого
элемента i-й строки. В переменной a содержится адрес первого элемента
матрицы, т. е. справедливо тождество &a[0][0] ≡ a.
Программа 9. Демонстрирует, что при объявлении двумерного массива,
определен массив указателей на первые элементы строк матрицы.
const n = 5, m =4;
void main()
{int b[n][m], i;
clrscr();
for (i = 0; i < n; i++) // Перебираем индексы строк двумерного
// массива.

 Языки программирования. Язык С. Конспект лекций -95-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
4. Двумерный массив

{ cout << " b(" << i << ")= " << b[i]; // Вывод значений элементов
// одномерного массива.
// Вывод адресов первых элементов строк матрицы.
cout << " \n адрес b(" << i << ",0)= " << &b[i][0] << "\n\n";
} getch(); }

Данная программа демонстрирует тот факт, что для переменной b[i]


справедливо равенство b[i] = &b[i][0]. Связь матрицы с массивом указателей
позволяет организовать доступ к элементам матрицы через адреса. Для
элемента матрицы a[i][j] справедливы следующие формулы:
выражение a + i * m + j определяет адрес a[i][j], т. е. &a[i][j] ≡ a + i * m + j
или a[i][j] ≡ *(a + i * m + j). Здесь i * m + j – порядковый номер элемента
a[i][j];
выражение a[i] + j определяет адрес a[i][j], т. е. &a[i][j] ≡ a[i]+j или
a[i][j] ≡ *( a[i] + j).
Программа 10. Демонстрирует различные формы доступа к элементам
матрицы через указатели.
void main(void)
{ const n = 4, m = 4;
FILE *f1;
int i,j, *p, a[n][m];
clrscr();
// Ввод двумерного массива из файла.
p = a[0];
f1 = fopen("t.dat", "r");
cout << "Доступ к элементам матрицы происходит по формуле: \n\n"
<< "\n *(a[i] + j) \n\n";
for (i = 0; i <= n – 1; i++)
{ for (j = 0; j <= m – 1; j++, p++)
{ fscanf( f1, "%d", p ); // Одна из форм доступа к элементам
// матрицы через массив указателей.
printf(" %d\t", *(a[i] + j)); // Одна из форм доступа к элементам
// матрицы через массив указателей.
}
printf("\n"); }
fclose(f1);
printf("\n");
p = a[0];
cout << "Доступ к элементам матрицы происходит по формулам: \n"
<< " *(a[i] + j) \n *(p + i *m + j) \n\n";
f1 = fopen("t1.dat", "w");
for (i = 0; i <= n – 1; i++)
{ for (j = 0; j <= m – 1; j++)
{ printf(f1,"%d ", *(a[i] + j ) ); // Одна из форм доступа к элементам
// матрицы через массив указателей.

 Языки программирования. Язык С. Конспект лекций -96-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
4. Двумерный массив

printf(" %d\t", *(p + i * m + j )); // Одна из форм доступа к элементам


// матрицы через массив указателей.
}
fprintf(f1,"\n");
printf("\n"); }
fclose(f1);
getch(); }

5. Динамическая память
Под динамической памятью понимается память, которая выделяется во
время работы программы. В языке С для выделения динамической памяти
используются указатели.
Одномерный массив. Для того чтобы задать память под одномерный
массив во время работы программы, необходимо:
определить указатель, например int *p;
задать требуемый размер n массива;
выполнить код p = new int [n].
Программа 11. Используется указатель для динамического выделения
памяти под одномерный массив.
void main()
{ int j,s;
int *data;
cout << "Введи размер массива ";
cin >> s; // Ввод размера массива.
data = new int[s]; // Выделяется динамическая память для элементов
// массива размерности s.
cout << “После инициализации в переменной data значение адреса = ”
<< data << “\n”;
cout << "\nРазмер массива = " << s
<< "\n\n Введи элементы массива с клавиатуры \n";
for (j = 0; j < s; j++)
{ cout << "data[" << j << "]= " ;
cin >> data [j]; // Доступ к элементам массива через индекс.
}
cout << "\nВведен массив: \n";
for ( j = 0; j < s; j++)
cout << data[j] << “ “; // Вывод элементов массива на экран.
cout << "\n";
delete [] data; // Удаляет память, которая использовалась для массива.
getch();
}
Замечание 2. Если в программе определен указатель, то в принципе без
предварительного выделения памяти можно использовать переменную с
индексом.

 Языки программирования. Язык С. Конспект лекций -97-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
5. Динамическая память

Программа 12. Программа демонстрирует тот факт, что при объявлении


указателя в программе можно использовать переменную с индексом без
предварительного выделения динамической памяти.
void main()
{ int j,s;
int *data;
clrscr();
cout << “В переменной data значение адреса = ” << data << “\n”;
cout << "Введи размер массива ";
cin >> s;
cout << "\nРазмер массива= " << s << "\n\n Введи массив с клавиатуры\n";
// Динамическую память НЕ ВЫДЕЛЯЕМ!
for (j = 0; j < s; j++)
{
cout << "data[" << j << "]= " ;
cin >> data [j]; // Доступ к элементам массива через индекс.
}
cout << "\nВведен массив: \n";
for (j = 0; j < s; j++)
cout << data[j] << “ “;
cout << "\n";
getch(); }

Замечание 3. При объявлении указателя любого типа программе


становятся доступны дополнительные области памяти, которые не находятся
под ее контролем. Использовать эти области памяти нельзя, потому что это
будет грубой ошибкой, которую транслятор языка не выявляет.
Двумерный массив. Для выделения динамической памяти под матрицу
существует два способа.
Первый способ заключается в том, что матрица a[n][m]
интерпретируется как одномерный массив. В этом случае динамическая
память под матрицу выделяется как под массив, отсюда следует:
определить указатель, например int *p;
во время работы программы задать n – число строк и m – число
столбцов в матрице;
выполнить код p = new int [n * m]. При выполнении данного кода
компьютер выделяет память для n * m чисел и адрес памяти для первого
числа засылается в указатель p.
В этом случае доступ к элементам матрицы возможен только через
адреса. Элемент p[i][j] в программе должен быть представлен кодом
*(p + i * m + j), если в p содержится адрес первого элемента матрицы. Адрес
элемента p[i][j] представляется выражением (p + i * m + j).
Второй способ выделения динамической памяти под матрицу
заключается в том, что сначала выделяется память под массив указателей:

 Языки программирования. Язык С. Конспект лекций -98-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
5. Динамическая память

размер массива указателей равен количеству строк матрицы. А затем


выделяется память под каждую строку матрицы. Для этого надо:
определить указатель на указатель, например int **p;
во время работы программы задать n – число строк и m – число
столбцов в матрице;
использовав директиву p = new int *[n], выделить память под массив
указателей размерности n;
выполнив в цикле директиву p[i] = new int[m], где i = 1, 2, ..., n,
выделить память под массив указателей для строк матрицы. При этом в
указатель p[i] засылается адрес первого элемента i-й строки.
При таком способе выделения динамической памяти под матрицу
доступ к элементам массива можно осуществлять через индексы, т. е. в виде
p[i][j].
Программа 13. Для выделения динамической памяти под матрицу
используется указатель.
void main()
{ clrscr();
int n,m,i,j;
int *ptr, *mas2; // ptr, mas2 – переменные для адресов (указатели).
FILE *f;
printf("Введите число строк матрицы: \n");
scanf("%d",&n);
printf("\nВведите число столбцов матрицы: \n");
scanf("%d",&m);
mas2 = new int[n*m]; // Выделяем блок памяти под элементы матрицы.
// Начальный адрес выделенного блока памяти
// размещается в переменной mas2.
// Ввод матрицы из файла.
f = fopen("t.dat", "r");
ptr = mas2; // Начальный адрес выделенного блока засылаем в ptr.
for(i = 0; i < n; i++)
for (j = 0; j < m; j++, ptr++)
fscanf(f,"%d",ptr); // Форма доступа к элементам матрицы.
fclose(f);
ptr = mas2; // Вопрос: для чего нужен этот код программы?
for(i = 0; i < n; i++)
{ for (j = 0; j < m; j++)
{ ptr = mas2 + i * m + j; // Определяем адрес элемента с индексами i, j.
printf(" %d", *ptr); // Вывод элемента матрицы с индексами i, j.
} printf("\n"); }
printf("Введите номер строки матрицы i = ");
scanf("%d",&i);
i--; // Индекс строки на 1 меньше номера.
printf("Введите номер столбца матрицы j = ");
scanf("%d",&j);

 Языки программирования. Язык С. Конспект лекций -99-


ЛЕКЦИЯ 10. УКАЗАТЕЛИ. СВЯЗЬ УКАЗАТЕЛЯ С МАССИВАМИ. ДИНАМИЧЕСКАЯ ПАМЯТЬ
5. Динамическая память

j--; // Индекс столбца на 1 меньше номера.


ptr = mas2 +i * m + j; // Определяем адрес элемента с индексами i, j.
printf(" a[%d,%d]= %d",i,j,*ptr); // Выводим на экран элемент ptr[i][j].
getch(); }

Программа 14. Выделяется динамическая память под матрицу с


использованием массива указателей.
void main()
{
int n,m,i,j;
int **mas2; // mas2 – указатель для адресов указателей.
FILE *f;
clrscr();
printf("Введите число строк матрицы: \n");
scanf("%d",&n);
printf("\nВведите число столбцов матрицы: \n");
scanf("%d",&m);
mas2 = new int *[n]; // Выделяем блок памяти под массив указателей.
for(i = 0; i < n; i++)
mas2[i] = new int [m]; // Выделяем блок памяти под строку матрицы.
f = fopen("t.dat", "r");
for(i = 0; i < n; i++)
for(j = 0; j < m; j++)
fscanf(f,“%d”, &mas2[i][j]); // Ввод элементов матрицы из файла.
fclose(f);
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
printf(" %d", mas2[i][j]); // Вывод элементов матрицы на экран.
printf("\n");
}
delete [] mas2;
getch(); }

 Языки программирования. Язык С. Конспект лекций -100-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ

План
1. Файлы.
2. Символы.
3. Стандартные программы.

1. Файлы
Файл – это именованная область памяти на внешнем запоминающем
устройстве. Файлы служат для записи и хранения информации. Полное имя
файла для С имеет следующий формат:
<имя>.<тип>
Тип в имени может отсутствовать. Число символов в типе не
превышает трех. Для того чтобы в языке С во время работы программы
организовать ее взаимодействие с файлом на внешнем запоминающем
устройстве, надо:
объявить переменную, которая называется указателем файла;
открыть файл: связать указатель файла с конкретным именем файла;
закрыть файл, если программа завершила все операции с файлом на
внешнем носителе.
Формат объявления переменной, которая является указателем файла,
следующий:
FILE *<имя>;
Для связи файла на носителе с указателем в программе используется
функция fopen(). Код, который открывает файл, имеет следующий вид:

FILE *f1;
f1 = fopen(“<имя файла на носителе>”,”<режим доступа к файлу>”);

Здесь <имя файла на носителе> – имя конкретного файла на внешнем


носителе; <режим доступа к файлу> задает режим доступа к файлу.
Возможные режимы доступа:
r – файл открыт для чтения;
w – файл открыт для записи;
a – файл открыт для дозаписи в конец;
r+ – файл открыт для чтения и записи;
w+ – файл открыт для записи и чтения.
Функция fopen() возвращает указатель файла. Если при открытии файла
произошла ошибка, то функция возвращает NULL. Для подтверждения того,
что функция fopen() выполнилась успешно, можно использовать следующий

 Языки программирования. Язык С. Конспект лекций -101-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
1. Файлы
набор операторов:
FILE *f1;
if ((f1 = fopen( “t.dat”, “r”)) != NULL)
{ // Если файл открыт, выполняется блок операторов в скобках.
}
else // Если файл не открыт, выдается сообщение об ошибке.
printf(“Ошибка при открытии файла!!! \n”);

Здесь t.dat – файл с информацией, который записан на внешнем носителе.


Закрывает доступ к файлу функция fclose(f1), где f1 – указатель на открытый
ранее файл.
Для ввода информации из файла в память компьютера используется
директива форматного ввода fscanf(). Формат директивы для считывания
информации из файла имеет следующий вид:

fscanf( F, <управляющая строка>, <список переменных>).

Для записи информации из памяти компьютера в файл используется


директива форматного ввода fprintf(). Формат директивы для записи
информации в файл имеет следующий вид:

fprintf( F,<управляющая строка>,<список переменных>).

Программа 1. Вводит целое число из файла и записывает число в


новый файл.
void main(void)
{ FILE *f1; // Объявляет файловую переменную.
int n;
clrscr();
/* Ввод из файла */
if( (f1 = fopen("num.dat", "r")) != NULL)
{ fscanf( f1, "%d", &n);
printf("n= %d", n);
fclose(f1);
// Запись числа в файл.
n = n * n;
f1 = fopen("t1_w.dat", "w");
fprintf( f1, "%d ", n );
fclose(f1); }
else
printf(" Файл не открыт. \n");
getch(); }

 Языки программирования. Язык С. Конспект лекций -102-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
1. Файлы
Программа 2. Ввод массива в память компьютера из файла при
заданном количестве чисел.
void main(void)
{ const n = 10; // n – для числа элементов в массиве.
FILE *f1; // Определяется файловая переменная f1.
int i, a[n];
clrscr();
f1 = fopen("t1.dat", "r");
for (i = 0; i <= n - 1; i++)
fscanf( f1, "%d", &a[i] );
fclose(f1);
f1 = fopen("t1_w.dat", "w");
for (i = 0; i <= n - 1; i++)
fprintf( f1, "%d ", a[i] ); // fprintf – директива записи данных в файл.
fclose(f1); }

Программа 3. Ввод массива по признаку конца файла.


void main(void)
{
const n = 100; // Определяется число элементов в массиве.
FILE *f1; // Определяется файловая переменная.
int i = -1, a[n], nr;
clrscr();
f1 = fopen("t1.dat", "r");
do {
i++; // !!! Формируем индекс элемента файла.
fscanf( f1, "%d", &a[i] );
}
while (!feof(f1 )); // Ввод чисел до признака конца файла.
fclose(f1);
// Запись элементов массива в файл.
nr = i; // В nr засылаем число элементов в массиве.
f1 = fopen("t1w.dat", "w");
for (i = 0; i <= nr; i++)
fprintf(f1, "%d ", a[i] ); // fprintf – директива записи данных в файл.
fclose(f1);
}

Такой способ ввода информации удобен, когда заранее не известно


число элементов в массиве.

2. Символы
Для объявления символьных переменных в языке С используется
ключевое слово char. Этот тип переменных выделяет 1 байт для записи

 Языки программирования. Язык С. Конспект лекций -103-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
2. Символы
символов и определяет целые числа без знака в диапазоне от 0 до 255. Для
идентификации символов используется код ASCII. Это означает, что
каждому символу строго соответствует некоторое число. Формат объявления
символьных переменных имеет следующий вид:

char <имя>.

При объявлении символьной переменной ее можно инициализировать.


Например, код char c1 = ‘*’ выделяет область памяти с именем c1 и засылает
в нее символ *. Символ в одинарных кавычках называется символьной
константой. Для ввода символа с клавиатуры можно использовать
следующие функции:
getchar() – возвращает символ, введенный с клавиатуры после
активизации клавиши Enter;
getch() – возвращает символ, введенный с клавиатуры. Введенный
символ на экран не отображается;
getche() – возвращает символ, введенный с клавиатуры. Введенный
символ на экран отображается;
cin – бесформатный ввод символа. Для ввода символа в переменную
надо нажать последовательно соответствующую клавишу символа, а затем
клавишу Enter;
scanf() – форматный ввод символа. Для ввода символа в переменную
надо нажать соответствующую клавишу символа, а затем клавишу Enter.
Спецификация формата ввода (вывода) символов имеет вид %c. Возможен,
например, следующий формат кода с функцией scanf():

scanf(%c%c%c”,&w, &al, &yy)

Здесь w, a1, yy – переменные символьного типа. Эта директива обеспечивает


ввод символов в переменные w, a1, yy. Для ввода символов по этой директиве
надо на клавиатуре набрать через пробелы три символа и нажать клавишу
Enter.
В языке С возможен код int n; n = ‘q’; В этом случае в переменную n
засылается число 113, которое является кодом символа q.

Программа 4. Для ввода символа с клавиатуры используются


различные функции.
void main()
{ char z = 'w';
clrscr();
cout << "При объявлении переменной z ей присвоено значение = "
<< z << "\n\n";
cout << "Введи символ с клавиатуры”
<< “ (После набора символа нажми Enter): ";
scanf("%c",&z);
cout << "При вводе в переменную z символа " << z

 Языки программирования. Язык С. Конспект лекций -104-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
2. Символы
<< "\n использовалась функция scanf() \n\n";

cout << "Введи символ с клавиатуры”


<< “ (После набора символа нажми Enter): ";
cin >> z;
cout << "При вводе в переменную z символа " << z
<< "\n использовалась функция cin \n\n";

cout << "Введи символ с клавиатуры \n "


<< "(ввод происходит при нажатии клавиши клавиатуры): ";
z = getche();
cout << "\nПри вводе в переменную z символа " << z
<< "\n использовалась функция getche() \n\n";

cout << "Введи символ с клавиатуры \n"


<< "(ввод происходит при нажатии клавиши клавиатуры): ";
z = getch();
cout << "\nПри вводе в переменную z символа " << z
<< "\n использовалась функция getch() \n\n";

z = 23; // В переменную z заносится символ, код которого равен 23.


cout << "В переменную z введен символ директивой z = "
<< (int) z << "\n";
cout << "Коду (числу) " << (int) z << " соответствует символ "
<< z << "\n\n";
getch(); }

Продемонстрируем трассировку работы программы 4.


При объявлении переменной z ей присвоено значение = w
Введи символ с клавиатуры. (После набора символа нажми Enter): s
При вводе в переменную z символа s
использовалась функция scanf()

Введи символ с клавиатуры. (После набора символа нажми Enter): r


При вводе в переменную z символа r
использовалась функция cin

Введи символ с клавиатуры


(ввод происходит при нажатии клавиши клавиатуры): t
При вводе в переменную z символа t
использовалась функция getche()
Введи символ с клавиатуры
(ввод происходит при нажатии клавиши клавиатуры): g
При вводе в переменную z символа g
использовалась функция getch()

 Языки программирования. Язык С. Конспект лекций -105-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
2. Символы
В переменную z введен символ директивой z = 23
Коду (числу) 23 соответствует символ _

Для вывода символов можно использовать либо форматный вывод


printf(), либо бесформатный cout, либо функцию putchar(). Формат директив
при выводе символов на экран может быть, например, таким:
putchar(c);
cout << “В переменной w содержится символ = “ << w << “\n”;
printf(“В переменную z введен символ %c\n”, z);
cout << “В переменную t введен символ “ << putchar(t).

Здесь c, w, z, t – переменные символьного типа. Все эти директивы выведут


на экран символы, которые находятся в c, w, z, t.

Программа 5. Программа выводит ASCII код символа.


void main()
{ char ch;
clrscr();
cout << " Введи символ ( Enter – выход из программы )\n ";
do
{
ch = getch(); // Ввод символа с клавиатуры.
if (ch != 13) // 13 – код символа Enter.
cout << "Cимволу " << ch // Вывод символа на экран.
<< " cоответствует код " << (int) ch // Вывод кода символа.
<< "\n";
else
cout << "Cимволу Enter " << " cоответствует код " << (int) ch
<< "\n";
}
while (ch != 13); // Продолжать ввод, пока не введен символ Enter.
cout << "\nДля выхода из программы нажми любую клавишу. \n ";
getch(); }

При работе программы запрашивается ввод символа. После ввода


символа, например q, на экран выводится сообщение:
Символу q соответствует код 113
Работа программы будет продолжаться до тех пор, пока не будет
нажата клавиша Enter.

Программа 6. Программа по числу (коду), которое вводится с


клавиатуры, выводит соответствующий символ.
void main()
{ int ch;

 Языки программирования. Язык С. Конспект лекций -106-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
2. Символы
clrscr();
cout << " Введи код символа (Сtrl + z – выход из программы ): ";
do
{ scanf("%d", &ch); // Ввод числа с клавиатуры.
if (!feof(stdin))
cout << "Коду " << ch // Вывод кода символа на экран.
<< " соответствует символ "
<< (char) ch << "\n\n"; // Вывод символа на экран.
cout << "Введи число (код символа): "; }
while (!feof(stdin) );
cout << "\n Для выхода из программы нажми любую клавишу \n ";
getch(); }

Программа 7. Программа посимвольно считывает текст из файла и


выводит этот текст на экран.
void main(void)
{ FILE *f1;
char c1;
clrscr();
cout << "Программа выводит текст, “
<< “который вводится из файла посимвольно. \n";
f1= fopen("str.dat", "r"); // Открываем файл с текстом.
do {c1 = getc(f1); // В переменную c1 из файла вводим символ.
printf("%c", c1); // Выводим символ на экран.
}
while (!feof(f1 )); // Цикл, пока не конец файла.
fclose(f1);
getch(); }

Программа 8. Выводит на экран символы английского алфавита.


void main()
{ char ch;
clrscr();
cout << "Символы английского алфавита:\n";
for (ch = 'a'; ch <= 'z'; ch++) // Цикл от a до z.
cout << ch << " "; // Вывод параметра цикла ch на экран.
cout << "\n";
getch(); }
Результат работы программы:

Символы английского алфавита:


a b c d e f g h i j k l m n o p q r s t u v w x y z

Заметим, что в представленной программе управляющим параметром


цикла является переменная типа char.

 Языки программирования. Язык С. Конспект лекций -107-


ЛЕКЦИЯ 11. ФАЙЛЫ И СИМВОЛЫ
2. Символы
Программа 9. Выводит на экран символы русского алфавита.
void main()
{char ch;
clrscr();
cout << "Символы русского алфавита:\n";
for ( ch = 'а'; ch <= 'я'; ch++ )
if( ! (ch > 'п' && ch < 'р')) cout << ch << " ";
cout << "\n";
getch(); }

Результат работы программы:

Символы русского алфавита:


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

Замечание. Условный оператор if(!(ch > 'п' && ch < 'р')) в программе
используется потому, что между русскими буквами ‘п’ (код 175) и ‘р’ (код
224) находятся символы, которые не являются буквами русского алфавита.
Коды упомянутых символов начинаются от значения 176 и заканчиваются
значением 223. Оператор if(!(ch > 'п' && ch < 'р')) исключает вывод этих
символов на экран.

3. Стандартные программы

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


isalnum(int c) – функция возвращает не нуль, если c – код буквы или
цифры, и нуль – в противном случае;
isalpha(int c) – функция возвращает не нуль, если c – код буквы,
и нуль – в противном случае;
isascii(int c) – функция возвращает не нуль, если c есть код ASCII, т. е.
код принимает значение от 0 до 127;
isdigit(int c) – функция возвращает не нуль, если c – цифра (0–9),
и нуль – в противном случае;
ispunct(int c) – функция возвращает не нуль, если c – символ-разде-
литель, и нуль – в противном случае.
Данные программы находятся в файле <ctype.h>.

 Языки программирования. Язык С. Конспект лекций -108-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК.
ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ

План
1. Строки.
2. Стандартные функции для работы со строками.
3. Массивы строк.
4. Доступ к функциональным клавишам.

1. Строки
Использовав символьные переменные, можно сформировать две
структуры данных: массив символов и строки. Массив есть множество
элементов одного типа. Строка – это массив символов, последним
элементом которого является символ ‘\0’ (обратный слеш, 0), называемый
концом строки. Принципиальная же разница между строкой и массивом
состоит в том, что присутствие в строке символа ‘\0’ неявно задает размер
массива. Форма объявления массива символов в языке С имеет следующий вид:

char <имя>[<размер>];

Специального формата для объявления строки в языке С не существует.


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

char s[]=“Саша – очень умный мальчик! ”;


char s2[10] = “Ура!!!”;
char *s3 = “Три мудреца в одном тазу. ”;
char s4[20] = “ “;

Для ввода строк с клавиатуры можно использовать функции:


scanf() – функция форматного ввода строки;
gets()– функция бесформатного ввода строки;
cin – директива бесформатного ввода строки.

Программа 1. Программа вводит строки с клавиатуры с использованием


функции scanf(). Функция вводит текст до первого пробела.
void main()
{
char a[80];
clrscr();
cout << "Введи строку (Ctrl + z Enter – конец ввода )\n";
do

 Языки программирования. Язык С. Конспект лекций -109-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
1. Строки

{
scanf("%s",a);
if (!feof(stdin))
cout << "!!!! " << a << "\n";
}
while (!feof(stdin));
}

Последовательность работы программы при вводе одной строки:


• запустить программу;
• после подсказки
Введи строку (Ctrl + z Enter – конец ввода )
• набрать строку
Наша Таня громко плачет
• нажать Enter.
На экране появится следующая информация:

!!!! Наша
!!!! Таня
!!!! громко
!!!! плачет

Программа 2. Программа вводит строки с клавиатуры, используя


функцию gets().
void main()
{ char a[80];
clrscr();
cout << "Введи строку (Ctrl + z Enter – конец ввода)\n";
while (!feof(stdin))
{
gets(a);
cout << "!!!! " << a << "\n";
}}

Последовательность работы программы при вводе одной строки:


• запустить программу;
• после подсказки
Введи строку (Ctrl + z Enter – конец ввода)
• набрать строку
Наша Таня громко плачет
• нажать Enter.
На экране выдачи результатов появится следующая информация:

!!!! Наша Таня громко плачет

 Языки программирования. Язык С. Конспект лекций -110-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
1. Строки

Программа 3. Программа вводит строки с клавиатуры, используя


директиву cin.
void main()
{ char a[80];
clrscr();
cout << "Введи строку \n";
do {
cin >> a;
cout << "!!!! " << a << "\n";
cout << "Продолжать ввод? (Enter – да)\n";
}
while (getch() == 13); }

Последовательность работы программы при вводе одной строки:


• запустить программу;
• после подсказки
Введи строку
• набрать строку
Наша Таня громко плачет
• нажать Enter.
На экране выдачи результатов появятся слова:
!!!! Наша
Продолжать ввод? (Enter – да)
При нажатии Enter на экране появятся слова:
!!!! Таня
Продолжать ввод? (Enter – да)
При нажатии Enter на экране появятся слова:
!!!! громко
Продолжать ввод? (Enter – да)
При нажатии Enter на экране появятся слова:
!!!! плачет
Продолжать ввод? (Enter – да)
После вывода последнего слова строки можно продолжать вводить
строки. Если в процессе работы программы после подсказки
Продолжать ввод? (Enter – да)
будет нажата не клавиша Enter, а любая другая, то программа завершит
работу.
Для ввода строк из файла используются функции:
fscanf() – функция форматного ввода текста из файла;
fgets() – функция бесформатного ввода текста из файла.
Формат кодов при использовании этих функций имеет следующий вид:
fscanf(f1,”%s”, str); fgets(str, k, f1).
Здесь f1 – имя файловой переменной; str – имя массива символов; “%s” –
управляющая строка для функции fscanf(); k – параметр целого типа (fgets

 Языки программирования. Язык С. Конспект лекций -111-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
1. Строки

вводит в str всю строку, если число символов в строке меньше или равно k – 1).
При успешном завершении работы функции fgets() в str засылается строка из
файла f1. При выполнении функции fscanf() в str последовательно засылается
из файла набор символов, расположенных между пробелами.

Программа 4. Программа вводит текст из файла, используя функцию


fscanf(). Наборы символов между пробелами последовательно засылаются в s1.
void main(void)
{
FILE *f1; // Объявляется файловая переменная f1.
char s1[81]; // Объявляется массив символов.
clrscr();
f1 = fopen("t.dat", "r"); // Открываем файл t.dat.
do
{
fscanf(f1,"%s",s1); // Ввод из файла t.dat в s1 набор символов
// между пробелами.
printf("%s \n", s1); // Вывод на экран информации из s1.
}
while (!feof(f1 )); // Вводить информацию из файла до конца файла.
fclose(f1);
getch();
}

Программа 5. Программа вводит текст из файла, используя функцию


fgets().
void main(void)
{
FILE *f1; // Объявляется файловая переменная f1.
char s1[81]; // Объявляется массив символов.
clrscr();
f1 = fopen("t.dat", "r"); // Открываем файл t.dat.
do
{
fgets(s1, k, f1); // Ввод строки в s1 из файла t.dat.
printf("%s \n", s1); // Вывод на экран информации из s1.
}
while (!feof(f1 )); // Вводить информацию из файла до конца файла.
fclose(f1);
getch(); }

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


выполнения фрагмента программы:

char *str; // Объявляется указатель.

 Языки программирования. Язык С. Конспект лекций -112-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
1. Строки

int k; // Параметр k – для размера массива.


cout << “Введи размер для строки: ”;
cin >> k;
str = new char [k];

под строку выделяется массив для символов размерностью k.

2. Стандартные функции для работы со строками

Функции содержатся в файле <string.h>:


strlen(st) возвращает длину строки st;
strchr(st,ch) возвращает значение указателя на первое вхождение
символа в строке st, который совпадает с символом в переменной ch. Если
символ не найден, функция strchr возвращает нулевой указатель. Иначе,
функция strchr определяет, содержится ли тот или иной символ в строке;
strcpy(st1, st2) копирует строку st2 в строку st1;
strcat(st1, st2) присоединяет строку st2 к строке st1;
strstr(st1, st2) определяет, содержится ли строка st2 в строке st1. Если
содержится, то strstr возвращает значение указателя на первое вхождение st2
в st1. Иначе strstr возвращает нулевой указатель;
strcmp(st1, st2) сравнивает в алфавитном порядке строки st1 и st2:
при st1 < st2 значение сравнения отрицательно;
при st1 = st2 значение сравнения равно нулю;
при st1 > st2 значение сравнения положительно;
atof(st) преобразует строку st в вещественное число типа double;
atoi(st) преобразует строку st в десятичное целое число;
atol(st) преобразует строку st в длинное десятичное целое число;
itoa(int k, char *st, int baz) преобразует целое число k в строку st,
переменная baz – основание (2 ≤ baz ≤ 36);
ltoa(int k, char *st, int baz) преобразует длинное целое число k в строку
st, переменная baz – основание (2 ≤ baz ≤ 36).
Замечание. Функцию strcmp можно использовать для сортировки слов
по алфавиту.

3. Массив строк

Массив строк – это двумерный символьный массив. Например, код

char str[10][81];

задает 10 строк для 80 символов каждая. Наиболее удобной формой


выделения памяти под массив строк является выделение динамической памяти.
Для этого, в простейшем случае, в программе следует предварительно

 Языки программирования. Язык С. Конспект лекций -113-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
3. Массив строк

объявить массив указателей типа char. Размер массива указателей может


превышать или соответствовать количеству ожидаемых строк.

Программа 6. Программа вводит с клавиатуры массив строк. Память


под строки выделяется в процессе работы программы, т. е. динамически.
const n = 20;
void main()
{
char *st[n]; // Объявляется массив указателей.
int k = -1, i;
clrscr();
printf("Введи строки (для окончания ввода нажми Ctrl + z Enter).\n");
do
{
k++; // Формируем индекс для ввода очередной строки.
st[k] = new char[81]; // Выделяем память для ввода строки.
}
while (gets(st[k]) != NULL); // Ввод и проверка
// завершения ввода строки.
printf("\n\nВведены %d строки: \n", k);
for (i = 0; i < k; i++)
printf("%s\n", st[i]); // Вывод массива строк.
getch(); }

Программа 7. Программа вводит из файла массив строк. Под строки


память выделяется динмическая. Для этого используется массив указателей.
void main()
{FILE *f1;
char *st[20]; // Определяется массив указателей.
int k = -1, i;
clrscr();
f1 = fopen("t.dat", "r"); // Открывается файл с информацией.
do { k++; // Формируем индекс для текущей строки.
st[k] = new char[81];
fgets(st[k],79,f1); } while (!feof(f1));
printf("\n Из файла ввели %d строки:\n",k);
for (i = 0; i < k; i++)
printf("%s", st[i]);
getch(); }

Программа 8. Программа вводит из файла массив строк. Под строки


память выделяется динамическая. Для этого используется указатель на указатель.
void main()
{FILE *f1;
char **st;

 Языки программирования. Язык С. Конспект лекций -114-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
3. Массив строк

int n, len, k = -1, i;


clrscr();
cout << "Введи количество строк ";
cin >> n;
st = new char *[n]; // Выделяется память под массив указателей.
cout << "Введи число символов в строке ";
cin >> len; // Определяется число символов в строке.
f1 = fopen("t.dat", "r");
do { k++;
st[k] = new char[len];
fgets(st[k],79,f1); // Вводим в строку st[k] информацию из файла.
} while (!feof(f1));
printf("\n Из файла ввели %d строки:\n",k);
for (i = 0; i < k; i++)
printf("%s", st[i]); // Вывод массива строк на экран.
getch(); }

Массив строк можно задавать при объявлении переменных. Это,


например, достигается следующим кодом:
char *str[] = {“Воскресенье”, “Понедельник”, “Вторник”, “Среда”,
“Четверг”, “Пятница”, “Суббота”};

Программа 9. Массив слов отсортировать по алфавиту. Слова в массив


строк вводятся из файла.
void main()
{ char *st[100], sr[81];
int k = -1, i, j, im;
FILE *f1;
sr[80]= '\0';
clrscr();
f1 = fopen("t.dat", "r");
do { k++;
st[k] = new char[81]; // Выделяется память под строку.
fgets(st[k],79,f1); // Ввод строки из файла.
} while (!feof(f1));
cout << " До сортировки:\n";
for (i = 0; i < k; i++)
printf("%s", st[i]);
getch();
clrscr();
// Сортировка по алфавиту.
for (i = 0; i <= k; i++)
{ im = i;
for (j = i+1; j < k; j++)
if (strcmp( st[im], st[j]) > 0)

 Языки программирования. Язык С. Конспект лекций -115-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
3. Массив строк

im = j;
strcpy(sr, st[i]); // Перестановка строк.
strcpy(st[i], st[im]);
strcpy(st[im], sr); }
cout << "\n После сортировки:\n";
for (i = 0; i < k; i++)
printf("%s", st[i]);
getch(); }

Задача. Вывести на экран слова, из которых состоит текст.


Алгоритм решения данной задачи формулируется так:
Шаг 1. Объявляем переменные:
str – для строковой константы с разделителями;
sr – для строки из файла; st – для слова из текста;
k – для индекса символа в слове. Если k = –1, то слово не
сформировано; ch – для текущего символа.
Шаг 2. Определяем начальные значения переменных:
str = ",. !?:;:'\n\0"; k = -1; ch =’ ‘ (пробел)
Шаг 3. Цикл по считыванию строк из файла. Выполнить шаги 4–10.
Шаг 4. Прочитать строку из файла в переменную sr.
Шаг 5. Цикл по i. Выполнить шаги 6–10 при i = 0, 1, …, strlen(sr).
Шаг 6. Если текущий символ sr[i] не является разделителем, то
перейти на шаг 9.
Шаг 7. Если слово не сформировано, то перейти на шаг 10.
Шаг 8. Преобразовать массив символов st, с выделенным словом,
в строку. Выдать слово на экран. Установить k = –1.
Шаг 9. Установить k = k + 1, st[k] = sr[i].
Шаг 10. Конец цикла по i.
Шаг 11. Конец цикла по считыванию строк из файла.

Программа 10
void main()
{FILE *f1;
char ch = ' ', *str = ",. !?:;:'\n\0";
char st[81], sr[81]; // sr – для строки из файла.
// st – для слова из строки.
int k = -1, i;
clrscr();
f1 = fopen("str_w.dat", "r");
do { fgets(sr,80,f1); // Ввод строки из файла.
for (i = 0; i < strlen(sr); i++) // Начало цикла по i.
{ ch = sr[i];
if (strchr( str,ch) ) // Если символ не разделитель, то иди на
// формирование слова.

 Языки программирования. Язык С. Конспект лекций -116-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
3. Массив строк

{ if (k != -1) // Если слово сформировано, значение k не равно –1.


{
k++; // Определяем индекс для символа \0 (конец строки).
st[k] = '\0'; // Из массива символов st формируем строку.
//___________________________________________________________
cout << "\n*** " << st; // Вывод слова на экран.
//____________________________________________________________
k = -1; // Устанавливаем k = –1.
}
}
else
{ // Формируем слово.
k++; // Определяем индекс k для текущего символа слова.
st[k] = ch; // В массив для слова st засылаем текущий символ.
}
} // Конец цикла по i.
} while (!feof(f1)); // Конец цикла по считыванию текста из файла.
fclose(f1);
getch();
}

4. Доступ к функциональным клавишам

К функциональным (управляющим) клавишам относятся: Esc, F1,


F2, …, F12, Insert, Home, …, Page Down, управляющие курсором стрелки
(влево, вправо, вверх, вниз), Enter. Рассмотрим две возможности, которые
позволяют проанализировать, была ли при работе программы нажата
управляющая клавиша. При нажатии любой клавиши в устройство компьютера,
которое называется буфером клавиатуры, заносится двухбайтовое число.
Анализ буфера клавиатуры можно провести, рассматривая каждый байт по
отдельности либо интерпретируя значение двухбайтового числа как
расширенный код символа.
Анализ каждого байта буфера клавиатуры можно провести на
основании того, что значение старшего байта для функциональных символов
равно нулю, а функция getch() считывает из буфера информацию по байтам.
Здесь уместно заметить, что значение второго байта буфера равно одному из
кодов ASCII. Поэтому первый способ анализа, является ли клавиша
функциональной, заключается в проверке значения старшего байта буфера
клавиатуры. Если значение старшего байта равно нулю, значит, клавиша
функциональная. Идентифицировать функциональную клавишу можно по
значению младшего байта. Ниже приведена программа, которая определяет
ASCII-коды следующих функциональных клавиш: F1, F2, F3, F4, F5, F10,
Insert, Page Up, Page Down, End, Home, ↓ , ← , → .

 Языки программирования. Язык С. Конспект лекций -117-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
4. Доступ к функциональным клавишам

Программа 11. Программа определяет значения младшего байта кода


функциональных клавиш.
const n = 15;
void main(void)
{ int w, i;
// Программа идентифицирует по младшему байту кода.
char *sys[] ={ " F1", " F2", " F3",
" F4", " F5", " F10", " Insert",
" Page Up", " Page Down", " End",
" Home",
" Стрелку вверх", " Стрелку вниз", " Стрелку влево",
"Стрелку вправо"
};
clrscr();
for (i = 0; i < n; i++)
{
cout << "Нажми клавишу " << sys[i] << " "; // Вывод подсказки.
w=getch(); // Из буфера клавиатуры в переменную w вводим
// значение старшего байта.
if (w == 0) // Если значение старшего байта равно 0, то клавиша
// функциональная.
{ w = getch(); // Из буфера клавиатуры в переменную w вводим
// значение младшего байта.
// Вывод значения младшего байта кода функциональной
// клавиши. Идентификация клавиши по младшему байту кода.
cout << "Код клавиши " << sys[i] << "= " << w
<< " это символ " << (char) w << "\n";
}
else printf("Клавиша нефункциональная. \n");
}
getch(); }

Результат работы программы приведен ниже (ввод клавиши с


клавиатуры производился по подсказке):
Нажми F1 код клавиши F1= 59 это символ ;
Нажми F2 код клавиши F2= 60 это символ <
Нажми F3 код клавиши F3= 61 это символ =
Нажми F4 код клавиши F4= 62 это символ >
Нажми F5 код клавиши F5= 63 это символ ?
Нажми F10 код клавиши F10= 68 это символ D
Нажми Insert код клавиши Insert= 82 это символ R
Нажми Page Up код клавиши Page Up= 73 это символ I
Нажми Page Down код клавиши Page Down= 81 это символ Q
Нажми End код клавиши End= 79 это символ O

 Языки программирования. Язык С. Конспект лекций -118-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
4. Доступ к функциональным клавишам

Нажми Home код клавиш Home= 71 это символ G


Нажми Стрелку вверх код клавиши Стрелку вверх=72 это символ H
Нажми Стрелку вниз код клавиши Стрелку вниз= 80 это символ P
Нажми Стрелку влево код клавиши Стрелку влево=75 это символ K
НажмиСтрелку вправо код клавиши Стрелку вправо=77 это символ M

Интерпретировать значение двухбайтового числа как расширенный код


символа позволяет специальная функция доступа к буферу клавиатуры int
bioskey (int k). Эта функция содержится в файле bios.h. Параметр k может
принимать два значения: 0 и 1. Значение bioskey(1) принимает (возвращает)
значение 0, если буфер пуст. При нажатии любой клавиши в буфере ввода
клавиатуры появится код, и в этом случае bioskey(1) возвращает ненулевое
значение. Считать код из буфера позволяет функция bioskey(0). Эта функция
возвращает из буфера в вызываемую программу расширенный код символа.
Функция bioskey () позволяет использовать коды символов, которые
формируются из комбинаций клавиш:
Alt + <символ>;
Ctrl + <символ>;

Программа 12. Программа выдает расширенные коды функциональных


клавиш и коды комбинаций клавиш.
void main(void)
{ int key;
clrscr();
printf("Активизируй функциональные клавиши (Esc – выход из программы)\n");
do
{
while (bioskey(1) == 0); // bioskey(1) == 0 – клавиша не нажата.
key = bioskey(0); // Выбирает символ из буфера.
printf("\n Двухбайтовый код введенного символа: %d\n ", key);
}
while (key != 283); // 283 – код клавиши Esc.
}
В данной программе нажатие любой клавиши или комбинаций двух
клавиш выводит двухбайтовый код.
Двухбайтовые коды используются в нижеследующей программе, которая
демонстрирует возможность управления программой с использованием
функциональных клавиш.

Программа 13. Программа демонстрирует как можно использовать


функциональные клавиши.
void main(void)
{
int key;
clrscr();

 Языки программирования. Язык С. Конспект лекций -119-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
4. Доступ к функциональным клавишам

printf(" ВНИМАНИЕ!!!!! Esc – выход из программы\n\n");


printf("Программа демонстрирует схему управления "
" при помощи функциональных клавиш:\n\n"
"Enter Ins Tab Probel Esc Backspace Home F1 F2 F10 и др. \n\n");
do
{
key = bioskey(0); // Выбирает символ из буфера.
switch (key)
{
case 7181:
printf("Вы нажали клавишу Enter\n");
break;
case 21040:
printf("Вы нажали клавишу Ins\n");
break;
case 3849:
printf("Вы нажали клавишу Tab\n");
break;
case 14624:
printf("Вы нажали клавишу Probel\n");
break;
case 283:
printf("Вы нажали клавишу Esc\n");
break;
case 3592:
printf("Вы нажали клавишу Backspace\n");
break;
case 18176:
printf("Вы нажали клавишу Home\n");
break;
case 20224:
printf("Вы нажали клавишу End\n");
break;
case 18688:
printf("Вы нажали клавишу Page Up\n");
break;
case 20736:
printf("Вы нажали клавишу Page Down\n");
break;
case 19712:
printf("Вы нажали клавишу «Стрелка вправо»\n");
break;
case 19200:
printf("Вы нажали клавишу «Стрелка влево»\n");
break;

 Языки программирования. Язык С. Конспект лекций -120-


ЛЕКЦИЯ 12. СТРОКИ И МАССИВЫ СТРОК. ДОСТУП К ФУНКЦИОНАЛЬНЫМ КЛАВИШАМ
4. Доступ к функциональным клавишам

case 18432:
printf("Вы нажали клавишу «Стрелка вверх»\n");
break;
case 20480:
printf("Вы нажали клавишу «Стрелка вниз»\n");
break;
case 15104:
printf("Вы нажали клавишу F1\n");
break;
case 15360:
printf("Вы нажали клавишу F2\n");
break;
case 17408:
printf("Вы нажали клавишу F10\n");
break;
default:
printf("Клавиша с кодом %d в программе не используется \n", key);
break;
}
}
while (key != 283); // Код 283 соответствует клавише Esc.
}

Программа 13 фактически демонстрирует схему организации так


называемых горячих клавиш, которые широко используются в
информационных системах.

 Языки программирования. Язык С. Конспект лекций -121-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ
НА СТРУКТУРУ

План

1. Структуры.
2. Указатели на структуру.

1. Структуры

В реальных информационных задачах, которые, например, связаны с


обработкой базы данных, объекты характеризуются (или идентифицируются)
многими параметрами. Студент, больной, сотрудник учреждения,
техническое изделие, книги и т. д. представляют примеры подобных
объектов. В современных языках программирования существует
возможность, которая позволяет объединять под одним именем различные
характеристики и тем самым манипулировать значениями нескольких
переменных как единым целым. Такая возможность организации данных
заключена в структуре. Структура – это новый тип данных, который можно
создать при разработке программы. Структура характеризуется тем, что под
одним именем объединяются несколько переменных. Переменные, которые
объединены структурой, называются полями структуры.
Замечание 1. Вообще в алгоритмах любую организацию данных
называют структурой данных. Отсюда следует, что термин «структура
данных» имеет более широкий смысл, чем термин «структура». Поэтому
структура – это частный случай структуры данных. В языке С синтаксис
объявления структуры следующий:

struct <имя>
{
<тип 1 > <имя 1 >;
<тип 2 > <имя 2 >;

<тип k > <имя k >;
};

Здесь struct – ключевое (служебное) слово языка С; k – число полей в


структуре <имя>; <имя>, <имя 1 >, …,<имяk> – идентификаторы; <тип 1 >,
…, <типk> – типы переменных. При объявлении структуры определяется
(заказывается) размер памяти, которая необходима для размещения нового
объекта. Объявление структуры создает новый тип (вид) данных.
Идентификатор <имя> фактически начинает играть ту же роль, что и

 Языки программирования. Язык С. Конспект лекций -122-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
1. Структуры
ключевые слова int, float, char и т. д., которые определяют базовые типы
данных в языке C. Приведем пример объявления структуры для модели
автомобиля.
struct mod // Имя структуры (имя нового типа переменных).
{
char nam[50]; // Поле для марки модели.
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
char vid[50]; // Поле для вида кузова.
float ves; // Поле для веса автомобиля.
};

При таком объявлении декларируется новый тип переменных mod


размерностью 108 байт, а директива mod w; выделяет область памяти с
именем w, размер которой составляет 108 байт. Имена полей структуры в
этом случае следующие:
w.nam – имя области памяти поля структуры nam;
w.mos – имя области памяти поля структуры mos;
w.vel – имя области памяти поля структуры vel;
w.vid – имя области памяти поля структуры vid;
w.ves – имя области памяти поля структуры ves.

Программа strk_1. В программе вводится структура под именем mod.


Затем определяется переменная sp типа mod. Поля sp инициализируются при
ее объявлении. При выводе значений полей структуры на экран
демонстрируется форма доступа к полям.
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
void main()
{ struct mod // mod – имя структуры (имя нового типа переменных).
{
char nam[50]; // Поле для марки модели.
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
char vid[50]; // Поле для модели кузова.
float ves; // Поле для веса, т.
};
mod sp = { "форд", 75, 350, "sitr", 2.5}; // Инициализация полей структуры sp
// при объявлении переменной.
clrscr();

// Вывод полей структуры на экран.


cout << " Данные об автомобиле:\n";

 Языки программирования. Язык С. Конспект лекций -123-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
1. Структуры
cout << "Марка автомобиля – " << sp.nam << "\n";
cout << " Мощность – " << sp.mos << " л. с.\n";
cout << " Скорость – " << sp.vel << " км/ч.\n";
cout << " Модель кузова – " << sp.vid << "\n";
cout << " Вес – " << sp.ves << " т.\n\n";

// Вывод адресов полей структуры на экран.


cout << " Адреса полей:\n";
cout << "адрес переменной sp – " << (unsigned long) &sp << "\n";
cout << "адрес поля nam – " << (unsigned long) &sp.nam << "\n";
cout << "адрес поля mos – " << (unsigned long) &sp.mos << "\n";
cout << "адрес поля vel – " << (unsigned long) &sp.vel << "\n";
cout << "адрес поля vid – " << (unsigned long) &sp.vid << "\n";
cout << "адрес поля ves – " << (unsigned long) &sp.ves << "\n";
getch();
}

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


информация:
Данные об автомобиле:
Марка автомобиля – "Форд"
Мощность – 75 л. с.
Скорость – 350 км/ч.
Модель кузова – sitr
Вес – 2.500000 т.

Адреса полей:
адрес переменной sp – N1
адрес поля nam – N1
адрес поля mos – N2
адрес поля vel – N3
адрес поля vid – N4
адрес поля ves – N5
Здесь N1, N2, N3, N4, N5 – адреса полей переменной sp.
Замечание 2. Вывод адресов полей структуры дается в десятичной
системе исчисления. Легко заметить в этом случае, что поля структуры в
памяти непрерывно следуют друг за другом и адреса отличаются на число
байт, которое зарезервировано для текущего поля.

Программа strk_2. В программе вводится структура под именем mod.


Затем определяется переменная sp типа mod. Ввод данных в поля sp
производится с клавиатуры. При вводе и выводе данных на экран
демонстрируется форма доступа к полям структуры.

 Языки программирования. Язык С. Конспект лекций -124-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
1. Структуры
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
void main()
{
struct mod
{
char nam[50]; // Поле для марки модели.
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
float mas; // Поле для веса, т.
char vid[50]; // Поле для модели кузова.
int obm; // Поле для объема цилиндра, куб. дм.
float den; // Поле для цены, тыс. долл.
};
mod sp;
clrscr();
// Ввод данных об автомобиле с клавиатуры.
printf("Введи модель автомобиля: ");
gets(sp.nam);
printf("Введи мощность в л. с.: ");
scanf("%d", &sp.mos);
printf("Введи максимальную скорость в км/ч.: ");
scanf("%d",&sp.vel);
printf("Введи массу в т.: ");
scanf("%f",&sp.mas);
printf("Введи модель кузова: ");
scanf("%s",&sp.vid);
printf("Введи объем цилиндра в куб. дм.: ");
scanf("%d",&sp.obm);
printf("Введи стоимость автомобиля в тыс. долл.: ");
scanf("%f",&sp.den);
clrscr();

// Вывод информации об автомобиле.


printf("Сведения об автомобиле:\n");
printf("Марка – %s\n",sp.nam);
printf(" Мощность – %d л. с.\n",sp.mos);
printf(" Скорость – %d км/ч.\n",sp.vel);
printf(" Масса – %f\n",sp.mas);
printf(" Модель кузова – %s\n",sp.vid);
printf(" Объем цилиндра – %d куб. дм.\n",sp.obm);

 Языки программирования. Язык С. Конспект лекций -125-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
1. Структуры
printf(" Цена – %f тыс. долл.\n",sp.den);
getch();
}

После загрузки программы на экране будут выводиться запросы на


ввод данных с клавиатуры. Для ввода надо на клавиатуре набрать требуемую
информацию и нажать клавишу Enter. После завершения ввода на экране
появится введенная информация.
Если структура задана, то можно объявить массив структур. Если
воспользоваться структурой mod, которая демонстрируется в программах
strk_1 и strk_2, то код объявления массива имеет вид mod sp[n] (здесь
предполагается, что значение константы n известно). В этом случае
выделяется n областей памяти, каждая из которых равна размеру переменной
mod. Код доступа, например, к полю vel объекта с индексом i имеет вид
sp[i].vel. В программе strk_3 демонстрируется ввод данных в массив структур
из файла. При таком вводе надо определить форму записи начальных данных
в файл. В предлагаемом примере в файле каждое поле структуры занимает
строку. Элементы структуры отделены друг от друга пустой строкой.

Программа strk_3. В программе вводится структура под именем mod.


Затем определяется массив структур sp[n] типа mod. Ввод данных в поля
элемента массива sp[i] производится из файла с именем mas_avt.dat. При
вводе и выводе данных на экран демонстрируется форма доступа к полям
элементов массива структур. Структура файла с данными на диске
следующая: каждое значение поля занимает строку, между марками
автомобилей – пустая строка.
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
const n = 3; // n – размер массива.
void main()
{
struct mod
{
char nam[50]; // Поле для марки модели.
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
float mas; // Поле для веса, т.
char vid[50]; // Поле для модели кузова.
int obm; // Поле для объема цилиндра, куб. дм.
float den; // Поле для цены, тыс. долл.
};

 Языки программирования. Язык С. Конспект лекций -126-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
1. Структуры
mod sp[n];
FILE *f1;
int i;
clrscr();
f1 = fopen("mas_avt.dat","r");
for(i = 0; i < n; i++)
{
fscanf(f1,"%s\n",&sp[i].nam); // Ввод в поле sp[i].nam из файла.
fscanf(f1,"%d\n",&sp[i].mos); // Ввод в поле sp[i].mos из файла.
fscanf(f1,"%d\n",&sp[i].vel); // Ввод в поле sp[i].vel из файла.
fscanf(f1,"%f\n",&sp[i].mas); // Ввод в поле sp[i].mas из файла.
fscanf(f1,"%s\n",&sp[i].vid); // Ввод в поле sp[i].vid из файла.
fscanf(f1,"%d\n",&sp[i].obm); // Ввод в поле sp[i].obm из файла.
fscanf(f1,"%f\n",&sp[i].den); // Ввод в поле sp[i].den из файла.
fscanf(f1,"\n"); // Пропускает пустую строку между марками автомобилей.
}
fclose(f1);
// Вывод информации о марках автомобилей.
for (i = 0; i < n; i++)
{
printf("модель – %s\n", sp[i].nam);
printf(" Мощность – %d л. с.\n", sp[i].mos);
printf(" Скорость – %d км/ч.\n", sp[i].vel);
printf(" Масса – %f т.\n", sp[i].mas);
printf(" Вид кузова – %s\n", sp[i].vid);
printf(" Объем цилиндра – %d куб. дм.\n", sp[i].obm);
printf(" Цена – %f тыс. долл.\n", sp[i].den);
}
getch(); }

После работы программы на экране появится информация об


автомобилях, которая была введена из файла.

2. Указатели на структуру

Они объявляются точно так же, как и указатели для других


переменных. Для структуры mod (см. программы strk_1, strk_2, strk_3) код
mod *p определяет область памяти размером два байта с именем p для
адресов переменных типа mod. Это значит, что если переменные sp и p
являются соответственно структурой и указателем на структуру одного типа
(например типа mod), то директива p = &sp переменной p присваивает адрес
sp. Если в p находится адрес sp, то доступ к полям структуры реализуется

 Языки программирования. Язык С. Конспект лекций -127-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
2. Указатели на структуру
конструкцией p -> <имя i >, где <имя i > – идентификатор поля структуры.
Для ранее определенной структуры mod имеем:

p -> nam – доступ к полю nam структуры sp;


p ->mos – доступ к полю mos структуры sp;
p ->vel – доступ к полю vel структуры sp;
p ->mas – доступ к полю mas структуры sp;
p ->vid – доступ к полю vid структуры sp;
p ->obm – доступ к полю obm структуры sp;
p ->den – доступ к полю ves структуры sp.

Фактически p -> nam, p ->mos , …, p ->den можно трактовать как


имена областей памяти соответствующих полей структуры sp. С этой точки
зрения запись p -> nam, p ->mos, …, p ->den идентична записи sp.nam,
sp.mos, …, sp.den.

Программа strk_4. Определяет указатель на структуру и демонстрирует


доступ к полям структуры через указатель. Выдаются адреса полей
структуры с использованием указателя.
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
void main()
{
struct mod // Имя структуры (имя нового типа переменных).
{
char nam[50]; // Поле для марки модели.
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
char vid[50]; // Поле для модели кузова.
float ves; // Поле для веса, т.
};

mod sp = { "форд", 75, 350, "sitr", 2.5};


clrscr();
mod *p;
clrscr();
printf(" Сведения об автомобиле (доступ через имя переменной):\n");
printf("Марка модели – %s\n",sp.nam);
printf(" Мощность – %d л. с.\n", sp.mos);
printf(" Скорость – %d км/ч.\n", sp.vel);
printf(" Вид кузова – %s\n", sp.vid);

 Языки программирования. Язык С. Конспект лекций -128-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
2. Указатели на структуру
printf(" Вес модели – %f т.\n", sp.ves);
p = &sp; // В указатель типа mod засылаем адрес структуры sp.
printf(" Сведения об автомобиле (доступ через указатель):\n");
printf("Марка модели – %s\n", p->nam); // Вывод данного из поля sp.nam.
printf(" Мощность – %d л. с.\n", p->mos); // Вывод данного из поля sp.mos.
printf(" Скорость – %d км/ч.\n", p->vel); // Вывод данного из поля sp.vel.
printf(" Вид кузова – %s\n", p->vid); // Вывод данного из поля sp.vid.
printf(" Вес модели – %f т.\n", p->ves); // Вывод данного из поля sp.ves.
getch();
cout << " Адреса полей:\n";
cout << "\nAдрес поля nam- " << (unsigned long) &sp.nam << " "
<< (unsigned long) &p ->nam << "\n"; // Вывод адреса поля sp.nam.
cout << "Aдрес поля nam- " << (unsigned long) &sp.mos << " "
<< (unsigned long) &p ->mos << "\n"; // Вывод адреса поля sp.mos.
cout << "Aдрес поля nam- " << (unsigned long) &sp.vel << " "
<< (unsigned long) &p ->vel << "\n"; // Вывод адреса поля sp.vel.
cout << "Aдрес поля nam- " << (unsigned long) &sp.vid << " "
<< (unsigned long) &p ->vid << "\n"; // Вывод адреса поля sp.vid.
cout << "Aдрес поля nam- " << (unsigned long) &sp.ves << " "
<< (unsigned long) &p ->ves << "\n"; // Вывод адреса поля sp.ves.
getch();}

После работы программы на экране появится информация об


автомобилях, введенная из файла, и адреса, с которых начинаются поля этих
структур.
Для определенных в программе структур можно объявить переменную
для адресов указателей. Такая переменная называется указателем на
указатель. Так, для ранее определенной структуры mod директива mod **p1
выделяет область памяти с именем p1 для адресов указателей типа mod.
В программе strk_5 объявляется указатель на указатель структуры типа mod
и демонстрируются способы доступа к полям структуры через указатель на
указатель.

Программа strk_5. Демонстрирует технику использования указателя


на указатель.
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
void main()
{ struct mod // Имя структуры (имя нового типа переменных).
{
char nam[50]; // Поле для марки модели.

 Языки программирования. Язык С. Конспект лекций -129-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
2. Указатели на структуру
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
char vid[50]; // Поле для модели кузова.
float ves; // Поле для веса, т.
};
mod sp = { "форд", 75, 350, "sitr", 2.5};
clrscr();
mod *p;
mod **p1;
clrscr();
// Для вывода информации используется имя переменной sp.
printf(" Сведения об автомобиле (доступ через имя переменной): \n");
printf("Марка модели – %s\n", sp.nam);
printf(" Мощность – %d л. с.\n", sp.mos);
printf(" Скорость – %d км/ч.\n", sp.vel);
printf(" Вид кузова – %s\n", sp.vid);
printf(" Вес модели – %f т.\n", sp.ves);
getch();
// Для вывода информации используется указатель p на структуру sp.
// В p находится адрес на структуру sp.
p = &sp; // Адрес области памяти структуры sp пересылаем в указатель p.
printf(" Сведения об автомобиле (доступ через указатель):\n");
printf("Марка модели – %s\n", p->nam); // Вывод данного из поля sp.nam.
printf(" Мощность – %d л. с.\n", p->mos); // Вывод данного из поля sp.mos.
printf(" Скорость – %d км/ч.\n", p->vel); // Вывод данного из поля sp.vel.
printf(" Вид кузова – %s\n", p->vid); // Вывод данного из поля sp.vid.
printf(" Вес модели – %f т.\n", p->ves); // Вывод данного из поля sp.ves.
getch();

// Для вывода используется указатель p1 на указатель p.


// В p1 находится адрес указателя p,
// а в p находится адрес на структуру sp.
p1 = &p; // Адрес указателя p пересылаем в указатель на указатель p1.
printf(" Сведения об автомобиле (через **p1 ):\n");
printf("Модель – %s\n", (**p1).nam); // Вывод из поля sp.nam.
printf(" Мощность – %d л. с.\n", (**p1).mos); // Вывод из поля sp.mos.
printf(" Скорость – %d км/ч.\n", (**p1).vel); // Вывод из поля sp.nam.
printf(" Вид кузова – %s\n", (**p1).vid); // Вывод данного из поля sp.vid.
printf(" Вес модели – %f т.\n", (**p1).ves); // Вывод данного из поля sp.ves.
getch();
// Для вывода используется значение адреса, который хранится в p1.
printf(" Сведения об автомобиле (доступ через указатель (*p1):\n");
printf("Марка модели – %s\n", (*p1)->nam); // Вывод из поля sp.nam.

 Языки программирования. Язык С. Конспект лекций -130-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
2. Указатели на структуру
printf("Мощность – %d л. с.\n", (*p1)->mos); // Вывод из поля sp.mos.
printf("Скорость – %d км/ч.\n", (*p1)->vel); // Вывод данного из поля sp.vel.
printf(" Вид кузова – %s\n", (*p1)->vid); // Вывод данного из поля sp.vid.
printf(" Вес модели – %f т.\n",(*p1)->ves); // Вывод из поля sp.ves.
getch(); }

В результате работы программы на экране появится информация об ав-


томобиле, которая была введена при объявлении переменных. Одна и та же
информация будет выведена четыре раза. Способы вывода (доступа)
информации даны в комментариях программы. Заметим, что при объявлении
переменных mod *p, mod **p1 и директив p = &sp и p1= &p коды

sp.nam, sp.mos, …, sp.den;


p -> nam, p ->mos , …, p ->den;
(**p1).nam, (**p1).mos, …, (**p1).den;
(*p1) -> nam, (*p1) -> mos, …, (*p1) -> den;

определяют одни и те же области памяти.


Переменная p предназначена для адресов переменных типа
«структура» (в данном случае типа mod), а p1 – для адресов указателей типа
«структура» (в данном случае типа mod). Если из программы исключить
директивы p = &sp и p1= &p, то случайные значения в p и p1 программа
будет интерпретировать как начальные адреса тех типов переменных, для
которых, собственно говоря, они предназначены. В этом случае
предоставляется доступ к областям памяти, не контролируемым программой.
Использовать эту память нельзя, так как это может в процессе работы
программы привести к серьёзным сбоям и ошибкам. Язык не выявляет такие
ошибки. Поэтому при использовании указателей следует внимательно
отслеживать подобную ситуацию. Для этого надо корректно определять
значение указателей и контролировать те области памяти, на которые
ссылается указатель.
Приведем пример неправильной программы strk_5a. Она демонстрирует
доступ к областям памяти, которые не контролируются программой.
Программа strk_5a идентична программе strk_5. Разница состоит только в
том, что в strk_5a используется доступ к областям памяти, которые не
зарезервированы программой. Выводимая на экран информация из полей
структуры случайная, но ее можно задать тем или иным способом.
Программа strk_5a. Демонстрирует возможность использовать через
указатели память, которая находится не под контролем программы.
#include <iostream.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>

 Языки программирования. Язык С. Конспект лекций -131-


ЛЕКЦИЯ 13. СТРУКТУРЫ И УКАЗАТЕЛИ НА СТРУКТУРУ
2. Указатели на структуру
void main()
{ struct mod // Имя структуры (имя нового типа переменных).
{
char nam[50]; // Поле для марки модели.
int mos; // Поле для мощности, л. с.
int vel; // Поле для скорости, км/ч.
char vid[50]; // Поле для модели кузова.
float ves; // Поле для веса, т.
};
clrscr();
mod *p;
mod **p1;
clrscr();
cout << " Случайное значение адреса в указателе p= " << p << "\n";
cout << "Случайные значения полей структуры, которая начинается "
<< "с адреса= " << p << "\n";
printf("Модель – %s\n", p->nam);
printf(" Мощность – %d л. с.\n", p->mos);
printf(" Скорость – %d км/ч.\n", p->vel);
printf(" Вид кузова – %s\n", p->vid);
printf(" Вес модели – %f т.\n", p->ves);
getch();
cout << " Случайное значение адреса указателя в p1= " << p1 << "\n";
cout << " Случайное значение адреса в указателе *p1= " << *p1 << "\n";

printf(" Сведения об автомобиле (через **p1 ):\n");


printf("Модель – %s\n", (**p1).nam);
printf(" Мощность – %d л. с.\n", (**p1). mos);
printf(" Скорость – %d км/ч.\n", (**p1).vel);
printf(" Вид кузова – %s\n", (**p1).vid);
printf(" Вес модели – %f т.\n", (**p1).ves);
getch();

printf(" Сведения об автомобиле (через (*p1 )-> :\n");


printf("Модель – %s\n", (*p1) -> nam);
printf(" Мощность – %d л. с.\n", (*p1) -> mos);
printf(" Скорость – %d км/ч.\n", (*p1) -> vel);
printf(" Вид кузова – %s\n", (*p1)->vid);
printf(" Вес модели – %f т.\n", (*p1)->ves);
getch(); }

 Языки программирования. Язык С. Конспект лекций -132-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС.
УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ
И ТЕКСТОВОМ РЕЖИМАХ

План
1. Интерфейс пользователя.
2. Графический интерфейс пользователя.
3. Оконный интерфейс.
4. Текстовый режим.
5. Графический режим.

1. Интерфейс пользователя
Представляет собой совокупность средств, при помощи которых
пользователь общается с различными устройствами, чаще всего с
компьютером или бытовой техникой. Интерфейс пользователя
компьютерного приложения включает:
средства отображения информации, отображаемую информацию,
форматы и коды;
командные режимы, язык «пользователь – интерфейс»;
устройства и технологии ввода данных;
диалоги и взаимодействие между пользователем и компьютером,
обратную связь с пользователем;
поддержку принятия решений в конкретной предметной области;
порядок использования программы и документацию на нее.

2. Графический интерфейс пользователя


ГИП, англ. graphical user interface, GUI. ГИП есть система средств для
взаимодействия пользователя с компьютером, основанная на представлении
всех доступных пользователю системных объектов и функций в виде
графических компонентов экрана (окон, значков, меню, кнопок, списков
и т. п.). Впервые концепция ГИП была предложена учеными из
исследовательской лаборатории Xerox PARC в 1970 г. Коммерческое
воплощение получила в продуктах корпорации Apple Computer.

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

 Языки программирования. Язык С. Конспект лекций -133-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС. УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ РЕЖИМАХ
3. Оконный интерфейс

оконного интерфейса является графический режим, основные его элементы


применимы и в текстовом режиме. Процедуры поддержки оконного
интерфейса призваны отрисовывать экран c располагающимися «поверх
него» окнами и разделять ввод пользователя между ними (при
существовании нескольких равноправных окон ввод пользователя
осуществляется в то, которое из них в данный момент является активным).
Окно обычно имеет прямоугольную форму с обрамлением и/или цветом
фона, отличным от цвета основного экрана. При необходимости окно имеет
заголовок (с пояснением функции) и органы управления. Иногда
используются различные эффекты для придания ощущения объемности
интерфейса, в том числе:
тени – затемнение под окном со сдвигом (как правило, вправо вниз,
предполагая наличие света слева (сверху);
создание иллюзии выпуклых и вдавленных структур;
полная или частичная прозрачность окна (применимо только в
графическом режиме).
Для хорошего оформления диалога пользователя с компьютером
(программой) необходимо умение управления работой экрана. Пакет
функций управления экраном делится на две части. Первая поддерживает
текстовый режим (text mode) работы. Вторая часть обеспечивает работу
экрана в графическом режиме (graphics mode).

4. Текстовый режим

Библиотека функций для работы в текстовом режиме conio.h. Все


функции управления экраном в текстовом режиме имеют прототипы в
заголовочном файле conio.h. Там же находятся некоторые константы и
макросы, используемые этими функциями. Большинство подпрограмм
связано с окнами (Windows), а не со всем экраном. Окно – это прямоугольная
область экрана, которую программа использует для выдачи сообщений.
Borland C++ позволяет устанавливать размер и местоположение окна на
экране. После задания окна все сообщения помещаются в этом окне.
Функция clrscr() очищает активное окно. Функция для задания размера и
местоположения окна имеет прототип

void windows(int left, int top, int right, int bottom);

где int left, int top и int right, int bottom – координаты левого верхнего и
правого нижнего угла окна соответственно. Задание окна void windows(1,1,
80, 25); означает весь экран. Если окно не задавать, то оно всегда имеет
размер windows(1,1, 80, 25). В следующем примере в заданное окно начиная с
пятой позиции по горизонтали зелеными буквами на красном фоне
выводится текст:
#include <conio.h>
int main(void){

 Языки программирования. Язык С. Конспект лекций -134-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС. УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ РЕЖИМАХ
4. Текстовый режим

textbackground(0);
clrscr();
window(50,11,70,13);
textcolor(2);
textbackground(4);
gotoxy(5, 1)
cprintf("Это тест\r\n");
getch();
return 0;
}
Функция позиционирования курсора имеет прототип void gotoxy(int x, int y).

5. Графический режим
Предназначен для вывода на экран графиков, рисунков и др. В этом
режиме экран монитора представляет собой множество точек (пикселов).
Инициализация графики. В состав графического пакета входят:
заголовочный файл graphics.h,
библиотечный файл graphics.lib,
драйверы графических устройств (*.bgi),
шрифты (*.chr).
Управление экраном в графическом режиме производится с помощью
набора функций, прототипы которых находятся в заголовочном файле
graphics.h. Для работы в графическом режиме файл graphics.h должен быть
подключен с помощью директивы #include. Перед использованием
графических функций необходимо переключить видеоадаптер в графический
режим (по умолчанию он находится в текстовом режиме). Для
инициализации графического режима предназначена функция initgraph(). Ее
прототип имеет вид

void initgraph(int *driver, int *mode, char *path);

где int *driver – тип подключаемого драйвера, int *mode – режим работы под-
ключенного драйвера, char *path – местоположение драйвера.
Функция initgraph() считывает в память указанный драйвер,
устанавливает видеорежим, соответствующий аргументу mode, и определяет
маршрут к директории, в которой находится файл *.bgi (драйвер). Если
маршрут не указан, то предполагается, что этот файл расположен в текущей
директории. В дальнейшем будем полагать, что драйвер находится в
текущей директории. При использовании initgraph() можно указать или
конкретный драйвер (например, egavga.bgi), или задать автоматическое
определение типа видеоадаптера и выбора соответствующего драйвера. Это
позволяет без изменения переносить программы на компьютеры с другими
видеоадаптерами. Например,

int grdrv=DETECT, grmod;

 Языки программирования. Язык С. Конспект лекций -135-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС. УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ РЕЖИМАХ
5. Графический режим

initgraph(&grdrv,&grmod,” ”);

Для выхода из графического режима используется функция

void closegraph(void);

Функция closegraph() устанавливает текстовый режим, который был до


вызова функции initgraph(), при этом происходит очистка экрана.
Палитра. В файле graphics.h определены константы, соответствующие
цветам стандартной палитры. Это соответствие приведено ниже.

BLACK Черный 0
BLUE Синий 1
GREEN Зеленый 2
CYAN Циановый 3
RED Красный 4
MAGENTA Малиновый 5
BROWN Коричневый 6
LIGHTGRAY Светлый серый 7
DARKGRAY Темный серый 8
LIGHTBLUE Голубой 9
LIGHTGREEN Светлый зеленый 10
LIGHTCYAN Светлый циановый 11
LIGHTRED Светлый красный 12
LIGHTMAGENTA Светлый малиновый 13
YELLOW Желтый 14
WHITE Белый 15

Изменение одного из цветов стандартной палитры производится функцией

void setpalette(int index, int color);

где int index – номер из палитры, а int color – цвет (для палитры EGA
диапазон от 0 до 63). Настройка палитры EGA осуществляется функцией

void setrgbpalette(int color, int red, int green, int blue);

где red, green и blue изменяются в диапазоне от 0 до 255. Малым значениям


red, green и blue соответствуют темные цвета, большим – более яркие. Если
red, green и blue имеют одинаковые значения, то формируется один из
оттенков серого цвета.
Точка экрана. Графический экран представляет собой массив
пикселей. Каждый пиксель соответствует одной точке на экране и может
иметь свой цвет. Установить цвет пикселя в точке экрана с координатами
(x,y) можно с помощью следующей функции:

 Языки программирования. Язык С. Конспект лекций -136-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС. УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ РЕЖИМАХ
5. Графический режим

void putpixel(int x, int y, int color);

Определить цвет точки с координатами (x, y) можно с помощью функции

unsigned getpixel(int x, int y);


Контурные фигуры. Группа линий на плоскости образует контурную
фигуру. Типичными представителями контурных фигур можно считать
отрезок прямой линии, дугу, окружность, прямоугольник, эллипс и т. д.
Кроме формы одна фигура от другой может отличаться цветом линии, ее
толщиной или типом. По умолчанию в графическом режиме существуют
следующие настройки:
текущий цвет контура – WHITE (белый);
толщина – один пиксель;
тип – сплошная линия.
Цвет рисования задается функцией

void setcolor(int color);

Остальные параметры контуров устанавливаются функцией

void setlinestyle(int linestyle, unsigned upattern, int thickness);

где linestyle – тип линии, а thickness – ее толщина.

Линия могут быть пяти типов

0 SOLID_LINE сплошная
1 DOTTED_LINE пунктирная
2 CENTER_LINE штрихпунктирная
3 DASHED_LINE штриховая
4 USERBIT_LINE задается пользователем

Для отображения наиболее часто используемых фигур можно


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

void line(int x1, int y1, int x2, int y2);

рисует на экране прямую линию от точки с координатами (x1, y1) до точки с


координатами (x2, y2). Функция

void rectangle(int left, int top, int right, int bottom);


рисует прямоугольник с координатами левого верхнего угла (left, top) и
правого нижнего (right, bottom). Функция

 Языки программирования. Язык С. Конспект лекций -137-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС. УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ РЕЖИМАХ
5. Графический режим

void circle(int x, int y, int radius);

рисует окружность с центром в точке (x, y) и радиусом radius. Функция

void arc(int x, int y, int stangle, int endangle, int radius);

рисует дугу с центром в точке (x,y) и радиусом radius.


Параметры stangle и endangle задают круговые координаты начальной
и конечной точек. Угол измеряется в градусах и отсчитывается против
часовой стрелки. При stangle = 0, endangle= 360, функция arc() рисует
полную окружность.
Плоскостные фигуры. Плоскостные фигуры получаются из контурных
закрашиванием области внутри или вне замкнутой линии, образующей
контур. Линия при этом должна быть сплошной. Данную операцию можно
выполнить при помощи функции

void floodfill(int x, int y, int border);

где x и y – координаты точки, расположенной внутри или вне контура,


соответственно (для заливки той или иной области); border – цвет линии,
образующей контур. Цвет всего контура должен быть одинаковым. Раскраска
осуществляется цветом color по шаблону pattern, которые устанавливаются
функцией

void setfillstyle(int pattern, int color);

Для наиболее часто встречающихся на практике фигур в графической


библиотеке языка существуют уже готовые функции. Функция

void bar(int left, int top, int right, int bottom);

рисует закрашенный прямоугольник. Прямоугольник закрашивается текущим


цветом с использованием текущего шаблона заполнения. Функция

void fillellipse(int x, int y, int rx, int ry);

рисует эллипс с центром в точке (x, y), горизонтальной и вертикальной осями


rx и ry соответственно и заливает его текущим цветом. Функция

void fillepoly(int num, int *pol);

рисует контур многоугольника, имеющий num точек, а затем закрашивает


его. Pol – массив целых чисел. Каждая пара чисел (x, y) является
координатами вершины многоугольника.

 Языки программирования. Язык С. Конспект лекций -138-


ЛЕКЦИЯ 14. ГРАФИЧЕСКИЙ ИНТЕРФЕЙС. УПРАВЛЕНИЕ В ГРАФИЧЕСКОМ И ТЕКСТОВОМ РЕЖИМАХ
5. Графический режим

Вывод текста в графическом режиме можно осуществить с


использованием функции outtextxy(). Функция

void outtextxy(int x, int y, char *textstring);

выводит строку текста textstring начиная с позиции (x, y). Сформировать


строку textstring необходимо заранее.
Текстовая информация отображается на экране с учетом параметров:
цвет, тип шрифта, размер шрифта и направление. Эти параметры задаются
функциями:

void setcolor(int color);


void settextstyle(int font, int direction, int charsize);

где color – цвет текста, font – тип шрифта, direction – направление вывода
текстовой информации, charsize – множитель, влияющий на размер.
Работа с частями экрана. Довольно часто при работе с графикой
возникает ситуация, когда фрагмент изображения необходимо передвинуть в
другое место на экране. Для этих целей можно применять функции getimage()
и putimage(). Функция

void getimage(int left, int top, int right, int bottom, void *bitmap);

копирует образ с экрана в оперативную память, параметры left, top, right и


bottom определяют область экрана прямоугольной формы, которая будет
скопирована. Параметр bitmap указывает на область в памяти, куда
записывается битовый образ. Функция

void far putimage(int left, int top, void far *bitmap, int op);

помещает ранее сохраненный битовый образ на экран. Левый верхний угол


нового места фрагмента на экране имеет координаты (left, top). Значение
bitmap есть указатель на область памяти, где хранился образ. Параметр op
задает режим вывода на экран:

0 COPY_PUT копия
1 XOR_PUT исключающее «или»
2 OR_PUT «или»
3 AND_PUT «и»
4 NOT_PUT копия источника с инверсией

 Языки программирования. Язык С. Конспект лекций -139-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ.
ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ
В ФУНКЦИИ

План
1. Функции.
2. Локальные и глобальные переменные.
3. Область действия функции.
4. Передача параметров в функцию.
5. Передача массивов в функцию.

1. Функции

При решении разных задач очень часто при обработке информации


используются одни и те же алгоритмы (например, сортировка массива,
определение максимального элемента, вычисление интеграла, определение
корня уравнения, перемножение матриц и т. д.). Для того чтобы каждый раз
не составлять программу для алгоритма, который часто используется при
решении разных задач, эту программу оформляют в виде автономной
программы или функции. Функцию можно использовать в дальнейшем и в
независимых друг от друга программах, а также в одной программе
несколько раз. Для выполнения функции достаточно записать директиву, в
которую входит имя функции со списком фактических параметров.
Функция – это автономная программа с именем. Она имеет следующий
общий вид:
<тип> <имя> (<список параметров>)
{
тело функции
}
Здесь <тип> определяет тип значения, которое возвращает функция.
Функция, которая не возвращает значение, имеет тип void. Значение, которое
возвращается функцией, называется результатом. Функция может
возвращать только одно значение, но любого типа данных. Если функция
возвращает значение, то в теле функции обязательно присутствует директива
return <выражение>. В качестве выражения может быть имя одной
переменной, тип которой соответствует типу возвращаемого значения;
<имя> – идентификатор, который определяет пользователь;
<список параметров> – это список элементов, которые отделяются
друг от друга запятой. Каждый такой элемент состоит из имени переменной
и ее типа. В общем случае список параметров имеет вид (<тип1> <имя1>,
<тип2> <имя2>, …,<типk> <имяk>). В список параметров включаются

 Языки программирования. Язык С. Конспект лекций -140-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
1. Функции

переменные, которые функция использует при своей работе. Эти параметры


называются формальными параметрами функции. Функция может быть
и без параметров.
Тело функции – это программа, структура которой полностью
соответствует структуре головной программы void main().
Ниже приведен пример функции, которая выводит на экран текст
«Доброе утро, СТУДЕНТ!!!»

Программа fun_1
void vivod()
{ cout << “Доброе утро, СТУДЕНТ !!!\n”; }

Для того чтобы функция заработала, ее надо вызвать из какой-либо


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

Программа fun_2
void vivod()
{
cout << “Доброе утро, СТУДЕНТ !!!\n”;
}
void main()
{ clrscr();
vivod(); // Вызов функции vivod().
getch(); }

После выполнения этой программы на экране монитора появится


сообщение:

Доброе утро, СТУДЕНТ !!!

Функция, которая вычисляет и возвращает произведение двух чисел,


может иметь вид, представленный в следующих программах.

Программа fun_3
float ymn(float x, float y)
{ float z;
z = y*x;
return z; }

Программа fun_3а
float ymn(float x, float y)
{ return x*y; }

 Языки программирования. Язык С. Конспект лекций -141-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
1. Функции

Замечание 1. Строку <тип> <имя> (<список параметров>) обычно


называют заголовком функции. Считается хорошим стилем при оформлении
программы, когда код функции следует за кодом вызывающей программы.
Замечание 2. Если код функции расположен ниже кода вызывающей
программы, то функция должна быть объявлена перед ней. Для этого
заголовок функции должен предшествовать заголовку программы. Код
заголовка, который записывается перед вызывающей программой,
называется прототипом функции. Код прототипа функции заканчивается
символом ; (точка с запятой).

Программа fun_4
float ymn(float a, float b); // Прототип функции.
void main()
{ int x=2, y=5;
cout << x << “*“ << y << “= “ << ymn(x,y) << “\n”;
getch();
}

float ymn(float z, float w) // Функция вычисляет произведение двух чисел.


{
return z*w;
}

Имена параметров в прототипе могут не совпадать с именами, которые


используются в коде функции.

2. Локальные и глобальные переменные

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


на две группы: локальные и глобальные. Локальные переменные – это
переменные, которые определяются внутри функции и используются только
внутри функции.
В языке С локальные переменные можно определять внутри блока программы.
Блок программы – это набор директив (в том числе и описание переменных),
которые заключены в фигурные скобки. Локальные переменные создаются
при входе в блок и ликвидируются при выходе. Переменные, которые
объявлены в одном блоке, не имеют никакого отношения к переменным
другого блока даже в случае совпадения имен. Например:

void main()
{

// 1-й блок.

 Языки программирования. Язык С. Конспект лекций -142-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
2. Локальные и глобальные переменные

{
int x, y, z;
...
}
// 2-й блок.
{
int x, y, z;
...
}
}

Переменные x, y, z первого блока никак не связаны с переменными x, y,


z второго блока. Это в принципе разные области памяти. Параметры,
которые перечисляются в заголовке функции, относятся к локальным
переменным.
Глобальные переменные – это те переменные, которые объявляются вне
функции (перед заголовком функции или блока программы). Глобальные
переменные можно использовать в любой директиве, независимо от того,
в какой функции или в каком блоке эта директива используется.

Программа fun_5
float ymn(float x, float y); // Прототип функции ymn().
int w= -10;
void main()
{ cout << “До входа в 1-й блок w= “ << w << “\n”;

// 1-й блок.
{ int x = 2, y = 5, z = 3;
w = x * y + z;
}
cout << “После выхода из 1-го блока w= “ << w << “\n”;
// 2-й блок.
{ int x = 5, y = 7, z = 2;
w= (x + y) * z;
}
cout << << “После выхода из 2-го блока w = “ << w << “\n”;
cout << “После обращения к функции ymn = “ << ymn(10,10) << “\n”;
cout << “После обращения к функции w = “ << w << “\n”;
getch(); }
float ymn(float x, float y) // Функция определяет произведение 2-х чисел.
{ float z;
z = y * x + w;
w = 500;
return z; }

 Языки программирования. Язык С. Конспект лекций -143-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
2. Локальные и глобальные переменные

После выполнения программы на экране появится следующая


информация:
До входа в 1-й блок w = -10
После выхода из 1-го блока w = 13
После выхода из 2-го блока w = 70
После обращения к функции ymn = 170
После обращения к функции w = 500

Здесь следует обратить внимание на то, что в головной программе и в


функции ymn() переменная w не объявляется, тем не менее и в директивах
головной программы, и в директивах функции переменная w используется.
Замечание 3. Локальные переменные имеют приоритет перед
глобальными переменными. Это означает следующее. Если имена
глобальной и локальной переменных совпали, то эти переменные между
собой не связаны и определяют разные области памяти. Для вызова функции
(как уже отмечалось выше) достаточно там, где это необходимо, записать
директиву, в которую входит имя функции со списком параметров.
Например, директива

cout << “2.5*2= “<< ymn(2.5,2);

выводит на экран результат умножения числа 2.5 на 2:

2.5*2= 5

Директива

r = 7*ymn(3.5,2);

в переменную r засылает значение умножения числа 7 на результат, который


вычисляется функцией ymn() для чисел 3.5 и 2.

3. Область действия функции

Каждая функция, в том числе и головная программа main(),


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

 Языки программирования. Язык С. Конспект лекций -144-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
3. Область действия функции

локальных переменных не сохраняется между вызовами функции.


Исключения составляют глобальные переменные и переменные, которые
объявляются как static. При использовании глобальных параметров надо
иметь в виду следующее. В случае совпадения имен глобальных и локальных
переменных приоритет имеют локальные. Область действия функции в этом
случае не распространяется на те глобальные переменные, имена которых
совпадают с именами локальных переменных.

4. Передача параметров в функцию

В языке С имеется три способа передачи параметров в функцию.


Первый способ – это передача параметров по значению. Если
в некоторую функцию надо передавать параметр через значение, то формат
заголовка функции должен быть следующим:

<тип> <имя> ( <тип> <имя>)

Например, пусть функция имеет заголовок void f_1(int x). В этом случае
передача параметра в тело данной функции будет происходить по значению,
а именно: информация из памяти вызывающей программы пересылается
(переписывается, копируется) в память функции. Этот способ передачи
параметров схематично демонстрируется на рис. 15.1, где A – адрес области
памяти, находящейся под контролем программы, из которой функция
вызывается; B – адрес области памяти под контролем вызываемой функции.
При таком способе передачи надо иметь в виду, что вызываемая функция
не может изменить информацию в области памяти A.

A B
х x
Число x из А пересылается в B

Рис. 15.1

Программа fun_6. Передача параметра в функцию по значению.


void f_1(int x);
void main()
{ int y = 2;
cout << “Значение переменной до вызова функции y = ” << y
<< “\n”;
f_1(y);
cout << “Вышли из функции. \n\n”;
cout << “Значение переменной после вызова функции y = “ << y
<< “\n”;
}
void f_1(int x)

 Языки программирования. Язык С. Конспект лекций -145-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
4. Передача параметров в функцию

{ cout << “Выполняется функция. \n\n”;


cout << “В функцию передана переменная x = “ << x << “\n”;
x = 10;
cout << “После преобразования переменной в функции x = “ << x << “\n”; }

Функция f_1() переменной x присваивает значение 10. При этом в


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

Значение переменной до вызова функции y = 2;


Выполняется функция
В функцию передана переменная x = 2;
После преобразования переменной в функции x = 10;
Вышли из функции
Значение переменной после вызова функции y = 2;

Из трассировки программы видно, что хотя значение локального


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

<тип> <имя> ( <тип> *<имя>)

Например, пусть функция имеет заголовок void f_1(int *x). Локальным


параметром в функции в этом случае является указатель, в который из
вызывающей программы пересылается адрес переменной. Данный способ
передачи параметра в тело функции называется передачей параметра по
указателю. Он позволяет из функции изменять информацию в памяти
вызывающей программы. Схема передачи параметра по указателю
представлена на рис. 15.2.

A B
x А Адрес А пересылается в указатель B A

Рис. 15.2

Программа fun_7. Передача параметра по указателю.


void f_1(int *x);
void main()
{
int y = 2;
cout << “Адрес переменной y = ” << &y << “\n”;

 Языки программирования. Язык С. Конспект лекций -146-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
4. Передача параметров в функцию

cout << “Значение переменной y до вызова функции y = ” << y << “\n”;


f_1( &y); // В функцию пересылаем адрес переменной y.
cout << “Вышли из функции. \n\n”;
cout << “Значение переменной y после вызова функции y = ” << y << “\n”;
}
void f_1(int *x) // x – переменная для адреса.
{
cout << “Выполняется функция. \n\n”;
cout << “В указатель x передан адрес переменной y. x = ” << x << “\n”;
// Выводится число из адреса, который содержится в x.
cout << “В этом адресе число = “ << *x << “\n”;
*x=10; // Число 10 засылается в адрес, который
// содержится в x.
cout << “После преобразования переменной в функции x = “ << x << “\n”;
}

Функция f_1() переменной, адрес которой записан в указателе x,


присваивает значение 10. Так как при вызове функции f_1() из головной
программы в указатель x пересылается адрес переменной y, то тем самым
значение 10 присваивается y из головной программы. Результат работы
данной программы будет следующий.

Адрес переменной y = Ny
Значение переменной y до вызова функции y = 2
Выполняется функция
В указатель x передан адрес переменной y. x = Ny
В этом адресе число = 2;
После преобразования переменной в функции x = 10;
Вышли из функции
Значение переменной y после вызова функции y = 10

Заметим, что после вызова функции значение переменной y в головной


программе изменилось. Кроме того, адрес Ny переменной y совпадает со
значением адреса в указателе x, который является локальным параметром для
функции void f_1().
Третий способ передачи параметров – передача параметра по ссылке.
Общий формат заголовка функции в этом случае должен быть следующим:

<тип> <имя> ( <тип> &<имя>)

Например, если функция имеет заголовок void f_1(int &x), то в этом


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

Программа fun_8. Передача параметра по ссылке.


void f_1(int &x);

 Языки программирования. Язык С. Конспект лекций -147-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
4. Передача параметров в функцию

void main()
{ int y = 2;
cout << “Значение переменной до вызова функции y = ” << y << “\n”;
f_1(y);
cout << “Вышли из функции.\n\n”;
cout << “Значение переменной y после вызова функции y = ” << y << “\n”;
}
void f_1(int &x)
{
cout << “Выполняется функция.\n\n”;
cout << “В функцию передано значение ”<< “x= “ << x << “\n”;
x = 10;
cout << “После преобразования переменной в функции x = “ << x << “\n”;
}

Результат работы данной программы будет следующий.

Значение переменной до вызова функции y = 2;


Выполняется функция
В функцию передано значение x = 2;
После преобразования переменной в функции x = 10;
Вышли из функции
Значение переменной после вызова функции y = 10;

5. Передача массивов в функцию

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


адрес первого элемента массива. Функция в этом случае работает с массивом
из вызывающей программы и поэтому может изменять или преобразовывать
информацию, которая непосредственно содержится в данном массиве.
Формат передачи адреса массива в функцию имеет различный синтаксис.
Например, коды void f_1(int a[]), void f_1(int *a), void f_1(int a[10])
декларируют передачу массива a в функцию f_1. Во всех трех случаях код
вызова функции одинаков и имеет следующий синтаксис: f_1(a), где a –
массив, определенный в вызывающей программе.

Программа fun_9. Программа вводит с клавиатуры элементы массива,


а затем выводит введенные данные на экран. Для ввода и вывода элементов
массива используются функции.
void vvod(int *x); // x – указатель.
void vivod (int *x);
const n = 10;
void main()
{

 Языки программирования. Язык С. Конспект лекций -148-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
5. Передача массивов в функцию

int w[n];
clrscr();
vvod(w); // Вызов функции ввода элементов массива.
vivod(w); // Вызов функции вывода элементов массива на экран.
getch();
}
void vvod(int *y) // Функция ввода элементов массива, y – указатель.
{ int i;
cout << “Введи элементы массива: \n”;
for (i = 0; i < n; i++)
{
cout << "a(" << i << ")= ";
scanf("%d", &y[i]);
}
}

void vivod (int *z) // Функция вывода массива на экран, z – указатель.


{
int i;
cout << “\nВведен массив:\n”;
for (i = 0; i < n; i++)
cout << z[i] << " ";
cout << "\n";
}

Обратим внимание на то, что имена локальных переменных в функциях


различны.
При передаче двумерного массива в функцию надо переслать в нее
адрес первого элемента массива. Заголовок функции, в который передается
двумерный массив, может иметь следующий синтаксис: void f_1( int x[][m]),
где m – ранее объявленная константа. При такой форме передачи задание
размера m (числа элементов в строке) обязательно.

Программа fun_10.
const n = 3, m=2;
void vvod(int x[][m]); // Функция ввода элементов матрицы.
void vivod (int x[][m]); // Функция вывода элементов матрицы.
void main()
{
int w[n][m];
clrscr();
vvod(w); // Вызов функции ввода элементов матрицы.
vivod(w); // Вызов функции вывода элементов матрицы на экран.
getch();
}

 Языки программирования. Язык С. Конспект лекций -149-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
5. Передача массивов в функцию

void vvod(int y[][m])


{ int i, j;
cout << “Введи элементы матрицы: \n”;
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
{ cout << "a(" << i << "," << j << ")= ";
scanf("%d", &y[i][j]);
}
}
void vivod (int z[][m])
{
int i, j;
cout << “\nВведена матрица:\n”;
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
cout << z[i][j] << " ";
cout << "\n";
}
cout << "\n"; }

Если функция используется для нескольких двумерных массивов


разной размерности, то двумерный массив надо интерпретировать как
одномерный. Синтаксис заголовка функции в данной ситуации будет
следующим:

void f_1 (int *x, int n, int m)

Здесь x – указатель на первый элемент матрицы; m, n – размер матрицы.


В теле функции доступ к элементам матрицы при таком обращении можно
осуществить только через адреса к элементам.

Программа fun_11. Функция ввода матрицы из файла используется


для матриц разного размера.
const n1 = 3, m1=2;
const n2 =3, m2 = 3;
void vvod(int *x, int n, int m);
void vivod (int *x, int n, int m);
void main()
{ int a[n1][m1], b[n2][m2];
clrscr();
vvod(a[0], n1, m1); // Вызов функции ввода элементов матрицы.
vivod(a[0], n1, m1); // Вызов функции вывода элементов матрицы.
vvod(b[0],n2,m2);
vivod(b[0],n2,m2);

 Языки программирования. Язык С. Конспект лекций -150-


ЛЕКЦИЯ 15. ФУНКЦИИ. ОБЛАСТЬ ДЕЙСТВИЯ. ПЕРЕДАЧА ПЕРЕМЕННЫХ И МАССИВОВ В ФУНКЦИИ
5. Передача массивов в функцию

getch(); }
// Функция ввода элементов матрицы.
void vvod( int *c, int n, int m) // Ввод элементов матрицы с клавиатуры.
{ int i, j; // Индексы для элементов матрицы.
cout << “Введи элементы матрицы: \n”;
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
{
cout << “x(” << i << “,” << j << “) = “;
scanf(“%d”, c + i * m + j); // i * m + j – порядковый номер элемента
// матрицы c индексом i, j. x + i * m + j – адрес этого элемента.
}}
// Функция вывода элементов матрицы на экран.
void vivod( int *c, int n, int m) // Вывод элементов матрицы.
{ int i, j;
cout << “\nВведена матрица:\n”;
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
// i * m + j – порядковый номер элемента матрицы с индексом i, j.
printf("%d ", *(c + i * m + j)); // c + i * m + j – адрес этого элемента.
printf ("\n");
}}

 Языки программирования. Язык С. Конспект лекций -151-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ.
АБСТРАКТНЫЕ ТИПЫ ДАННЫХ.
ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ

План

1. Обработка исключений.
2. Абстрактный тип данных.
3. Инкапсуляция.
4. Классы и объекты.

1. Обработка исключений

Во всех программах присутствуют ошибки, и поэтому должны быть


учтены условия, при которых возможны возникновения ошибок. Те ошибки,
которые обнаруживаются в процессе компиляции, естественно, устраняются.
Точно так же исправляются ошибки, выявленные в процессе тестирования
программы. Но существуют ошибки, которые остаются в программе даже
после тщательной отладки. Кроме того, при работе компьютера могут
возникнуть сбои различного рода – переполнение памяти, нарушение связей
между устройствами компьютера, отсутствие файла с данными. Эти сбои
могут привести к неадекватной реакции программы. Практика показывает, как
бы тщательно не тестировалась программа избежать ошибок,
непредвиденных обстоятельств, которые приводят к сбоям программы,
нельзя. В языке C++ существует инструмент, который называется
обработкой исключительных ситуаций. Этот инструмент предоставляет
программе возможность, в случае возникновения ошибки, не просто
прекратить выполнение, а как-то обработать эту ошибку (исключительную
ситуацию).
Для обработки исключительных ситуаций в языке C++ используются
три оператора try, catch и throw. Коды директив, которые применяются для
контроля возникновения ошибки, располагают в блоке операторов за
ключевым словом try. Блоки

try { <директивы проверок ошибок> }

надо размещать в программе там, где возможно появление ошибки. Сразу


стоить заметить, что это не очень простая задача. И успех в ее решении
зависит от опыта программиста.
После выявление ошибки управление передается блоку операторов,
который следует за ключевым словом catch. Блок обработки

catch (<тип> <имя>) {<директивы обработки ошибки> }

 Языки программирования. Язык С. Конспект лекций -152-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ
1. Обработка исключений

располагаются непосредственно после блока try. Блок catch классифицирует


выявленные исключения. Общий формат обработки исключений выглядит
следующим образом:
try
{
<Операторы, проверяющие наличие ошибки.>
}

catch( <тип1> <имя1>)


{
}

catch( <тип2> <имя2>)


{
}

catch( <тип n> <имя n>)


{
}

Инструкция throw находится внутри блока try (или внутри функций,


которые вызывает блок try), и она передает управление обработчику, то есть
блоку catch.
В заключение приведем код простой программы, которая
демонстрирует одну (простую) из возможных организаций обработки
исключений.
#include <stdio.h>
#include <conio.h>
#include <iostream.h>
#include <string.h>
void vvod_n (int &k,char *st)
{
cout << "k= ";
cin >> k;
if ( k == 0 ) throw 0; // Для k = 0 определяем номер кода ошибки.
if ( k < 0 ) throw 1; // Для k < 0 определяем номер кода ошибки.
}
void vvod_s(char *st)
{
cout << "Введи строку:\n\n";
cin >> st;
if (*st== NULL) throw 3; // Для пустой строки определяем
// номер кода ошибки.

 Языки программирования. Язык С. Конспект лекций -153-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ
1. Обработка исключений

}
void main ()
{
int t;
char st1[20];
st1[0]='\0';
cout << "\t\tПрограмма демонстрирует обработку исключений.\n\n";
try
{
vvod_n (t, st1);
cout << "Введено число k= " << t << "\n";
}
catch ( int m)
{
cout << "Зафиксирована ошибка с кодом " << m << "\n";
}

try
{
vvod_s(st1);
cout << "Введена строка:\n " << st1 << "\n";

}
catch (int m)
{
cout << "\nСтрока не введена. Ошибка с кодом " << m << "\n";
}
cout << "Конец работы программы." << "\n";
}

Программа работает следующим образом. После загрузки происходит


запрос ввода числа. Если ввести число, равное нулю или отрицательное,
программа выдаст код ошибки. При вводе положительного числа диагноз
ошибки отсутствует. Затем программа производит запрос на ввод строки.
Если строку не вводить (для этого надо ввести Ctrl+z Enter), то компьютер
выдаст код ошибки. В противном случае на экране монитора появится текст
введенной строки.

2. Абстрактные типы данных

Перечислим условия, которые характеризуют абстрактный тип


данных (АТД):
Определение типа данных и операции над переменными данного типа
(объектами данного типа) должны содержаться в одной синтаксической

 Языки программирования. Язык С. Конспект лекций -154-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ
2. Абстрактные типы данных

структуре. Над объектами данного типа можно производить лишь те


операции, которые декларируются в определении типа.
Описание объектов данного типа должно быть скрыто от программных
модулей, которые их используют.
Итак, АТД – это совокупность данных и множество операторов над
ними. При определении АТД можно использовать структуры данных (СД).
СД – это конструкция, которая определяется в языке программирования для
хранения и записи информации. АТД дает общее представление структуры
данных независимо от ее применения. Соотношения между АТД и
алгоритмом решения задачи следующее: вид АТД может диктовать выбор
алгоритма, а алгоритм, в свою очередь, может диктовать выбор АТД,
которые обеспечивают должный уровень (более простой и удобный)
организации и управления большими и сложными программами. Результатом
применения АТД является методология объектно-ориентированного
программирования (ООП). У всех объектно-ориентированных языков
существуют общие понятия. К этим понятиям можно отнести абстрактные
типы данных, классы, наследование, полиморфизм и модульность
прикладных программ на основе классов. Цель ООП – отделить реализацию
объекта от его использования. Это можно сделать, если в структуре самого
класса предусмотреть инструменты, которые будут препятствовать доступу к
данным объекта, для программ, использующих эти объекты.
В реальной жизни каждый из нас ежедневно пользуется некоторыми
готовыми объектами, не очень задумываясь над этим фактом. Пульт от
телевизора (да и сам телевизор), стиральная машина, кухонный комбайн, лифт,
компьютер – вот далеко не полный перечень этих самых объектов, которые
сопровождают нашу жизнедеятельность. Что же общее присуще всем этим
объектам (модулям, контейнерам, пакетам)? Мы знаем функции этих объектов
и умеем управлять этими функциями. Но никому из нас не интересно, как
реализованы эти функции. Другими словами, не зная детальную структуру
объекта, мы управляем его работой. На самом деле это и есть идеология
ООП. Итак, ООП позволяет составлять программы, используя некоторые
модули, представляющие собой набор переменных и множество функций
(операторов) над переменными. Иными словами, ООП воспринимает мир как
мир, созданный из объектов. В языке C инструмент, при помощи которого
создаются независимые объекты (модули), называется классом. Класс можно
трактовать как некоторый шаблон, в котором компонуются структуры
данных и множество операторов для преобразования этих данных. Из
вышесказанного следует, что основными компонентами при конструировании
АТД являются переменные и функции. Поэтому, чтобы конструктивно и
осмысленно использовать АТД и ООП, на первом этапе мы должны изучить
составляющие их компоненты.

 Языки программирования. Язык С. Конспект лекций -155-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ

3. Инкапсуляция

Инкапсуляция – это некоторое объединение данных и функций,


которые эти данные преобразовывают. При этом объединенные компоненты
защищены инкапсуляцией от внешнего вмешательства. При ООП
компонентами инкапсуляции являются переменные и функции, которые
определенны над этими переменными. Внутри объекта переменные и
функции могут быть разбиты на две группы. К первой группе относятся
закрытые, а ко второй – открытые компоненты. Открытые переменные и
функции доступны к другим частям программы. Закрытые недоступны к
частям программы, которые существуют вне определенного объекта.
В языке C инкапсуляция реализована за счет формирования пользова-
тельских типов данных, которые называются классами. Инкапсуляция
фактически предназначена для управления доступом к данным. Она
связывает данные с операциями и защищает данные от внешней среды.

4. Классы и объекты

Класс – это инструмент для создания объектов в языке C++. Для


объявления класса используется ключевое слов class. Объявление класса
имеет следующий общий вид:
classs <имя>
{
private:
<закрытые функции и переменные>
public:
<открытые функции и переменные>
};

Переменные и функции, объявленные внутри класса, называются


членами данного класса. При объявлении класса используется ключевые
слова private, public и protected. Все переменные и функции, которые
следуют за ключевым словом public, доступны как для членов других
классов, так и для любой части программы. Функции и переменные,
объявленные после ключевого слова private, считаются закрытыми. Заметим,
что при объявлении класса могут отсутствовать как закрытые члены класса,
так и открытые. Кроме того, ключевое слово private в определении класса
может отсутствовать. В этом случае по умолчанию все функции и
переменные, которые не следуют за ключевым словом public, считаются
закрытыми. Возможны следующие схемы определения класса:
classs <имя>
{
<закрытые функции и переменные>
public:

 Языки программирования. Язык С. Конспект лекций -156-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ
4. Классы и объекты

<открытые функции и переменные>


};

classs <имя>
{
public:
<открытые функции и переменные>
};

или

classs <имя>
{
<закрытые функции и переменные>
};

Таким образом, каждый класс создает определенный вид объекта путем


инкапсулирования данных, которые описывают объект, и функций
манипулирования этим объектом. Приведем простой пример определения
класса.
class point
{
private:
double x;
double y;
public:
void set_xy( double xw, double yw) // Устанавливает значение переменных
// x и y.
{ x = xw;
y = yw;
}
double x_out() // Возвращает абциссу точки.
{ return x; }
double y_out() // Возвращает ординату точки.
{
return y;
}

Рассмотренный пример объявляет класс point. Членами данного класса


являются защищенные переменные x и y. Кроме того, в данном классе
определены три функции-члена x_out(), y_out() и set_xy(). Функции-члены
x_out() и y_out() возвращают значения введенных координат, а функция-член
set_xy() устанавливает значение переменных x и y. При объявлении класса
point память не резервируется. Этим объявлением компьютеру сообщается,
что существует класс point, который содержит защищенные переменные x

 Языки программирования. Язык С. Конспект лекций -157-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ
4. Классы и объекты

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


если под элемент класса потребуется эту память выделять. Напомним, что
класс формирует связку между переменными и функциями класса. Любая
функция класса имеет доступ к закрытым элементам класса.
Определив класс, можно создать объект нового типа point. Объект
нового типа создается директивой

point M1, M2;

Эта директива для объектов M1 и M2, которые присутствуют в коде,


выделит память, и каждый из них будет иметь собственные копии
переменных x и y. Код доступа к члену класса через объект этого класса
формируется при помощи оператора «точка», имени объекта и имени члена.
Например, код

cout << M1.x;

выведет абсциссу объекта (точки) M1 на экран.


Приведем код программы, который демонстрирует определение класса
и некоторые элементарные операции над объектами класса.
#include <iostream.h>
#include <conio.h>
#include <math.h>
class POINT // Начало определение класса POINT.
{
private: // Начало определения закрытых членов класса.
float x;
float y;
public: // Начало определения открытых членов класса.
// Функция-член set_xy():
// устанавливает значение переменных x и y.
void set_xy( float xw, float yw)
{ x = xw;
y = yw;
}
// Функция-член x_out():
// возвращает значение x – абциссы точки. M( x, y).
float x_out()
{
return x;
};
// Функция-член y_out():
// возвращает значение y – ординаты точки. M( x, y).
float y_out()
{

 Языки программирования. Язык С. Конспект лекций -158-


ЛЕКЦИЯ 16. ОБРАБОТКА ИСКЛЮЧЕНИЙ. АБСТРАКТНЫЕ ТИПЫ ДАННЫХ. ИНКАПСУЛЯЦИЯ. КЛАССЫ И ОБЪЕКТЫ
4. Классы и объекты

return y;
}
// Функция-член mod():
// возвращает значение расстояния от начала координат до точки M( x, y).
float mod(POINT m)
{
return sqrt(m.x*m.x + m.y*m.y);
}
};

void main()
{
POINT m; // Определяем объект m типа POINT.
float x, y;
clrscr();
cout << "Введите координаты точки x y: ";
cin >> x >> y;
m.set_xy(x, y); // Инициализируем объект m.
// Выводим координаты объекта m.
cout << "\n\n\tM(" << m.x_out() << ", " << m.y_out() << ")\n";
// Выводим рассояние от начала координат до точки m.
cout << "\n\n\tR= " << m.mod(m) << "\n";
getch();
}

Результат работы программы. Компьютер выводит на экран


поясняющее сообщение (подсказку):

Введите координаты точки x y: 3. 4.

Через пробел набираем два числа 3 и 4. Нажимаем Enter. На экране


монитора появляется информация:

M(3,4)
R=5

Заметим, что в приведенном примере класса отсутствуют данные,


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

 Языки программирования. Язык С. Конспект лекций -159-


ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ
АБСТРАКТНЫХ ТИПОВ ДАННЫХ

План
1. Реализация АТД на примере комплексных чисел.
2. Конструктор класса.
3. Деструктор класса.
4. Файл реализации.
5. Файл приложения.

1. Реализация АТД на примере комплексных чисел


Прежде чем создавать какой-либо объект, мы должны его внимательно
изучить, определить структуру и то, какие операции можно выполнять над
этим объектом. В данном случае объект – комплексные числа – хорошо
изучен в математике. Комплексное число состоит из двух компонент: из
действительной и мнимой части. Известна геометрическая интерпретация
комплексного числа и определены четыре операции над комплексными
числами. Однако не существует одного множество операторов, которые
выполняются над комплексными числами. Можно определять аргумент
комплексного числа, его модуль, корень n-й степени и т. д. Таким образом,
множество операторов, которое потребуется для обработки комплексного
числа, зависит от поставленной задачи.
Итак, обозначим через M комплексное число, которое определяется
действительной частью x и мнимой y. Будем рассматривать следующее
множество операторов над комплексным числом:
set_xy( float xw, float yw). Определяет действительную и мнимую часть
числа.
float x_out(). Возвращает действительную часть комплексного числа.
float y_out(). Возвращает мнимую часть комплексного числа.
float mod(COMPL m). Возвращает модуль комплексного числа.
void sum (COMPL m1, COMPL m2). Определяет сумму двух
комплексных чисел.
Для демонстрации реализации АТД комплексного числа ограничимся
только этими операторами. Произведение, деление двух чисел можно далее
организовать по примеру вычисления суммы чисел. Следует обратить
внимание на исключение возможности деления на ноль. Итак,
предварительная работа для построения класса проведена. Теперь можно
определить новый тип в виде класса под именем COMPL.
class COMPL
{
private:
float x;

 Языки программирования. Язык С. Конспект лекций -160-


ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ АБСТРАКТНЫХ ТИПОВ ДАННЫХ
1. Реализация АТД на примере комплексных чисел
float y;
public:
// КОНСТРУКТОР
COMPL ( float p_x = 0., float p_y = 0.); // Аргументы по умолчанию.
void set_xy( float xw, float yw);
float x_out(); // Возвращает абциссу точки.
float y_out(); // Возвращает ординату точки.
float mod(COMPL m); // Возвращает модуль числа.
void sum (COMPL m1, COMPL m2); // Определяет сумму 2-х чисел.
};

Обратим внимание на то, что помимо вышеперечисленных функций в


определение класса включен так называемый конструктор класса

COMPL ( float p_x = 0., float p_y = 0.).

2. Конструктор класса

Представляет собой специальную функцию, которую язык C++


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

3. Деструктор класса

Помимо конструктора в классе может присутствовать деструктор.


Деструктор автоматически запускается каждый раз, когда программа
ликвидирует объект. Имя деструктора совпадает с именем класса, но только
перед именем деструктора надо еще ставить символ ~ (тильда). Деструктор
не имеет никаких параметров и не возвращает никаких значений. Он
выполняется автоматически, как только объект выходит из области
видимости.
Для лучшей структуризации при разработке программ (особенно
больших) принято всю информацию о классе записывать в заголовочном
файле. Имя заголовочного файла определяется пользователем. Существует
хороший стиль оформления заголовочного файла комментариями, но мы
оставляем за скобками данный вопрос. Только скажем кратко о назначении
комментариев в заголовочном файле. Они необходимы для того, чтобы
пользователь, который решил воспользоваться данным классом, мог

 Языки программирования. Язык С. Конспект лекций -161-


ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ АБСТРАКТНЫХ ТИПОВ ДАННЫХ
3. Деструктор класса
осмысленно это сделать. Итак, в заголовочном файле содержится вся
информация, необходимая для использования класса.
Для того чтобы завершить построение класса, надо реализовать
программы для всех функций-членов, которые декларированы в этом классе.
При реализации функции-члена используется оператор :: – разрешение
области видимости. Этот оператор сообщает ПК о том, какому классу
принадлежит представляемая функция.

4. Файл реализации

Принято коды всех функций-членов записывать в файл реализации. В


данном файле должна содержаться директива #include «имя заголовочного
файла». Имя файла реализации определяется пользователем. В нашем случае
файл реализации содержит следующую информацию.
#include "zag.fil" // zag.fil – имя заголовочного файла.
#include <math.h>
COMPL :: COMPL (float p_x, float p_y) !!!!!! //: x(p_x), y(p_y)
{
x = p_x;
y = p_y;
}
void COMPL :: set_xy( float xw, float yw) // Устанавливает значение
// переменных x и y.
{ x = xw;
y = yw;
}

float COMPL :: x_out() // Возвращает абциссу точки.


{
return x;
};

float COMPL :: y_out() // Возвращает ординату точки.


{
return y;
}
float COMPL :: mod(COMPL m)
{
return sqrt(m.x*m.x + m.y*m.y);
}

void COMPL :: sum (COMPL m1, COMPL m2)


{
x = m1.x + m2.x;

 Языки программирования. Язык С. Конспект лекций -162-


ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ АБСТРАКТНЫХ ТИПОВ ДАННЫХ
4. Файл реализации
y = m1.y +m2.y; }

Здесь zag.fi – имя заголовочного файла.

5. Файл приложения

На этом завершен этап формирования интерфейса класса и можно


переходить к программе, которая использует определенный класс COMPL.
Ниже приведен пример такой программы, которая размещается в некотором
файле приложения.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include "real.cpp" // real.cpp – имя файла реализации.
void main()
{ float x, y;
COMPL m(1, 1); // Инициализация обекта m типа COMPL.
COMPL m1(2,3); // Инициализация обекта m1 типа COMPL.
COMPL m2(1,2), m3; // Инициализация обекта m2 типа COMPL.
// Объявляется объект m3 типа COMPL.
clrscr();
m3.sum (m1, m2); // Вычисляем суму чисел 1 + 2i и 2 + 3i.
// Выводим результат суммы чисел 1 + 2i и 2 + 3i
cout << "Сумма чисел "
<< m1.x_out() << "+" << m1.y_out() << "i и "
<< m2.x_out() << "+" << m2.y_out() << "i = "
<< m3.x_out() << "+" << m3.y_out() << "i\n";

// Выводим комплексное число 1 + i.


cout << "\nМодуль числа " << m.x_out() << "+"
<< m.y_out() << "i = " << m.mod(m) << "\n";

cout << "\n\nВведите действительную и мнимую части числа a b: ";


cin >> x >> y;
m.set_xy(x, y);
cout << "\nВы ввели число " << m.x_out() << "+" << m.y_out() << "i\n";
cout << "\nМодуль числа " << m.x_out() << "+"
<< m.y_out() << "i = " << m.mod(m) << "\n";
getch();
}

Результат работы программы


Сумма чисел 2 + 3i и 10 + 2i = 12 + 5i

 Языки программирования. Язык С. Конспект лекций -163-


ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ АБСТРАКТНЫХ ТИПОВ ДАННЫХ
5. Файл приложения
Модуль числа 1 + 1i = 1. 414214
Введите действительную и мнимую части числа a b = 3 4 <Enter>
Вы ввели число 3 + 4i
Модуль числа 3 + 4i = 5

В заключение приведем код программы, которая создает объект


данных стек, используя классы. В данной реализации отсутствует
распределение отдельных разделов программы по файлам.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
const nr = 10; // Размер памяти стека.
// Объявления класса.
class stak
{ int top; // Число элементов в стеке.
int st[nr]; // Для элементов стека.
// Прототипы функций-членов.
public:
void initc (); // Инициализирует стек.
int viv_st(); // Оперделяет элемент вершины стека.
void push(int k); // Добавляет элемент в стек.
int pop(); // Удаляет элемент из стека.
int pyst(); // Проверяет наличие элементов в стеке.
int full(); // Проверяет наличие свободной памяти у стека.
int num_st(); // Определяет число элементов в стеке.
};
// Реализация функций-членов.
void stak :: initc ()
{ top = 0; }
int stak :: viv_st()
{ return st[top]; }
void stak :: push (int kk)
{ st[++top] = kk; }
int stak :: pop()
{ return st[top--]; }
int stak :: full ()
{ return top == nr; }
int stak :: pyst()
{ return top == 0; }
int stak :: num_st()
{ return top; }
// Прикладная программа.
void main ()

 Языки программирования. Язык С. Конспект лекций -164-


ЛЕКЦИЯ 17. РЕАЛИЗАЦИЯ АБСТРАКТНЫХ ТИПОВ ДАННЫХ
5. Файл приложения
{
stak st_1; // Определяем объект типа stak.
int i;
clrscr();
st_1.initc(); // Инициализируем объект st_1.
for (i = 0; i < 5; i++)
{
if ( !st_1.full() )
st_1.push(i); // Заполняем стек элементами, значение которых
// равны i.
}
// Выдаем на экран число элементов в стеке.
cout << "В стеке " << st_1.num_st() << " элементов\n";
while ( !st_1.pyst())
{
cout << st_1.pop() << " "; // Удаляем элементы из стека.
}
cout << "В стеке " << st_1.num_st() << " элементов\n";
getch();
}

Результат работы программы: после активизации программы и


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

В стеке 5 элементов
543210
В стеке 0 элементов

 Языки программирования. Язык С. Конспект лекций -165-


ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ
И ПАРАМЕТРИЗАЦИЯ

План

1. Спецификация.
2. Параметризация.

1. Спецификация

В языке C++ спецификаторы класса (public, private, protected) являются


важными инструментами для построения объекта. При помощи этих
спецификаторов фактически происходит управление доступом к элементам
класса. Доступ к членам класса является важным моментом в объектно-
ориентированном программировании (ООП). Члены класса могут
объявляться защищенными (protected), в результате чего они будут
недоступными для внешних по отношению к классу функций. Члены класса,
которые объявлены как открытыми (public), становятся доступными для
всех функций программы. Члены класса, которые объявляются закрытыми
(private), доступны только для членов данного класса.
Для использования класса программы должны знать информацию,
которую хранят класс и методы, преобразовывающие данные. Алгоритмы, по
которым работают методы, а также способы их реализации программам знать
не надо. Прикладным программам, которые используют класс, достаточно
знать, какие функции может выполнять класс, а как эти функции
выполняются, неважно. Важно уметь использовать функции класса и уметь
передавать параметры этим функциям в процессе использования класса и
выполнения программы. Способ сокрытия информации представляет собой
инструмент, при помощи которого программе, использующей класс,
предоставляется минимальная информация для успешного и полного
использования возможностей класса. Спецификации класса возлагают на него
ответственность за внутренний порядок. Класс сам распределяет информацию
таким образом, чтобы вне класса невозможно было бы манипулировать им.
Вся ответственность за функционирование класса лежит на его создателе.
В самом классе информация должна быть строго специфицирована, в том
числе там четко должна быть указана информация, при помощи которой
устанавливается связь между классом и программой. Степень взаимосвязи
класса с программой, которая этот класс использует, строго регламентируется
конструкцией класса. Надо помнить, что класс, с четко выстроенным
интерфейсом, легче использовать. Такой класс обладает иммунитетом от
непредвиденной своей модификации сторонними функциями.
Остановимся еще на роли спецификатора protected. Язык C++
поддерживает механизм наследования, который позволяет в объявлении

 Языки программирования. Язык С. Конспект лекций -166-


ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ И ПАРАМЕТРИЗАЦИЯ
1. Спецификация
класса использовать другой класс. Наследование – принцип ООП, который
дает возможность производить иерархическую классификацию информации.
Используя наследование, можно создать сначала некоторый общий класс с
определенными характеристиками. Затем этот класс можно использовать для
создания другого класса, который наследует общие характеристики, но имеет
свои уникальные особенности. Класс, который наследуется, в языке C++
называется базовым. Класс, наследующий базовый, называется производным.
В этом случае спецификаторы доступа определяют, как элементы базового
класса наследуются производным.
Если для базового класса, а именно он включается в структуру другого
класса, используется спецификатор public, то все формы доступа в
производном классе к членам базового класса не меняются. Они фактически
определяются спецификаторами базового класса. Если для базового класса
используется спецификатор private, то все открытые члены базового класса
становятся закрытыми в производном классе. Статус остальных членов
базового класса с точки зрения доступа к ним, как к элементам производного
класса, не меняется.
Спецификатор protected в языке C++ придает большую гибкость
механизму наследования. Если базовый класс объявляется как protected, то
элементы, открытые и защищенные в рамках своего класса, становятся
защищенными в рамках производного класса. Иными словами, protected
предоставляет возможность создать член, который будет доступен данной
иерархии классов, но закрыт для внешних программ. Спецификатор protected
вводится тогда, когда некоторые структуры данных должны быть доступны
для некоторых других классов. В отличие от закрытых защищенные
переменные и члены-функции полностью видимы производным классам.
Возможные доступы при наследовании можно свести в таблицу.

Таблица 18.1
Возможные доступы при наследовании

Спецификаторы Доступ Доступ


класса в базовый класс в производный класс
private private private
public private
protected private
public private private
public public
protected protected
protected private private
public protected
protected protected

 Языки программирования. Язык С. Конспект лекций -167-


ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ И ПАРАМЕТРИЗАЦИЯ
1. Спецификация
Рассмотрим простой пример, который демонстрирует механизм
наследования и приемы доступа объектов одного класса к функциям-членам
другого класса.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
class P_1 // Класс P_1 – базовый.
{
protected:
float x;
float y;
public:
void in_xy( ); // Устанавливает значение переменных x и y.
void out_xy();
};
class P_2 : public P_1 // Класс P_2 – производный.
{
float w;
public:
float out_w();
float f_m();
};

class P_3 : public P_2 // Класс P_3 – производный.


{
public:
void f_xy ();
};
// Реализация функций класса P_1.
void P_1 :: in_xy( )
{ cout << "Введите два числа ";
cin >> x >> y; }
void P_1 :: out_xy()
{
cout << "Введены числа: x= " << x << " y= " << y << "\n";
};
// Реализация функций класса P_2.
float P_2 :: out_w()
{ return w; }
float P_2 :: f_m()
{ w = x*y;
return w; }
// Реализация функций класса P_3.

 Языки программирования. Язык С. Конспект лекций -168-


ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ И ПАРАМЕТРИЗАЦИЯ
1. Спецификация
void P_3 :: f_xy ()
{ cout << "Введите два числа ";
cin >> x >> y; }
// Реализация прикладной программы.
void main()
{
clrscr();
P_2 m2; // Объект m2 принадлежит классу P_2.
P_3 m3; // Объект m3 принадлежит классу P_3.
m2.in_xy(); // Функция-член in_xy() принадлежит классу P_1,
// используется для объекта m2 класса P_2.
m2.out_xy(); // Функция-член out_xy() принадлежит классу P_1,
// используется для объекта m2 класса P_2.
m2.f_m(); // Функция-член f_m() принадлежит классу P_2.
// Функция-член out_w() принадлежит классу P_2.
cout << "**** " << m2.out_w() << "\n";
getch();
m3.f_xy(); // Функция-член f_xy() принадлежит классу P_3.
m3.out_xy(); // Функция-член out_xy() принадлежит классу P_1,
// используется для объекта m3 класса P_3.
m3.f_m(); // Функция-член f_m() принадлежит классу P_2,
// используется для объекта m3 класса P_3.
cout << "**** " << m3.out_w() << "\n";
getch();
}
В языке C++ существует еще один инструмент, который позволяет
элементы одного класса использовать в другом. Это можно сделать, если в
конструкцию одного класса включить, определив его как дружественный.
Для объявления класса дружественным надо использовать ключевое слово
friend. Формат:

class P_1 { …};


class P_2
{…
friend class P_1; }

объявляет класс P_1 дружественным по отношению к классу P_2. Тем самым


элементам класса P_1 разрешается доступ к элементам класса P_2.

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

 Языки программирования. Язык С. Конспект лекций -169-


ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ И ПАРАМЕТРИЗАЦИЯ
2. Параметризация
когда две или более функций выполняют одинаковую обработку
информации, но работают с разными типами данных. Например, задача
определить максимум из двух значений переменных может решаться для
переменных типа int, float, double, char. В языке C++ предоставляется
возможность создавать функции, которые могут возвращать значение разных
типов. Для этой цели используются шаблоны. Шаблон функции определяет
типонезависимую функцию. Шаблон представляет собой функцию или
класс, реализованные для нескольких типов данных, причем на момент
написания программы для функции неизвестны. Можно сказать, что в этом
случае функция параметризирована несколькими типами. Для реализации
шаблона или параметризованного типа в языке C++ используется ключевое
слово template. Приведем пример определения шаблона для функции с
именем max:
template <class T> T max ( T a, T b)
{
if ( a > b)
return a;
else
return b;
}

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


представляет собой общий тип шаблона. После определения шаблона в
программе надо определить прототипы функций для каждого типа
переменных, которые будут использовать эту функцию. Ниже дан полный
код программы, которая будет использовать определенный шаблон.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
template <class T> T max ( T a, T b)
{
if ( a > b)
return a;
else
return b;
}

int max( int, int);


char max (char, char);
void main()
{ int a =5, b = 23;
char c = ‘c’, d = ‘d’;
clrscr();

 Языки программирования. Язык С. Конспект лекций -170-


ЛЕКЦИЯ 18. СПЕЦИФИКАЦИЯ И ПАРАМЕТРИЗАЦИЯ
2. Параметризация
cout << “Из двух значений “ << a << “ и “ << b
<<”:\nmax= “ << max(a,b);
cout << “\n\nИз двух значений “ << c << “ и “ << d
<<”:\nmax= “ << max(c, d);
getch();
}

В процессе компиляции компьютер автоматически создает операторы


для построения одной функции, работающей с типом int, и второй функции,
работающей с типом char. В результате программы при помощи шаблонов
могут использовать одно и то же имя для функций, которые выполняют
определенные преобразования, независимо от типов параметров и типа
возвращаемого значения. Подобная идеология применяется при создании
шаблонных классов. Использование шаблонов уменьшает объем программ,
позволяя языку C++ генерировать операторы для функций, которые
отличаются только типами параметров. Шаблоны дают возможность
избежать дублирования функций, коды которых отличаются только типами
переменных. Кроме того, шаблоны можно использовать для создания
типонезависимых или общих классов. Это, в свою очередь, помогает
избавиться от дублирования классов, которые отличаются друг от друга
только типами переменных. Синтаксис шаблонов классов, например, может
быть следующим.
template <class T>
class Test
{
public:
Test (T t1, T t2);
T nam_1 ();
T nam_2 ();
private:
T r; };

 Языки программирования. Язык С. Конспект лекций -171-


ЛЕКЦИЯ 19. ГЕНЕРАТОР КОДА/ПРИЛОЖЕНИЙ

План

1. Генератор кодов.
2. Пример формирования окна.

1. Генератор кодов

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


простом примере. Даны два числа a и b. Составить программу, которая по
команде меню определяет результат суммы, разности, произведения или
деления этих чисел. Ниже дан код программы, которая решает поставленную
задачу без применения генератора кода/приложений. При реализации
используется текстовое меню.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
void main()
{ float a, b, c;
int k;

do
{ clrscr();
cout << "\n\n\t\tКоманды меню: \n\n";
cout << "\t1: Определяет сумму чисел a+b \n";
cout << "\t2: Определяет разность чисел a-b\n";
cout << "\t3: Определяет произведение чисел ab\n";
cout << "\t4: Определяет частное чисел a:b\n";
cout << "\t5: Выход\n";
cout << "\nВведи номер команды меню ";
cin >> k;
cout << "\n\n";
if ( k >= 1 && k <= 4)
{ cout << "Введи два числа a b = ";
cin >> a >> b;
cout << "\n\t\t";
}
switch (k)
{
case 1: c = a+b;
cout << a << " + " << b << " = " << c << "\n";
break;

 Языки программирования. Язык С. Конспект лекций -172-


ЛЕКЦИЯ 19. ГЕНЕРАТОР КОДА/ПРИЛОЖЕНИЙ
1. Генератор кодов
case 2: c = a-b;
cout << a << " - " << b << " = " << c << "\n";
break;
case 3: c = a*b;
cout << a << " * " << b << " = " << c << "\n";
break;
case 4: if (b)
{ c = a/b;
cout << a << " : " << b << " = " << c << "\n"; }
else
cout << "\nДелить на 0 нельзя !!!!";
break;
case 5: exit (1);
default: cout << "Команда меню с номером " << k << " отсутствует\n";

} getch();
} while (1); }

После запуска программы на экране монитора появляется сообщение:

Команды меню:
1: Определяет сумму чисел a+b
2: Определяет разность чисел a+b
3: Определяет произведение чисел ab
4: Определяет частное чисел a+b
5: Выход
Введи команды меню 2 <Enter>
Введи два числа a b = 10.2 13 <Enter>
10-13 = -2.8

Далее работа программы продолжается до тех пор, пока не будет


введена команда меню с номером 5.
Такой формат работы программы не отвечает современным требованиям.
Современный пользователь привык к тому, чтобы управление работой
программы происходило по технологии Windows. Команды меню и обмен
информацией должен происходить в окне, определенном пользователем.
На самом деле, используя язык, привлекая графический интерфейс или
текстовый режим управления экраном, опытный программист в состоянии
оформить меню на современном уровне (ранее так и делали). Но это
трудоемкая задача. Каждый раз создавать оболочку меню для управления
работой программы накладно по времени и малоинтересно с точки зрения
реализации этого меню. Генератор кода/приложений автоматизирует
процесс генерации оболочки для работы с программой. Демонстрацию
работы генератора проведем при решении ранее рассмотренной задачи.

 Языки программирования. Язык С. Конспект лекций -173-


ЛЕКЦИЯ 19. ГЕНЕРАТОР КОДА/ПРИЛОЖЕНИЙ

2. Пример формирования окна

Рассмотрим порядок формирования окна для управления работой


программы, которая определяет результат арифметических операций для
двух чисел. Естественно, предварительно должны продумать и разработать
визуальную структуру окна. Предположим, что надо создать окно по
следующей схеме:
1. Загружаем VC++. Появляется рабочий экран VC++.
2. Выбираем команду меню File.
3. В меню File выбираем команду New. Появляется окно New с полями
Project name, Location и кнопками переключателями File, Projects, Workspase,
Other Documents.
4. Активизируем кнопку Projects, и в окне с именами проектов
выбираем вариант MFC AppWizard [exe].
5. Устанавливаем курсор в поле Project и набираем имя проекта
(Test_1).
6. Устанавливаем курсор в поле Location и набираем путь к папке для
размещения проекта (d:\TEST).
7. В окне New активизируем кнопку <OK>. Появляется окно MFC
AppWizard_step1 с командами меню:
- single document
- multiple documents
- dialog based.
8. Выбираем dialog based и выполняем команду <Finish>.
9. Появляется окно New Project Information. Можно ознакомиться с
информацией о сгенерированном объекте и нажать кнопку <OK>.
На рабочем экране VC++ появляется окно нашего проекта с именем
Test_1 с двумя кнопкам <OK> и <Cancel> и окно Controls с набором
элементов для формирования рабочего поля окна нашего проекта Test_1.
Кнопки <OK> и <Cancel> можно разместить в любом месте окна Test_1.
Передвигаем кнопки традиционным способом, используя курсор мыши. Но в
нашем проекте кнопки <OK> и <Cancel> не будут использоваться, поэтому
мы удаляем их из окна. Удаляются элементы из окна традиционным
способом. Элементы выделяем, используя мышь, а затем нажимаем на
клавиатуре клавишу <Delete>.
10. В окне Test_1 формируем поле для числа a. Для этого из окна
Controls при помощи курсора мыши переносим элемент <Edit Box> в поле
окна Test_1.
11. Повторяем пункт 10 для чисел b и c.
После завершения пунктов 10 и 11 в окне Test_1 появятся три поля с
текстом Edit для ввода информации. Теперь нам надо определить имена трех
переменных и их тип.
12. Устанавливаем курсор мыши в любую точку окна Test_1 и
нажимаем правую кнопку. Появляется меню. В этом меню выполняем

 Языки программирования. Язык С. Конспект лекций -174-


ЛЕКЦИЯ 19. ГЕНЕРАТОР КОДА/ПРИЛОЖЕНИЙ
2. Пример формирования окна
команду ClassWizard.
13. Появляется окно MFC ClassWizard.
14. Активизируем в окне кнопку Members Variables.
15. В поле Controls IDS устанавливаем курсор на имя первой
переменной и активизируем кнопку Add Variable.
16. Появляется окно Add Members Variables с тремя полями:
member variable name;
Category
Variable type
17. Устанавливаем курсор в поле member variable name и формируем
имя переменной первого числа. При формировании имени переменной
принято соблюдать определенный стиль, а именно: к символу m_ добавляем
имя Edit – имя элемента из окна Controls, при помощи которого создавалось
поле для числа, и имя идентификатора переменной a (имя идентификатора
определяет пользователь). Итак, в поле member variable name набираем имя
m_Edit_a.
18. Устанавливаем курсор в поле Variable type. Из предлагаемого
списка типов выбираем тип float, который соответствует типу нашей
переменной m_Edit_a.
19. В поле окна Add Members Variables активизируем кнопку <OK>.
20. Пункты 17, 18, 19 повторяем для переменной b и c.
21. После формирования имен переменных возвращаемся в окно MFC
ClassWizard и активизируем кнопку <OK>. Происходит переход в окно
проекта Test_1.
Теперь в окне проекта Test_1 начнем формировать кнопки для
арифметических операций.
22. Из поля Controls в поле проекта Test_1 переносим элемент Botton,
используя курсор мыши.
23. Пункт 22 повторяем четыре раза. Кнопки Botton распологаем в окне
проекта согласно заранее разработанной схеме. Определяем имя кнопок,
созданных для арифметических операций. Для этого устанавливаем курсор
мыши на соответствующую кнопку и делаем два клика правой кнопкой мыши.
24. Устанавливаем курсор мыши на соответствующую кнопку и делаем
клик правой кнопки мыши. Появляется меню. Выполняем команду
Properties.
25. Появляется окно Push Button Propertes. Устанавливаем курсор в
поле Caption, набираем символ + и закрываем окно.
26. Пункты 25 и 26 повторяем для каждой кнопки Botton.
Переходим к описанию действий, которые должны происходить при
нажатии кнопок операций.
27. Устанавливаем курсор мыши на кнопку операции и два раза делаем
клик левой кнопкой мыши. Появляется окно Add Members Functions с полем
Members function name и кнопками <OK>, <Cancel>.
28. Активизируем кнопку <OK>.

 Языки программирования. Язык С. Конспект лекций -175-


ЛЕКЦИЯ 19. ГЕНЕРАТОР КОДА/ПРИЛОЖЕНИЙ
2. Пример формирования окна
29. Компьютер переходит в рабочее поле VC++ с формированным
кодом программы. Курсор установлен на том фрагменте программы, в
который надо добавить необходимые директивы для вычисления суммы двух
чисел. Код определения суммы двух чисел должен быть распложен между
двумя директивами: UpdateData (true) и UpdateData (false);
UpdateData (true); – директива входа в окно.
m_Edit_c = m_Edit_a + m_Edit_b; – директива вычисляет сумму двух
чисел.
UpdateData (false); – директива завершения опроса.
30. Переходим в окно проекта Test_1. Для этого в поле Worcspase VC++
актив
31. Пункты 27, 28, 29 и 30 повторяем для кнопок разности, умножения
и деления.
При завершении данного процесса в соответствующей папке будет
сгенерирован програмнный комплекс для вычисления значений
арифметических операций двух чисел. Эту программу надо отладить (если
обнаружились ошибки), затем оттранслировать и запустить на счет.
Возникает важный методический вопрос. Имеет ли смысл учить писать
программы без использования генератора кодов? Не является ли анахронизмом
такой подход к обучению? По мнению авторов нет. И здесь в качестве
оправдания такой позиции можно привести несколько доводов. Во-первых,
концепция IDE и все, что с ней связано, была нацелена на проектирование
больших и очень больших программных систем. Для демонстрационных
методических задач она слишком громоздка. Это все равно, что для
обработки участка в 6 соток пригнать трактор «Кировец». Во-вторых,
прикидку, отработку, моделирование алгоритмов, даже предназначенных для
больших программ, на первых шагах легче и быстрее производить на менее
громоздкой системе. Для большинства комплектующих программ сложной
системы не требуется. Далее, процесс обучения становится менее
эффиктивным, если привлекать к освоению некоторого метода те
инструменты, которые отвлекают от сути вопроса, без которых можно
обойтись. Код программы без этих инструментов легче для понимания.
И, наконец, можно привести еще один довод. Несмотря на то, что существуют
калькуляторы, школьников целесообразно обучать арифметическим
действиям без его применения. Это нужно для приобретения навыков, без
которых им в дальнейшем будет трудно обойтись, более глубокого
понимания сути выполнения арифметических действий, интелектуальной
тренировки и общего развития.

 Языки программирования. Язык С. Конспект лекций -176-


ЛЕКЦИЯ 20. РЕКУРСИЯ

План

1. Общие сведения о рекурсии.


2. Пример рекурсивной функции.
3. Формы рекурсивного обращения.
4. Выполнение действий на рекурсивном спуске.

1. Общие сведения о рекурсии

При изучении дисциплины «Алгоритмические языки и


программирование» тема «Рекурсия» является одной из самых сложных и
интересных. Рекурсия – важный инструмент (способ) при составлении
программ. Однако свободное владение этим методом программирования
демонстрирует далеко не каждый студент. Надо сказать, что и в литературе
по программированию методика изложения этой важной темы весьма
однообразна и демонстрация рекурсии очень часто сводится к программе
вычисления n! или программам, в которых применение рекурсии с
практической точки зрения нежелательно, а с точки зрения методической –
весьма просто. Но и эта формальная простота часто не очень ясно изложена.
На самом деле рекурсия – это некоторая форма цикла. Поэтому схема
программирования для чисто рекурсивных процедур такова: сформируй
программу, оформленную в виде функции для частного случая, а затем
организуй в этой программе обращение к самой себе. Заметим, что любую
итерационную программу очень легко превратить в рекурсивную функцию,
не вникая в смысл и особенности рекурсии. Под итерационной программой
авторы понимают программу, составленную для задач, параметры которой на
шаге n+1 полностью определяются параметрами n-го шага. Программы
такого типа реализуются при помощи операторов цикла for, while, do while.
Обратное утверждение почти неверно. Вообще говоря, любую
рекурсивную программу можно превратить в программу не рекурсивную,
а состоящую из циклов. Переход от рекурсии к циклам для так называемых
типично рекурсивных процедур труден с точки зрения программирования,
и логика такого перехода бывает подчас сложна, поэтому и возникает
необходимость использования рекурсивных процедур. Если для типично
рекурсивных алгоритмов код программ, основанных на рекурсии, прост и как-
то обозрим, то этот же код, основанный на циклах, сложен и логически бывает
весьма и весьма запутан даже в простейших случаях. Таким образом, техника
рекурсивного программирования является желательным инструментом,
особенно часто для типично информационных задач, т. е. таких задач, которые
требуют исследования не количественных характеристик каких-то законов,
а преобразования и анализа символьной информации. Алгоритмы
преобразования такой информации часто рекурсивны по своему определению.

 Языки программирования. Язык С. Конспект лекций -177-


ЛЕКЦИЯ 20. РЕКУРСИЯ
1. Общие сведения о рекурсии
Авторы надеются, что их попытка пояснить рекурсию поможет
разобраться в отдельных деталях техники рекурсивного программирования.
Дальнейшее изложение материала предполагает владение простейшими
базовыми приемами программирования при решении задач на языке C.

2. Пример рекурсивной функции

Формальное определение рекурсивной функции следующее:


рекурсивной называется функция, которая обращается к самой себе, т. е.
рекурсивная функция имеет следующую структуру:

void rec(...)
{
rec(...);
}

В теле функции обязательно присутствует код обращения функции к


самой себе. Разбирать рекурсию, следуя [11], начнем с задачи: вывести в
столбик цифры числа в порядке их написания. В результате число 1234
должно на экране выглядеть следующим образом:
1
2
3
4
Сначала составляем программу для решения этой задачи
традиционным способом, используя операторы цикла. Программу составим в
два этапа. Первый этап заключается в составлении программы для
следующей задачи: дано число n; выдать на экран цифры, из которых состоит
это число. Код программы для этой задачи приведен ниже.

Программа 1
#include <conio.h>
#include <iostream.h>
void separ( long n);
void main()
{
long a;
clrscr();
cout << "Введи целое число a= " ;
cin >> a;
cout << "Число " << a << " состоит из цифр: \n";
separ(a);
getch();
}

 Языки программирования. Язык С. Конспект лекций -178-


ЛЕКЦИЯ 20. РЕКУРСИЯ
2. Пример рекурсивной функции
void separ( long a)
{
while ( a != 0 )
{
cout << a % 10 << "\n";
a = a/10;
}
}

Данная программа выдает в столбик цифры, из которых составлено это


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

Программа 2
#include <conio.h>
#include <iostream.h>
void separ( long n);
int ms[12];
void main()
{
long a;
clrscr();
cout << "Введи целое число a= " ;
cin >> a;
cout << "Число " << a << " состоит из цифр: \n";
separ(a);
getch();
}
void separ( long a)
{
int j = -1, i;
while ( a != 0 )
{
j++;
ms[j] = a % 10;
a = a/10;
}
for ( i = j; i >= 0; i--)
cout << ms[i] << "\n";
}

 Языки программирования. Язык С. Конспект лекций -179-


ЛЕКЦИЯ 20. РЕКУРСИЯ
2. Пример рекурсивной функции
Если сравнить коды вышеизложенных программ, то легко выделить
отличия. Во втором примере вместо выдачи цифр на экран, т. е. вместо кода
cout<<a%10<<"\n", предварительно запоминаются полученные цифры в
массиве, при этом используется код ms[j] = a % 10; После окончания работы
первого цикла выводятся ранее определенные цифры, но в порядке, обратном
их вычислению. Забегая вперед, можно сказать, что основной принцип
рекурсии как раз и демонстрирует программа 2 (в терминах обычных и
привычных для пользователя директив цикла). Теперь, не объясняя сути
метода рекурсивного программирования, рассмотрим решение поставленной
задачи с использованием рекурсивной функции.

Программа 3
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void separ( long n );

void main ()
{ long n;
clrscr();
cout << "Введи число n= " ;
cin >> n;
separ(n);
getch(); }
Рекурсивная функция:
void separ( long n )
{ long a;
if ( n < 10 )
cout << n << "\n";
else
{
a = n / 10;
separ(a);
//AV
cout << n%10 << "\n";
}}

Если сравним коды программ 1 и 3, то увидим, что принципиальной


разницы в алгоритме определения цифр, из которых состоит число, нет. При
выполнении директивы cout << n%10 << "\n" на экран выдается последняя
цифра числа, а при выполнении директивы a=n/10 отбрасывается последняя
цифра числа и результат заносится в переменную a.
Однако существует некоторая разница в порядке выполнения директив.
Кроме того, в программе 3 вместо директивы цикла есть директива обращения

 Языки программирования. Язык С. Конспект лекций -180-


ЛЕКЦИЯ 20. РЕКУРСИЯ
2. Пример рекурсивной функции
функции к самой себе и директива условного оператора, который регулирует
количество таких обращений функций к самой себе, т. е. регулирует число
выполнения директив, которые расположены после служебного слова else.
Рассмотрим подробнее, что происходит после выполнения директивы
separ(n), расположенной в головной программе. Предположим, что,
выполняя требование поясняющего текста, ввели с клавиатуры число
n = 1234. После выполнения директивы головной программы separ(n) по
законам языка начинают выполняться директивы функции separ(). Точнее,
пока число, передаваемое через параметр функции, больше или равно 10, а в
данном случае n = 1234, будут выполняться коды операторов, которые
записаны после служебного слова else. В результате вычисляется значение
1234/10, равное 123, и это значение засылается в переменную a. Затем
следует директива separ(a), т. е. идет обращение функции к самой себе,
только с новым значением входного параметра. По законам языка снова
должны выполняться директивы функции separ(), но после обращения к
функции separ(a) в рекурсивной функции есть еще директива
cout << n%10 << "\n", а именно при выполнении этой директивы на экране
должна появиться в данном случае цифра 4. Так что же происходит с этой
директивой, которая находится после рекурсивного обращения? Как мы
знаем, при каждом обращении к функции в памяти машины сохраняются:
текущее состояние программы, из которой вызывается функция;
адрес памяти (адрес возврата), с которого должен продолжить работать
компьютер после того, как завершится выполнение вызванной функции.
Часть памяти компьютера, в которой сохраняется состояние
программы и адрес возврата, называется стеком. При каждом рекурсивном
обращении компьютер:
приостанавливает выполнение текущего обращения;
засылает в стек значение локальных параметров текущего обращения;
засылает в стек адрес возврата, т. е. адрес, с которого компьютер
продолжает работать после завершения рекурсивного обращения. Это место
возврата в программе условно помечено комментарием //AV.
Рассмотрим, как меняется состояние памяти стека при каждом
обращении. Итак, при первом обращении рекурсивной функции к самой себе
состояние памяти стека будет следующее:
№ п/п Возврат Переменная n Переменная a
1 AV 1234 123

Состояние памяти стека при втором обращении функции к самой себе


будет следующее:
№ п/п Возврат Переменная n Переменная a
2 AV 123 12
1 AV 1234 123

 Языки программирования. Язык С. Конспект лекций -181-


ЛЕКЦИЯ 20. РЕКУРСИЯ
2. Пример рекурсивной функции
Соответственно состояние памяти стека после третьего и четвертого
обращения функции к самой себе будет следующее:

№ п/п Возврат Переменная n Переменная a


4 AV 1 Не определена
3 AV 12 1
2 AV 123 12
1 AV 1234 123

После четвертого обращения функции к самой себе в переменной n


окажется число 1. Так как в этом случае значение n меньше 10, то будет
выполняться код cout << n << "\n", записанный после ключевого слова if.
В результате выполнения этого кода произойдет вывод числа 1 на экран и
компьютер перейдет на завершение работы функции при четвертом вызове.
Далее будут происходить рекурсивные возвраты. При рекурсивном возврате
компьютер возобновляет выполнение незавершенных обращений, т. е. в
приведенном примере компьютер перейдет к третьему обращению и начнет
завершать третий вызов к функции с кода, который в программе условно
обозначен комментарием //AV. Напомним, что адрес возврата, т. е. места,
с которого компьютер будет завершать соответствующий вызов к функции,
сохраняется в стеке. Итак, компьютер выполнит код cout << n%10 << "\n".
В памяти сохранилось значение в переменной n при третьем обращении. Это
значение равно 12. Поэтому в результате выполнения кода cout << n%10 << "\n"
на экран выведется число 2, после чего завершится третье обращение
функции к самой себе. После завершения третьего вызова состояние стека
будет следующим:

№ п/п Возврат Переменная n Переменная a


2 AV 123 12
1 AV 1234 123

Далее произойдет рекурсивный возврат для завершения второго вызова


функции к самой себе. Результатом завершения второго вызова будет вывод
на экран числа 3, а состояние стека после завершения второго вызова будет
следующим:
№ п/п Возврат Переменная n Переменная a
1 AV 1234 123

И наконец, при завершении первого вызова рекурсивной функции на


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

 Языки программирования. Язык С. Конспект лекций -182-


ЛЕКЦИЯ 20. РЕКУРСИЯ
2. Пример рекурсивной функции
обязательно совпадает с числом рекурсивных вызовов. Рекурсивный возврат
происходит всякий раз, когда очередное прерванное или непрерванное
обращение к функции полностью завершается.

3. Формы рекурсивного обращения


Выделим три формы рекурсивной функции по структуре организации
обращения функции к самой себе.
1. Форма выполнения действий до рекурсивного вызова (выполнение
действий происходит на рекурсивном спуске). Структура данной процедуры
представлена ниже.
void rec()
{
s;
if ( <условие> ) rec();
}

или

void rec()
{
if ( <условие>)
{
s1
}
else
{
s2;
rec();
}
}

Здесь и далее под s, s1, s2 подразумевается блок директив.


2. Форма выполнения действий после рекурсивного вызова
(выполнение действий происходит на рекурсивном возврате). Структура
данной процедуры следующая:
void rec()
{
if ( <условие> )
{
rec();
s;
}
}

 Языки программирования. Язык С. Конспект лекций -183-


ЛЕКЦИЯ 20. РЕКУРСИЯ
3. Формы рекурсивного обращения
или

void rec()
{
if ( <условие>)
{
s1;
}
else
{
rec();
s2;
}
}

3. Форма выполнения действий как до, так и после рекурсивного


вызова функции (выполнение действий происходит как на рекурсивном
спуске, так и на рекурсивном возврате). Структура данной процедуры
представлена ниже.
void rec()
{
if ( <условие> )
{
s1;
rec();
s2;
}
}

или

void rec()
{
if ( <условие>)
{
s;
}
else
{
s1;
rec();
s2;
}
}

 Языки программирования. Язык С. Конспект лекций -184-


ЛЕКЦИЯ 20. РЕКУРСИЯ
3. Формы рекурсивного обращения
Продемонстрируем каждую форму рекурсии на конкретном примере
и разберем последовательность ее выполнения.

4. Выполнение действий на рекурсивном спуске

Демонстрацию первой и второй формы рекурсии проведем на задаче


вычисления n! Код программы вычисления n! с использованием рекурсивной
функции приведен ниже.

Программа 4
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
int n, f=1;
void fact( int l );

void main ()
{
clrscr();
cout << "Введи число n= " ;
cin >> n;
fact (1);
getch();
}
void fact( int k )
{
if ( k <= n )
{
f = f * k;
cout << "На шаге k= " << k << " значение f*k = " << f << "\n";
fact ( k+1);
}
}
Для того чтобы провести анализ программы 4, приведем еще код
вычисления n! в нерекурсивном (итерационном) варианте.

Программа 5
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
int n, f=1;
void fact( int l );

void main ()

 Языки программирования. Язык С. Конспект лекций -185-


ЛЕКЦИЯ 20. РЕКУРСИЯ
4. Выполнение действий на рекурсивном спуске
{
clrscr();
cout << "Введи число n= " ;
cin >> n;
fact (1);
getch();
}

void fact( int k )


{
while ( k <= n )
{
f = f * k;
cout << "На шаге k= " << k << " значение f*k = " << f << "\n";
k=k+1;
}
}

Если теперь сравнить коды примера 4, в котором для вычисления n!


используется рекурсия, и примера 5, в котором для вычисления n!
используется итерационный алгоритм, организованный при помощи цикла
while, то увидим, что принципиальной разницы в организации функций fact
в этих программах нет. Более того, из итерационной программы, совершенно
не задумываясь над понятием рекурсии, формально легко получить
рекурсивную функцию вычисления n! Для этого нужно заменить директиву
цикла while (k <= n) на директиву if (k <= n), а директиву k=k+1 на
директиву рекурсивного обращения функции к самой себе, т. е. на код
fact(k+1). На самом деле такой формальный переход от итерационного цикла
к рекурсии справедлив для любого итерационного цикла. Единственное,
к чему надо отнестись с осторожностью, так это к начальным значениям
входных параметров, то есть для того чтобы итерационный цикл

<оператор цикла>
{
<Тело цикла>;
}

заменить на рекурсивную функцию, надо формально этот цикл заменить на


следующую структуру:

void rec(входные параметры)


{
if (<условие окончания цикла>)
{
<Тело цикла>;

 Языки программирования. Язык С. Конспект лекций -186-


ЛЕКЦИЯ 20. РЕКУРСИЯ
4. Выполнение действий на рекурсивном спуске
rec(входные параметры);
}
}

Заметим, что условие окончания цикла определяется конкретным


видом оператора цикла и его управляющими параметрами. При формировании
рекурсивной функции это условие надо включить в оператор if.
Рассмотренный пример показывает, что форма рекурсивного спуска
идентична просто итерационному циклу и отличается от него только
директивами входа в цикл и выходом из цикла. Для иллюстрации
вышесказанного приведем коды нескольких функций, которые реализованы
в виде итерационного цикла и рекурсивного обращения.
Программа 6. Рекурсивная функция ввода элементов массива из файла
и запись элементов массива в файл.
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <iostream.h>
FILE *f1;
void vvod_1(int a[], int i, int n);
void vivd_1(int a[], int i, int n);

void main(void)
{
const n = 10;
int i, a[n];
clrscr();
f1 = fopen("tr.dat", "r");
vvod_1(a,0,n);
fclose(f1);
f1 = fopen("tw.dat", "w");
vivd_1(a,0,n);
fclose(f1);
}
// Рекурсивная функция ввода элементов массива из файла.
void vvod_1(int a[], int i, int n)
{
if ( !feof(f1) )
{
fscanf( f1, "%d", &a[i] );
vvod_1(a,i+1,n);
}
}
// Рекурсивная функция записи элементов массива в файл.

 Языки программирования. Язык С. Конспект лекций -187-


ЛЕКЦИЯ 20. РЕКУРСИЯ
4. Выполнение действий на рекурсивном спуске
void vivd_1(int a[], int i, int n)
{
if( i < n )
{
fprintf( f1, "%d ", a[i] );
vivd_1( a,i+1,n);
}
}

Нерекурсивный вариант программы 6.


Программа 7
#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <iostream.h>
FILE *f1;
void vvod_1(int a[], int i, int n);
void vivd_1(int a[], int i, int n);
void main(void)
{
const n = 10;
int i, a[n];
clrscr();
f1 = fopen("tr.dat", "r");
vvod_1(a,0,n);
fclose(f1);
f1 = fopen("tw.dat", "w");
vivd_1(a,0,n);
fclose(f1);
}

void vvod_1(int a[], int i, int n)


{
do
{
fscanf( f1, "%d", &a[i] );
i++;
} while ( !feof(f1) );
}

void vivd_1(int a[], int i, int n)


{
for ( i = 0; i < n; i++ )

 Языки программирования. Язык С. Конспект лекций -188-


ЛЕКЦИЯ 20. РЕКУРСИЯ
4. Выполнение действий на рекурсивном спуске
fprintf( f1, "%d ", a[i] );
}

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


функций, которые используются для выполнения тех или иных задач.
Программа 8. Вывод на экран чисел от 1 до n.
Итерационный вариант:
void vivod( int k, int n )
{
while ( k <= n)
{
printf("Это – %d\n",k);
k = k+1;
}
}

Рекурсивный вариант:
void vivod( int k, int n )
{
if ( k <= n )
{
printf("Это – %d\n",k);
vivod(k+1, n);
}
x1 x 2 x3 xn
Программа 9. Нахождение суммы ряда S = + + + ... + .
1! 2! 3! n!
Итерационный вариант:
void f_exp(int i, int n)
{
for ( i = 1; i <= n; i++ )
{
f = f * i;
xs = xs * x;
s = s + xs / f;
cout << "Значение суммы на " << i << "-м шаге = " << s << "\n";
}
}

Рекурсивный вариант:
void f_exp(int i, int n)
{ if ( i <= n )
{
f = f * i;
xs = xs * x;

 Языки программирования. Язык С. Конспект лекций -189-


ЛЕКЦИЯ 20. РЕКУРСИЯ
4. Выполнение действий на рекурсивном спуске
s = s + xs / f;
cout << "Значение суммы на " << i << "-м шаге = " << s << "\n";
f_exp(i+1,n);
}}

Замечание. Необъявленные в функции переменные описываются как


глобальные. Это делается для того, чтобы не перегружать коды функций.

 Языки программирования. Язык С. Конспект лекций -190-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)

План
1. Выполнение действий на рекурсивном возврате.
2. Выполнение действий на рекурсивном спуске и возврате.

1. Выполнение действий на рекурсивном возврате


Эта форма рекурсии предполагает, что действия происходят после
рекурсивного обращения. В этом случае, как уже отмечалось выше, все
директивы, которые расположены ниже рекурсивного обращения, временно
не выполняются, а запоминаются в стеке, для того чтобы выполнить их после
завершения рекурсивного обращения. Причем, согласно структуре стека,
выполнение этих директив происходит в порядке, обратном загрузке:
директива, записанная в стек последней, после завершения рекурсивного
обращения выполняется первой. Поэтому следует учитывать выбор
начального параметра загрузки при первом обращении к рекурсивной
функции. Параметры при первом обращении должны быть такими, чтобы по
завершении обращения к функции их значения соответствовали правильному
выполнению последним приостановленным и загруженным в стек
директивам. Проведем разбор данной формы рекурсии на задаче вычисления n!
Для детального разбора дадим два кода данной функции, которые
составлены с учетом того, что входные параметры при первом обращении
к функции различны.
Составим программу вычисления n! При первом обращении к
рекурсивной функции входной параметр равен n.
Программа 1
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
int n, f=1;
void fact( int l );
void main ()
{ clrscr();
cout << "Введи число n= " ;
cin >> n;
fact (n);
getch(); }

// Рекурсивная функция
void fact( int k )
{ if ( k >=1 )
{
fact ( k-1);

 Языки программирования. Язык С. Конспект лекций -191-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
1. Выполнение действий на рекурсивном возврате
// AV
f = f * k;
cout << k << "!= " << f << "\n";
}}

Составим программу вычисления n! При первом обращении к


рекурсивной функции входной параметр равен 1.
Программа 2
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
int n, f=1;
void fact( int l );
void main ()
{ clrscr();
cout << "Введи число n= " ;
cin >> n;
fact (1);
getch(); }
void fact( int k )
{
if ( k <= n )
{
fact ( k+1);
// AV
f = f * k;
cout << k << "!= " << f << "\n";
}}

При рассмотрении данных программ обратим внимание на то, что и эту


форму рекурсивного обращения легко организовать из итерационного цикла
по рассмотренному выше методу. Разница состоит в том, что если ранее все
директивы итерационного цикла располагались до рекурсивного вызова, то
теперь после. И этот формальный переход от итерационного цикла к
рекурсии, в которой действия происходят на рекурсивном возврате, можно
отобразить в следующей схеме.
Итерационный цикл:
<оператор цикла>
{
<Тело цикла>;
}
Рекурсивная функция, соответствующая итерационному циклу:

void rec(входные параметры)

 Языки программирования. Язык С. Конспект лекций -192-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
1. Выполнение действий на рекурсивном возврате
{ if (<условие окончания цикла>)
{
rec(входные параметры);
<Тело цикла>;
} }

Дадим состояние памяти стека при выполнении рекурсивной функции


в программе 1. Рассматривать задачу будем для n = 6. Обратим внимание на
то, что переменные f и n объявлены как глобальные. В кодах программ через
комментарий //AV выделяется место (адрес), с которого начинает
выполняться программа при рекурсивном возврате. После 7 обращений
функции к самой себе состояние памяти стека будет следующим.

№ п/п Возврат Значение k Значение n Значение f


7 AV 0 6 1
6 AV 1 6 1
5 AV 2 6 1
4 AV 3 6 1
3 AV 4 6 1
2 AV 5 6 1
1 AV 6 6 1

После 7-го обращения рекурсивное обращение к функции прекратится.


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

Первый рекурсивный возврат


№ п/п Возврат Значение k Значение n Значение f
5 AV 2 6 1*1
4 AV 3 6 1
3 AV 4 6 1
2 AV 5 6 1
1 AV 6 6 1

Второй рекурсивный возврат


№ п/п Возврат Значение k Значение n Значение f
4 AV 3 6 1*1*2
3 AV 4 6 1
2 AV 5 6 1
1 AV 6 6 1

 Языки программирования. Язык С. Конспект лекций -193-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
1. Выполнение действий на рекурсивном возврате
Третий рекурсивный возврат
№ п/п Возврат Значение k Значение n Значение f
3 AV 4 6 1*2*3
2 AV 5 6 1
1 AV 6 6 1

Четвертый рекурсивный возврат


№ п/п Возврат Значение k Значение n Значение f
2 AV 5 6 1*2*3*4
1 AV 6 6 1

Пятый рекурсивный возврат


№ п/п Возврат Значение k Значение n Значение f
1 AV 6 6 1*2*3*4*5

Шестой рекурсивный возврат


№ п/п Возврат Значение k Значение n Значение f
1 AV 6 6 1*2*3*4*5*6

После 6-го рекурсивного возврата завершается обращение к функции


fakt(), происходит возврат в головную программу, выводится значение 6!, что
завершает работу программы. Итак, при каждом рекурсивном возврате
выполняются следующие коды рекурсивной функции:
f = f * k;
cout << k << "!= " << f << "\n";
Эти коды выполняются при следующих значениях k.

Номер возврата Значение k Значение f


1 k=1 f = 1*1
2 k=2 f = 1*1*2
3 k=3 f = 1*2*3
4 k=4 f = 1*2*3*4
5 k=5 f = 1*2*3*4*5
6 k=6 f = 1*2*3*4*5*6

Анализ работы программы 2 идентичен анализу работы программы 1.


Поэтому сразу приведем состояние стека после завершения рекурсивных
вызовов.

 Языки программирования. Язык С. Конспект лекций -194-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
1. Выполнение действий на рекурсивном возврате
№ п/п Возврат Значение k Значение n Значение f
7 AV 7 6 1
6 AV 6 6 1
5 AV 5 6 1
4 A 4 6 1
3 AV 3 6 1
2 AV 2 6 1
1 AV 1 6 1

Состояние стека отличается только последовательностью значений


переменной k. Далее начнутся рекурсивные возвраты, при которых будут
выполняться следующие коды рекурсивной функции:
f = f * k;
cout << k << "!= " << f << "\n";
Эти коды выполняются при следующих значениях k.

Номер возврата Значение k Значение f


1 k=6 f = 1*6
2 k=5 f = 1*6*5
3 k=4 f = 1*6*5*4
4 k=3 f = 1*6*5*4*3
5 k=2 f = 1*6*5*4*3*2
6 k=1 f = 1*6*5*4*3*2*1

Вычисление факториала в примере 10 происходит в обратном порядке:


6 умножается на 5, затем полученный результат умножается на 4 и т. д.
Отсюда можно сделать вывод о том, что начинать обращение к рекурсивной
программе, выполняющей действие на возврате, с начального значения
параметра не всегда правильно, так как промежуточные данные могут при
вычислении не соответствовать ожидаемым значениям. В данном примере
первое значение оказалось равным 6, второе – 30 и т. д. Такое
рассогласование шагов может привести к неверным результатам. Обычно
при вычислениях требуется, чтобы первое значение равнялось 1!, второе – 2!
и т. д. Совершенно очевидно, что организовать расчет задачи примера 8
рекурсивной функции, которая выполняет действие на возврате, с начальным
входным параметром 1 нельзя.

2. Выполнение действий на рекурсивном спуске и возврате

Рекурсия такой формы была подробно разобрана выше. Здесь приведем


хрестоматийный пример – обращение строки.
Программа 3. Вывести символы введенной строки в обратном порядке.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>

 Языки программирования. Язык С. Конспект лекций -195-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
2. Выполнение действий на рекурсивном спуске и возврате
void rev_str()
{
char c;

c = getch();
putch(c);
if (c != 13)
{
rev_str();
putch(c);
}
else cout <<"\n";
}
void main()
{ clrscr();
cout << "Вводи строку:\n";
rev_str();
getch(); }

Приведем пример рекурсивной функции для вычисления n!, которая


возвращает значение.
float fact( int n )
{ if ( n > 1 )
{
return n*fact(n-1);
}
return 1;}

Для вызова этой функции в головной программе используется


директива s = fact(n). В результате выполнения этой директивы в переменной s
окажется значение n! Для того чтобы выяснить, как выполняется данная
программа, запишем ее в некотором эквивалентном виде:
float fact( int n )
{ int r;
if ( n > 1 )
{
r = fact(n-1);
r = n*r;
// AV
return r; }
return 1; }

Из этого кода следует, что при возвращении значения директива return


выполняется после обращения к рекурсивной функции (т. е. на возврате) и,
значит, все эти возвращения рекурсивная функция временно

 Языки программирования. Язык С. Конспект лекций -196-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
2. Выполнение действий на рекурсивном спуске и возврате
приостанавливает. Итак, рассмотрим трассировку работы данной программы
при n = 6. Как и ранее, комментарий //AV фиксирует место, с которого
компьютер будет завершать временно приостановленное обращение при
рекурсивных возвратах. Трассировку проиллюстрируем состоянием стека
в разные моменты исполнения программы.

Стек после рекурсивных обращений


№ п/п Возврат Значение n Значение r
6 AV 1 r = 1! = 1
5 AV 2 Не определено
4 AV 3 Не определено
3 AV 4 Не определено
2 AV 5 Не определено
1 AV 6 Не определено

Стек после первого возврата


№ п/п Возврат Значение n Значение r
5 AV 2 r = 2*1! = 2!
4 AV 3 Не определено
3 AV 4 Не определено
2 AV 5 Не определено
1 AV 6 Не определено

Стек после второго возврата


№ п/п Возврат Значение n Значение r
4 AV 3 r = 3*2! = 3!
3 AV 4 Не определено
2 AV 5 Не определено
1 AV 6 Не определено

Стек после третьего возврата


№ п/п Возврат Значение n Значение r
3 AV 4 r = 4*3! = 4!
2 AV 5 Не определено
1 AV 6 Не определено

Стек после четвертого возврата


№ п/п Возврат Значение n Значение r
2 AV 5 r = 5*4! = 5!
1 AV 6 Не определено

Стек после пятого возврата


№ п/п Возврат Значение Значение r
1 AV 6 r = 6*5! = 6!

 Языки программирования. Язык С. Конспект лекций -197-


ЛЕКЦИЯ 21. РЕКУРСИЯ (ОКОНЧАНИЕ)
2. Выполнение действий на рекурсивном спуске и возврате
После завершения рекурсивных возвратов происходит возврат
в головную программу и завершается ее выполнение. При возобновлении
прерванных обращений после рекурсивных вызовов будут выполняться
следующие коды:

r = fact(n-1);
r = n*r;
return r;

Они возвращают значение (k–1)! из завершенного возврата в текущий


(выполняемый) рекурсивный возврат и вычисляют значение k!
Приведем еще один пример рекурсивной функции, которая возвращает
значение. Трассировка аналогична предыдущей задаче.
Программа 4. Дан массив. Найти минимальное значение в массиве.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
int N;
int d[100];
// Функция возвращает минимум начиная с k-го элемента.
int min1(int k)
{ int tmp;
if (k == N-1)
return d[k];
else
{
tmp = min1(k+1);
return d[k] < tmp ? d[k] : tmp;
}}
void main()
{FILE *f1;
int i = -1;
clrscr();
f1 = fopen("t1.dat", "r");
cout << "Масиив: \n";
do
{ i++;
fscanf( f1, "%d", &d[i] );
cout << d[i] << " ";
} while ( !feof(f1) );
N=++i;
getch();
fclose(f1);
cout << "\n\nMIN: " << min1(0) << "\n\n";
getch(); }

 Языки программирования. Язык С. Конспект лекций -198-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА

План
1. Быстрая сортировка с использованием рекурсивных функций.
2. Быстрая сортировка с использованием циклов.

1. Быстрая сортировка с использованием рекурсивных функций


Как уже говорилось, используют рекурсию не для того, чтобы
итерационные циклы записывать в другой форме. Рекурсия значительно
упрощает код программы, если алгоритм рекурсивен по своей сути, т. е. если
общая задача сводится к более простой задаче того же класса или подзадача
является уменьшенным аналогом общей задачи. Примером задач этого типа
является алгоритм быстрой сортировки массива.
Пусть дан одномерный массив a[n] размерности n. Тогда алгоритм
быстрой сортировки формулируется следующим образом.
Шаг 1. Выбирается средний элемент массива.
Шаг 2. Массив разбивается на два – левый и правый – относительно
среднего элемента. В левом располагаются элементы меньше среднего,
в правом – больше среднего.
Шаг 3. Далее повторяется шаг 2 для каждого из двух вновь
образованных массивов. Каждый раз при повторении преобразования
очередная часть массива разбивается на два меньших и т. д., пока
не получится массив из двух элементов. На рисунке показана схема быстрой
сортировки для массива, состоящего из 9 элементов [15].

Рис. 22.1

 Языки программирования. Язык С. Конспект лекций -199-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
Очевидно, что в этом случае имеем рекурсивную процедуру, так как
одну и ту же программу надо применять для разных частей массивов,
отличающихся друг от друга размером. Заметим, что после каждого
очередного разбиения в позиции элемента, относительно которого
сортируются все прочие элементы массива, оказывается элемент, положение
которого далее меняться не будет (так как в левой части массива все
элементы должны быть меньше, а в правой части все элементы должны быть
больше элемента, относительно позиции которого происходит
преобразование начального массива). На основании этого замечания из
повторного шага можно исключать позицию элемента, относительно
которого прошло преобразование массива. Составим программу быстрой
сортировки по частям. Сначала выполним шаг 1 алгоритма быстрой
сортировки.

Программа 1. Выполняет шаг 1 быстрой сортировки.


#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void qsort (int a[], int lef, int rit);
void main()
{
const n = 9;
FILE *f1;
int i, a[n];
clrscr();
cout << "Массив до работы программы:\n";
f1 = fopen("t.dat", "r");
for ( i = 0; i <= n-1; i++ )
{
fscanf( f1, "%d", &a[i] );
cout << a[i] << " ";
}
cout << "\n";
fclose(f1);
qsort (a,0,n-1); // Обращение к процедуре быстрой сортировки.
cout << "Массив после работы программы: \n";
for ( i = 0; i <= n-1; i++ )
{
fscanf( f1, "%d", &a[i] );
cout << a[i] << " ";
}
cout << "\n";
getch();
}

 Языки программирования. Язык С. Конспект лекций -200-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
void qsort (int a[], int lef, int rit)
{
int ir, rm, buf; // ir – для индекса среднего элемента
// текущей части массива.
// rm – для значения среднего
// элемента текущего массива.
// buf – для промежуточных значений
// обрабатываемой информации.

int l1, r1; // l1 – для индекса элемента, который


// расположен левее и
// больше среднего.
// r1 – для индекса элемента, который
// расположен правее и
// меньше среднего.
int i; // i – для управляющего параметра цикла.

l1 = lef; // Определяем начальный индекс текущей


// части массива.
r1 = rit; // Определяем конечный индекс текущего
// массива.
ir = (l1+r1)/2; // Определяем индекс среднего элемента
// текущей части массива.
rm = a[ir]; // В rm пересылаем значение среднего
// элемента текущего массива.
do // Цикл для перебора элементов, которые
// находятся левее и правее среднего
// элемента текущей части массива.
{
while ( a[l1] < rm ) l1++; // Находим 1-й элемент, который лежит
// слева и больше среднего элемента.
// Индекс этого элемента фиксируется
// в переменной l1.
while ( a[r1] > rm ) r1--; // Находим 1-й элемент, который лежит
// справа и больше среднего элемента.
// Индекс этого элемента фиксируется
// в переменной r1.

if ( l1 <= r1 ) // Начало перестановки элементов


// массива. Меньший относительно
// среднего переставляем в позицию l1,
// а больший относительно среднего
// переставляем в позицию r1 и директивами
// l1++; и r1++; переходим к рассмотрению
// следующих элементов, расположенных

 Языки программирования. Язык С. Конспект лекций -201-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
// соответственно левее и правее среднего элемента.
{
// Переставляем элементы массива.
buf = a[l1];
a[l1] = a[r1];
a[r1] = buf;
l1++;
r1--;
} // Конец перестановки элементов массива.
}
while ( r1 > l1 );
}

Программа для первого шага быстрой сортировки абсолютно


прозрачна. Необходимые комментарии даны в ее тексте. Результатом работы
программы является новое расположение элементов массива и вычисленные
значения двух индексов r1 и l1, которые определяют соответственно
последний индекс новой левой части массива и первый индекс новой правой
части массива (диапазоны изменения индексов новых массивов, для которых
надо опять применить программу шага 1 быстрой сортировки, определяются
неравенствами lef <=i<=r1, l1<=i<=rit). Теперь нужно организовать
повторение в цикле шага 1 быстрой сортировки, но только с другими
значениями входных параметров. Понятно, что для этой цели можно
использовать рекурсию, предусмотрев окончание рекурсивного вызова.
Директива if (lef < r1) qsort(a, lef, r1); полностью отвечает поставленной
задаче. Текст данного кода приведен ниже.

Программа 2. Первый этап быстрой сортировки.


#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void qsort (int a[], int lef, int rit);
void main()
{
const n = 9;
FILE *f1;
int i, a[n];
clrscr();
cout << "Массив до работы программы:\n";
f1 = fopen("t.dat", "r");
for ( i = 0; i <= n-1; i++ )
{
fscanf( f1, "%d", &a[i] );
cout << a[i] << " ";

 Языки программирования. Язык С. Конспект лекций -202-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
}

cout << "\n";


fclose(f1);
qsort (a,0,n-1); // Обращение к процедуре быстрой сортировки.
cout << "Массив после работы программы: \n";
for ( i = 0; i <= n-1; i++ )
{
fscanf( f1, "%d", &a[i] );
cout << a[i] << " ";
}
cout << "\n";
getch();
}

void qsort (int a[], int lef, int rit)

{
int ir, rm, buf; // ir – для индекса среднего элемента
// текущей части массива.
// rm – для значения среднего
// элемента текущей части массива.
// buf – для промежуточных значений
// обрабатываемой информации.
int l1, r1; // l1 – для индекса элемента, который
// расположен левее среднего и
// больше среднего элемента.
// r1 – для индекса элемента, который
// расположен правее и
// меньше среднего элемента.
int i; // i – для управляющего параметра цикла.
l1 = lef; // Определяем начальный индекс текущей части
// массива.
r1 = rit; // Определяем конечный индекс текущей части
// массива.
ir = (l1+r1)/2; // Определяем индекс среднего элемента
// текущей части массива.
rm = a[ir]; // В rm пересылаем значение среднего
// элемента текущей части массива.
do // Цикл для перебора элементов, которые
// находятся левее и правее среднего
// элемента текущей части массива.
{

 Языки программирования. Язык С. Конспект лекций -203-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
while ( a[l1] < rm ) l1++; // Находим 1-й элемент, который лежит
// слева и больше среднего элемента.
// Индекс этого элемента фиксируется
// в переменной l1.
while ( a[r1] > rm ) r1--; // Находим 1-й элемент, который лежит
// справа и больше среднего элемента.
// Индекс этого элемента фиксируется
// в переменной r1.
if ( l1 <= r1 ) // Начало перестановки элементов
// массива. Меньший относительно
// среднего переставляем в позицию l1,
// а больший относительно среднего
// переставляем в позицию r1 и директивами
// l1++; и r1++; переходим к рассмотрению
// следующих элементов, расположенных
// соответственно левее и правее среднего элемента.
// Переставляем элементы массива.
{
buf = a[l1];
a[l1] = a[r1];
a[r1] = buf;
l1++;
r1--;
} // Конец перестановки элементов массива.
}
while ( r1 > l1 );
if ( lef < r1 ) qsort(a, lef, r1);
}
Ясно, что программа 2 не выполнит задачу сортировки массива, так как
фактически она не обрабатывает те части массива, диапазон изменения
индексов которых определялся неравенством l1<=i<=rit. На самом деле
итогом работы данной программы будет правильная расстановка всего-навсего
двух первых элементов преобразованного массива. Теперь, для того чтобы
решить задачу сортировки, надо возвратиться к каждой, ранее полученной и
необработанной части массива и преобразовать каждую такую часть,
используя программу 2. Таким образом, надо повторить в цикле программу 2
для массивов, которые ранее обозначались в процессе работы, но не
обрабатывались. Для циклического повторения программы 2 опять можно
использовать рекурсивное обращение к функции. В этом случае получим
окончательный вариант программы быстрой сортировки массива.

Программа 3. Быстрая сортировка массива.


#include <iostream.h>
#include <conio.h>

 Языки программирования. Язык С. Конспект лекций -204-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
#include <stdio.h>
void qsort (int a[], int lef, int rit);
void main()
{
const n = 9;
FILE *f1;
int i, a[n];
clrscr();
f1 = fopen("t.dat", "r");
for ( i = 0; i <= n-1; i++ )
fscanf( f1, "%d", &a[i] );
fclose(f1);
cout << " Массив до сортировки:\n";
for ( i = 0; i <= n-1; i++ )
printf( "%d ", a[i] );
qsort (a,0,n); // Обращение к процедуре быстрой сортировки.
// AVM
cout << "\n\n Массив после сортировки: \n";
for ( i = 0; i <= n-1; i++ )
printf( "%d ", a[i] );
getch();
}
// Рекурсивная функция быстрой сортировки.
void qsort (int a[], int lef, int rit)
{
int l1, r1; // l1 – для первого индекса второй части массива.
// r1 – для последнего индекса первой части массива.
// lef – первый индекс всего массива.
// rit – последний индекс всего массива.

int rm, buf, ir;


l1 = lef; // Устанавливаем начальное значение l1 = lef.
r1 = rit; // Устанавливаем начальное значение r1 = rit.
ir = (l1+r1)/2; // Вычисляем индекс среднего элемента массива.
rm = a[ir];
do
{

while ( a[l1] < rm ) l1++; // Находим 1-й элемент, который лежит


// слева и больше среднего элемента.
// Индекс этого элемента фиксируется
// в переменной l1.
while ( a[r1] > rm ) r1--; // Находим 1-й элемент, который лежит
// справа и больше среднего элемента.
// Индекс этого элемента фиксируется

 Языки программирования. Язык С. Конспект лекций -205-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
// в переменной r1.
if ( l1 <= r1 )
{
buf = a[l1];
a[l1] = a[r1];
a[r1] = buf;
l1++;
r1--;
}

}
while ( r1 > l1 );
if ( lef < r1 ) qsort(a, lef, r1);
//AV
if ( l1 < rit ) qsort(a, l1, rit);
//AV1
}

Подобные рассуждения, результатом которых явилась рекурсивная


программа быстрой сортировки, могут помочь при составлении рекурсивных
функций при решении и некоторых других задач. Ясно, что единой техники
при проектировании рекурсивных программ (как, впрочем, и не только
рекурсивных) не существует. Самое главное в программировании – это
понимать принципы и, отталкиваясь от них, совершенствовать навыки,
приемы и технологию реализации решения задач на компьютере.
Далее отметим, что включенная в программу 2 последняя директива if
(l1<rit) qsort(a,l1,rit); делает ее код принципиально отличным от кода
программы алгоритма быстрой сортировки без рекурсивного обращения.
Если теперь попробовать реализовать алгоритм быстрой сортировки с
использованием операторов циклов, то потребуется создать конкретную
технику, которая бы позволяла запоминать начальный и конечный индексы
массивов, временно исключаемых из рассмотрения, и затем возвращаться
к этим частям массивов для их обработки. В то же время техника
рекурсивного вызова функции автоматически позволяет запоминать в стеке
значения локальных параметров и при возобновлении прерванных
обращений использовать эти параметры, чтобы корректно завершить ранее
прерванное выполнение рекурсивной функции.
Рассмотрим подробнее трассировку работы программы qsort() для
массива размерности n = 9. Чтобы детали трассировки были более понятны,
приведем общую схему трассировки. Каждый раз при входе в функцию qsort
через локальные переменные lef и rit передаются первый и последний
индексы части массива, который подвергается преобразованию. А именно:
часть массива с первым индексом lef и последним индексом rit, в свою
очередь, разбивается функцией qsort на два массива. Первый и последний

 Языки программирования. Язык С. Конспект лекций -206-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
индексы этих новых двух частей массива соответственно определяются
в переменных lef, r1 и l1, rit. Итак, перед выполнением директив
рекурсивного обращения

if ( lef < r1 ) qsort(a, lef, r1),


// AV
if ( l1 < rit ) qsort(a, l1, rit);
// AV1

определены значения двух пар локальных переменных lef, r1 и l1, rit. Если
оказывается, что неравенство lef < r1 справедливо, то прерывается текущее
обращение к функции и выполняется новое рекурсивное обращение
qsort(a, lef, r1). В результате этого нового рекурсивного обращения в память
стека размещается набор локальных параметров и адрес (//AV), с которого
в определенный момент возобновится прерванное текущее обращение.
Аналогично: если справедливо неравенство l1 < rit, то выполняется
рекурсивное обращение qsort(a, l1, rit) и в память стека размещается набор
локальных параметров и адрес (//AV1), с которого в определенный момент
возобновится прерванное текущее обращение. Рекурсивные возвраты
происходят тогда, когда не происходит рекурсивного вызова. Заметим, что
прерванные обращения начинаются либо с директивы, которая следует после
комментария //AV, либо с директивы, которая следует за комментарием //AV1.
Но после комментария //AV1 выполнение функции завершается. Поэтому
если рекурсивный возврат начинает выполняться после комментария //AV1,
то тут же происходит следующий рекурсивный возврат. Теперь перейдем к
более детальной трассировке работы программы qsort().
Итак, при вызове qsort() из головной программы в локальные
переменные lef и rit соответственно передадутся числа 0 и 8. После входа
в функцию программа выполняет основной шаг 2 алгоритма быстрой
сортировки, а именно: массив разбивается на два массива – левый и правый –
относительно среднего элемента. В левом располагаются элементы меньше
среднего, в правом – больше среднего.
Первый и последний индексы первой части массива засылаются
соответственно в переменные lef и r1, а в ll и rit засылаются соответственно
первый и последний индексы второй части массива. Перед выполнением
директивы if (lef < r1) qsort(a, lef, r1) в перечисленных выше переменных
будут определены следующие значения: lef = 0, r1 = 3, ll = 5, rit = 8.
Поэтому при первом рекурсивном обращении функции к самой себе
состояние памяти стека будет следующим.

Состояние стека при первом рекурсивном обращении к функции


№ п/п Глубина Возврат lef r1 ll rit
1 1 AV 0 3 5 8

 Языки программирования. Язык С. Конспект лекций -207-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
Далее программа повторит шаг 2 с индексами lef = 0 и rit = 3. После
завершения шага 2 значения в переменных lef, r1, ll, rit будут
соответственно равны 0, 1, 2, 3. Так как lef < r1, то произойдет второе
рекурсивное обращение к функции qsort().

Состояние стека при втором рекурсивном обращении к функции


№ п/п Глубина Возврат lef r1 ll rit
2 2 AV 0 1 2 3
1 1 AV 0 3 5 8

Далее программа повторит шаг 2 с индексами lef = 0 и rit = 1. После


завершения шага 2 значения в переменных lef, r1, ll, rit будут
соответственно равны 0, –1, 1, 1. Так как в этом случае lef > r1 и ll = rit, то
рекурсивного обращения не произойдет ни при выполнении кода if (lef< r1)
qsort(a, lef, r1), ни при выполнении кода if (l1 < rit) qsort(a, l1, rit).
Произойдет первый рекурсивный возврат. Это означает, что из стека
извлекаются локальные переменные с шага 2 и работа функции
возобновляется с директивы if (l1 < rit) qsort(a, l1, rit). Параметр ll = 2,
а rit = 3. Так как неравенство ll < rit истинно, то происходит третье
обращение к рекурсивной функции.

Состояние стека при третьем рекурсивном обращении к функции


№ п/п Глубина Возврат lef r1 ll rit
3 2 AV1 0 1 2 3
1 1 AV 0 3 5 8

При третьем обращении программа повторит шаг 2 с индексами lef = 2


и rit = 3. После завершения шага 2 значения переменных lef, r1, ll, rit будут
соответственно равны 2, 1, 3, 3. Так как в этом случае lef > r1 и ll = rit, то
рекурсивного обращения не произойдет ни при выполнении кода if (lef< r1)
qsort(a, lef, r1), ни при выполнении кода if (l1 < rit) qsort(a, l1, rit).
Произойдет второй рекурсивный возврат. Из стека извлекается состояние,
которое характеризуется строкой с номером 3. Но после метки //AV1 работа
функции завершается. Поэтому произойдет третий рекурсивный возврат.

Состояние стека после второго рекурсивного возврата


№ п/п Глубина Возврат lef r1 ll rit
1 1 AV 0 3 5 8

Из стека извлекается состояние, которое характеризуется строкой


с номером 1, и компьютер начинает выполнять директивы, которые
находятся после метки //AV. Работа функции возобновится с директивы
qsort(a, l1, rit), потому что ll = 5, rit = 8 и неравенство ll < rit выполняется.
Кстати, заметим, что после второго рекурсивного возврата стек пуст и только
при выполнении директивы qsort(a, l1, rit) в стек запишется новое состояние.

 Языки программирования. Язык С. Конспект лекций -208-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций

Состояние стека при четвертом рекурсивном обращении к функции


№ п/п Глубина Возврат lef r1 ll rit
4 1 AV1 0 3 5 8

Далее программа повторит шаг 2 с индексами lef = 5 и rit = 8. После


завершения шага 2 значения в переменных lef, r1, ll, rit будут
соответственно равны 5, 7, 7, 8. Так как lef < r1, произойдет следующее
рекурсивное обращение к функции qsort(). В стек занесется новое состояние.

Состояние стека при пятом рекурсивном обращении к функции


№ п/п Глубина Возврат lef r1 ll rit
5 2 AV 5 7 7 8
4 1 AV1 0 3 5 8

После пятого обращения к функции программа повторит шаг 2


с индексами lef = 5 и rit = 7. После завершения шага 2 значения
в переменных lef, r1, ll, rit будут соответственно равны 5, 5, 7, 7. И так как
в этом случае lef = r1 и ll = rit, то рекурсивного обращения не произойдет
ни при выполнении кода if (lef< r1) qsort(a, lef, r1), ни при выполнении кода
if (l1 < rit) qsort(a, l1, rit). Произойдет четвертый рекурсивный возврат. Из
стека извлекается состояние, которое характеризуется строкой с номером 5.

Состояние стека после четвертого рекурсивного возврата


№ п/п Глубина Возврат lef r1 l rit
4 1 AV1 0 3 5 8

Далее возобновится прерванное пятое обращение к функции и начнут


выполняться директивы, которые находятся после метки //AV. В результате
работа функции возобновится с директивы qsort(a, l1, rit), потому что ll = 7,
rit = 8 и неравенство ll < rit выполняется. Напомним, что после рекурсивного
возврата параметры lef, r1, ll, rit соответственно равны 5, 7, 7, 8. Состояние
стека после шестого обращения к рекурсивной функции будет следующим.

Состояние стека при шестом рекурсивном обращении к функции


№ п/п Глубина Возврат lef r1 ll rit
6 2 AV1 5 7 7 8
4 1 AV1 0 3 5 8

После шестого обращения к функции программа повторит шаг 2


с индексами lef = 7 и rit = 8. После завершения шага 2 значения
в переменных lef, r1, ll, rit будут соответственно равны 7, 6, 8, 8. Так как
в этом случае lef > r1 и ll = rit, то рекурсивного обращения не произойдет
ни при выполнении кода if (lef< r1) qsort(a, lef, r1), ни при выполнении кода
if (l1 < rit) qsort(a, l1, rit). Произойдет пятый рекурсивный возврат. Из стека

 Языки программирования. Язык С. Конспект лекций -209-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
1. Быстрая сортировка с использованием рекурсивных функций
извлекается состояние, которое характеризуется строкой с номером 6.
Программа начнет выполнять директивы, которые находятся после метки
//AV1, т. е. работа функции завершается. Поэтому произойдет шестой
рекурсивный возврат. Из стека извлекается состояние, которое
характеризуется строкой с номером 4. Программа начнет выполнять
директивы, которые находятся после метки //AV1, т. е. работа функции
завершается. А так как стек после шестого рекурсивного возврата пуст, то
работа функции быстрой сортировки завершается.

2. Быстрая сортировка с использованием циклов


В заключение приведем нерекурсивный код программы быстрой
сортировки.

Программа 4
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void qsort (int a[], int lef, int rit);
FILE *f1;
const n = 20;
void main()
{
int i, a[n];
clrscr();
f1 = fopen("speed_w.dat", "r");
for (i = 0; i <= n-1; i++)
fscanf( f1, "%d", &a[i] );
fclose(f1);
qsort (a,0,n-1); // Обращение к процедуре быстрой сортировки.
for (i = 0; i <= n-1; i++ )
printf("%d ", a[i]);
getch();
}

void qsort (int a[], int lef, int rit)


{
int i;
int l1, r1, st, ir;
int stak1[n] = { 0 }, stak2[n] = { 0 };
int buf, rm;
st = 0;
stak1[0] = lef;
stak2[st] = rit;
do // Начало цикла, который заменяет рекурсивное
// обращение.

 Языки программирования. Язык С. Конспект лекций -210-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
2. Быстрая сортировка с использованием циклов
{
lef = stak1[st]; // Восстанавливается из стека левый индекс
// части массива, для которого будет выполняться
// шаг быстрой сортировки.
rit = stak2[st]; // Восстанавливается из стека правый индекс
// части массива, для которого будет выполняться
// шаг быстрой сортировки.
st--; // Так как из стеков выбраны индексы, размер
// стеков уменьшается на 1.
do
{ l1 = lef; r1 = rit;
ir = (l1+r1)/2;
rm = a[ir];
do
{
while ( a[l1] < rm ) l1++;
while ( a[r1] > rm ) r1--;
if ( l1 <= r1 )
{
buf = a[l1];
a[l1] = a[r1];
a[r1] = buf;
l1++;
r1--;
} }
while ( r1 > l1 );
if ( l1 < rit ) // Начало записи в стек индексов частей массивов,
// которые формируются в процессе
// работы программы.
{ st++;
stak1[st] = l1;
stak2[st] = rit;
} // Конец записи индексов массива
// в стек.
rit = r1; // Формируем правый индекс левой
// части обработанного массива.
}
while ( rit >= lef );
}
while ( st >= 0 ); // Цикл выполняется, пока стек не пуст.
}

Прокомментируем разницу кодов программ примеров 15 и 16.


1. Рекурсивный вызов

 Языки программирования. Язык С. Конспект лекций -211-


ЛЕКЦИЯ 22. БЫСТРАЯ СОРТИРОВКА
2. Быстрая сортировка с использованием циклов
if (lef < r1) qsort(a, lef, r1);
if (l1 < rit) qsort(a, l1, rit);

заменяется циклом с постусловием

do {Тело цикла}
while (st >= 0);

В нерекурсивной процедуре вводятся два массива stak1[n] и stak2[n],


которые служат для хранения соответственно левых и правых позиций
получаемых в процессе решения задачи частей массивов, обработка которых
временно откладывается. Коды программ 3 и 4 отличаются только теми
фрагментами, которые обеспечивают правильную загрузку массивов stak1 и
stak2 параметрами l1 и rit.
Сложность перехода от рекурсивных программ к нерекурсивным
зависит от количества параметров, которые необходимо запомнить, для того
чтобы затем вернуться к ним и возобновить вычисления по сформулирован-
ному алгоритму. И, конечно, эта сложность зависит от логики рекурсивных
вызовов в программе. Такой переход бывает далеко не тривиальным.

 Языки программирования. Язык С. Конспект лекций -212-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ

План
1. Односвязный список.
2. Формирование списка.
3. Операции над списком.
4. Программа обработки списка.

Массив – широко используемая структура данных в задачах, при


решении которых размер массива не меняется. Такие задачи, как правило,
возникают в физике, механике, вычислительной математике и во многих
других областях науки и техники. Но в чисто информационных задачах
объем обрабатываемых данных постоянно меняется. Использовать в таких
задачах массивы, конечно, можно, но это весьма затратный метод, особенно
для реальных задач с большим объемом данных. Связано это с тем, что две
операции над массивом – удалить и/или добавить элемент – трудоемки.
Фактически при выполнении этих операций надо переписывать большие
объемы информации из одной области памяти в другую. Поэтому для
информации, которая в процессе работы программы изменяется в размерах,
используются так называемые динамические структуры данных (ДСД) –
структуры, которые формируются в процессе выполнения программы. Для
этих структур операции «удалить и/или добавить элемент» не связаны с
глобальной передислокацией данных. К таким структурам относятся
связанные списки, деревья и другие структуры, основанные на технике
формирования списков. Далее будут рассмотрены только связанные списки.

1. Односвязный список

Односвязные списки относятся к динамическим структурам данных,


которые характеризуются следующим:
1) память под данные выделяется по мере необходимости;
2) из этой структуры можно исключить элемент, который по тем или
иным условиям задачи в дальнейшем не будет востребован. Элементом
(узлом, звеном) списка является структура, в которой обязательно
присутствует поле указателя на эту структуру. Односвязный список
представляет собой множество таких узлов. При этом каждый элемент
данного списка содержит адрес следующего элемента – ссылку на
следующий элемент списка.
Чтобы сформировать список, надо следовать такому алгоритму:

 Языки программирования. Язык С. Конспект лекций -213-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
1. Односвязный список

• определить структуру, в которой обязательно присутствует поле


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

struct node
{
int inf; // Поле для записи целых чисел.
node *next; // Поле указателя для записи адресов элементов типа node.
};

Ниже, на рис. 23.1–23.32 элемент, который определяется структурой


node, будет изображаться прямоугольником, состоящим из двух частей. Одна
часть будет иллюстрировать поле для чисел inf, другая – поле для указателей
next. Для формирования списка нам потребуются три переменные типа node.
Переменная fr – для адреса первого элемента списка; переменная r – для
адреса нового узла списка; переменная er – для адреса последнего узла
списка. При объявлении переменных node *fr = NULL, *r, * er; выделяется
три области памяти fr, r, er для адресов переменных типа node (рис. 23.1).
Отметим, что значения в переменных r и er не определены.

2. Формирование списка

Формирование списка из одного элемента. Для формирования первого


элемента списка надо использовать код r = new node. При выполнении этого
кода выделяется область памяти для элемента типа node и адрес этой области
памяти заносится в указатель r. Именно эта область памяти и будет первым
элементом списка.

 Языки программирования. Язык С. Конспект лекций -214-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
2. Формирование списка
r

Fr NULL

er

Рис. 23.1

Выполнение этой директивы представлено на схемах рис. 23.2, где


левая часть объекта node соответствует полю inf структуры, а правая – полю
next, которое является указателем типа node.
После того как создан первый элемент списка, его надо
инициализировать, т. е. заслать информацию в поля inf и node нового
объекта. Код r -> inf = a в поле inf первого элемента списка пересылает
значение из переменной a (рис. 23.3).
При выполнении кода r -> next = NULL в поле указателя next первого
элемента засылается значение NULL (рис. 23.4).
Для завершения формирования первого элемента списка нужно
выполнить еще два кода: fr = r и er = r.

r A1

A1 inf next
Fr NULL

er

Рис. 23.2

r A1

A1 inf next
Fr NULL a1

er

Рис. 23.3

 Языки программирования. Язык С. Конспект лекций -215-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
2. Формирование списка
r A1

A1 inf next
Fr NULL a1 NULL

er

Рис. 23.4

r A1

A1 inf next
Fr A1 a1 NULL

er

Рис. 23.5

r A1

A1
Fr A1 a1 NULL

er A1

Рис. 23.6

При выполнении директивы fr = r в указатель fr занесется значение


A1 – адрес первого элемента списка (рис. 23.5). При выполнении директивы
er = r в указатель er занесется значение A1 – адрес нового (в данном случае
первого) элемента списка. На этом завершается формирование списка,
который состоит из одного элемента (рис. 23.6).
Формирование списка из двух элементов. Далее при следующем
выполнении кода r = new node выделится новая область памяти для нового
(второго) элемента списка типа node. Значение A2 адреса этой области
памяти заносится в указатель r (рис. 23.7).

 Языки программирования. Язык С. Конспект лекций -216-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
2. Формирование списка

r A2

A1 A2
Fr A1 a1 NULL

er A1

Рис. 23.7

При выполнении директивы r -> inf = a в поле inf второго элемента


списка пересылается значение из переменной a (рис. 23.8). При выполнении
директивы r -> next = NULL в поле указателя next второго элемента
засылается значение NULL (рис. 23.9).

r A2

A1 A2
Fr A1 a1 NULL a2

er A1

Рис. 23.8

r A2

A1 A2
Fr A1 a1 NULL a2 NULL

er A1

Рис. 23.9

r A2

A1 A2
Fr A1 a1 A2 a2 NULL

er A1

Рис. 23.10

 Языки программирования. Язык С. Конспект лекций -217-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
2. Формирование списка

r A2

A1 A2
Fr A1 a1 A2 a2 NULL

er A2

Рис. 23.11

Теперь нам надо присоединить новый (второй) сформированный узел


списка к предыдущему (первому) узлу списка. Это достигается выполнением
кода:

er -> next = r.

При выполнении этой директивы в поле указателя next первого


элемента списка занесется значение A2 адреса второго элемента списка. Тем
самым новый (второй) объект типа node присоединяется к списку из одного
элемента (рис. 23.10).
При выполнении директивы er = r в указатель er занесется значение A2
адреса второго элемента списка. На этом завершается формирование списка,
который состоит из двух элементов (рис. 23.11).
Формирование списка из трех элементов. При выполнении директивы
r = new node выделяется область памяти для нового (третьего) элемента
списка типа node. Значение A3 адреса этой области памяти заносится в
указатель r (рис. 23.12).
При выполнении директивы r -> inf = a в поле inf третьего элемента
списка пересылается значение из переменной a (рис. 23.13). При выполнении
директивы r -> next = NULL в поле указателя next третьего элемента
засылается значение NULL (рис. 23.14).
При формировании третьего элемента списка (так как fr ≠ NULL)
выполняется директива er -> next = r. При этом в поле указателя next
второго элемента списка занесется значение A3 адреса третьего элемента
списка. Тем самым новый (третий) объект типа node присоединяется к
списку из двух элементов (рис. 23.15).

r A3

A1 A2 A3
Fr A1 a1 A2 a2 NULL

er A2

Рис. 23.12

 Языки программирования. Язык С. Конспект лекций -218-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
2. Формирование списка
r A3

A1 A2 A3
Fr A1 a1 A2 a2 NULL a3

er A2

Рис. 23.13

r A3

A1 A2 A3
Fr A1 a1 A2 a2 NULL a3 NULL

er A2

Рис. 23.14

r A3

A1 A2 A3
Fr A1 a1 A2 a2 A3 a3 NULL

er A2

Рис. 23.15

r A3

A1 A2 A3
Fr A1 a1 A2 a2 A3 a3 NULL

er A3

Рис. 23.16

При выполнении директивы er = r в указатель er занесется значение


A3 адреса третьего элемента списка. На этом завершается формирование
списка, который состоит из трех элементов (рис. 23.16).
Далее процесс формирования списка продолжается до тех пор, пока
не завершится ввод начальных данных из файла. Причем заметим, что
последовательность кодов при формировании очередного элемента списка
абсолютна одинакова. Выпишем эту последовательность:

 Языки программирования. Язык С. Конспект лекций -219-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
2. Формирование списка
r = new node; – создается новый элемент списка, выделяется память
для нового элемента;
r->inf = a; – поле inf нового элемента списка;
r->next = NULL; – инициализируется поле указателя нового элемента
списка;
er -> next = r; – элемент присоединяется к списку;
er = r; – новый элемент объявляется последним.
В результате многократного повторения этих кодов программа
сформирует список из n элементов (n – количество чисел в файле). Этот
список можно представить в виде схемы, приведенной на рис. 23.17.

r An

Fr A1
A1
a1 A2
A2
a2 A3 ... An an NULL

er An

Рис. 23.17

В заключение заметим, что адреса A1, A2, A3, ..., An, которые
использовались в схемах, в реальных задачах принимают конкретные
значения. Более того, при желании значения этих адресов можно вывести на
экран, например, используя код

cout << er << “ “ << fr << “ “ << fr -> next.

3. Операции над списком

Вывод списка на экран. При выполнении директивы r = fr адрес A1 из


указателя fr пересылается в указатель r. После этой директивы через
указатель r доступны все поля первого элемента списка. Далее в цикле
происходит выполнение следующих кодов:

cout << r -> inf << "\n"; – вывод на экран поля inf элемента списка,
адрес которого записан в указателе r;
r = r -> next; – в указатель r засылается адрес следующего
элемента списка.

На рис. 23.18 показан фрагмент списка при входе в цикл.

 Языки программирования. Язык С. Конспект лекций -220-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
r A1

A1 A2
a1 A2 a2 A3

Рис. 23.18

В этом состоянии на экран выводится поле inf (т. е. число a1) элемента
списка, адрес которого содержится в r. На рис. 23.19 приведен фрагмент
списка после выполнения директивы r = r -> next.
r A2

A1 A2
a1 A2 a2 A3

Рис. 23.19

Тем самым после каждого выполнения директивы r = r -> next в


указатель r пересылается информация из поля next текущего элемента
списка, а в этом поле содержится адрес следующего элемента списка, то есть
директивой r = r -> next осуществляется переход от одного элемента списка
к другому. После перехода к следующему элементу открывается доступ к его
полям. Здесь можно провести аналогию перебора элементов списка с
перебором элементов массива при использовании цикла for (i = 0; i < n; i++).
Код r = r -> next фактически соответствует коду i++, который и
обеспечивает переход от элемента массива с индексом i к элементу массива с
индексом (i + 1).
Программа 1. Программа формирует односвязный список и выводит
сформированный список на экран. Информация для элементов списка
вводится из файла с именем t.dat. В файле записан массив целых чисел.
Первый сформированный элемент списка является головным.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
// Определяется структура. Заказываем необходимый размер
// памяти для записи информации о новом объекте.
struct node
{
int inf; // Поле для записи целых чисел.
node *next; // Поле указателя для записи адресов элементов типа node.
};
void main()
{
node *r, *fr = NULL, *er; // fr – указатель на головной элемент списка.
// er – указатель на последний элемент списка.

 Языки программирования. Язык С. Конспект лекций -221-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
// r – указатель для формирования нового узла списка.
int a; // a – переменная для ввода целых чисел из файла.
clrscr();
FILE *f;
f = fopen("t.dat","r");
// Начало формирования списка.
do // Начало цикла ввода чисел из файла.
{ fscanf(f,"%d", &a); // Ввод числа из файла.
r = new node; // Создаем новый элемент списка. Выделяем
// память для нового элемента.
r->inf = a; // Инициализируем поле inf нового элемента списка.
r->next = NULL; // Инициализируем поле указателя нового элемента
// списка.
if ( fr == NULL) // Проверяем: список существует или нет. Если
// fr = NULL, то списка нет.
fr = r; // Поэтому новый (первый) элемент объявляем головным.
else // Если список существует, то
er -> next = r; // новый элемент присоединяем к списку.
er = r; // Новый элемент объявляем последним.
}
while (!feof(f)); // Конец цикла ввода чисел из файла.
fclose(f);
// Конец формирования списка.

// Вывод списка на экран.


r = fr;
while (r != NULL) // Пока не дошли до последнего элемента списка.
{
cout << r -> inf << "\n"; // Вывод информации из поля inf элемента,
// адрес которого находится в указателе r.
r = r -> next; // Переход к следующему элементу списка.
// Для этого из поля next текущего элемента списка
} // в указатель r пересылаем адрес на следующий элемент.
getch(); }

Поиск элемента в списке. Для поиска элемента списка необходимо:


• перебрать элементы списка;
• поля inf элементов списка сравнивать со значением, которое надо найти.
Это обеспечивает следующий фрагмент программы.
clrscr();
cout << "\nВведи информацию для поиска k = ";
cin >> a;
r = fr; // Адрес 1-го элемента списка засылаем в указатель r.

 Языки программирования. Язык С. Конспект лекций -222-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
while ( r!= NULL )
{ if ( r-> inf == a ) // Если нашли элемент списка, равный a, завершить
break; // поиск.
r = r -> next; } // Код перебора элементов списка.

Если элемент со значением a в списке присутствует, то его адрес будет


находиться в указателе r. В противном случае значение в указателе r будет
равно NULL. Следующий набор директив устанавливает, найден требуемый
элемент списка или нет.
if ( r -> inf == a )
cout << "Элемент с информацией k = " << r -> inf << " найден.\n";
else
cout << "Элемент с информацией k = " << a << " не найден.\n";
getch();

Код фрагмента поиска элемента с заданным значением

while (r != NULL)
{ if (r-> inf == a) break;
r = r -> next;
}
можно заменить на следующий:
while (r != NULL && r -> inf != a)
r = r -> next;
Удаление элемента из списка. Решение данной задачи зависит от
расположения элемента в списке. Способ удаления головного (первого)
элемента списка отличается от способа удаления любого другого элемента.
Рассмотрим фрагмент списка, представленный на рис. 23.20.

A1 A2
Fr A1 a1 A2 a2 A3

Рис. 23.20

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


следующие директивы:

r = fr;
fr = fr -> next;
delete r;

 Языки программирования. Язык С. Конспект лекций -223-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
r A1

A1 A2
Fr A2 a1 A2 a2 A3

Рис. 23.21

r A1

A2
Fr A2 a2 A3

Рис. 23.22

На рис. 23.21 демонстрируется фрагмент списка после выполнения


первого и второго кода.
Первый элемент списка еще доступен через указатель r, и область
памяти, которую занимает первый элемент, находится под контролем
программы. На рис. 23.22 демонстрируется этот же фрагмент списка после
выполнения кода delete r.
Напомним, что директива delete r освобождает использованный
программой участок памяти, адрес которого содержался в указателе r.
Для того чтобы удалить элемент со значением b ≠ a1, надо:
• найти элемент списка, для которого b = ak;
• при поиске сохранить в указателе значение адреса на предыдущий
элемент списка.
Это обеспечивает следующий фрагмент программы.
cout << "\n\nУдалить узел cо значением k = ";
cin >> a;
r = fr;
while ((r -> inf != a) && (r != NULL))
{
rp = r; // В указателе rp сохраняется адрес на
// предыдущий элемент списка.
r = r -> next; // Переход на следующий элемент списка.
}

Если при выполнении данного фрагмента элемент списка со значении-


ем b был найден, то это соответствует схеме на рис. 23.23.
Для того чтобы удалить элемент списка со значением ak = b, необходимо
выполнить следующие две директивы:

 Языки программирования. Язык С. Конспект лекций -224-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
rp->next = r->next;
delete r;

На рис. 23.24 показан фрагмент списка после выполнения первого кода,


на рис. 23.25 – фрагмент списка после выполнения кода delete r.

r Ak

A[k-1] Ak A[k+1]
a[k-1] Ak ak A[k+1] a[k+1] A[k+2]

rp A[k-1]

Рис. 23.23

r Ak
A[k-1] Ak A[k+1]
a[k-1] A[k+1] ak A[k+1] a[k+1] A[k+2]

rp A[k-1]

Рис. 23.24

r Ak
A[k-1] A[k+1]
a[k-1] A[k+1] a[k+1] A[k+2]

rp A[k-1]

Рис. 23.25

Программа 2. Программа формирует односвязный список и удаляет


элемент из списка. Информация для элементов списка вводится из файла с
именем t.dat. В файле записан массив целых чисел. Первый сформированный
элемент списка является головным.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
// Определяется структура. Заказываем необходимый размер
// памяти для записи информации о новом объекте.
struct node
{

 Языки программирования. Язык С. Конспект лекций -225-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
int inf; // Поле для записи целых чисел.
node *next; // Поле указателя для записи адресов элементов типа node.
};

void main()
{
node *r, *fr = NULL, *er; // fr – указатель на головной элемент списка.
// er – указатель на последний элемент списка.
// r – указатель для формирования нового узла списка.
node *rp;
int a; // a – переменная для записи целых чисел.
clrscr();
FILE *f;
f = fopen("t.dat","r");

// Начало формирования списка.


do // Начало цикла ввода чисел из файла.
{
fscanf(f,"%d", &a); // Ввод числа из файла.
r = new node; // Создаем новый элемент списка.
// Выделяем память для нового элемента.
r->inf = a; // Инициализируем поле inf нового элемента списка.
r->next = NULL; // Инициализируем поле указателя нового элемента
//списка.
if (fr == NULL) // Проверяем: список существует или нет. Если
// fr = NULL, то списка нет.
fr = r; // Поэтому новый элемент объявляем головным.
else // Если список существует, то
er -> next = r; // новый элемент присоединяем к списку.
er = r; // Новый элемент объявляем последним.

}
while (!feof(f)); // Конец цикла ввода чисел из файла.
fclose(f);
// Конец формирования списка.
// Вывод списка на экран.
cout << "Сформирован список:\n";
r = fr;
while (r != NULL) // Пока не дошли до последнего элемента списка.
{ cout << r -> inf << "\n"; // Вывод информации из поля inf элемента,
// адрес которого находится в указателе r.
r = r -> next; // Переход к следующему элементу списка.
// Для этого из поля next текущего элемента списка
// в указатель r пересылаем адрес на следующий элемент.
}

 Языки программирования. Язык С. Конспект лекций -226-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
getch();
cout << "\n\nУдалить узел cо значением k= ";
cin >> a;
r = fr;
if (r -> inf == a)
{ cout << "удаляется головной узел списка";
fr = fr -> next; // В указатель на первый элемент пересылаем адрес
// второго элемента списка.
delete r; }
else
{
while ((r -> inf != a) && ( r != NULL)) // Поиск элемента, который
{ // надо удалить.
rp = r; // Запоминаем адрес пройденного элемента.
r = r -> next; } // Переходим на новый элемент.
if (r -> inf == a) // Проверяем, найден элемент или нет.
{ cout << " Удаляется узел со значением= " << r -> inf << "\n";
rp -> next = r -> next; // Поле указателя удаляемого элемента
// пересылаем в поле указателя элемента, который
// расположен перед удаляемым.
delete r;
cout << “Список после удаления элемента: \n";
r = fr;
while (r != NULL) // Пока не дошли до последнего элемента списка.
{ cout << r -> inf << "\n"; // Вывод значения текущего элемента списка.
r = r -> next; // Переход к следующему элементу списка.
// Для этого из поля next текущего элемента списка
} // в указатель r пересылаем адрес на следующий элемент.
getch(); }
else
cout << "\n" << "Узел со значением " << a << " не найден ";
getch(); } }

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


элементов из списка, можно сразу сформулировать алгоритм для добавления
нового элемента в список.
Задача 1. Новый элемент списка со значением b вставить за элементом
списка со значением k.
Алгоритм решения задачи 1.
Шаг 1. Объявить переменные *r, *fr = NULL, *er, *rp, a, b.
Шаг 2. Определить значение b нового элемента списка.
Шаг 3. Определить значение a элемента списка, за которым надо
вставить новый элемент.
Шаг 4. Используя указатель r, найти элемент, за которым надо вставить
новый. Если такой элемент найден, то перейти на шаг 5. Иначе – на шаг 11.

 Языки программирования. Язык С. Конспект лекций -227-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
На рис. 23.26 приведена схема фрагмента списка после выполнения
шага 4.
Шаг 5. Сформировать новый элемент списка, используя директиву
rp = new node; На рис. 23.27 приведен фрагмент списка после выполнения
этой директивы.
Шаг 6. Инициализировать поле inf нового элемента, используя
директиву rp -> inf = b;
Шаг 7. Присоединить правую часть списка к новому элементу. Для
этого использовать директиву rp -> next = r -> next;
На рис. 23.28 приведена схема фрагмента списка после выполнения
шагов 6 и 7.

r Ak
Ak A[k+1]
a[k-1] A[k+1] a[k+1] A[k+2]

Рис. 23.26

r Ak
Ak A[k+1]
a[k-1] A[k+1] a[k+1] A[k+2]

AN

rp AN

Рис. 23.27

r Ak
Ak A[k+1]
a[k-1] A[k+1] a[k+1] A[k+2]

AN
b A[k+1]

rp AN

Рис. 23.28

Шаг 8. Присоединить к левой части списка новый элемент директивой


r -> next = rp. После шага 8 завершается вставка в список нового элемента со
значением b.

 Языки программирования. Язык С. Конспект лекций -228-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
На рис. 23.29 приведена схема фрагмента списка после выполнения шага 8.
r Ak
Ak A[k+1]
a[k-1] AN a[k+1] A[k+2]

AN
b A[k+1]

rp AN

Рис. 23.29

Шаг 9. Выдать преобразованный список на экран и перейти на шаг 11.


Шаг 10. Выдать сообщение:
Элемент списка со значением a не найден.
Шаг 11. Завершить работу алгоритма.
Задача 2. Новый элемент списка со значением b сделать головным
(первым).
Алгоритм решения задачи 2.
Шаг 1. Объявить переменные *r, *fr = NULL, *er, *rp, a, b.
Шаг 2. Определить значение b нового элемента списка.
Шаг 3. Сформировать новый элемент списка директивой rp = new node
и заполнить поле inf нового узла, выполняя код rp->inf = b.
На рис. 23.30 приведена схема фрагмента списка после выполнения шага 3.
Шаг 4. К новому элементу присоединить список. Иначе говоря, новый
элемент сделать в списке первым. Это достигается при выполнении кода
rp -> next = fr;
AN
AN
rp
rp AN
AN bb

AA1
1 AA2
2 AA3
3
Fr
Fr
A1
A1 aa1
1 AA22 aa22 AA3
3

Рис. 23.30

AN
AN
rp
rp AN
AN b AA1
1

Fr
Fr AA1
1 AA2
2 A
A33
A1 aa11 A2 aa22 AA3
3
A2

Рис. 23.31

На рис. 23.31 приведена схема фрагмента списка после выполнения


шага 4.
Шаг 5. В указатель fr заслать адрес нового элемента списка: fr = rp.

 Языки программирования. Язык С. Конспект лекций -229-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
Шагом 5 завершается вставка нового элемента в начало списка.
Окончательные изменения, которые произошли в списке при такой вставке,
демонстрируются на рис. 23.32.

AN
AN
rp
rp AN
AN bb AA11

Fr AA11 AA22 A
A33
Fr AN
AN aa1
1 AA22 aa22 AA33

Рис. 23.32

Программа 3. Формирует односвязный список и вставляет новый


элемент в список. Информация для элементов списка вводится из файла с
именем t.dat. В файле записан массив целых чисел. Первый сформированный
элемент списка является головным.
#include <conio.h>
#include <stdio.h>
#include <iostream.h>
// Определяется структура. Заказываем необходимый размер
// памяти для записи информации о новом объекте.
struct node
{
int inf; // Поле для записи целых чисел.
node *next; // Поле указателя для записи адресов элементов типа node.
};
void main()
{
node *r, *fr = NULL, *er; // fr – указатель на головной элемент списка.
// er – указатель на последний элемент списка.
// r – указатель для формирования нового узла списка.
node *rp;
int a, b; // a – переменная для записи целых чисел.
clrscr();
FILE *f;
f = fopen("t.dat","r");

// Начало формирования списка.


do // Начало цикла ввода чисел из файла.
{
fscanf(f,"%d", &a); // Ввод числа из файла.
r = new node; // Создаем новый элемент списка. Выделяем
// память для нового элемента.
r->inf = a; // Инициализируем поле inf нового элемента списка.
r->next = NULL; // Инициализируем поле указателя нового элемента

 Языки программирования. Язык С. Конспект лекций -230-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
// списка.
if (fr == NULL) // Проверяем: список существует или нет. Если
// fr = NULL, то списка нет.
fr = r; // Поэтому новый элемент объявляем головным.
else // Если список существует, то
er -> next = r; // новый элемент присоединяем к списку.
er = r; // Новый элемент объявляем последним.
}
while (!feof(f)); // Конец цикла ввода чисел из файла.
fclose(f);
// Конец формирования списка.

// Вывод списка на экран.


cout << "Сформирован список:\n";
r = fr;
while (r != NULL)
{
cout << r -> inf << "\n";
r = r -> next;
}
getch();

// Вставка нового элемента в список.


cout << "\n\nВставить в список элемент со значением b= ";
cin >> b;
cout << "\n\nЗа элементом со значением n = ";
cin >> a;
r = fr;
while ((r -> inf != a) && (r != NULL))
r = r -> next;
if (r -> inf == a)
{
rp = new node; // Выделяем память под новый элемент.
rp -> inf = b; // Заполняем поле inf нового элемента.
rp -> next = r -> next; // К новому элементу присоединяем
// правую часть списка.
r -> next = rp; // К левой части списка присоединяем
// новый элемент списка.
cout << "Cписок после вставки элемента со значением: " << b << "\n";
r = fr;
while (r != NULL)
{
cout << r -> inf << "\n";
r = r -> next;
}
}

 Языки программирования. Язык С. Конспект лекций -231-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
else
cout << "\nЭлемент списка со значением " << a << " не найден ";

// Вставка в список нового головного элемента.


cout << "\n\nВведи значение для нового головного элемента n = ";
cin >> a;
rp = new node; // Выделяем память под новый элемент.
rp -> inf = a; // Заполняем поле inf нового элемента.
rp -> next = fr; // Новый элемент делаем первым.
fr = rp; // В указатель на первый элемент списка
// пересылаем адрес нового элемента.
cout << "\nСписок с новым головным элементом:\n";
r = fr;
while (r != NULL)
{
cout << r -> inf << "\n";
r = r -> next;
}
getch();
}
Операции над списками можно оформить в виде функций. Ниже
приведем коды этих функций. Следует помнить: для того чтобы передать
список в функцию, надо в эту функцию передать указатель на список.
Код функции «Формирование списка». Назначение переменных:
h, sl – для адресов указателей на первый и последний элементы списка;
ad – для значения текущего элемента списка;
r – указатель на переменную типа node для адресов формируемых
элементов списка.
void for_sps ( node **h, node **sl, int ad) // h – для адреса указателя
// на первый элемент. sl – для адреса
// на последний элемент списка.
{
node *r;
r = new node; // Выделяется память под новый элемент списка.
r -> inf = ad; // Заполняем поле inf нового элемента.
r -> next = NULL; // Заполняем поле next нового элемента.
if (*h == NULL) // *h – значение в указателе для первого элемента
// списка.
*h = r; // В указатель засылаем адрес на первый элемент списка.
else
(*sl) -> next = r; // Присоединяет новый элемент к списку.
*sl = r; // В указатель на последний элемент списка
} // засылается адрес нового элемента.

Код функции «Вывод списка на экран»

 Языки программирования. Язык С. Конспект лекций -232-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
void viv_sps ( node *r)
{
if (r == NULL)
cout << " !!!! Список пуст !!!!\n";
else
while (r != NULL)
{
cout << r -> inf << "\n";
r = r -> next;
}
}

Код функции «Поиск элемента в списке»


node *poisk (node *r, int b)
// Функция возвращает значение указателя элемента.
// Если элемент не найден, возвращается NULL.
{
while ((r != NULL) && (r -> inf != b)) // Пока элемент не найден,
r = r -> next; // переходи к новому элементу.
if (r -> inf != b)
else
{
cout << "Элемент со значением " << b << " отсутствует \n";
getch();
}
return r;
}

Код функции «Удаление элемента из списка»


int ydal ( node **f, int b)
{
node *r, *rp;
r = *f;
if (r -> inf == b) // Проверяется, является ли удаляемый элемент первым.
{
r = *f; // Запоминаем адрес первого элемента.
*f = (*f) -> next; // В указатель на первый элемент
// пересылаем адрес второго элемента.
delete r; // Удаляется первый элемент списка из памяти программы.
return 1;
}
// Поиск удаляемого элемента. Если этот элемент не первый.
while ((r -> inf != b) && (r != NULL))
{
rp = r;

 Языки программирования. Язык С. Конспект лекций -233-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
3. Операции над списком
r = r -> next;
}
if (r -> inf == b)
{
rp -> next = rp -> next -> next ; // Исключается элемент из списка.
delete r; // Удаляется элемент списка.
return 1;
}
return 0; }

Код функции «Формирование элемента списка»


node *add_1(int k)
{
node *pr;
pr = new node;
pr -> inf = k;
pr -> next = NULL;
return pr;
}

4. Программа обработки списка

В заключение приведем программу, в которую включены все операции


над списком. Управление операциями осуществляется через текстовое меню.
//
// ************** Линейный список **************
// Все операции над списком реализованы через функции
//
#include<conio.h>
#include<stdio.h>
#include<stdlib.h>
#include<iostream.h>
#define FALSE 0
#define TRUE 1

struct node
{
int inf;
node *next;
};
void viv_sps ( node *r);
node *add_1(int k);
void vstv( node *r, int b);
node *poisk (node *r, int b);

 Языки программирования. Язык С. Конспект лекций -234-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
int ydal ( node **r, int b);

void main()
{
node *fr, *r, *p_r; // fr – указатель для головного элемента.
int k, l, h;
fr = NULL;
clrscr();
int iv = FALSE;
while ( !iv )
{
clrscr();
cout << "\n\nКоманды меню:\n\n";
cout << "1 Сформировать список\n";
cout << "2 Вставить в список\n";
cout << "3 Вставить в список перед головным элементом\n";
cout << "4 Удалить из списка\n";
cout << "5 Выдать на экран\n";
cout << "6 Выход\n";
cout << "\nВведи команды меню: ";
scanf("%d",&k);
switch (k)
{
case 1 : // Формируется список.
cout << "Введи узел (Ctrl + z Enter – конец ввода): ";
scanf("%d", &h);
do
{
if (fr == NULL)
{
fr = p_r = add_1(h);
}
else
{
p_r -> next = add_1(h);
p_r = p_r -> next;
}
scanf("%d", &h);
}while (!feof(stdin));
break;

case 2 : // Добавляется новый элемент в список.


if (fr == NULL)
{
cout << "Список пуст !!!\n"

 Языки программирования. Язык С. Конспект лекций -235-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
<< "Введи номер 1-го элемента списка: ";
scanf("%d",&h);
fr = p_r = add_1(h);
}
else
{
viv_sps(fr);
cout << "\n\nВставить в список элемент со значением b = ";
cin >> h;
cout << "\n\nЗа элементом со значением n = ";
cin >> k;
r = poisk(fr,k);
if (r -> inf == k)
vstv(r,h);
}
break;
case 3 : // Формируется новый первый (головной) элемент списка.
if (fr == NULL)
{
cout << "Список пуст !!!\n"
<< "Введи значение для 1-го элемента списка: ";
scanf("%d",&h);
fr = p_r = add_1(h);
}
else
{
cout << "Введи значение для 1-го элемента списка: ";
scanf("%d",&h);
p_r = add_1(h);
p_r -> next = fr;
fr = p_r;
}
break;

case 4 : // Удаляется элемент из списка.


if (fr == NULL )
{
cout << "!!! В списке элементов нет !!!\n";
getch();
}
else
{
viv_sps(fr);
cout << "\n\nУдалить из списка узел cо значением k = ";
cin >> h;

 Языки программирования. Язык С. Конспект лекций -236-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
{
if (!ydal(&fr,h) )
cout << "\n" << "Узел со значением " << h << " не найден ";
}
}
break;

case 5 : // Список выдается на экран.


viv_sps(fr);
cout << "Нажми клавишу Enter\n";
getch();
break;

case 6 : // Выход из программы.


cout << "\n\n Вы вышли из программы!!!.\n ";
cout << "Нажми любую клавишу\n ";
iv = TRUE;
break;
default : cout << "Команда меню с номером" << k
<< "отсутствует\n";
} }
getch(); }
// Функция вставки элемента в список.
void vstv( node *r, int b)
{ node *rp;
rp = new node;
rp -> inf = b;
rp -> next = r -> next;
r -> next = rp;
}
// Функция поиска элемента в списке.
node *poisk (node *r, int b)
{
while ((r != NULL) && (r -> inf != b))
r = r -> next;
if (r -> inf != b)
{
cout << "Элемент со значением " << b << " отсутствует \n";
getch();
}
return r; }

// Функция формирования списка.


node *add_1(int k)
{ node *pr;

 Языки программирования. Язык С. Конспект лекций -237-


ЛЕКЦИЯ 23. ОДНОСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
pr = new node;
pr -> inf = k;
pr -> next = NULL;
return pr;
}
// Функция для вывода списка.
void viv_sps ( node *r)
{ if (r == NULL)
cout << " !!!! Список пуст !!!!\n";
else
while (r != NULL)
{ cout << r -> inf << "\n";
r = r -> next;
}}

// Функция для удаления элемента из списка.


int ydal (node **f, int b)
{
node *r, *rp;
r = *f;
if (r -> inf == b)
{
r = *f;
*f = (*f) -> next;
delete r;
return 1;
}
while ((r -> inf != b) && ( r != NULL))
{
rp = r;
r = r -> next;
}
if (r -> inf == b)
{
rp -> next = rp -> next -> next ;
delete r ;
return 1;
}
return 0; }

 Языки программирования. Язык С. Конспект лекций -238-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ

План
1. Стек.
2. Операции над стеком.
3. Программа обработки стека.

1. Стек

К односвязным спискам относятся такие стандартные структуры


данных, как стек и очередь.
Стек – это односвязный список, в котором можно удалять только
первый элемент, а добавлять новый элемент – только перед первым.
Таким образом, стек есть список, в котором возможно удалить только
первый элемент, т. е. элемент, включенный в список последним, является
головным (или первым). Иначе говоря, стек – это структура, у которой вход и
выход совпадают.

2. Операции над стеком

На практике структура «стек» встречается очень часто: это и


железнодорожный тупик, и пирамида, и рожок от автомата, и способ
организации данных при работе программы на компьютере и т. д. Для стека
существуют всего две операции – добавить элемент в стек и удалить элемент
из стека. Эти операции принято оформлять соответственно в виде функций
push() и pop(). Ниже приведена программа, которая работает со структурой
«стек». Управление работой программы происходит через текстовое меню.
Так как стек является линейным списком, то детально разбирать процесс его
формирования не имеет смысла. Эти детали были рассмотрены выше. В
самой программе даны подробные комментарии при формировании стека и
удалении элемента из стека.

3. Программа обработки стека

Программа «стек». Программа управляет преобразованием стека.


#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#define FALSE 0
#define TRUE 1
struct node
{
int info;

 Языки программирования. Язык С. Конспект лекций -239-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ
3. Программа обработки стека
node *l;
};

void push (node **h, int ad); // Функция добавляет элемент в стек.
void pop (node **h, int *ad); // Функция удаляет элемент из стека.
void spisprint(node *p); // Функция выводит стек на экран.
void main(void)
{
node *rp=NULL, *wh;
int no = 0; // Для текущего элемента в стеке.
int iv = FALSE; // iv – параметр, который управляет работой меню.
int k;
rp = NULL;
while ( !iv )
{
clrscr();
cout << "\n\nКоманды меню:\n\n";
cout << "1 Добавить в стек\n";
cout << "2 Удалить из стека\n";
cout << "3 Выдать на экран\n";
cout << "4 Выход\n";
cout << "\nВведи команды меню: ";
scanf("%d",&k);
switch (k)
{ case 1 : // Добавить элемент в стек.
cout << "Введи ключ (значение) элемента – ";
cin >> no;
push(&rp, no );
break;

case 2 : // Удалить элемент из стека.


pop(&rp,&k );
if (k > 0)
cout << "Удалили элемент = " << k << " Нажми Enter\n";
getch();
break;

case 3 : // Выдать стек на экран.


wh = rp;
spisprint(wh);
cout << "Нажми Enter\n";
getch();
break;

case 4 : // Вывод стека на экран.

 Языки программирования. Язык С. Конспект лекций -240-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ
3. Программа обработки стека
cout << " \n\n Вы вышли из программы!!!.\n ";
cout << "Нажми любую клавишу\n ";
iv = TRUE;
break;
default : cout << "Команда меню с номером" << k
<< "отсутствует\n";
} }
getch();
}
// Функция включает элемент в стек.
void push ( node **h, int ad) // h – для адреса указателя. В h передается
// адрес указателя на вершину стека.
{ node *r;
r = new node; // Сформировали новый элемент стека.
r -> info = ad; // В поле inf нового узла засылаем его значение.
r -> l = *h; // Новый элемент делаем первым. Или к новому элементу
// присоединяем стек.
(*h) = r; // В указатель на вершину засылаем адрес нового элемента.
// То есть новый элемент объявляем вершиной стека.
}

void spisprint(node *p) // Вывод стека на экран.


{
node *r;
if ( p != NULL)
{
r = p;
while (r != NULL)
{
cout << r -> info << "\n";
r = r -> l;
}
}
else
cout << "********** CTEK П У С Т!!!!! ************\n";
}

// Функция удаляет элемент из стека.


void pop (node **h, int *ad) // h – для адреса указателя. В h передается
// адрес указателя на вершину стека.
// *ad – для значения удаляемого элемента.

{
node *r;
if ( *h == NULL )

 Языки программирования. Язык С. Конспект лекций -241-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ
3. Программа обработки стека
{
cout << "********** CTEK П У С Т!!!!! ************\n";
*ad = -1;
}
else
{
*ad = (*h) -> info; // В *ad засылаем значение удаляемого элемента.
// Этот элемент является первым в стеке.
r = (*h); // Запоминаем адрес удаляемого элемента.
*h = (*h) -> l; // Из поля указателя l удаляемого элемента
// пересылаем в указатель на вершину стека
// адрес на новый первый элемент.
delete r; // Исключаем из программы память, которую
// занимал удаляемый элемент.
}
}

Для работы программы ее надо загрузить, а затем, руководствуясь


командами меню, работать со структурой «стек».
Очередь – это односвязный список, в котором можно удалять только
первый элемент, а добавлять новый элемент – только к последнему.
Таким образом, для этой структуры должны быть определены два
указателя: в первом указателе должна быть ссылка (адрес) на первый элемент
списка, во втором должна быть ссылка (адрес) на последний элемент списка.
Так как все детали формирования списка и операции над ним были подробно
рассмотрены ранее, дадим без дополнительных разъяснений код программы,
которая работает со структурой «очередь».
Программа «очередь». Программа управляет преобразованием очереди.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#define FALSE 0
#define TRUE 1
struct node
{
int info;
node *l;
};

void add_o (node **h, node **sl, int ad); // Функция добавляет элемент
// в очередь.
void del_o (node **h, int *ad); // Функция удаляет элемент из очереди.
void spisprint(node *p); // Функция выводит очередь на экран.
void main(void)

 Языки программирования. Язык С. Конспект лекций -242-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ
3. Программа обработки стека
{
node *wh;
node *fo, *lo; // fo – указатель на первый элемент очереди.
// lo – указатель на последний элемент очереди.
int no = 0;
fo = NULL;
lo = NULL;
int iv = FALSE;
int k;
while (!iv)
{ clrscr();
cout << "\n\nКоманды меню:\n\n";
cout << "1 Добавить элемент в очередь\n";
cout << "2 Удалить элемент из очереди\n";
cout << "3 Выдать очередь на экран\n";
cout << "4 Выход из программы\n";
cout << "\nВведи команды меню: ";
scanf("%d",&k);
switch (k)
{
case 1 : // Добавить элемент в очередь.
cout << "Введи элемент очереди (целое число): ";
cin >> no;
add_o(&fo, &lo, no ); // Добавить элемент в очередь.
break;

case 2 :
del_o(&fo, &k ); // Удалить элемент из очереди.
if ( k > 0 )
cout << "Удалили элемент " << k << " Нажми Enter\n";
getch();
break;

case 3 : // Выдать очередь на экран.


wh = fo;
spisprint(wh);
cout << "Нажми Enter\n";
getch();
break;

case 4 : // Выход из программы.


cout << " \n\n Вы вышли из программы!!!.\n ";
cout << "Нажми любую клавишу\n ";
iv = TRUE;
break;

 Языки программирования. Язык С. Конспект лекций -243-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ
3. Программа обработки стека
default : cout << "Команда меню с номером" << k
<< "отсутствует\n";
} }
getch(); }
// Функция включает элемент в очередь.
void add_o (node **h, node **sl, int ad)
// h – для адреса указателя на первый элемент очереди.
// sl – для адреса указателя на последний элемент очереди.
{ node *r;
r = new node; // Создаем элемент очереди.
r -> info = ad;
r -> l = NULL;
if (*h == NULL) // Создаем первый элемент очереди.
{ *h = r; // Если очередь из одного элемента, то значения
*sl = r; // указателей на первый и последний элементы, совпадают.
}
else
{
(*sl) -> l = r; // Новый элемент присоединяется к последнему
// элементу очереди.
*sl = r; // Новый элемент объявляем последним.
}}
// Функция удаляет элемент из очереди.
void del_o (node **h, int *ad)
// h – для адреса указателя на первый элемент очереди.
{ node *r;
if (*h == NULL)
{ cout << "********** ОЧЕРЕДЬ П У С Т А!!!!! ************\n";
*ad = -1;
}
else
{ *ad = (*h) -> info;
r = (*h); // Запоминаем адрес элемента, который надо удалить.
*h = (*h) -> l; // В указатель на первый элемент пересылаем адрес
// второго элемента очереди.
delete r; // Исключаем из программы память, которую занимал
// первый элемент.
} }

// Функция выдает очередь на экран.


void spisprint(node *p)
{ node *r;
if (p != NULL)

 Языки программирования. Язык С. Конспект лекций -244-


ЛЕКЦИЯ 24. СТЕК И ОЧЕРЕДЬ
3. Программа обработки стека
{ r = p;
while (r != NULL)
{ cout << r -> info << "\n";
r = r -> l ;
} }
else
cout << "********** ОЧЕРЕДЬ П У С Т А!!!!! ************\n"; }

Для работы программы ее надо загрузить, а затем руководствоваться


командами меню, работать со структурой «очередь».

 Языки программирования. Язык С. Конспект лекций -245-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ

План

1. Двусвязные списки.
2. Формирование списка.
3. Операции над списком.
4. Программа обработки списка.

1. Двусвязные списки

Двусвязный список есть динамическая структура данных (ДСД).


Каждый его узел связан как с предыдущим, так и с последующим элементом.
Алгоритмы формирования двусвязного и односвязного списков отличаются
друг от друга незначительно, поэтому здесь не будем подробно излагать
этапы формирования двусвязного списка. Знания, полученные при изучении
односвязного списка, позволяют сразу изобразить схему двусвязного списка,
приведенную на рис. 25.1.
Структура для элемента двусвязного списка определяется следующим
образом:

struct node { int inf;


node *lt;
node *rt; };

На рис. 25.1–25.19 элемент, который определяется структурой node,


будет изображаться прямоугольником, состоящим из трех частей. Одна часть
будет иллюстрировать поле для чисел inf, две другие – поля для указателей lt
и rt. Далее поле указателя rt будет использоваться для ссылки на
предыдущий (правый) элемент списка, а поле указателя lt – для ссылки на
следующий (левый) элемент списка (рис. 25.2).

...

Рис. 25.1

lt inf rt

Рис. 25.2

 Языки программирования. Язык С. Конспект лекций -246-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
2. Формирование списка

2. Формирование списка

Считая, что вы разобрались в технике формирования односвязного


списка, перейдем к двусвязному. Приведем фрагмент программы для
формирования первого элемента списка. Смысл параметров, которые
используются во фрагменте, следующий:
r – указатель типа node для формирования нового узла списка, или
указатель на текущий (новый) элемент списка;
fr – указатель типа node для адреса последнего элемента списка, или
указатель на последний элемент списка;
en – указатель типа node для адреса последнего элемента списка, или
указатель на последний элемент списка.

r = new node; // Выделяется память под первое звено списка.


// Адрес звена засылается в указатель r.
r -> lt = NULL; // Определяется поле указателя lt первого звена.
r -> rt = NULL; // Определяется поле указателя rt первого звена.
r -> inf = nz; // Определяется поле inf первого звена.
fr = r; // Определяется указатель на первое звено списка.
en = r; // Определяется указатель на последнее звено списка.

Проиллюстрируем работу данного фрагмента на схеме. Состояние


памяти после выполнения директивы r = new node схематично
демонстрирует рис. 25.3. Последующие коды фрагмента определяют поля
нового элемента с адресом A1 и значения в переменных fr и er (рис. 25.4).

A1 A1
NULL NZ NULL
lt inf rt lt inf rt
fr r A1 er fr A1 r A1 er A1

Рис. 25.3 Рис. 25.4

Заметим, что если список состоит из одного элемента, то очевидно


fr = er. Приведем фрагмент программы, который демонстрирует, как новый
элемент присоединяется к существующему списку. Каждый код фрагмента
поясняется в комментариях. Смысл параметров, которые используются во
фрагменте, следующий:
r – указатель типа node для формирования нового узла списка, или
указатель на текущий (новый) элемент списка;
en – указатель типа node для адреса последнего элемента списка, или
указатель на последний элемент списка.

r = new node; // Выделяется память под новое текущее звено.

 Языки программирования. Язык С. Конспект лекций -247-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
2. Формирование списка

// Адрес нового звена заносится в указатель r.


r -> inf = nz; // В поле inf нового узла засылается его значение.
r -> rt = NULL; // Правый указатель нового звена должен иметь значение
// NULL.
r -> lt = en; // К новому узлу присоединяется сформированная часть
// двусвязного списка. Для этого в левый указатель нового
// узла пересылаем адрес на последний элемент списка.
// Заметим, что по отношению к новому узлу эта часть
// является левой частью списка.
en -> rt = r; // К списку присоединяем новый узел. Для этого в правый
// указатель последнего элемента списка засылаем адрес
// нового звена.
en = r; // Новое звено объявляем последним.

Проиллюстрируем работу данного фрагмента на схеме. Итак, пусть


сформирован список (рис. 25.5).
Состояние памяти после выполнения кода r = new node продемонстри-
ровано на рис. 25.6, а состояние памяти после выполнения кода r -> inf = nz
изображено на рис. 25.7.
На рис. 25.8 представлено состояние памяти после выполнения кода
r -> rt = NULL. Состояние памяти после выполнения кода r -> lt = en
демонстрирует рис. 25.9.

AA1
1 NULL
NULL aa1
1 A2
A2 ... A(N–1)
A (N-1) aN
aN NULL
NULL

l l tt inf
inf rtrt ll t inf
inf rtrt
ff r A 1
A1 ee nn AN
AN

Рис. 25.5

AA1
1 NULL
NULL aa1
1 A2
A2 ... ANAN
A(N–1)
A (N-1) aN
aN NULL
NULL
AK
AK

llt inf
inf rtrt l ltt inf
inf rtrt ll t inf
inf rtrt
ff rr A
A11 ee n AN
AN r AK

Рис. 25.6

A1
NULL a1 A2 ... AN
A (N-1) aN NULL
AK
nz

lt inf rt lt inf rt lt inf rt


f r A1 e n AN r AK

Рис. 25.7

 Языки программирования. Язык С. Конспект лекций -248-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
2. Формирование списка

AA1
1 NULL
NULL aa1
1 A2
A2 ... AN
AN
A(N–1)
A (N-1) aN NULL
NULL
AK
AK
nz
nz NULL
NULL

l l tt inf
inf rtrt ll t inf
inf rtrt l ltt inf
inf rtrt
ff r A1
A1 ee n AN
AN r AKAK

Рис. 25.8

AA1
1 NULL
NULL aa1
1 A2
A2 ... AN
AN
A(N–1)
A (N-1) aN
aN NULL
NULL
AK
AK
AN
AN nz
nz NULL
NULL

l ltt inf
inf rtrt ll t inf
inf rtrt l l tt inf
inf rtrt
f rr A1
A1 ee nn AN
AN rr AK

Рис. 25.9

AA1
1 NULL
NULL aa1
1 A2
A2 ... AN A(N–1)
A (N-1) aN AK
AK
AK
AK
AN
AN nz
nz NULL
NULL

lt inf
inf rtrt l tt inf
inf rtrt ll t inf
inf rtrt
A1
ff r A1 ee nn ANAN rr AK

Рис. 25.10

AA1
1 NULL
NULL aa1
1 AA2
2 ... AN
AN
A(N–1)
A (N-1) aN
aN AK
AK
AK
AK
AN
AN nz
nz NULL
NULL

ll t inf
inf rtrt ll t inf
inf rtrt l l tt inf
inf rtrt
ff r AA1
1 ee nn AKAK rr AK

Рис. 25.11

На рис. 25.10 показано состояние памяти после выполнения кода


en -> rt = r, на рис. 25.11 – состояние памяти после выполнения кода en = r.
На этом завершается программа формирования двусвязного списка.
Операции вывода элементов двусвязного списка на экран и операция поиска
узла двусвязного списка абсолютно идентичны с подобными операциями
односвязного списка. Поэтому на их обсуждении останавливаться не будем.

3. Операции над списком

Рассмотрим схему изменения указателей в двусвязном списке при


удалении элемента. Предположим, что адрес удаляемого элемента AX
находится в указателе r и этот элемент не является первым и последним
(рис. 25.12). Заметим, что в поле r -> lt находится адрес узла списка, который
предшествует удаляемому элементу (правый по отношению к удаляемому).
Соответственно в поле r -> rt находится адрес узла списка, который следует

 Языки программирования. Язык С. Конспект лекций -249-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
3. Операции над списком
за удаляемым элементом (правый по отношению к удаляемому). Обратим
внимание на то, что при такой схеме памяти при выполнении директивы cout
<< r -> lt << “ “ << r -> lt -> rt << “\n” на экране появится
следующая информация: AXL AX. Разумеется, AXL и AX будут представлять
конкретные шестнадцатеричные числа. Аналогично директива

cout << r -> rt << “ “ << r -> rt -> lt << “\n”

выдаст на экран следующую информацию: AXR AX.


Итак, рассмотрим рис. 25.12. Для удаления элемента, адрес которого
находится в указателе r, необходимо выполнить коды:

r -> lt -> rt = r -> rt; // В поле указателя rt предшествующего


// (левого) узла пересылаем адрес на правый
// (последующий) узел списка.
r -> rt -> lt = r -> lt; // В поле указателя lt следующего (правого)
// узла пересылаем адрес на левый
// (предшествующий) узел списка.

Рис. 25.13 демонстрирует схему памяти после выполнения


вышеприведенных кодов.

AXL AX AXR
AXL AP
AP aa AX
AX AXR
AXL bb AXR
AXR AX
AX c AS
AS

ll t inf
inf rtrt ll t inf
inf rtrt ll t inf
inf rtrt
rr AX AX

Рис. 25.12

AXL AP AX
AX AXR
AXR
AXL AP aa AXR c AS
AXL
AXL bb AXR
AXR AXL
AXL AS

ll t inf
inf rtrt ll t inf
inf rtrt l l tt inf
inf rtrt
rr AX

Рис. 25.13

Если в этой схеме применить директиву delete r, то область памяти


с адресом AX будет исключена из памяти программы. Приведем
последовательность кодов, которые выполняют операцию удаления первого
и последнего узла двусвязного списка.

if (r == en)
{ en = r -> lt; // В указатель для адреса последнего узла

 Языки программирования. Язык С. Конспект лекций -250-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
3. Операции над списком
// пересылаем адрес предпоследнего узла.
en -> rt = NULL; // В поле правого указателя предпоследнего
} // узла засылаем NULL, тем самым
// формируется новое последнее звено списка.
if (r== fr)
{ fr = fr -> rt; // В указатель для адреса первого узла
// пересылаем адрес второго элемента списка.
fr -> lt = NULL; // В поле левого указателя второго
} // узла засылаем NULL, тем самым
// формируется новый первый элемент списка.

Рассмотрим операцию вставки (включения) нового узла в двусвязный


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

v = new node; // Выделяет память под новый элемент узла.


v -> inf = nv; // В поле inf нового узла засылаем число nv.
v -> rt = r -> rt; // В поле указателя rt нового узла пересылаем
// значение поля правого указателя элемента, адрес
// которого определен в r. Или к новому узлу
// присоединяем правую часть списка.
v -> lt = r; // В поле указателя lt нового узла пересылаем
// значение адреса элемента, адрес которого
// определен в r. Или к новому узлу
// присоединяем левую часть списка.
if (r != en)
r -> rt -> lt = v; // К правой части списка присоединяем новое звено.
// Это возможно сделать только в том случае, если
// узел, за которым вставляется элемент, не последний.
else
en = v; // Если новый узел вставляется за последним.
r -> rt = v; // К левой части списка присоединяем новый узел.

Теперь продемонстрируем на схеме изменения значений в указателях


при выполнении этой операции. На рис. 25.14 изображена схема памяти
после выполнения директивы v = new node.
Далее демонстрируем изменения, которые происходят в схеме при
последовательном выполнении директив:
после выполнения директивы v -> inf = nv получаем рис. 25.15;
после выполнения директивы v -> rt = r -> rt получаем рис. 25.16;
после выполнения директивы v -> lt = r получаем рис. 25.17;
после выполнения директивы r -> rt -> lt = v получаем рис. 25.18;
после выполнения директивы r -> rt = v получаем рис. 25.19.

 Языки программирования. Язык С. Конспект лекций -251-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
3. Операции над списком

AV AN AVR
AVR
AVL
AVL a AVR
AVR AV
AV cc AS
AS

ll t inf
inf rt
rt l l tt inf
inf rtrt ll t inf
inf rt
rr AV vv AN

Рис. 25.14

AN

AV AN AVR
AVR
AVL
AVL a
a AVR
AVR nnv
v AV
AV cc AS
AS

ll t inf
inf rt ll tt inf
inf rtrt ll t inf
inf rt
rt
rr AV vv AN

Рис. 25.15

AV AN
AN AVR
AVR
AVL
AVL a
a AVR
AVR nnv
v AVR
AVR AV
AV cc AS
AS

ll t inf
inf rtrt ll tt inf
inf rt
rt ll t inf
inf rt
rt
rr AVAV vv AN
AN

Рис. 25.16

AV
AV AN AVR
AVR
AVL
AVL a AVR
AVR AAV
V nnv
v AVR
AVR AV
AV cc AS
AS

ll t inf
inf rtrt ll tt inf
inf rt
rt ll t inf
inf rt
rr AVAV vv AN

Рис. 25.17

AV
AV AN
AN AVR
AVR
AVL
AVL aa AVR
AVR AV
A V nnv
v AVR
AVR AV
AN cc AS
AS

ll tt inf
inf rtrt ll tt inf
inf rt
rt l l tt inf
inf rt
rt
rr AV vv AN

Рис. 25.18

 Языки программирования. Язык С. Конспект лекций -252-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
3. Операции над списком

AV
AV AN
AN AVR
AVR
AVL
AVL aa AN
AN AV
A V nnv
v AVR
AVR AN
AV cc AS
AS

l ltt inf
inf rtrt ll tt inf
inf rt
rt l l tt inf
inf rtrt
rr AV AV vv AN AN

Рис. 25.19

Для двусвязного списка можно организовать вставку перед


существующим элементом списка. Читатель может решить данную задачу в
качестве упражнения. Ниже приведем фрагмент программы, которая новый
элемент делает головным, т. е. первым. Иначе говоря, рассмотрим операцию
вставки нового элемента перед первым. Для этого надо выполнить
следующие коды:

v = new node; // Выделяет память под новый элемент узла.


v -> inf = nv; // В поле inf нового узла засылаем число nv.
fr -> lt = v; // В поле указателя lt первого узла засылается адрес
// нового элемента, т. е. новый узел присоединяем
// к списку.
v -> rt = fr; // В поле указателя rt нового узла пересылаем
// адрес первого узла списка, т. е. к новому
// элементу присоединяем список.
v -> lt = NULL; // В поле указателя lt нового звена
// засылаем значение NULL.
fr = v; // Указателю fr, предназначенному для адреса первого
// элемента, присваиваем значение адреса нового
// элемента.

4. Программа обработки списка

В заключение приведем программу формирования двусвязного списка,


в которую включены все вышерассмотренные операции над списком.
Программа spsw. Формирует двунаправленный список. Производит
операции над списком. Информация для списка считывается из файла.
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
struct node {
int inf;
node *lt;
node *rt; };
void main()
{

 Языки программирования. Язык С. Конспект лекций -253-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
node *fr, *en, *r, *v;
int i, nz, np, nv;
FILE *f;
clrscr();
fr = en = NULL;
f = fopen("t.dat","r");
fscanf(f,"%d",&nz);
while( !feof(f) )
{
r = new node; // Выделяет память под новый элемент узла.
r -> inf = nz; // В поле inf нового узла засылаем число nz.
r -> rt = NULL; // В поле указателя rt нового звена
// засылаем значение NULL.
if (fr == NULL) // Если список пустой, то формируем список
// из одного элемента.
{
r -> lt = NULL;
fr = r; // В указатель fr засылаем адрес первого элемента.
}
else
{
r -> lt = en; // К новому элементу присоединяем список.
en -> rt = r; // К списку присоединяем новый элемент.
}
en = r; // Указателю en для адреса последнего элемента
// присваиваем адрес нового элемента. Тем самым
// новый элемент стал последним элементом списка.
fscanf(f,"%d",&nz);
}

cout << "Список выдается слева направо:\n";


r = fr;
while (r != NULL)
{ cout << r -> inf << " ";
r = r -> rt; // Переход к следующему правому элементу списка.
}
cout << "\n\n";
cout << "Список выдается справа налево:\n";
r = en;
while (r != NULL)
{cout << r -> inf << " ";
r = r -> lt; // Переход к следующему левому элементу списка.
}
getch();
cout << "\nВставь звено с номером n = ";

 Языки программирования. Язык С. Конспект лекций -254-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
cin >> nv;
cout << "За номером k = ";
cin >> np;
r = fr;
while ((r -> inf != np) && (r != NULL))
r = r -> rt; // Поиск элемента, за которым надо вставить
// новый узел.
if (r -> inf != np)
{ cout << "Элемент с номером " << np << " НЕ НАЙДЕН \n";}
else
{
v = new node; // Выделяет память под новый элемент узла.
v -> inf = nv; // В поле inf нового узла засылаем число nv.
v -> rt = r -> rt; // В поле указателя rt нового узла пересылаем
// значение поля правого указателя элемента, адрес
// которого определен в r. Или к новому узлу
// присоединяем правую часть списка.
v -> lt = r; // В поле указателя lt нового узла пересылаем
// значение адреса элемента, который
// определен в r. Или к новому узлу
// присоединяем левую часть списка.
if (r != en)
r -> rt -> lt = v; // К правой части списка присоединяем новое звено.
// Это возможно сделать только в том случае, если
// узел, за которым вставляется элемент, не последний.
else
en = v; // Если новый узел вставляется за последним.
r -> rt = v; // К левой части списка присоединяем новый узел.
}
cout << "Список выдается слева направо:\n";
r = fr;
while (r != NULL)
{
cout << r -> inf << " ";
r = r -> rt;
}
cout << "\n\n";
cout << "Список выдается справа налево:\n";
r = en;
while (r != NULL)
{
cout << r -> inf << " ";
r = r -> lt;
}
getch();

 Языки программирования. Язык С. Конспект лекций -255-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка

cout << "\n\nУдали элемент с номером n = ";


cin >> np;
r = fr;
while (( r -> inf != np) && (r != NULL))
r = r -> rt; // Поиск элемента, который надо удалить.
if ( r -> inf != np )
{ cout << "Элемент с номером " << np << " НЕ НАЙДЕН \n";}
else
{ if (r != en && r != fr) // Если удаляемый элемент находится между
// двумя узлами списка.
{ r -> lt -> rt = r -> rt;
r -> rt -> lt = r -> lt;
}
else
{ if (r == en) // Если удаляемый элемент последний.
{ en = r -> lt;
en -> rt = NULL;
}
if (r == fr) // Если удаляемый элемент первый.
{ fr = fr -> rt;
fr -> lt = NULL;
}
}
delete r;
}
cout << "Список выдается слева направо:\n";
r = fr;
while (r != NULL)
{ cout << r -> inf << " ";
r = r -> rt;
}
cout << "\n\n";
cout << "Список выдается справа налево:\n";
r = en;
while (r != NULL)
{ cout << r -> inf << " ";
r = r -> lt;
}
getch();
cout << "\nВведи номер головного элемента: ";
cin >> nv;
v = new node; // Выделяет память под новый элемент узла.
v -> inf = nv; // В поле inf нового узла засылаем число nv.
fr -> lt = v; // В поле указателя lt первого узла засылается адрес

 Языки программирования. Язык С. Конспект лекций -256-


ЛЕКЦИЯ 25. ДВУСВЯЗНЫЕ СПИСКИ
4. Программа обработки списка
// нового элемента, т. е. новый узел присоединяем
// к списку.
v -> rt = fr; // В поле указателя rt нового узла пересылаем
// адрес первого узла списка, т. е. к новому
// элементу присоединяем список.
v -> lt = NULL; // В поле указателя lt нового звена
// засылаем значение NULL.
fr = v; // Указателю fr, предназначенному для адреса первого
// элемента, присваиваем значение адреса нового
// элемента.
cout << "Список выдается слева направо:\n";
r = fr;
while (r != NULL)
{ cout << r -> inf << " ";
r = r -> rt;
}
cout << "\n\n";
cout << "Список выдается справа налево:\n";
r = en;
while (r != NULL)
{ cout << r -> inf << " ";
r = r -> lt; }
getch(); }

 Языки программирования. Язык С. Конспект лекций -257-


ЛЕКЦИЯ 26. ДЕРЕВО

План

1. Дерево как рекурсивный тип данных.


2. Алгоритм формирования дерева

1. Дерево как рекурсивный тип данных

Следуя [1], дадим определение: дерево – это структурированное


множество узлов, где:
• имеется один специально обозначенный узел, называемый корнем
данного дерева;
• остальные узлы (исключая корень) содержатся в m≥ = 0 попарно
непересекающихся множествах T1 , ..., Tm , каждое из которых является
деревом.
Деревья Ti (i = 1, ..., m) называются поддеревьями данного корня.
Заметим, что данное определение является рекурсивным, так как дерево было
определено в терминах самого же дерева. Среди деревьев особо важную роль
играет двоичное, или бинарное, дерево, которое подразделяется на три
семейства узлов: корневой узел n; двоичное дерево, называемое левым
поддеревом для n; двоичное дерево, называемое правым поддеревом для n.
На рис. 26.1 изображено простейшее бинарное дерево, состоящее из трех
узлов.

47
A 21 77
14 43 65 93
7 16 31 44 68
B C
15

Рис. 26.1 Рис. 26.2

Узел A является корневым, узел B называется левым потомком узла A,


узел C – правым потомком узла A. Если выполняется отношение порядка
A > B и A < C, то данное дерево называется бинарным деревом поиска.
Иногда корневой узел A называют предком (или родителем) узлов C и B.
В дальнейшем будем иметь дело только с бинарным деревом поиска.
Заметим, что определение бинарного дерева не исключает отсутствие
потомков у предка или наличие у предка только левого или правого потомка.
На рис. 26.2 дана схема бинарного дерева поиска, состоящего из n узлов.

 Языки программирования. Язык С. Конспект лекций -258-


ЛЕКЦИЯ 26. ДЕРЕВО
1. Дерево как рекурсивный тип данных
Дерево относится к динамическим структурам данных. Это значит, что
память для узлов дерева выделяется в процессе его формирования. Если
элемент дерева не имеет потомков, то его называют листом. Для каждого
элемента дерева вводят понятие уровень. Корень дерева имеет уровень,
равный 0. Уровень любого другого элемента на 1 больше уровня родителя
(предка).
Для того чтобы сформировать дерево, надо определить структуру для
элемента дерева. В учебных целях чаще всего используется структура
следующего вида:

struct <имя_1>
{
<тип> <имя>;
<имя_1> * <left>;
<имя_1> * <rite>;
}

Здесь <имя_1> – тип узла (элемента, звена) дерева;


<имя> – поле для идентификации узла;
<left> – указатель для левого потомка родителя;
<rite> – указатель для правого потомка родителя.
Далее будет использоваться структура следующего формата:

struct node // node – тип узла дерева.


{
int key; // key – поле для идентификации узла дерева.
node *l, *r; // l – указатель для левого потомка узла.
// r – указатель для правого потомка узла.
}

2. Алгоритм формирования дерева

Рассмотрим алгоритм формирования двоичного дерева поиска:


Шаг 1. Сформировать корень A.
Шаг 2. Построить тем же способом левое поддерево с узлами.
Шаг 3. Построить тем же способом правое поддерево с узлами.
Составим программу формирования дерева по этапам. Сначала
выполним шаг 1 алгоритма формирования, т. е. сформируем корень дерева.
Код данной программы представлен в примере 1. Напомним только, что если
в переменной p записан указатель на звено, то доступ к полям key, l, r звена
определяется соответственно кодами: p -> key, p -> l, p -> r.
Пример 1. Программа формирует корень узла дерева.
#include<conio.h>
#include<stdio.h>

 Языки программирования. Язык С. Конспект лекций -259-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
#include<stdlib.h>
#include<iostream.h>
struct node // node – тип узла дерева.
{ int key; // key – поле для идентификации узла дерева.
node *l, *r; // l – указатель для левого потомка узла.
// r – указатель для правого потомка узла.
}
int n;
FILE *f;
node *tree(node *p, int h)
{ if ( p == NULL )
{ p = new node; // Выделяется память для узла дерева, и
// в переменную p засылается адрес области
// памяти для узла дерева.
p -> key=h; // В поле key нового узла засылаем информацию
// о звене. В данном случае в поле key засылаем
// число.
p -> l = NULL; // В поле l нового узла засылаем NULL. Это
// означает, что у нового звена нет левого потомка.
p -> r = NULL; // В поле r нового узла засылаем NULL. Это
// означает, что у нового звена нет правого потомка.
}
return p; // Возвращаем указатель (адрес) памяти, которая
} // была выделена для нового звена.
void main()
{ node *root; // root – для корня дерева.
int h; // h – для входных данных.
root = NULL; // Инициализация указателя на корень (дерева нет).
clrscr();
f = fopen("t.dat","r"); // В файле t.dat численное значение для
// корня дерева.
while( !feof(f) )
{
fscanf(f,"%d",&h); // В h из файла вводится число для значения корня
// дерева.
root = tree(root,h); // Обращение к функции, которое формирует дерево.
}
fclose(f);
cout << root -> key << "\n"; // Вывод результата на экран.
getch(); }

В результате работы данной программы будет сформирован корневой


узел дерева со значением, равным числу, которое записано в файле t.dat.
Программа будет работать следующим образом:
определяется значение указателя на корень root = NULL;

 Языки программирования. Язык С. Конспект лекций -260-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
из файла в переменную h вводится число a0;
происходит обращение к функции tree();
в локальные переменные p и h функции tree() пересылаются
соответственно значения из головной программы NULL и a0;
так как неравенство p == NULL истинно, то формируется узел типа
node;
инициализируются (определяются) поля нового узла;
значение указателя на новый узел возвращается в переменную root.
После завершения работы программы в переменной root головной
программы будет записан адрес на корневой узел дерева.
Заметим, что если значение переменной p не равно NULL, то при
выполнении директивы p = tree(p,h) значение в переменной p не меняется.
В этом легко убедиться при помощи следующей программы примера 2.
Отметим, что данный факт пригодится при анализе программы построения
дерева из n узлов.
Пример 2. Демонстрирует работу программы для примера 1 в случае,
когда указатель на корень не инициализируется значением NULL.
#include<conio.h>
#include<stdio.h>
#include<stdlib.h>
#include<iostream.h>

struct node
{
int key;
node *l, *r; };
int n;
FILE *f;
node *tree(node *p, int h)
{ if ( p == NULL )
{ p = new node;
p -> key=h;
p -> l = NULL;
p -> r = NULL;
}
return p;
}
void main()
{
node *root, w;
int h;
clrscr();
root = &w; // В указатель root засылаем адрес
// переменной w.
// Вывод значения переменной root до обращения к функции.

 Языки программирования. Язык С. Конспект лекций -261-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
cout << "До обращения к функции root= " << root << "\n";
for ( h = 0; h <= 5; h++)

{
root = tree(root,h);
// Вывод значения переменной root после обращения к функции.
cout << "При h= " << h << " root= " << root << "\n";
}
getch(); }

Теперь, используя рекурсивность алгоритма для построения дерева из


нескольких узлов и программу шага 1, можно решить задачу формирования
дерева. Для этого надо формально организовать рекурсивное обращение
функции к самой себе. При этом в качестве входного параметра для
указателя при таком обращении будем использовать указатель родителя l,
если значение нового узла h меньше значения родителя (если h < key), или
указатель родителя r – в противном случае (если h > key). В итоге получаем
следующую рекурсивную функцию для формирования дерева.
node *tree(node *p, int h)
{
if( p == NULL )
{ // Формируем корень дерева.
p = new node;
p -> key=h;
p -> l = NULL;
p -> r = NULL; }
else
if ( h < p-> key)
p-> l = tree( p -> l, h); // p -> l – доступ к полю l родителя.
else
p->r=tree(p -> r,h); // p -> r – доступ к полю r родителя.
//AV
return p; }

Если говорить о форме рекурсии в данной функции, то, как у всякой


функции, возвращающей значение, у нее есть коды, которые выполняются
как на спуске (т. е. до обращения к функции), так и на возврате (т. е. после
обращения к функции). Действия, которые выполняются после обращения,
как мы знаем, засылаются в стек. В данной ситуации, заметим, происходит
только возврат значения указателя. Проанализируем состояния стека во
время работы программы, если формируется дерево из четырех узлов со
значениями 14, 10, 15, 9.
Из головной программы
void main()

 Языки программирования. Язык С. Конспект лекций -262-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
{ node *root;
int h;
root = NULL;
clrscr();
f = fopen("test.dat","r");
while( !feof(f) )
{ fscanf(f,"%d",&h);
root = tree(root,h); }
fclose(f); }

после того как в переменную h вводится число из файла, происходит


обращение к функции tree(). При первом входе в функцию tree() переменная
root = NULL, а переменная h = 14. Эти значения, как отмечалось выше, при
обращении пересылаются из головной программы в локальные переменные
p и h функции tree(), а затем начинают выполняться директивы функции.
Но так как значение параметра p = NULL, то в этом случае:
выполнится блок директив после оператора if;
затем выполняется оператор return p;
происходит возврат в головную программу, при этом в переменную
root засылается адрес первого звена дерева (адрес корневого узла).
Тем самым завершается выполнение шага 1 алгоритма построения
дерева. Рассмотрим на блок-схемах, как меняется структура памяти при
выполнении директив программы. На рис. 26.3 дана схема состояния памяти
после выполнения директивы fscanf(f,"%d",&h).

root h
NULL 14

Рис. 26.3

После выполнения директивы обращения к рекурсивной функции


root = tree(root,h) компьютер загружает tree() и выделяется память для
функции под локальные переменные p и h. При этом в указатель p из
головной программы из указателя root пересылается значение NULL,
а в локальную переменную h пересылается значение 14. Состояние памяти
после обращения к функции tree() представлено на рис. 26.4.
Далее после выполнения директивы p = new node выделяется память
под узел дерева, адрес которого A0 засылается в указатель p (рис. 26.5).
Директивы p -> key = h, p -> l = NULL, p -> r = NULL
в соответствующие поля структуры засылают информацию. В результате
получаем схему памяти, которая дана на рис. 26.6.
После выполнения директивы return p происходит возврат в головную
программу. В root возвращается значение из локальной переменной p.
Память для локальных переменных p и h в функции уничтожается.

 Языки программирования. Язык С. Конспект лекций -263-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
В результате получаем дерево, которое состоит из одного корня.
Итоговый результат работы данной последовательности директив изображен
на рис. 26.7. Заметим, что после завершения рекурсивного возврата в памяти
окончательно формируется структура дерева.

root h p h
NULL 14 NULL 14

Рис. 26.4

p
h root A0
NULL A0
14
l r
h
14

Рис. 26.5

h root p h
14 NULL A0 14

A0 14
NULL NULL
l r

Рис. 26.6

h root
14 A0

A0 14
NULL NULL
l r

Рис. 26.7

 Языки программирования. Язык С. Конспект лекций -264-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева

h root p h
10 A0 A0 10

A0 14
NULL NULL
p->l p->r

Рис. 26.8

Далее из файла t1.dat в h засылается значение 10, из головной


программы происходит вновь обращение к функции tree() с параметрами h
(h = 10) и root (root = адресу корневого узла дерева). При обращении
к функции tree() выделяется память под локальные переменные p и h.
В указатель p при этом засылается значение A0, а в переменную h – значение 10.
Результат обращения к функции tree() изображен на рис. 26.8.
Однако в данном случае значение в указателе p не равняется NULL.
Поэтому программа переходит к выполнению оператора, который находится
после ключевого слова else, т. е. к выполнению директивы:

if ( h < p-> key) p -> l = tree(p -> l, h);


else p -> r = tree(p -> r,h);

Так как в h (h = 10) число меньше, чем число в переменной p->key


(p->key = 14) (key – поле корня, так как в p занесен указатель на корень), то
начинает выполняться директива p -> l = tree(p -> l, h) (h = 10, l – левый
указатель корня и l = NULL). По правилам языка программа, не завершив
текущее обращение, выполняет новое обращение к рекурсивной функции
tree(). При этом в памяти стека размещаются значения локальных параметров
и адрес директивы, с которой функция будет завершать прерванное текущее
обращение при рекурсивном возврате. Итак, программа временно
приостанавливает выполнение директивы return p, что фактически
(см. пояснение к примеру 1) равносильно директиве root = root (так как в p
при первом обращении находился указатель на корневой узел).

Состояние стека при первом рекурсивном обращении к функции tree()


№ п/п Глубина Возврат p p -> l p->r h
1 1 AV A0 NULL NULL 10

При рекурсивном вызове функции из локальной переменной p->l


(p = A0) прерванного текущего обращения в новую локальную переменную p
из поля l пересылается значение NULL. Далее программа работает так:
формируется новый элемент дерева;
адрес нового узла A1 записывается в локальный параметр p;

 Языки программирования. Язык С. Конспект лекций -265-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
в поле key нового элемента засылается значение h = 10;
в поле l левого указателя нового элемента засылается значение NULL;
в поле r правого указателя нового элемента засылается значение NULL;
выполняется директива return p, которая левому указателю звена из
предыдущего присвоит значение A1;
завершается очередное обращение к рекурсивной функции.
После завершения обращения к рекурсивной функции происходит
рекурсивный возврат. В этом случае:
из стека извлекается значение локальных переменных;
выполняется директива return p, которая переменной root из головной
программы присвоит значение A0;
завершается очередное обращение к рекурсивной функции.
Так как после очередного завершения обращения к рекурсивной
функции стек пуст, то происходит возврат в головную программу.
Продемонстрируем на блок-схемах результат выполнения директив. На
рис. 26.9 демонстрируется схема памяти после обращения к функции tree() из
головной программы. Затем выполняется код p -> l = tree(p -> l, h). При
рекурсивном обращении опять выделяется память под локальные
переменные p и h.

h root p h
10 A0 A0 10

A0 14
NULL NULL
p->l p->r

Рис. 26.9

В указатель p засылается значение NULL, а в переменную p


пересылается значение 10. Состояние памяти в этом случае изображено на
схеме рис. 26.10.
Так как значение локальной переменной p при втором входе
в функцию равно NULL, то формируется второй узел дерева (рис. 26.11).
Далее происходит два рекурсивных возврата, т. е. два раза выполняется
код return p. На рис. 26.12 показана схема памяти после первого выполнения
кода return p. После второго выполнения кода return p происходит возврат
в головную программу. В результате получаем схему, которая представлена
на рис. 26.13.
Результатом второго шага выполнения программы будет дерево,
которое состоит из двух элементов.

 Языки программирования. Язык С. Конспект лекций -266-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
h root p h
10 A0 A0 10

A0 14
NULL NULL
p->l p->r
p h
NULL 10

Рис. 26.10

h root p h
10 A0 A0 10

A0 14
NULL NULL
p->l p->r
p h
NULL 10

A1 10
NULL NULL
p->l p->r

Рис. 26.11

h root p h
10 A0 A0 10

A0 14
A1 NULL
p->l p->r

A1 10
NULL NULL
l r

Рис. 26.12

 Языки программирования. Язык С. Конспект лекций -267-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
h root
10 A0

A0 14
A1 NULL
l r

A1 10
NULL NULL
l r

Рис. 26.13

Далее из файла t1.dat в h засылается значение 15, из головной


программы происходит вновь обращение к функции tree() с параметрами
h = 15 и root = A0. Опишем, не комментируя подробно, как будет работать
программа после обращения root = tree(root,h).
1. Из головной программы значения переменных root и h пересылаются
в локальные переменные p и h функции tree().
2. Так как p = A0, h = 15 и p -> key = 14, то будет выполняться
директива p -> r = tree(p -> r, h). Произойдет рекурсивное обращение
к функции tree(). В результате этого обращения приостановится текущее
обращение. Значение локальных параметров текущего обращения
размещается в памяти стека.

Состояние стека при рекурсивном обращении к функции tree()


№ п/п Глубина Возврат p p -> l p->r h
1 1 AV A0 A1 NULL 15

3. Из функции tree() значения переменных p->r = NULL и h = 15


пересылаются в локальные переменные p и h, которые создаются при каждом
обращении к функции tree(). И так как в этом случае значение переменной
p = NULL, то далее программа будет работать так:
формируется новый элемент дерева;
адрес нового узла A2 записывается в локальный параметр p;
в поле key нового элемента засылается значение h = 15;
в поле l левого указателя нового элемента засылается значение NULL;
в поле r правого указателя нового элемента засылается значение NULL;
выполняется директива return p, которая правому указателю звена из
предыдущего присвоит значение A2;
завершается очередное обращение к рекурсивной функции.
После завершения обращения к рекурсивной функции происходит
рекурсивный возврат. В этом случае:
из стека извлекается значение локальных переменных;

 Языки программирования. Язык С. Конспект лекций -268-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
выполняется директива return p, которая переменной root из головной
программы присвоит значение A0;
завершается очередное обращение к рекурсивной функции.
Так как после очередного завершения обращения к рекурсивной
функции стек пуст, то происходит возврат в головную программу. Здесь
не будем иллюстрировать блок-схемами выполнение каждого кода
программы. Предоставим эту возможность читателю. Представим сразу на
рис. 26.14 результат очередного шага формирования дерева.

A0

A0

A1 A2

A1 A2

NULL NULL NULL NULL

Рис. 26.14

Так как алгоритм рекурсивен, то уже на основании рассмотренных трех


шагов можно сделать важные выводы для понимания работы функции
tree(node *p, int h). Формирование нового узла дерева со значением h всегда
начинается с директивы root = tree(root,h), т. е. с одним и тем же значением
указателя root на корневой узел. Далее, если значение локальной переменной
p ≠ NULL, выполняется одна из директив:

p -> l = tree(p -> l, h),


или
p -> r = tree(p -> r,h).

Эти директивы находят родителя для нового узла со значением h


и определяют, каким потомком для этого родителя будет новый узел. Путь
к этому родителю фиксируется в стеке – в памяти стека сохраняются
значения локальных параметров p. В эти параметры при каждом обращении
к функции tree() пересылаются адреса узлов дерева, которые и формируют
путь к этому родителю. После того как родитель для нового звена найден,
формируется новый потомок (новый узел дерева) и определяются значения
полей нового узла (key = h, l = NULL, r = NULL). После этого завершается
обращение к рекурсивной функции tree() и последовательно начинают
происходить рекурсивные возвраты. При всех рекурсивных возвратах будут
выполняться прерванные обращения. Фактически это приводит

 Языки программирования. Язык С. Конспект лекций -269-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
к выполнению директивы return p. Эта директива возвращает значение (адрес
узла) из локального параметра p в переменную из предыдущего обращения:
это или левый указатель (p -> l) или правый указатель узла из предыдущего
обращения. Окончательный результат построения дерева для узлов 14, 10, 15,
9 представлен на рис. 26.15.

A0

A0

A1 A2

A1 A2

NULL NULL NULL

A3

NULL NULL

Рис. 26.15

В заключение дадим код программы формирования дерева.


Пример 3. Программа формирования дерева.
#include<conio.h>
#include<stdio.h>
#include<stdlib.h>
#include<iostream.h>
struct node
{
int key;
node *l, *r;
};
int n;
FILE *f;

node *tree(node *p, int h)


{ if( p == NULL )
{ p = new node;
p -> key=h;

p -> l = NULL;

 Языки программирования. Язык С. Конспект лекций -270-


ЛЕКЦИЯ 26. ДЕРЕВО
2. Алгоритм формирования дерева
p -> r = NULL;
}

else if ( h < p-> key)


p-> l = tree(p-> l, h);
else
p->r=tree(p->r,h);
return p;
}
void main()
{ node *root;
int h;
root = NULL;
clrscr();
f = fopen("test.dat","r");

while( !feof(f) )
{
fscanf(f,"%d",&h);
root = tree(root,h);
}
fclose(f); }

 Языки программирования. Язык С. Конспект лекций -271-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО

План

1. Обходы дерева.
2. Идеально сбалансированное дерево.
3. Удаление узла из дерева.

1. Обходы дерева

Важным моментом любого алгоритма является перебор элементов


информационных структур. И это понятно, потому что прежде чем
анализировать какую-то информацию, надо уметь находить ее
местоположение в памяти компьютера. Перебор элементов массива прост,
поскольку индексы массива и определяют его место в памяти. Для элементов
бинарного дерева место в памяти определяет порядок расположения узлов.
Исходя из этого порядка, можно предложить три способа перебора узлов
дерева. Все они основаны на рекурсивных алгоритмах. Если через R, A, B
обозначить соответственно корень, левое и правое поддерево, то три способа
доступа к узлам дерева можно определить следующим образом:
1. Прямой обход (preorder):
обработать корень R;
перейти к левому узлу A;
перейти к правому узлу B;
2. Симметричный обход (inorder):
перейти к левому узлу A;
обработать корень R;
перейти к правому узлу B;
3. Обход в обратном порядке (postorder):
перейти к левому узлу A;
перейти к правому узлу B;
обработать корень R.
Замечание 1. Для перечисленных обходов в некоторой литературе
используют иногда иную терминологию:
прямой обход соответствует обходу сверху, обходу в ширину;
симметричный обход соответствует обходу слева направо;
обход в обратном порядке соответствует обходу в глубину, обходу
снизу.
Обходы по определению рекурсивны. Приведем программные
реализации перечисленных выше обходов, которые базируются на
рекурсивных алгоритмах.
Пример 1. Программа прямого обхода узлов дерева. Схема обхода:
R, A, B.
void preorder ( node *t )

 Языки программирования. Язык С. Конспект лекций -272-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
{ cout << "узел- " << t -> key << "\n"; // Вывод информации об узле, адрес
// которого определен в указателе t.
if( t -> l != NULL ) preorder(t -> l); // Переходи к левому потомку узла,
// адрес которого определен в указателе t.
if( t -> r != NULL ) preorder(t -> r); } // Переходи к правому потомку узла,
// адрес которого определен в указателе t.

Пример 1а. Программа симметричного обхода узлов дерева. Схема


обхода: A, R, B.
void inorder ( node *t )
{ if( t -> l != NULL ) inorder( t -> l); // Переходи к левому потомку узла,
// адрес которого определен в указателе t.
cout << "узел- " << t -> key << "\n"; // Вывод информации об узле, адрес
// которого определен в указателе t.
if( t -> r != NULL) inorder( t -> r );} // Переходи к правому потомку узла,
// адрес которого определен в указателе t.

Пример 1б. Программа обхода узлов дерева в обратном порядке.


Схема обхода: A, B, R.
void postorder ( node *t )
{
if( t -> l != NULL )postorder(t -> l ); // Переходи к левому потомку узла,
// адрес которого определен в указателе t.
//AV1
if( t -> r != NULL )postorder(t -> r ); // Переходи к правому потомку узла,
// адрес которого определен в указателе t.
//AV2
cout << "узел- " << t -> key << "\n"; // Вывод информации об узле, адрес
// которого определен в указателе t.
}

На простейшем примере подробно разберем обход в обратном порядке


дерева.
1. После выполнения директивы головной программы postorder (root)
в локальную переменную t функции postorder передается адрес A47.
2. Так как значение t -> l ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> l ).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
1 1 AV1 A47 A21 A77 47

3. Прерывается текущее обращение. Начинается новое обращение


к функции postorder().
4. В локальную переменную t функции postorder передается адрес A21.

 Языки программирования. Язык С. Конспект лекций -273-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
5. Так как значение t -> l ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> l ).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
2 2 AV1 A21 A14 A43 21
1 1 AV1 A47 A21 A77 47

6. Прерывается текущее обращение. Начинается новое обращение


к функции postorder().
7. В локальную переменную t функции postorder передается адрес A14.
8. Так как значение t -> l ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> l ).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->ke
3 3 AV1 A14 A7 A16 14
2 2 AV1 A21 A14 A43 21
1 1 AV1 A47 A21 A77 47

9. Прерывается текущее обращение. Начинается новое обращение


к функции postorder().
10. В локальную переменную t функции postorder передается адрес A7.
11. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if( t -> r != NULL )postorder(t -> r ).
12. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран выводится узел 7.
13. Завершится текущее обращение. По правилам языка из стека
извлекаются локальные переменные 3-го обращения:

Возврат t t -> l t->r t->key


AV1 A14 A7 A16 14

и компьютер возобновляет работу с позиции //AV1 – это ранее прерванное


обращение. Начнет выполняться директива if(t -> r != NULL) postorder(t -> r ).
14. Так как значение t -> r = A16 ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> r ).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
4 3 AV2 A14 A7 A16 14
2 2 AV1 A21 A14 A43 21
1 1 AV1 A47 A21 A77 47

 Языки программирования. Язык С. Конспект лекций -274-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
Замечание 2. Обратите внимание на разницу состояния памяти стека
при 3 и 4-м обращении к функции. Эта разница заключается в позиции
возврата.
15. В локальную переменную t функции postorder передается адрес A16.
16. Так как значение t -> l = A15 ≠ NULL, то начнет выполняться
следующая директива текущего вызова if (t -> l ! = NULL )postorder(t-> l).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t ->key
5 4 AV1 A16 A15 NULL 16
4 3 AV2 A14 A7 A16 14
2 2 AV1 A21 A14 A43 21
1 1 AV1 A47 A21 A77 47

17. В локальную переменную t функции postorder передается адрес A15.


18. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if(t -> r != NULL) postorder(t -> r).
19. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран будет выведен
узел 15. Завершится текущее обращение. По правилам языка из стека
извлекаются локальные переменные 5-го обращения:

Возврат t t -> l t->r t ->key


AV1 A16 A15 NULL 16

и компьютер возобновляет работу с позиции //AV1 – это ранее прерванное


обращение, т. е. начнет выполняться директива if(t -> r != NULL)
postorder(t -> r).
20. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран будет выведен
узел 16.
21. Завершится 5-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 4-го обращения:

Возврат t t -> l t->r t ->key


AV2 A14 A7 A16 14
и компьютер возобновляет работу с позиции //AV2 – это ранее прерванное
обращение, т. е. начнется выполняться следующая директива cout << "узел- "
<< t -> key << "\n". На экран будет выведен узел 14.
22. Завершится 4-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 2-го обращения:

Возврат t t -> l t->r t ->key


AV1 A21 A14 A43 21

 Языки программирования. Язык С. Конспект лекций -275-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева

и компьютер возобновляет работу с позиции //AV1 – это ранее прерванное


обращение, т. е. начнет выполняться директива if( t -> r != NULL)
postorder(t -> r ).
Замечание 3. В памяти стека на данном этапе сохранится следующее
состояние:

№ п/п Глубина Возврат t t -> l t->r t->key


1 1 AV1 A47 A21 A77 47

23. Так как значение t -> r = A43 ≠ NULL, то произойдет рекурсивное


обращение к функции postorder(t -> r ).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
6 2 AV2 A21 A14 A43 21
1 1 AV1 A47 A21 A77 47

24. В локальную переменную t функции postorder передается адрес A43.


25. Так как значение t -> l = A31 ≠ NULL, то начнет выполняться
следующая директива текущего вызова if (t -> l ! = NULL) postorder(t-> l).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
7 2 AV1 A43 A31 A44 43
6 2 AV2 A21 A14 A43 21
1 1 AV1 A47 A21 A77 47

26. Прерывается текущее обращение. Начинается новое обращение


к функции postorder().
27. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if(t -> r != NULL) postorder(t -> r).
28. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран будет выведен
узел 31.
29. Завершится текущее обращение. По правилам языка из стека
извлекаются локальные переменные 7-го обращения:

Возврат t t -> l t->r t->key


AV1 A43 A31 A44 43

и компьютер возобновляет работу с позиции //AV1 – это ранее прерванное


обращение, т. е. начнет выполняться директива if( t -> r != NULL) postorde
r(t -> r).

 Языки программирования. Язык С. Конспект лекций -276-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
30. Так как значение t -> r = A44 ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> r).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
8 2 AV2 A43 A31 A44 43
6 2 AV2 A21 A14 A43 21
1 1 AV1 A4 A21 A77 47

31. Прерывается текущее обращение. Начинается новое обращение


к функции postorder().
32. В локальную переменную t функции postorder передается адрес A44.
33. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if(t -> r != NULL) postorder(t -> r).
34. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран будет выведен
узел 44.
35. Завершится 7-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 8-го обращения:

Возврат t t -> l t->r t->key


AV2 A43 A31 A44 43

и компьютер возобновляет работу с позиции //AV2 – это ранее прерванное


обращение, т. е. начнет выполняться директива cout << "узел- " << t -> key
<< "\n". На экран будет выведен узел 43.
36. Завершится 8-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 6-го обращения:

Возврат t t -> l t->r t->key


AV2 A21 A14 A43 21

и компьютер возобновляет работу с позиции //AV2 – это ранее прерванное


обращение, т. е. начнет выполняться директива cout << "узел- " << t -> key
<< "\n". На экран будет выведен узел 21.
37. Завершится 6-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 1-го обращения:

Возврат t t -> l t->r t->key


AV1 A47 A21 A77 47

и компьютер возобновляет выполнение 1-го обращения с позиции AV1, т. е.


начнет выполняться директива if( t -> r != NULL) postorder(t -> r ).
Замечание 4. Стек после завершения этого этапа пуст.

 Языки программирования. Язык С. Конспект лекций -277-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
38. Так как значение t -> r = A77 ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> r ).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
9 1 AV2 A47 A21 A77 47

39. В локальную переменную t функции postorder передается


адрес A77.
40. Так как значение t -> l = 65 ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> l).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
10 2 AV1 A77 A65 A93 77
9 1 AV2 A47 A21 A77 47

41. В локальную переменную t функции postorder передается


адрес A65.
42. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if( t -> r != NULL )postorder(t -> r).
43. Так как значение t -> r = A68 ≤ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> r).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
11 3 AV2 A65 NULL A68 65
10 2 AV1 A77 A65 A93 77
9 1 AV2 A47 A21 A77 47

44. В локальную переменную t функции postorder передается


адрес A68.
45. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if( t -> r != NULL )postorder(t -> r).
46. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран будет выведен
узел 68.
47. Завершится 12-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 11-го обращения:

Возврат t t -> l t->r t->key


AV2 A65 NULL A68 65

 Языки программирования. Язык С. Конспект лекций -278-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
и компьютер возобновляет выполнение 11-го обращения с позиции AV2, т. е.
начнет выполняться директива cout << "узел- " << t -> key << "\n". На экран
будет выведен узел 65.
48. Завершится 11-е обращение к функции. По правилам языка из стека
извлекаются локальные переменные 10-го обращения:

Возврат t t -> l t->r t->key


AV1 A77 A65 A93 77

и компьютер возобновляет выполнение 10-го обращения с позиции AV1,


т. е. начнет выполняться директива 10-го обращения if(t -> r != NULL)
postorder(t -> r).
49. Так как значение t -> r = A93 ≠ NULL, то произойдет рекурсивное
обращение к функции postorder(t -> r).

Состояние стека
№ п/п Глубина Возврат t t -> l t->r t->key
13 2 AV2 A77 A65 A93 77
9 1 AV2 A47 A21 A77 47

50. Прерывается текущее обращение. Начинается новое обращение


к функции postorder().
51. В локальную переменную t функции postorder передается адрес A93.
52. Так как значение t -> l = NULL, то начнет выполняться следующая
директива текущего вызова if( t -> r != NULL )postorder(t -> r).
53. Так как значение t -> r = NULL, то начнет выполняться следующая
директива cout << "узел- " << t -> key << "\n". На экран будет выведен
узел 93.
54. Завершится очередное обращение к функции. По правилам языка из
стека извлекаются локальные переменные 13-го обращения:

Возврат t t -> l t->r t->key


AV2 A77 A65 A93 7

и компьютер возобновляет выполнение 13-го обращения с позиции AV2, т. е.


начнет выполняться директива cout << "узел- " << t -> key << "\n". На экран
будет выведен узел 77.
55. Завершится 13-е обращение к функции. По правилам языка из
стека извлекаются локальные переменные 9-го обращения:

Возврат t t -> l t->r t->key


AV2 A47 A21 A77 47

 Языки программирования. Язык С. Конспект лекций -279-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
и компьютер возобновляет выполнение 9-го обращения с позиции AV2, т. е.
начнет выполняться директива cout << "узел- " << t -> key << "\n". На экран
будет выведен узел 47.
56. Завершится 9-е обращение к функции.
57. Так как стек пуст, то произойдет выход из рекурсивной функции
и компьютер вернется в головную программу.
Результат работы программы будет следующий:

7 15 16 14 31 44 43 21 68 65 93 77 47

Дадим без комментариев программу выдачи дерева на экран, так как


эта программа представляет собой один из способов обхода дерева.
void treeprint(node *p, int n)
{
int k;
if(p!=NULL)
{ treeprint(p->l, n+1);
for ( k = 1; k <= n; k++) cout << " " ;
cout << p -> key << "\n";
treeprint(p->r, n+1);
}
}

В этой программе имеет смысл объяснить только директиву for


(k=1;k<=n;k++) cout<<" ", которая представляет собой просто сдвиг
курсора относительно начала строки перед выдачей узла на экран. Причем
курсор для узлов одного уровня располагается на одинаковом расстоянии от
начала строки выдачи. Такое позиционирование курсора делает вывод дерева
на экран более наглядным.
Далее рассмотрим трассировки рекурсивных функций следующих
структур:
void rec()
{
S;
if ( <условие> ) rec();
}

void rec()
{
S1;
if ( <условие> ) rec();
S2
}

 Языки программирования. Язык С. Конспект лекций -280-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
1. Обходы дерева
void rec()
{
S1;
if ( <условие> ) rec();
if ( <условие> ) rec();
}

void rec()
{
if ( <условие> ) rec();
if ( <условие> ) rec();
S2;
}.

Авторы полагают, что для начального этапа освоения рекурсии


рассмотренных примеров достаточно, тем не менее подчеркивают, что при
изучении любого курса нет такого набора примеров, под которым можно
было бы подвести черту при освоении дисциплины и сказать: «Все!
Достаточно!»
Далее приведем две программы, которые демонстрируют
преимущество программирования с использованием рекурсии. Программы,
взятые из [5], строят идеально сбалансированное дерево с использованием
рекурсивного определения и нерекурсивного аналога. Анализировать
программы здесь не будем, так как рекурсивный вариант прост и прозрачен,
а анализ нерекурсивной программы по своей сути мало чем отличается
от анализа нерекурсивного варианта быстрой сортировки.

2. Идеально сбалансированное дерево

Дадим определение идеально сбалансированного дерева. Дерево


является сбалансированным, когда для каждого узла глубина его двух
поддеревьев различается не более чем на 1. Глубина узла n определяется
рекурсивно: глубина(n) = глубина(родителя(n)) + 1; глубина корня = 0.
Основная особенность сбалансированного дерева состоит в том, что
количество узлов левого и правого поддерева любого узла отличается не
более чем на 1. Алгоритм формирования сбалансированного дерева
формулируется лучше всего с помощью рекурсии [5]:
Шаг 1. Взять один узел в качестве корня.
Шаг 2. Построить левое поддерево тем же способом с числом узлов
nl = n/2.
Шаг 3. Построить правое поддерево тем же способом с числом узлов
nr = n-n/2–1.

 Языки программирования. Язык С. Конспект лекций -281-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
2. Идеально сбалансированное дерево
Пример 2. Программа построения идеально сбалансированного дерева.
Используется рекурсивный алгоритм.
#include<conio.h>
#include<stdio.h>
#include<stdlib.h>
#include<iostream.h>

struct node
{
int kl;
node *l, *r;
};
node *tree( int x );
void treeprint(node *p, int n);
FILE *f;

void main()
{
node *root;
int n;
clrscr();
f = fopen("bal.dat","r");
fscanf(f,"%d",&n); // Считываем из файла количество узлов.
root = tree(n);
fclose(f);
treeprint( root, 0);
getch();
}

node *tree( int n ) // Функция строит идеально сбалансированное дерево.


{
node *p;

int nl, nr, x;


if ( n == 0 )
{
p = NULL;
return p;

}
else
{
nl = n / 2; // Определяем число узлов в левом поддереве.
nr = n - nl -1; // Определяем число узлов в правом поддереве.
fscanf(f,"%d",&x); // Ввод нового ключа узла из файла.

 Языки программирования. Язык С. Конспект лекций -282-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
2. Идеально сбалансированное дерево
p = new node;
p -> kl = x;
p -> l = tree( nl ); // Строим левое поддерево.
p -> r = tree( nr ); // Строим правое поддерево.
return p;

}
}

void treeprint(node *p, int n)


{
int k;
if(p!=NULL)
{
treeprint(p -> l, n+1);
for ( k = 1; k <= n; k++) cout << " " ;
cout << p -> kl << "\n";
treeprint(p -> r, n+1);
}
}

3. Удаление узла из дерева

Удаление элементов бинарного дерева. Дерево является важной


структурой данных в информационных задачах. Основные операции над
двоичными деревьями следующие:
сформировать дерево;
включить узел в дерево;
поиск узла дерева;
удалить узел из дерева.
Ранее были разработаны и составлены программы, которые реализуют
первые три операции над деревьями. Составим программу, которая удаляет
заданный узел из дерева.
При удалении узла дерева могут возникнуть такие ситуации (рис. 27.1):
у удаляемого узла Z один потомок (левый или правый);
у удаляемого узла Z два потомка.
A A

Z C Z C

D D W

E F E N

Рис. 27.1

 Языки программирования. Язык С. Конспект лекций -283-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
3. Удаление узла из дерева
В первом случае удаление элемента происходит просто и алгоритм
удаления узла совпадает с алгоритмом удаления элемента из
однонаправленного списка. У элемента, который является родителем для
удаляемого узла (потомка), меняем значение указателя, а именно: в указатель
родителя пересылаем значение указателя на потомка удаляемого звена.
Схема подобного удаления продемонстрирована на рис. 27.2.
Рассмотрим теперь случай, когда из вершины, которая удаляется,
выходят две ветви. В этом случае удаление звена происходит по следующей
схеме:
1. Сначала находим узел Y дерева, значение которого можно
переставить в поле значения удаляемого звена Z. Этим узлом является либо
самый правый элемент левого поддерева удаляемого звена, либо самый
левый элемент правого поддерева. Заметим, что по сути поиска у узла Y
будет только один потомок.
2. Найденный узел Y удаляем по схеме первого случая, предварительно
переписав значение найденного узла в поле значения узла Z, которое надо
удалить.
Фактически при удалении узла из дерева происходит некоторая
перестройка дерева, а именно перестановка значений узлов. И после того как
эта перестановка произошла, удаляется из дерева тот узел, значение которого
изменило свое местоположение в дереве. Схема подобного удаления узла Z
продемонстрирована на рис. 27.3.
A1 A1 A1
a1 a1 a1
A2 B1 A3 B1 A3 B1
A2 A2
a2 a2
A3 NULL A3 NULL
A3 A3 A3
a3 a3 a3
A4 A5 A4 A5 A4 A5

Рис. 27.2

A a1 A a1 A a1
Z z1 W w1 Z y1 W w1 Z y1 W w1
C c1 B b1 C c1 B b1 C c1 B b1
D d1 Y y1 D d1 Y y1 D d1
E e1 E e1 E e1

Рис. 27.3

Ниже даны две функции, при помощи которых возможно удалить


заданный узел из дерева. Функция ydalenie ():

 Языки программирования. Язык С. Конспект лекций -284-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
3. Удаление узла из дерева
осуществляет поиск удаляемого узла Z;
удаляет узел Z из дерева, если у узла Z один потомок.
Функция ydal() подключается в случае, когда из удаляемого элемента
Z выходят два потомка. Функция ydal():
находит узел Y, значение которого можно переставить в поле значения
удаляемого звена Z;
значение из узла Y переписывает в поле значения узла Z;
удаляет узел Y как узел, имеющий одного потомка.
Обратим внимание на детали реализации программ, которые удаляют
из дерева заданный узел. Для функций ydalenie() и ydal() объявлена
глобальная переменная q, являющаяся указателем, тип которого совпадает с
типом элемента дерева. В данной программе этот тип определен как node.
Формальным параметром в заголовках функций является параметр, который
определяется как указатель на указатель типа node. В функцию передается
адрес поля указателя элемента дерева, в котором находится указатель
на следующий элемент дерева. Фактически это обеспечивает из тела функции
доступ к полям текущего элемента дерева и к полю указателя родителя.
node *q;
void ydal ( node **rp) // Функция определяет самое правое звено левого
// поддерева удаляемого элемента. Это необходимо в
// случае, когда удаляемый элемент дерева имеет двух потомков.
// rp – для адреса на поле указателя родителя, в котором
// содержится адрес на потомка. Значит, в *rp содержится
// адрес на потомка.
{
if ( (*rp) -> r != NULL )
ydal ( &((*rp) -> r) ); // Поиск узла, значение которого надо переписать
// в поле значения удаляемого элемента.
else
{
q -> kl = (*rp) -> kl; // В поле значения удаляемого звена переписываем
// значение из самого правого элемента левого поддерева.
q = *rp; // Запоминаем указатель на самый правый элемент
// левого поддерева в переменной q.
*rp = (*rp) -> l; // В правое поле родителя засылаем указатель на левое
// поддерево удаляемого звена.
delete q; // Из программы удаляем память. Указатель на удаляемую
// память находится в q.
}
}

void ydalenie ( int n, node **p) // Поиск удаляемого звена.


{ // В переменную p пересылается адрес левого
// или правого указателя предыдущего узла.

 Языки программирования. Язык С. Конспект лекций -285-


ЛЕКЦИЯ 27. БИНАРНОЕ ДЕРЕВО
3. Удаление узла из дерева
// Это значит, что в *p находится адрес на текущий элемент дерева.
// *p – это левое или правое поле указателей узла дерева.
if ( *p == NULL )
{ cout << "Звена с ключом " << n << " НЕТ !!!\n";}
else
{
if ( n < (*p) -> kl )
ydalenie(n, &((*p) -> l)); // Идем по левой ветви.
else
{
if ( n > (*p) -> kl )
ydalenie(n, &((*p) -> r)); // Идем по правой ветви.
else
{
q = *p; // Из поля указателя родителя пересылаем в q адрес звена,
// которое надо удалить. В q запоминаем адрес этого звена.
if ( q -> r == NULL ) // Если удаляемое звено не имеет правого потомка.
{
*p = q -> l; // В поле указателя родителя переписываем указатель
// на левый потомок удаляемого узла.
delete q;
}
else
{
if ( q -> l == NULL ) // Если удаляемое звено не имеет левого потомка.
{
*p = q -> r; // В поле указателя родителя переписываем указатель
// на правый потомок удаляемого узла.
delete q;
}
else ydal ( &(q -> l)); // Если у узла, который надо удалить, два потомка.
} } }}}

 Языки программирования. Язык С. Конспект лекций -286-


ЛЕКЦИЯ 28. СОРТИРОВКА

План

1. Классы сортировок.
2. Сортировка выбором.
3. Сортировка обменом (методом пузырька).
4. Сортировка вставками.
5. Пирамидальная сортировка.

1. Классы сортировок

Рассмотрим традиционный спектр задач, которые обычно разбираются


в учебниках по программированию [1, 2, 3, 4, 5, 6]. Конечно, возникает
вопрос о целесообразности повторения изложения этих задач. Авторы эту
целесообразность видят в следующем. Во-первых, этот набор задач
необходимо рассмотреть (по крайней мере, кратко) уже потому, что это
учебное пособие по программированию. Во-вторых, не так уж много
учебников, в которых эти задачи реализованы на языке С. И наконец,
в-третьих, нет границ в методике представления задач и способах подачи
материала, в последовательности изложения и группировке задач при их
изложении. Собственно говоря, руководствуясь этими доводами, авторы
решили еще раз рассмотреть этот сложившийся базовый набор задач в курсе
программирования.
Д. Кнут в [3, с. 13–463] рассматривает около 25 алгоритмов сортировок.
И это далеко не полный перечень. Такое обилие алгоритмов сортировок
свидетельствует только об одном: нет «наилучшего» способа сортировки
[3, с. 94]. Этим фактом мы хотим подчеркнуть важность и нетривиальность
задач, которые связаны с сортировкой данных.
Алгоритмы сортировки хорошо изучены. Если есть множество из
n элементов, то, как известно из комбинаторики, существует n! различных
последовательностей перечисления этих элементов. Одной из важных задач
информатики является поиск элемента в заданном множестве из n элементов.
Естественно, элемент множества можно быстрее найти, если само множество
каким-то образом упорядочено, например: слова по алфавиту, числа по
возрастанию или убыванию. Бинарное дерево – это тоже пример некоторого
упорядоченного множества. Сортировка позволяет записать начальные
данные памяти в том порядке, необходимость которого диктуется задачей.
Кнут предлагает трактовать слово «сортировка» в узком смысле, а именно
как «сортировку по порядку». Авторы разделяют такой подход.
Задачи сортировки мы будем рассматривать только на массиве чисел.
Итоговый порядок представления данных определяется поставленной ниже
задачей.

 Языки программирования. Язык С. Конспект лекций -287-


ЛЕКЦИЯ 28. СОРТИРОВКА
1. Классы сортировок
Задача 1. Упорядочить массив чисел в порядке возрастания (убывания)
его элементов.
Методы сортировки можно разбить на три группы:
сортировка выбором;
сортировка обменом;
сортировка вставками.
Рассмотрим основные принципы, на которых основаны эти виды
сортировок.

2. Сортировка выбором

Алгоритм сортировки выбором кратко можно сформулировать так. Из


массива выбираем минимальный элемент (если сортируем по возрастанию) и
переставляем его с первым. Далее этот процесс повторяется для частей
массива без первого элемента, затем без первых двух и т. д. Тот же самый
алгоритм можно описать иначе:
• последовательно рассматриваем части массива a[k], a[k+1], …, a[n-1],
где n – размер массива; k = 0, 1, 2, …, n – 2;
• среди элементов каждой части массива выбираем элемент a[ima], где
ima – индекс элемента, который принимает наименьшее (или наибольшее)
значение;
• меняем местами элемент a[ima] с a[k].
Пример 1. Пример сортировки выбором рассмотрен на рис. 28.1, где
выделяются части массива, из которых выбирается наименьшее число,
а подчеркнутые числа меняются местами.
Рассмотрим массив с начальными данными: 12 24 -36 7 25 0.

Шаг 1: 12 24 -36 7 25 0
Шаг 2: -36 24 12 7 25 0
Шаг 3: -36 0 12 7 25 24
Шаг 4: -36 0 7 12 25 24
Шаг 5: -36 0 7 12 25 24
Шаг 6: -36 0 7 12 24 25

Рис. 28.1

Программа 1. Сортировка массива методом выбора.


#include <conio.h>
#include <iostream.h>
#include <stdio.h>
void main()
{
const n = 10;
float a[n], b;

 Языки программирования. Язык С. Конспект лекций -288-


ЛЕКЦИЯ 28. СОРТИРОВКА
2. Сортировка выбором
int i, j, max; // i, j – для текущих параметров цикла.
// max – для индекса максимального элемента.
FILE *f1;
clrscr();
f1 = fopen("t1.dat", "r"); // Открывается файл t1.dat на диске.
for (i = 0; i <= n-1; i++)
{ fscanf( f1, "%f", &a[i] ); // Ввод чисел в массив из файла.
printf(" %f ", a[i]); // Вывод массива на экран.
}
for ( i = 0; i <= n-2; i++)
{ max = i;
for ( j = i+1; j <= n-1; j++) // Поиск максимального элемента
// у части массива от индекса i+1 до n-1.
if ( a[max] < a[j] ) max = j;
if( i != max ) { b = a[i]; // Элемент с индексом a[i]
// переставляем с максимальным элементом.
a[i] = a[max];
a[max] = b; }
}
printf("\n ");
for (i = 0; i <= n-1; i++)
{ printf(" %f ", a[i]); // Массив выводим на экран. }
getch(); }

3. Сортировка обменом (методом пузырька)

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


сортировки. Алгоритм сортировки методом пузырька кратко можно
сформулировать так: сравниваем два соседних элемента и меняем их
местами, если они нарушают порядок. Пользуясь методом пузырька, надо:
• последовательно (в цикле) рассматривать части массива: a[k],
a[k+1], …, a[n-1], где n – размер массива, k = 1, 2, …, n – 1;
• начиная с элемента a[n-1], последовательно сравнивать элемент a[j]
c a[j-1];
• если a[j] < a[j-1], то поменять местами эти элементы и a[j-1]
сравнить с a[j-2] и т. д.;
• если же a[j] > a[j-1], то сразу переходить к сравнению элемента a[j-1]
с элементом a[j-2] и т. д.
Сравнение элементов следует всегда начинать либо с первого элемента,
либо с последнего. На рис. 28.2 дана трассировка изменений, которые
происходят в массиве при первом проходе, если используется сортировка
методом пузырька. Первый проход происходит от элемента a[n-1] до a[1].
Элементы, которые сравниваются между собой, выделяются. В случае когда
эти элементы должны поменяться местами, один из них подчеркивается.

 Языки программирования. Язык С. Конспект лекций -289-


ЛЕКЦИЯ 28. СОРТИРОВКА
3. Сортировка обменом (методом пузырька)
Дан массив 12 24 -36 7 25 0. С помощью метода пузырька,
совершается проход массива от (n-1)-го до элемента с индексом 1.

Шаг 1: 12 24 -36 7 25 0
Шаг 2: 12 24 -36 7 0 25
Шаг 3: 12 24 -36 0 7 25
Шаг 4: 12 24 -36 0 7 25
Шаг 5: 12 -36 24 0 7 25
Шаг 6: -36 12 24 0 7 25

Рис. 28.2

В результате такого преобразования самое маленькое значение в


массиве (в данном случае –36) переместится на первое место. Это позволяет
при дальнейшем преобразовании исключить из рассмотрения этот первый
элемент. Далее совершается такой же проход от (n-1)-го элемента до элемента
с индексом 2; затем от (n-1)-го элемента до элемента с индексом 3 и т. д.
Приведем несколько вариантов программ сортировки обменом,
которые реализовывают рассмотренный алгоритм.
Программа 2. Сортировка массива методом пузырька.
Алгоритм программы 2
Шаг 1. Цикл по i = 1, ..., n.
Шаг 2. Цикл по j = n-1, n-2, …, 1+i.
Шаг 3. Если a[j] > a[j+1] (т. е. текущий больше предыдущего), то шаг 4,
иначе – шаг 5.
Шаг 4. Меняем местами (производим обмен) элементы a[j-1] и a[j].
Шаг 5. Конец цикла по j.
Шаг 6. Конец цикла по i.

#include <conio.h>
#include <iostream.h>
#include <stdio.h>
void main()
{
const n = 10; // n – для размера массива.
int i, a[n], x, j;
FILE *f1;
clrscr();
f1 = fopen("fr.dat","r");
cout << " Из файла введен массив: \n";
for (i = 0; i < n; i++)
{
fscanf(f1,"%d", &a[i]); // Ввод массива из файла.
printf( "%d ",a[i]);
}

 Языки программирования. Язык С. Конспект лекций -290-


ЛЕКЦИЯ 28. СОРТИРОВКА
3. Сортировка обменом (методом пузырька)
fclose(f1);

for (i = 0; i < n; i++) // Шаг 1. Цикл по i от 0.


for( j = n-1; j >= 1+i ; j--) // Шаг 2. Цикл по j от n-1.
if ( a[j] < a[j-1] ) // Шаг 3. Сравниваются элементы массива.
{
x = a[j]; // Шаг 4. Перестановка a[j] с a[j-1].
a[j] = a[j-1];
a[j-1] = x;
} // Шаг 5. Конец цикла по j.
// Шаг 6. Конец цикла по i.

cout << "\n"; // Перевод на другую строку.


cout << "\n\nМассив после сортировки:\n";
for (i = 0; i < n; i++) // Вывод отсортированного массива на экран.
printf( "%d ",a[i]);
getch(); }

Программа 2а. Сортировка массива методом пузырька.


Программа 2а отличается от программы 2 шагами 2 и 3, а именно тем,
что самый большой элемент рассматриваемых частей массива перемещается
на последнее место.
Алгоритм программы 2а
Шаг 1. Цикл по i = 1, ..., n.
Шаг 2. Цикл по j = 0, 1, …, n-1-i. Части массива начинают преобразо-
вывать от первого элемента.
Шаг 3. Если a[j] > a[j+1] (т. е. текущий больше предыдущего), то шаг 4,
иначе – шаг 5.
Шаг 4. Меняем местами (производим обмен) элементы a[j-1] и a[j].
Шаг 5. Конец цикла по j.
Шаг 6. Конец цикла по i.

#include <conio.h>
#include <iostream.h>
#include <stdio.h>
void main()
{
const n = 10; // n – для размера массива.
int i, a[n], x, j;
FILE *f1;
clrscr();
f1 = fopen("fr.dat","r");
cout << " Из файла введен массив:\n";
for (i = 0; i < n; i++)

 Языки программирования. Язык С. Конспект лекций -291-


ЛЕКЦИЯ 28. СОРТИРОВКА
3. Сортировка обменом (методом пузырька)
{
fscanf(f1,"%d", &a[i]); // Ввод массива из файла.
printf( "%d ",a[i]); // Вывод массива на экран.
}
fclose(f1);

for (i = 0; i < n; i++) // Шаг 1. Цикл по i = 0, 1, …, n-1.


for( j = 0; j < n-1-i ; j++) // Шаг 2. Цикл по j = 0, 1, …, n-1-i.
if ( a[j] > a[j+1] ) // Шаг 3. a[j] сравнивается с a[j+1].
{
x = a[j]; // Шаг 4. Перестановка a[j] с a[j+1].
a[j] = a[j+1];
a[j+1] = x;
}