Загрузил Ulugbek Reypnazarov

cpp-lab-practice

Реклама
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
ФИЛИАЛ ФЕДЕРАЛЬНОГО ГОСУДАРСТВЕННОГО ОБРАЗОВАТЕЛЬНОГО УЧРЕЖДЕНИЯ
ВЫСШЕГО ОБРАЗОВАНИЯ
НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ «МИСиС»
В ГОРОДЕ АЛМАЛЫК
Нишонов М.М., Темербекова Б.М.
Лабораторный практикум
Основы программирование и алгоритмизации
Алмалык, 2019
Редакция авторов 3.14 LATEX
2
Оглавление
1 Лабораторная работа № 1 . . . . . . . . . . . . . . . . . . .
1.1 Основные сведения о языке C++. . . . . . . . . . . . . .
1.2 Основные сведения о программах на языке C++ . . . .
1.3 Стандартные библиотечные функции. . . . . . . . . .
1.4 Литералы. Идентификаторы, константы, переменные,
1.5 Операторы языка С++. . . . . . . . . . . . . . . . . . .
1.6 Выражения языка С++. . . . . . . . . . . . . . . . . . .
1.7 Стандартные математические функции. . . . . . . . .
1.8 Основы отладки программ. . . . . . . . . . . . . . . . .
1.9 Методика и порядок выполнения работы. . . . . . . .
1.10 Пример выполнения лабораторной работы №1: . . . .
1.10.1 Индивидуальное задание №1. . . . . . . . . . .
2 Лабораторная работа № 2 . . . . . . . . . . . . . . . . . . .
2.1 Унифицированный язык моделирования (UML). . . . .
2.2 Алгоритм разветвляющейся структуры. . . . . . . . .
2.3 Условный оператор (оператор if). . . . . . . . . . . .
2.4 Переключатель (оператор switch). . . . . . . . . . . .
2.5 Методика и порядок выполнения работы. . . . . . . .
2.6 Пример выполнения лабораторной работы № 2 . . . .
2.6.1 Индивидуальное задание № 1 . . . . . . . . . .
2.6.2 Индивидуальное задание № 2 . . . . . . . . . .
2.6.3 Индивидуальное задание № 3 . . . . . . . . . .
3 Лабораторная работа № 3 . . . . . . . . . . . . . . . . . . .
3.1 Алгоритм циклической структуры . . . . . . . . . . .
3.2 Цикл с параметром или цикл типа for . . . . . . . . .
3.3 Цикл с предусловием или цикл типа while. . . . . . .
3.4 Цикл с постусловием или цикл типа do ... while . .
3.5 Оператор безусловного перехода goto. . . . . . . . . .
3.6 Оператор break. . . . . . . . . . . . . . . . . . . . . . .
3.7 Оператор return. . . . . . . . . . . . . . . . . . . . . .
3.8 Методика и порядок выполнения работы. . . . . . . .
3.9 Пример выполнения лабораторной работы № 3 . . . .
3.9.1 Индивидуальное задание № 1 . . . . . . . . . .
3
. . . . . . . . . . 7
. . . . . . . . . . 7
. . . . . . . . . . 14
. . . . . . . . . . 15
ключевые слова. 22
. . . . . . . . . . 30
. . . . . . . . . . 34
. . . . . . . . . . 36
. . . . . . . . . . 37
. . . . . . . . . . 39
. . . . . . . . . . 41
. . . . . . . . . . 43
. . . . . . . . . . 45
. . . . . . . . . . 45
. . . . . . . . . . 49
. . . . . . . . . . 52
. . . . . . . . . . 56
. . . . . . . . . . 63
. . . . . . . . . . 72
. . . . . . . . . . 72
. . . . . . . . . . 75
. . . . . . . . . . 78
. . . . . . . . . . 81
. . . . . . . . . . 81
. . . . . . . . . . 82
. . . . . . . . . . 90
. . . . . . . . . . 94
. . . . . . . . . . 97
. . . . . . . . . . 99
. . . . . . . . . . 102
. . . . . . . . . . 104
. . . . . . . . . . 111
. . . . . . . . . . 111
Оглавление
4
5
6
7
8
9
3.9.2 Индивидуальное задание № 2 . . . . . . . .
3.9.3 Индивидуальное задание № 3 . . . . . . . .
3.9.4 Индивидуальное задание № 4 . . . . . . . .
Лабораторная работа № 4 . . . . . . . . . . . . . . . . .
4.1 Основные сведения о массивах в языке C++. . . . .
4.2 Методика и порядок выполнения работы. . . . . .
4.3 Пример выполнения лабораторной работы № 4 . .
4.3.1 Индивидуальное задание № 1 . . . . . . . .
4.3.2 Индивидуальное задание № 2 . . . . . . . .
Лабораторная работа № 5 . . . . . . . . . . . . . . . . .
5.1 Основные сведения об указателях в языке C++. . .
5.2 Инициализация указателей. . . . . . . . . . . . . .
5.3 Операции с указателями. . . . . . . . . . . . . . . .
5.4 Динамические массивы. . . . . . . . . . . . . . . . .
5.5 Ссылки. . . . . . . . . . . . . . . . . . . . . . . . . .
5.6 Методика и порядок выполнения работы. . . . . .
5.7 Пример выполнения лабораторной работы № 5: . .
5.7.1 Индивидуальное задание № 1: . . . . . . . .
5.7.2 Индивидуальное задание № 2: . . . . . . . .
Лабораторная работа № 6 . . . . . . . . . . . . . . . . .
6.1 Основные сведения о двумерных массивах в языке
6.2 Динамические массивы. . . . . . . . . . . . . . . . .
6.3 Методика и порядок выполнения работы. . . . . .
6.4 Пример выполнения лабораторной работы № 6: . .
6.4.1 Индивидуальное задание № 1: . . . . . . . .
6.4.2 Индивидуальное задание № 2: . . . . . . . .
Лабораторная работа № 7 . . . . . . . . . . . . . . . . .
7.1 Основные сведения о функциях в языке С++. . . .
7.2 Перегрузка функций. . . . . . . . . . . . . . . . . .
7.3 Шаблоны функций. . . . . . . . . . . . . . . . . . .
7.4 Методика и порядок выполнения работы. . . . . .
7.5 Пример выполнения лабораторной работы № 7: . .
7.5.1 Индивидуальное задание № 1: . . . . . . . .
7.5.2 Индивидуальное задание № 2: . . . . . . . .
7.5.3 Индивидуальное задание № 3: . . . . . . . .
Лабораторная работа № 8 . . . . . . . . . . . . . . . . .
8.1 Основные сведения о строках в языке С++. . . . . .
8.2 Операции со строками. . . . . . . . . . . . . . . . .
8.3 Работа с символами. . . . . . . . . . . . . . . . . . .
8.4 Пример выполнения лабораторной работы № 8: . .
8.4.1 Индивидуальное задание № 1: . . . . . . . .
Лабораторная работа № 9 . . . . . . . . . . . . . . . . .
9.1 Основные сведения о структурах в языке С++. . .
9.2 Методика и порядок выполнения работы. . . . . .
4
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
С++.
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
114
117
120
123
123
132
139
139
142
147
147
149
152
154
162
172
178
178
182
187
187
191
209
213
213
218
223
223
249
255
259
265
265
270
273
275
275
280
281
307
307
309
309
329
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
9.3 Пример выполнения лабораторной работы № 9: . . . . . . . .
9.3.1 Индивидуальное задание № 1: . . . . . . . . . . . . . .
10 Лабораторная работа № 10 . . . . . . . . . . . . . . . . . . . . . . .
10.1 Структура программы на объектно-ориентированном языке.
10.2 Понятие объекта. . . . . . . . . . . . . . . . . . . . . . . . . .
10.3 Понятие класса. . . . . . . . . . . . . . . . . . . . . . . . . . .
10.4 Инкапсуляция. . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.5 Наследование (Inheritance). . . . . . . . . . . . . . . . . . .
10.6 Полиморфизм. . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.7 Конструкторы. . . . . . . . . . . . . . . . . . . . . . . . . . . .
10.8 Методика и порядок выполнения работы. . . . . . . . . . . .
Литература . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
335
335
338
338
339
340
346
347
350
351
406
408
Предисловие
Данный лабораторный практикум предназначен для привития студентам навыков и умений разработки консольных приложений на языке C++ с использованием
структурного и объектно-ориентированного подходов.
Основная цель лабораторного практикума - помочь студентам закрепить знания,
полученные на лекционных занятиях и овладеть навыками структурного и объектноориентированного программирования при разработке приложений на языке C++ в
среде Geany.
В процессе выполнения заданий студенты рассмотрят процесс создания модульных программ, элементы теории модульного программирования, объектноориентированный подход к проектированию и разработке программ, объектные
типы данных и переменные объектного типа, основные принципы объектноориентированного программирования (инкапсуляцию, наследование, полиморфизм).
Вопросы, изученные в ходе выполнения лабораторных работ, помогут студентам
освоить основные принципы разработки приложений на объектно-ориентированном
языке C++, что будет являться основой для дальнейшего изучения как языков и
технологий программирования, так и систем управления базами данных.
6
Лабораторная работа № 1
Cреда разработки Geany.
Программирование алгоритмов линейной структуры.
Работа с переменными. Базовые типы переменных.
Цель работы и содержание:
Изучение возможностей среды разработки Geany, разработка консольных приложений в среде Geany, приобретение навыков программирования линейных процессов,
основы отладки программ в среде Geany. Освоить технологию программирования
линейных алгоритмов на языке C++, функции ввода/вывода данных, оператора
присваивания, работа с переменными, базовыми типами переменных.
Ход работы
1.1 Основные сведения о языке C++.
Язык C++ является универсальным языком программирования, в дополнение
к которому разработан набор разнообразных библиотек. Поэтому он позволяет
решить практически любую задачу программирования. Язык программирования
C++(читается «си плюс плюс») был разработан на основе языка C(Си) Бьярном
Страуструпом в 1972 году. На первых этапах разработки язык носил условное название «Си с классами», а позже Рик Маскитти придумал название «C++», что
образно отразило происхождение этого нового языка от языка C. Язык C++ является расширением языка C, поэтому программы, написанные на языке C, могут
обрабатываться компилятором языка C++. Язык C++ был создан, чтобы улучшить
язык C, поддержать абстракцию данных и обеспечить объектно-ориентированное
программирование (ООП). C++ - это язык программирования общего назначения,
хорошо известный своей эффективностью, экономичностью, и переносимостью. Указанные преимущества C++ обеспечивают хорошее качество разработки почти любого
вида программного продукта. Использование C++ в качестве инструментального
языка позволяет получать быстрые и компактные программы. Во многих случаях
7
1 Лабораторная работа № 1
программы, написанные на C++, сравнимы по скорости с программами, написанными
на языке Ассемблера.
Основные элементы среды разработки Geany.
Integrated Development Environment (интегрированная среда разработки),
или, сокращенно, IDE - это программный продукт, объединяющий текстовый редактор, компилятор, отладчик и справочную систему.
Geany — среда разработки программного обеспечения, написанная с использованием библиотеки GTK+. Доступна для следующих операционных систем: BSD,
Linux, Mac OS X, Solaris и Windows. Geany распространяется согласно GNU General
Public License.
Geany не включает в свой состав компилятор. Для создания исполняемого
кода используется GNU Compiler Collection или, при необходимости, любой другой
компилятор.
Любая программа, создаваемая в среде Geany, даже такая простая, как «Hello,
World!», может быть оформлен как отдельный проект (project). Проект - это набор
взаимосвязанных исходных файлов и, возможно, включаемых (заголовочных) файлов, компиляция и компоновка которых позволяет создать исполняемую программу.
Рабочая область может содержать любое количество различных проектов, сгруппированных вместе для согласованной разработки: от отдельного приложения до
библиотеки функций или целого программного пакета. Очевидно, что для решения
наших учебных задач каждая программа будет воплощаться в виде одного проекта,
поэтому рабочая область проекта у нас всегда будет содержать ровно один проект.
Запуск IDE.
Типы приложений. Вызов Geany осуществляется или через Меню→
Программирование→Geany, или щелчком мышью по пиктограмме с соответствующим
именем. После запуска Geany появляется главное окно программы, показанное на
рисунке 1.1. (В зависимости от настроек для вашего рабочего стола Geany его вид
может несколько отличаться от показанного на рисунке 1.1).
1. Левое окно (окно рабочей области) предназначено для оказания помощи при
написании и сопровождении больших многофайловых программ. Пока что оно
закрыто, но после создания нового проекта (или загрузки сохраненного ранее
проекта) одна из вкладок этого окна будет содержать список файлов проекта.
2. Правое окно (окно редактора) используется для ввода и проверки исходного
кода.
3. Нижнее окно (окно вывода) служит для вывода сообщений о ходе компиляции,
сборки и выполнения программы. В частности, сообщения о возникающих
ошибках появляются именно в этом окне.
8
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 1.1: IDE Geany
Под заголовком главного окна, находится строка меню. Назначение команд
меню и кнопок панелей инструментов мы будем рассматривать по мере необходимости, разбирая основные приемы работы в IDE. Для кнопок панелей инструментов
предусмотрена удобная контекстная помощь: если навести курсор мыши на кнопку
и задержать, то всплывет подсказка с назначением кнопки.
Geany позволяет строить проекты разных типов, ориентированные на различные
сферы применения. В то же время разработчики Geany предусмотрели работу и с так
называемыми консольными приложениями. При запуске консольного приложения
операционная система создает так называемое консольное окно, через которое идет
весь ввод-вывод программы. Внешне это напоминает работу в операционной системе
MS DOS или других операционных системах в режиме командной строки.
Создание нового проекта.
Для создания нового проекта типа «консольное приложение» выполните следующие действия:
- выберите в строке меню главного окна команду Проект → Новый (рис. 1.2);
- в открывшемся диалоговом окне Новый введите имя в текстовом поле Имя:. При
этом происходит авто заполнение следующего текстового поле Имя файла:. В
нашем примере имя проекта myproject, имя файла в котором будет сохраняется наш проект будет: myproject.geany. Следующее поле показывает путь к
каталогу где будут находится файлы проекта (рис. 1.2);
9
1 Лабораторная работа № 1
Рис. 1.2: Раздел главного меню: Проект
- щелкните левой кнопкой мыши на кнопке Создать. Появится диалоговое окно
с вопросом: Каталог "путь/имя каталога" не существует. Создать каталог
проекта? (рис. 1.3);
- щелкните левой кнопкой мыши на кнопке OK (рис. 1.3);
Рис. 1.3: Имя проекта и его создание
Добавление к проекту файлов с исходным кодом.
Для этого необходимо выполнить следующие действия:
1) выберите в строке меню главного окна команду Файл→Создать; в результате
появиться окно, точно такой, как на рис.1.1, с файлом без имени;
10
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2) наберите текст исходного кода так, как на рис.1.4;
3) сохраните файл так, как показано на рис.1.5; щелкните на кнопке показанной
на левом рисунке (кнопка с надписью Сохранить текущий файл) рис.1.5;
4) появиться диалоговое окно (рисунок с права рис.1.5), нужно присвоить имя
файлу с расширением .cpp;
5) появившимся окне, в текстовом поле Имя: присвойте имя (в нашем случае:
main.cpp);
6) в конце создается файл с исходным кодом принадлежащий к проекту myproject
(рис.1.6).
Рис. 1.4: Создание файла для сохранение текста программ написанный на C++
Рис. 1.5: Сохранение и присвоение имени в файл
11
1 Лабораторная работа № 1
Рис. 1.6: Имя файла с расширением .cpp
Компиляция, компоновка и запуск исполняемого файла.
Эти операции могут быть выполнены или через меню Сборка главного окна,
или с нажатием соответствующих кнопок показанные на рис.1.7, 1.8, 1.9. Опишем
кратко основные команды меню Сборка:
- Скомпилировать текущий файл - компиляция выбранного файла (рис.1.7, окно
слева). Результаты компиляции выводятся в нижнем окне (рис.1.7, окно справа).
- Собрать текущий файл - компоновка проекта. Компилируются все файлы, в
которых произошли изменения с момента последней компоновки. После компиляции происходит сборка (link) всех объектных модулей (рис.1.8), включая
библиотечные, в результирующий исполняемый файл. Сообщения об ошибках
компоновки выводятся в нижнем окне. Если обе фазы компоновки завершились без ошибок, то созданный исполняемый файл может быть запущен на
выполнение (рис.1.9).
- Запустить или посмотреть текущий файл - выполнение исполняемого файла
(рис.1.9), созданного в результате компоновки проекта.
12
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 1.7: Компиляция и сообщение компилятора
Рис. 1.8: Компоновка (сборка) и сообщение компоновщика
Рис. 1.9: Запуск исполняемого файла и результат работы программы
13
1 Лабораторная работа № 1
Создание первой программы.
Необходимо ввести в редакторе кода текст, представленный в листинге 1.1.
Листинг 1.1
#include <iostream>
using namespace std;
int main()
{
cout << "Hello,␣World!" << endl;
return 0;
}
Это и есть наша первая программа на языке C++ в среде Geany. По традиции
она выводит на экран надпись «Hello, World!» (Здравствуй, Мир!).
Запустите программу на выполнение. Если вы все сделали правильно, на экране
появится окно с соответствующими надписями, точно такой же, как на рис.1.9. Чтобы
закрыть это окно, нажмите клавишу Ввод.
1.2 Основные сведения о программах на языке C++
Состав языка C++:
- Алфавит языка или его символы - это основные неделимые знаки, с помощью
которых пишутся все тексты на языке.
- Лексема или элементарная конструкция - это минимальная единица языка,
имеющая самостоятельный смысл.
- Выражение задает правило вычисления некоторого значения.
- Оператор задает законченное описание некоторого действия.
Алфавит C++ включает:
- прописные и строчные латинские буквы и знак подчеркивания;
- арабские цифры от 0 до 9;
- специальные знаки:
“ { } , | [ ] ( ) + - / \% * . \ ‘ : ? < = > ! \& # ~ ; ^
- пробельные символы: пробел, символы табуляции, символы перехода на новую
строку.
14
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Из символов алфавита формируются лексемы языка:
- идентификаторы;
- ключевые (зарезервированные) слова;
- знаки операций;
- константы;
- разделители (скобки, точка, запятая, пробельные символы).
Основная программная единица на языке C++ - это текстовый файл с названием
имя.cpp, где cpp - это принятое расширение для программ на C++, а имя - определяется
исходя из семантики разрабатываемой программы. Текстовый файл с программой на
C++ вначале обрабатывает препроцессор, который распознает команды (директивы)
препроцессора (каждая такая команда начинается с символа "]") и выполняет их.
Практически в каждой программе на C++ используется процессорная команда
#include <имя_включаемого_(заголовочного)_файла>
1.3 Стандартные библиотечные функции.
Все стандартные функции имеют прототип в соответствующем заголовочном
файле. Далее в таблице 1.3 приведен список наиболее часто используемых заголовочных файлов, присутствующий почти в каждом компиляторе C++.
Программа на языке C++ состоит из функций, описаний и директив препроцессора. Одна из функций должна иметь имя main(). Простейшее определение функции
имеет следующий формат:
тип возвращаемого значения имя ([ параметры ])
{
операторы, составляющие тело функции
}
Как правило, функция используется для вычисления какого-либо значения, поэтому
перед именем функции указывается его тип.
Сведения:
- если функция не должна возвращать значение, указывается тип void;
- тело функции является блоком и, следовательно, заключается в фигурные
скобки;
- функции не могут быть вложенными;
- каждый оператор заканчивается точкой с запятой (кроме составного оператора).
15
1 Лабораторная работа № 1
Заголовочный файл
assert.h
ctype.h
errno.h
floaf.h
limits.h
locale.h
math.h
sefjmp.h
stdarg.h
stdio.h
stdlib.h
string.h
conio.h
time.h
dos.h
iostream.h
Таблица 1.1: Типы заголовочных файлов
Назначение
Содержит декларации функций для диагностики программ
Содержит декларации функций преобразования типов
Содержит описание кодов ошибок
Содержит декларации математических функций для работы
с вещественными числами одинарной точности
Содержит определение границ диапазона изменения значений
переменных различных типов
Содержит функции для поддержки национальных стандартов
Содержит декларации математический функций для работы
с вещественными числами двойной точности
Содержит декларации функций для осуществления межсегментных переходов (используется только в операционной системе
MS-DOS)
Содержит макросы для работы с функциями с переменным
числом аргументов
Содержит декларации функций ввода-вывода в C++
Содержит декларации функций библиотеки времени
исполнения (RTL - Runtime Library) C++
Содержит декларации функций для работы с ASCIIZ строками
Содержит декларации функций консольного ввода-вывода
(используется только в операционной системе MS-DOS)
Содержит декларации функций для работы с датой/временем
Содержит декларации функций, используемые для обращения
к сервисам DOS (используется только в операционной системе
MS-DOS)
Содержит объекты и классы потокового ввода-вывода
Каждая программа должна содержать функцию с именем main. Эта функция
является точкой входа, с которой начинается выполнение программы. Обычно в
main() отсутствуют параметры. Тело функции всегда заключается в фигурные
скобки. Ключевое слово int используется для обозначения того факта, что функция
возвращает некоторое целочисленное значение, которое затем будет использовано
операционной системой для определения факта завершения программы с ошибкой.
Если ошибок не было, то функция main должна возвратить значение, равное 0:
// Приложение с использованием стандартной
// библиотеки ввода-вывода
#include <stdio.h>
using namespace std;
16
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int main()
{
printf("\tHello,␣World!\n");
return 0;
}
// Приложение с использованием потоковой
// библиотеки ввода-вывода
#include <iostream>
using namespace std;
int main()
{
cout << ’\t’ << "Hello,␣World" << ’\n’;
// можно и так: cout << "\tHello, World!\n";
return 0;
}
Эта программа выводит на экран фразу «Hello, World!». Кроме переменных,
cout и printf воспринимают еще и управляющие символы, такие как \n - символ
перевода строки. Все, что встречается после символов \n, будет показано с начала
следующей строки. В приведенном примере кроме \n присутствует еще и символ
табуляции \t. Он приводит к тому, что фраза «Hello, World!» окажется не в начале
строки, а с некоторым отступом вправо.
Последовательности символов, начинающиеся с обратной косой черты, называют управляющими, или escape-последовательностями. Управляющая последовательность интерпретируется как одиночный символ. Если непосредственно за обратной
косой чертой следует символ, не предусмотренный таблице, результат интерпретации не определен. Если в последовательности цифр встречается недопустимая, она
считается концом цифрового кода.
Управляющие последовательности могут использоваться и в строковых константах, называемых иначе строковыми литералами. Например, если внутри строки
требуется записать кавычку, ее предваряют косой чертой, по которой компилятор
отличает ее от кавычки, ограничивающей строку:
"НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ ТЕХНОЛОГИЧЕСКИЙ УНИВЕРСИТЕТ \"МИСиС\""
Все строковые литералы рассматриваются компилятором как различные объекты. Строковые константы, отделенные в программе только пробельными символами,
при компиляции объединяются в одну. Длинную строковую константу можно разместить на нескольких строках, используя в качестве знака переноса обратную косую
черту, за которой следует перевод строки.
17
1 Лабораторная работа № 1
Таблица 1.2: Управляющие символы
Название
Обозначение
Символ новой строки
\n
Горизонтальная табуляция
\t
Вертикальная табуляция
\v
Возврат на шаг
\b
Возврат каретки
\r
Обратная слэш
\\
Апостроф
\’
Двойная кавычка
\"
Нулевой символ
\0
Звуковой сигнал
\a
Перевод страницы (формата)
\f
Восьмеричный код символа
\0ddd
Шестнадцатеричный код символа
\0xddd
Эти символы игнорируются компилятором, при этом следующая строка воспринимается как продолжение предыдущей.
Управляющие символы (табл. 1.3) заключаются в одинарные кавычки и начинаются с обратной косой черты (обратного слеша). Не будь ее, объект cout и
функция printf воспринял бы запись ’n’ как строчную латинскую букву «n», а ’t’
- как латинскую букву «t» и т.д. Поэтому фразу «Hello, World!» можно вывести
на экран «по буквам».
#include <iostream>
using namespace std;
int main()
{
cout << ’\t’ << ’H’ << ’e’ << ’l’ << ’l’ << ’o’
<< ’\x20’ << ’w’ << ’o’ << ’r’ << ’l’ << ’d’
<< ’!’ << ’\n’;
return 0;
}
Одна из особенностей языка C++ - так называемая перегрузка дает необязательным использование указателей формата. В отличие от функции printf(), которая
требует обязательное указание формата, cout при передаче параметров сам определяет формат на основании типа получаемых данных. Этот процесс называется
перегрузкой. Аналогично оператору потокового вывода cout работает оператор потокового ввода cin: cin » t; где t - переменная, значение которой необходимо ввести
пользователем.
18
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Программа, составленная на языке C++, может содержать одну или больше
функций. Многие функции, которые могут понадобиться, уже написаны, откомпилированы и помещены в библиотеки, так что достаточно просто указать компилятору
использовать одну из стандартных функций. Необходимость написания собственной
функции возникает только в том случае, если подходящей нет в библиотеках.
В программах на C++, использующих консоль, должна присутствовать функция main(), в отличие от приложений Windows, где точкой входа является функция
WinMain(). Круглые скобки являются частью имени функции и ставить их надо
обязательно, так как именно они указывают компилятору, что имеется в виду функция, а не просто английское слово main. В противном случае компиляция не будет
завершена. Фактически каждая функция включает в свое имя круглые скобки, но в
большинстве случаев в них содержится некая информация. В дальнейшем в тексте
книги, ссылаясь на функцию, мы всегда будем ставить после ее имени круглые
скобки.
Следом за main() вводятся инструкции. Инструкции могут быть представлены
в виде стандартных команд и имен функций, содержащихся в библиотеках или
написанных вами самостоятельно. Прямая, или открывающая фигурная скобка ({)
помещается перед первой инструкцией, а обратная, или закрывающая фигурная
скобка (}) следует за последней инструкцией.
Открывающая и закрывающая фигурные скобки называются ограничителями
блока и служат для выделения части кода в единый блок. Каждая функция всегда
должна начинаться и заканчиваться фигурными скобками. Кроме того, отдельные
блоки внутри функции также могут отмечаться при помощи своих собственных пар
фигурных скобок.
При запуске программы компьютер начинает ее выполнение с первой инструкции функции main().
Функции консольного ввода-вывода clrscr(), getchar(), puts().
Поскольку разрабатываемые в данном методическом указании программы
будут совместимы с MS-DOS, то рассмотрим функции консольного ввода-вывода.
Все прототипы этих функций находятся в файле conio.h. Приведем краткое
назначение каждой из этих функций.
Функция clrscr() предназначена для очистки экрана.
Функция getchar() предназначена для ввода одного символа с клавиатуры.
Она ожидает нажатие клавиши и не отображает введенный символ на экран. Часто
эту функцию используют для задержки окна вывода.
Функция puts() выводит на экран текстовую константу, заключенную в кавычки, в конце строки в обязательном порядке печатаются символы ‘\r’ и ‘\n’,
иными словами при использовании данной функции курсор автоматически переводится на новую строку. В отличие от предыдущих двух функций, данная функция
входит в библиотеку стандартного ввода-вывода, и для ее использования необходимо
подключить заголовочный файл stdio.h. Аналогом данной функции в библиотеке
консольного ввода-вывода служит функция cputs().
19
1 Лабораторная работа № 1
Ниже приведен листинг программа на C++, которая выводит на экран монитора
фразу «Hello, World!» с помощью описанных функций.
#include <iostream>
using namespace std;
int main()
{
puts("Hello,␣World!");
}
Кавычки, отмечающие слово внутри круглых скобок в функции puts() не выводятся
на экран. В языке C++ они означают, что на экран следует вывести заключенную в
них последовательность символов, а не константу или переменную.
Точка с запятой в языке C/C++ является разделителем и отмечает конец инструкции. Разделитель показывает компилятору что данная инструкция завершена
и дальше начинается следующая инструкция или заканчивается программа. Точку
с запятой необходимо ставить после каждой отдельной инструкции, так, как это
показано в листинге 1.5. Вышесказанное не означает, что точку с запятой надо
ставить в конце каждой строки. Иногда инструкция занимает больше одной строки и
в этом случае точку с запятой надо ставить только один раз, отмечая конец команды.
C++ является языком свободного формата. Это означает, что для него не имеет
значения, где будут помещены ограничители и начало строки. С таким же успехом
можно написать программу следующим образом:
int main(){puts("Hello,␣World!"); return 0;}
и компилятор обработает ее так же, как и предыдущую. Но для того, чтобы сделать
программу более читабельной, принято следовать определенным правилам:
- помещать функцию main() на отдельной строке;
- помещать фигурные скобки на отдельных строках;
- создавать в тексте программы отступы с помощью табуляции.
Когда ваша программа станет достаточно длинной, вы увидите, что с помощью
отступов можно сделать более понятной структуру программы и выделить логические
единицы.
В то время как наличие или отсутствие пробелов не оказывает влияния на
код, создаваемый компилятором, правильная расстановка всех знаков пунктуации
имеет принципиальное значение. Если вы пропустите скобку, кавычку или точку
с запятой, компилятор немедленно остановит работу и сообщит об ошибке. Такие
ошибки называются синтаксическими, и для того, чтобы компилятор мог создать
исполняемый код программы, вам придется исправить их.
20
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Использование комментариев. По мере того, как программа усложняется,
становится все труднее запоминать, почему вы употребили ту или иную инструкцию,
особенно если вы решили снова посмотреть программу по прошествии некоторого
времени с момента ее создания. Еще сложнее разобраться в программе, написанной
другим человеком.
Добавление комментариев сделает любую программу более легкой для понимания. Комментарий - это сообщение для того, кто читает исходный код программы.
Комментарии могут помещаться в любом месте программы. Компилятор и компоновщик игнорируют комментарии, так что их содержимое не включается в объектный и
исполняемый файлы.
Тому, кто только начинает программировать, введение комментариев не нужной
роскошью, ведь он и так тратит массу времени на создание программы. Действительно, небольшой, простой, легко тестируемой программе, ее из небольшого числа
строк, комментарии не нужны. Однако, читая программу, вы будете благодарны ее
автору за любые пояснения, пусть программа не так уж и сложна.
Комментарий либо начинается с двух символов «прямая косая черта» (//)
и заканчивается символом перехода на новую строку, либо заключается между
символами-скобками /* и */. Внутри комментария можно использовать любые
допустимые на данном компьютере символы, а не только символы из алфавита
языка C++, поскольку компилятор комментарии игнорирует. Вложенные комментариискобки стандартом не допускаются, хотя в некоторых компиляторах разрешены.
/* Эта программа выводит сообщение на экран */
#include <iostream>
using namespace std;
int main()
{
puts("Hello,␣World!");
return 0;
}
Символы /* указывают начало строки комментария, а символы */ отмечают
ее конец. Все, что помещено между ними, C++ игнорирует. Как правило, в начале
текста программы программисты помещают строку комментария чтобы пояснить
цель ее создания. Внутри текста программы помещаются комментарии, поясняющие
употребление отдельных инструкций или последовательность логических шагов.
Такие комментарии обычно помещают после разделителя, то есть точки с запятой:
/* Эта программа выводит сообщение на экран */
#include <iostream>
using namespace std;
int main()
{
/* На экран выводится сообщение "ОК" */
21
1 Лабораторная работа № 1
puts("Hello␣world");
return 0; /* Возврат в операционную систему */
}
При записи инструкции и комментария в одной строке принято (исключительно
для удобства чтения) разделять их некоторым количеством пробелов. Комментарий
может быть многословным и занимать не одну, а несколько строк. В этом случае
символы */, указывающие конец комментария, можно поместить сразу после текста
комментария или на отдельной строке.
Весьма распространенным недосмотром, который часто допускают начинающие программисты, является то, что они забывают ставить символы */ в конце
комментария, и в результате получают сообщение об ошибке компилятора, в C++ использование комментария несколько облегчается за счет введения пары символов //,
указывающих начало строки комментария. В этом с концом комментария считается
конец строки, так что нет необходимости отмечать его специальным символом:
// Эта программа выводит сообщение на экран
#include <iostream>
using namespace std;
int main()
{
puts("Hello␣world!");
return 0; // Возврат в операционную систему
}
Однако если комментарий занимает больше одной строки, каждая строка
должна начинаться с символов //.
1.4 Литералы. Идентификаторы, константы,
переменные, ключевые слова.
Литералом называется любой элемент данных, который вводится непосредственно в инструкции языка C++. Литералом может являться любое число, символ или
строка, которые вводятся как начальные значения переменной. Например: cout=8,
где число 8 являться литералом.
Каждой программе для работы необходима информация - данные. Данные
вводятся в компьютер, он обрабатывает их, следуя нашим инструкциям, затем выдает
результат.
Идентификатор - это имя программного объекта. В идентификаторе могут
использоваться латинские буквы, цифры и знак подчеркивания. Прописные и строчные буквы различаются, например, sysop, SySoP и SYSOP - три различных имени.
22
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Первым символом идентификатора может быть буква или знак подчеркивания, но
не цифра. Пробелы внутри имен не допускаются.
Длина идентификатора по стандарту не ограничена, но некоторые компиляторы
и компоновщики налагают на нее ограничения. Идентификатор создается на этапе
объявления переменной, функции, типа и т.п., после этого его можно использовать
в последующих операторах программы. При выборе идентификатора необходимо
иметь в виду следующее:
- идентификатор не должен совпадать с ключевыми словами именами используемых стандартных объектов языка;
- не рекомендуется начинать идентификаторы с символа подчеркивания, поскольку они могут совпасть с именами системных функций или переменных, и,
кроме того, это снижает мобильность программы;
- на идентификаторы, используемые для определения внешних переменных, налагаются ограничения компоновщика (использование различных компоновщиков
или версий компоновщика накладывает разные требования на имена внешних
переменных).
Константами называют неизменяемые величины. Различаются целые, вещественные, символьные и строковые константы. Компилятор, выделив константу в
качестве лексемы, относит ее к одному из типов по ее внешнему виду. Форматы
констант, соответствующие каждому типу, приведены в таблице 1.3.
Если требуется сформировать отрицательную целую или вещественную константу, то перед константой ставится знак унарной операции изменения знака (−),
например: -218, -022, -0х3C, -4.8, -0.1e4.
Вещественная константа в экспоненциальном формате представляется в виде
мантиссы и порядка. Мантисса записывается слева от знака экспоненты (Е или
е), порядок - справа от знака. Значение константы определяется как произведение
мантиссы и возведенного в указанную в порядке степень числа 10. Обратите внимание,
что пробелы внутри числа не допускаются, а для отделения целой части от дробной
используется не запятая, а точка.
Символьные константы, состоящие из одного символа, занимают в памяти один
байт и имеют стандартный тип char. Двухсимвольные константы занимают два
байта и имеют тип int, при этом первый символ размещается в байте с меньшим
адресом.
Символ обратной косой черты используется для представления:
- кодов, не имеющих графического изображения (например, \a - звуковой сигнал,
\n - перевод курсора в начало следующей строки);
- символов апострофа ( ’ ), обратной косой черты (\), знака вопроса (?) и
кавычки (");
23
1 Лабораторная работа № 1
Таблица 1.3: Форматы констант, соответствующие каждому типу
Константа
Формат
Примеры
Десятичный: последовательность
десятичных цифр, начинающаяся
8, 0, 199226
не с нуля, если это не число нуль
Целая
Восьмеричный: нуль, за которым
следуют восьмеричные цифры
(0,1,2,3,4,5,6,7)
Шестнадцатеричный: 0х или 0Х,
за которым следуют шестнадцатеричные цифры (0,l,2,3,4,5,6,7,8,9,A
,B,C,D,E,F)
Десятичный: [цифры].[цифры]
Вещественная
Строковая
01, 020, 07155
0хА, 0x1B8, 0X00FF
5.7, .001, 35.
Экспоненциальный:
[цифры][.][цифры]Е|е[+-][ЦиФры]
0.2Е6, .lle-3, 5E10
Один или два символа, заключенных в апострофы
Последовательность символов,
заключенная в кавычки
’А’, ’ю’, ’*’, ’db’, ’\0’,
’\n’, ’\012’, ’\x07\x07’
"Здесь был Vasia" ,
"\tЗначение r=\0xF5\n"
- любого символа с помощью его шестнадцатеричного или восьмеричного кода,
например, \073, \0xF5. Числовое значение должно находиться в диапазоне от 0
до 255.
Определить константу - это, значит, сообщить компилятору C ее имя,
тип и значение. Для определения константы в языке C перед функцией main() часто
помещают директиву ]define для объявления констант и макроопределений. Однако
использование этой директивы сопряжено с появлением трудно диагностируемых
ошибок, лучшей альтернативой для констант является использование переменных с
модификатором const. Такие переменные носят название константных переменных и
объявляются следующий образом:
const <Тип> <Имя_Переменной> =
<Значение>;
Наличие инициализатора <Значение> при объявлении константной переменной обязательно. Значение константной переменной не может быть изменено в процессе
работы программы. Например:
const double pi = 3.1415926;
// вещественная кон.
24
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
const int size = 400;
const char new_line = ’\n’;
const char topic[] = "Тема";
// целая константа
// символьная константа
// строковая константа
В любой программе требуется производить вычисления. Для вычисления значений используются выражения, которые состоят из операндов, знаков операций и
скобок. Операнды задают данные для вычислений. Операции задают действия, которые необходимо выполнить. Каждый операнд является, в свою очередь, выражением
или одним из его частных случаев, например, константой или переменной. Операции
выполняются в соответствии с приоритетами. Для изменения порядка выполнения
операций используются круглые скобки. Рассмотрим составные части выражений и
правила их вычисления.
Переменная - это именованная область памяти, в которой хранятся данные
определенного типа. У переменной есть имя и значение. Имя служит для обращения
к области памяти, в которой хранится значение. Во время выполнения программы
значение переменной можно изменять. Перед использованием любая переменная
должна быть описана. Пример описания целой переменной с именем а и вещественной
переменной х:
int a; double x;
Общий вид оператора описания переменных:
[класс памяти] [const|volatile] тип имя[инициализатор];
Каждой используемой в программе константе и переменной должно быть
присвоено имя. Для формирования имен констант, переменных, ключевых слов
используются:
- большие и малые буквы латинского алфавита,
- арабские цифры,
- символ подчеркивания «_».
Компилятор C++ рассматривает одну и ту же большую и малую буквы как
разные символы. Например, переменные dx_1, Dx_1, dX_1, DX_1 имеют различные
имена и являются, соответственно, разными литералами.
Первым символом в имени должна стоять буква или знак подчеркивания.
Например: Num, _min, max_, sum_7, и так далее. Тем не менее, не рекомендуется использовать знак подчеркивания в качестве первого символа литерала, поскольку
таковые в основном используются для системных нужд и определяют литералы
собственно компилятора или операционной системы, например _UNICODE, литерал,
используемый в директивах условной компиляции, если программа написана с использованием кодировки Unicode.
25
1 Лабораторная работа № 1
Определить переменную - это, значит, сообщить ее имя и тип компилятору C++. Объявление переменной можно совместить с ее инициализацией. В этом
случае объявлений переменной записывают следующим способом:
<Тип> <Имя_Переменной> = <Начальное_значение>;
Знак «=» обозначает инструкцию присваивания. Она предназначена для изменения
значения переменных
<имя>=<выражение>;
Разновидность операции присваивания
<имя>=<имя> <знак операции> <выражение>;
Имя переменной не должно совпадать с ключевыми словами языка C++, перечень которых, приведен в табл. 1.4. Ключевые слова - это зарезервированные идентификаторы,
которые имеют специальное значение для компилятора. Их можно использовать
только в том смысле, в котором они определены.
Таблица 1.4: Ключевые слова стандарта языка C++
asm
auto
bool
break
case
catch
char
class
const
const_cast
continue
default
delete
do
double
dynamic_cast
else
enum
explicit
export
extern
false
float
for
friend
goto
if
int
inline
long
mutable
namespace
new
operator
private
protected
public
register
reinterpret_cast
return
short
signed
sizeof
static
static_cast
struct
switch
template
this
throw
true
try
typedef
typeid
typename
union
unsigned
using
virtual
void
volatile
wchar_t
while
Описание переменной, кроме типа и класса памяти, явно или по умолчанию
задает ее область действия. Класс памяти и область действия зависят не только от
собственно описания, но и от места его размещения в тексте программы. Область действия идентификатора - это часть программы, в которой его можно использовать
для доступа к связанной с ним области памяти. В зависимости от области действия
переменная может быть локальной или глобальной. Если переменная определена
внутри блока, она называется локальной, область ее действия - от точки описания
до конца блока, включая все вложенные блоки. Если переменная определена вне
любого блока, она называется глобальной и областью ее действия считается файл, в
26
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
котором она определена, от точки описания до его конца. Класс памяти определяет
время жизни и область видимости программного объекта (в частности, переменной).
Если класс памяти не указан явным образом, он определяется компилятором исходя
из контекста объявления.
Время жизни может быть постоянным (в течение выполнения программы) и
временным (в течение выполнения блока).
Областью видимости идентификатора называется часть текста программы,
из которой допустим обычный доступ к связанной с идентификатором областью
памяти. Чаще всего область видимости совпадает с областью действия. Исключением
является ситуация, когда во вложенном блоке описана переменная с таким же именем.
В этом случае внешняя переменная во вложенном блоке невидима, хотя он и входит
в ее область действия. Тем не менее, к этой переменной, если она глобальная, можно
обратиться, используя операцию доступа к области видимости ::.
Для задания класса памяти используются следующие спецификаторы:
- auto - автоматическая переменная. Память под нее выделяется в стеке и
при необходимости инициализируется каждый раз при выполнении оператора,
содержащего ее определение. Освобождение памяти происходит при выходе из
блока, в котором описана переменная. Время ее жизни - с момента описания до
конца блока. Для глобальных переменных этот спецификатор не используется,
а для локальных он принимается по умолчанию, поэтому задавать его явным
образом большого смысла не имеет.
- extern - означает, что переменная определяется в другом месте программы (в
другом файле или дальше по тексту). Используется для создания переменных,
доступных во всех модулях программы, в которых они объявлены.
- static - статическая переменная. Время жизни - постоянное. Инициализируется один раз при первом выполнении оператора, содержащего определение
переменной. В зависимости от расположения оператора описания статические
переменные могут быть глобальными и локальными. Глобальные статические
переменные видны только в том модуле, в котором они описаны.
- register - аналогично auto, но память выделяется по возможности в регистрах процессора. Если такой возможности у компилятора нет, переменные
обрабатываются как auto.
int a;
int main()
{
int b;
extern int x;
static int c;
a = 1;
// 1 глобальная переменная a
// 2 локальная переменная b
/* 3 переменная х определена в
другом месте */
/* 4 локальная статическая переменная c */
// 5 присваивание глобальной переменной
27
1 Лабораторная работа № 1
int a;
a = 2;
::a = 3;
return 0;
}
int x = 4;
// 6 локальная переменная a
// 7 присваивание локальной переменной
// 8 присваивание глобальной переменной
// 9 определение и инициализация x
В этом примере глобальная переменная a определена вне всех блоков. Память
под нее выделяется в сегменте данных в начале работы программы, областью действия является вся программа. Область видимости - вся программа, кроме строк 6-8,
так как в первой из них определяется локальная переменная с тем же именем, область
действия которой начинается с точки ее описания и заканчивается при выходе из
блока. Переменные b и с - локальные, область их видимости - блок, но время жизни
различно: память под b выделяется в стеке при входе в блок и освобождается при
выходе из него, а переменная с располагается в сегменте данных и существует все
время, пока работает программа.
Если при определении начальное значение переменных явным образом не
задается, компилятор присваивает глобальным и статическим переменным нулевое
значение соответствующего типа. Автоматические переменные не инициализируются.
Имя переменной должно быть уникальным в своей области действия (например,
в одном блоке не может быть двух переменных с одинаковыми именами).
Описание переменной может выполняться в форме объявления или определения. Объявление информирует компилятор о типе переменной и классе памяти,
а определение содержит, кроме этого, указание компилятору выделить память в
соответствии с типом переменной. В C++ большинство объявлений являются одновременно и определениями. В приведенном выше примере только описание 3 является
объявлением, но не определением.
Переменная может быть объявлена многократно, но определена только в одном
месте программы, поскольку объявление просто описывает свойства переменной, а
определение связывает ее с конкретной областью памяти.
Простые типы данных. Прежде чем использовать в программе на языке
C++ какую-либо переменную, ее необходимо описать. Переменные в языке C++ могут
быть описаны как в теле функции (между { }), так и вне ее. При описании переменной внутри функции область ее действия ограничена функцией. При описании
переменной указать тип переменной и ее имя (идентификатор), для того, чтобы C++
должен зарезервировать достаточное количество памяти для хранения введенной
информации. Разные типы данных занимают не одинаковый объем памяти. Не все
функции языка C могут работать с данными любого типа.
В процессе написания программы необходимо определить все данные, которые
будут использоваться, причем сделать это надо и для вводимой информации, и для
результата. Данные классифицируются по типу значений, которые они содержат.
Значение не обязательно означает числовую величину, но и буквы, слова, фразы.
Тип данных определяет:
28
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
- внутреннее представление данных в памяти компьютера;
- множество значений, которые могут принимать величины тина;
- операции и функции, которые можно применять к величинам типа.
Рассмотрим наиболее часто употребляемые простые типы языка C++:
- целые числа со знаком int, long, short,
- целые беззнаковые константы unsigned,
- символы, занимающие один байт char и два байта wchar_t,
- числа с плавающей точкой float, double.
Тип char определяет целочисленные переменные, занимающие один байт, в
диапазоне от -128 до 127. Этот тип, как правило, применяется для символьных
переменных (числовым значением является код символа).
Тип wchar_t определяет целочисленные переменные, занимающие два байта, в
диапазоне от -32768 до 32767. Этот тип, как правило, применяется для символьных
переменных в кодировке Unicode (числовым значением является код символа в
кодировке Unicode).
Тип short определяет целые переменные, занимающие два байта, в диапазоне
от -32768 до 32767. Этот тип используется для небольших целых чисел, в основном
для управления циклами.
Тип long определяет целочисленные переменные, занимающие четыре байта, в
диапазоне от -2147483647 до 2147483646.
В зависимости от компилятора и операционной системы тип int может быть
эквивалентен либо типу short, либо типу long.
Беззнаковые типы unsigned определяяют беззнаковые целые числа. Это ключевое слово используется с другими типами данных для определения этого типа как
беззнакового, т.е. только положительные числа и ноль. К беззнаковым типам относятся unsigned char, unsigned wchar_t, unsigned short, unsigned long, unsigned
int. Тип unsigned эквивалентен типу unsigned int.
Тип float определяет переменные, занимающие четыре байта, для чисел с
плавающей точкой в диапазоне от −1038 до 1038 .
Тип double определяет переменные, занимающие восемь байт, для чисел с
плавающей точкой в диапазоне от −10308 до 10308 . Также используется в научных
расчетах, но может содержать до 15 значащих цифр.
При описании данных достаточно ввести тип, за которым должен следовать
список имен переменных. Например:
int tdw, _sek, g1o; char elen, ogi;
long kid, g2o; char isi =’j’;
float z2_c; unsigned rib = 6;
double pi = 3.14159;
29
1 Лабораторная работа № 1
1.5 Операторы языка С++.
Рассмотрим список всех операций, определенных в языке C++, в соответствии
с их приоритетами (по убыванию приоритетов, операции с разными приоритетами
разделены чертой). В соответствии с количеством операндов, которые используются
в операциях, они делятся на унарные (один операнд), бинарные (два операнда) и
тернарную (три операнда).
Все приведенные в таблице 1.5 операции, кроме условной «? :», sizeof, селектора членов класса «.», доступа к области видимости «::» и оператора последовательного вычисления «,» могут быть перегружены.
Таблица 1.5: Операции, определенные в С++
Операция
::
.
−>
[ ]
( )
++
−−
typeid
dynamic_cast
static_cast
reinterpret_cast
const_cast
sizeof
−−
++
∼
!
−
+
&
*
new
delete
(<тип>)
.*
->*
*|
/
%
+
−
«
»
<
<=
>
>=
==
!=
&
ˆ
|
&&
| |
? :
=
*=
/=
%=
+=
−=
«=
»=
&=
|=
ˆ=
throw
,
Краткое описание
Доступ к области видимости
Селектор членов класса
Селектор членов класса
Индексация
Вызов функции
Постфиксный инкремент
Постфиксный декремент
Идентификация типа
Преобразование типа с проверкой на этапе выполнения
Преобразование типа с проверкой на этапе компиляции
Преобразование типа без проверки
Константное преобразование типа
Размер объекта или типа
Префиксный декремент
Префиксный инкремент
Поразрядное отрицание
Логическое отрицание
Арифметическое отрицание (унарный минус)
Унарный плюс
Взятие адреса
Разадресация
Выделение памяти
Освобождение памяти
Преобразование типа
Селектор членов класса по указателю
Селектор членов класса по указателю
Умножение
Деление
Остаток от деления
Сложение
Вычитание
Сдвиг влево
Сдвиг вправо
Меньше
Меньше или равно
Больше
Больше или равно
Равно
Не равно
Поразрядная конъюнкция (И)
Поразрядное исключающее ИЛИ
Поразрядная дизъюнкция (ИЛИ)
Логическое И
Логическое ИЛИ
Условная операция (тернарная)
Присваивание
Присваивание с умножением
Деление с присваиванием
Остаток от деления с присваиванием
Сложение с присваиванием
Вычитание с присваиванием
Сдвиг влево с присваиванием
Сдвиг вправо с присваиванием
Поразрядное И с присваиванием
Поразрядное ИЛИ с присваиванием
Исключающее ИЛИ с присваиванием
Инициировать исключение
Последовательное вычисление
30
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рассмотрим основные операции подробнее.
Операции увеличения и уменьшения на 1 (++ и −−). Эти операции,
называемые также инкрементом и декрементом, имеют две формы записи - префиксную, когда операция записывается перед операндом, и постфиксную. В префиксной
форме сначала изменяется операнд, а затем его значение становится результирующим значением выражения, а в постфиксной форме значением выражения является
исходное значение операнда, после чего он изменяется.
В отличие от других языков программирования в Си и C++ инструкция присваивания, выполняющая некоторое действие, может быть записана несколькими
способами.
Например, вместо x = x + dx можно записать x += dx, вместо у = у*х можно
записать у *= х а вместо i = i + 1 воспользоваться оператором инкремента и
записать i++. Список наиболее часто используемых таких операций приведен в
таблице 1.6.
Операндом операции инкремента в общем случае является так называемое
L-значение (L-value). Так обозначается любое выражение, адресующее некоторый
участок памяти, в который можно занести значение. Название произошло от операции
присваивания, поскольку именно ее левая (Left) часть определяет, в какую область
памяти будет занесен результат операции. Переменная является частным случаем
L-значения.
Операция определения размера sizeof предназначена для вычисления
размера объекта или типа в байтах, и имеет две формы:
sizeof выражение
sizeof (тип)
Пример:
#include <iostream>
int main()
Таблица 1.6: Арифметические операции с присваиванием в C++
Наименование операции
Выражение Эквивалентное
выражение
Префиксный и постфиксный
++x или x++ x = x + 1
инкремент
Префиксный и постфиксный
–x или x–
x = x - 1
декремент
Сложение с присваиванием
x += y
x = x + y
Вычитание с присваиванием
x -= y
x = x - y
Умножение с присваиванием
x *= y
x = x * y
Деление с присваиванием
x /= y
x = x / y
Взятие остатка с присваиванием x %= y
x = x % y
31
1 Лабораторная работа № 1
{
float x = 1;
cout << "sizeof␣(float)␣:" << sizeof (float);
cout << "\nsizeof␣x␣:" << sizeof x;
cout << "\nsizeof␣(x␣+␣1.0)␣:" << sizeof(x + 1.0);
return 0;}
Результат работы программы:
sizeof (float) : 4
sizeof x : 4
sizeof (x + 1.0) : 8
Последний результат связан с тем, что вещественные константы по умолчанию
имеют тип double, к которому, как к более длинному, приводится тип переменной х
и всего выражения. Скобки необходимы для того чтобы выражение, стоящее в них,
вычислялось раньше операции приведения типа, имеющей больший приоритет, чем
сложение.
Операции отрицания (−, ! и ∼). Арифметическое отрицание (унарный минус −) изменяет знак операнда целого или вещественного типа па противоположный.
Логическое отрицание (!) дает в результате значение 0, если операнд есть истина
(не нуль), и значение 1, если операнд равен нулю. Операнд должен быть целого
или вещественного типа, а может иметь также тип указатель. Поразрядное отрицание (∼), часто называемое побитовым, инвертирует каждый разряд в двоичном
представлении целочисленного операнда.
Деление (/) и остаток от деления (%). Операция деления применима к
операндам арифметического типа. Если оба операнда целочисленные, результат операции округляется до целого числа, в противном случае тип результата определяется
правилами преобразования. Операция остатка от деления применяется только к
целочисленным операндам. Знак результата зависит от реализации.
Операции сдвига (« и ») применяются к целочисленным операндам. Они
сдвигают двоичное представление первого операнда влево или вправо на количество
двоичных разрядов, заданное вторым операндом. При сдвиге влево («) освободившиеся разряды обнуляются. При сдвиге вправо (») освободившиеся биты заполняются
нулями, если первый операнд беззнакового типа, и знаковым разрядом в противном
случае. Операции сдвига не учитывают переполнение и потерю значимости.
Операции отношения (<, <=. >, >=, ==. !=) сравнивают первый операнд
со вторым. Операнды могут быть арифметического типа или указателями. Результатом операции является значение true или false (любое значение, не равное нулю,
интерпретируется как true). Операции сравнения на равенство и неравенство имеют
меньший приоритет, чем остальные операции сравнения.
Поразрядные операции (&, |, ˆ) применяются только к целочисленным операндам и работают с их двоичными представлениями. При выполнении операций
операнды сопоставляются побитово (первый бит первого операнда с первым битом
32
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
второго, второй бит первого операнда со вторым битом второго, и т.д.). При поразрядной конъюнкции, или поразрядном И (операция обозначается &) бит результата
равен 1 только тогда, когда соответствующие биты обоих операндов равны 1.
При поразрядной дизъюнкции, или поразрядном ИЛИ (операция обозначается
|) бит результата равен 1 тогда, когда соответствующий бит хотя бы одного из
операндов равен 1.
При поразрядном исключающем ИЛИ (операция обозначаетсяˆ) бит результата
равен 1 только тогда, когда соответствующий бит только одного из операндов равен
1.
Логические операции (&& и ||). Операнды логических операций И (&&)
и ИЛИ (||) могут иметь арифметический тип или быть указателями, при этом
операнды в каждой операции могут быть различных типов. Преобразования типов
не производятся, каждый операнд оценивается с точки зрения его эквивалентности
нулю операнд, равный нулю, рассматривается как false, не равный нулю - как true).
Результатом логической операции является true или false. Результат операции
логическое И имеет значение true только если оба операнда имеют значение true.
Результат операции логическое ИЛИ имеет значение true, если хотя бы один из
операндов имеет значение true. Логические операции выполняются слева направо.
Если значения первого операнда достаточно, чтобы определить результат операции,
второй операнд не вычисляется.
Операции присваивания (=, +=, -=, *= и т.д.). Операции присваивания
могут использоваться в программе как законченные операторы. Формат операции
простого присваивания (=):
операнд_1 = операнд_2
Первый операнд должен быть L-значением, второй - выражением. Сначала вычисляется выражение, стоящее в правой части операции, а потом его результат
записывается в область памяти, указанную в левой части (мнемоническое правило:
«присваивание - это передача данных "налево"»). То, что ранее хранилось в этой
области памяти, естественно, теряется.
В сложных операциях присваивания ( +=, *=, /= и т.п.) при вычислении
выражения, стоящего в правой части, используется и L-значение из левой части.
Например, при сложении с присваиванием ко второму операнду прибавляется первый,
и результат записывается в первый операнд, то есть выражение, а +=b является
более компактной записью выражения a = a + b.
Условная операция (? :). Эта операция тернарная, то есть имеет три операнда. Ее формат:
операнд_1 ? операнд_2 : операнд_3
Первый операнд может иметь арифметический тип или быть указателем. Он
оценивается с точки зрения его эквивалентности нулю (операнд, равный нулю,
рассматривается как false, не равный пулю - как true). Если результат вычисления
первого операнда равен true, то результатом условной операции будет значение
33
1 Лабораторная работа № 1
второго операнда, иначе - третьего операнда. Вычисляется всегда либо второй
операнд, либо третий. Их тип может различаться. Условная операция является
сокращенной формой условного оператора if.
#include <stdio.h>
int main()
{
int a = 11, b = 4, max;
max = (b > a)? b : a;
printf("Наибольшее число: %d", max);
return 0;
}
Результат работы программы:
Наибольшее число: 11.
1.6 Выражения языка С++.
Выражения состоят из операндов, знаков операций и скобок и используются
для вычисления некоторого значения определенного типа. Каждый операнд является,
в свою очередь, выражением или одним из его частных случаев - константой или
переменной. Примеры выражений:
(а + 0.12) / 6
x && y || !z
(t * sin(x) - 1.05e4)/((2 * k + 2) * (2 * k + 3))
Операции выполняются в соответствии с приоритетами. Для изменения порядка
выполнения операций используются круглые скобки. Если в одном выражении
записано несколько операций одинакового приоритета, унарные операции, условная
операция и операции присваивания выполняются справа налево, остальные - слева
направо. Например, а = b = с означает а = (b = с), а а + b + с означает (а + b)
+ с. Порядок вычисления подвыражений внутри выражений не определен: например,
нельзя считать, что в выражении (sin(x + 2) + cos (у + 1)) обращение к синусу
будет выполнено раньше, чем к косинусу, и что х + 2 будет вычислено раньше, чем
y + 1.
Результат вычисления выражения характеризуется значением и типом. Например, если а и b - переменные целого типа и описаны так:
int а = 2, b = 5;
то выражение а + b имеет значение 7 и тип int, а выражение а = b имеет значение,
равное помещенному в переменную а (в данному случае 5) и тип, совпадающий с
34
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
типом этой переменной. Таким образом, в C++ допустимы выражения вида а = b =
с: сначала вычисляется выражение b = с, а затем его результат становится правым
операндом для операции присваивания переменной а.
В выражение могут входить операнды различных типов. Если операнды имеют
одинаковый тип, то результат операции будет иметь тот же тип. Если операнды
разного типа, перед вычислениями выполняются преобразования типов по определенным правилам, обеспечивающим преобразование более коротких типов в более
длинные для сохранения значимости и точности.
Преобразования бывают двух типов:
- изменяющие внутреннее представление величин (с потерей точности или без
потери точности);
- изменяющие только интерпретацию внутреннего представления.
К первому типу относится, например, преобразование целого числа в вещественное (без потери точности) и наоборот (возможно, с потерей точности), ко второму преобразование знакового целого в беззнаковое.
В любом случае величины типов char, signed char, unsigned char, short
int и unsigned short int преобразуются в тип int, если он может представить все
значения, или в unsigned int в противном случае.
После этого операнды преобразуются к типу наиболее длинного из них, и он
используется как тип результата. Программист может задать преобразования типа
явным образом.
35
1 Лабораторная работа № 1
1.7 Стандартные математические функции.
В языке C для математических вычислений используются стандартные математические функции, декларированные в заголовочном файле math.h (табл. 1.7 и табл.
1.8).
Таблица 1.7: Тригонометрические и гиперболические функции
Тригонометрические
функции (угол
задается в радианах)
sin(x) - синус
Обратные
тригонометрические
функции (возвращают
угол в радианах)
asin(x) - арксинус
cos(x) - косинус
acos(x) - арккосинус
tan(x) - тангенс
atan(x) - арктангенс
Гиперболические
функции
sinh(x) гиперболический
синус
cosh(x) гиперболический
косинус
tanh(x) гиперболический
тангенс
atan2(y, x) - угол в
полярных координатах
точки (x, y) в диапазоне
[−π/2, π/2].
Таблица 1.8: Другие наиболее часто используемые математические функции
Функция
hypot(x,y)
exp(x)
log(x)
log10(x)
pow(x, y)
pow10(p)
sqrt(x)
ceil(x)
floor(x)
fabs(x)
abs(x)
Действие
вычисляет гипотенузу прямоугольного треугольника с
катетами x и y
экспоненциальная функция, ex
натуральный логарифм, ln(x), x > 0
десятичный логарифм, lg(x), x > 0
вычисляет xy . Ошибка области, если x = 0 и y ≤ 0 или
x < 0, y - не целое
Вычисляет 10 p . Результат вычисляется в виде double.
Все аргументы считаются допустимыми, p типа int.
корень квадратный из x, x ≥ 0
находит наименьшее целое типа double, не меньшее x
ceil(6.25) = 6.00, ceil(-6.25) = -6.00
находит наибольшее целое типа double не превышающее
значение x. floor(6.25) = 6.00, floor(-6.25) = -7.00
абсолютное значение (модуль) числа с плавающей точкой
fabs(-6.25) = 6.25
абсолютное значение (модуль) числа целого типа
abs(-6) = 6
36
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Пример:
#include "math.h"
using namespace std;
int main()
{
double x, z, s;
const double a=0.5, b=1.08, c=2.1, m=0.7;
z=(sin(x)/sqrt(1+m*m*sin(x)*sin(x))-c*m*log(m*x));
cout<< "z=";
cout<< z;
s=(exp(-a*x)*sqrt(x+1)+exp(-b*x)*sqrt(x+1.5));
cout<< "\ns=";
cout<< s;
getchar();
return 0;
}
В этом примере вычисляется функция:
sin x
p
− c · m · ln(mx),
1 + m2 · sin2 x
√
√
s = e−ax x + 1 + e−bx x + 1.5
z =
при заданных значениях: x = 1.7; a = 0.5; b = 1.08; c = 2.1; m = 0.7.
1.8 Основы отладки программ.
Ошибки, возникающие в программах, можно разделить на три категории:
ошибки, выявляемые на этапе компиляции, ошибки времени выполнения и логические
ошибки (или ошибки проектирования).
К ошибкам, выявляемым на этапе компиляции, относятся, прежде всего, синтаксические ошибки (нарушения правил языка C++ в исходном тексте программы).
Такие ошибки обычно выявляются самой средой программирования. Встретив синтаксическую ошибку в программе, прервет компиляцию и выдаст сообщение об
ошибке.
К ошибкам времени выполнения программы относятся такие, которые возникают не в результате нарушения синтаксиса C++, а в результате выполнения программой
каких-либо недопустимых действий (например, попытка разделить число на нуль
или записать данные на заполненный диск). Некоторые из этих ошибок отличаются
тем, что возникают лишь при определенных условиях, например при заполнении
диска или обращении в нуль значения какой-либо переменной. Как правило, при
возникновении ошибки времени выполнения программа выдает специальное сообщение об ошибке (и часто ее выполнение на этом завершается), Логические ошибки в
37
1 Лабораторная работа № 1
программе относятся к самому «труднонаходимому» типу ошибок, поскольку их зачастую невозможно выявить формальными методами. При наличии логических ошибок
программа выполняется и компилируется без проблем, но делает не то, что ждет
от нее программист. Обнаружить и устранить логические ошибки (а также ошибки
времени выполнения) в программе можно с помощью тестирования и отладки.
Сообщение об ошибке содержит указание на имя файла и строку, в которой
обнаружена ошибка, код ошибки и ее краткое описание.
Иногда одна синтаксическая ошибка может привести к неправильной интерпретации нескольких строк программы и, как следствие, к сообщениям о синтаксических
ошибках в тех строках, где их в действительности нет. После исправления действительной ошибки и повторной компиляции эти сообщения исчезают.
Кроме сообщений об ошибках выводит предупреждения (warnings). Предупреждения означают, что компилятор обнаружил в программе фрагмент, который может
быть ошибочным. Таким образом, компилятор пытается предотвратить возможные
ошибки времени выполнения, и даже логические ошибки. Структура предупреждающего сообщения аналогична структуре сообщения об ошибках. Справочная система
также предоставляет информацию о предупреждениях компилятора. Само появление
предупреждений еще не означает, что вы обязательно должны что-то изменить в
тексте программы, но необходимо относиться к ним внимательно.
Аппаратура и материалы. Для выполнения лабораторной работы необходим
персональный компьютер c необходимым программным обеспечением - операционная
система MS Windows или GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности. Техника безопасности при выполнении
лабораторной работы совпадает с общепринятой для пользователей персональных
компьютеров, самостоятельно не производить ремонт персонального компьютера,
установку и удаление программного обеспечения; в случае неисправности персонального компьютера сообщить об этом обслуживающему персоналу лаборатории
(оператору, администратору); соблюдать правила техники безопасности при работе с
электрооборудованием; не касаться электрических розеток металлическими предметами; рабочее место пользователя персонального компьютера должно содержаться в
чистоте; не разрешается возле персонального компьютера принимать пищу, напитки.
38
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.9 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем. Номер индивидуального задания студента , где - номер студента в журнале
преподавателя.
Порядок выполнения работы:
1. Оформить программу, указав свою фамилию и инициалы, группу с использованием однострочного и многострочного комментариев.
2. Объявить и проинициализировать переменные основных типов (int, double,
char).
3. По инструкции cin » ввести одно из предложенных значений переменных с
клавиатуры (int, double).
4. Оставшиеся значения определить как константы, используя константные переменные.
5. Вычислить значения выражение по заданным расчетным формулам и наборам
исходных данных.
6. Инструкцией cout « вывести на экран значения исходных данных и результаты
вычислений, сопровождая вывод именами выводимых величин на экран. При переводе на новую строку использовать управляющий символ ’\n’ и манипулятор
endl.
7. Изменить программу таким образом, чтобы значения всех переменных вводились с клавиатуры.
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуального задания и порядка его выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
39
1 Лабораторная работа № 1
Вопросы для защиты работы
1. Назовите преимущества языка C++.
2. Какие окна включает в себя рабочий стол Geany?
3. Каковы этапы создания консольного приложения в среде Geany?
4. Какова структура программы, написанной на языке C++?
5. Какие существуют основные типы данных в языке C++? Дать их назначение.
6. Каково назначение директивы ]include в языке C++?
7. Каково назначение функций printf() и puts() в языке C++?
8. Каково назначение функций clrscr(), getchar() и cputs() в языке C++?
9. Каким образом осуществляется потоковый ввод-вывод в языке C++? Каково
назначение объектов cin и cout?
10. Что такое литерал?
11. Как производится объявление переменной и константной переменной в языке
C++? В чем отличие присваивания от инициализации?
12. Какие спецификаторы используются для задания класса памяти?
13. Какое существует деление операций в соответствии с количеством операндов,
которые в них используются?
14. Из чего состоят выражения?
15. Какие существуют математические функции в языке C++?
16. Каким образом осуществляется диагностирование ошибок компиляции в среде
Geany?
40
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.10 Пример выполнения лабораторной работы №1:
1. Формулировка индивидуального задания:
Вычислить:
sin x
− c · m · ln(mx),
1 + m2 · sin2 x
√
√
s = e−ax x + 1 + e−bx x + 1.5
z =
при x = 1.0; a = 0.5; b = 1.08; c = 2.1; m = 0.7.
2. Листинг программы:
// Лабораторная работа №1
/* Немо А.А. гр. 1-18 */
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
double x, z, s;
//const double a = 0.5, b = 1.08, c = 2.1, m = 0.7;
double a, b, c, m;
cout<<"Введите переменную"<<"\nx=";
cin >> x;
cout<<"Введите переменную"<<"\na=";
cin >> a;
cout<<"Введите переменную"<<"\nb=";
cin >> b;
cout<<"Введите переменную"<<"\nc=";
cin >> c;
cout<<"Введите переменную"<<"\nm=";
cin >> m;
z=(sin(x)/sqrt(1+m*m*sin(x)*sin(x))-c*m*log(m*x));
cout<< "z=";
cout<< z;
s=(exp(-a*x)*sqrt(x+1)+exp(-b*x)*sqrt(x+1.5));
cout<< "\ns=";
cout<< s;
return 0;
}
41
1 Лабораторная работа № 1
3. Результат работы программы:
42
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.10.1 Индивидуальное задание №1.
№
Задание
a
1
№
2 cos x − π
6
=
b
2
0.5 + sin2 y
=
2
1+
3 + z 2 /5
при x = 1.426; y = −1.220;
z = 3.5
3
=
s
5
=
t
=
x+
x2
+
x3
x4
+
,
2!
3!
4!
3
2
x sin x + cos x
4
bt
√
bt,
sin(at + b) −
=
e
s
=
b sin(at cos(2t)) − 1
6
8
2
2
=
p
y
=
cos x − p
3
x2 + b − b sin
2
x
10
a2 + b2
при a = −1.5; b = 15.5; x = −2.9.
11
s
a
3
2
x tan(x + b) + √
,
x+b
=
d
13
s
=
b−
s
xb
y
=
3
+ a)
,
2
3
r
sin (x + a) −
x
3
a
=
q
3
a2 x + e−x cos(bx)
bx − e−x sin(bx) + 1
e
2x
ln(a + x) − b
3x
,
ln(x)
=
√
x + 2 cos(x + π/6)
2.4 − sin2 (x + y)
b
=
1.8 +
a
=
e
e
,
−yz
x
b
−mx
m + x2
|m2 + x2 |
p
2
−cy
b
=
ln(c + c + y) + e
при x = 0.9; m = 1.2; c = 2.4.
z
=
s
=
3
2
2
x sin (x + b) + x/(x + b )
q
2
2 my
(m + y )e
+ 3m + ln(m + 4y)
y
2
=
2 2
x
r
cos (x + a ) +
b2
=
s
=
t
=
ν
=
s
x
x
xy − 5
,
y
(x − y)
sin(x + y) − tan(y − x)z
1 + (x − y)2
при x = 1.82; y = 18.23; z = 3.44.
,
16
3
z
z
=
+ cos(x + b)
3
при x = 0.2; a = 1.1; b = 0.04.
17
3
− x /a
a2
(x+b)3
+e
x2
при x = 0.54; a = 1.1; b = 1.22.
14
2
2
=
z
+ cos (x + b)
a
при a = 0.7; b = 0.05; x = 0.5.
15
=
x2 (x + 1)
sin2 (x
=
a
12
eax − 1
при a = 16.5; b = 3.4; x = 0.61.
z
ax · sin(2x) + b · e−2x
3| sin(bx)|
e
при x = 5.4; y = 1.9; m = 0.2.
bx2 − a
=
=
x+a
x
3
w
q
1 + tan(x + y + z)
при x = 1.426; y = −0.823;
z = 2.724.
y
w
u
f
при a = −0.5; b = 1.7;
t = 0.44.
9
=
при x = 0.3; a = 0.5; b = 2.9
при x = 0.335; y = 0.025.
7
z
при x = 1.4; a = 0.5; b = 3.1
r
y
y
xx − 3
,
x
y − z(y − x)
t
=
(y − x)
1 + (y − x)2
при x = 1.825; y = 18.225;
z = −3.289
s
Задание
m tan(t) + |c sin(t)|,
=
q
√
|bt − a| − e
bt
sin(bt + a),
2
1 + b sin(at cos(3t))
при a = 1.5; b = 15.6; t = 0.9.
18
f
=
q
3
m tan(t) + |c sin(3t)|,
−t
z
=
m cos(bt · sin(t)) + c
при x = 0.2; c = −1; t = 1.2.
z
=
m cos(bt + e ) + c
при b = 0.7; c = −1.8; t = 1.2.
43
1 Лабораторная работа № 1
№
19
21
23
Задание
№
a
,
sin(x/a)
√
bx
− a
d
=
a·e
cos
a
при a = 3.2; b = 17.5; x = −4.8.
y
2
b tan (x) −
=
f
2
=
2
ln(a + x ) + sin
x
b
20
,
√
x+a
−cx x +
z
=
e
p
x − |x − b|
при a = 10.2; b = 9.2; x = 2.2; c = 0.5.
y
=
k
=
a2x + b−x cos(a + b) · x
x+1
e
ax
√
2
cos
,
Задание
y
=
b
2
a · tan (x + a) −
sin2 (a/x)
√
ax
b
d
=
b·e
cos
+ 1.4
b
при a = 3.44; b = 17.52; x = −4.8; z = 5.34.
z
22
=
x2
b
2
2
− ln(a + x )
p
|x − b|
√
x− x+b
при x = 3.23; a = 10.23; b = 9.84; c = 0.5.
f
24
bx
s
=
d
=
=
−cx
e
x+
a
3
2
x tan(x + b) + √
,
x+b
bx2 − a
eax − 1
при a = 0.001; b = 5.8; x = 1.77.
2
при a = 0.3; b = 0.9; x = 0.61.
44
Лабораторная работа № 2
Программирование алгоритмов разветвляющейся
структуры в языке C++.
Условные операторы.
Цель работы и содержание
Приобретение навыков программирования разветвляющихся алгоритмов. Освоить операторов языка C++ if и switch, позволяющего реализовывать разветвляющиеся алгоритмы.
Ход работы
2.1 Унифицированный язык моделирования (UML).
Унифицированный язык моделирования (Unified Modeling Language, UML) является стандартным инструментом для создания «чертежей» программного обеспечения. С помощью UML можно визуализировать, специфицировать, конструировать и
документировать артефакты программных систем. UML пригоден для моделирования
любых систем: от информационных систем масштаба предприятия до распределенных Web-приложений и даже встроенных систем реального времени. Это очень
выразительный язык, позволяющий рассмотреть систему со всех точек зрения, имеющих отношение к ее разработке и последующему развертыванию. Несмотря на обилие
выразительных возможностей, этот язык прост для понимания и использования.
Посмотрим на известной картинке, которая уже более двух десятилетий "живет" в Интернете, но источник ее никому не известен. Эта картинка прекрасно
иллюстрирует типичный процесс создания продукта, или "решения" (поскольку
продукт решает проблему заказчика) (рис. 2.1).
Здесь мы видим все проблемы программной инженерии, в частности проблемы
с коммуникацией и пониманием, вызванные отсутствием четкой спецификации создаваемого продукта. UML определяется как графический язык моделирования
общего назначения (т. е. его можно применять для проектирования чего угодно - от
45
2 Лабораторная работа № 2
Рис. 2.1: Процесс создания продукта, или "решения"
простой качели, как на рисунке, до сложного аппаратно-программного комплекса
или даже космического корабля), предназначенный для спецификации, визуализации, проектирования и документирования всех артефактов, создаваемых в
ходе разработки.
Когда мы говорим о том, что UML - это средство визуализации, мы имеем в виду
модельные спецификации. Все мы знаем, как иногда трудно заставить себя "вникнуть"
в суть материала, излагаемого в очередном учебнике или мануале. Изучение чегото нового идет гораздо проще, если документ содержит не только текст, а еще и
иллюстрации к нему. А если руководство или учебник выглядят как картинки с
подписями, то усвоение нового материала происходит еще проще и эффективнее.
Недаром до сих пор так популярны комиксы, которые также представляют собой
картинки с текстом!
Так вот, такие картинки с подписями наглядны и интуитивно понятны, причем
почти однозначно понимаются любыми заинтересованными лицами, так что могут
использоваться в качестве средства общения между людьми. UML позволяет создавать
такие простые и понятные картинки (модели), описывающие систему с разных
сторон, которые можно показать заказчику и обсудить с ним, т. е. служит средством
коммуникации в команде. Посмотрите на рисунок ниже (рис. 2.2). Все ведь понятно,
правда?
Диаграммы деятельности - это один из пяти видов диаграмм, применяемых
46
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 2.2: Так учатся студенты МИСиС
в UML для моделирования динамических аспектов поведения системы. Диаграмма
деятельности - это, по существу, блок-схема, которая показывает, как поток управления переходит от одной деятельности к другой, однако, по сравнению с последней, у ней есть явные преимущества: поддержка многопоточности и объектноориентированного проектирования.
Диаграмма деятельности (Activity diagram) показывает поток переходов от
одной деятельности к другой. Деятельность (Activity) - это продолжающийся во
времени неатомарный шаг вычислений в автомате. Деятельности в конечном счете
приводят к выполнению некоего действия (Action), составленного из выполняемых
атомарных вычислений, каждое из которых либо изменяет состояние системы, либо возвращает какое-то значение. Действие может заключаться в вызове другой
операции, посылке сигнала, создании или уничтожении объекта либо в простом
вычислении - скажем, значения выражения. Графически диаграмма деятельности
представляется в виде графа, имеющего вершины и ребра.
Состояние действия и состояние деятельности. В потоке управления, моделируемом диаграммой деятельности, происходят различные события. Вы можете
вычислить выражение, в результате чего изменяется значение некоторого атрибута
или возвращается некоторое значение. Также, например, можно выполнить операцию над объектом, послать ему сигнал или даже создать его или уничтожить.
Все эти выполняемые атомарные вычисления называются состояниями действия,
поскольку каждое из них есть состояние системы, представляющее собой выполнение
некоторого действия. Как показано на рис. 2.3, состояния действия изображаются
Рис. 2.3: Состояния действия и комментарий: а) простое действие; б) выражение; в)
комментарий
47
2 Лабораторная работа № 2
прямоугольниками с закругленными краями. Внутри такого символа можно записывать произвольное выражение.
Состояния действия не могут быть подвергнуты декомпозиции. Кроме того,
они атомарны. Это значит, что внутри них могут происходить различные события,
но выполняемая в состоянии действия работа не может быть прервана. Обычно
предполагается, что длительность одного состояния действия занимает неощутимо малое время. В противоположность этому состояния деятельности могут быть
подвергнуты дальнейшей декомпозиции, вследствие чего выполняемую деятельность
можно представить с помощью других диаграмм деятельности.
Состояния деятельности не являются атомарными, то есть могут быть прерваны. Предполагается, что для их завершения требуется заметное время. Можно
считать, что состояние действия - это частный вид состояния деятельности, а конкретнее - такое состояние, которое не может быть подвергнуто дальнейшей декомпозиции.
А состояние деятельности можно представлять себе как составное состояние, поток
управления которого включает только другие состояния деятельности и действий.
Переходы. Когда действие или деятельность в некотором состоянии завершается, поток управления сразу переходит в следующее состояние действия или
деятельности. Для описания этого потока используются переходы, показывающие
путь из одного состояния действия или деятельности в другое. В UML переход представляется простой линией со стрелкой, как показано на рис. 2.4. Поток управления
Рис. 2.4: Нетриггерные переходы
должен где-то начинаться и заканчиваться (разумеется, если это не бесконечный
поток, у которого есть начало, но нет конца). Как показано на рисунке, вы можете задать как начальное состояние (закрашенный кружок), так и конечное (закрашенный
кружок внутри окружности).
Ветвление. Простые последовательные переходы встречаются наиболее часто,
но их одних недостаточно для моделирования любого потока управления. Как и в
блок-схеме, вы можете включить в модель ветвление, которое описывает различные
пути выполнения в зависимости от значения некоторого булевского выражения.
Как видно из рис. 2.5, точка ветвления представляется ромбом. В точку ветвления
48
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 2.5: Ветвление
может входить ровно один пере- ход, а выходить - два или более. Для каждого
исходящего перехода задается булевское выражение, которое вычисляется только
один раз при входе в точку ветвления. Ни для каких двух исходящих переходов эти
сторожевые условия не должны одновременно принимать значение «истина», иначе
поток управления окажется неоднозначным. Но эти условия должны покрывать все
возможные варианты, иначе поток остановится.
Для удобства разрешается использовать ключевое слово else для пометки
того из исходящих переходов, который должен быть выбран в случае, если условия,
заданные для всех остальных переходов, не выполнены. Реализовать итерацию
можно, если ввести два состояния действия - в первом устанавливается значение
счетчика, во втором оно увеличивается - и точку ветвления, вычисление в которой
показывает, следует ли прекратить итерации.
2.2 Алгоритм разветвляющейся структуры.
Алгоритм разветвляющейся структуры - это алгоритм, в котором вычислительный процесс осуществляется либо по одной, либо по другой ветви, в зависимости
от выполнения некоторого условия. Программа разветвляющейся структуры реализует такой алгоритм. В программе разветвляющейся структуры имеется один или
несколько условных операторов. Для программной реализации условия используется
логическое выражение. В сложных структурах с большим числом ветвей применяют
оператор выбора.
Любое выражение, завершающееся точкой с занятой, рассматривается как
оператор, выполнение которого заключается в вычислении выражения. Частным
случаем выражения является пустой оператор ; (он используется, когда по синтаксису
оператор требуется, а по смыслу - нет).
Примеры:
i++;
a *= b + c;
fun(i, k);
// выполняется операция инкремента
// выполняется умножение с присваиванием
// выполняется вызов функции
49
2 Лабораторная работа № 2
Логическое выражение - некоторое утверждение, относительно которого можно
сказать: истинно оно или ложно.
В языке C++ любое выражение, равное нулю считается ложным, тогда как
любое выражение не равное нулю будет истинным.
В C++ используются шесть операторов отношения (табл. 2.1), позволяющих сравнивать между собой значения числовых переменных, а также значение переменной
и константы.
Условия, которые составлены с использованием одного оператора сравнения,
называются простыми условиями.
Общий вид:
<выражение>
<оператор_сравнения>
<выражение>
Из простых условий, которые являются выражениями логического типа можно
строить составные условия. В этом случае простые условия необходимо связывать
при помощи логических операций: !(не), &&(и), ||(или).
Таблица 2.1: Операторы отношения в языке C++
a = b равно
a == b
a > b больше
a > b
a < b меньше
a < b
a ≥ b больше или равно a >= b
a ≤ b меньше или равно a <= b
a 6= b не равно
a != b
Операция логического И (&&).
Логическая операция И используется в том случае, когда требуется одновременность выполнения двух условий, входящих в данную операцию. В языке C++ данное
выражение может быть представлено следующим образом
<условие 1> && <условие 2>
На практике эта логическая операция часто применяется для создания условия, что
некоторая переменная принадлежит указанному промежутку. Например, составим
условие, которое будет истинно тогда и только тогда, когда переменная х принадлежит промежутку от 10 до 20: 10 < x < 20. Это произойдёт только в том случае,
когда х >10 и x < 20. На языке C++ это записывается так:
50
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
x > 10 && x < 20
Операция логического ИЛИ (||).
Логическая операция ИЛИ используется только в том случае, когда требуется
выполнение хотя бы одного из условий, входящих в данную операцию. В языке C++
данное выражение может быть представлено следующим образом:
<условие 1> || <условие 2>
На практике эта логическая операция часто применяется для создания условия, что
некоторая переменная принимает одно из допустимых значений. Например, составим
условие, которое истинно тогда и только тогда, когда x принимает фиксированные
значения: 1,2; 2,3; 3,4. Это произойдет только в том случае, когда x = 1,2 или x =
2,3 или x = 3,4. На языке C++ это запишется как
x == 1.2 || x == 2.3 || x == 3.4
Операция логического НЕ (!)
. Операция НЕ принимает значение истина, если ее операнд принимает значение
ложь, и наоборот, результатом операции НЕ будет ложь, если ее операнд принимает значение истина. В языке C++ данное выражение может быть представлено
следующим образом:
!<условие>
На практике операция НЕ применяется для отрицания какого-либо факта. Все
логические операции (за исключением операции логического НЕ) имеют приоритет
ниже операций отношения, поэтому обрамлять скобками простые условия на основе
операторов отношения не обязательно.
Примеры:
if (a<0) b = 1;
// 1
if (a<b && (a>d||a==0)) b++;
else
{
51
2 Лабораторная работа № 2
b*=a;
a=0;
}
if (a<b)
{
if (a<c)
else m =
}
else
{
if (b<c)
else m =
}
// 2
m = a;
c;
m = b;
c;
if (a++) b++;
if (b>a) max = b;
else max = a;
// 3
// 4
// 5
В примере 1 отсутствует ветвь else. Подобная конструкция называется
«пропуск оператора», поскольку присваивание либо выполняется, либо пропускается
в зависимости от выполнения условия.
Если требуется проверить несколько условий, их объединяют знаками логических операций. Например, выражение в примере 2 будет истинно в том случае, если
выполнится одновременно условие a<b и одно из условий в скобках. Если опустить
внутренние скобки, будет выполнено сначала логическое И, а потом - ИЛИ.
Оператор в примере 3 вычисляет наименьшее значение из трех переменных.
Фигурные скобки в данном случае не обязательны, так как компилятор относит
часть else к ближайшему if.
Пример 4 напоминает о том, что хотя в качестве выражений в операторе if
чаще всего используются операции отношения, это не обязательно.
Конструкции, подобные оператору в примере 5, проще и нагляднее записывать
в виде условной операции (в данном случае: max=(b>a)?b:a;).
2.3 Условный оператор (оператор if).
Условный оператор if используется для разветвления процесса вычислений
на два направления.
Сначала вычисляется выражение, которое может иметь арифметический тип
или тип указателя. Если оно не равно нулю (имеет значение true), выполняется
52
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 2.6: Структурная схема условного оператора
первый оператор, иначе - второй. После этого управление передается на оператор,
следующий за условным.
Одна из ветвей может отсутствовать, логичнее опускать вторую ветвь вместе
с ключевым словом else. Если в какой-либо ветви требуется выполнить несколько
операторов, их необходимо заключить в блок, иначе компилятор не сможет понять,
где заканчивается ветвление. Блок может содержать любые операторы, в том числе
описания и другие условные операторы (но не может состоять из одних описаний).
Необходимо учитывать, что переменная, описанная в блоке, вне блока не существует.
Условный оператор в языке C++ имеет формат:
if(<Условие>) <Оператор>;
его действие можно описать с помощью фрагмента UML-диаграммы дея- тельности,
изображенного на рис. 2.7 а:
Рис. 2.7: Фрагмент диаграммы деятельности UML, описывающей действия оператора
if в языке C++: а) без альтернативной ветви else; б) с альтернативной
ветвью else
Если помимо тех действий, которые требуется выполнить, когда заданное
53
2 Лабораторная работа № 2
условие истинно, требуется исполнить и ряд действий, когда заданное условие ложно,
то применяют следующую форму условного оператора:
if(<Условие>) <Оператор_1>; else <Оператор_2>;
его действие можно описать с помощью фрагмента блок-схемы алгоритма, изображенного на рис. 2.7 б. Иными словами, если «условие» принимает значение «истина»,
то выполняется «оператор1», и если «условие» принимает значение «ложь», то выполняется «оператор2».
Например, для оператора if без альтернативной ветви else можно привести
следующий фрагмент кода:
// Условный оператор
if(temperature >= 0)
cout << "Выше точки замерзания!\n";
// Действия, не относящиеся к условному оператору
cout << "Температура " << temperature << endl;
// тогда как для оператора if-else
// можно привести следующий пример:
// Условный оператор
if(x < ) min = x;
else min = y;
// Действия, не относящиеся к условному оператору
cout << "min␣=␣" << min;
Последний пример может быть прокомментирован следующим образом. Если
условие x < у истинно, то переменной min будет присвоено значение х, если оно
ложно, то min будет присвоено значение у. После выполнения инструкции if-else
будет напечатано значение min.
Оба оператора <Оператор_1> и <Оператор_2> могут представлять простые операторы (один оператор), в этом случае они не заключаются в фигурные скобки. Если
же <Оператор_1> и/или <Оператор_2> представляют составной оператор (несколько
операторов), то их нужно заключить в фигурные скобки.
{
Оператор_1;
Оператор_2;
...
Оператор_n;
}
54
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Каждый оператор внутри скобок должен заканчиваться точкой с запятой.
Структура называется вложенной, если после <условия> или служебного слова
else используются вновь условные операторы. Число вложений может быть произвольным. При этом справедливо следующее правило: служебное слово else всегда
относится к ближайшему выше <условию>.
Пример 2.1
Составить UML-диаграмму деятельности и программу с использованием конструкции ветвления для вычисления значения функции:

 2x2 + cos(x), x ≤ 0;
x + 1,
0 < x < 5;
f =
(2.1)

sin(2x) − x2 , x ≥ 5.
Составим UML-диаграмму деятельности вычисления значения функции (рис.
2.8).
Рис. 2.8: UML-диаграмма деятельности для задачи расчета значения функции
По составленной диаграмме может быть написана программа вычисления
значения функции.
55
2 Лабораторная работа № 2
Листинг 2.1
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
double x, y;
cout << "Введите x: ";
cin >> x;
if (x <= 0)
y=2*x*x + cos(x);
else
if (x<5) y = x + 1;
else y = sin(x) - x*x;
cout << "y(x␣=␣" << x << ")␣=␣" << y << endl;
getchar();
return 0;
}
2.4 Переключатель (оператор switch).
Оператор switch (переключатель) предназначен для разветвления процесса
вычислений на несколько направлений. Переключатель является наиболее удобным
средством для организации мультиветвления. Синтаксис переключателя таков:
switch(Выражение)
{
case Константа_1: Операторы_1;
case Константа_2: Операторы_2;
...
case Константа_n: Операторы_n;
default: Операторы_(n+1);
}
Выполнение оператора начинается с вычисления выражения (оно должно быть
целочисленным), а затем управление передается первому оператору из списка, помеченного константным выражением, значение которого совпало с вычисленным. После
этого, если выход из переключателя явно не указан, последовательно выполняются
56
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 2.9: Структурная схема оператора switch
все остальные ветви. Выход из переключателя обычно выполняется с помощью операторов break или return. Оператор break выполняет выход из самого внутреннего
из объемлющих.
Управляющая конструкция switch передает управление к тому из помеченных
с помощью case операторов, для которого значение константного выражения совпадает со значением переключающего выражения. Переключающее выражение должно
быть целочисленным или его значение приводится к целому. Значения константных
выражений, помещаемых за служебными словами case, приводятся к типу переключающего выражения. В одном переключателе все константные выражения должны
иметь различные значения, но быть одного типа. Любой из операторов, помещенных
в фигурных скобках после конструкции switch(...), может быть помечен одной
или несколькими метками вида
case константное_выражение:
Если значение переключающего выражения не совпадает ни с одним из константных выражений, то выполняется переход к оператору, отмеченному меткой
default:. В каждом переключателе должно быть не больше одной метки default,
однако эта метка может и отсутствовать. В случае отсутствия метки default при
несовпадении переключающего выражения ни с одним из константных выражений,
помещаемых вслед за case, в переключателе не выполняется ни один из операторов.
Сами по себе метки case и default не изменяют последовательности выполнения операторов. Если не предусмотрены переходы или выход из переключателя, то в
нем последовательно выполняются все операторы, начиная с той метки, на которую
передано управление. Фрагмент UML-диаграммы деятельности, соответствующий
переключателю, изображен на рис. 2.10.
57
2 Лабораторная работа № 2
Рис. 2.10: Фрагмент диаграммы деятельности UML, описывающей действие оператора
switch в языке C++
Пример 2.2
Написать программу вывода нечетных чисел, не меньших чем введенное с
клавиатуры число от 0 до 9.
Листинг 2.2
// Названия нечетных целых цифр не меньше заданной
#include <iostream>
using namespace std;
int main()
{
int ic;
cout << "\nВведите любую десятичную цифру: ";
58
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
cin >> ic;
cout << endl;
switch(ic)
{
case 0: case 1: cout << "один, ";
case 2: case 3: cout << "три, ";
case 4: case 5: cout << "пять, ";
case 6: case 7: cout << "семь, ";
case 8: case 9: cout << "девять. ";
// Выход из переключателя
break;
default: cout << "Ошибка! Это не цифра!\n"
// Конец переключателя
}
getchar();
return 0;
}
В результате двух выполнений программы:
Введите любую десятичную цифру: 4 <Enter>
пять, семь, девять.
Введите любую десятичную цифру: z <Enter>
Ошибка! Это не цифра!
Кроме сказанного о возможностях переключателя, приведенная программа
иллюстрирует действие оператора break. С его помощью выполняется выход из
переключателя. Если поместить операторы break после вывода каждой цифры, то
программа будет печатать название только одной нечетной цифры. Несмотря на
то, что в формате переключателя после конструкции switch() приведен составной
оператор, это не обязательно. После switch() может находиться любой оператор,
помеченный с использованием служебного слова case. Однако без фигурных скобок
такой оператор может быть только один, и смысл переключателя теряется: он
превращается в разновидность сокращенного условного оператора. Совместно с
оператором break синтаксис переключателя имеет следующий вид:
switch(Выражение)
{
case Константа_1: Операторы_1; break;
case Константа_2: Операторы_2; break;
...
case Константа_n: Операторы_n; break;
default: Операторы_(n+1);
}
59
2 Лабораторная работа № 2
его действия можно описать с помощью фрагмента блок-схемы алгоритма, изображенного на рис. 2.11.
Рис. 2.11: Фрагмент диаграммы деятельности UML, описывающей действие оператора
switch совместно с оператором break в языке C++
Пример 2.3
Составить блок-схему алгоритма и программу с использованием переключателя
и вывести наименование времени года по вводимому с клавиатуры номеру месяца от
1 до 12. Составим UML-диаграмму деятельности определения наименования времени
года по вводимому с клавиатуры номеру месяца (рис. 2.12).
Рис. 2.12: UML-диаграмма деятельности для задачи вывода названия времени года по
номеру месяца
60
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
На UML-диаграммах деятельности логическое отношение И в условии обозначается с помощью связки and, логическое отношение ИЛИ - с помощью связки or, а
логическое отрицание НЕ - c помощью связки not.
По составленной диаграмме может быть написана программа решения поставленной задачи.
Листинг 2.3
#include <iostream>
using namespace std;
int main()
{
int n;
cout << "Введите номер месяца: ";
cin >> n;
cout<<"\nВремя года: ";
switch(n)
{
case 1:case 2:case 12: cout << "Зима\n";
break;
case 3:case 4:case 5: cout << "Весна\n";
break;
case 6:case 7:case 8: cout << "Лето\n";
break;
case 9:case 10:case 11: cout << "Осень\n";
break;
default: cout << "Ошибка!\n";
}
getchar();
return 0;
}
В приведенном примере программы при вводе номера месяца от 1 до 12 на
экране печатается соответствующее время года, если же номер месяца превышает
12, выводится сообщение о неверном вводе месяца, для чего служит зарезервированное слово языка default. После каждой из констант, перечисляющих значение
переключателя, ставится двоеточие. Заключительный для каждой ветви оператор
break служит для прерывания цикла проверки и перехода в конец переключателя. В
случае отсутствия break переключатель работает неверно - происходит переход на
следующую ветвь.
61
2 Лабораторная работа № 2
Аппаратура и материалы. Для выполнения лабораторной работы необходим
персональный компьютер c необходимым программным обеспечением - операционная
система MS Windows или GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности. Техника безопасности при выполнении
лабораторной работы совпадает с общепринятой для пользователей персональных
компьютеров, самостоятельно не производить ремонт персонального компьютера,
установку и удаление программного обеспечения; в случае неисправности персонального компьютера сообщить об этом обслуживающему персоналу лаборатории
(оператору, администратору); соблюдать правила техники безопасности при работе с
электрооборудованием; не касаться электрических розеток металлическими предметами; рабочее место пользователя персонального компьютера должно содержаться в
чистоте; не разрешается возле персонального компьютера принимать пищу, напитки.
62
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.5 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Для программы из примера 2 составить соответствующую ей диаграмму деятельности UML.
3. Составить UML-диаграмму деятельности и программу с использованием конструкции ветвления и вычислить значение функции. Номер варианта это номер
студента по списку преподавателя.
Индивидуальное задание №1.
Вариант:
1)

x < 1.3
 πx2 − x7√
2,
2
ax − 7 x,
x = 1.3, где a = 1.3
y =
√

lg(x + 7 x), x > 1.3
2)

 1.5 cos2 (x), x < 1
1.8ax,
x = 1, где a = 2.3
y =

(x + 2)+ a, x > 1
3)
y =
ln3 (x2 + 1),
x ≤ −3
1
), x > 3
arctan( 2x+1
4)
 √
x>a
 x x − a,
x sin(xa),
x = a, где a = 2.573
y =
 −ax
e
cos(ax), x < a
63
2 Лабораторная работа № 2
5)
 2
 at ln(t), 1 ≤ t ≤ 2
1,
t < 1, где a = −0.5; b = 2
y =
 at
e + 5t4 , t > 2
6)

sin(3x),
x > 7.7
 ln(x)
√
5
5
y =
x − 1,
x = 7.7

1 + cos3 (2x) − 3 sin2 (3x), x < 7.7
7)
 −1.2x
+ b tan(x), x < 2.8
 e
x+2
,
2.8 ≤ x < 6, где a = 2.574; b = −0.418
y =
 a+b
2x
cos(x) + e ,
x≥6
8)

2

 sin(x ), x ≥ −3
−x
1 + e , −3 < x < 5
y =

 ln(x2 ) ,
x<5
3+4x
9)

 a sin2 (2x + 1), x < −2.5
x sin(ax),
−2.5 ≤ x < 4, где a = 0.361
y =
 x
e ,
x≥4
10)
 √
3
x<1
 5x + 9,
2
x
y =
,
1≤x<4
 6+e−x 3
cos(3x + 8), x ≥ 5
11)

2
 ax + bx + c, x < 1.2
a
,
x = 1.2, где a = 2.8; b = −0.3; c = 4
y =
 x√a+bx
,
x > 1.2
x2 +1
12)
y =





6−x2
3 ,
aex
,
3+4x6
√
3
3
6x −
64
x<2
2 ≤ x = 4, где a = 5
4x, x > 4
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
13)
y =





cos(6x2 +3)
,
4−x4
2−ex
,
8+x2
2x3 +4
,
5x2 −2
x < −2
−2 ≤ x < 3
x≥3
14)

2
 x
√sin (x), x < 1
5
y =
x + 15, x = 1
 x
e ,
x>0
15)

√
x>0
 2x x,
5
cos(x ) + 5, x = 0
y =
 x2
x<0
5 ,
16)

 sin5 (5x2 + 3x + 1), x < −2.5
x sin(4x − 1),
−2.4 ≤ x < 4
y =
 −6x
e
+ 8,
x≥4
17)
√

x>2
 5x + x2 + x,
4
y =
9x − sin(3x − 1), x = 2
 −2x
e
+ cos(2x),
x<2
18)

x<1
 4.8x4 + cos2 (2x2 − 1),
1.8x + ln(4x),
x=1
y =
 5
3
2
2
(x − 2x + 5x ) + sin(3x − 1), x > 1
19)
 √
3
x<1
 5x2 + 9x + 3x,
5 −2x4 −5
x
y =
,
1≥x<4
 2x3 +e−x
3
6x + cos(5x + 8), x ≥ 4
20)
y =





2+cos(x3 +3)
,
4+x2
2−e−2x
,
2x2 +x3
cos(x2 +5x)
,
5x2 −2
65
x < −2
−2 ≤ x < 3
x≥3
2 Лабораторная работа № 2
21)

3√
5
x>0
 3x x − 5x ,
3
5
cos(2x − 1) + 5x , x = 0
y =
 3x2 +sin(x3 −3)
,
x<3
5
22)
 √
x>0
 3 x cos(2x),
sin(2x3 − x) + x2 , x = 0
y =
 tan(x3 −3)
x<0
5 ln x ,
23)
 p
6

5x2 + sin(x) − x + 3x, x < 1

cos(x)−x5 −5
y =
1≤x<4
5 +e−2x−1 ,

 62xtan(2x)
3
+ ln(5x + 8), x ≥ 4
24)
y =





6 sin(x)−x2
,
3
e3x−cos(x)
,
3+4x6
p
5
3
6x − 4 tan(x
x<2
2≤x<4
+ 1), x ≥ 4
25)
y =





sin(6x3 )
,
4x−x4
2 ln(x−1)−ex
,
8x+x2
2x3 +4
tan(2x
5x2 −2
x < −2
−2 ≤ x < 3
+ 1), x ≥ 3
4. Решить задачу согласно варианта, составить UML-диаграмму деятельности и
программу с использованием переключателя. Номер варианта определяется по
номеру студента по списку преподавателя.
Индивидуальное задание №2.
Вариант:
1) Дано натуральное число n > 100. Вывести на экран фразу «Мне n лет»,
учитывая, что при некоторых значениях n слово «лет» надо заменить на
слово «год» или «года».
2) Дано число m (1 ≤ m ≤ 12). Определить, сколько дней в месяце с номером
m.
66
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3) Дано число m (1 ≤ m ≤ 7). Вывести на экран название дня недели,
который соответствует этому номеру.
4) С клавиатуры вводится натуральное число n. В зависимости от значения
остатка r при делении числа n на 7 вывести на экран число n в виде
n = 7 · k + r.
5) С клавиатуры вводится цифра m (от 1 до 4). Вывести на экран названия месяцев, соответствующих времени года с номером m (считать зиму
временем года № 1).
6) Дано целое число С такое, что || < 9. Вывести это число в словесной
форме, учитывая его знак.
7) С клавиатуры вводится цифра m (от 1 до 12). Вывести на экран название
месяца, соответствующего цифре,
8) Дано число m (1 ≤ m ≤ 12). Определить полугодие, на которое приходится
месяц с номером m и количество дней в том месяце (год не високосный).
9) Вводится число экзаменов N ≤ 20. Напечатать фразу "Мы успешно сдали
N экзаменов согласовав слово "экзамен" с числом N .
10) Вводится число карандашей N ≤ 10. Вывести фразу "Я купил N карандашей" , согласовав слово "карандаш" с числом N .
11) Компания по снабжению электроэнергией взимает плату с клиентов по
тарифу:
- 7 р. за 1 кВт/ч за первые 250 кВт/ч;
- 17 р. за кВт/ч, если потребление свыше 250, но не превышает 300
кВт/ч;
- 20 р. за кВт/ч, если потребление свыше 300 кВт/ч.
Потребитель израсходовал n кВт/ч. Подсчитать плату.
12) При покупке товара на сумму от 200 до 500 руб предоставляется скидка
3%, при покупке товара на сумму от 500 до 800 - скидка 5%, при покупке
товара на сумму от 800 до 1000 руб - скидка 7%, свыше 1000 руб - скидка
9%. Покупатель приобрел 8 рулонов обоев по цене 1 и две банки краски
по цене 2. Сколько он заплатил?
13) Студенты убирают урожай помидоров. При сборе до 50 кг в день работа
оплачивается из расчета 30 коп. за 1 кг; при сборе от 50 до 75 кг в день 50 коп. за 1 кг; при сборе от 75 до 90 кг в день - 65 коп. за 1 кг; при сборе
свыше 90 кг в день - 70 коп. за 1 кг плюс 20 руб. премия. Студент собрал
X кг за день. Определить его заработок.
5. Решить задачу согласно варианта, составить UML-диаграмму деятельности и
программу с использованием конструкций ветвления и переключателя. Номер
варианта определяется по номеру студента по списку преподавателя.
67
2 Лабораторная работа № 2
Индивидуальное задание №3.
Вариант:
1) В японском календаре был принят 60-летний цикл, состоящий из пяти
12-летных подциклов. Внутри подцикла года носили названия животных
мыши, коровы, тигра, зайца, дракона, змеи, лошади, овцы, обезьяны, курицы, собаки и свиньи. Попарно года в подцикле обозначались названиями
цвета: зеленый, красный, желтый, белый и черный. По номеру года определить его название по японскому календарю, считая за начало очередного
цикла 1984 год - год зеленой мыши (1985 - год зеленой коровы, 1986 - год
красного тигра, 1987 - год красного зайца и т.д.).
2) Даны действительные числа x и y. Найти U = max2 (x2 y, xy 2 ) + min2 (x −
y, x + 2y).
3) Из трех действительных чисел a, b и c выбрать те, модули которых не
меньше 4.
4) Напечатать три данных действительных числа a, b и c сначала в порядке
их возрастания, затем - в порядке убывания.
5) Определить принадлежит ли точка (a, b) кольцу определяемому окружностями x2 + y 2 = 1 и x2 + y 2 = 0.25.
6) Решить квадратное неравенство ax2 + bx + c > 0 ( 6= 0), где a, b и действительные числа.
7) Провести исследование биквадратного уравнения ax4 + bx2 + c = 0 ( 6= 0),
где a, b и - действительные числа. Если действительных корней нет, то об
этом должно быть выдано сообщение, иначе должны быть выданы 2 или
4 действительных корня.
√
8) Решить неравенство a − x > x − 2 , где - произвольное действительное
числа.
9) Найти координаты точки пересечения прямых заданных уравнениями
a1 x+b1 y+c1 = 0 и a2 x+b2 y+c2 = 0, либо сообщить совпадают, параллельны
или не существуют.
10) Вывести на экран большее из трех заданных чисел.
11) Определить, есть ли среди трех заданных чисел четные.
12) Две окружности заданы координатами центра и радиусами. Сколько точек
пересечения имеют эти окружности?
13) Составить программу, выясняющую делится ли натуральное число нацело
на натуральное число .
14) Составить программу нахождения из трех чисел наибольшего и наименьшего.
15) Составить программу решения квадратного уравнения.
68
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
16) Даны три действительных числа. Составить программу, выбирающую из
них те, которые принадлежат интервалу (0,1).
17) Определить, есть ли среди трех заданных чисел нечетные.
18) Даны произвольные действительные числа a, b и c. Вывести на экран
сообщения: треугольник с данными длинами сторон построить можно
(указать равнобедренный, равносторонний или разносторонний получится
треугольник), либо треугольник с данными длинами сторон построить
нельзя.
19) Какая из точек A(a1 , a2 ) или B(b1 , b2 ) находится дальше от начала координат?
20) Попадет ли точка (a1 , a2 ) в окружность заданного радиуса с центром в
начале координат?
21) Симметричны ли точки M1 (x1 , y1 ) и M2 (x2 , y2 ) относительно начала координат?
22) Треугольник задан координатами своих вершин. Определить принадлежит ли данная точка треугольнику. Координаты вершин треугольника и
координаты точки задать самостоятельно.
23) Симметричны ли точки M1 (x1 , y1 ) и M2 (x2 , y2 ) относительно оси или
относительно оси ?
69
2 Лабораторная работа № 2
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. UML-диаграмму деятельности для программы примера 2 лабораторной работы.
5. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
70
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Вопросы для защиты работы
1. Для чего нужны диаграммы деятельности UML?
2. Что такое состояние действия и состояние деятельности?
3. Какие нотации существуют для обозначения переходов и ветвлений в диаграммах деятельности?
4. Какой алгоритм является алгоритмом разветвляющейся структуры?
5. Чем отличается разветвляющийся алгоритм от линейного?
6. Что такое условный оператор? Какие существуют его формы?
7. Что такое составной оператор? Каков формат его записи?
8. Какие операторы сравнения используются в Си?
9. Что называется простым условием? Приведите примеры.
10. Что такое составное условие? Приведите примеры.
11. Какие логические операторы допускаются при составлении сложных условий?
12. Может ли оператор ветвления содержать внутри себя другие ветвления?
13. Что такое множественный выбор?
14. В каких случаях применяется переключатель?
15. Зачем ставится в переключателе оператор break?
16. Зачем в переключателе употребляется зарезервированное слово default?
17. Какие UML-диаграммы показывают работу переключателя?
71
2 Лабораторная работа № 2
2.6 Пример выполнения лабораторной работы № 2
2.6.1 Индивидуальное задание № 1
1.1. Постановка задачи:
Составить UML-диаграмму деятельности и программу с использованием конструкции ветвления и вычислить значение функции

cos(2x),
x > 3.5
 ln(x)
√
3
3
y =
x − 1,
x = 3.5

1 + sin2 (x) + 2 cos2 (2x), x < 3
1.2. UML-диаграмма:
72
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.3. Листинг программы:
// Лабораторная работа № 2
// Индивидуальное задание № 1
/*
* Немо А.А.
* гр. 1-18
*/
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
double x, y;
cout<<"Введите x"<<"\nx=";
cin>> x;
if (x > 3.5)
y=log(x)*cos(2*x);
else
{
if(x==3.5) y=pow(x*x*x-1,1./3);
else y=(1+sin(x)*sin(x)-2*cos(2*x)*cos(2*x));
}
cout<< "y=";
cout<< y;
getchar();
return 0;
}
73
2 Лабораторная работа № 2
1.4. Результат работы программы:
74
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.6.2 Индивидуальное задание № 2
2.1. Постановка задачи:
Решить задачу, составить UML-диаграмму деятельности и программу с использованием переключателя.
Задача: в понедельник фирма работает с 9-00 до 16-00; во вторник, среду,
четверг, пятницу - с 8-00 до 19-00; в субботу - с 10-00 до 15-00; воскресенье - выходной.
По заданному номеру дня недели определить часы работы.
2.2. UML-диаграмма:
75
2 Лабораторная работа № 2
2.3. Листинг программы:
// Лабораторная работа № 2
// Индивидуальное задание № 2
/*
* Немо А.А.
* гр. 1-18
*/
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
int n;
cout<<"Введите номер дня недели\n"<<"\nn=";
cin>> n;
switch(n)
{
case 1:
cout<<"\nВремя работы фирмы: с 9-00 до 16-00\n";
break;
case 2:case 3:case 4:case 5:
cout<<"\nВремя работы фирмы: с 8-00 до 19-00\n";
break;
case 6:
cout<<"\nВремя работы фирмы: с 10-00 до 15-00\n";
break;
case 7:
cout<<"\nВыходной\n";
break;
default:
cout<<"\nОшибка!\n";
}
getchar();
return 0;
}
76
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.4. Результат работы программы:
77
2 Лабораторная работа № 2
2.6.3 Индивидуальное задание № 3
3.1. Постановка задачи:
Решить задачу, составить UML-диаграмму деятельности и программу с использованием конструкций ветвления и переключателя.
Задача: школьники сдают нормы по прыжкам в длину. Если длина прыжка
больше 2,5 м, то оценка - 5, если от 2 м до 2,5 - оценка 4; от 1,5 м до 2 м - оценка
3; если меньше 1,5 м - 2. Выставить школьнику оценку, если известна длина его
прыжка.
3.2. UML-диаграмма:
78
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3.3. Листинг программы:
// Лабораторная работа № 2
// Индивидуальное задание № 3
/*
* Немо А.А.
* гр. 1-18
*/
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
double dlina;
cout<<"Введите длину прыжка\n"<<"\ndlina=";
cin>> dlina;
if (dlina > 2.5) cout<<"\nОценка: 5\n";
else
if (dlina > 2) cout<<"\nОценка: 4\n";
else
if (dlina > 1.5) cout<<"\nОценка: 3\n";
else
if (dlina > 0) cout<<"\nОценка: 2\n";
else cout<<"\nОшибка! Введите положительное число\n";
getchar();
return 0;
}
79
2 Лабораторная работа № 2
3.4. Результат работы программы:
80
Лабораторная работа № 3
Организация циклических вычислений в языке C++.
Операторы управления программой. Циклы, операторы
перехода.
Цель работы и содержание
Приобретение навыков программирования циклических алгоритмов. Освоить
конструкции языка C++, позволяющего реализовывать циклические алгоритмы.
Ход работы
3.1 Алгоритм циклической структуры
Алгоритм циклической структуры - это алгоритм, в котором происходит многократное повторение одного и того же участка программы. Такие повторяемые
участки вычислительного процесса называются циклами.
Операторы цикла используются для организации многократно повторяющихся
вычислений. Любой цикл состоит из тела цикла, то есть тех операторов, которые
выполняются несколько раз, начальных установок, модификации параметра цикла и
проверки условия продолжения выполнения цикла. Один проход цикла называется
итерацией. Проверка условия выполняется на каждой итерации либо до тела цикла
(тогда говорят о цикле с предусловием), либо после тела цикла (цикл с постусловием). Разница между ними состоит в том, что тело цикла с постусловием всегда
выполняется хотя бы один раз, после чего проверяется, надо ли его выполнять еще
раз. Проверка необходимости выполнения цикла с предусловием делается до тела
цикла, поэтому возможно, что он не выполнится ни разу.
Переменные, изменяющиеся в теле цикла и используемые при провер- ке условия
продолжения, называются параметрами цикла. Целочисленные параметры цикла,
изменяющиеся с постоянным шагом на каждой итерации, называются счетчиками
цикла.
Начальные установки могут явно не присутствовать в программе, их смысл
81
3 Лабораторная работа № 3
состоит в том, чтобы до входа в цикл задать значения переменным, которые в нем
используются.
Программа циклической структуры содержит один или несколько циклов.
Различают детерминированные циклы с заранее известным числом повторений и
итерационные циклы, в которых число повторений заранее неизвестно.
В языке C++ существует 3 вида циклов:
1) цикл с параметром или цикл типа for,
2) цикл с предусловием или цикл типа while,
3) цикл с постусловием или цикл типа do ... while.
Во всех циклах повторяющаяся часть (тело цикла) состоит из одного оператора, если требуется выполнить в цикле несколько операторов, они заключаются в
фигурные скобки, образуя составной оператор. Также во всех типах циклов условие
продолжения цикла заключается в круглые скобки.
Цикл завершается, если условие его продолжения не выполняется. Возможно
принудительное завершение, как текущей итерации, так и цикла в целом. Для этого
служат операторы break, continue, return и goto.
3.2 Цикл с параметром или цикл типа for
Для цикла типа for заголовок цикла состоит из трех разделов: инициализации
(присваивания начальных значений), проверки условия повторения, модификации
(изменения параметров). Разделителем между разделами заголовка цикла типа for
служит точка с запятой.
Рис. 3.1: Структурная схема оператора цикла с параметром (for)
Основная форма цикла for имеет следующий вид:
82
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
for ( <список_инициализации>; <условие_продолжения>; <переход_к_следующей_итерации> )
<тело_цикла>;
Все три параметра цикла for должны быть разделены точкой с запятой. Перед
выполнением цикла выполняются операторы, содержащиеся в первом параметре
<список_инициализации> оператора for, затем осуществляется проверка условия продолжения цикла (обычное условное выражение, определяющее, при каких условиях
цикл будет продолжен) в параметре <условие_продолжения>, если данное условие
принимает истинное значение, то выполняются операторы, образующие тело цикла (<тело_цикла>). Изменение (приращение) обычно используется для изменения
параметра цикла каждый раз при повторении цикла. Если условие продолжения
принимает ложное значение, то работа цикла завершается, и выполняются операторы,
следующие за телом цикла. После выполнения последнего оператора в теле цикла,
выполняются <переход к следующей итерации> операторы третьего параметра цикла for. После чего снова производится проверка условия продолжения и процесс
повторяется.
Инициализация используется для объявления и присвоения начальных значений величинам, используемым в цикле. В этой части можно записать несколько
операторов, разделенных занятой (операцией «последовательное выполнение»). Областью действия переменных, объявленных в части инициализации цикла, является
цикл. Инициализация выполняется один раз в начале исполнения цикла.
Выражение определяет условие выполнения цикла: если его результат, приведенный к типу bool, равен true, цикл выполняется. Цикл с параметром реализован
как цикл с предусловием.
Модификации выполняются после каждой итерации цикла и служат обычно
для изменения параметров цикла. В части модификаций можно записать несколько
операторов через запятую. Простой или составной оператор представляет собой тело
цикла. Любая из частей оператора for может быть опущена (но точки с запятой
надо оставить на своих местах!).
В качестве параметра цикла необязательно использовать целочисленный счетчик. Далее показан фрагмент программы, выводящей на экран все буквы английского
алфавита:
unsigned char ch;
for(ch = ’A’; ch <= ’Z’; ch++) cout << ch;
Следующий фрагмент программы показывает как вывести на печать квадраты
вещественных чисел от -2 до 3 с шагом 0.1:
double x;
for(x = -2.; x <= 3.; x += 0.1)
cout << x*x << endl;
83
3 Лабораторная работа № 3
Рис. 3.2: Фрагмент диаграммы деятельности UML, описывающей действия оператора
for в C++
С помощью цикла типа for удобно находить суммы, произведения, искать
максимальные и минимальные значения и т.п. При нахождении суммы некоторой
переменной, например S присваивается значение 0, затем в цикле к этой переменной
прибавляется соответствующий член заданной последовательности. Далее показано,
как можно найти сумму натуральных чисел от 1 до 10:
S = 0;
for(i = 1; i < 11; i++) S += i;
с использованием операции «запятая» и операции постфиксного инкремента данная
задача может быть решена следующим образом:
for(S = 0, i = 1; i < 11; S += i++);
Цикл for может быть вложенным. В качестве примера рассмотрим программу,
печатающую таблицу умножения целых чисел от 0 до 9:
Листинг 3.1
#include <iostream>
using namespace std;
int main()
84
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
int i, j;
for(i = 0; i < 10; i++)
for(j = 0; j < 10; j++)
cout << i << "*" j << "␣=␣"<< i*j << endl;
getchar();
return 0;
}
Пример 3.1.
Составить UML-диаграмму деятельности и написать программу, позволяющую
вычислить сумму квадратов натуральных чисел от 1 до n, где n вводится с клавиатуры.
S =
n
X
i2 .
(3.1)
i=1
Составим UML-диаграмму деятельности расчета суммы квадратов натуральных
чисел от 1 до n (рис. 3.3).
По составленной диаграмме может быть написана программа вычисления
значения функции.
Листинг 3.2
#include <iostream>
using namespace std;
int main()
{
int n, i, S = 0;
cout << "Введите значение n: ";
cin >> n;
for(i = 1; i <= n; i++) S += i*i;
cout << "Сумма чисел S = " << S << endl;
getchar();
return 0;
}
85
3 Лабораторная работа № 3
Рис. 3.3: UML-диаграмма деятельности для задачи расчета суммы квадратов натуральных чисел от 1 до n
Пример 3.2.
Составить UML-диаграмму деятельности и написать программу, позволяющую
вычислить конечную сумму
S =
n
X
ln(kx)
k=1
k2
,
(3.2)
где n и x вводятся с клавиатуры.
Составим UML-диаграмму деятельности расчета конечной суммы (рис. 3.4).
По составленной диаграмме может быть написана программа вычисле- ния
значения конечной суммы.
Листинг 3.3
#include <iostream>
#include <math.h>
86
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 3.4: UML-диаграмма деятельности для задачи расчета конечной суммы
using namespace std;
int main()
{
double x, S = 0.;
int k, n;
cout << "Введите значение x: ";
cin >> x;
cout << "Введите значение n: ";
cin >> n;
for(k = 1, k <= n; k++)
{
double a = log(k*x)/(k*k);
S += a;
}
cout << "Конечная сумма: " << S << endl;
getchar();
return 0;
}
Очень часто в операторе for используются операторы инкремента (++) - увеличения на единицу и декремента (−−) - уменьшения на единицу. Оба оператора
используются как в префиксной, так и в постфиксной формах. Префиксная операция
инкремента (++i) - увеличение на 1 операнда до его использования, соответственно,
87
3 Лабораторная работа № 3
префиксная операция декремента (−−i) - уменьшение на 1 операнда до его использования. Постфиксная операция инкремента (i++) - увеличение значения операнда
на 1 после его использования, соответственно, постфиксная операция декремента
(i−−) - уменьшение значения операнда на 1 после его использования. Операнд этих
операций не может быть константой либо другим праводопустимым выражением.
Операндом не может быть и произвольное выражение. Операндом унарных операций
++ и −− должны быть всегда леводопустимые выражения, например, переменные.
Пример 3.3.
Составить UML-диаграмму деятельности и написать программу, позволяющую
протабулировать функцию, заданную формулой (3.3), в диапазоне от xmin = −10 до
xmax = 10 в N = 100 равноудаленных точках.

 2x2 + cos(x), x ≤ 0
x + 1,
0<x<5
y =
(3.3)

2
sin(2x) − x , x ≥ 5
Составим UML-диаграмму деятельности расчета значений функции в указанных точках (рис. 3.5).
Рис. 3.5: UML-диаграмма деятельности для задачи табулирования функции
88
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
По составленной диаграмме может быть написана программа табулирования
функции.
Листинг 3.4
#include <iostream>
#include <math.h>
using namespace std;
const double x_min = -10.;
const double x_max = 10.;
const int N = 100;
int main()
{
double y, dx = (x_max - x_min) / N;
for(double x = x_min; x <= x_max; x += dx)
{
if(x <= 0.) y = 2*x*x + cos(x);
else if(x < 5.) y = x + 1;
else y = sin(2*x) - x*x;
cout << "x␣=␣" << x << "␣y␣=␣" << y << endl;
}
getchar();
}
Любой из трех параметров цикла for может быть опущен. Например, найти
сумму натуральных чисел от 1 до 10 можно с помощью следующего фрагмента
программы:
for (S = 0, i = 1; i < 11)
{
S += i;
i++;
}
Если не задано условие завершения в цикле for, то он будет выполняться бесконечно. В этом случае для завершения работы цикла необходимо использовать
оператор break. Для нахождения суммы чисел от 1 до 10 можно использовать также
следующий фрагмент кода:
for (S = 0, i = 1; ; i++)
89
3 Лабораторная работа № 3
{
if(i > 10) break;
S += i;
}
Также может быть пропущен список инициализации в цикле for, в этом случае:
S = 0;
i = 1;
for ( ; i < 11; i++) S += i;
При выполнении итерационного цикла for условие продолжения может изменяться либо при вычислении его значений, либо под действием операторов тела
цикла, либо под действием выражений из списка заголовка. Если условие продолжения не изменяется, либо отсутствует, то цикл бесконечен. Следующие операторы
обеспечивают бесконечное выполнение пустых операторов:
for ( ; ; ) ; \\ бесконечный цикл
for ( ; 1; ) ; \\ бесконечный цикл
3.3 Цикл с предусловием или цикл типа while.
Не всегда число повторений цикла известно заранее, в этих случаях применяются циклы с предусловием (проверка перед циклом) или с постусловием (проверка
после цикла) - это цикл while. Основная его форма
while ( <выражение_условие>) <тело_цикла>
При входе в цикл вычисляется <выражение_условие>. Если его значение отлично от нуля, то выполняется <тело_цикла>. Затем вычисление условия, заданного
в <выражении_условии> и выполнение операторов <тела_цикла> выполняются последовательно, пока <выражение_условие> не станет равным 0. Данная инструкция
может быть выполнена ноль или более раз. Оператором while удобно пользоваться
для просмотра всевозможных последовательностей, если в конце каждой из них
находится заранее известный признак.
Например, сумма натуральных чисел от 1 до 10 с использованием цикла while
может быть найдена следующим образом:
S = 0;
i = 1;
90
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 3.6: Структурная
схема оператора цикла
с предусловием
Рис. 3.7: Фрагмент диаграммы деятельности
UML, описывающей действия оператора while в C++
while (i <= 10)
{
S += i;
i++;
}
Пример 3.4.
Найти значение натурального логарифма ln x от аргумента x > 0, значение
которого вводится с клавиатуры с точностью ε, по его разложению в ряд
ln x = 2
∞
X
n=0
(x − 1)2n+1
.
(2n + 1)(x + 1)2n+1
(3.4)
Чтобы вычислить сумму ряда найдем рекуррентное соотношение, позволяющее
найти следующий член ряда, исходя из значения текущего. Для этого разделим
следующий член ряда на текущий. Текущий член ряда задается выражением
an =
(x − 1)2n+1
.
(2n + 1)(x + 1)2n+1
(3.5)
тогда как следующий член ряда может быть определен следующим образом
an+1 =
(x − 1)2(n+1)+1
(x − 1)2n+3
.
=
(2n + 3)(x + 1)2n+3
(2(n + 1) + 1)(x + 1)2(n+1)+1
91
(3.6)
3 Лабораторная работа № 3
Отношение следующего и текущего членов ряда
an+1
an
(2n + 1) (x − 1)2
,
(2n + 3) (x + 1)2
(3.7)
(2n + 1) (x − 1)2
· an .
(2n + 3) (x + 1)2
(3.8)
=
следовательно
an+1 =
Первый член ряда
a0 =
x−1
(x − 1)2·0+1
=
.
2·0+1
(2 · 0 + 1)(x + 1)
x+1
(3.9)
С учетом выражения (3.9) формула (3.8) может быть записана в следующем виде
an+1 =
(2n + 1) 2
· a · an .
(2n + 3) 0
(3.10)
Условие завершения вычисления суммы ряда может быть записано следующим
образом an ≤ ε. На основании данных рассуждений может быть построена диаграмма
деятельности UML для решения поставленной задачи (рис. 3.8)
По составленной диаграмме может быть написана программа вычисления
значения натурального логарифма по его разложению в бесконечный ряд.
Листинг 3.5
#include <iostream>
#include <math.h>
using namespace std;
// Точность вычислений.
const double eps = 1e-10;
int main()
{
double x, a, a02, S;
int n = 0;
cout << "Введите значение x: ";
cin >> x;
// Первый член ряда.
a = (x - 1)/(x + 1);
92
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 3.8: UML-диаграмма деятельности для расчета значения натурального логарифма
по его разложению в ряд
// Квадрат первого члена ряда.
a02 = a*a;
// Инициализировать сумму членов ряда.
S = a;
while(fabs(a) > eps)
{
a *= ((n + n + 1)*a02)/(n + n + 3);
// 2*n = n + n
n++; S += a;
}
cout << "Натуральный логарифм: " << (S + S)
93
3 Лабораторная работа № 3
<< endl
<< "Точное значение: " << log(x) << endl;
getchar();
return 0;
}
3.4 Цикл с постусловием или цикл типа do ... while
Инструкция do представляет собой вариант инструкции while. Но вместо
проверки условия в начале цикла, в инструкции do она производится в конце. Это
значит, что инструкция, контролируемая условием do, выполняется по крайней
мере один раз, тогда как while может вообще не передать управление своему телу,
если условие изначально не выполняется. Оператор цикла do ... while называется
оператором цикла с постусловием. Основная форма цикла do
do
<тело_цикла>
while ( <выражение_условие> );
Сначала выполняется <тело_цикла>, затем вычисляется условие продолжения
цикла <выражение_условие>. Если оно истинно то управление передается обратно
на начало инструкции do и процесс повторяется. Когда значение условия становится
ложно, управление передается следующей инструкции после тела цикла. Например,
сумма натуральных чисел от 1 до 10 с использованием цикла do ... while может
быть найдена следующим образом:
S = 0;
i = 1;
do
{
S += i;
i++;
}
while ( i < 11 );
Пример 3.6.
Найти значение квадратного корня x = a из положительного числа a, вводимого с клавиатуры, с некоторой заданной точностью ε с помощью рекуррентного
94
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 3.10: Фрагмент диаграммы деятельности
UML, описывающей
действия оператора do ... while в
C++
Рис. 3.9: Структурная схема оператора цикла с постусловием
соотношения
xn+1 =
1
2
a
xn +
.
xn
(3.11)
В качестве начального значения примем x0 = 1. Цикл должен выполняться до
тех пор, пока не будет выполнено условие xn+1 − xn ≤ ε.
Составим диаграмму деятельности UML для задачи отыскания значения квадратного корня по итеративной формуле (3.11), которая соответствует следующей
прорамме.
#include <iostream>
#include <math.h>
using namespace std;
// Точность вычислений.
const double eps = 1e-10;
int main()
{
double x = 1., xp, a;
cout << "Введите значение a: ";
95
3 Лабораторная работа № 3
cin >> a;
if(a <= 0)
{
cout << "Неверное значение a!\n";
return 1;
}
do
{
xp = x;
// Предыдущее значение x.
x = (x + a/x)/2; // Текущее значение x.
} while ( fabs(x - xp) > eps )
cout << "Квадратный корень " << x << endl
<< "Точное значение " << sqrt(a) << endl;
getchar();
return 0;
}
Любой цикл while может быть приведен к эквивалентному ему циклу for и,
наоборот, по следующей схеме:
for (b1; b2; b3) оператор;
b1;
while ( b2 )
{
оператор;
b3;
}
Часто встречающиеся ошибки при программировании циклов - использование
в теле цикла неинициализированных переменных и неверная запись условия выхода
из цикла.
Чтобы избежать ошибок, рекомендуется:
- проверить, всем ли переменным, встречающимся в правой части операторов
присваивания в теле цикла, присвоены до этого начальные значения (а также
возможно ли выполнение других операторов);
- проверить, изменяется ли в цикле хотя бы одна переменная, входящая в условие
выхода из цикла;
96
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
- предусмотреть аварийный выход из цикла по достижению некоторого количества итераций
- если в теле цикла требуется выполнить более одного оператора, нужно заключать их в фигурные скобки.
Операторы цикла взаимозаменяемы, но можно привести некоторые рекомендации по выбору наилучшего в каждом конкретном случае.
Оператор do ... while обычно используют, когда цикл требуется обязательно
выполнить хотя бы раз (например, если в цикле производится ввод данных).
Оператор for предпочтительнее в большинстве остальных случаев (однозначно
- для организации циклов со счетчиками).
Оператором while удобнее пользоваться в случаях, когда число итераций
заранее не известно, очевидных параметров цикла нет или модификацию параметров
удобнее записывать не в конце тела цикла.
В C++ есть четыре оператора, изменяющих естественный порядок выполнения
вычислений:
- оператор безусловного перехода goto;
- оператор выхода из цикла break;
- оператор перехода к следующей итерации цикла continue;
- оператор возврата из функции return.
3.5 Оператор безусловного перехода goto.
Оператор безусловного перехода имеет вид:
goto идентификатор;
где идентификатор - имя метки оператора, расположенного в той же функции, где
используется оператор безусловного перехода.
Передача управления разрешена на любой помеченный оператор в теле функции. Однако существует одно важное ограничение: запрещено «перескакивать» через
описания, содержащие инициализацию объектов. Это ограничение не распространяется на вложенные блоки, которые можно обходить целиком. Следующий фрагмент
иллюстрирует сказанное:
goto B;
float x = 0.0;
// Ошибочный переход, минуя описание
// Инициализация не будет выполнена
goto B;
// Допустимый переход, минуя блок
{
int n = 10; // Внутри блока определена переменная
97
3 Лабораторная работа № 3
x = n*x + x;
}
B: cout << "\tx␣=␣" << x;
Все операторы блока достижимы для перехода к ним из внешних блоков.
Однако при таких переходах необходимо соблюдать то же самое правило: нельзя передавать управление в блок, обходя инициализацию. Следовательно, будет
ошибочным переход к операторам блока, перед которыми помещены описания с
явной или неявной инициализацией. Это же требование обязательного выполнения
инициализации справедливо и при внутренних переходах в блоке.
Принятая в настоящее время дисциплина программирования рекомендует либо
вовсе отказаться от оператора goto, либо свести его применение к минимуму и строго
придерживаться следующих рекомендаций:
- не входить внутрь блока извне;
- не входить внутрь условного оператора, т.е. не передавать управление операторам, размещенным после служебных слов if или else;
- не входить извне внутрь переключателя (switch);
- не передавать управление внутрь цикла.
Следование перечисленным рекомендациям позволяет исключить возможные
нежелательные последствия бессистемного использования оператора безусловного
перехода. Полностью отказываться от оператора goto вряд ли стоит. Есть случаи,
когда этот оператор обеспечивает наиболее простые и понятные решения. Один из
них - это ситуация, когда в рамках текста одной функции необходимо из разных
мест переходить к одному участку программы. Если по каким-либо причинам эту
часть программы нельзя оформить в виде функции (например, это может быть текст
на ассемблере), то наиболее простое решение - применение безусловного перехода с
помощью оператора goto. Такое положение возникает, например, при необходимости
обрабатывать ошибки, выявляемые в процессе выполнения программы.
Второй случай возникает, когда нужно выйти из нескольких вложенных друг в
друга циклов или переключателей. Оператор break прерывания цикла и выхода из
переключателя здесь не поможет, так как он обеспечивает выход только из самого
внутреннего вложенного цикла или переключателя. Например, в задаче поиска в
матрице хотя бы одного элемента с заданным значением для перебора элементов
матрицы обычно используют два вложенных цикла. Как только элемент с заданным
значением будет найден, нужно выйти сразу из двух циклов, что удобно сделать с
помощью goto.
98
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3.6 Оператор break.
Оператор break служит для принудительного выхода из цикла или переключателя. Определение «принудительный» подчеркивает безусловность перехода. Например,
в случае цикла не проверяются и не учитываются условия дальнейшего продолжения
итераций. Оператор break прекращает выполнение оператора цикла или переключателя и осуществляет передачу управления (переход) к следующему за циклом
или переключателем оператору. При этом в отличие от перехода с помощью goto
оператор, к которому выполняется передача управления, не должен быть помечен.
Оператор break нельзя использовать нигде, кроме циклов и переключателей.
Необходимость в использовании оператора break в теле цикла возни- кает, когда
условия продолжения итераций нужно проверять не в начале итерации (циклы for,
while), не в конце итерации (цикл do), а в середине тела цикла. В этом случае тело
цикла может иметь такую структуру:
{
операторы;
if ( условие ) break;
операторы;
}
Циклы и переключатели могут быть многократно вложенными. Однако следует
помнить, что оператор break позволяет выйти только из самого внутреннего цикла
или переключателя.
При многократном вложении циклов и переключателей оператор break не
может вызвать передачу управления из самого внутреннего уровня непосредственно
на самый внешний. Например, при решении задачи поиска в матрице хотя бы одного
элемента с заданным значением удобнее всего пользоваться не оператором break, а
оператором безусловной передачи управления (goto).
Оператор continue. Оператор continue употребляется только в операторах
цикла. С его помощью завершается текущая итерация и начинается проверка условия
дальнейшего продолжения цикла, т.е. условий начала следующей итерации. Для
объяснений действия оператора continue рекомендуется рассматривать следующие
три формы основных операторов цикла:
while ( <условие> )
{
...
if ( <условие_прерывания> ) continue;
...
}
do
{
99
3 Лабораторная работа № 3
...
if ( <условие_прерывания> ) continue;
...
} while ( <условие> );
for ( <список_инициализации>; <условие>; <следующая_итерация> )
{
...
if ( <условие_прерывания> ) continue;
...
}
В каждой из форм многоточием обозначены операторы тела цикла. Вслед за
ними размещен пустой оператор с меткой continue. Если среди операторов тела
цикла есть оператор continue и он выполняется, то его действие эквивалентно
оператору безусловного перехода на метку continue.
Пример 3.7.
Вычислить значение специальной функции (интегральной показательной функции)
Zx
∞
X xk
et
dt = γ + ln x +
t
k · k!
Ei(x) =
(3.12)
k=1
−∞
по ее разложению в ряд с точностью ε = 10−10 , аргумент функции x вводится с
клавиатуры.
Чтобы вычислить сумму ряда найдем рекуррентное соотношение, позволяющее
определить следующий член ряда исходя из значения текущего. Для этого разделим
следующий член ряда на текущий. Текущий член ряда задается выражением
ak =
xk
k · k!
(3.13)
тогда как следующий член ряда может быть определен следующим образом
ak+1 =
xk+1
(k + 1) · (k + 1)!
(3.14)
воспользовавшись свойством факториала (k + 1)! = (k + 1) · k! последнее выражение
может быть записано следующим образом
ak+1 =
xk+1
xk+1
=
(k + 1)(k + 1) · k!
(k + 1)2 · k!
100
(3.15)
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Можно показать, что отношение следующего и текущего членов ряда будет
ak+1
ak
=
xk
(k + 1)2
(3.16)
xk
· ak
(k + 1)2
(3.17)
x
= x.
1 · 1!
(3.18)
Следовательно
ak+1 =
Первый член ряда
a1 =
Условие завершения вычисления суммы ряда может быть записано следующим
образом ak ≤ ε. Программа имеет следующий вид:
Листинг 3.7
#include <iostream>
#include <math.h>
using namespace std;
// Точность вычислений.
const double eps = 1e-10;
// Постоянная Эйлера
const double euler = 0.5772156649;
int main()
{
double x, a, S;
cout << "Введите значение x: ";
cin >> x;
// Начальное значение члена ряда и суммы.
a = x;
S = a;
// Найти сумму членов ряда.
for(int k = 1; fabs(a) > eps; k++)
{
// Найти значение следующего члена ряда.
a *= x*k/((k + 1)*(k+1));
101
3 Лабораторная работа № 3
// Добавить полученный член к сумме.
S += a;
}
// Вывести значение интегральной
// показательной функции.
cout << "Ei(x␣=␣" << x << ")␣=␣" << (euler + log(x) + S) << endl;
getchar();
return 0;
}
3.7 Оператор return.
Оператор возврата из функции return завершает выполнение функции и передает управление в точку ее вызова. Вид оператора:
return [ выражение ];
Выражение должно иметь скалярный тип. Если тип возвращаемого функцией
значения описан как void, выражение должно отсутствовать.
102
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Аппаратура и материалы. Для выполнения лабораторной работы необходим
персональный компьютер c необходимым программным обеспечением - операционная
система MS Windows или GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности. Техника безопасности при выполнении
лабораторной работы совпадает с общепринятой для пользователей персональных
компьютеров, самостоятельно не производить ремонт персонального компьютера,
установку и удаление программного обеспечения; в случае неисправности персонального компьютера сообщить об этом обслуживающему персоналу лаборатории
(оператору, администратору); соблюдать правила техники безопасности при работе с
электрооборудованием; не касаться электрических розеток металлическими предметами; рабочее место пользователя персонального компьютера должно содержаться в
чистоте; не разрешается возле персонального компьютера принимать пищу, напитки.
103
3 Лабораторная работа № 3
3.8 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Составить UML-диаграмму деятельности и программу с использованием конструкций цикла для решения задачи. Номер варианта определяется по номеру
студента по списку преподавателя.
Индивидуальное задание № 1.
Вариант:
1) Найти все трехзначные натуральные числа, сумма цифр которых равна
их произведению.
2) Найти сумму целых положительных чисел, больших 20, меньших 100 и
кратных 3.
3) Начав тренировки, спортсмен пробежал 10 км. Каждый следующий день
он увеличивал дневную норму на 10% от нормы предыдущего дня. Какой
суммарный путь пробежит спортсмен за 7 дней?
4) Через сколько дней спортсмен из задания 1 будет пробегать в день больше
15 км?
5) Одноклеточная амеба каждые три часа делится на 2 клетки. Определить,
сколько будет клеток через 6 часов.
6) Напечатать таблицу соответствия между весом в фунтах и весом в кг для
значений от 1 до а фунтов с шагом 1 фунт, если 1 фунт = 400 г.
7) Определить среди всех двузначных чисел те, которые делятся на сумму
своих цифр.
8) Сумма цифр трехзначного числа кратна 7. Само число также делится на
7. Найти все такие числа.
9) Если к сумме цифр двузначного числа прибавить квадрат этой суммы, то
снова получится это двузначное число. Найти все эти числа.
10) Сколько можно купить быков, коров и телят, платя за быка 10 р., за корову
- 5 р., а за теленка - 0,5 р., если на 100 р. надо купить 100 голов скота?
104
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
11) Составьте программу, которая печатает таблицу умножения натуральных
чисел в десятичной системе счисления.
12) Покупатель должен заплатить в кассу S р. У него имеются 1, 2, 5, 10, 100,
500 р. Сколько купюр разного достоинства отдаст покупатель, если он
начинает платить с самых крупных.
13) Дано натуральное число n. Получить все его натуральные делители.
14) Вычислить сумму всех n-значных чисел (1 ≤ n ≤ 4)
15) Вычислить сумму всех n-значных чисел, кратных k (1 ≤ n ≤ 4)
16) Ученик выучил в первый день 5 английских слов. В каждый следующий день он выучивал на 2 слова больше, чем в предыдущий. Сколько
английских слов выучит ученик в 10-ый день занятий.
17) Составьте программу, которая печатает таблицу сложения натуральных
чисел в десятичной системе счисления.
18) Составить программу, выдающую 1, если заданное число - простое и 0 - в
противном случае. Число называется простым, если он делится толькона
1 и на само себя. Делители числа лежат в интервале от 2 до корня из k,
где k заданное число.
19) У гусей и кроликов вместе 64 лапы. Сколько могло быть кроликов и гусей
(указать все сочетания, которые возможны).
20) Заданы три натуральных числа А, В, С, которые обозначают число, месяц
и год. Найти порядковый номер даты, начиная отсчет с начала года.
21) Ежемесячная стипендия студента составляет А р., а расходы на проживание превышают стипендию и составляют В р. в месяц. Рост цен ежемесячно
увеличивает расходы на 3 %. Составьте программу расчета необходимой
суммы денег, которую надо единовременно просить у родителей, чтобы
можно было прожить учебный год (10 месяцев), используя только эти
деньги и стипендию.
22) Составьте программу, которая по номеру дня в году выводит число и
месяц в общепринятой форме (например, 33-й день года - 2 февраля).
3. Составить UML-диаграмму деятельности и программу для нахождения значения
конечной суммы. Номер варианта определяется по номеру студента по списку
преподавателя.
Индивидуальное задание № 2.
Вариант:
1) Вводится целое число N > 01 .
S =
N
X
k(x + 1) cos( xk )
π
, x=
(k + 1)!
2
k=m
1
При вычислении факториалов необходимо результат накапливать в переменных типа double.
105
3 Лабораторная работа № 3
2) Вводится целое число N > 0.
N
X
sink (x)
S =
k!
k=1
, x=
π
4
, x=
π
4
3) Вводится целое число N > 0.
N
X
cosk (x)
S =
k!
k=2
4) Вводится целое число N > 0.
S =
N
X
(2i − 3) sini (x)
i!
i=1
, x=
5) Вводится целое число N > 0.
S =
N
X
ex − i
, x=1
(i + 1)!
i=2
6) Вводится целое число N > 0.
S =
N
X
ln(xi)
, x=2
(i + 2)!
i=1
7) Вводится целые числа N > 0 и m > 0, N > m.
N
X
S =
k 2 ln(k)
k=m
8) Вводится целое число N > 0.
N
X
xi+5
S =
, x=2
i+7
i=5
9) Вводится целые числа N > 0 и M > 0.
F
=
M! + N!
(M + N )!
10) Вводится целое число N > 0.
N
X
(−1)k+1
S =
k(k + 1)
k=1
106
π
4
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
11) Вводится целое число N > 0.
N
X
S =
i=1
xi
, xi = 1 + i, yi = 1/i
1 + yi
12) Вводится целое число N > 0.
S1 =
S2 =
N
X
i=1
N
X
sini (x), x =
π
4
sin(xi ), x =
π
4
i=1
S = S1 + S2
13) Вводится целое число N > 0.
S =
N
X
k=1
q
π
ak , ak = x2 + sin2 (kx)/4; x =
4
14) Вводится целое число N > 2.
S =
N
X
k 2 ln(1 + k 2 )
i=1
15) Вводится целое число N > 0.
S =
N
X
i=1
i!
(N + 1)!
16) Вводится целое число N > 0.
S =
N
X
7 + i2
i=1
i+5
17) Вводятся целые числа N > 0 и M > 0.
S =
N X
M
X
i=1 k=1
sin
π
4
i+
π 2
k
8
18) Вводится целое число N > 0.
S =
i
N X
X
i=1 j=1
107
sin (0.1i + 0.2j)
3 Лабораторная работа № 3
19) Вводится целое число N > 0.
N X
i
X
S =
(i + k)2
i=1 k=1
4. Составить UML-диаграмму деятельности и написать программу, позволяющую
протабулировать функцию, определенную в соответствии с индивидуальным
заданием № 1 лабораторной работы № 2, в диапазоне от xmin = −15 до xmax = 20
в N = 1000 равноудаленных точках.
5. Составить программу и произвести вычисления значения специальной функции
по ее разложению в ряд с точностью ε = 10−10 , аргумент функции x вводится
с клавиатуры. Номер варианта определяется по номеру студента по списку
преподавателя.
Индивидуальное задание № 3.
Вариант:
1) Интегральный синус
Zx
Si(x) =
∞
X
sin t
(−1)n x2n+1
dt =
;
t
(2n + 1)(2n + 1)!
n=0
0
2) Интегральный косинус
Zx
Ci(x) = γ + ln x +
∞
X (−1)n x2n
cos t − 1
dt = γ + ln x +
;
t
(2n)(2n)!
n=1
0
3) Интегральный гиперболический синус
Zx
Shi(x) =
∞
X
sh t
x2n+1
dt =
;
t
(2n + 1)(2n + 1)!
n=0
0
4) Интегральный гиперболический косинус
Zx
Chi(x) = γ + ln x +
∞
X
x2n
ch t − 1
dt = γ + ln x +
;
t
(2n)(2n)!
n=1
0
5) Первый интеграл Френеля
Zx
C(x) =
2n
∞
π X
(−1)n π2
2
cos
t dt =
x4n+1 ;
2
(2n)!(4n + 1)!
n=0
0
108
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
6) Интеграл вероятности
erf (x) =
2
√
π
Zx
0
∞
2 X (−1)n x2n+1
2
e−t dt = √
;
π n=0 (2n + 1)n!
7) Функция Бесселя первого рода Jn (x), значение n = 0, 1, 2, . . . также должно
вводиться с клавиатуры
Jn (x) =
∞
x n X
2
k=0
2
− x4
k
k!(k + n)!
;
8) Функция Бесселя первого рода In (x), значение n = 0, 1, 2, . . . также должно
вводиться с клавиатуры
In (x) =
∞
x n X
2
k=0
x2
4
k
k!(k + n)!
;
9) Дилогарифм
Zx
f (x) = −
∞
X (−1)k (x − 1)k
ln t
dt =
, 0 ≤ x ≤ 2.
t
k2
k=1
1
Здесь γ = 0, 5772156649 – постоянная Эйлера, π = arccos(−1) = 3, 1415926535 –
отношение длины окружности к ее диаметру.
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
109
3 Лабораторная работа № 3
Вопросы для защиты работы
1. Какой алгоритм является алгоритмом циклической структуры?
2. Типы циклов в языке Си.
3. Назовите основные параметры цикла.
4. Что представляют собой операторы инкремента и декремента, в каких формах
они используются?
5. Как записывается условие продолжения цикла в циклах типа while и do ...
while?
6. Основная форма цикла do ... while.
7. Какие три раздела записываются в круглых скобках для оператора цикла типа
for? Какой разделитель между разделами?
8. Что такое вложенные циклы? Примеры.
9. Как образуется бесконечный цикл и как выйти из него?
10. Схема приведения цикла while к эквивалентному ему циклу for.
11. Назовите операторы, способные изменять естественный порядок выполнения
вычислений.
12. Для чего нужен оператор break?
13. Где употребляется оператор continue и для чего он используется?
14. Для чего используется оператор return?
15. Какой тип должно иметь выражение?
110
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3.9 Пример выполнения лабораторной работы № 3
3.9.1 Индивидуальное задание № 1
1.1. Постановка задачи:
Составить UML-диаграмму деятельности и программу с использованием конструкций цикла для решения задачи.
Задача: в 1985 г. урожай ячменя составил 20 ц с га. В среднем каждые 2 года
за счет применения передовых агротехнических приемов, урожай увеличился на 5%.
Определить, через сколько лет урожай достигнет 25 ц с га.
1.2. UML-диаграмма:
1.3. Листинг программы:
// Лабораторная работа № 3
// Индивидуальное задание № 1
#include <iostream>
#include "math.h"
using namespace std;
111
3 Лабораторная работа № 3
int main()
{
const double productivity = 20, prirost = productivity/100*2.5;
int count = 0;
cout<< "Лабораторная работа № 3\n";
cout<< "\nНемо А.А., 1-18\n";
cout<< "\nВариант № 6\n";
cout<< "\n\nИндивидуальное задание № 1:\n";
cout<< "\nрешить задачу с использованием конструкций цикла.\n";
cout<< "\n\nЗадача:\n";
cout<< "\nв 1985 г. урожай ячменя составил 20 ц с га."
<< "В среднем каждые 2 года за счет\n";
cout<< "\nприменения передовых агротехнических"
<< "приемов урожай увеличился на 5%.\n";
cout<< "\nОпределить, через сколько лет урожай"
<< "достигнет 25 ц с га.\n";
cout<< "\n\nРабота программы:\n";
while((prirost*count + 20) < 25)
{
count++;
}
cout<< "\n\nУрожай достигнет 25 ц с га через: " <<count;
// выбор формы согласования (год, года или лет)
while(count - 10 > 0)
{
count = count - 10;
}
switch (count)
{
case 1: cout<< "␣год"; break;
case 2: case 3: case 4: cout<<"␣года"; break;
default: cout<<"␣лет"; break;
}
getchar();
return 0;
}
112
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.4. Результат работы программы:
113
3 Лабораторная работа № 3
3.9.2 Индивидуальное задание № 2
2.1. Постановка задачи:
Составить UML-диаграмму деятельности и программу для нахождения значения
конечной суммы:
x
x
N
X
e i + e− i
S =
, x = 1.
(i + 2)4
i=1
Вводится целое число N > 0.
2.2. UML-диаграмма:
114
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.3. Листинг программы:
// Лабораторная работа № 3
// Индивидуальное задание № 2
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
double a, S=0;
const double x=1;
int i, N;
cout<<"Лабораторная работа № 3\n";
cout<<"\nНемо А.А., 1-18\n";
cout<<"\nВариант № 6\n";
cout<<"\n\nИндивидуальное задание № 2:\n";
cout<<"\nнайти значение конечной суммы S при x=1.\n";
cout<<"\n\nРабота программы:\n";
cout<<"\nВведите значение N>0\n"<<"\nN=";
cin>> N;
for(i=1; i<=N; i++)
{
a=(exp(x/i)+exp(-x/i))/(i+2)*(i+2)*(i+2)*(i+2);
S+=a;
}
cout<<"\nКонечная сумма:\n"<<"\nS=";
cout<< S;
return 0;
}
115
3 Лабораторная работа № 3
2.4. Результат работы программы:
116
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3.9.3 Индивидуальное задание № 3
3.1. Постановка задачи:
Составить UML-диаграмму деятельности и написать программу, позволяющую
протабулировать функцию

cos(2x),
x > 3.5
 ln(x)
√
3
y =
x3 − 1,
x = 3.5

1 + sin2 (x) − 2 cos2 (2x), x < 3.5
диапазоне от xmin = −15 до xmax = 20 в N = 1000 равноудаленных точках.
3.2. UML-диаграмма:
117
3 Лабораторная работа № 3
3.3. Листинг программы:
// Лабораторная работа № 3
// Табуляция функции
#include <iostream>
#include "math.h"
using namespace std;
const double x_min=-15;
const double x_max=20;
const int N=1000;
int main()
{
double y, dx=(x_max-x_min / N);
cout<<"Лабораторная работа № 3\n";
cout<<"\nНемо А.А., 1-18\n";
cout<<"\nВариант № 6\n";
cout<<"\n\nЗадание:\n";
cout<<"\nпротабулировать функцию y"
<<"в диапазоне от x_min=-15 до x_max=20\n";
cout<<"\n␣N=1000␣␣.\n";
cout<<"\n\n␣:\n";
for (double x=x_min; x<=x_max; x+=dx)
{
if (x>3.5) y=(log(x)*cos(2*x));
else
if (x==3.5) y=(pow(x*x*x-1,1./3));
else y=(1+sin(x)*sin(x)- 2*cos(2*x)*cos(2*x));
cout<<
cout<<
cout<<
cout<<
"\nx=";
x;
"\ny=";
y;
}
return 0;
}
118
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3.4. Результат работы программы:
119
3 Лабораторная работа № 3
3.9.4 Индивидуальное задание № 4
4.1. Постановка задачи:
Составить программу и произвести вычисление значения специальной функции
второго интеграла Френеля:
Zx
S(x) =
0
2n+1
∞
π X
(−1)n π2
2
sin
t dt =
x4n+3
2
(2n + 1)!(4n + 3)
n=0
по ее разложению в ряд с точностью ε = 10−10 , аргумент функции x вводится с
клавиатуры.
4.2. UML-диаграмма:
120
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
4.3. Листинг программы:
// Лабораторная работа № 3
// Индивидуальное задание № 4
#include <iostream>
#include "math.h"
using namespace std;
double fact(double
{
int k, i;
i = 1; k = 1;
if (number == 0
return 1;
else
while (i <=
{
k *=
i++;
}
number)
|| number == 1)
number)
i;
return k;
}
int main()
{
double x, S, eps = 1e-10, a, n, pi;
pi = acos(-1.);
cout <<"Лабораторная работа № 3\n";
cout <<"\nНемо А.А., 1-18\n";
cout <<"\nВариант № 6\n";
cout <<"\n\nИндивидуальное задание № 4:\n";
cout <<"\nвычислить значение специальной"
<<"функции второго интеграла Френеля\n";
cout <<"\nпо ее разложению в ряд с точностью eps=10^-10.\n";
cout <<"\n\nРабота программы:\n";
cout <<"\nВведите значение x\n"<< "\nx␣=␣";
cin >> x;
a = 0;
S = x;
for(n = 1; fabs(a) < eps; n++)
{
a=pow(-1,n)*pow((pi/2),2*n+1)*pow(x,(4*n+3))/(fact(2*n+1)*(4*n+3));
S += a ;
}
cout << "\nS(" << x <<")␣=␣" << S << endl;
return 0;
}
121
3 Лабораторная работа № 3
4.4. Результат работы программы:
122
Лабораторная работа № 4
Одномерные массивы в языке C++
Цель работы и содержание
Закрепление знаний об одномерных массивах, составление программ с одномерными массивами.
Ход работы
4.1 Основные сведения о массивах в языке C++.
В случае использования простых переменных каждой области памяти для
хранения одной величины соответствует свое имя. Если требуется работать с группой
величин одного типа, их располагают в памяти последовательно и дают им общее имя,
а различают по порядковому номеру. Такая последовательность однотипных величин
называется массивом. Массивы, как и любые другие объекты, можно размещать либо
с помощью операторов описания в сегментах данных или стека, либо в динамической
области памяти с помощью операций выделения памяти.
При описании массива после имени в квадратных скобках задается количество
его элементов (размерность), например int a[10]. Массив располагается в зависимости от места его описания либо в сегменте данных, либо в сегменте стека, и все
инструкции по выделению памяти формирует компилятор до выполнения программы.
Вследствие этого размерность массива может быть задана только константой или
константным выражением. Если при описании массива не указана размерность, должен присутствовать инициализатор, в этом случае компилятор выделит намять по
количеству инициализирующих значений. В дальнейшем мы увидим, что размерность
может быть опущена также в списке формальных параметров.
Элементы массива нумеруются с нуля. При описании массива используются
те же модификаторы (класс памяти, const и инициализатор), что и для простых
переменных. При описании массив можно инициализировать, то есть присвоить его
элементам начальные значения, например:
123
4 Лабораторная работа № 4
int a[10] = {1, 1, 2, 2, 5, 100};
Для данного массива элементы имеют номера от 0 до 9. Номер элемента указывается после его имени в квадратных скобках, например, а[0], а[3]. Инициализирующие значения для массивов записываются в фигурных скобках. Значения элементам
присваиваются по порядку. Если элементов в массиве больше, чем инициализаторов,
элементы, для которых значения не указаны, обнуляются:
int b[5] = {3. 2, 1};
//b[0]=3,b[l]=2,b[2]=l,b[3]=0,b[4]=0
Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма
элементов массива.
#include <iostream>
using namespace std;
int main()
{
const int n = 10; int i, sum;
int marks[n] = {3. 4, 5, 4, 4};
for (i =0. sum = 0; i<n; i++) sum += marks[i];
cout << "Сумма элементов: " << sum;
return 0;
}
Размерность массивов предпочтительнее задавать с помощью именованных констант, как это сделано в примере, поскольку при таком подходе для ее изменения
достаточно скорректировать значение константы всего лишь в одном месте программы. Обратите внимание, что последний элемент массива имеет номер, на единицу
меньший заданной при его описании размерности.
При обращении к элементам массива автоматический контроль выхода индекса
за границу массива не производится, что может привести к ошибкам. Идентификатор
массива является константным указателем на его нулевой элемент. Например, для
массива из предыдущего листинга имя mark - это то же самое, что & mark[0], а к
i-му элементу массива можно обратиться, используя выражение *(mark+i). Можно
описать указатель, присвоить ему адрес начала массива и работать с массивом через
указатель. Следующий фрагмент программы копирует все элементы массива а в
массив b:
int a[100], b[100];
124
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int *pa = a;
int *pb = b;
for ( int i = 0; i<100; i++ )
*pb++ = *pa++;
// или int *a = &a[0];
// или pb[i] = pa[i];
Пример 4.1.
Написать программу, которая для целочисленного массива из 100 элементов
определяет, сколько положительных элементов располагается между его максимальным и минимальным элементами.
Запишем алгоритм в самом общем виде.
1. Определить, где в массиве расположены его максимальный и минимальный
элементы, то есть найти их индексы.
2. Просмотреть все элементы, расположенные между ними. Если элемент массива
больше нуля, увеличить счетчик элементов на единицу.
Ясно, что порядок расположения элементов в массиве заранее не известен, и
сначала может следовать как максимальный, так и минимальный элемент, кроме
того, они могут и совпадать. Поэтому прежде чем просматривать массив в поисках
количества положительных элементов, требуется определить, какой из этих индексов
больше. Запишем уточненный алгоритм:
1. Определить, где в массиве расположены его максимальный и минимальный
элементы:
- задать начальные значения для индексов максимального и минимального
элементов (например, равные нулю, но можно использовать любые другие
значения индекса, не выходящие за границу массива).
- просмотреть массив, поочередно сравнивая каждый его элемент с ранее
найденными максимумом и минимумом. Если очередной элемент больше
ранее найденного максимума, принять этот элемент за новый максимум (т.е.
запомнить его индекс). Если очередной элемент меньше ранее найденного
минимума, принять этот элемент за новый минимум.
2. Определить границы просмотра массива для поиска положительных элементов,
находящихся между его максимальным и минимальным элементами:
- если максимум расположен в массиве раньше, чем минимум, принять
левую границу просмотра равной индексу максимума, иначе - индексу
минимума.
125
4 Лабораторная работа № 4
- если максимум расположен в массиве раньше, чем минимум, принять
правую границу просмотра равной индексу минимума, иначе - индексу
максимума.
3. Обнулить счетчик положительных элементов. Просмотреть массив в указанном
диапазоне. Если очередной элемент больше нуля, увеличить счетчик на единицу.
Для экономии времени при отладке значения элементов массива задаются
путем инициализации.
Листинг 4.1
#include <iostream>
using namespace std;
int main()
{
const int n = 10;
int a[n] = {1, 3, -5, 1, -2, 1, -1, 3, 8, 4};
int i, imax, imin, count;
for(i = imax = imin = 0; i < n; i++)
{
if(a[i] > a[imax]) imax = i;
if(a[i] < a[imin]) imin = i;
}
cout << "\n\t␣max␣=␣" << a[imax]
<< "\t␣min␣=␣"
<< a[imin] << endl;
int ibeg = (imax < imin) ? imax : imin;
int iend = (imax < imin) ? imin : imax;
cout << "\n\t␣ibeg␣=␣" << ibeg
<< "\t␣iend␣=␣"
<< iend << endl;
for(count = 0, i = ibeg + 1; i < iend; i++)
if(a[i] > 0) count++;
cout <<"Количество положительных "<< count <<endl;
return 0;
}
126
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
В программе использована управляющая последовательность \t, которая задает
отступ при выводе на следующую позицию табуляции. Массив просматривается,
начиная с элемента, следующего за максимальным (минимальным), до элемента,
предшествующего минимальному (максимальному). Индексы границ просмотра
хранятся в переменных ibeg и iend. В приведенной выше программе для определения
их значений используется тернарная условная операция. Можно поступить и подругому: просматривать массив всегда от максимума к минимуму, а индекс при
просмотре увеличивать или уменьшать в зависимости от их взаимного расположения.
В приведенной ниже программе направление просмотра, то есть приращение
индекса, хранится в переменной d. Если массив просматривается «слева направо»,
она равна 1, иначе - −1. Обратите внимание и на изменившееся условие продолжения
этого цикла.
Листинг 4.2
#include <iostream>
using namespace std;
int main()
{
const int n = 10;
int a[n] = {1, 3, -5, 1, -2, 1, -1, 3, 8, 4};
int i, imax, imin, count;
for(i = imax = imin = 0; i < n; i++)
{
if(a[i] > a[imax]) imax = i;
if(a[i] < a[imin]) imin = i;
}
cout << "\n\t␣max␣=␣" << a[imax]
<< "\t␣min␣=␣"
<< a[imin] << endl;
int d = 0;
if(imax < imin) d = 1;
else if(imax > imin) d = -1;
for(count = 0, i = imax + d; i != imin; i += d)
if(a[i] > 0) count++;
127
4 Лабораторная работа № 4
cout << "Количество положительных " << count
<< endl;
return 0;
}
Ввод массива в этом варианте программы осуществляется с клавиатуры. Напоминаем, что в этом случае желательно для проверки вывести введенные значения на
печать.
Тестовых примеров для этой задачи должно быть по крайней мере три для
случаев, когда:
1) a[imin] расположен левее a[imax];
2) a[imin] расположен правее a[imax];
3) a[imin] и a[imax] совпадают.
Последняя ситуация имеет место, когда в массиве все элементы имеют одно и
то же значение. Кстати, во втором варианте программы третий случай корректно
обрабатывается благодаря значению d = 0 для этой ситуации. Желательно также
проверить, как работает программа, если a[imin] и a[imax] расположены рядом, а
также в начале и в конце массива (граничные случаи). Элементы массива нужно
задавать как положительные, так и отрицательные.
Разница между приведенными способами решения несущественна, но первый
вариант более «прозрачен», поэтому он, на наш взгляд, предпочтительнее.
Измените приведенную выше программу так, чтобы она вычисляла произведение отрицательных элементов, расположенных между минимальным и максимальным
по модулю элементами массива.
Часто бывает, что точное количество элементов в исходном массиве не задано,
но известно, что оно не может превышать некое конкретное значение. В этом случае
память под массив выделяется «по максимуму», а затем заполняется только часть
этой памяти. Память можно выделить либо с помощью оператора описания в стеке
или сегменте данных, либо в динамической области. Фактическое количество введенных элементов запоминается в переменной, которая затем участвует в организации
циклов по массиву, задавая его верхнюю границу. Этот подход является весьма
распространенным, поэтому мы приводим ниже небольшую, но полезную программу,
в которой выполняется только считывание элементов массива с клавиатуры и их
вывод на экран:
const int n = 1000;
int a[n];
int i, kol_a;
cout << "Введите количество элементов: ";
128
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
cin >> kol_a;
if(kol_a > n)
{
cout << "Превышение размера массива" << endl;
return 1;
}
for(int i = 0; i < kol_a; i++) cin >> a[i];
for(int i = 0; i < kol_a; i++) cout << a[i] << "␣";
cout << endl;
Несмотря на то, что значение константы n определяется «с запасом», надо
обязательно проверять, не запрашивается ли большее количество элементов, чем
возможно. Привычка к проверке подобных, казалось бы, маловероятных случаев
позволит вам создавать более надежные программы, а нет ничего более важного для
программы, чем надежность.
Пример 4.2.
Выполнить сортировку массива по возрастанию, состоящего из n элементов
методом «пузырька». На языке C++ пузырьковая сортировка массива может быть
запрограммирована следующим образом:
Листинг 4.3
#include <iostream>
using namespace std;
int main()
{
const int N = 1000;
int a[N];
int n, i, j, temp;
cout << "Введите количество элементов: ";
cin >> n;
if(n <= 0 || n > N)
{
cout << "Неверный размер массива" << endl;
return 1;
}
129
4 Лабораторная работа № 4
for(i = 0; i < n; i++) cin >> a[i];
for(i = 0; i < n; i++) cout << a[i];
cout << endl;
// Сортировка
for(i = 0; i < n; i++)
for(j = i + 1; j < n; j++)
if(a[i] > a[j])
{
temp = a[i]; a[i] = a[j];
a[j] = temp;
}
cout << "После сортировки: ";
for(i = 0; i < n; i++) cout << a[i] << "␣";
cout << endl;
return 0;
}
130
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
131
4 Лабораторная работа № 4
4.2 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Составить программу с использованием одномерных массивов для решения
задачи. Номер варианта определяется по номеру студента по списку преподавателя.
Индивидуальное задание № 1.
Вариант:
1. Ввести массив А из 10 элементов, найти наибольший элемент и переставить
его с первым элементом. Преобразованный массив вывести.
2. Ввести массив А из 10 элементов, найти произведение положитель-ных
элементов и вывести его на экран.
3. Ввести массив А из 10 элементов, найти наименьший элемент и переставить
его с последним элементом. Преобразованный массив вывести.
4. Ввести массив А из 10 элементов, найти сумму отрицательных элементов
и вывести ее на экран.
5. Ввести массив А из 10 элементов, найти сумму элементов, больших 3 и
меньших 8 и вывести ее на экран.
6. Ввести массив А из 10 элементов, найти разность положительных элементов, и вывести ее на экран.
7. Ввести массив А из 10 элементов, найти произведение отрицательных
элементов и вывести его на экран.
8. В заданном массиве подсчитать число нулевых элементов и вывести на
экран их индексы.
9. Составить программу, выдающую индексы заданного элемента или сообщающую, что такого элемента в массиве нет.
10. Ввести массив А из 10 элементов, найти произведение положительных
элементов кратных 3, их количество и вывести результаты на экран.
11. Ввести массив А из 10 элементов, найти сумму отрицательных элементов
кратных 7, их количество и вывести результаты на экран.
132
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
12. Ввести массив А из 10 элементов, найти сумму элементов, больших 2 и
меньших 20 и кратных 8, их количество и вывести результаты на экран.
13. Ввести массив А из 10 элементов, найти сумму элементов, меньших по
модулю 3 и кратных 9, их количество и вывести результаты на экран.
14. Ввести массив А из 10 элементов, найти разность положительных элементов
кратных 11, их количество и вывести результаты на экран.
15. Ввести массив А из 10 элементов, найти произведение элементов, больших
8 и меньших 18 и кратных 10, их количество и вывести результаты на
экран.
16. Ввести массив А из 10 элементов, найти сумму элементов кратных 2, их
количество и вывести результаты на экран.
17. Ввести массив А из 10 элементов, найти квадраты элементов кратных 4 и
их количество. Преобразованный массив вывести.
18. Ввести массив А из 10 элементов, найти сумму положительных элементов
кратных 5, их количество и вывести результаты на экран.
19. Ввести массив А из 10 элементов. Определить количество элементов, кратных 3 и индексы последнего такого элемента.
20. В массивах U[7], D[7], V[7] содержатся значения утренней, дневной и
вечерней температуры соответственно за каждый день недели. Подсчитать
среднее значение дневной температуры за каждый день.
21. В массивах А[n], G[n], F[n] содержатся оценки учащихся по алгебре,
геометрии и физике соответственно. Определить, по какому предмету
лучше успеваемость.
22. В массивах U[7], D[7], V[7] содержатся значения утренней, дневной и
вечерней температуры соответственно за каждый день недели. Сформировать массив S[7], в котором будут содержаться значения среднедневной
температуры. Определить среднее значение температуры а неделю.
23. В массивах А[n], G[n], F[n] содержатся оценки учащихся по алгебре,
геометрии и физике соответственно. Определить среднюю оценку по алгебре и количество учащихся, не имеющих ни одной «двойки».
24. Определить средний рост девочек и мальчиков одного класса. В классе
учится n учеников. (n > 15).
25. В ЭВМ по очереди поступают результаты соревнований по плаванию на
дистанции 200 м, в которых участвует n спортсменов (n > 10). Выдать на
экран дисплея лучший результат.
26. Из массива целых чисел составить три других, в первый из которых
записать числа, кратные 5, во второй - числа, кратные 7, а в третий остальные числа.
133
4 Лабораторная работа № 4
27. Для заданного массива определить, каких элементов больше: положительных или отрицательных. Вывести на экран их количество.
28. В заданном одномерном массиве все отрицательные элементы заменить
нулями и подсчитать их количество.
3. Составить программу с использованием одномерных массивов для решения задачи на переупорядочивание элементов массива. В качестве алгоритма сортировки
использовать сортировку методом «пузырька». Номер варианта определяется
по номеру студента по списку преподавателя.
Индивидуальное задание № 2.
Вариант:
1. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) сумму отрицательных элементов массива;
2) произведение элементов массива, расположенных между максимальным и минимальным элементами.
Упорядочить элементы массива по возрастанию.
2. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) сумму положительных элементов массива;
2) произведение элементов массива, расположенных между максимальным по модулю и минимальным по модулю элементами.
Упорядочить элементы массива по убыванию.
3. В одномерном массиве, состоящем из n целых элементов, вычислить:
1) произведение элементов массива с четными номерами;
2) сумму элементов массива, расположенных между первым и последним
нулевыми элементами.
Преобразовать массив таким образом, чтобы сначала располагались все
положительные элементы, а потом - все отрицательные (элементы, равные
0, считать положительными).
4. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) сумму элементов массива с нечетными номерами;
2) сумму элементов массива, расположенных между первым и последним
отрицательными элементами.
Сжать массив, удалив из него все элементы, модуль которых не превышает
1. Освободившиеся в конце массива элементы заполнить нулями.
134
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
5. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) максимальный элемент массива;
2) сумму элементов массива, расположенных до последнего положительного элемента.
Сжать массив, удалив из него все элементы, модуль которых находится в
интервале [а, b]. Освободившиеся в конце массива элементы заполнить
нулями.
6. В одномерном массиве, состоящем из n целых элементов, вычислить:
1) номер максимального элемента массива;
2) произведение элементов массива, расположенных между первым и
вторым нулевыми элементами.
Преобразовать массив таким образом, чтобы в первой его половине располагались элементы, стоявшие в нечетных позициях, а во второй половине элементы, стоявшие в четных позициях.
7. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) номер минимального элемента массива;
2) сумму элементов массива, расположенных между первым и вторым
отрицательными элементами.
Преобразовать массив таким образом, чтобы сначала располагались все
элементы, модуль которых не превышает 1, а потом - все остальные.
8. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) максимальный по модулю элемент массива;
2) сумму элементов массива, расположенных между первым и вторым
положительными элементами.
Преобразовать массив таким образом, чтобы элементы, равные нулю,
располагались после всех остальных.
9. В одномерном массиве, состоящем из n целых элементов, вычислить:
1) минимальный по модулю элемент массива;
2) сумму модулей элементов массива, расположенных после первого элемента, равного нулю.
Преобразовать массив таким образом, чтобы в первой его половине располагались элементы, стоявшие в четных позициях, а во второй половине элементы, стоявшие в нечетных позициях.
10. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
135
4 Лабораторная работа № 4
1) номер минимального по модулю элемента массива;
2) сумму модулей элементов массива, расположенных после первого
отрицательного элемента.
Сжать массив, удалив из него все элементы, величина которых находится
в интервале [а, b]. Освободившиеся в конце массива элементы заполнить
нулями.
11. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) номер максимального по модулю элемента массива;
2) сумму элементов массива, расположенных после первого положительного элемента.
Преобразовать массив таким образом, чтобы сначала располагались все
элементы, целая часть которых лежит в интервале [а, b], а потом - все
остальные.
12. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) количество элементов массива, лежащих в диапазоне от А до В;
2) сумму элементов массива, расположенных после максимального элемента.
Упорядочить элементы массива по убыванию модулей элементов.
13. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) количество элементов массива, равных 0;
2) сумму элементов массива, расположенных после минимального элемента.
Упорядочить элементы массива по возрастанию модулей элементов.
14. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) количество элементов массива, больших С;
2) произведение элементов массива, расположенных после максимального
по модулю элемента.
Преобразовать массив таким образом, чтобы сначала располагались все
отрицательные элементы, а потом - все положительные (элементы, равные
0, считать положительными).
15. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) количество отрицательных элементов массива;
136
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2) сумму модулей элементов массива, расположенных после минимального по модулю элемента.
Заменить все отрицательные элементы массива их квадратами и упорядочить элементы массива по возрастанию.
16. В одномерном массиве, состоящем из n целых элементов, вычислить:
1) количество положительных элементов массива;
2) сумму элементов массива, расположенных после последнего элемента,
равного нулю.
Преобразовать массив таким образом, чтобы сначала располагались все
элементы, целая часть которых не превышает 1, а потом - все остальные.
17. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) количество элементов массива, меньших С;
2) сумму целых частей элементов массива, расположенных после последнего отрицательного элемента.
Преобразовать массив таким образом, чтобы сначала располагались все
элементы, отличающиеся от максимального не более чем на 20%, а потом все остальные.
18. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) произведение отрицательных элементов массива;
2) сумму положительных элементов массива, расположенных до максимального элемента.
Изменить порядок следования элементов в массиве на обратный.
19. В одномерном массиве, состоящем из n вещественных элементов, вычислить:
1) произведение положительных элементов массива;
2) сумму элементов массива, расположенных до минимального элемента.
Упорядочить по возрастанию отдельно элементы, стоящие на четных
местах, и элементы, стоящие на нечетных местах.
137
4 Лабораторная работа № 4
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Как объявляются одномерные массивы в языке C++?
2. Какими должны быть размерности при описании статического массива в языке
C++?
3. Каков диапазон изменения индекса массива в языке C++?
4. Каким образом производится инициализация массива в языке C++?
5. Чем является идентификатор массива?
6. Для чего используется управляющая последовательность \t?
138
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
4.3 Пример выполнения лабораторной работы № 4
4.3.1 Индивидуальное задание № 1
1.1. Постановка задачи:
Составить программу с использованием одномерных массивов для решения
задачи.
Задача: ввести массив А из 10 элементов, найти сумму элементов, меньших по
модулю 5, и вывести ее на экран.
1.2. UML-диаграмма:
139
4 Лабораторная работа № 4
1.3. Листинг программы:
// Лабораторная работа № 4
// Индивидуальное задание № 1
#include <iostream>
using namespace std;
int main()
{
const int n = 10;
int marks[n] = { };
int sum=0;
cout<<"Лабораторная работа № 4\n";
cout<<"\nНемо А.А., 1-18\n";
cout<<"\nВариант № 6\n";
cout<<"\n\nИндивидуальное задание № 1:\n";
cout<<"\nввести массив А из 10 элементов, найти сумму\n";
cout<<"\nэлементов, меньших по модулю 5, и вывести ее на экран.\n";
cout<<"\n\nРабота программы:\n";
cout<<"\nВведите элементы массива: \n\n";
for(int i = 0; i < n; i++)
{
cout<<"A["<<i<<"]=";
cin >> marks[i];
if(marks[i]< 0) marks[i] = marks[i] * -1;
if(5 > marks[i])
sum += marks[i];
}
cout<<"\n␣=␣"<< sum;
return 0;
}
140
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.4. Результат работы программы:
141
4 Лабораторная работа № 4
4.3.2 Индивидуальное задание № 2
2.1. Постановка задачи:
Составить программу с использованием одномерных массивов для ре- шения
задачи на переупорядочивание элементов массива. В качестве алго- ритма сортировки использовать сортировку методом «пузырька». Задача: в одномерном массиве,
состоящем из n вещественных элементов, вычислить:
1) минимальный элемент массива;
2) сумму элементов массива, расположенных между первым и последним положительными элементами.
Преобразовать массив таким образом, чтобы сначала располагались все элементы,
равные нулю, а потом – все остальные.
2.2. UML-диаграмма:
142
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.3. Листинг программы:
// Лабораторная работа № 4
// Индивидуальное задание № 2
#include <iostream>
using namespace std;
int main()
{
const int N = 1000;
int a[N];
int n, i, j, temp;
int firstPos = -1, lastPos;
cout<<"Лабораторная работа № 4\n";
cout<<"\nНемо А.А., 1-18\n";
cout<<"\n␣␣6\n";
cout<<"\n\nИндивидуальное задание № 2:\n";
cout<<"\nв одномерном массиве, состоящем из n"
<<"вещественных элементов, вычислить:\n";
cout<<"\n1)␣минимальный элемент массива;\n";
cout<<"\n2)␣сумму элементов массива, расположенных между первым\n";
cout<<"\nи последним положительными элементами.\n";
cout<<"\nПреобразовать массив таким образом,"
<<"чтобы сначала располагались\n";
cout<<"\nвсе элементы, равные нулю, а потом "
<<"все остальные.\n";
cout<<"\n\nРабота программы:\n";
cout<<"\nВведите количество элементов: ";
cin >> n;
if(n <= 0 || n > N)
{
cout<<"Неверный размер массива"<< "\n";
return 1;
}
cout<<"\nВведите элементы массива: \n\n";
for(i = 0; i < n; i++)
cin >> a[i];
// Поиск минимального элемента массива
143
4 Лабораторная работа № 4
temp = a[0];
for(i = 0; i < n; i++)
{
if(a[i] < temp)
temp = a[i];
}
cout<<"\nМинимальный элемент массива: "<< temp
<< "\n";
// Сумма элементов между первым и последним
// положительными элементами
temp = 0;
for(i = 0; i < n; i++)
{
// первый положительный элемент не найден и текущий >0?
if(firstPos == -1 && a[i] > 0) firstPos = i;
// тогда записываем координаты текущего элемента
// как первого положительного
if(a[i] > 0) lastPos = i;
}
if(firstPos != -1)
{
for(i = firstPos; i <= lastPos; i++) temp += a[i];
}
cout<<"\nСумма элементов массива, расположенных между";
cout<<"\nпервым и последним положительными элементами: "
<< temp << "\n";
// Сортировка
temp = 0;
for(i = 0; i < n; i++)
for(j = 0; j < n; j++)
if(a[j+1] == 0)
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
cout<<"\nПосле сортировки: ";
144
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
for(i = 0; i < n; i++) cout << a[i] << "␣";
cout << endl;
return 0;
}
145
4 Лабораторная работа № 4
2.4. Результат работы программы:
146
Лабораторная работа № 5
Указатели и ссылки в языке C++
Цель работы и содержание:
закрепление знаний об указателях и ссылках, составление программ с указателями и ссылками.
Ход работы
5.1 Основные сведения об указателях в языке C++.
Когда компилятор обрабатывает оператор определения переменной, например,
int i=10;, он выделяет память в соответствии с типом (int) и инициализирует ее
указанным значением (10). Все обращения в программе к переменной по ее имени (i)
заменяются компилятором на адрес области памяти, в которой хранится значение
переменной. Программист может определить собственные переменные для хранения
адресов областей памяти. Такие переменные называются указателями.
Указатели предназначены для хранения адресов областей памяти. В C++ различают три вида указателей - указатели на объект, на функцию и на void, отличающиеся свойствами и набором допустимых операций. Указатель не является
самостоятельным типом, он всегда связан с каким-либо другим конкретным типом.
Указатель на функцию содержит адрес в сегменте кода, по которому располагается исполняемый код функции, то есть адрес, по которому передается управление
при вызове функции.
Указатели на функции используются для косвенного вызова функции (не
через ее имя, а через обращение к переменной, хранящей ее адрес) а также для
передачи имени функции в другую функцию в качестве параметра. Указатель
функции имеет тип «указатель функции, возвращающей значение заданного типа и
имеющей аргументы заданного типа»:
тип (*имя) ( список_типов_аргументов );
147
5 Лабораторная работа № 5
Например, объявление:
int (*fun) (double, double);
задает указатель с именем fun на функцию, возвращающую значение типа int и
имеющую два аргумента типа double.
Указатель на объект содержит адрес области памяти, в которой хранятся
данные определенного типа (основного или составного). Простейшее объявление
указателя на объект (в дальнейшем называемого просто указателем) имеет вид:
тип *имя;
где тип может быть любым, кроме ссылки и битового поля, причем тип может быть
к этому моменту только объявлен, но еще не определен (следовательно, в структуре,
например, может присутствовать указатель на структуру того же типа).
Звездочка относится непосредственно к имени, поэтому для того, чтобы объявить несколько указателей, требуется ставить ее перед именем каждого из них.
Например, в операторе
int *a, b, *c;
описываются два указателя на целое с именами а и с, а также целая переменная b.
Размер указателя зависит от модели памяти. Можно определить указатель на
указатель и т.д.
Указатель на void применяется в тех случаях, когда конкретный тип объекта,
адрес которого требуется хранить, не определен (например, если в одной и той же
переменной в разные моменты времени требуется хранить адреса объектов различных
типов).
Указателю на void можно присвоить значение указателя любого типа, а также
сравнивать его с любыми указателями, но перед выполнением каких-либо действий
с областью памяти, на которую он ссылается, требуется преобразовать его к конкретному типу явным образом.
Указатель может быть константой или переменной, а также указывать на
константу или переменную. Рассмотрим примеры:
int i;
const int ci = 1;
int * pi;
const int * pci;
int * const cp = &i;
//
//
//
//
//
целая переменная
целая константа
указатель на целую переменную
указатель на целую константу
указатель-константа на
148
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
//
const int * const cpc = &ci; //
//
целую переменную
указатель
константа на целую константу
Как видно из примеров, модификатор const, находящийся между именем
указателя и звездочкой, относится к самому указателю и запрещает его изменение,
a const слева от звездочки задает постоянство значения, на которое он указывает.
Для инициализации указателей использована операция получения адреса &.
Величины типа указатель подчиняются общим правилам определения области
действия, видимости и времени жизни.
5.2 Инициализация указателей.
Указатели чаще всего используют при работе с динамической памятью, называемой некоторыми эстетами кучей (перевод с английского языка слова heap).
Это свободная память, в которой можно во время выполнения программы выделять
место в соответствии с потребностями. Доступ к выделенным участкам динамической памяти, называемым динамическими переменными, производится только через
указатели. Время жизни динамических переменных - от точки создания до конца
программы или до явного освобождения памяти. В C++ используется два способа
работы с динамической памятью. Первый использует семейство функций mallос и
достался в наследство от С, второй использует операции new и delete.
При определении указателя надо стремиться выполнить его инициализацию,
то есть присвоение начального значения. Непреднамеренное использование неинициализированных указателей - распространенный источник ошибок в программах.
Инициализатор записывается после имени указателя либо в круглых скобках, либо
после знака равенства.
Существуют следующие способы инициализации указателя:
1. Присваивание указателю адреса существующего объекта:
1.1. с помощью операции получения адреса:
int a = 5;
// целая переменная
int* p = &a; // в указатель записывается адрес а
int* p (&a); // то же самое другим способом
1.2. с помощью значения другого инициализированного указателя:
int* r = p;
149
5 Лабораторная работа № 5
1.3. с помощью имени массива или функции, которые трактуются как адрес:
int b[10];
// массив
int* t = b;
// присваивание адреса начала массива
void f(int a ){ /* ... */ } // определение функции
void (*pf)(int); // указатель на функцию
pf = f;
// присваивание адреса функции
2. Присваивание указателю адреса области памяти в явном виде:
char* vp = (char *)0xB8000000;
Здесь 0хВ8000000 - шестнадцатеричная константа, (char *) - операция приведения типа: константа преобразуется к типу «указатель на char».
3. Присваивание пустого значения:
int* suxx = NULL;
int* rulez = 0;
В первой строке используется константа NULL, определенная в некоторых заголовочных файлах С как указатель, равный нулю. Можно использовать просто
0, так как это значение типа int будет правильно преобразовано стандартными
способами в соответствии с контекстом. Поскольку гарантируется, что объектов
с нулевым адресом нет, пустой указатель можно использовать для проверки,
ссылается указатель на конкретный объект или нет.
4. Выделение участка динамической памяти и присваивание ее адреса указателю:
4.1. с помощью операции new:
int* n = new int;
int* m = new int (10);
int* q = new int [10];
// 1
// 2
// 3
4.2. с помощью функции malloc:
int* u = (int *)malloc(sizeof(int));
150
// 4
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
В операторе 1 операция new выполняет выделение достаточного для размещения величины типа int участка динамической памяти и записывает адрес начала
этого участка в переменную n. Память под саму переменную n (размера, достаточного
для размещения указателя) выделяется на этапе компиляции.
В операторе 2, кроме описанных выше действий, производится инициализация
выделенной динамической памяти значением 10.
В операторе 3 операция new выполняет выделение памяти под 10 величин типа
int (массива из 10 элементов) и записывает адрес начала этого участка в переменную
q, которая может трактоваться как имя массива. Через имя можно обращаться к
любому элементу массива. Если память выделить не удалось, по стандарту должно
порождаться исключение bad_alloc. Старые версии компиляторов могут возвращать
0.
В операторе 4 делается то же самое, что и в операторе 1, но с помощью
функции выделения памяти malloc, унаследованной из библиотеки С. В функцию
передается один параметр - количество выделяемой памяти в байтах. Конструкция
(int*) используется для приведения типа указателя, возвращаемого функцией, к
требуемому типу. Если память выделить не удалось, функция возвращает 0.
Операцию new использовать предпочтительнее, чем функцию malloc, особенно
при работе с объектами.
Освобождение памяти, выделенной с помощью операции new, должно выполняться с помощью delete, а памяти, выделенной функцией mallос - посредством
функции free. При этом переменная-указатель сохраняется и может инициализироваться повторно. Приведенные выше динамические переменные уничтожаются
следующим образом:
delete n;
delete m;
delete [] q;
free (u);
Если память выделялась с помощью new[], для освобождения памяти необходимо
применять delete[]. Размерность массива при этом не указывается. Если квадратных скобок нет, то никакого сообщения об ошибке не выдается, но помечен как
свободный будет только первый элемент массива, а остальные окажутся недоступны
для дальнейших операций. Такие ячейки памяти называются мусором.
С помощью комбинаций звездочек, круглых и квадратных скобок можно описывать составные типы и указатели на составные типы, например, в операторе
int *(*p[10])();
объявляется массив из 10 указателей на функции без параметров, возвращающих
указатели на int.
151
5 Лабораторная работа № 5
По умолчанию квадратные и круглые скобки имеют одинаковый приоритет,
больший, чем звездочка, и рассматриваются слева направо. Для изменения порядка
рассмотрения используются круглые скобки.
При интерпретации сложных описаний необходимо придерживаться правила
«изнутри наружу»:
1) если справа от имени имеются квадратные скобки, это массив, если скобки
круглые - это функция;
2) если слева есть звездочка, это указатель на проинтерпретированную ранее
конструкцию;
3) если справа встречается закрывающая круглая скобка, необходимо применить
приведенные выше правила внутри скобок, а затем переходить наружу;
4) в последнюю очередь интерпретируется спецификатор типа.
Для приведенного выше описания порядок интерпретации указан цифрами:
int *(*p[10])();
5 4 2
1 3
// порядок интерпретации описания
5.3 Операции с указателями.
С указателями можно выполнять следующие операции: разадресация, или косвенное обращение к объекту (*), присваивание, сложение с константой, вычитание,
инкремент (++), декремент (–), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&).
Операция разадресации, или разыменования, предназначена для доступа к
величине, адрес которой хранится в указателе. Эту операцию можно использовать
как для получения, так и для изменения значения величины (если она не объявлена
как константа):
char a;
char *p = new char;
*p = ’Ю’; a = *p;
// переменная типа char
/* выделение памяти под указатель и под
динамическую переменную типа char */
/* присваивание значения обеим переменным */
Как видно из примера, конструкцию *имя_указателя можно использовать в
левой части оператора присваивания, так как она является L-значением, то есть
определяет адрес области памяти. Для простоты эту конструкцию можно считать
именем переменной, на которую ссылается указатель. С ней допустимы все действия,
определенные для величин соответствующего типа (если указатель инициализирован).
152
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
На одну и ту же область памяти может ссылаться несколько указателей различного
типа. Примененная к ним операция разадресации даст разные результаты. Например,
программа
#include <stdio.h>
int main()
{
unsigned long int A = 0Xcc77ffaa;
unsigned short int* pint = (unsigned short int*) &A;
unsigned char* pchar = (unsigned char *) &A;
printf("␣|␣%x␣|␣%x␣|␣%x␣|", A, *pint, *pchar);
return 0;
}
на IBM PC-совместимом компьютере выведет на экран строку:
| cc77ffaa | ffaa | аа |
Значения указателей pint и pchar одинаковы, но разадресация pchar дает
в результате один младший байт по этому адресу, a pint - два младших байта.
В приведенном выше примере при инициализации указателей были использованы
операции приведения типов. Синтаксис операции явного приведения типа прост:
перед именем переменной в скобках указывается тип, к которому ее требуется
преобразовать. При этом не гарантируется сохранение информации, поэтому в общем
случае явных преобразований типа следует избегать.
При смешивании в выражении указателей разных типов явное преобразование
типов требуется для всех указателей, кроме void*. Указатель может неявно преобразовываться в значение типа bool (например, в выражении условного оператора), при
этом ненулевой указатель преобразуется в true, а нулевой в false. Присваивание
без явного приведения типов допускается в двух случаях:
1. указателям типа void*;
2. если тип указателей справа и слева от операции присваивания один и тот же.
Таким образом, неявное преобразование выполняется только к типу void*.
Значение 0 неявно преобразуется к указателю на любой тип. Присваивание указателей на объекты указателям на функции (и наоборот) недопустимо. Запрещено и
присваивать значения указателям-константам, впрочем, как и константам любого
типа (присваивать значения указателям на константу и переменным, на которые
ссылается указатель-константа, допускается).
Арифметические операции с указателями (сложение с константой, вычитание,
инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа
153
5 Лабораторная работа № 5
и имеют смысл в основном при работе со структурами данных, последовательно
размещенными в памяти, например, с массивами.
Инкремент перемещает указатель к следующему элементу массива, декремент
- к предыдущему. Фактически значение указателя изменяется на величину sizeof
(тип). Если указатель на определенный тип увеличивается или уменьшается на
константу, его значение изменяется на величину этой константы, умноженную на
размер объекта данного типа, например:
short *p = new short [3];
p++;
// значение p увеличивается на 2
long *q = new long [5];
q++;
// значение q увеличивается на 4
Разность двух указателей - это разность их значений, деленная на размер типа
в байтах (в применении к массивам разность указателей, например, на третий и
шестой элементы равна 3).
Суммирование двух указателей не допускается. При записи выражений с указателями следует обращать внимание на приоритеты операций. В качестве примера
рассмотрим последовательность действий, заданную в операторе
*p++ = 10;
Операции разадресации и инкремента имеют одинаковый приоритет и выполняются справа налево, но, поскольку инкремент постфиксный, он выполняется после
выполнения операции присваивания. Таким образом, сначала по адресу, записанному
в указателе р, будет записано значение 10, а затем указатель будет увеличен на
количество байт, соответствующее его типу. То же самое можно записать подробнее:
*p = 10; p++;
Выражение (*р)++, напротив, инкрементирует значение, на которое ссылается
указатель.
Унарная операция получения адреса & применима к величинам, имеющим
имя и размещенным в оперативной памяти. Таким образом, нельзя получить адрес
скалярного выражения, неименованной константы или регистровой переменной.
Примеры операции приводились выше.
5.4 Динамические массивы.
Если до начала работы программы неизвестно, сколько в массиве элементов, в
программе следует использовать динамические массивы. Память под них выделяется
с помощью операции new или функции mallос в динамической области памяти
154
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
во время выполнения программы. Адрес начала массива хранится в переменнойуказателе.
Обращение к элементу динамического массива осуществляется так же, как и к
элементу обычного - например а[3]. Можно обратиться к элементу массива и другим
способом - *(а+3). В этом случае мы явно задаем те же действия, что выполняются
при обращении к элементу массива обычным образом. Рассмотрим их подробнее.
В переменной-указателе а хранится адрес начала массива. Для получения адреса
третьего элемента к этому адресу прибавляется смещение 3. Операция сложения с
константой для указателей учитывает размер адресуемых элементов, то есть на самом
деле индекс умножается на длину элемента массива: а + 3*sizeof(int). Затем с
помощью операции * (разадресации) выполняется выборка значения из указанной
области памяти.
Если динамический массив в какой-то момент работы программы перестает
быть нужным и мы собираемся впоследствии использовать эту память повторно,
необходимо освободить ее с помощью операции delete[], например:
delete[] a;
Размерность массива при этом не указывается.
Таким образом, время жизни динамического массива, как и любой динамической переменной, - с момента выделения памяти до момента ее освобождения.
Область действия зависит от места описания указателя, через который производится
работа с массивом. Область действия и время жизни указателей подчиняются общим
правилам, рассмотренным на первом семинаре. Как вы помните, локальная переменная при выходе из блока, в котором она описана, «теряется». Если эта переменная
является указателем и в ней хранится адрес выделенной динамической памяти, при
выходе из блока эта память перестает быть доступной, однако не помечается как
свободная, поэтому не может быть использована в дальнейшем. Это называется
утечкой памяти и является распространенной ошибкой:
{ // пример утечки памяти
int n;
cin >> n;
int *pmas = new int[n];
...
} // После выхода из блока указатель pmas недоступен
Пример 5.1.
Написать программу, которая для вещественного массива из n элементов определяет сумму его элементов, расположенных правее последнего отрицательного
элемента.
155
5 Лабораторная работа № 5
В этой задаче размерность массива задана переменной величиной. Предполагается, что она будет нам известна на этапе выполнения программы до того, как мы
будем вводить сами элементы. В этом случае мы сможем выделить в динамической
памяти непрерывный участок нужного размера, а потом заполнять его вводимыми
значениями. Если же стоит задача вводить заранее неизвестное количество чисел
до тех пор, пока не будет введен какой-либо признак окончания ввода, то заранее
выделить достаточное количество памяти не удастся и придется воспользоваться
так называемыми динамическими структурами данных, например списком.
Просматривая массив с начала до конца, найти номер последнего отрицательного элемента, а затем организовать цикл суммирования всех элементов, расположенных
правее него. Вот как выглядит построенная по этому алгоритму программа:
Листинг 5.1
#include <math.h>
#include <iostream>
using namespace std;
int main()
{
int n;
cout<<"\nВведите количество элементов: ";
int i, ineg;
float sum, *a = new float[n]; // 1
cout<< "\nВведите элементы массива: \n\n";
for(i = 0; i < n; i++) cin >> a[i];
for(i = 0; i < n; i++) cout << a[i] << ’␣’; // 2
for(i = 0; i < n; i++)
if(a[i] < 0) ineg = i; // 3
for(sum = 0., i = ineg + 1; i < n; i++)
sum += a[i]; // 4
cout<<"\nСумма: "<< sum;
delete[] a;
return 0;
}
Поскольку количество элементов заранее не задано, память под массив выделяется в операторе 1 на этапе выполнения программы с помощью операции new.
Выделяется столько памяти, сколько необходимо для хранения n элементов вещественного типа, и адрес начала этого участка заносится в указатель а.
Номер последнего отрицательного элемента массива формируется в переменной
ineg. При просмотре массива с помощью оператора 3 в эту переменную последова-
156
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
тельно записываются номера всех отрицательных элементов массива, таким образом,
после выхода из цикла в ней остается номер самого последнего.
С целью оптимизации программы может возникнуть мысль объединить цикл
нахождения этого номера с циклами ввода и контрольного вывода элементов массива,
но это не рекомендуется делать, потому что ввод данных, их вывод и анализ - разные
по смыслу действия, и смешивание их в одном цикле не прибавит программе ясности.
После отладки программы контрольный вывод (оператор 2) можно удалить или
закомментировать.
Теперь перейдем к критическому анализу нашей первой попытки решения задачи. Для массивов, содержащих отрицательные элементы, эта программа работает
верно, но при их отсутствии, как правило, завершается аварийно. Это связано с
тем, что если в массиве нет ни одного отрицательного элемента, переменной ineg
значение не присваивается. Поэтому в операторе for (оператор 4) будет использовано
значение ineg, инициализированное произвольным образом. Поэтому в программу
необходимо внести проверку, есть ли в массиве хотя бы один отрицательный элемент.
Для этого переменной ineg присваивается начальное значение, не входящее в множество допустимых индексов массива (например, −1). После цикла поиска номера
отрицательного элемента выполняется проверка, сохранилось ли начальное значение
ineg неизменным. Если это так, это означает, что условие a[i]<0 в операторе 3 не
выполнилось ни разу, и отрицательных элементов в массиве нет:
Листинг 5.2
#include <math.h>
#include <iostream>
using namespace std;
int main()
{
int n;
cout<<"\nВведите количество элементов: ";
cin >> n;
int i, ineg = -1;
float sum, *a = new float[n];
// 1
cout<<"\nВведите элементы массива: \n\n";
for(i = 0; i < n; i++) cin >> a[i];
for(i = 0; i < n; i++) cout << a[i] << ’␣’; // 2
for(i = 0; i < n; i++)
if(a[i] < 0) ineg = i;
// 3
if(ineg != -1)
{
for(sum = 0., i = ineg + 1; i < n; i++)
sum += a[i];
// 4
157
5 Лабораторная работа № 5
cout<<"\nСумма: "<< sum;
}
delete[] a;
return 0;
}
Можно предложить и более рациональное решение этой задачи: просматривать
массив в обратном порядке, суммируя его элементы, и завершить цикл, как только
встретится отрицательный элемент:
Листинг 5.3
#include "math.h"
#include <iostream>
using namespace std;
int main()
{
int n;
cout<<"\nВведите количество элементов: ";
cin >> n;
int i, ineg = -1;
float sum, *a = new float[n];
// 1
cout<<"\nВведите элементы массива: \n\n";
for(i = 0; i < n; i++) cin >> a[i];
bool flag_neg = false;
float sum = 0.f;
for(i = n - 1; i >= 0; i--)
{
if(a[i] < 0)
{
flag_neg = true;
break;
}
sum += a[i];
}
if(flag_neg)
cout<<"\nСумма: "<< sum;
158
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
else
cout<<"\nОтрицательных элементов нет ";
return 0;
}
В этой программе каждый элемент массива анализируется не более одного
раза, а ненужные элементы не просматриваются вообще. Для больших массивов это
играет роль, поэтому последний вариант программы предпочтительнее, хотя на вид
он отличается от предыдущего незначительно.
Для исчерпывающего тестирования этой программы необходимо ввести по
крайней мере три варианта исходных данных - когда массив содержит один, несколько
или ни одного отрицательного элемента.
Пример 5.2.
Написать программу, которая упорядочивает вещественный массив методом
быстрой сортировки.
Идея алгоритма состоит в следующем. Применим к массиву так называемую
процедуру разделения относительно среднего элемента. Вообще-то, в качестве «среднего» можно выбрать любой элемент массива, но для наглядности мы будем выбирать
по возможности, средний по своему номеру элемент.
Процедура разделения делит массив на две части. В левую помещаются элементы, меньшие, чем элемент, выбранный в качестве среднего, а в правой - большие.
Это достигается путем просмотра массива попеременно с обоих концов, при этом
каждый элемент сравнивается с выбранным средним, и элементы, находящиеся в
«неподходящей» части, меняются местами. После завершения процедуры разделения
средний элемент оказывается на своем окончательном месте. Далее процедуру разделения необходимо повторить отдельно для левой и правой части: в каждой части
выбирается среднее, относительно которого она делится на две, и так далее.
Понятно, что одновременно процедура не может заниматься и левой, и правой
частями, поэтому необходимо каким-то образом запомнить запрос на обработку
одной из двух частей (например, правой), и заняться оставшейся частью (например,
левой). Так продолжается до тех пор, пока не окажется, что очередная обрабатываемая часть содержит ровно один элемент. Тогда нужно вернуться к последнему из
необработанных запросов, применить к нему все ту же процедуру разделения и так
далее. В конце концов, массив окажется полностью упорядочен.
Для хранения границ еще не упорядоченных частей массива более всего подходит структура данных, называемая стеком. Стек является одной из наиболее часто
употребляемых структур данных и функционирует по принципу: «первым пришел,
последним ушел».
В приведенной ниже программе стек реализуется в виде двух массивов stackr
и stackl и одной переменной sp, используемой как «указатель» на вершину стека
159
5 Лабораторная работа № 5
(она хранит номер последнего заполненного элемента массива). Для этого алгоритма
количество элементов в стеке не может превышать n, поэтому размер массивов задан
равным именно этой величине. При занесении в стек переменная sp увеличивается
на единицу, а при выборке - уменьшается.
Ниже приведена программа, реализующая этот алгоритм:
Листинг 5.4
#include <iostream>
using namespace std;
int main()
{
const int n = 20;
float *arr = new float[n], middle, temp;
int *stackl = new int[n], *stackr = new int[n];
int sp = 0, i, j, left, right;
cout << "Введите элементы массива: ";
for(i = 0; i < n; i++) cin >> a[i];
//
sp = 1; stackl[1] = 0; stackr[1] = n - 1; // 1
while(sp > 0)
{
// 2
// Выборка из стека последнего запроса
left = stackl[sp];
// 3
right = stackr[sp];
// 4
sp--;
// 5
while(left < right)
{
// 6
// Разделение {arr[left]..arr[right]}
i = left; j = right;
// 7
middle = arr[(left + right)/2];
// 8
while(i < j)
{
while(arr[i] < middle) i++;
while(middle < arr[j]) j--;
if(i <= j)
160
// 9
// 10
// 11
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++; j--;
}
}
if(i < right)
{
// 12
//Запись в стек запроса из правой части
sp++;
stackl[sp] = i;
stackr[sp] = right;
}
right = j;
// 13
// Теперь left и right ограничивают левую часть
}
}
// Вывод результата
for(i = 0; i < n; i++) cout << arr[i] <<
cout << endl;
;
// Удаление динамических массивов
delete[] arr;
delete[] stackl;
delete[] stackr;
return 0;
}
На каждом шаге сортируется один фрагмент массива. Левая граница фрагмента хранится в переменной left, правая - в переменной right. Сначала фрагмент
устанавливается размером с массив целиком (строка 1). В операторе 8 выбирается
«средний» элемент фрагмента.
Для продвижения по массиву слева направо в цикле 10 используется переменная
i, справа налево - переменная j (в цикле 11). Их начальные значения устанавливаются
в операторе 7. После того, как оба счетчика «сойдутся» где-то в средней части массива,
происходит выход из цикла 9 на оператор 12, в котором заносятся в стек границы
правой части фрагмента. В операторе 13 устанавливаются новые границы левой
части для сортировки на следующем шаге.
161
5 Лабораторная работа № 5
Если сортируемый фрагмент уже настолько мал, что сортировать его не требуется, происходит выход из цикла 6, после чего выполняется выборка из стека границ
еще не отсортированного фрагмента (операторы 3,4). Если стек пуст, происходит
выход из главного цикла 2. Массив отсортирован.
5.5 Ссылки.
Ссылка представляет собой синоним имени, указанного при инициализации
ссылки. Ссылку можно рассматривать как указатель, который всегда разыменовывается. Формат объявления ссылки:
тип & имя;
где тип - это тип величины, на которую указывает ссылка, & - оператор ссылки,
означающий, что следующее за ним имя является именем переменной ссылочного
типа, например:
int kol;
int& pal = kol;
const char& CR = "\n";
// ссылка pal-альтернативное имя для kol
// ссылка на константу
Запомните следующие правила.
- переменная-ссылка должна явно инициализироваться при ее описании, кроме
случаев, когда она является параметром функции, описана как extern или
ссылается на поле данных класса.
- после инициализации ссылке не может быть присвоена другая переменная.
- тип ссылки должен совпадать с типом величины, на которую она ссылается.
- не разрешается определять указатели на ссылки, создавать массивы ссылок и
ссылки на ссылки.
Ссылки применяются чаще всего в качестве параметров функций и типов
возвращаемых функциями значений. Ссылки позволяют использовать в функциях
переменные, передаваемые по адресу, без операции разадресации, что улучшает
читаемость программы.
Ссылка, в отличие от указателя, не занимает дополнительного пространства в
памяти и является просто другим именем величины. Операция над ссылкой приводит
к изменению величины, на которую она ссылается.
162
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Пример 5.3.
Составить программу нахождения тех элементов массива С (из n элементов),
индексы которых являются степенями двойки (1, 2, 4, 8. . . ). UML-диаграмма этого
алгоритма приведена на рисунке 5.1.
Рис. 5.1: UML-диаграмма деятельности для для примера 5.3
163
5 Лабораторная работа № 5
Листинг 5.5
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
const int N = 1000;
int n, i, j, left, right, sp = 0, m = 1;
float *C = new float[N], middle, temp;
int *stackl = new int[N], *stackr = new int[N];
cout<<"\n\nЗадание:\n";
cout<<"\nСоставить программу с использованием"
<<" динамических массивов\n";
cout<<"\nдля решения задачи на переупорядочивание"
<<" элементов массива.\n";
cout<<"\nВ качестве алгоритма сортировки использовать метод"
<<" быстрой сортировки массива.\n";
cout<<"\n\nЗадача: составить программу нахождения"
<<" тех элементов массива C,\n";
cout<<"\nиндексы которых являются степенями двойки"<<
<<"␣(1,␣2,␣4,...).\n";
cout<<"\n\nРабота программы:\n"
A:
cout<<"\nВведите количество элементов: ";
cin >> n;
if(n <= 0 || n > N)
{
cout<<"Неверный размер массива"<< "\n";
goto A;
}
cout<<"\nВведите элементы массива: \n\n";
for(i = 0; i < n; i++) cin >> C[i];
sp = 1;
stackl[1] = 0;
stackr[1] = n - 1;
while(sp > 0)
{
164
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
left = stackl[sp];
right = stackr[sp];
sp--;
while(left < right)
{
i = left;
j = right;
middle = C[(left + right)/2];
while(i < j)
{
while(C[i] < middle) i++;
while(middle < C[j]) j--;
if(i <= j)
{
temp = C[i];
C[i] = C[j];
C[j] = temp;
i++;
j--;
}
}
if(i < right)
{
sp++;
stackl[sp] = i;
stackr[sp] = right;
}
right = j;
}
}
cout<<"\nМассив после сортировки:\n\n";
for(i = 0; i < n; i++)
{
cout << C[i] << "\n";
}
cout<<"\nМассив из элементов, индексы которых являются "
<<"степенями двойки:\n\n"<<"\n" ;
for(i = 0; i < n; i++)
{
while (m < n)
{
cout << C[m] << "\n" ;
m = m * 2;
165
5 Лабораторная работа № 5
}
}
delete[] C;
delete[] stackl;
delete[] stackr;
return 0;
}
166
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Пример 5.4.
В массиве { aj }, j = 1, 2, ... 10 есть положительные и отрицательные элементы. Вычислить произведение отрицательных элементов. UML-диаграмма этого
алгоритма приведена на рисунке 5.2.
Рис. 5.2: UML-диаграмма деятельности для для примера 5.4
167
5 Лабораторная работа № 5
Листинг 5.6
// Лабораторная работа № 5_1
#include <iostream>
#include <math.h>
using namespace std;
const int n = 1000;
int main()
{
int n;
int RANGE_MIN = 0;
int RANGE_MAX = 20;
cout<< "Лабораторная работа № 5_1" << ’\n’;
cout<< ’\n’;
cout<< "␣В массиве {a_j}, j = 1, 2, ...10 "
<<"есть положительные и отрицательные элементы. " ;
cout<< ’\n’;
cout<< "␣Вычислить произведение отрицательных элементов " ;
cout<< ’\n’<< ’\n’<< ’\n’;
cout<< "\nВведите резмерность массива " ;
cin >> n;
int i = 0, s = 0, klav;
double *arr = new double[n];
double piece = 1;
cout<< ’\n’;
cout<< "␣Как будет произведено заполнение? " ;
cout<< ’\n’;
cout<<"␣␣1␣-␣случайным образом " ;
cout<< ’\n’;
cout<< "␣␣2␣-␣в ручную " ;
cout<< ’\n’;
cout<<"Для продолжения нажмите клавишу выбора ";
cin >> klav;
cout << ’\n’;
if (klav == 1)
{
srand( (unsigned int)time( NULL ) );
for( i = 0; i < n; i++)
168
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
int rand100 = ( ((double) rand()/(double) RAND_MAX)*
RANGE_MAX+RANGE_MIN);
arr[i]=rand100-10;
}
for( i = 0; i < n; i++) cout << arr[i] << "␣␣";
}
if (klav==2)
{
for( i = 0; i < n; i++)
{
cout<< "Введите " << i+1 ;
cout<< "␣элемент ";
cin >> arr[i];
}
for( i = 0; i < n; i++) cout << arr[i] << "␣␣";
}
if
{
(klav != 1 && klav !=2 )
cout<< "Неправильный выбор" ;
cout<< "␣Для выхода нажмите любую клавишу...";
goto stop;
}
cout << ’\n’;
for( i = 0; i < n; i++)
{
if (arr[i] < 0)
{
piece = piece*arr[i];
s++;
}
}
if (s==0)
{
cout<< ’\n␣’<<"В массиве отсутствуют отрицательные элементы";
}
else
{
169
5 Лабораторная работа № 5
cout<< ’\n’
<<"␣Произведение отрицательных элементов = "
<< piece;
}
delete []arr;
stop: getchar ();
return 0;
}
170
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
171
5 Лабораторная работа № 5
5.6 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание в соответствии с индивидуальным заданием лабораторной работы № 4.
Защита лабораторной работы происходит только после его выполнения (индивидуального задания). При защите лабораторной работы студент отвечает на контрольные
вопросы, приведенные в конце, и поясняет выполненное индивидуальное задание.
Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Составить программу с использованием динамических массивов для решения
задачи. Номер варианта определяется по формуле , где - номер студента по
списку преподавателя.
Индивидуальное задание № 1.
Вариант:
1) Сформировать массив, содержащий 7 элементов, задав элементы с клавиатуры. Определить количество элементов, кратных 3 и индексы последнего
такого элемента.
2) В заданном массиве подсчитать число нулевых элементов и вывести а
экран их индексы.
3) Сформировать с помощью датчика случайных чисел массив, элементы которого находятся в промежутке от -60 до 60. Подсчитать в нем количество
положительных элементов.
4) В заданном одномерном массиве все отрицательные элементы заменить
нулями и подсчитать их количество.
5) В массивах U[7], D[7], V[7] содержатся значения утренней, дневной и
вечерней температуры соответственно за каждый день недели. Подсчитать
среднее значение дневной температуры за каждый день.
6) В массивах А[n], G[n], F[n] содержатся оценки учащихся по алгебре,
геометрии и физике соответственно. Определить, по какому предмету
лучше успеваемость.
7) Сформировать с помощью датчика случайных чисел массив A[n], элементы которого находятся в промежутке от −60 до 60. Создать массив B[n],
каждый элемент которого вычисляется по формуле:

если A [i] < 0;
 A [i] ,
2A [i] + 10, если A [i] > 0;
B [i] =

0,
если A [i] = 0.
172
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
8) В заданном одномерном массиве найти максимальный элемент и поменять
его местами с последним элементом массива.
9) Задан массив из N случайных чисел, принадлежащих промежутку [-50,
100]. Найти сумму тех элементов массива, которые больше 15, но меньше
45, а также вычислить количество этих элементов.
10) В массивах U[7], D[7], V[7] содержатся значения утренней, дневной и
вечерней температуры соответственно за каждый день недели. Сформировать массив S[7], в котором будут содержаться значения среднедневной
температуры. Определить среднее значение температуры за неделю.
11) В массивах А[n], G[n], F[n] содержатся оценки учащихся по алгебре,
геометрии и физике соответственно. Определить среднюю оценку по алгебре и количество учащихся, не имеющих ни одной «двойки».
12) Из массива целых чисел составить три других, в первый из которых
записать числа, кратные 5, во второй - числа, кратные 7, а в третий остальные числа.
13) Дана последовательность из 100 различных чисел. Составить программу
нахождения суммы чисел этой последовательности, расположенных между
максимальным и минимальным числами (в сумму включить и оба эти
числа)
14) Задан одномерный числовой массив. Вычислить сумму произведений всех
пар соседних чисел
15) Определить в одномерном числовом массиве число соседств из двух чисел
разного знака.
16) Проверить, имеется ли в данном одномерном числовом массиве хотя бы
одна пара чисел, совпадающих по величине.
17) Проверить, имеется ли в одномерном числовом массиве хотя бы одна пара
соседних чисел, являющихся противоположными.
18) Дан числовой массив a1 , a2 , . . . an . Вывести на печать массив b1 , b2 , . . . bn ,
в котором bi = a1 , b2 = a1 + a2 , b3 = a1 + a2 + a3 и т.д.
19) В заданном массиве найти максимальный элемент. Элементы, стоящие
после максимального элемента заменить нулями.
20) Из чисел a1 , a2 , . . . an выбрать те, которые меньше заданного числа с , и
образовать из них новый массив, сохранив порядок следования элементов.
21) В массиве { aj }, j = 1, 2, ... 10 есть хотя бы один отрицательный
элемент. Вычислить произведение элементов массива до первого отрицательного.
22) В массиве { aj }, j = 1, 2, ... 8 есть хотя бы один нуль. Вычислить
произведение массива до первого нуля
173
5 Лабораторная работа № 5
23) .В массиве { aj }, j = 1, 2, ... 8 есть хотя бы один отрицательный элемент. Вычислить сумму элементов массива до первого отрицательного
элемента.
24) В массиве { aj }, j = 1, 2, ... 8 есть хотя бы один ноль. Вычислить
сумму элементов массива до первого нуля.
25) В массиве { aj }, j = 1, 2, ... 10 есть положительные и отрицательные
элементы. Вычислить произведение положительных элементов.
26) В массиве { aj }, j = 1, 2, ... 10 подсчитать количество элементов, больших 3
27) Найти сумму первых чисел последовательности a1 , a2 , . . . an , произведение
которых не превосходит заданного числа M.
28) Из чисел a1 , a2 , . . . an выбрать те, которые больше по модулю заданного
числа c, и образовать из них новый массив, сохранив порядок следования
элементов.
3. Составить программу с использованием динамических массивов для решения задачи на переупорядочивание элементов массива. В качестве алгоритма
сортировки использовать метод быстрой сортировки массива.
Номер варианта определяется по номеру студента по списку преподавателя.
Индивидуальное задание № 2.
Вариант:
1) Массив содержит 2n чисел. Из суммы первых n его элементов вычесть
сумму последних n элементов.
2) Даны два массива разных размеров. Определить, какие элементы первого
массива и сколько раз встречаются во втором массиве.
3) Даны два целочисленных массива одинакового размера. Получить третий
массив того же размера, каждый элемент которого равен большему из
соответствующих элементов данных массивов.
4) Задан массив С, содержащий m чисел. Составить программу формирования
массивов А и В, включая в массив А четные по номеру элементы массива С
в порядке их следования, а в массив В - нечетные.
5) Заданы два массива А и В, содержащие по n чисел. Составить программу
формирования массива С, включая в него сначала все элементы массива
А, затем все элементы массива В.
6) Числовой массив a1 , a2 , . . . an упорядочен по возрастанию. Известно, что
число х принадлежит отрезку числовой оси, вмещающему заданный массив.
Определить номер k, для которого ak − 1 < x ≤ ak .
7) Заменить отрицательные элементы в числовом массиве из n чисел (n>10)
их квадратами, оставив остальные без изменения.
174
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
8) В заданном массиве найти среднее арифметическое положительных чисел,
средне арифметическое отрицательных чисел и число нулей.
9) В массиве из 2n чисел найти сумму квадратов элементов с четными индексами и сумму кубов элементов с нечетными индексами.
10) Транспонировать массив, т.е. по a1 , a2 , . . . an сформировать n , n−1 , . . . , a1 .
11) Из заданного целочисленного массива удалить все повторяющиеся элементы, оставив только их первые вхождения, т.е. из заданного массива
получить новый массив, состоящий из различных целых чисел.
12) Заменить отрицательные числа в массиве их квадратами, оставив остальные без изменения.
13) В заданном массиве найти среднее арифметическое положительных чисел,
среднее арифметическое отрицательных чисел и число нулей.
14) В массиве из 2n чисел найти сумму квадратов элементов с четными индексами и сумму кубов элементов с нечетными индексами.
15) Из чисел a1 , a2 , . . . an выбрать те, которые больше по модулю заданного
числа с, и образовать из них новый массив, сохранив порядок следования
элементов.
16) Из массива целых чисел составить три других, в первый из которых
записать числа, кратные 5, во второй - числа, кратные 7, а в третий остальные числа.
17) Задан массив из 100 целых случайных чисел, принадлежащих промежутку
[0, 100]. Найти сумму тех элементов массива, которые больше 15, но меньше
45, а также вычислить количество этих элементов.
18) В линейном массиве заменить все элементы на число m (m - индекс максимального элемента).
19) Дан массив, состоящий как из положительных, так и отрицательных чисел.
Нужно сначала записать положительные числа, а затем отрицательные
в том же порядке, как они были расположены в исходном массиве. Если
есть нули, записать их в последнюю очередь.
20) Найти сумму элементов данного массива. Разделить каждый элемент
исходного массива на полученное значение.
21) Вычислить сумму и разность массивов одного размера.
22) Найти среднее арифметическое значение элементов заданного массива.
Преобразовать исходный массив, вычитая из каждого элемента среднее
значение.
23) Даны два массива одинакового размера. Рассматривая их как арифметические векторы, найти длины этих векторов и их скалярное произведение.
175
5 Лабораторная работа № 5
24) Заданы два массива разных размеров. Объединить их в один массив,
включив второй массив между k-ым и (k + 1)-ым элементами первого (k
задано).
25) Вычесть из положительных элементов данного массива элемент с номером
k1 а к отрицательным элементам прибавить элемент с номером k2 . Нулевые
элементы заменить 1. Номера k1 и k2 вводятся с клавиатуры.
26) К четным элементам целочисленного массива прибавить данное число а, а
из элементов с четными номерами вычесть данное число b.
27) Дан первый член геометрической прогрессии и ее знаменатель. Сформировать одномерный массив, элементами которого служат первые n членов
этой прогрессии.
28) Вставить одно и то же число, введенное с клавиатуры, перед каждым
отрицательным элементом заданного целочисленного массива.
29) Дан массив четного размера. Поменять местами его половины следующим образом: первый элемент - с последним, второй - с предпоследним
элементом и т.д.
Индивидуальное задание № 3.
Инициализировать массив при помощи C-функций (malloc, realloc). Реализовать
в отдельной функции пузырьковую сортировку массива, используя функцию
сравнения двух элементов, также реализованною самостоятельно.
176
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Что является указателем в языке C++?
2. Какие виды указателей существуют в языке C++?
3. Каким образом осуществляется инициализация указателя в языке C++?
4. Какие существуют способы инициализации указателя?
5. Время жизни динамических переменных.
6. С помощью какой языковой конструкции выделяется память под динамический
массив в языке C++?
7. Какие операции можно выполнять с указателями?
8. Что является ссылкой в языке C++?
9. Формат объявления ссылки.
10. К чему приводит операция над ссылкой?
177
5 Лабораторная работа № 5
5.7 Пример выполнения лабораторной работы № 5:
5.7.1 Индивидуальное задание № 1:
1.1. Постановка задачи:
Составить программу с использованием динамических массивов для решения
задачи.
Задача: составить программу, выдающую индексы заданного элемента или
сообщающую, что такого элемента в массиве нет.
1.2. UML-диаграмма:
178
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.3. Листинг программы:
// Лабораторная работа № 5
// Индивидуальное задание № 1
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
const int N = 1000;
int n, i, a;
float *C = new float[N];
bool flag = false;
cout<<"Лабораторная работа № 5\n";
cout<<"\nНемо А.А., 1-18\n";
cout<<"\nВариант № 6\n";
cout<<"\n\nИндивидуальное задание № 1:\n";
cout<<"\nСоставить программу с использованием динамических\n";
cout<<"\nмассивов для решения задачи.\n";
cout<<"\n\nЗадача: составить программу, выдающую"
<<"␣индексы заданного элемента\n";
cout<<"\nили сообщающую, что такого элемента"
<<"␣в массиве нет.\n";
cout<<"\n\nРабота программы:\n";
A:
cout<<"\nВведите количество элементов: ";
cin >> n;
if(n <= 0 || n > N)
{
cout<<"\nНеверный размер массива!"<< "\n";
goto A;
}
cout<<"\nВведите элементы массива: \n\n";
for(i = 0; i < n; i++) cin >> C[i];
if(false)
{
B:
cout<<"\nТакого элемента в массиве нет!"<< "\n";
179
5 Лабораторная работа № 5
}
cout<<"\nВведите элемент массива, индексы которого хотите узнать: ";
cin >> a;
for(i = 0; i < n; i++)
{
if (C[i] == a)
{
cout<<"\nИндекс заданного элемента: ";
cout << "[" << i << "]" << "\n";
flag = true;
}
else if((i+1) == n && flag == false)
goto B;
}
delete[] C;
getchar();
return 0;
}
180
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.4. Результаты работы программы:
181
5 Лабораторная работа № 5
5.7.2 Индивидуальное задание № 2:
2.1. Постановка задачи:
Составить программу с использованием динамических массивов для решения
задачи на переупорядочивание элементов массива. В качестве алгоритма сортировки
использовать метод быстрой сортировки массива. В одномерном массиве, состоящем
из n вещественных элементов, вычислить:
1. максимальный по модулю элемент массива;
2. сумму элементов массива, расположенных между первым и вторым положительными элементами.
Преобразовать массив таким образом, чтобы элементы, равные нулю, располагались
после всех остальных.
2.2. UML-диаграмма:
182
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.3. Листинг программы:
#include <iostream>
using namespace std;
int sortirovka(float array[],int numberel)
{
int j=0;
float sum=0;
cout<<endl;
/********Алгоритм быстрой сортировки*******/
float middle,temp;
int *stackl=new int [numberel], *stackr=new int [numberel];
int sp,i,left,right;
sp=1;
stackl[1]=0; stackr[1]=numberel-1;
while(sp>0)
{
left=stackl[sp];
right=stackr[sp];
sp--;
while(left<right)
{
i=left;j=right;
middle=array[(left+right)/2];
while(i<j)
{
while(array[i]<middle) i++;
while(middle<array[j]) j--;
if (i<=j)
{
temp=array[i];
array[i]=array[j];
array[j]=temp;
i++;j--;
}
}
if (i<right)
{
sp++;
stackl[sp]=i;
stackr[sp]=right;
183
5 Лабораторная работа № 5
}
right=j;
}
}
cout <<"Результат быстрой сортировки: "<<endl;
for(i=0;i<numberel;i++) cout << array[i]<<"␣";
cout<<endl;
delete [] stackl;
delete [] stackr;
/*******Сумма первых неотрицательных чисел******/
sum=0;
for(int i=0;i<numberel;++i)
{
if (array[i]>0)
{
while (array[i]>0 && i<numberel)
{
sum+=array[i];
++i;
}
break;
}
}
cout<<"Сумма первых неотрицательных чисел = "<<sum<<endl;
j=0;
cout<<endl;
for(int i=0;i<numberel;++i)
if (array[i]!=0) {array[j]=array[i]; ++j;}
for(int i=j;i<numberel;++i)
array[i]=0;
/*********Массив с нулями в конце************/
cout<<"Массив с нулями в конце: "<<endl;
cout<<endl;
for(int i=0;i<numberel;++i)
{
cout<<array[i]<<"\t";
}
cout<<endl;
delete [] array;
return 0;
184
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
}
int main()
{
int metod=0;
int n=13,j=0;
cout<<"Способ заполнения массива: "<<endl;
cout<<"\t1-\n";
cout<<"\t2-Случайное заполнение\n";
cin>>metod;
if (metod==1)
{
cout<<"Введите число элементов ";
cin>>n;
float *m=new float [n];
cout<<endl;
for(int i=0;i<n;++i)
{
++j;
cout<<"Введите "<<j<<"␣элемент массива: ";
cin>>m[i];
}
sortirovka(m,n);
}
if (metod==2)
{
cout<<"генерация массива"<<endl;
float *l=new float [n];
for(j=0;j<n;++j)
l[j]=rand() %13;
cout<<"Полученный массив"<<endl;
for(j=0;j<n;++j)
cout<<l[j]<<"\t";
cout<<endl;
sortirovka(l,n);
}
return 0;
}
185
5 Лабораторная работа № 5
2.4. Результаты работы программы:
186
Лабораторная работа № 6
Двумерные массивы в языке C++
Цель работы и содержание
закрепление знаний о двумерных массивах, составление программ с двумерными
массивами.
Ход работы
6.1 Основные сведения о двумерных массивах в языке
С++.
Двумерный массив представляется в C++ как массив, состоящий из массивов.
Для этого при описании в квадратных скобках указывается вторая размерность.
Если массив определяется с помощью операторов описания, то обе его размерности
должны быть константами или константными выражениями, поскольку инструкции по выделению памяти формируются компилятором до выполнения программы.
Например:
int a[3][5]; // Целочисл. матрица из 3 строк и 5 столбцов
Массив хранится по строкам в непрерывной области памяти:
a00 , a01 , a02 , a03 , a04
/––- 0-я строка ––-/
a10 , a11 , a12 , a13 , a14
/––- 1-я строка ––-/
a20 , a21 , a22 , a23 , a24
/––- 2-я строка ––-/
Строки массива ничем не отделены одна от другой. В памяти сначала располагается одномерный массив а[0], представляющий собой нулевую строку массива а,
затем - массив а[1], представляющий собой первую строку массива а, и т. д. Количество элементов в каждом из этих массивов равно длине строки, то есть количеству
столбцов в матрице. При просмотре массива от начала в первую очередь изменяется
правый индекс (номер столбца).
187
6 Лабораторная работа № 6
Для доступа к отдельному элементу массива применяется конструкция вида
а[i][j], где i (номер строки) и j (номер столбца) - выражения целочисленного типа.
Каждый индекс может изменяться от 0 до значения соответствующей размерности,
уменьшенной на единицу. Первый индекс всегда воспринимается как номер строки,
второй - как номер столбца, независимо от имени переменной.
Можно обратиться к элементу массива и другими способами:
*(*(a + i) + j) или *(a[i] + j).
Они приведены для лучшего понимания механизма индексации, поскольку здесь
в явном виде записаны те же действия, которые генерируются компилятором при
обычном обращении к массиву. Рассмотрим их подробнее.
Допустим, требуется обратиться к элементу, расположенному на пересечении
второй строки и третьего столбца - а[2][3]. Как и для одномерных массивов, имя
массива а представляет собой константный указатель на начало массива. В данном
случае это массив, состоящий из трех массивов. Сначала требуется обратиться ко
второй строке массива, то есть одномерному массиву а[2]. Для этого надо прибавить к адресу начала массива смещение, равное номеру строки, и выполнить
разадресацию: *( а + 2). При сложении указателя с константой учитывается длина
адресуемого элемента, поэтому на самом деле прибавляется число 2, умноженное
на длину элемента, то есть 2 * (5 * sizeof(int)), поскольку элементом является
строка, состоящая из 5 элементов типа int.
Далее требуется обратиться к третьему элементу полученного массива. Для
получения его адреса опять применяется сложение указателя с константой 3 (на самом
деле прибавляется 3 * sizeof(int)), а затем применяется операция разыменования
для получения значения элемента: *(*(а + 2) + 3).
При описании массива можно задать начальные значения его элементов. Их
записывают в фигурных скобках. Элементы массива инициализируются в порядке
их расположения в памяти. Например, оператор
int [3][5] = {1, 2, 1, 3, 5, 2, 3, 4, 5, 1, 1, 3, 2, 6, 1};
определяет матрицу со следующими значениями элементов:
1 2 1 3 5 2 3 4 5 1 1 3 2 6 1
Если количество значений в фигурных скобках превышает количество элементов в массиве, при компиляции будет выдано сообщение об ошибке. Если значений
меньше, оставшиеся элементы массива инициализируются значением по умолчанию
(для основных типов это 0). Можно задавать начальные значения не для всех элементов массива. Для этого список значений констант для каждой строки заключается в
дополнительные фигурные скобки. Вот, например, как заполнить единицами нулевой
и первый столбцы приведенного выше массива:
int [3][5] = {{1, 1}, {1, 1}, {1, 1}};
188
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Остальные элементы массива обнуляются. При явном задании хотя бы одного инициализирующего значения для каждой строки количество строк массива можно
не задавать; память будет выделена под столько строк, сколько серий значений в
фигурных скобках указано в списке, например:
int [][5] = {{1, 1, 7, 7, 7}, {1, 1, 0 }, {1, 1, 2, 2, 2}};
Пример 6.1.
Среднее арифметическое и количество положительных элементов
Написать программу, которая для целочисленной матрицы 10 × 20 определяет
среднее арифметическое ее элементов и количество положительных элементов в
каждой строке.
Алгоритм решения: для вычисления среднего арифметического элементов
массива требуется найти их общую сумму, после чего разделить ее на количество
элементов. Порядок просмотра массива (по строкам или по столбцам) роли не
играет. Определение количества положительных элементов каждой строки требует
просмотра матрицы по строкам. Обе величины вычисляются при одном просмотре
матрицы. UML-диаграмма этого алгоритма приведена на рисунке 6.1.
Рис. 6.1: UML-диаграмма деятельности для для примера 6.1
189
6 Лабораторная работа № 6
Листинг 6.1
#include <iostream>
using namespace std;
int main()
{
const int nrow = 10, ncol = 20;
int a[nrow][ncol];
int i, j;
cout << "Введите элементы массива:" << endl;
for (i = 0; i <
for (j = 0;
for (i = 0; i <
{
for ( j= 0;
cout <<
<<
}
nrow; i++)
j < ncol; j++) cin >> a[i][j];
nrow; i++)
j < ncol; j++)
setw(4) << a[i][j]
"␣" << endl;
int n_pos_el;
float s = 0;
for (i = 0; i < nrow; i++)
{
n_pos_el = 0;
for (j = 0; j < ncol; j++)
{
s += a[i][j];
if (a[i][j] > 0) n_pos_el++;
}
cout << "Строка:"<< i << "количество:"
<< n_pos_el << endl;
}
s /= nrow * ncol;
cout << "Среднее арифметическое:" << s << endl;
}
Размерности массива заданы именованными константами, что позволяет легко
их изменять. Для упрощения отладки рекомендуется задать небольшие значения этих
величин или приготовить тестовый массив в текстовом файле и изменить программу
190
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
так, чтобы она читала значения из файла.
Следует составлять тестовые примеры таким образом, чтобы строки массива
содержали разное количество отрицательных элементов (ни одного элемента, один и
более элементов, все элементы).
При вычислении количества положительных элементов для каждой строки
выполняются однотипные действия: обнуление счетчика, просмотр каждого элемента
строки и сравнение его с нулем, при необходимости увеличение счетчика на единицу,
а после окончания вычислений - вывод результирующего значения счетчика.
Здесь следует обратить внимание на два момента. Во-первых, требуется
еще до написания алгоритма решить, каким образом будут храниться результаты.
Для хранения среднего арифметического необходима одна простая переменная вещественного типа. Количество же положительных элементов для каждой строки свое,
и в результате получается столько значений, сколько строк в матрице. В данном примере мы можно отвести для хранения этих значений одну-единственную переменную
целого типа, поскольку они вычисляются последовательно, после чего выводятся
на экран. Однако в других задачах эти значения могут впоследствии потребоваться
одновременно. В этом случае для их хранения придется описать целочисленный
массив с количеством элементов, равным количеству строк матрицы.
Второй важный момент - место инициализации суммы и количества. Сумма
обнуляется перед циклом просмотра всей матрицы, а количество положительных
элементов - перед циклом просмотра очередной строки, поскольку для каждой строки
его вычисление начинается заново.
Следует записывать операторы инициализации накапливаемых в цикле величин
непосредственно перед циклом, в котором они вычисляются.
При написании вложенных циклов необходимо следить за отступами. Все операторы одного уровня вложенности должны начинаться в одной и той же колонке. Это
облегчает чтение программы и, следовательно, поиск ошибок. По вопросу расстановки фигурных скобок существуют разные мнения специалистов. В настоящее время
наиболее распространенными являются два стиля: стиль 1TBS (One True Bracing
Style), которого придерживались основоположники языка С - Б. Керниган (Brian
Kerni-ghan) и Д. Ритчи (Dennis Ritchie), и стиль Алмена (Eric Allman).
6.2 Динамические массивы.
В динамической области памяти можно создавать двумерные массивы с помощью операции new или функции malloc. Остановимся на первом варианте, поскольку
он более безопасен и прост в использовании.
При выделении памяти сразу под весь массив количество строк (самую левую
размерность) можно задавать с помощью переменной или выражения, а количество
столбцов должно быть константным выражением, то есть явно определено до выполнения программы. После слова new записывается тип создаваемого массива, а
затем - его размерности в квадратных скобках (аналогично описанию «обычных»,
нединамических массивов), например:
191
6 Лабораторная работа № 6
int n;
const int m = 5;
cin >> n;
int (*a)[m] = new int [n][m];
//1
int ** b = (int **) new int [n][m]; //2
В этом фрагменте показано два способа создания динамического массива.
В операторе 1 адрес начала выделенного с помощью new участка памяти
присваивается переменной а, определенной как указатель на массив из m элементов
типа int. Именно такой тип значения возвращает в данном случае операция new.
Скобки необходимы, поскольку без них конструкция интерпретировалась бы как
массив указателей. Всего выделяется n элементов.
В операторе 2 адрес начала выделенного участка памяти присваивается переменной b которая описана как «указатель на указатель на int, поэтому перед
присваиванием требуется выполнить преобразование типа.
По стандарту в этом случае рекомендуется применять другую операцию преобразования типа, но старые компиляторы могут ее не поддерживать:
int ** b = reinterpret_cast <int-**> (new int [n][m]);
Обращение к элементам динамических массивов производится точно так же,
как к элементам «обычных», с помощью конструкции вида a[i][j].
Поскольку для доступа к элементу массива применяется две операции разадресации, то переменная, в которой хранится адрес начала массива, должна быть
указателем на указатель.
Более универсальный и безопасный способ выделения памяти под двумерный массив, когда обе его размерности задаются на этапе выполнения программы,
приведен ниже:
int nrow, ncol;
cout << "Введите количество строк и столбцов:";
cin >> nrow >> ncol;
int **a = new int *[nrow];
//1
for(int i = 0; i < nrow; i++)
//2
a[i] = new int [ncol];
//3
В операторе 1 объявляется переменная типа «указатель на указатель на int»
и выделяется память под массив указателей на строки массива (количество строк nrow). В операторе 2 организуется цикл для выделения памяти под каждую строку
массива. В операторе 3 каждому элементу массива указателей на строки присваивается адрес начала участка памяти, выделенного под строку дву- мерного массива.
Каждая строка состоит из ncol элементов типа int (рисунок 6.2).
192
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 6.2: Схема динамической области памяти, выделяемой под массивы
Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции delete [].
Пример 6.2.
Номер столбца из положительных элементов
Написать программу, которая для прямоугольной целочисленной матрицы определяет номер самого левого столбца, содержащего только положительные элементы.
Если такого столбца нет, вывести сообщение.
UML-диаграмма алгоритма приведена на рисунке 6.3. Для решения этой задачи
матрицу необходимо просматривать по столбцам. При этом быстрее меняется первый
индекс (номер строки). Сделать вывод о том, что какой-либо столбец содержит
только положительные элементы, можно только после просмотра столбца целиком;
зато если в процессе просмотра встретился отрицательный элемент, можно сразу
переходить к следующему столбцу.
Эта логика реализуется с помощью переменной-флага allposit, которая перед началом просмотра каждого столбца устанавливается в значение true, а при
нахождении отрицательного элемента «опрокидывается» в false. Если все элементы столбца положительны, флаг не опрокинется и останется истинным, что будет
являться признаком присутствия в матрице искомого столбца. Если столбец найден,
просматривать матрицу дальше не имеет смысла, поэтому выполняется выход из
цикла и вывод результата.
Листинг 6.2
#include <iostream>
int main()
{
193
6 Лабораторная работа № 6
Рис. 6.3: UML-диаграмма деятельности для примера 6.2
int nrow, ncol;
cout << "Введите количество строк и столбцов:";
cin >> nrow >> ncol; // ввод размерности массива
int i, j;
int **a = new int *[nrow]; /* выделение памяти
под массив
*/
for(i = 0; i < nrow; i++) a[i] = new int[ncol];
cout << "Введите элементы массива:" << endl;
for(i = 0; i < nrow; i++)
for(j = 0; j < ncol; j++)
cin >> a[i][j]; // ввод массива
setw(4) <<
for(i = 0; i < nrow; i++)
{
for(j = 0; j < ncol; j++)
cout << a[i][j] << "␣";
194
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
cout << endl;
}
int num = -1;
bool all_posit;
for (j = 0; j< ncol; j++) // просмотр по столбцам
{
all_posit = true;
for (i = 0; i < nrow; i++)
if (a[i][j]<0)
{
all_posit = false;
break;
}
if (all_posit)
{
num = j;
break;
}
}
if (-1 == num )
cout << "␣Столбцов нет " << endl;
else
cout << "␣Номер столбца: " << num << endl;
return 0;
}
В программе необходимо предусмотреть случай, когда ни один столбец не
удовлетворяет условию. Для этого переменной nun, в которой будет храниться номер
искомого столбца, присваивается начальное значение, не входящее в множество значений, допустимых для индекса, например -1. Перед выводом результата его значение
анализируется. Если оно после просмотра матрицы сохранилось неизменным, то есть
осталось равным -1, то столбцов, удовлетворяющих заданному условию, в матрице
нет.
Можно обойтись без анализа переменной num, да и вообще без этой переменной,
если вывести номер столбца сразу после его определения, после чего завершить
программу. Этот вариант приведен ниже.
Текстовые файлы очень удобно использовать для отладки программ, требующих
ввода хотя бы нескольких величин, - ведь, как правило, программу не удается
написать сразу без ошибок, а многократный ввод одних и тех же значений замедляет
195
6 Лабораторная работа № 6
процесс отладки и может сильно испортить настроение. Кроме того, при подготовке
данных в файле до выполнения программы можно спокойно продумать тестовые
примеры для исчерпывающей проверки правильности программы.
Листинг 6.3
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream fin("input.txt",ios :: in);
if (!fin)
{
cout << "Файл input.txt не найден."
<< endl;
return 1;
}
ofstream fout("output.txt");
if (!fout)
{
cout << "Невозможно открыть файл для записи."
<< endl;
return 1;
}
int nrow, ncol;
fin >> nrow >> ncol;
int i, j;
int **a = new int *[nrow];
for(i = 0; i < nrow; i++) a[i] = new int[ncol];
for (i = 0; i < nrow; i++)
for(j = 0; j < ncol; j++) fin >> a[i][j];
for (i = 0; i < nrow; i++)
{
for(j = 0; j < ncol; j++) fout <<
setw(4) << a[i][j] << "␣";
196
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
fout << endl;
}
bool all_posit;
for (j = 0; j < ncol; j++)
{
all_posit = true;
for (i = 0; i < nrow; i++)
if (a[i][j] < 0)
{
all_posit = false; break;
}
if (all_posit)
{
fout << "Номер столбца: " << j;
cout << "␣Работа завершена " << endl;
return 0;
}
}
fout << "␣Столбцов нет ";
cout << "␣Работа завершена " << endl;
return 0;
}
Ввод размерности массива и его элементов выполняется из файла input.txt,
расположенного в том же каталоге, что и программа, а результаты выводятся в файл
output.txt. В программе определены объект fin класса входных файловых потоков
и объект fout класса выходных файловых потоков. Файловые потоки описаны в
заголовочном файле <fstream.h>. Работа с этими объектами аналогична работе со
стандартными объектами cin и cout.
Предполагается, что файл с именем input.txt находится в том же каталоге,
что и текст программы.
После определения объектов проверяется успешность их создания. Это особенно
важно делать для входных файлов, чтобы исключить вероятность ошибки в имени
или местоположении файла.
Если программа завершается успешно, то на экран выводится сообщение «Работа завершена».
Входной файл input.txt можно создать в любом текстовом редакторе. Он
должен существовать до первого запуска программы. На расположение и формат
исходных данных в файле никаких ограничений не накладывается.
197
6 Лабораторная работа № 6
Пример 6.3.
Упорядочивание строк матрицы
Написать программу, которая упорядочивает строки прямоугольной цело- численной матрицы по возрастанию сумм их элементов.
I. Исходные данные, результаты и промежуточные величины.
Исходные данные. Поскольку размерность матрицы неизвестна, придется использовать динамический массив элементов целого типа. Ограничимся типом
int.
Результаты. Результатом является та же матрица, но упорядоченная. Это
значит, что не следует заводить для результата новую область памяти, а необходимо упорядочить матрицу in situ, то есть на том же месте.
Промежуточные величины. Кроме конечных результатов, в любой программе
есть промежуточные, а также служебные переменные. Следует выбрать их тип
и способ хранения.
Если требуется упорядочить матрицу по возрастанию сумм элементов ее строк,
эти суммы надо вычислить и где-то хранить. Поскольку все они потребуются
при упорядочивании, их надо записать в массив, количество элементов которого
соответствует количеству строк матрицы, а i-й элемент содержит сумму элементов i-й строки. Количество строк заранее неизвестно, поэтому этот массив
также должен быть динамическим. Сумма элементов строки может превысить
диапазон значений, допустимых для отдельного элемента строки, поэтому для
элемента этого массива надо выбрать тип 1ong.
II. Алгоритм работы программы.
Для сортировки строк воспользуемся одним из самых простых методов - методом выбора. Он состоит в том, что из массива выбирается наименьший элемент
и меняется местами с первым элементом, затем рассматриваются элементы, начиная со второго, и наименьший из них меняется местами со вторым элементом
и так далее п - 1 раз (при последнем проходе цикла при необходимости меняются
местами предпоследний и последний элементы массива). Одновременно с обменом элементов массива выполняется и обмен значений двух соответствующих
строк матрицы. Любой алгоритм можно первоначально разбить на этапы ввода
исходных данных, вычислений и вывода результата. Вычисление в данном
случае состоит из двух шагов: формирование сумм элементов каждой строки
и упорядочивание матрицы. Упорядочивание состоит в выборе наименьшего
элемента и обмене с первым из рассматриваемых. Разветвленные алгоритмы и
алгоритмы с циклами полезно представить в виде обобщенной блок-схемы.
III. Когда алгоритм полностью прояснился, можно переходить к написанию программы. Одновременно с этим продумываются и подготавливаются тестовые
примеры. Рекомендуется придумать переменным понятные имена и сразу же
при написании аккуратно форматировать текст программы, чтобы по положению оператора было видно, на каком уровне вложенности он находится.
198
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Функционально завершенные части алгоритма отделяются пустой строкой,
комментарием или хотя бы комментарием.
Сначала пишется и отлаживается фрагмент, содержащий ввод исходных данных.
Затем можно переходить к следующему функционально законченному фрагменту
алгоритма. Для отладки полезно выполнять программу по шагам с наблюдением
значений изменяемых величин. Все популярные оболочки предоставляют такую
возможность. Ниже приведен текст программы сортировки.
Листинг 6.4
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream fin("input.txt", ios::in);
if (!fin)
{
cout << "Файл input.txt не найден."
<< endl; return 1;
}
int nrow, ncol;
fin >> nrow >> ncol; // ввод размерности массива
int i, j;
int **a = new int *[nrow]; /* выделение памяти
под массив
*/
for(i = 0; i < nrow; i++) a[i] = new int [ncol];
for (i = 0; i < nrow; i++) // ввод массива
for (j = 0; j < ncol; j++) fin >> a[i][j];
long *sum = new long [nrow] /* массив сумм
элементов строк */
for (i = 0; i < nrow; i++)
{
sum[i] = 0;
for (j = 0; j < ncol; j++) sum[i] += a[i][j];
}
for (i = 0; i < nrow; i++) // контрольный ввод
{
for (j = 0; j < ncol; j++)
199
6 Лабораторная работа № 6
cout << setw(4) << a[i][j] << "␣";
cout << "|␣" << sum[i] << endl;
}
cout << endl;
long buf_sum;
int nmin, buf_a;
for (i = 0; i < nrow - 1; i++) // упорядочивание
{
nmin = i;
for (j = i + 1; j < nrow; j++)
if (sum[j] < sum[nmin]) nmin = j;
buf_sum = sum[i];
sum[i] = sum[nmin];
sum[nmin] = buf_sum;
for (j = 0; j < ncol; j++)
{
buf_a = a[i][j];
a[i][j] = a[nmin][j];
a[nmin][j] = buf_a;
}
}
for (i = 0; i < nrow; i++) /* вывод упорядоченной матрицы */
{
for (j = 0; j < ncol; j++)
cout << setw(4) << a[i][j] << "␣";
cout << endl;
}
return 0;
}
В программе используются две буферные переменные: buf_sum, через которую
осуществляется обмен двух значений сумм, имеет такой же тип, что и сумма, а для
обмена значений элементов массива определена переменная buf_a того же типа, что
и элементы массива.
Как и в предыдущем примере, данные читаются из файла. Рекомендуется
пользоваться именно этим способом, а не стандартным вводом, поскольку при формировании файла легче продумать, какие значения лучше взять для исчерпывающего
тестирования программы. В данном случае для первого теста следует подготовить
массив не менее чем из четырех строк с небольшими значениями элементов для того,
200
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
чтобы можно было в уме проверить, правильно ли вычисляются суммы.
Для контроля вместе с исходным массивом рядом с каждой строкой выводится
сумма еѐ элементов, отделенная вертикальной чертой.
В качестве второго тестового примера рекомендуется ввести значения элементов
массива, близкие к максимальным для типа int. Дополнительно следует проверить,
правильно ли упорядочивается массив из одной и двух строк и столбцов, поскольку
многие ошибки при написании циклов связаны с неверным указанием их граничных
значений.
Рекомендации по порядку создания программы.
1. Выбрать тип и способ хранения в программе исходных данных, результатов и
промежуточных величин.
2. Записать алгоритм сначала в общем виде, стремясь разбить его на простую
последовательность шагов, а затем детализировать каждый шаг.
3. Написать программу. При написании программы рекомендуется:
- давать переменным понятные имена;
- не пренебрегать содержательными комментариями;
- использовать промежуточную печать вычисляемых величин в удобном
формате;
- при написании вложенных циклов следить за отступами;
- операторы инициализации накапливаемых в цикле величин задавать непосредственно перед циклом, в котором они вычисляются.
4. Параллельно с написанием программы задать тестовые примеры, которые
проверяют все ветви алгоритма и возможные диапазоны значений исходных
данных. Исходные данные удобнее формировать в файле (по крайней мере,
при отладке), не забывая проверять в программе успешность его открытия.
Пример 6.4.
Сглаживание заданной вещественной матрицы, работа с файлами
Операция сглаживания матрицы дает новую матрицу того же размера, каждый элемент которой получается как среднее арифметическое имеющихся соседей
соответствующего элемента исходной матрицы.
Построить результат сглаживания заданной вещественной матрицы размером
10 на 10. В сглаженной матрице найти сумму модулей элементов, расположенных
выше главной диагонали. Ввод и вовод данных в программе осуществить с помощью
файла. UML-диаграмма этого алгоритма приведена на рисунке 6.4.
201
6 Лабораторная работа № 6
Рис. 6.4: UML-диаграмма деятельности для примера 6.4
Листинг 6.5
#include <iostream>
#include <fstream>
202
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
using namespace std;
int main()
{
const int nrow=10;
const int ncol=10;
ifstream fin("input.txt",ios::in);
if (!fin)
{
cout<<"Can’t␣find␣input.txt"<<endl;
return 1;
}
ofstream fout("output.txt");
if (!fout)
{
cout<<"Write␣falure:␣check␣permission"<<endl;
return 1;
}
int k,l,k1,l1,counter,i,j;
double sum,sumdiag=0; // заполнение массива из файла
double a[nrow][ncol];
cout << "Чтение данных из файла..." << endl;
for(i=0;i<nrow;i++)
for(j=0;j<ncol;j++) fin>>a[i][j];
double m[nrow][ncol];
cout<<"Обработка данных..."<<endl; // процедура сортировки
counter=0;sum=0;l=0;l1=0;
for(i=0;i<nrow;i++)
for(j=0;j<ncol;j++)
{
k=i-1;
k1=i+1;
if (k<0) k++;
if (k1>ncol-1) k1--;
if ((j<=ncol-1)&&(j>=ncol-i)) sumdiag = sumdiag
+ a[i][j];
for(k;k<=k1;k++)
{
l=j-1;
l1=j+1;
if (l<0) l++;
if (l1>ncol-1) l1--;
for(l;l<=l1;l++)
if ((k==i)&&(l==j)) continue;
203
6 Лабораторная работа № 6
else
{
sum=sum+a[k][l];
counter++;
}
}
m[i][j]=(float) sum/counter;
sum=0;
counter=0;
}
// запись отсортированного массива в файл
cout << "Запись результатов обработки в файл..." << endl;
for(i=0;i<nrow;i++)
{
for(j=0;j<ncol;j++)
fout<<setw(5)<<left<<setprecision(3)<<m[i][j]<<"␣";
fout<<endl;
}
fout << endl;
fout << "Сумма всех эл-ов ниже глав. диагонали:"
<< setw(5) << setprecision(9) << sumdiag << endl;
cout << "Обработка закончена!" << endl;
return 0;
}
204
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Пример 6.5.
Определение количества отрицательных элементов в тех строках
данной целочисленной прямоугольной матрицы, которые со- держат
хотя бы один нулевой элемент
Дана целочисленная прямоугольная матрица. Определить количество отрицательных элементов в тех строках, которые содержат хотя бы один нулевой элемент.
UML-диаграмма этого алгоритма приведена на рисунке 6.5.
Рис. 6.5: UML-диаграмма деятельности для примера 6.5
205
6 Лабораторная работа № 6
Листинг 6.6
#include <math.h>
#include <iostream>
using namespace std;
int main()
{
srand((unsigned)time(NULL));
int m,n,h,c,i,j,ch;
cout << "Лабораторная работа № 6\n";
cout << "\nЗадание:\nДана целочисленная прямоугольная матрица."
<< "Определить:\nколичество отрицательных элементов в тех"
<< "строках,\n которые содержат хотя бы один нулевой элемент.";
cout << "\n␣Введите размерность массива: \n";
cin >> n;
cin >> m;
int **a = new int *[n];
for(i = 0; i < n; i++) a[i] = new int [m];
cout << "\nВыберите способ ввода массива";
cout << "\n1.␣Ввести массив с клавиатуры."
<< "\n2.␣Заполнить массив случайным образом.\n";
cin >> ch;
if (ch==1)
{
cout << "\n␣Введите элементы массива\n"<<endl;
for(i = 0; i < n; i++)
for(j = 0; j < m; j++) cin >> a[i][j];
for(i = 0; i < n; i++)
{
for(j = 0; j < m; j++)
cout << a[i][j] << ’␣’;
cout << endl;
}
}
if (ch == 2)
{
206
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
for(i = 0;
for(j = 0;
for(i = 0;
{
for(j =
cout <<
cout <<
}
i < n; i++)
j < m; j++) a[i][j]=rand()%20;
i < n; i++)
0; j < m; j++)
a[i][j] << ’␣’;
endl;
}
h = 0;
for(i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
if (a[i][j] < 0) h++;
for (j = 0; j < m; j++)
if (a[i][j] == 0)
{
cout << "\nНомер строки\n" << i;
cout << "\nЧисло отрицательных элементов\n" << h;
h = 0;
}
else
cout << "\nНет нулевых элементов\n";
}
return 0;
}
207
6 Лабораторная работа № 6
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
208
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
6.3 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Составить программу с использованием двумерных локальных массивов для
решения задачи. Размерности локальных массивов задавать именованными
константами, значения элементов массива - в списке инициализации. Номер
варианта определяется по номеру студента по списку преподавателя.
Индивидуальное задание № 1.
Вариант:
1) Дана целочисленная прямоугольная матрица. Определить:
a) количество строк, не содержащих ни одного нулевого элемента;
b) максимальное из чисел, встречающихся в заданной матрице более
одного раза.
2) Дана целочисленная прямоугольная матрица. Определить количество
столбцов, не содержащих ни одного нулевого элемента.
Характеристикой строки целочисленной матрицы назовем сумму ее положительных четных элементов. Переставляя строки заданной матрицы,
расположить их в соответствии с ростом характеристик.
3) Дана целочисленная прямоугольная матрица. Определить:
a) количество столбцов, содержащих хотя бы один нулевой элемент;
b) номер строки, в которой находится самая длинная серия одинаковых
элементов.
4) Дана целочисленная квадратная матрица. Определить:
a) произведение элементов в тех строках, которые не содержат
отрицатель- ных элементов;
b) максимум среди сумм элементов диагоналей, параллельных главной
диагонали матрицы.
5) Дана целочисленная квадратная матрица. Определить:
a) сумму элементов в тех столбцах, которые не содержат отрицательных
элементов;
209
6 Лабораторная работа № 6
b) минимум среди сумм модулей элементов диагоналей, параллельных
по- бочной диагонали матрицы.
6) Для заданной матрицы размером 8 × 8 найти такие k, что k -я строка
матрицы совпадает с k-м столбцом.
Найти сумму элементов в тех строках, которые содержат хотя бы один
отрицательный элемент.
7) Характеристикой столбца целочисленной матрицы назовем сумму модулей
его отрицательных нечетных элементов. Переставляя столбцы заданной
матрицы, расположить их в соответствий с ростом характеристик. Найти
сумму элементов в тех столбцах, которые содержат хотя бы один отрицательный элемент.
8) Соседями элемента Aj в матрице назовем элементы Ak с i-1<k<i+1,
j-1<i<j+1, (k,i)/(i,j). Операция сглаживания матрицы дает новую матрицу того же размера, каждый элемент которой получается как среднее
арифметическое имеющихся соседей соответствующего элемента исходной матрицы. Построить результат сглаживания заданной вещественной
матрицы размером 7 × 7. В сглаженной матрице найти сумму модулей
элементов, расположенных ниже главной диагонали.
9) Элемент матрицы называется локальным минимумом, если он строго
меньше всех имеющихся у него соседей. Подсчитать количество локальных
минимумов заданной матрицы размером 10 × 10. Найти сумму модулей
элементов, расположенных выше главной диагонали.
10) Коэффициенты системы линейных уравнений заданы в виде прямоугольной матрицы. С помощью допустимых преобразований привести систему
к треугольному виду. Найти количество строк, среднее арифметическое
элементов которых меньше заданной величины.
11) Уплотнить заданную матрицу, удаляя из нее строки и столбцы, заполненные нулями. Найти номер первой из строк, содержащих хотя бы один
положительный элемент.
12) Осуществить циклический сдвиг элементов прямоугольной матрицы на
n элементов вправо или вниз (в зависимости от введенного режима), n
может быть больше количества элементов в строке или столбце.
13) Осуществить циклический сдвиг элементов квадратной матрицы размерности × N вправо на к элементов таким образом: элементы 1-й строки
сдвигаются в последний столбец сверху вниз, из него - в последнюю строку
справа налево, из нее - в первый столбец снизу вверх, из него - в первую
строку; для остальных элементов - аналогично.
14) Дана целочисленная прямоугольная матрица. Определить номер первого
из столбцов, содержащих хотя бы один нулевой элемент.
210
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Характеристикой строки целочисленной матрицы назовем сумму ее отрицательных четных элементов. Переставляя строки заданной матрицы,
расположить их в соответствии с убыванием характеристик.
15) Упорядочить строки целочисленной прямоугольной матрицы по возрастанию количества одинаковых элементов в каждой строке.
Найти номер первого из столбцов, не содержащих ни одного отрицательного элемента.
16) Путем перестановки элементов квадратной вещественной матрицы добиться того, чтобы ее максимальный элемент находился в левом верхнем углу,
следующий по величине - в позиции (2,2), следующий по величине - в
позиции (3,3) и т. д., заполнив таким образом всю главную диагональ.
Найти номер первой из строк, не содержащих ни одного положительного
элемента.
17) Дана целочисленная прямоугольная матрица. Определить:
a) количество строк, содержащих хотя бы один нулевой элемент;
b) номер столбца, в котором находится самая длинная серия одинаковых
элементов.
18) Дана целочисленная квадратная матрица. Определить:
a) сумму элементов в тех строках, которые не содержат отрицательных
элементов;
b) минимум среди сумм элементов диагоналей, параллельных главной
диа- гонали матрицы.
19) Дана целочисленная прямоугольная матрица. Определить:
a) количество положительных элементов в тех строках, которые не содержат нулевых элементов;
b) номера строк и столбцов всех седловых точек матрицы.
Примечание. Матрица имеет седловую точку AP если AP является минимальным элементом в i-й строке и максимальным в j-м столбце.
3. Составить программу с использованием двумерных динамических массивов
для решения задачи согласно варианту индивидуального задания № 1.
211
6 Лабораторная работа № 6
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Как представляется в C++ двумерный массив?
2. Где и каким образом хранится двумерный массив?
3. В каких пределах изменяются индексы двумерного массива?
4. Какими способами можно описать двумерный массив?
5. Какие действия выполняются для каждой строки при вычислении количества
положительных элементов?
6. В каком месте программы следует записывать операторы инициализации накапливаемых в цикле величин?
7. Каким образом в динамической области памяти можно создавать двумерные
массивы?
8. Способы создания динамического массива.
9. Каким образом производится обращение к элементам динамических массивов?
212
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
6.4 Пример выполнения лабораторной работы № 6:
6.4.1 Индивидуальное задание № 1:
1.1. Постановка задачи:
Составить программу с использованием двумерных локальных массивов для
решения задачи. Размерности локальных массивов задавать именованными константами, значения элементов массива - в списке инициализации.
Задача:
Дана целочисленная прямоугольная матрица1 . Определить:
1) сумму элементов в тех строках, которые содержат хотя бы один отрицательный
элемент;
2) номера строк и столбцов всех седловых точек матрицы.
1.2. UML-диаграмма:
1
Матрица имеет седловую точку As , если As является минимальным элементом в i-й строке и
максимальным в j-м столбце.
213
6 Лабораторная работа № 6
1.3. Листинг программы:
// Лабораторная работа № 6
// Индивидуальное задание № 1
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
const int nrow = 3, ncol = 5;
int A[nrow][ncol];
int i, j, summ;
cout<<"Лабораторная работа № 6\n"
<<"\nНемо А.А., 1-18\n"
<<"\nВариант № 6\n"
<<"\n\nИндивидуальное задание № 1:\n"
<<"\nСоставить программу с использованием двумерных локальных\n"
<<"\nмассивов для решения задачи. Размерности локальных\n"
<<"\n массивов задавать именованными константами, значения \n"
<<"\nэлементов массива - в списке инициализации.\n"
<<"\n\nЗадача: дана целочисленная прямоугольная матрица.\n"
<<"Определить: сумму элементов в тех строках, которые \n"
<<"\nсодержат хотя бы один отрицательный элемент;\n"
<<"\n2)␣номера строк и столбцов всех седловых точек матрицы.\n"
<<"\n\nПримечание: матрица A имеет седловую точку As, если As\n"
<<"\n␣является минимальным элементом в i-й строке и \n"
<<"\nмаксимальным в j-м столбце.\n"
<<"\n\nРабота программы:\n"
<<"\nВведите элементы массива: \n\n";
for (i = 0; i < nrow; i++)
{
for (j = 0; j < ncol; j++)
{
cout << "A[" << i << "][" << j << "]␣=␣";
cin >> A[i][j];
}
}
cout << "\n1)␣";
for (i = 0; i < nrow; i++)
214
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
bool flag = false;
summ = 0;
for (j = 0; j < ncol; j++)
{
summ += A[i][j];
if(A[i][j] < 0) flag = true;
}
if(flag == true)
{
cout << "Сумма элементов строки "<< i;
cout << "␣с отрицательным элементом"<< "␣=␣"
<< summ << "\n\n";
}
}
cout<<"\n2)␣";
for (i = 0; i < nrow; i++)
{
int min, max;
int tempValue; // временная переменная
min = 0;
tempValue = A[i][0]; // поиск минимального в строке элемента
for (j = 0; j < ncol; j++)
{
if(A[i][j] < tempValue)
{
min = j;
tempValue = A[i][j];
}
}
// поиск максимального в столбце элемента
max = 0;
tempValue = A[0][min];
for(j = 0; j < nrow; j++)
{
if(A[j][min] > tempValue)
{
215
6 Лабораторная работа № 6
max = j;
tempValue = A[j][min];
}
}
if(i == max)
{
cout <<"Седловая точка: "<< "As["
<< max <<"][" << min << "]";
}
}
return 0;
}
216
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
1.4. Результаты работы программы:
217
6 Лабораторная работа № 6
6.4.2 Индивидуальное задание № 2:
2.1. Постановка задачи:
Составить программу с использованием двумерных динамических массивов
для решения задачи индивидуального задания № 1.
2.2. UML-диаграмма:
218
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2.3. Листинг программы:
// Лабораторная работа № 6
// Индивидуальное задание № 2
#include <iostream>
#include "math.h"
using namespace std;
int main()
{
int nrow, ncol, i, j, summ;
cout<<"Лабораторная работа № 6\n";
cout<<"\nНемо А.А., 1-18\n";
cout<<"\nВариант № 6\n";
cout<<"\n\nИндивидуальное задание № 2:\n";
cout<<"\nСоставить программу с использованием двумерных\n";
cout<<"\n динамических массивов для решения задачи"
<<"␣индивидуального задания № 1.\n";
cout<<"\n\nРабота программы:\n";
cout<<"\nВведите количество строк и столбцов:\n\n"<< "i␣=␣" ;
cin >> nrow;
cout<< "j␣=␣";
cin >> ncol;
int **A = new int *[nrow];
cout<<"\nВведите элементы массива: \n\n";
for (i = 0; i < nrow; i++)
{
A[i] = new int[ncol];
for (j = 0; j < ncol; j++)
{
cout << "A[" << i << "][" << j << "]␣=␣";
cin >> A[i][j];
}
}
cout<<"\n1)␣";
for (i = 0; i < nrow; i++)
{
bool flag = false;
219
6 Лабораторная работа № 6
summ = 0;
for (j = 0; j < ncol; j++)
{
summ += A[i][j];
if(A[i][j] < 0) flag = true;
}
if(flag == true)
{
cout<<"Сумма элементов строки "<< i;
cout<<"␣с отрицательным элементом"<< "␣=␣"
<< summ << "\n\n";
}
}
cout<<"\n2)␣";
for (i = 0; i < nrow; i++)
{
int min, max;
int tempValue; // временная переменная
min = 0;
tempValue = A[i][0];
// поиск минимального в строке элемента
for (j = 0; j < ncol; j++)
{
if(A[i][j] < tempValue)
{
min = j;
tempValue = A[i][j];
}
}
// поиск максимального в столбце элемента
max = 0;
tempValue = A[0][min];
for(j = 0; j < nrow; j++)
{
if(A[j][min] > tempValue)
{
max = j;
tempValue = A[j][min];
}
}
220
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
if(i == max)
{
cout<< "Седловая точка: "<< "As[" << max
<< "][" << min << "]";
}
}
return 0;
}
221
6 Лабораторная работа № 6
2.4. Результаты работы программы:
222
Лабораторная работа № 7
Функции и перегрузка в языке C++
Цель работы и содержание
закрепление знаний о функциях и перегрузке, составление программ с функциями и
перегрузкой.
Ход работы
7.1 Основные сведения о функциях в языке С++.
Функция - это группа операторов, выполняющая законченное действие. К
функции можно обратиться по имени, передать ей значения и получить из нее
результат.
Функции нужны для упрощения структуры программы. Передача в функцию
различных аргументов позволяет, записав ее один раз, использовать многократно
для разных данных. Чтобы использовать функцию, не требуется знать, как она
работает - достаточно знать, как ее вызвать.
Для использования функции тоже требуется знать только ее интерфейс. Интерфейс грамотно написанной функции определяется ее заголовком, потому что в
нем указывается все, что необходимо для ее вызова: имя функции, тип результата, который она возвращает, а также сколько аргументов и какого типа ей нужно
передать.
Формат простейшего заголовка (прототипа) функции:
тип имя ([список параметров]);
В квадратных скобках записано то, что может быть опущено. Например, заголовок функции main обычно имеет вид:
int main( );
223
7 Лабораторная работа № 7
Это означает, что никаких параметров этой функции извне не передается, а
возвращает она одно значение типа int (код завершения). Функция может и не
возвращать никакого значения, в этом случае должен быть указан тип void. Вот, к
примеру, заголовок стандартной библиотечной функции, вычисляющей синус угла:
double sin (double);
Здесь записано, что функция имеет имя sin, вычисляет значение синуса типа
double, и для этого нужно передать ей аргумент типа double. А вот заголовок
функции memcpy, копирующей блок памяти длиной n байтов, начиная с адреса src,
по адресу dest:
void *memcpy (void *dest,const void *src,size_t n);
Эта функция возвращает указатель неопределенного типа на начало области
памяти, в которую выполнялось копирование. Какой именно смысл имеет каждый из
параметров функции, описывается в документации на функцию. Имена параметров
при записи прототипа функции имеют чисто декоративное значение, то есть они
могут понадобиться нам, а не компилятору, поэтому их можно опускать:
void *memcpy(void *, const void *, size_t);
Грамотно написанная функция наряду с аргументами использует и глобальные
переменные, которые доступны из любого блока текущего файла. Поскольку это
никак не отражается на заголовке, для использования такой функции требуется
исследовать и ее текст.
Все, что передается в функцию и обратно, должно отражаться в ее заголовке.
Это требование не синтаксиса, а хорошего стиля.
Заголовок задает объявление функции. Определение функции, кроме заголовка,
включает ее тело, то есть те операторы, которые выполняются при вызове функции,
например:
int sum(int a,int b)
{
return a + b;
}
/* функция находит сумму двух значений */
// тело функции
В тексте программы может содержаться произвольное количество объявлений
одной и той же функции и только одно определение (в этом функции не отличаются
от других программных объектов). Тело функции представляет собой блок, заключенный в фигурные скобки. Для возврата результата, вычисленного в функции,
224
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
служит оператор return. После него указывается выражение, результат вычисления
которого и передается в точку вызова функции. Результат при необходимости преобразуется по общим правилам к типу, указанному в заголовке. Функция может иметь
несколько операторов возврата, это определяется алгоритмом.
Для того чтобы вызвать функцию, надо указать ее имя, а также передать ей
набор аргументов в соответствии с указанным в ее заголовке.
Вызов функции, возвращающей значение определенного типа (то есть не имеющей тип void), может быть записан в любом месте, где по синтаксису допустимо
выражение - в правой части оператора присваивания, в составе выражения, в цепочке
вывода и так далее. Вот, например, как можно вызвать функции, приведенные выше:
double y, x1 = 0.34, x2 = 2;
y = sin (x1);
cout << y <<
<< sin (x2) << endl;
y = sin (x1 + 0.5) - sin(x1 - 0.5);
char *cite = "␣Never␣say␣never␣";
char b [100];
memcpy (b, cite, strlen(cite) + 1);
int summa, a = 2;
summa = sum(a, 4);
В определении, в объявлении и при вызове одной и той же функции типы и
порядок следования параметров должны совпадать. Для имен параметров никакого
соответствия не требуется.
Пример 7.1. Передача в функцию параметров стандартных типов
Написать программу вывода таблицы значений функции cosh x (гиперболический косинус) для аргумента, изменяющегося в заданных пределах с заданным шагом.
Значения функции вычислять с помощью разложения в ряд Тейлора с точностью ε.
Для нахождения значения функции cosh x с точностью ε воспользуемся формулой Тейлора (разложение функции в бесконечную сумму степенных функций):
∞
X x2n
x2 x4
cosh x = 1 +
+
+ ... =
.
2
4
(2n + 1)!
n=0
xn =
x2n
(2n + 1)!
,
тогда
xn−1 =
x2(n−1)
.
(2(n − 1) + 1)!
Рекуррентное соотношение
xn+2
xn+1
=
x2
.
(2n + 1) × (2n + 2)
225
7 Лабораторная работа № 7
Алгоритм работы программы: для каждого из серии значений аргумента вычисляется и затем выводится на экран значение функции. Очевидно, что подсчет
суммы ряда для одного значения аргумента логично оформить в виде отдельной
функции.
Разработка любой функции ведется в том же порядке, что и разработка программы в целом. Сначала определяется интерфейс функции, то есть какие значения
подаются ей на вход и что должно получиться в результате. Затем продумываются
структуры данных, в которых будут храниться эти и промежуточные значения; затем
составляется алгоритм, программа и тестовые примеры.
Нашей функции подсчета суммы ряда требуется получить извне значение
аргумента и точность. Пусть эти величины, а также результат имеют тип double.
Следовательно, заголовок функции может выглядеть так:
double cosh(double x, double eps);
Для вычисления суммы ряда понадобятся две промежуточные переменные для хранения очередного члена ряда и его номера. Эти переменные должны быть
описаны внутри функции, поскольку вне ее они не нужны.
#include <stdio.h>
#include <math.h>
double cosh(double x, double eps); // прототип ф-ции
int main()
{
double Xn, Xk, dX, eps;
printf("Enter␣Xn,␣Xk,␣dX,␣eps␣\n");
scanf("%lf%lf%lf%lf",&Xn, &Xk, &dX, &eps);
printf ("................................␣\n");
printf("|␣␣␣␣␣␣␣X␣␣␣␣␣␣␣|␣␣␣␣␣␣␣␣␣␣␣Y␣|\n");
printf(".............................\n");
for (double x = Xn; x <= Xk; x += dX)
printf("|%9.2lf␣|%14.6g␣|\n",x, cosh(x, eps));
printf("...............................\n");
return 0;
}
double cosh(double x, double eps)
{
const int MaxIter = 500;
/* максимальное количество итераций */
226
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
double ch = 1, y = ch;
/* первый член ряда и нач. значение
суммы */
for (int n = 0; fabs(ch) > eps; n++)
{
ch = x * x /((2 * n + 1)*(2 * n + 2));
// член ряда
y += ch;
// добавление члена ряда к сумме
if (n > MaxIter)
{
puts("␣Ряд расходится!\n");
return 0;
}
}
return y;
}
За счет использования функции программа получилась более ясной и компактной, потому что задача была разделена на две: вычисление функции и печать
таблицы. Кроме того, написанную нами функцию можно при необходимости без
изменений перенести в другую программу или поместить в библиотеку.
Если определение функции размещается после ее вызова, то перед функцией,
в которой он выполняется, размещают прототип (заголовок). Обычно заголовки
всех используемых в программе функций размещают в самом начале файла или
в отдельном заголовочном файле. Заголовок нужен для того, чтобы компилятор
мог проверить правильность вызова функции. Стандартные заголовочные файлы,
которые мы подключаем к программам, содержат прототипы функций библиотеки
именно с этой целью.
В этой программе для ввода-вывода мы применили не классы, а функции,
унаследованные из библиотеки языка С, поскольку с их помощью форматированный
вывод записывается более компактно. Спецификация формата применяется для
вывода вещественных чисел в широком диапазоне значений. Первое число модификатора (14) задает, как и для других спецификаций, ширину отводимого под число
поля, а второе (6) - не точность, как в формате f, а количество значащих цифр.
При этом число выводится либо в формате f, либо в формате е (с порядком) в
зависимости от того, какой из них получится короче.
При написании нашей функции возникает проблема, как сигнализировать о
том, что ряд расходится. Рассмотрим существующие способы решения проблемы
получения из подпрограммы признака ее аварийного завершения. Каждый из них
имеет свои плюсы и минусы.
Во-первых, можно поступить так, как сделано в приведенной выше программе: вывести текстовое сообщение, сформировать какое-либо определенное значение
функции (чаще всего это 0) и выйти из функции. Недостаток этого способа - печать
диагностического сообщения внутри функции. Это нежелательно, а порой (например,
когда функция входит в состав библиотеки) и вовсе недопустимо. Попробуйте задать
227
7 Лабораторная работа № 7
в качестве исходных данных большие значения аргумента и высокую точность. Вы
увидите, что 500 итераций для ее достижения недостаточно, и таблицу результатов
«портит» сообщение о том, что ряд расходится.
Более грамотное решение - сформировать в функции и передать наружу признак успешного завершения подсчета суммы, который должен анализироваться в
вызывающей программе. Такой подход часто применяется в стандартных функциях.
В качестве признака используется либо возвращаемое значение, которое не входит в
множество допустимых (например, отрицательное число при поиске номера элемента
в массиве или ноль для указателя), либо отдельный параметр ошибки.
Обычно параметр ошибки представляет собой целую величину, ненулевые значения которой сигнализируют о различных ошибках в функции. Если ошибка может
произойти всего одна, параметру можно назначить тип bool. Параметр передается
в вызывающую программу и там анализируется. Для нашей задачи это решение
выглядит так:
# include <stdio.h>
# include <math.h>
double cosh(double x, double eps, int
&err);
int main()
{
double Xn, Xk, dX, eps, y;
int err;
printf("Enter␣Xn,␣Xk,␣dX,␣eps␣\n");
scanf("%lf%lf%lf%lf", &Xn, &Xk, &dX, &eps);
printf (".......................................\n");
printf("|␣␣␣␣␣␣␣␣␣X␣␣␣␣␣␣␣|␣␣␣␣␣␣␣␣␣␣␣Y␣␣␣␣␣␣␣␣|\n");
printf("........................................\n");
for (double x = Xn; x <= Xk; x += dX)
{
y = cosh(x, eps, err);
if (err) printf("|␣␣␣%9.2lf␣␣␣|␣␣Рядрасходится! |\n", y);
printf("|␣␣␣␣%9.2lf␣␣␣␣␣|␣␣␣%14.6g␣␣|\n", x, y);
}
printf("........................................\n");
return 0;
}
double cosh(double x, double eps, int &err)
{
228
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
err = 0;
const int MaxIter = 500;
int n;
double ch = 1, y = ch;
for (n=0; fabs(ch) > eps; n++)
ch = x * x /((2 * n + 1)*(2 * n + 2));
y += ch;
if (n > MaxIter)
{
err = 1;
return 0;
}
return y;
}
Недостатком этого метода является увеличение количества параметров функции. Знак & перед параметром егг - это признак передачи параметра по ссылке.
Такой способ позволяет передавать значения из функции в вызывающую программу.
Механизм передачи параметров в функцию весьма прост. Когда мы пишем в
списке параметров функции выражение вида double х, это значит, что в функцию
при ее вызове должно быть передано значение соответствующего аргумента. Для
этого в стеке создается его копия, с которой и работает функция. Естественно, что
изменение этой копии не может оказать никакого влияния на ячейку памяти, в
которой хранится сам параметр. Кстати, именно поэтому на месте такого параметра
можно при вызове задавать и выражение, например:
y = cosh(x + 0.2, eps / 100, err );
Выражение вычисляется, и его результат записывается в стек на место, выделенное для соответствующего параметра. Ссылка, синтаксически являясь синонимом
имени некоторого объекта, в то же время содержит его адрес. Поэтому ссылку, в
отличие от указателя, не требуется разадресовывать для получения значения объекта.
Если мы передаем в функцию ссылку, то есть пишем в списке параметров выражение
вида double Seps, а при вызове подставляем на его место аргумент, например eps
fact, мы тем самым передаем в функцию адрес переменной eps fact. Этот адрес
обрабатывается так же, как и остальные параметры: в стеке создается его копия.
Функция, работая с копией адреса, имеет доступ к ячейке памяти, в которой хранится
значение переменной eps fact, и тем самым может его изменить.
Можно передать в функцию и указатель; в этом случае придется применять операции разадресации и взятия адреса явным образом. Для нашей функции применение
указателя для передачи третьего параметра будет выглядеть так:
// прототип функции;
229
7 Лабораторная работа № 7
double cosh(double , double eps, int * err);
// вызов функции;
y = cosh(x, eps, &err);
// & - взятие адреса
// обращение к еrr внутри функции;
*err = 0;
// * - разадресация
В прототипе (и, конечно, в определении функции) явным образом указывается, что третьим параметром будет указатель на целое. При вызове на его место
передается адрес переменной err. Чтобы внутри функции изменить значение этой
переменной, применяется операция получения значения по адресу.
Итак, для входных данных функции используется передача параметров по
значению, для передачи результатов ее работы - возвращаемое значение и/или
передача параметров по ссылке или указателю. На самом деле у передачи по значению
есть один серьезный недостаток: для размещения в стеке копии данных большого
размера (например, структур, состоящих из многих полей) тратится и время на
копирование, и место. Кроме того, стек может просто переполниться. Поэтому более
безопасный, эффективный и грамотный способ - передавать входные данные по
константной ссылке, чтобы исключить возможность непреднамеренного изменения
параметра в функции.
Для нашей программы передача входных данных по константной ссылке выглядит так:
// прототип функиии;
double cosh(const double &x, const double &eps, int &err);
// вызов функции;
y = cosh(x, eps, err);
/*
обращение к x и eps внутри
функции не изменяется
*/
Таким образом, входные данные функции надо передавать по значению или
по константной ссылке, результаты ее работы - через возвращаемое значение, а при
необходимости передачи более одной величины - через параметры по ссылке или
указателю.
Генерация исключения. Воспользуемся средством C++, называемым значениями
параметров по умолчанию. Может оказаться неудобным каждый раз при вызове
функции cosh задавать требуемую точность вычисления суммы ряда. Конечно, можно определить точность в виде константы внутри функции, задав максимальное
допустимое значение, но иногда это может оказаться излишним, поэтому желательно
230
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
сохранить возможность задания точности через параметры. Для этого либо в определении (если оно находится выше по тексту, чем любой вызов функции), либо в
прототипе функции после имени параметра указывается его значение по умолчанию,
например:
double cosh(double x, double eps = DBL EPSILON);
DBL EPSILON - это константа, определенная в файле <float.h>. Ее значение
равно минимальному числу, которое, будучи прибавлено к единице, даст не равный
единице результат. Теперь нашу функцию можно вызывать с одним параметром, к
примеру:
y = cosh(x);
Функция может иметь несколько параметров со значениями по умолчанию.
Они должны находиться в конце списка параметров.
Вариант прототипа функции с использованием параметра ошибки, а также
значением точности по умолчанию выглядит так:
double cosh(const double x, int &err, const double eps = DBL EPSILON);
Соответствующим образом изменится и вызов функции. Указание перед параметром ключевого слова const в данном случае (при передаче по значению)
применяется только для того, чтобы четко указать, какие из параметров являются
входными. В случае передачи по ссылке указание const, кроме того, дает возможность передавать на месте этого параметра константу.
Мы оформили в виде функции вычисление суммы ряда, однако задача вывода
таблицы значений функции сама но себе достаточно типична и может встретиться
в других задачах. Поэтому было бы логично оформить ее решение также в виде
функции.
Пример 7.2. Передача в функцию имени функции
Назовем функцию вывода таблицы значений print_tabl. Прежде всего надо определить ее интерфейс. Для того чтобы вывести таблицу, нашей функции
потребуется знать диапазон и шаг изменения значений аргумента, а также какую,
собственно, функцию мы собираемся вычислять. В функцию вычисления суммы ряда
надо передавать точность, поэтому точность следует включить в список параметров
вызывающей ее функции print_tabl. Функция print_tabl не возвращает никакого
значения, то есть перед ее именем надо указать void.
Чтобы передать в функцию имя функции следует в списке параметров перед
именем параметра указать его тип. До этого момента мы передавали в функцию
231
7 Лабораторная работа № 7
величины стандартных типов, а теперь нам потребуется определить собственный тип.
Тип функции определяется типом ее возвращаемого значения и типом ее параметров.
Для нашей функции это выглядит так:
double (*fun)(double, double);
Здесь описывается указатель по имени fun на функцию, получающую два
аргумента типа double и возвращающую значение того же типа. Часто, если описание
типа сложное, с целью улучшения читаемости программы задают для него синоним
с помощью ключевого слова typedef:
typedef double (*Pfun)(double, double);
В этом операторе задается тип Pfun, который можно использовать наряду со
стандартными типами при описании переменных. Таким образом, заголовок функции
печати таблицы должен иметь вид:
void print_tabl(Pfun fun, double Xn, double Xk, double dX, double eps);
Запишем теперь текст программы, сведя к минимуму диагностику ошибок (при
превышении максимально допустимого количества итераций функция завершается,
возвращая 0, а вызывающая программа выводит это значение):
Листинг 7.2
#include <stdio.h>
#include <math.h>
typedef const double cdouble;
typedef double (*Pfun)(cdouble, cdouble);
void print_tabl(Pfun fun, cdouble Xn, cdouble Xk, cdouble dX, cdouble eps)
;
double cosh(cdouble x, cdouble eps);
int main()
{
double Xn, Xk, dX, eps;
printf("Enter␣Xn,␣Xk,␣dX,␣eps␣\n");
scanf("%lf%lf%lf%lf", &Xn, &Xk, &dX, &eps);
print_tabl(cosh, Xn, Xk, dX, eps);
return 0;
232
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
}
void print_tabl(Pfun fun, cdouble Xn, cdouble Xk, cdouble dX, cdouble eps)
{
double x;
printf("␣................................\n");
printf("|␣␣␣␣␣␣␣X␣␣␣␣␣␣␣|␣␣␣␣␣␣␣␣␣␣␣Y␣␣␣|\n");
printf(".................................\n");
for (x = Xn; x <= Xk; x += dX)
printf("|%9.2lf␣|␣%14.6g␣|␣\n",x,fun(x, eps));
printf(".................................\n");
}
double cosh(cdouble x, cdouble eps)
{
const int MaxIter = 500;
double ch = 1, y = ch;
for (int n = 0; fabs(ch) > eps; n++)
{
ch = x * x /(2 * n + 1)/(2 * n + 2);
y += ch;
if(n > MaxIter) return 0;
}
return y;
}
Функция print_tabl предназначена для вывода таблицы значений любой функции, принимающей два аргумента типа double и возвращающей значение того же
типа.
Таким образом, наряду с большей общностью мы добились и лучшего структурирования программы, разбив ее на две логически не связанные подзадачи: вычисление
функции и вывод таблицы. В главной программе остался только ввод исходных
данных и вызов функции.
Пример 7.3. Передача одномерных массивов в функцию
Даны два массива из n целых чисел каждый. Определить, в каком из них
больше положительных элементов.
Для решения этой задачи потребуется подсчитать количество положительных
элементов в двух массивах, то есть выполнить для обоих массивов одни и те же
действия. Следовательно, эти действия надо поместить в функцию. Интерфейс
функции: входные данные - массив и количество его элементов, результат - количество
233
7 Лабораторная работа № 7
положительных элементов в массиве. Таким образом, заголовок функции должен
иметь вид:
int nposit(const int *a, const int n);
Имя массива представляет собой указатель на его нулевой элемент, поэтому
в функцию массивы передаются через указатели. Количество элементов в массиве
должно передаваться отдельным параметром, потому что, в отличие от строк символов, использующих признак конца строки, для массивов общего вида никакого
признака конца массива не существует.
Листинг 7.3
#include <iostream>
#include <stdio.h>
using namespace std;
int nposit(const int *a, const int n);
int main()
{
int i, n;
cout << "Введите количество элементов: "; cin >> n;
int *a = new int[n];
int *b = new int[n];
cout << "Введите элементы первого массива: ";
for (i = 0; i < n; i++) cin >> a[i];
cout << "Введите элементы второго массива: ";
for (i = 0; i < n; i++) cin >> b[i];
if (nposit(a, n) > nposit(b, n))
cout << "␣В первом положительных больше" << endl;
else if(nposit(a, n) < nposit(b, n))
cout << "␣Во втором положительных больше" << endl;
else
cout << "␣Одинаковое количество" << endl;
234
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
return 0;
}
int nposit(const int *a, const int n)
{
int count = 0;
for (int i = 0; i < n; i++)
if (a[i] > 0) count++;
return count;
}
В этой программе место под массивы выделяется в динамической области
памяти, поскольку в задании не указано конкретное количество элементов. Однако
функцию nposit можно без изменений применять и для «обычных» массивов, потому
что для каждого из них имя тоже является указателем на нулевой элемент, только
константным. Например, опишем массив из 10 элементов и инициализируем первые
шесть из них (оставшимся будут присвоены нулевые значения):
int [10] = {2, 3.
-1,
-10, 4,
-2};
cout << nposit(x, 10); // будет выведено значение 3
Рассмотрим способ анализа результатов работы функции. Функция вызывается в составе выражения в условном операторе. Для перебора всех трех вариантов
результата приходится вызывать ее для каждого массива дважды, что для больших
массивов, конечно, нерационально. Чтобы избежать повторного вызова, можно завести две переменные, в которые записываются результаты обработки обоих массивов,
а затем использовать эти переменные в условных операторах:
int nposita = nposit(a, n), npositb = nposit(b, n);
if (nposita > npositb)
cout << "␣В первом положительных больше" << endl;
else if (nposita < npositb)
cout << "␣Во втором положительных больше" << endl;
else
cout << "␣Одинаковое количество" << endl;
Современные компиляторы обладают широкими возможностями оптимизации
и сами отслеживают подобные ситуации, преобразуя код программы, но это не
означает, что на эффективность своих программ вообще не надо обращать внимания.
235
7 Лабораторная работа № 7
Главным же критерием при выборе варианта написания программы, тем не менее,
остается простота ее структуры и читаемость.
Пример 7.4. Передача строк в функцию
Написать программу, определяющую, сколько чисел содержится в каждой
строке текстового файла. Длина каждой строки не превышает 100 символов.
Эту задачу можно разбить на две: ввод данных из файла и их анализ. Для
каждой строки проверка выполняется отдельно, поэтому в виде функции логично
оформить поиск и подсчет количества чисел в одной строке. На вход функции будем
подавать строку, а на выходе получать количество чисел в этой строке.
Отличие передачи в функцию строки от передачи обычного массива состоит в
том, что можно не передавать отдельным параметром размерность строки, а определять конец строки внутри функции по нуль-символу. Для простоты предположим,
что числа могут быть либо целые, либо вещественные с фиксированной точкой и
непустой дробной частью. Распространить действие программы на другие виды
чисел предоставляется вам в виде самостоятельного упражнения.
Листинг 7.4
#include <iostream>
#include <fstream>
using namespace std;
int num_num(const char *str);
int main()
{
ifstream fin("test.txt");
if (!fin)
{
cout << "Нет файла test.txt" << endl;
return 0;
}
const int len = 101;
int i = 1;
char str[len];
while (fin.getline(str,len))
{
cout << "В
строке " <<i<<"␣содержится "<<num_num(str)<<"␣чисел"<<endl;
i++;
236
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
}
return 0;
}
int num_num(const char *str)
{
int count = 0;
while (*str)
{
if (isdigit(*str) && isdigit(*(str + 1)) && *(str + 1) != ’.’)
count++;
str++;
}
return count;
}
Увеличение счетчика чисел в функции происходит каждый раз, когда заканчивается число, то есть если после цифры стоит не цифра и не точка. Цикл заканчивается
по достижении нуль-символа.
Пример 7.5. Передача двумерных массивов в функцию
Написать программу, определяющую, в какой строке целочисленной матрицы
т х п находится самая длинная серия одинаковых элементов.
Под серией имеются в виду элементы, расположенные подряд. В виде функции
здесь удобно оформить решение основной задачи, оставив главной программе только
ввод исходных данных и вывод результатов. Для удобства отладки ввод массива
в программе выполняется из текстового файла. Первая строка файла содержит
значения для тип, каждая следующая строка - набор чисел для одной строки матрицы.
Память под массив выделяется в цикле для того, чтобы можно было задавать обе
размерности массива в виде переменных.
Листинг 7.5
#include <iostream>
#include <fstream>
using namespace std;
int ser_equals(int **a, const int m, const int n);
int main()
237
7 Лабораторная работа № 7
{
ifstream fin("matrix.txt",ios::in);
if (!fin)
{
cout << "Нет файла matrix.txt" << endl;
return 0;
}
int m, n, i, j;
fin >> m >> n;
int **a = new int *[m];
for (i = 0; i < m; i++)
a[i] = new int [n];
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
fin >> a[i][j];
// выделение памяти
// ввод массива
int line = ser_equals(a, m, n);
// вызов функции
if (line >= 0) cout << "␣Самая длинная серия в строке " << line;
else cout << "␣Серий одинаковых элементов нет ";
return 0;
}
int ser_equals(int **a, const int m, const int n)
{
int i, j, count, line = -1, maxcount = 0;
for (i = 0; i < m; i++)
{
count = 0;
for (j = 0; j < n - 1; j++)
{
if (a[i][j] == a[i][j + 1])
count++;
else
{
if (count > maxcount)
{
maxcount = count;
line = i;
}
count = 0;
}
}
238
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
if (count > maxcount)
{
maxcount = count;
line = i;
}
}
return line;
}
Алгоритм работы функции прост: в каждой строке выполняется сравнение
соседних элементов (оператор 2). Если они равны, мы находимся внутри серии, при
этом увеличиваем ее текущую длину. Она накапливается в переменной count, которая
обнуляется перед обработкой каждой строки (оператор 1). Если же элементы не
равны, это означает либо окончание серии, либо просто одиночный элемент (оператор
3). В этом случае надо посмотреть, не является ли данная серия самой длинной из
рассмотренных и, если да, то запомнить ее длину и номер строки, в которой она
встретилась (оператор 4). Для подготовки к анализу следующих серий в этой же
строке надо обнулить счетчик count. Аналогичная проверка после цикла просмотра
строки (оператор 5) выполняется для серии, которая расположена в конце строки,
поскольку в этом случае ветвь else выполняться не будет.
Если в массиве нет ни одной серии одинаковых элементов, функция вернет
значение, равное -1.
Пример 7.6. Передача структур в функцию
Написать программу дополнения бинарного файла, сформированного в задаче
6.3, вводимыми с клавиатуры сведениями о сотрудниках.
Эту задачу можно разбить на две части: ввод сведений о сотрудниках в структуру и добавление этой информации в бинарный файл, поэтому в нашей программе
будет две функции. Первая функция возвращает сформированную структуру, ничего
не получая извне. Вторая получает структуру и имя файла и возвращает признак
успешности добавления.
Для проверки правильности занесения данных в бинарный файл напишем еще
одну функцию, которая будет по введенному номеру записи выводить ее на экран.
Листинг 7.6
#include <iostream>
#include <cstring>
const int l_name = 30;
struct Man
239
7 Лабораторная работа № 7
{
char name[l_name + 1];
int birth_year;
float pay;
};
Man read_data();
int append2binfile(const Man &man, const char* filename);
int print_from_bin(const char * filename);
int main()
{
bool contin;
char y_n[2];
char filename[] = "dbase.bin";
do
{
contin = false;
if (append2binfile(read_data(), filename) != 0)
{
puts("␣Ошибка при записи в файл! ");
return 1;
}
puts("␣Продолжить (/n)?");
fgets(y_n,2,stdin);
if ( (y_n[0] == ’y’) || (y_n[0] == ’Y’) ) contin = true;
} while (contin);
print_from_bin(filename);
return 0;
}
int append2binfile(const Man &man, const char* filename)
{
FILE *fout=fopen(filename, "ab");
if ((fout)==NULL) return 1;
int success = fwrite(&man, sizeof(man), 1, fout);
fclose(fout);
if (success == 1) return 0;
else return 2;
}
240
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int print_from_bin(const char * filename)
{
int num; Man man; FILE *f = fopen(filename, "rb");
if ((f) == NULL ) return 1;
fseek(f, 0, SEEK_END);
int n_record = ftell(f) / sizeof (man);
while (true)
{
puts("Введите номер записи или -1; ");
scanf("%i", &num);
if (num < 0 || num >= n_record) break ;
fseek(f, num * sizeof(man), SEEK_SET) ;
fread(&man, sizeof(man), 1, f);
// CharToOem(man,name, man,name);
printf("%30s%5i%15.2f\n", man.name, man.birth_year, man.pay);
return 0;
}
}
Man read_data()
{
Man man;
char buf[80];
char name[l_name + 1];
puts("Введите фамилию И.О. ");
fgets(name,l_name+1,stdin);
if (strlen(name) < l_name)
for (int i = strlen(name); i < l_name; i++)
name[l_name] = 0;
// OemToChar(name. name);
strncpy(man.name, name,l_name + 1);
do
{
puts("Введите год рождения ");
fgets(buf,80,stdin);
} while ((man.birth_year = atoi(buf)) == 0);
do
{
puts("Bвeдитe оклад ");
fgets(buf,80,stdin);
} while (!(man.pay =atof(buf)));
241
7 Лабораторная работа № 7
return man;
}
В функции ввода read_data предусмотрено заполнение пробелами оставшейся
части строковой переменной name, чтобы формат имени был идентичен формату
ввода в текстовом файле.
Следует обратить внимание на то, как в этой функции выполняется проверка
правильности ввода числовой информации. Чтение выполняется в буферную строку,
которая затем преобразуется с помощью функций atoi () и atof () в числа. Если
функции возвращают 0, преобразование выполнить не удалось (например, вместо
цифр были введены буквы), и информация запрашивается повторно. Условие повторения циклов 3 и 4 записано в двух разных вариантах, чтобы вы сами могли
оценить, какой из них вам более понятен (профессионалы предпочли бы второй,
более лаконичный вариант).
Структура, в отличие от массива, может быть возвращаемым значением функции. В этой программе структура передавалась в функцию по константной ссылке;
можно передавать ее и по значению, что несколько хуже, потому что в этом случае
затрачивается время на копирование и требуется дополнительное место в стеке
параметров.
Пример 7.7. Рекурсивные функции
Написать программу упорядочивания массива методом быстрой сортировки,
используя рекурсию.
Рекурсивной называется функция, в которой имеется обращение к ней самой.
Любая функция в программе на C++ может вызываться рекурсивно. При этом
в стеке выделяется новый участок памяти для размещения копий параметров, а
также автоматических и регистровых переменных, поэтому предыдущее состояние
выполняемой функции сохраняется.
Одна из возможных версий программы сортировки приведена ниже.
Листинг 7.7
#include <iostream>
#include <stdio.h>
using namespace std;
void qsort(float* array, int left, int right);
int main()
{
const int n = 10;
float arr[n];
242
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int i, r;
cout << "введите элементы массива:";
for (i = 0; i < n; i++)
i = 0; r = n - 1;
cin >> arr[i];
/*
левая и правая границы
начального фрагмента
*/
qsort(arr, 1, r);
// 1
for (i = 0; i < n; i++) cout << arr[i] << ’␣’;
return 0;
}
void qsort(float* array, int left, int right)
{
int i = left, j = right;
float middle = array[(left + right) / 2];
float temp;
while (i < j)
{
while (array[i] < middle) i++;
while (middle < array[j]) j--;
if (i <= j)
{
temp = array[i];
array[i] = array[j];
array[j] = temp;
i++;
j--;
}
}
if (left < j) qsort(array, left, j);
if (i < right) qsort(array, i, right);
}
Процедура разделения реализована здесь в виде рекурсивно вызываемой функции
qsort(), в теле которой есть два обращения к самой себе: в операторе 2 - для
сортировки левой половинки текущего фрагмента, и в операторе 3 - для сортировки
его правой половинки.
Однако, у рекурсии есть и недостатки: во-первых, такую программу труднее
отлаживать, поскольку требуется контролировать глубину рекурсивного обращения,
во-вторых, при большой глубине стек может переполниться, а в-третьих, использо-
243
7 Лабораторная работа № 7
вание рекурсии повышает накладные расходы (например, в данном случае в стеке
сохраняются отнюдь не два числа, представляющие собой границы фрагмента, а
гораздо больше, не говоря уже о затратах, связанных с вызовом функции).
Пример 7.8. Многофайловый проект - форматирование текста
Написать программу форматирования текста, читаемого из файла unformt.txt
и состоящего из строк ограниченной длины. Слова в строке разделены произвольным количеством пробелов. Программа должна читать входной файл по строкам,
форматировать каждую строку и выводить результат в выходной файл formatd.txt.
Форматирование заключается в выравнивании границ текста слева и справа путем
равномерного распределения пробелов между соседними словами, а также в отступе
с левой стороны страницы на margin позиций, то есть результирующий текст должен
находиться в позициях margin + 1 .. margin + maxljine. Кроме этого, программа
должна подсчитать общее количество слов в тексте.
Алгоритм решения задачи:
1. Открыть входной файл.
2. Читать файл построчно в текстовый буфер line, попутно удаляя возможные
пробелы в начале строки (до первого слова).
3. Для каждой строки line выполнить следующие действия:
- Вычислить величину интервала (количество пробелов), которую необходимо обеспечить между соседними словами для равномерного распределения
слов в пределах строки.
- Вывести каждое слово из строки line в выходной файл, вставляя между
словами необходимое количество пробелов и одновременно увеличивая
счетчик слов на единицу.
4. После обработки последней строки входного файла вывести на экран значение
счетчика слов и закрыть выходной файл.
Разбиение на подзадачи.
В результате детализации описанного алгоритма определяем спецификации
нужных нам функций:
void DefInter (const char *pline, int &base_int, int &add_int, int &inter)
определяет для строки, на которую указывает pline, количество межсловных промежутков inter, требуемую величину основного интервала base_int для каждого
244
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
промежутка (количество пробелов) и величину дополнительного интервала add_int,
определяемую как остаток от деления общего количества пробелов в строке на
количество межсловных промежутков; последняя величина должна быть равномерно распределена путем добавления одного пробела в каждый из первых add_int
промежутков;
void GetLine
(FILE *finp,char *pline)
читает очередную строку из входного файла в массив символов с адресом pline,
ликвидируя при этом пробелы в начале строки;
void PutInterval (FILE *fout, const int k)
выводит очередной интервал, состоящий из к пробелов;
int PutWord (FILE *fout,const char *pline, const int startpos)
выводит очередное слово в выходной файл, начиная с позиции startpos текущей
строки pline; возвращает номер позиции в строке pline, следующей за последним
переданным символом, или 0 - если достигнут конец строки;
int SearchNextWord(const char *pline, const int curpos)
возвращает номер позиции, с которой начинается следующее слово в строке pline,
или 0, если достигнут конец строки (поиск начинается с позиции curpos).
Разбиение на модули.
Наша программа будет располагаться в двух исходных файлах: lst7_8.cpp с функцией main, edit.cpp - с реализацией перечисленных выше функций, а также заголовочный файл edit.h с интерфейсом этих функций. Ниже приводится
содержимое этих файлов.
Листинг 7.8
/*
Файл lst7_8.cpp
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "edit.h"
245
7 Лабораторная работа № 7
// Глобальные переменные
const int maxline = 63;
const int margin = 5;
int main()
{
FILE* finp;
FILE* fout;
char line[maxline + 1];
int b_i, a_i, start, next, inter;
int nword = 0;
printf("Работает программа lst7_8.\n");
if(!(finp = fopen("unformt.txt","r")))
{
printf("Файл unformt.txt не найден.\n");
exit(0);
}
printf("Читается файл unformt.txt \n");
if(!(fout = fopen("formatd.txt", "w")))
{
printf("Фaйл formatd.txt не создан.\n");
exit(0);
}
printf("Выполняется запись в файл formatd.txt.\n");
while(GetLine(finp, line))
{
DefInter (line, b_i, a_i, inter);
PutInterval(fout, margin);
next = PutWord(fout, line, start, nword);
for (int i = 0; i < inter; i++)
{
start = SearchNextWord(line, next);
PutInterval(fout, b_i);
if (a_i)
{
a_i--; PutInterval(fout, 1);
}
246
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
next = PutWord(fout, line, start, nword);
if (!next) break;
}
fprintf(fout, "\n");
}
printf ("\nКоличество слов - %d\n", nword);
fclose(fout);
printf("Работа завершена \n");
return 0;
}
Файл edit.h
/*
Файл edit.h
Прототипы функций
*/
void
int
void
int
int
DefInter(const char *pline,int &base_int,int &add_int,int &inter);
GetLine(FILE*, char*);
PutInterval(FILE*, const int);
PutWord(FILE*, const char*, const int, int&);
SearchNextWord(const char*, const int);
// Глобальные переменные
extern const int maxline;
/*
Файл edit.cpp
*/
int GetLine(FILE* finp, char* pline)
{
int i = 0;
char c;
while ((c = fgetc(finp)) == ’␣’) i++;
if(c == EOF) return 0;
fseek(finp, -1, SEEK_CUR);
fgets(pline, maxline - i + 1, finp);
pline[strlen(pline) - 1] = 0;
return 1;
}
247
7 Лабораторная работа № 7
int SearchNextWord(const char* pline, const int curpos)
{
int i = curpos;
while(pline[i] !=’␣’)
{
if (pline[i] == ’\n’) return 0;
i++;
}
while (pline[i] == ’␣’ && pline[i + 1] == ’␣’) i++;
return i + 1;
}
void DefInter(const char* pline, int &base_int, int &add_int, int &inter)
{
int k = 0, end;
end = strlen(pline) - 1;
while ((pline[end]==’␣’)||(pline[end]==’\n’)||(pline[end]==’\r’)) end
--;
inter = 0;
for (unsigned int i= 0; i < (unsigned int)end; i++)
{
if (pline[i] == ’␣’)
{
if (pline[i + 1] != ’␣’) inter++;
}
}
int blank_amount = k + maxline - end;
if (!k)
{
base_int = 0;
add_int = 0;
}
else
{
base_int = blank_amount / inter;
add_int = blank_amount % inter;
}
return;
248
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
}
int PutWord (FILE* fout, const char* pline, const int startpos, int& n)
{
int i = startpos;
char c;
n++;
while ((c - pline[i++]) !=’␣’)
{
fprintf(fout, "%c", c);
if ((c = ’\n’) || (c == ’\0’))
{
i = 0;
break;
}
}
return i - 1;
}
void PutInterval(FILE* fout, const int k)
{
for (int i=0; i<k; i++) fprintf(fout, "␣");
return;
}
Имена функций мы здесь записали (для разнообразия) в стиле Microsoft, с
использованием прописных букв для выделения осмысленных частей имени. Константу maxline следует задавать большей, чем максимальная длина строки исходного
файла. В качестве самостоятельного упражнения измените программу так, чтобы
можно было форматировать текст и в более узкую колонку, чем в исходном файле.
7.2 Перегрузка функций.
Перегрузкой функций называется использование нескольких функций с одним
и тем же именем, но с различными списками параметров. Перегруженные функции
должны отличаться друг от друга либо типом хотя бы одного параметра, либо количеством параметров, либо и тем и другим одновременно. Перегрузка является видом
полиморфизма и применяется в тех случаях, когда одно и то же по смыслу действие
реализуется по-разному для различных типов или структур данных. Компилятор
сам определяет, какой именно вариант функции вызвать, руководствуясь списком
аргументов.
Если же алгоритм не зависит от типа данных, лучше реализовать его не в виде
249
7 Лабораторная работа № 7
группы перегруженных функций для различных типов, а в виде шаблона функции.
В этом случае компилятор сам с генерирует текст функции для конкретных типов
данных, с которыми выполняется вызов, и программисту не придется поддерживать
несколько практически одинаковых функций.
Небольшие перегруженные функции удобно применять при отладке программ.
Допустим, вам требуется промежуточная печать различного вида: в одном месте
требуется выводить на экран структуру, в другом - пару целых величин с пояснениями
или вещественный массив. Проще сразу оформить печать в виде функций, например
таких:
void print(char *str, const int i, const int j)
{
cout<<str<<"␣|"<<oct<<setw(4)<<i<<"␣|"<<setw(4)<<j<<"␣|"<< endl;
}
void print(float mas[], const int n)
{
cout << "Массив:" << endl;
cout.setf(ios::fixed);
cout.precision(2);
for (int i = 0; i < n; i++)
{
cout << mas[i] << "␣␣␣␣";
if ((i + 1) % 4 == 0) cout << endl;
}
cout << endl;
}
void print(Man m)
{
cout.setf(i os::fixed);
cout.precision(2);
cout<<setw(40)<<m.name<< ’␣’ << m.birth_year <<’␣’<< m.pay<<endl;
}
В первой из этих функций на экран выводятся строка и два целых числа в
восьмеричной форме, разделенных вертикальными черточками для читаемости. Под
каждое число отводится по 4 позиции (действие манипулятора setw распространяется
только на ближайшее выводимое поле).
Во второй функции для вывода вещественных значений по четыре числа на
строке задается вид вывода с фиксированной точкой и точностью в два десятичных знака после запятой. Для этого используются методы установки флагов setf,
250
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
установки точности precision и константа fixed, определенная в классе 1os. Точность касается только вещественных чисел, ее действие продолжается до следующей
установки.
Третья функция выводит поля знакомой нам по шестому семинару структуры
так, чтобы они не склеивались между собой. Манипулятор setw устанавливает
ширину следующего за ним поля. Это приведет к тому, что фамилии будут выведены
с отступом от края экрана. Вызов этих функций в программе может выглядеть,
например, так:
print("После цикла ", 1, n);
print(a, n);
print(m);
По имени функции сразу понятно, что она делает, кроме того, при необходимости
вызов функции легче закомментировать или перенести в другое место, чем группу
операторов печати. Конечно, промежуточная печать - не единственный метод отладки,
но зато универсальный, потому что отладчик не всегда доступен.
При написании перегруженных функций основное внимание следует обращать
на то, чтобы в процессе поиска нужного варианта функции по ее вызову не возникало
неоднозначности.
Неоднозначность может возникнуть по нескольким причинам. Во-первых, изза преобразований типов, которые компилятор выполняет по умолчанию. Смысл
правил преобразования арифметических типов сводится к тому, что более короткие
типы преобразуются в более длинные. Если соответствие между формальными
параметрами и аргументами функции на одном и том же этапе может быть получено
более чем одним способом, вызов считается неоднозначным и выдается сообщение об
ошибке.
Неоднозначность может также возникнуть из-за параметров по умолчанию и
ссылок. Рассмотрим создание перегруженных функций на примере.
Пример 7.9. Перегрузка функций
Написать программу, которая для базы сотрудников выдает по запросу список
сотрудников либо родившихся раньше заданного года, либо имеющих оклад больше
введенного с клавиатуры.
Варианты выборки из базы по различным запросам оформим в виде перегруженных функций. Мы от природной лени и для простоты рассматриваем базу с
минимальным количеством полей; в реальных ситуациях их может быть гораздо
больше, соответственно, больше будет и вариантов перегруженных функций. Также оформим в виде отдельной функции чтение базы из файла - и для лучшего
структурирования программы, и для того, чтобы в случае необходимости было легче
заменить эту функцию на другую, например на чтение из бинарного файла.
251
7 Лабораторная работа № 7
Листинг 7.9
#include <iostream>
#include <fstream>
#include <string.h>
#include <stdlib.h>
#include <iomanip>
using namespace std;
const int l_name = 30, l_year = 5, l_pay = 10, l_buf = l_name + l_year +
l_pay;
struct Man
{
int
birth_year;
char name[l_name + 1];
float pay;
};
int read_dbase(const char *filename, Man dbase[], const int l_dbase, int
&n_record);
void print(Man m);
void select(Man dbase[], const int n_record, const int year);
void select(Man dbase[], const int n_ecord, const float pay);
int main()
{
const int l_dbase = 100;
Man dbase[l_dbase];
int n_record = 0;
if(read_dbase("txt6.txt",dbase,l_dbase,n_record) != 0) return 1;
int option;
int year;
float pay;
do
{
cout <<"-----------------------------"<< endl;
cout <<"1␣-␣Cведения по году рождения" << endl;
cout <<"2␣-␣Сведения по окладу"<<endl;
cout <<"3␣-␣выход"<<endl;
cin >> option;
switch (option)
{
252
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
case 1: cout<<"Введите год"; cin >> year;
select(dbase,n_record,year); break;
case 2: cout<<"Введите оклад"; cin >> pay;
select(dbase,n_record,pay); break;
case 3: return 0;
default: cout << "Надо вводить число от 1 до 3" << endl;
}
}
while(true);
return 0;
}
void select(Man dbase[], const int n_record, const int year)
{
cout << "Ввод сведений по году рождения" << endl;
bool success = false;
for(int i = 0; i < n_record; i++)
if(dbase[i].birth_year >= year)
{
print(dbase[i]);
success = true;
}
if (!success) cout <<"Таких сотрудников нет"<<endl;
}
void select(Man dbase[], const int n_record, const float pay)
{
cout << "Ввод сведений по окладу" << endl;
bool success = false;
for(int i = 0; i < n_record; i++)
if(dbase[i].pay >= pay)
{
print(dbase[i]);
success = true;
}
if(!success) cout <<"Таких сотрудников нет"<<endl;
}
void print (Man m)
{
cout.setf(ios::fixed);
253
7 Лабораторная работа № 7
cout.precision(2);
cout<<setw(40)<<m.name<<’␣’<<m.birth_year<<’␣’<<m.pay<<endl;
}
int read_dbase(const char *filename,Man dbase[],const int l_dbase,int &
n_record)
{
char buf[l_buf +1];
ifstream fin(filename, ios::in);
if (!fin )
{
cout << "Нет файла " << filename << endl;
return 1;
}
int i = 0;
while (fin.getline(buf, l_buf))
{
strncpy(dbase[i].name,buf,l_name);
dbase[i].name[l_name] = ’\0’;
dbase[i].birth_year = atoi(&buf[l_name]);
dbase[i].pay = atof(&buf[l_name + l_year]);
i++;
if (i > l_dbase)
{
cout << "Слишком длинный файл!";
return 2;
}
}
n_record = i;
fin.close();
return 0;
}
Правила описания перегруженных функций:
1. Перегруженные функции должны находиться в одной области видимости,
иначе произойдет сокрытие аналогично одинаковым именам переменных во
вложенных блоках.
254
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
2. Перегруженные функции могут иметь параметры по умолчанию, при этом
значения одного и того же параметра в разных функциях должны совпадать.
В различных вариантах перегруженных функций может быть различное количество параметров по умолчанию.
3. Функции не могут быть перегружены, если описание их параметров отличается
только модификатором const или использованием ссылки.
7.3 Шаблоны функций.
Зададим шаблон функции, а компилятор пусть самостоятельно создает столько
перегруженных функций, для скольких типов данных нам потребуется вызвать
шаблон. Это получится только в том случае, если реализуемый алгоритм независим
от типа данных. Таким образом, области применения перегрузки функций и шаблонов
отличаются: перегруженные функции мы применяем для оформления действий,
аналогичных по названию, но различных по реализации, а шаблоны - для идентичных
действий над данными различных типов.
Шаблон функции определяется следующим образом:
template <class тип>
тип имя ([список параметров])
{
/* тело функции */
}
Идентификатор тип, задающий так называемый параметризованный тип, может
использоваться как в остальной части заголовка, так и в теле функции. Параметризованный тип - это всего лишь фиктивное имя, которое компилятор автоматически
заменит именем реального типа данных при создании конкретной версии функции.
В общем случае шаблон функции может содержать несколько параметризованных
типов <class тип1, class тип2, class типЗ, ... >.
Процесс создания конкретной версии функции называется инстанцированием
шаблона или созданием экземпляра функции. Возможны два способа инстанцирования шаблона: а) явный, когда объявляется заголовок функции, в котором все
параметризованные типы заменены на конкретные типы, известные в этот момент в
программе, б) неявный, когда создание экземпляра функции происходит автоматически, если встречается фактический вызов функции.
Шаблоны тоже можно перегружать, причем как шаблонами, так и обычными
функциями.
255
7 Лабораторная работа № 7
Пример 7.10. Шаблоны функций
Написать программу, которая определяет максимальные элементы в одномерных массивах различных арифметических типов.
Поиск максимума - весьма распространенная задача. Для этого достаточно простейшего шаблона с одним параметром-типом. В саму функцию будет передаваться
два аргумента: указатель на массив и длина этого массива.
Листинг 7.10
#include <iostream>
#include <string.h>
using namespace std;
template <class T>
T Max(T *b, int n);
int main()
{
const int n = 20;
int i, b[n];
cout<<"Введите " << n << "␣целых чисел:" << endl;
for (i = 0; i < n; i++) cin >> b[i];
cout << Max(b, n) << endl;
double a[] = {0.22, 117.2, -0.08, 0.21, 42.5};
cout << Max(a, 5) << endl;
char *str = (char*)"Sophisticated␣fantastic␣template";
cout << Max(str, strlen(str)) << endl;
return 0;
}
template <class T>
T Max(T *b, int n)
{
int imax = 0;
for (int i = 1; i < n; i++)
if (b[i] > b[imax]) imax = i;
return b[imax];
}
Шаблон функции имеет имя Мах. После ключевого слова template в угловых
скобках перечисляются все параметры шаблона. В данном случае параметр один. При
256
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
инстанцировании шаблона (в данном случае - неявном), то есть когда компилятор
будет создавать конкретный вариант функции, этот тип будет заменен конкретным
стандартным или пользовательским типом. Соответствие устанавливается при вызове функции либо по типу аргументов, либо по явным образом указанному типу.
Например, последний вызов функции можно записать так:
cout << Max <char> (str, strlen(str));
Этот способ применяется в тех случаях, когда тип не определяется по виду
оператора вызова функции.
Аналогично обычным параметрам функции, можно задавать значение параметра шаблона по умолчанию.
При работе с многофайловым проектом нужно не забывать, что если какойто шаблон функции имеет инстанцирование в нескольких исходных файлах, то
определение этого шаблона должно повторяться в каждом из этих файлов. Поэтому
обычно определение шаблона выносят в заголовочный файл и подключают его в
нужных местах директивой ]include.
257
7 Лабораторная работа № 7
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
258
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
7.4 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Выполнить задания согласно варианта индивидуального задания №1 лабораторной работы №7, оформив каждый пункт задания в виде функции. Все
необходимые данные для функций должны передаваться им в качестве параметров. Использование глобальных переменных в функциях не допускается.
3. Выполнить задания согласно варианта индивидуального задания №1 лабораторной работы №7, оформив каждый пункт задания в виде шаблона функции.
Все необходимые данные для функций должны передаваться им в качестве параметров. Использование глобальных переменных в функциях не допускается.
4. В соответствии с вариантом, используя прямую рекурсию, написать и выполнить программу. Номер варианта определяется по формуле (N mod 23) + 1,
где N - номер студента по списку преподавателя.
Вариант
1. Напечатать в обратном порядке последовательность чисел, признаком конца
которой является 0.
2. Для n = 12 найти числа Фибоначчи. Числа Фибоначчи: F (0) = 1, F (1) = 1
F (n) = F (n − 2) + F (n − l)
3. Даны целые числа m и n, где 0 ≤ m ≤ n, вычислить, используя рекурсию,
m + C m−1 при
число сочетаний (n, m) по формуле: Cn0 = Cnn = 1, Cnm = Cn−1
n−1
n!
0 ≤ m ≤ n. Воспользовавшись формулой Cnm = m!(n−m)! можно проверить
правильность результата.
4. Опишите рекурсивную функцию, которая по заданным вещественному x и
целому п вычисляет величину xn согласно формуле:

n = 0;
 1,
1
n
,
n < 0;
x =
 x|n| n−1 x· x
, n > 0.
259
7 Лабораторная работа № 7
5. Задана последовательность положительных чисел, признаком конца которых
служит отрицательное число. Используя рекурсию, подсчитать количество
чисел и их сумму.
6. Дан вектор X из n вещественных чисел. Найти минимальный элемент вектора,
используя вспомогательную рекурсивную функцию, находящую минимум среди
последних элементов вектора X, начиная с n- гo.
7. Напишите рекурсивную функцию для нахождения биномиальных коэффициентов (для заданного ≥ i ≥ j > 0 вычислите все Cij ):

при m = 0, n > 0 или m = n ≥ 0;
 1,
n
0,
при m > n > 0;
Cm =
 n−1
n−1 , иначе.
Cm−1 + Cm
8. Напишите программу вычисления функции Аккермана для всех неотрицательных целых аргументов m и n:

 A(0, n) = n + 1,
A(m, 0) = A(m − 1, 1),
m>0
A(n, m) =

A(m, n) = A(m − 1, A(m, n − 1)), m, n > 0.
9. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
 a·x n−1
R a·x
e
cos
x(a cos x+n sin x)

Z
+ n(n−1)
e cosn−2 xdx, n ≥ 2;

a2 +n2
a2 +n2
a·x
a·x
n
−e (a sin x+n cos x)
e cos xdx =
,
n = 1;
2
2

 ea·n a +n
n = 0.
a ,
10. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
 cosn−1 x sin x n−1 R
Z
+ n
cosn−2 xdx, n > 2;

n
n
1
x
cos xdx =
+ sin 2x,
n = 2;
 2 4
sin x,
n = 1.
11. Напишите рекурсивную функцию, которая вычисляет y =
формуле:
√
k
x по следующей
y0 = 1,
yn+1 = yn +
x
k−1
yn
− yn
k
,
здесь n = 0, 1, 2, . . .. За ответ принять приближение, для которого выполняется
условие |yn − yn+1 | < ε, где ε = 0, 0001.
260
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
12. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
 sinn−1 x cos x n−1 R
Z
+ n
sinn−2 xdx, n > 2;
 −
n
n
x
1
sin xdx =
− sin 2x,
n = 2;
 2 4
− cos x,
n = 1.
13. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
R dx

cos x
1
n−2
Z
− n−1
, n ≥ 2;

n−1 x + n−1
sin
sinn−2 x
dx
x
=
n = 1;
ln tan 2 ,

sinn x
x,
n = 0.
14. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
R

1
sin x
n−2
dx
Z
− n−1
+
, n ≥ 2;

n−1
cos
x n−1
cosn−2 x
dx
x
π
=
+
,
n = 1;
ln
tan
4
2

cosn x
x,
n = 0.
15. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
xn ea·x n R n−1 a·x
Z
−a x
e dx, n > 1;
n a·x
a
x e dx =
ea·x
(a · x − 1),
n = 1.
a2
16. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
( n m·x
R n−1 m·x
Z
n
x a
x
a dx, n > 1;
n m·x
nm·x
ln a − mm·x
ln a
x a dx =
a
xa
n = 1.
m ln a − m(ln a)2 ,
17. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
R
Z
x lnn x − n lnn−1 xdx, n > 1;
n
ln xdx =
x ln x − x,
n = 1.
18. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
( xm+1 n
R m n−1
Z
n
x
−
xi ln
xdx, n > 1;
m+1 ln
m+1
m n
h
x ln xdx =
ln
x
1
xm+1 m+1 − (m+1)2 ,
n = 1.
261
7 Лабораторная работа № 7
19. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
h
i
(
R
Z
x
dx
1
dx
+
(2n
−
3)
, n > 1;
2(n−1)a2 (a2 +x2 )n−1
(a2 +x2 )n−1
=
2
2
n
1
x
(a + x )
arctan ,
n = 1.
a
a
20. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:

R
tann−1 x

Z
 (n−1) − tann−2 xdx, n ≥ 2;
tann xdx =
− ln | cos x|,
n = 1;

 x,
n = 0.
21. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:

R
ctann−1 x

Z
 − (n−1) − ctann−2 xdx, n ≥ 2;
ctann xdx =
ln | sin x|,
n = 1;

 x,
n = 0.
22. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
R m−1
xm
Z
− a cos ax + m
x
cos axdx, m ≥ 1;
m
a
x sin axdx =
m = 0.
− cosaax ,
23. Для заданных границ интегрирования a и b вычислите значение определенного
интеграла следующего вида:
R m−1
xm
Z
− a sin ax + m
x
sin axdx, m ≥ 1;
m
a
x cos axdx =
sin ax
− a ,
m = 0.
262
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Что представляет собой функция в С++? Что нужно для ее использования?
2. Приведите пример заголовка функции.
3. Что включает в себя определение функции?
4. В чем отличие функции от других программных объектов?
5. Каким образом происходит вызов функции?
6. Охарактеризуйте существующие способы решения проблемы получения из
подпрограммы признака ее аварийного завершения.
7. Каков механизм передачи параметров в функцию?
8. Способы передачи входных данных.
9. Что представляет собой средство С++, называемое значениями параметров по
умолчанию?
10. Каким образом происходит передача в функцию имени функции?
11. Каким образом происходит передача одномерных массивов в функцию?
12. Каким образом происходит передача строк в функцию?
13. Каким образом происходит передача двумерных массивов в функцию?
14. Каким образом происходит передача структур в функцию?
15. Какая функция называется рекурсивной? Преимущества и недостатки рекурсии.
16. Что называется перегрузкой функций? Перечислите правила описания перегруженных функций.
263
7 Лабораторная работа № 7
17. Область применения шаблонов.
18. Что такое инстанцирование? Способы инстанцирования шаблона.
264
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
7.5 Пример выполнения лабораторной работы № 7:
7.5.1 Индивидуальное задание № 1:
1.1. Постановка задачи:
выполнить задание, оформив каждый пункт в виде функции. Все необходимые
данные для функций должны передаваться им в качестве параметров. Использование
глобальных переменных в функциях не допускается.
Задача:
построить результат сглаживания заданной вещественной матрицы размером 10 на
10. В сглаженной матрице найти сумму модулей элементов, расположенных выше
главной диагонали. Ввод и вовод данных в программе осуществить с помощью файла.
Примечание. Операция сглаживания матрицы дает новую матрицу того же размера, каждый элемент которой получается как среднее арифметическое имеющихся
соседей соответствующего элемента исходной матрицы.
1.2. Листинг программы:
// Лабораторная работа № 7
// Индивидуальное задание № 1
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int sort_array(int ncol, int nrow);
int sort_array(int ncol, int nrow)
{
ifstream fin("input.txt",ios::in);
if (!fin)
{
cout << "Can’t␣find␣input.txt" << endl;
return 1;
}
ofstream fout("output.txt");
if (!fout)
{
cout << "Write␣falure:␣check␣permission" <<endl;
return 1;
265
7 Лабораторная работа № 7
}
int k, l, k1, l1, counter, i, j;
double sum,sumdiag = 0;
double a[nrow][ncol];
cout << "Чтение данных из файла..." << endl;
for(i = 0; i < nrow; i++)
for(j = 0; j < ncol; j++)
fin >> a[i][j];
cout << "Обработка данных..." << endl;
counter = 0;sum = 0; l = 0; l1 = 0;
double m[nrow][ncol];
//calculate neighbour’ sum & sum diagonalies
for(i = 0; i < nrow; i++)
for(j = 0; j < ncol; j++)
{
k
k1
if
if
if
= i -1;
= i + 1;
(k<0) k++;
(k1 > ncol- 1) k1--;
((j <= ncol - 1)&&(j >= ncol - i)) sumdiag = sumdiag+a[i][j];
for(k; k <= k1; k++)
{
l = j - 1;
l1 = j + 1;
if (l < 0) l++;
if (l1 > ncol - 1) l1--;
for(l; l <= l1; l++)
if ((k == i) && (l == j)) continue;
else
{
sum = sum + a[k][l];
counter++;
}
}
m[i][j] = (float) sum/counter;
sum = 0;
counter = 0;
}
266
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
for(i = 0; i < nrow; i++)
{
for(j =
fout <<
<<
<<
0; j < ncol; j++)
setw(5) << left
setprecision(3)
m[i][j] <<"␣" << endl;
}
fout << endl;
fout << "Сумма эл-ов ниже главной диагонали: "
<< setw(5) << setprecision(9) << sumdiag << endl;
return 0;
}
int main()
{
const int nrow = 10;
const int ncol = 10;
if (!sort_array(ncol,nrow))
{
cout<<"Запись результатов обработки в файл..."<<endl;
cout<<"Обработка успешно закончена!"<<endl;
return 0;
}
else
{
cout<<"Ошибка обработки данных!"<<endl;
return 1;
}
}
1.3. Результаты работы программы:
Файл для входных данных: input.txt
18.23
15.02
18.23
15.02
18.23
15.02
19.11
78.36
19.11
78.36
19.11
78.36
20.42
22.31
20.42
22.31
20.42
22.31
21.23
23.00
21.23
23.00
21.23
23.00
32.20
21.10
32.20
21.10
32.20
21.10
43.34
87.39
43.34
87.39
43.34
87.39
32.66
73.18
32.66
73.18
32.66
73.18
267
45.44
89.43
45.44
89.43
45.44
89.43
66.23
61.57
66.23
61.57
66.23
61.57
23.45
56.56
23.45
56.56
23.45
56.56
7 Лабораторная работа № 7
18.23
15.02
18.23
15.02
19.11
78.36
19.11
78.36
20.42
22.31
20.42
22.31
21.23
23.00
21.23
23.00
32.20
21.10
32.20
21.10
43.34
87.39
43.34
87.39
32.66
73.18
32.66
73.18
268
45.44
89.43
45.44
89.43
66.23
61.57
66.23
61.57
23.45
56.56
23.45
56.56
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Файл для выходных данных: output.txt
37.5
30.6
41.2
30.6
41.2
30.6
41.2
30.6
41.2
38.6
30.9
19.1
33.8
19.1
33.8
19.1
33.8
19.1
33.8
19
32.8
27.9
36
27.9
36
27.9
36
27.9
36
32.4
23.8
23.9
23.2
23.9
23.2
23.9
23.2
23.9
23.2
23.5
39.2
38
40.9
38
40.9
38
40.9
38
40.9
41.4
49.3
38.8
53.5
38.8
53.5
38.8
53.5
38.8
53.5
40.5
67.8
52.5
73.6
52.5
73.6
52.5
73.6
52.5
73.6
59.7
64.6
52.9
68.4
52.9
68.4
52.9
68.4
52.9
68.4
55.8
55.3
52
60.5
52
60.5
52
60.5
52
60.5
56.2
Сумма эл-ов ниже главной диагонали: 2343.13
Рис. 7.1: Вывод программы на экран
269
61.5
48.2
60.5
48.2
60.5
48.2
60.5
48.2
60.5
50.4
7 Лабораторная работа № 7
7.5.2 Индивидуальное задание № 2:
2.1. Постановка задачи:
выполнить задания согласно варианта индивидуального задания № 1 лабораторной
работы № 7, оформив в виде функций законченные последовательности действий. Все
необходимые данные для функций должны передаваться им в качестве параметров.
Использование глобальных переменных в функциях не допускается.
2.2. Листинг программы:
// Лабораторная работа № 7
// Индивидуальное задание № 2
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int readfile(string fileName)
{
ifstream fin(fileName.c_str(),ios::in);
if (!fin)
{
cout << "Ошибка открытия файла" << endl;
return 1;
}
int nword;
cout << "Введите искомое число слов в предложении: ";
cin >> nword;
fin.seekg(0,ios::end);
int len = fin.tellg();
char *buf = new char [len + 1];
fin.seekg(0,ios::beg);
fin.read(buf,len);
buf[len] = ’\0’;
int l_beg = 0, i = 0, n = 0, j = 0;
bool exist;
exist = false;
while (buf[i])
{
if (buf[i] == ’␣’) n++;
270
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
if (buf[i] == ’.’)
{
n++;
if (n == nword)
{
for(j =
cout <<
exist =
cout <<
l_beg; j <= i; j++)
buf[j];
true;
endl;
}
l_beg = i + 2;
i = i + 2;
n = 0;
}
i++;
}
if (!exist)
cout << "Таких предложений не найдено" << endl;
fin.close();
return 0;
}
int main()
{
setlocale( LC_ALL, "Russian" );
cout <<"\n"
<<"\tЛабораторная работа № 7\n"
<<"\n"
<<"------------------------------------------\n"
<<"Задание: Считать текст из файла."
<<"Вывести на экран только предложения, \n"
<<"\tсостоящие из заданного количества слов\n"
<<"-----------------------------------------\n";
string str;
cout << "Какой файл открыть? ";
cin >> str;
readfile(str);
return 0;
}
271
7 Лабораторная работа № 7
2.3. Результаты работы программы:
Файл для входных данных: text.txt
Под файловой системой (ФС) будем понимать набор спецификаций, отвечающих
за создание, уничтожение, организацию, а также управление доступом к
именованным областям памяти (файлам). Как правило, все современные
операционные системы имеют соответствующее ФС специфическое программное
обеспечение, называемое системой управления файлами (СУФ).
Несмотря на то, что понятия "ФС" и "СУФ" в большинстве источников
отождествляют, в некоторых ситуациях важно помнить их отличия. Термин "ФС"
определяет, прежде всего, принципы доступа к данным, организованных в
файлы, а термин "СУФ" следует употреблять по отношению к конкретной
реализации ФС, то есть комплекс программных модулей, обеспечивающих работу
с файлами в конкретной операционной сестеме.
В настоящее время количество ФС измеряется сотнями, причем по около 120-ти из
них доступна документация в интернете. Среди этого числа автор выделяет 12
современных ФС, то есть ФС, получивших в настоящее время более широкое
распространение.
272
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
7.5.3 Индивидуальное задание № 3:
3.1. Постановка задачи:
дан вектор X из n вещественных чисел. Найти минимальный элемент вектора, используя вспомогательную рекурсивную функцию, находящую минимум среди последних
элементов вектора X, начиная с n -гo.
3.2. Листинг программы:
// Лабораторная работа № 7
// Индивидуальное задание № 3
#include <iostream>
#include <math.h>
using namespace std;
float sort_array(float array[], int size, int pos)
{
float min = array[pos];
pos++;
for(int i = pos; i < 7; i++)
{
if (array[i] < min) min = array[i];
sort_array(array, size, pos);
}
return min;
}
int main()
{
int size = 0;
cout << "Введите количество элементов вектора X: ";
cin >> size;
float *array = new float [size];
for(int i = 0; i < size; i++)
{
cout << "Введите " <<i<< "␣элемент вектора X: ";
cin >> array[i];
}
int pos = 0;
cout << "С какого элемента начать сортировку? ";
cin >> pos;
273
7 Лабораторная работа № 7
cout
cout
cout
cout
<<
<<
<<
<<
<<
endl;
"Ввод данных закончен...\n";
"Идет процесс сортировки...\n";
"Минимальный элемент вектора X: "
sort_array(array,size,pos) << "\n";
return 0;
}
2.3. Результаты работы программы:
Рис. 7.2: Вывод программы на экран
274
Лабораторная работа № 8
Строки и файлы в языке C++
Цель работы и содержание:
закрепление знаний о строках и файлах, составление программ со строками и файлами.
Ход работы
8.1 Основные сведения о строках в языке С++.
C++ есть два вида строк: С-строки и класс стандартной библиотеки C++ string.
С-строка представляет собой массив символов, завершающийся символом с кодом 0.
Класс string более безопасен в использовании, чем С-строки, но и более ресурсоемок.
Описание строк. Память под строки, как и под другие массивы, может выделяться как компилятором, так и непосредственно в программе. Длина динамической
строки может задаваться выражением, длина нединамической строки должна быть
только константным выражением. Чаще всего длина строки задается частным случаем константного выражения - константой. Удобно задавать длину с помощью
именованной константы, поскольку такой вариант, во-первых, лучше читается, а вовторых, при возможном изменении длины строки потребуется изменить программу
только в одном месте:
const int len_str = 80;
char str [len_str];
При задании длины необходимо учитывать завершающий нуль-символ. Например, в строке, приведенной выше, можно хранить не 80 символов, а только 79.
Строки можно при описании инициализировать строковыми константами, при этом
нуль-символ в позиции, следующей за последним заданным символом, формируется
автоматически:
275
8 Лабораторная работа № 8
char a[100] = "Never␣trouble␣trouble";
Если строка при определении инициализируется, ее размерность можно опускать (компилятор сам выделит память, достаточную для размещения всех символов
строки и завершающего нуля):
char a[ ] = "Never␣trouble␣trouble"; // 22 символа
Для размещения строки в динамической памяти надо описать указатель на
char, а затем выделить память с помощью new или mallос (первый способ предпочтительнее):
char *p = new char [m];
char *q = (char *)malloc(m * sizeof(char));
Естественно, что в этом случае длина строки может быть переменной и задаваться на этапе выполнения программы. Динамические строки, как и другие
динамические массивы, нельзя инициализировать при создании. Оператор
char *str = "Never␣trouble␣trouble"
создает не строковую переменную, а указатель на строковую константу, изменить
которую невозможно.
Ввод-вывод строк. Для ввода-вывода строк используются как уже известные
нам объекты cin и cout, так и функции, унаследованные из библиотеки С. Рассмотрим
сначала первый способ:
#include<iostream>
using namespace std;
int main()
{
const int n = 80;
char s[n];
cin>>s;
cout << s << endl;
return 0;
}
276
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Строка вводится точно так же, как и переменные известных нам типов. Если
запустите программу и ввести строку из нескольких слов, выводится только первое
слово. Это связано с тем, что ввод выполняется до первого пробельного символа (то
есть пробела, знака табуляции или символа перевода строки ’\n’).
Можно ввести слова входной строки в отдельные строковые переменные:
#include <iostream>
using namespace std;
int main()
{
const int n = 80;
char s[n], t[n], r[n];
cin >> s >> t >> r;
cout << s << endl << t << endl << r << endl;
return 0;
}
Если требуется ввести строку, состоящую из нескольких слов, в одну строковую переменную, используются методы getline или get класса istream, объектом
которого является cin. Синтаксис вызова метода - после имени объекта ставится
точка, а затем пишется имя метода:
#include<iostream>
using namespace std;
int main()
{
const int n = 80;
char s[n];
cin.getline(s, n); cout << s << endl;
cin.get(s, n);
cout << s << endl;
return 0;
}
Метод getline считывает из входного потока n - 1 символов или менее (если
символ перевода строки встретится раньше) и записывает их в строковую переменную
s. Символ перевода строки также считывается (удаляется) из входного потока, но
не записывается в строковую переменную, вместо него размещается завершающий
0. Если в строке исходных данных более n - 1 символов, следующий ввод будет
выполняться из той же строки, начиная с первого несчитанного символа.
Метод get работает аналогично, но оставляет в потоке символ перевода строки.
В строковую переменную добавляется завершающий 0.
277
8 Лабораторная работа № 8
Никогда не обращайтесь к разновидности метода get с двумя аргументами два
раза подряд, не удалив \n из входного потока. Например:
cin.get(s, n);
cout << s << endl;
cin.get(s, n);
cout << s << endl;
cin.get(s, n);
cout << s << endl;
cout << "Конец-делу венец" << endl;
//1-считывание строки
//2-вывод строки
//3-считывание строки
//4-вывод строки
//5-считывание строки
//6-вывод строки
//7
При выполнении этого фрагмента вы увидите на экране первую строку, выведенную оператором 2, а затем завершающее сообщение, выведенное оператором 7.
Какие бы прекрасные строки вы ни ввели с клавиатуры в надежде, что они будут
прочитаны операторами 3 и 5, метод get в данном случае «уткнется» в символ
\n, оставленный во входном потоке от первого вызова этого метода (оператор 1).
В результате будут считаны и, соответственно, выведены на экран пустые строки
(строки, содержащие 0 символов). А символ \n так и останется «торчать» во входном
потоке. Возможное решение этой проблемы - удалить символ \n из входного потока
путем вызова метода get без параметров, то есть после операторов 1 и 3 нужно
вставить вызов cin.get().
Однако есть и более простое решение - использовать в таких случаях метод
getline, который после прочтения строки не оставляет во входном потоке символ
\n.
Если в программе требуется ввести несколько строк, метод getline удобно
использовать в заголовке цикла, например:
#include <iostream>
using namespace std;
int main()
{
const int n = 80;
char s[n];
while(cin.getline(s, n))
{
cout << s << endl;
//...
// обработка строки
}
return 0;
}
278
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рассмотрим теперь способы ввода-вывода строк, перекочевавшие в C++ из языка
С. Во-первых, можно использовать для ввода строки известную нам функцию scanf,
а для вывода - printf, задав спецификацию формата %s:
#include <stdio.h>
int main()
{
const int n = 10;
char s[n];
scanf("%s", s);
printf("%s", s);
return 0;
}
Имя строки, как и любого массива, является указателем на его начало, поэтому
использовавшаяся в предыдущих примерах применения функции scanf операция
взятия адреса (&) опущена. Ввод будет выполняться так же, как и для классов
ввода-вывода - до первого пробельного символа. Чтобы ввести строку, состоящую из
нескольких слов, используется спецификация %s (символы) с указанием максимального количества вводимых символов, например:
scanf("%10s", s);
Количество символов может быть только целой константой. При выводе можно
задать перед спецификацией & количество позиций, отводимых под строку:
printf("%15s", s);
Строка при этом выравнивается по правому краю отведенного поля. Если
заданное количество позиций недостаточно для размещения строки, оно игнорируется,
и строка выводится целиком.
#include <stdio.h>
int main()
{
const int n = 10;
char s[n];
fgets(s,n,stdin);
puts(s);
return 0;
}
279
8 Лабораторная работа № 8
Функция fgets(s,n,stdin) читает символы с клавиатуры до появления символа новой строки и помещает их в строку s. Функция возвращает указатель на
строку s, а в случае возникновения ошибки или конца файла - NULL.
Функция puts(s) выводит строку s на стандартное устройство вывода, заменяя
завершающий 0 символом новой строки. Возвращает неотрицательное значение при
успехе или EOF при ошибке.
Функциями семейства printf удобнее пользоваться в том случае, если в одном
операторе требуется ввести или вывести данные различных типов. Если же работа
выполняется только со строками, проще применять специальные функции для вводавывода строк fgets и puts.
8.2 Операции со строками.
Для строк не определена операция присваивания, поскольку строка является не
основным типом данных, а массивом. Присваивание выполняется с помощью функций
стандартной библиотеки или посимвольно «вручную» (что менее предпочтительно,
так как чревато ошибками). Например, чтобы присвоить строке р строку а, можно
воспользоваться функциями strcpy или strncpy:
char a[100] = "Never␣trouble␣trouble";
char *p = new char[m];
strcpy (p, a);
strncpy (p, a, strlen(a) + 1);
Для использования этих функций к программе следует подключить заголовочный файл <string.h>.
Функция strcpy(р, а) копирует все символы строки, указанной вторым параметром (а), включая завершающий 0, в строку, указанную первым параметром
(р).
Функция strncpy (p, а, n) выполняет то же самое, но не более n символов, то
есть числа символов, указанного третьим параметром. Если нульсимвол в исходной
строке встретится раньше, копирование прекращается, а оставшиеся до n символы
строки р заполняются нуль-символами. В противном случае (если n меньше или
равно длине строки а) завершающий нульсимвол в р не добавляется.
Обе эти функции возвращают указатель на результирующую строку. Если области памяти, занимаемые строкой-назначением и строкой-источником, перекрываются,
поведение программы не определено.
Функция strlen(a) возвращает фактическую длину строки а, не включая
нуль-символ.
280
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Программист должен сам заботиться о том, чтобы в строке-приемнике хватило
места для строки-источника (в данном случае при выделении памяти значение
переменной m должно быть больше или равно 100), и о том, чтобы строка всегда
имела завершающий нуль-символ.
Выход за границы строки и отсутствие нуль-символа являются распространенными причинами ошибок в программах обработки строк.
Для преобразования строки в целое число используется функция atoi(stг).
Функция преобразует строку, содержащую символьное представление целого числа,
в соответствующее целое число. Признаком конца числа служит первый символ,
который не может быть интерпретирован как принадлежащий числу. Если преобразование не удалось, возвращает 0.
Аналогичные функции преобразования строки в длинное целое число (long)
и в вещественное число с двойной точностью (double) называются atol и atof
соответственно.
Пример применения функций преобразования:
char a[ ] = "10)␣Рост-162см, вес-59.5кг";
int num;
long height;
double weight;
num = atoi(a);
height = atol(&a[13]);
weight = atof(&a[29]);
cout << num << ’’ << height << ’’ << weight;
Библиотека предоставляет также различные функции для сравнения строк и
подстрок, объединения строк, поиска в строке символа и подстроки и выделения из
строки лексем.
8.3 Работа с символами.
Для хранения отдельных символов используются переменные типа char. Их
ввод-вывод также может выполняться как с помощью классов ввода-вывода, так и с
помощью функций библиотеки.
При использовании классов ввод-вывод осуществляется как с помощью операций помещения в поток (и извлечения из потока), так и методов get() и get(char).
Ниже приведен пример применения операций:
#include <iostream>
using namespace std;
int main()
{
281
8 Лабораторная работа № 8
char c, d, e;
cin >> c;
cin >> d >> e;
cout << c << d << e << endl;
return 0;
}
Вводимые символы могут разделяться или не разделяться пробельными символами, поэтому таким способом ввести символ пробела нельзя. Для ввода любого
символа, включая пробельные, можно воспользоваться методами get() или get(с):
#include <iostream>
using namespace std;
int main()
{
char c, d, e;
c = cin.get(); cin.get(d); cin.get(e);
cout << c << d << e << endl;
return 0;
}
Метод get() возвращает код извлеченного из потока символа, а метод get(с)
записывает извлеченный символ в переменную, переданную ему в качестве аргумента,
и возвращает ссылку на поток.
В заголовочном файле <stdiо.h> определена функция getchar() для ввода
символов со стандартного ввода, а также putchar() для вывода:
#include <iostream>
int main()
{
char c, d;
c = getchar();
putchar(c);
d = getchar();
putchar(d);
return 0;
}
В библиотеке также определен целый ряд функций, проверяющих принадлежность символа какому-либо множеству, например множеству букв (isalfa),
разделителей (isspace), знаков пунктуации (ispunct), цифр (isdigit) и т. д.
282
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Пример 8.1. Поиск подстроки
Написать программу, которая определяет, встречается ли в заданном текстовом
файле заданная последовательность символов. Длина строки текста не превышает
80 символов, текст не содержит переносов слов, последовательность не содержит
пробельных символов.
I. Исходные данные и результаты
Исходные данные:
1. Текстовый файл неизвестного размера, состоящий из строк длиной не
более 80 символов. Поскольку по условию переносы отсутствуют, можно
ограничиться поиском заданной последовательности в каждой строке отдельно. Следовательно, необходимо помнить только одну текущую строку
файла. Для ее хранения выделим строковую переменную длиной 81 символ
(дополнительный символ требуется для завершающего нуля).
2. Последовательность символов для поиска, вводимая с клавиатуры. Поскольку по условию задачи она не содержит пробельных символов, ее
длина также не должна быть более 80 символов, иначе поиск завершится
неудачей. Для ее хранения также выделим строковую переменную длиной
81 символ.
Результатом работы программы является сообщение либо о наличии заданной
последовательности, либо об ее отсутствии. Представим варианты сообщений в
программе в виде строковых констант.
Для хранения длины строки будем использовать именованную константу. Для
работы с файлом потребуется служебная переменная соответствующего типа.
II. Алгоритм решения задачи
1. Построчно считывать текст из файла.
2. Для каждой строки проверять, содержится ли в ней заданная последовательность.
3. Если да, напечатать сообщение о наличии заданной последовательности и
завершить программу.
4. При нормальном выходе из цикла напечатать сообщение об отсутствии
заданной последовательности и завершить программу.
III. Программа и тестовые примеры
Листинг 8.1
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
283
8 Лабораторная работа № 8
int main()
{
const int len = 81;
char word[len], line[len];
//1
//2
cout <<"Введите слово для поиска:"; cin >> word;
ifstream fin("text.txt",ios::in);
if (!fin)
{
cout <<"Ошибка открытия файла." << endl;
return 1;
}
//3
//4
while (fin.getline(line, len))
{
if (strstr(line, word))
{
cout <<"Присутствует!" <<endl;
}
else
{
cout << "Отсутствует!" << endl;
}
}
//5
return 0;
//7
//6
}
Рассмотрим помеченные операторы. В операторе 1 описывается константа,
определяющая длину строки файла и длину последовательности. В операторе
2 описывается переменная line для размещения очередной строки файла и
переменная word для размещения искомой последовательности символов.
В операторе 3 определяется объект fin класса входных потоков ifstream. С
этим бъектом можно работать так же, как со стандартными объектами cin
и cout, то есть использовать операции помещения в поток (и извлечения из
потока).
Предполагается, что файл с именем text.txt находится в том же каталоге,
что и текст программы, иначе следует указать полный путь, дублируя символ
284
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
обратной косой черты, так как иначе он будет иметь специальное значение,
например:
ifstream fin("text.txt",ios::in); // 3
В операторе 4 проверяется успешность создания объекта fin. Файлы, открываемые для чтения, проверять нужно обязательно!
В операторе 5 организуется цикл чтения из файла в переменную line.
Метод getline, описанный выше, при достижении конца файла вернет значение,
завершающее цикл.
Для анализа строки в операторе 6 применяется функция strstr(line, word).
Она выполняет поиск подстроки word в строке line. Обе строки должны
завершаться нуль-символами. В случае успешного поиска функция возвращает
указатель на найденную подстроку, в случае неудачи - NULL.
Если вторым параметром передается указатель на строку нулевой длины,
функция возвращает указатель на начало строки line.
В качестве тестового примера приготовьте текстовый файл, состоящий из
нескольких строк. Длина хотя бы одной из строк должна быть равна 80 символам. Для тестирования программы следует запустить ее по крайней мере два
раза: введя с клавиатуры слово, содержащееся в файле, и слово, которого в
нем нет.
Даже такую простую программу мы рекомендуем вводить и отлаживать по
шагам. Предлагаемая последовательность отладки:
1. Ввести «скелет» программы (директивы ]include, функцию main(), операторы 1-4). Добавить контрольный вывод введенного слова. Запустив
программу, проверить ввод слова и успешность открытия файла. Выполнить программу, задав имя несуществующего файла, для проверки вывода
сообщения об ошибке. Удалить контрольный вывод слова.
2. Проверить цикл чтения из файла: добавить оператор 5 с его завершающей фигурной скобкой, внутри цикла поставить контрольный вывод
прочитанной строки:
cout << line << endl;
Удалить контрольный вывод строки.
3. Дополнить программу операторами проверки и вывода сообщений. Для
полной проверки программы следует выполнить ее для нескольких последовательностей. Длина одной из них должна составлять максимально
допустимую - 80 символов.
285
8 Лабораторная работа № 8
Пример 8.2. Подсчет количества вхождений слова в
текст
Написать программу, которая определяет, сколько раз встретилось заданное
слово в текстовом файле, длина строки в котором не превышает 80 символов. Текст
не содержит переносов слов.
На первый взгляд эта программа не сильно отличается от предыдущей: вместо
факта наличия искомой последовательности в файле требуется подсчитать количество вхождений слова, то есть после первого удачного поиска не выходить из цикла
просмотра, а увеличить счетчик и продолжать просмотр. В целом это верно, однако
в данной задаче нам требуется найти не просто последовательность символов, а
законченное слово.
Определим слово как последовательность алфавитно-цифровых символов, после которых следует знак пунктуации, разделитель или признак конца строки. Слово
может находиться либо в начале строки, либо после разделителя или знака пунктуации. Это можно записать следующим образом (фигурные скобки и вертикальная
черта означают выбор из альтернатив):
слово = (начало строки | знак пунктуации | разделитель)
символы, составляющие слово (конец строки | знак пунктуации | разделитель)
I. Исходные данные и результаты
Исходные данные:
1. Текстовый файл неизвестного размера, состоящий из строк длиной не
более 80 символов. Поскольку по условию переносы отсутствуют, можно
ограничиться поиском слова в каждой строке отдельно. Для ее хранения
выделим строку длиной 81 символ.
2. Слово для поиска, вводимое с клавиатуры. Для его хранения также выделим строку длиной 81 символ.
Результатом работы программы является количество вхождений слова в текст.
Представим его в программе в виде целой переменной.
Для хранения длины строки будем использовать именованную константу, а для
хранения фактического количества символов в слове - переменную целого типа.
Для работы с файлом потребуется служебная переменная соответствующего
типа.
II. Алгоритм решения задачи
1. Построчно считывать текст из файла.
2. Просматривая каждую строку, искать в ней заданное слово. При каждом
нахождении слова увеличивать счетчик.
Детализируем второй пункт алгоритма. Очевидно, что слово может встречаться
в строке многократно, поэтому для поиска следует организовать цикл просмот-
286
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
ра строки, который будет работать, пока происходит обнаружение в строке
последовательности символов, составляющих слово.
При обнаружении совпадения с символами, составляющими слово, требуется
определить, является ли оно отдельным словом, а не частью другого. Например, мы задали слово «кот». Эта последовательность символов содержится,
например, в словах «котенок», «трикотаж», «трескотня» и «апперкот». Следовательно, требуется проверить символ, стоящий после слова, а в случае, когда
слово не находится в начале строки - еще и символ перед словом. Эти символы
проверяются на принадлежность множеству знаков пунктуации и разделителей.
III. Программа и тестовые примеры
Разобьем написание программы на последовательность шагов.
Шаг 1. Ввести «скелет» программы (директивы ]include, функцию main(), описание переменных, открытие файла). Добавить контрольный вывод введенного
слова. Запустив программу, проверить ввод слова и успешность открытия файла. Для проверки вывода сообщения об ошибке следует выполнить программу
еще раз, задав имя несуществующего файла.
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
const int len = 81;
char word[len], line[len];
cout << "Введите слово для поиска:";
cin >> word;
ifstream fin("text.txt",ios::in);
if (!fin)
{
cout << "Ошибка открытия файла." << endl;
}
return 0;
}
Шаг 2. Добавить в программу цикл чтения из файла, внутри цикла поставить контрольный вывод считанной строки (добавляемые операторы помечены
признаком комментария):
#include <fstream>
#include <iostream>
using namespace std;
287
8 Лабораторная работа № 8
int main()
{
const int len = 81;
char word[len], line[len];
cout << "Введите слово для поиска:";
cin >> word;
ifstream fin("text.txt",ios::in);
if (!fin)
{
cout << "Ошибка открытия файла." <<endl;
return 1;
}
while (fin.getline(line,len))
{
cout << line << endl;
}
return 0;
}
Шаг 3. Добавить в программу цикл поиска последовательности символов,
составляющих слово, с контрольным выводом:
#include <fstream>
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
const int len = 81;
char word[len], line[len];
cout << "Введите слово для поиска:";
cin >> word;
int l_word = strlen(word);
ifstream fin("text.txt",ios::in);
if (!fin)
{
cout << "Ошибка открытия файла." <<endl;
return 1;
}
288
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int count = 0;
while (fin.getline(line,len))
{
char *p = line;
while (p = strstr(p,word))
{
cout << "Совпадение:" << p << endl;
p += l_word; count++;
}
}
cout << count << endl;
return 0;
}
Для многократного поиска вхождения подстроки в заголовке цикла используется функция strstr. Очередной поиск должен выполняться с позиции,
следующей за найденной на предыдущем проходе подстрокой. Для хранения
этой позиции определяется вспомогательный указатель р, который на каждой
итерации цикла наращивается на длину подстроки. Также вводится счетчик
количества совпадений. На данном этапе он считает не количество слов, а
количество вхождений последовательности символов, составляющих слово.
Шаг 4. Добавить в программу анализ принадлежности символов, находящихся
перед словом и после него, множеству знаков пунктуации и разделителей:
Листинг 8.2
#include <fstream>
#include <iostream>
#include <string.h>
#include <ctype.h>
using namespace std;
int main()
{
const int len = 81;
char word[len], line[len];
cout << "Введите слово для поиска: ";
cin >> word;
int l_word = strlen(word);
ifstream fin("text.txt",ios::in);
if (!fin)
289
8 Лабораторная работа № 8
{
cout << "Ошибка открытия файла." <<endl;
return 1;
}
int count = 0;
while (fin.getline(line,len))
{
char *p = line;
while (p == strstr(p,word))
{
char *c = p;
p += l_word; // слово не в начале строки?
if(c!=line) // символ перед словом не разделитель?
if(!ispunct(*(c-1)) && !isspace(*(c-1))) continue;
// символ после слова разделитель?
if(ispunct(*p) || isspace(*p) || (*p==’\0’)) count++;
}
}
cout << "Количество вхождений слова:" << count << endl;
return 0;
}
Здесь вводится служебная переменная с для хранения адреса начала вхождения
подстроки. Символы, ограничивающие слово, проверяются с помощью функций ispunct и isspace, прототипы которых хранятся в заголовочном файле
<ctype.h>. Символ, стоящий после слова, проверяется также на признак конца
строки (для случая, когда искомое слово находится в конце строки).
Для тестирования программы требуется создать файл с текстом, в котором
заданное слово встречается:
- в начале строки;
- в конце строки;
- в середине строки;
- несколько раз в одной строке;
- как часть других слов, находящаяся в начале, середине и конце этих слов;
- в скобках, кавычках и других разделителях.
Длина хотя бы одной из строк должна быть равна 80 символам. Для тестирования программы следует выполнить ее по крайней мере два раза: введя с
клавиатуры слово, содержащееся в файле, и слово, которого в нем нет.
Теперь рассмотрим другой вариант решения этой задачи. В библиотеке есть
функция strtok, которая разбивает переданную ей строку на лексемы в соответствии
290
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
с заданным набором разделителей. Если мы воспользуемся этой функцией, нам не
придется «вручную» выделять и проверять начало и конец слова, потребуется лишь
сравнить с искомым словом слово, выделенное с помощью strtok. Правда, список
разделителей придется задать вручную.
Пример 8.3. Вывод вопросительных предложений
Написать программу, которая считывает текст из файла и выводит на экран
только вопросительные предложения из этого текста.
I. Исходные данные и результаты
Исходные данные: текстовый файл неизвестного размера, состоящий из неизвестного количества предложений. Предложение может занимать несколько
строк, поэтому ограничиться буфером на одну строку в данной задаче нельзя.
Будем хранить длину файла в переменной длинного целого типа. Для организации вывода предложений понадобятся переменные того же типа, хранящие
позиции начала и конца предложения.
II. Алгоритм решения задачи
1. Открыть файл.
2. Определить его длину в байтах.
3. Выделить в динамической памяти буфер соответствующего размера.
4. Считать файл с диска в буфер.
5. Анализируя буфер посимвольно, выделять предложения. Если предложение оканчивается вопросительным знаком, вывести его на экран.
Детализируем последний пункт алгоритма. Для вывода предложения необходимо хранить позиции его начала и конца. Предложение может оканчиваться
точкой, восклицательным или вопросительным знаком. Во-первых двух случаях
предложение пропускается. Это выражается в том, что значение позиции начала
предложения обновляется. Оно устанавливается равным символу, следующему
за текущим, и просмотр продолжается. В случае обнаружения вопросительного
знака предложение выводится на экран, после чего также устанавливается
новое значение позиции начала предложения.
III. Программа и тестовые примеры
Ниже приводится текст программы. Рекомендуем вам самостоятельно разбить
его для отладки на последовательность шагов аналогично предыдущим
примерам, вставляя и удаляя отладочную печать. Файл с тестовым примером
должен содержать предложения различной длины (от нескольких символов до
291
8 Лабораторная работа № 8
нескольких строк), в том числе и вопросительные.
Листинг 8.3
#include <fstream>
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
ifstream fin("text.txt",ios::in);
if (!fin)
{
cout << "Ошибка открытия файла." << endl;
return 1;
}
fin.seekg(0,ios::end);
//1
long len = fin.tellg();
char *buf = new char[len + 1];
//2
//3
fin.seekg(0, ios :: beg);
fin.read(buf. len);
//4
//5
buf[len] = ’\0’;
long n = 0, i = 0, j = 0;
//6
while(buf[i])
//7
{
if(buf[i] = ’?’)
//8
{
for(j = n; j <= i; j++) cout<< buf[j];
n = i + j;
}
if(buf[i] == ’.’||buf[i] == ’!’) n = i + j;
i++;
}
fin.close();
cout << endl;
return 0;
//9
}
292
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Для определения длины файла используются методы seekg и tellg класса
stream. С любым файлом при его открытии связывается так называемая текущая позиция чтения или записи. Когда файл открывается для чтения, эта
позиция устанавливается на начало файла. Для определения длины файла
мы перемещаем ее на конец файла с помощью метода seekg (оператор 1), а
затем с помощью tellg получаем ее значение, запомнив его в переменной len
(оператор 2).
Метод seekg(offset, org) перемещает текущую позицию чтения из файла на
offset байтов относительно org. Параметр org может принимать одно из трех
значений:
ios::beg - от начала файла;
ios::cur - от текущей позиции;
ios::end - от конца файла.
beg, cur и end являются константами, определенными в классе ios, предке
ifstream, а символы :: означают операцию доступа к этому классу.
В операторе 3 выделяется len+1 байтов под символьную строку buf, в которой
будет храниться текст из файла. Мы выделяем на один байт больше, чем длина
файла, чтобы после считывания файла записать в этот байт нуль-символ.
Для чтения информации требуется снова переместить текущую позицию на
начало файла (оператор 4). Собственно чтение выполняется в операторе 5 с
помощью метода read(buf, len), который считывает из файла len символов
(или менее, если конец файла встретится раньше) в символьный массив buf.
В операторе 6 определяются служебные переменные. В переменной n будет
храниться позиция начала текущего предложения, переменная i используется
для просмотра массива, переменная j - для вывода предложения.
Цикл просмотра массива buf (оператор 7) завершается, когда встретился нульсимвол. Если очередным символом оказался вопросительный знак (оператор
8), выполняется вывод символов, начиная с позиции n до текущей, после чего
в переменную n заносится позиция начала нового предложения.
Оператор 9 (закрытие потока) в данном случае не является обязательным, так
как явный вызов close() необходим только тогда, когда требуется закрыть
поток раньше окончания действия его области видимости.
Если требуется вывести результаты выполнения программы не на экран, а в
файл, в программе следует описать объект класса выходных потоков ofstream,
а затем использовать его аналогично другим потоковым объектам, например:
ofstream fout("textout.txt");
if(!fout)
{
293
8 Лабораторная работа № 8
cout << "Ошибка открытия файла вывода" << endl;
return 1;
}
...
fout << buf[j];
требуется закрыть поток раньше окончания действия:
fout.close( );
чтение из файла. При использовании потоков оно выполняется с помощью
метода get(). Например, для программы, приведенной выше, посимвольный
ввод выглядит следующим образом:
while((buf[i] = fin.get()) != EOF
{
...
i++;
}
Надо учитывать, что посимвольное чтение из файла гораздо менее эффективно.
В заключение приведем вариант решения этой же задачи с использованием
вместо потоковых классов библиотечных функций, унаследованных из языка
С.
Листинг 8.4
#include <stdio.h>
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
FILE *fin = fopen("text.txt","r");
if (!fin)
{
puts("Ошибка открытия файла");
return 1;
}
294
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
fseek(fin, 0, SEEK_END);
long int len = ftell(fin);
cout << len;
char *buf = new char[len + 1];
const int l_block = 1024;
int num_block = len/l_block;
fseek(fin, 0, SEEK_SET);
fread(buf, l_block, num_block + 1, fin);
buf[len] = ’\0’;
long n = 0, i = 0, j = 0;
while(buf[i])
{
if (buf[i] == ’?’)
{
for (j = n; j <= i; j++)
putchar(buf[j]);
n = i + j;
}
}
fclose(fin);
printf("\n");
return 0;
}
В операторе 1 определяется указатель на описанную в заголовочном файле
<stdio.h> структуру FILE. Указатель именно такого типа формирует функция
открытия файла fopen. Ее вторым параметром задается режим открытия файла.
В данном случае файл открывается для чтения (r).
Файл можно открыть в двоичном (b) или текстовом (t) режиме. Эти символы
записывают во втором параметре, например, "rb" или "rt". Двоичный режим
означает, что символы перевода строки и возврата каретки (0x13 и 0x10) обрабатываются точно так же, как и остальные. В текстовом режиме эти символы
преобразуются в одиночный символ перевода строки. По умолчанию файлы
открываются в текстовом режиме.
Для позиционирования указателя текущей позиции используется функция
fseek с параметрами, аналогичными соответствующему методу потока (операторы 3 и 7). Константы, задающие точку отсчета смещения, описаны в
295
8 Лабораторная работа № 8
заголовочном файле <stdiо.h> и имеют имена:
SEEK_SET - от начала файла;
SEEK_CUR - от текущей позиции;
SEEK_END - от конца файла.
Чтение из файла выполняется функцией fread(buf, size, num, file) блоками по size байт. Требуется также задать количество блоков num. В программе
размер блока задан в переменной равным 1024, поскольку размер кластера кратен степени двойки. В общем случае чем более длинными блоками мы читаем
информацию, тем быстрее будет выполнен ввод. Для того чтобы обеспечить
чтение всего файла, к количеству блоков добавляется 1 для округления после
деления.
Вывод на экран выполняется посимвольно с помощью функции putchar.
Если требуется с помощью функций библиотеки вывести результаты выполнения программы не на экран, а в файл, в программе следует описать указатель
на структуру FILE, с помощью функции fopen открыть файл для записи (второй
параметр функции - w), а затем использовать этот указатель в соответствующих
функциях вывода, например:
FILE *fout;
fout = fopen("textout.txt", "w");
if (!fout)
{
puts("Ошибка открытия файла вывода");
return 1;
}
putc(buf[j], fout); // или fputc(buf[j], fout);
После окончания вывода файл закрывается с помощью функции fclose:
fclose(fout);
Смешивать в одной программе ввод-вывод с помощью потоковых классов и с
помощью функций библиотеки не рекомендуется.
В целом программа, написанная с использованием функций библиотеки, может получиться более быстродействующей, но менее безопасной, поскольку
программист должен сам заботиться о большем количестве деталей.
Пример 8.4. Вывод предложений, состоящих из
заданного количества слов
Написать программу, которая считывает текст из файла и выводит на экран
только предложения, состоящие из заданного количества строк.
296
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Листинг 8.5
#include <iostream>
#include <fstream>
using namespace std;
int main( )
{
cout<<"\n"
<<"Задание: \nСчитать текст из файла."
<<"Вывести на экран только предложения, \n"
<<"состоящие из заданного количества слов\n"
<<"----------------------------------------\n\n";
ifstream fin("text.txt",ios::in);
if (! fin)
{
cout<<"Ошибка открытия файла"<<endl;
return 1;
}
int nword;
cout<<"Введите искомое число слов в предложении: ";
cin>>nword;
cout<<"\n";
fin.seekg(0,ios::end);
int len=fin.tellg();
char *buf=new char [len+1];
fin.seekg(0,ios::beg);
fin.read(buf,len);
buf[len]=’\0’;
int l_beg=0,i=0,n=0,j=0;
bool exist;
exist=false;
while (buf[i])
{
if (buf[i]==’␣’) n++;
if (buf[i]==’.’)
297
8 Лабораторная работа № 8
{
n++;
if (n==nword)
{
for(j=l_beg;j<=i;j++)
cout<<buf[j];
exist=true;
cout<<endl;
}
l_beg = i+2;
i = i+2;
n = 0;
}
i++;
}
if (!exist) cout<<"Таких предложений не найдено!"<<endl;
fin.close();
return 0;
}
Пример 8.5. Вывод предложений, содержащих
максимальное количество знаков пунктуации
Написать программу, которая считывает текст из файла и выводит на экран
только предложения, содержащие максимальное количество знаков пунктуации.
Листинг 8.6
#include <iostream>
#include <fstream>
#include <locale>
using namespace std;
int main()
{
locale loc ("ru_RU.UTF8");
cout << "Задание:\nНаписать программу, которая"
<< "считывает текст из файла и выводит на экран\n"
<< "предложения, содержащие максимальное количество "
<< "знаков пунктуации.\n\n";
298
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
// открываем файл для чтения
ifstream FileInput("file.txt",ios::in);
// если файл не обнаружен, то аварийно выходим
if (!FileInput)
{
cout << "Ошибка! Файл не открыт!" << endl;
return 1;
}
cout << "Текст, в котором осуществляется поиск"
<< "предложения с max кол-вом знаков пунктуации\n\n";
cout << "Чтение из файла выполняется функцией"
<< "fread(buf,␣size,␣num,␣file)␣блоками по size"
<< "байт! Требуется также задать количество блоков num."
<< "В программе размер блока задан в переменной равным 1024,"
<< "поскольку размер кластера кратен степени двойки.В общем"
<< "случае чем более длинными блоками мы читаем информацию,"
<< "тем быстрее будет выполнен ввод.Для того, чтобы"
<< "обеспечить чтение всего файла, к количеству блоков"
<< "добавляется 1 для округления после деления.\n\n";
// устанавливаем позицию курсора в конец файла
FileInput.seekg(0,ios::end);
/*
узнаем длину файла методом tellg(), который
сообщает о позиции курсора в файле (т.е. сколько байт
прошел курсор)
*/
int LengthOfFile = FileInput.tellg();
// создаем переменную для хранения содержимого файла
char *str = new char [LengthOfFile+1];
// устанавливаем позицию курсора в начало файла
FileInput.seekg(0,ios::beg);
/*
считываем содержимое файла в переменную str
299
8 Лабораторная работа № 8
на заданное число байт LengthOfFile
*/
FileInput.read(str,LengthOfFile);
// добавляем символ конца строки в переменную str
str[LengthOfFile]=’\0’;
/*
pos - текущая позиция в файле;
counter - текущее кол-во пунктационных знаков;
max - текущее максимальное кол-во пунктационныхзнаков;
l - число символов в предложении;
begin, end - начало и конец предложения
с макс. кол-вом пункт. знаков
*/
int pos = 0,l = 0,end = 0,begin = 0;
int counter = 0,max = 0;
// поиск предложения c макс кол-вом пункт. знаков
while (str[pos])
{
/*
если текущий символ явл-ся пунктационным,
то счетчик увеличиваем на 1
*/
if (ispunct(str[pos],loc)) counter++;
// если достигнут конец предложения, то...
if (str[pos]==’.’ || str[pos]==’!’ || str[pos]==’?’)
{
/*
...при условии максиммального кол-ва
пунктационных символов вычисляем начало
и конец данного предложения...
*/
if (counter>max)
{
max = counter;
300
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
counter = 0;
/*
здесь вычисляем начало предложения
из текущего положения вычитаем кол-во
прошедших символов
*/
begin = pos - l;
end = pos;
l = 0;
}
/*
...иначе обнуляем количество пунктационных
символов и количество символов в пердложении
*/
else
{
counter = 0; l = 0;
}
}
l++;
pos++;
}
/*
Вывод предложения с максимальным
количеством пунктационных знаков
*/
for(l=begin; l < end + 1; l++)
cout << str[l];
return 0;
}
301
8 Лабораторная работа № 8
Пример 8.6. Определение количества слов в файле,
состоящих не более чем из четырех букв
Написать программу, которая считывает текст из файла и определяет, сколько
в нем слов, состоящих из не более чем четырех букв.
Листинг 8.7
// Лабораторная работа № 8
#include <fstream>
#include <string.h>
#include <iostream>
#include <conio.h>
#include <ctype.h>
using namespace std;
int main( )
{
int n, s, ss, i, ii;
const int len = 100;
char word[len], line[len];
cout << "\t\tЛабораторная работа №8\n\n";
cout << "Написать программу, которая считывает"
<< "текст из файла и определяет, сколько в нем слов,"
<< "состоящих из не более чем четырех букв. \n" ;
cout<<"\nНажмте клавишу для продолжения...\n\n" ;
getchar();
int lword = static_cast<int>(strlen(word));
ifstream fin("text.txt");
if (!fin)
{
cout << "Error␣opening␣file." <<endl;
return 1;
}
s = 0;
ss = 0;
int count = 0;
while (fin.getline(line,len))
{
char *p = line;
ii = strlen(p);
fin.seekg(0,ios::beg);
for (i = 0; i < ii; i++)
302
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
if (p[i]!=’\x20’) s++ ;
if (p[i]==’\x20’)
{
if (s < 5)
{
ss++;
}
s = 0;
}
}
}
cout << "\nВ файле содержатся слова, содержащие не"
<< "более 4-х букв в количестве" << ss << "␣шт.";
return 0;
}
303
8 Лабораторная работа № 8
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Составить программу с использованием двумерных локальных мас- сивов для
решения задачи. Размерности локальных массивов задавать имено- ванными
константами, значения элементов массива - в списке инициализации. Номер
варианта определяется по формуле (N mod 23) + 1, где N - номер студента по
списку преподавателя.
Вариант
1. Написать программу, которая считывает из текстового файла три предложения
и выводит их в обратном порядке.
2. Написать программу, которая считывает текст из файла и выводит на экран
только предложения, содержащие введенное с клавиатуры слово.
3. Написать программу, которая считывает текст из файла и выводит на экран
только строки, содержащие двузначные числа.
4. Написать программу, которая считывает английский текст из файла и выводит
на экран слова, начинающиеся с гласных букв.
5. Написать программу, которая считывает текст из файла и выводит его на
экран, меняя местами каждые два соседних слова.
6. Написать программу, которая считывает текст из файла и выводит на экран
только предложения, не содержащие запятых.
304
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
7. Написать программу, которая считывает текст из файла и определяет, сколько
в нем слов, состоящих из не менее чем семи букв.
8. Написать программу, которая считывает текст из файла и выводит на экран
только цитаты, то есть предложения, заключенные в кавычки.
9. Написать программу, которая считывает английский текст из файла и выводит
на экран слова текста, начинающиеся и оканчивающиеся на гласные буквы.
10. Написать программу, которая считывает текст из файла и выводит на экран
только строки, не содержащие двузначных чисел.
11. Написать программу, которая считывает текст из файла и выводит на экран
только предложения, начинающиеся с тире, перед которым могут находиться
только пробельные символы.
12. Написать программу, которая считывает английский текст из файла и выводит
его на экран, заменив каждую первую букву слов, начинающихся с гласной
буквы, на прописную.
13. Написать программу, которая считывает текст из файла и выводит его на
экран, заменив цифры от 0 до 9 на слова «ноль», «один», ..., «девять», начиная
каждое предложение с новой строки.
14. Написать программу, которая считывает текст из файла, находит самое длинное
слово и определяет, сколько раз оно встретилось в тексте.
15. Написать программу, которая считывает текст из файла и выводит на экран
сначала вопросительные, а затем восклицательные предложения.
16. Написать программу, которая считывает текст из файла и выводит его на
экран, после каждого предложения добавляя, сколько раз встретилось в нем
введенное с клавиатуры слово.
17. Написать программу, которая считывает текст из файла и выводит на экран
все его предложения в обратном порядке.
18. Написать программу, которая считывает текст из файла и выводит на экран
сначала предложения, начинающиеся с однобуквенных слов, а затем все остальные.
19. Написать программу, которая считывает текст из файла и выводит на экран
предложения, содержащие минимальное количество знаков пунктуации.
305
8 Лабораторная работа № 8
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Характеристика видов строк в языке С++.
2. Как описываются строки?
3. Охарактеризуйте способы ввода-вывода строк.
4. Что делают функции gets( ) и puts( )?
5. Каким образом выполняется операция присваивания при работе со строками?
6. Какие функции предоставляет библиотека <string.h>? Какие действия они
выполняют?
7. Охарактеризуйте способы ввода-вывода символов.
8. Какая функция используетя для многократного поиска вхождения подстроки?
9. Для чего нужна функция strtok?
10. Какие значения может принимать параметр org?
11. Какие действия необходимо выполнить, чтобы вывести результаты выполнения
программы не на экран, а в файл?
306
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
8.4 Пример выполнения лабораторной работы № 8:
8.4.1 Индивидуальное задание № 1:
1.1. Постановка задачи:
написать программу, которая считывает текст из файла и выводит на экран
только предложения, содержащие запятые.
1.2. Листинг программы:
// Лабораторная работа № 8
// Индивидуальное задание № 1
#include <iostream>
#include <fstream>
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
using namespace std;
int main()
{
cout << "Лабораторная работа № 8\n"
<< "\nНемо А.Б. гр. 1-18\n"
<< "\nВариант № 6\n"
<< "\n\nИндивидуальное задание № 1:\n"
<< "\nНаписать программу, которая считывает текст из файла и\n"
<< "\nвыводит на экран только предложения, содержащие запятые.\n"
<< "\n\nРабота программы:\n";
int i;
bool flag = false;
char ch;
string str = "";
ifstream input("text.txt");
if (! input)
{
cout << "Ошибка открытия файла" << endl;
return 1;
}
input.seekg(0,ios::end);
long unsigned int len = input.tellg();
input.seekg(0, ios :: beg);
for (i = 0; i < len; i++)
{
307
8 Лабораторная работа № 8
ch = input.get();
if (ch==’,’) flag = true;
if (ch==’.’)
{
if (flag) cout << str.c_str();
str = "";
flag = false;
}
else
str+=ch;
}
getchar();
return 0;
}
1.3. Результаты работы программы:
Рис. 8.1: Вывод программы на экран
308
Лабораторная работа № 9
Структуры в языке C++
Цель работы и содержание:
закрепление знаний о структурах, составление программ со структурами.
Ход работы
9.1 Основные сведения о структурах в языке С++.
Структуры в C++ обладают практически теми же возможностями, что и
классы, но чаще их применяют просто для логического объединения связанных
между собой данных. В структуру, в противоположность массиву, можно объединять
данные различных типов.
Например, требуется обрабатывать информацию о расписании работы
конференц-зала, и для каждого мероприятия надо знать время, тему, фамилию
организатора и количество участников. Поскольку вся эта информация относится к
одному событию, логично дать ему имя, чтобы впоследствии можно было к нему
обращаться. Для этого описывается новый тип данных:
struct Event
{
int hour, min;
char theme[100], name[100];
int num;
}
Имя этого типа данных - Event. Можно описать переменные этого типа точно
так же, как переменные встроенных типов, например:
Event e1, e2[10];
// структура и массив структур
309
9 Лабораторная работа № 9
Если структура используется только в одном месте программы, можно совместить описание типа с описанием переменных, при этом имя типа можно не
указывать:
struct
{
int hour, min;
char theme[100], name[100];
int num;
} e1, e2[10];
Переменные структурного типа можно размещать и в динамической области
памяти, для этого надо описать указатель на структуру и выделить под нее место:
Event *pe = new Event;
Event *pm = new Event[m];
// структура
// массив стуктур
Элементы структуры называются полями. Поля могут быть любого основного типа,
массивом, указателем, объединением или структурой. Для обращения к полю используется операция выбора («точка» для переменной и -> для указателя), например:
e1.hour = 12; e1.min = 30;
strncpy(e2[0].theme,"Выращивание какт. в условиях Крайнего Севера",99);
pe->num = 30;
// или (*pe).num = 30;
pm[2].hour = 14;
// или (*(pm + 2)).hour = 14;
Структуры одного типа можно присваивать друг другу:
* = e1: pm[1] >> e1: pm[4] = e2[0];
Но присваивание - это и все, что можно делать со структурами целиком. Другие
операции, например сравнение на равенство или вывод, не определены. Впрочем,
пользователь может задать их самостоятельно, поскольку структура является видом
класса, а в классах можно определять собственные операции.
Ввод/вывод структур, как и массивов, выполняется поэлементно. Вот, например, как выглядит ввод и вывод описанной выше структуры el с использованием
классов ввода-вывода (<iostream>):
cin >> e1.hour >> e1.min;
cin.getline(e1.theme, 100);
cout << e1.hour <<’␣’<< e1.min <<’␣’<< e1.theme << endl;
310
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Вариант с использованием ввода-вывода в стиле С (подключается заголовочный
файл <stdio.h>):
scanf("MM", Sel.hour, Sel.min); gets (e1.theme);
printf("&d␣%d␣%s", e1.hour, e1.min, e1.theme);
Структуры (но, конечно, не динамические) можно инициализировать перечислением
значений их элементов:
Event
= {12, 30, "Выращивание кактусов в условиях Крайнего Севера", 25};
Пример 9.1. Поиск в массиве структур
В текстовом файле хранится база отдела кадров предприятия. На предприятии
100 сотрудников. Каждая строка файла содержит запись об одном сотруднике.
Формат записи: фамилия и инициалы (30 поз., фамилия должна начинаться с первой
позиции), год рождения (5 поз.), оклад (10 поз.). Написать программу, которая по
заданной фамилии выводит на экран сведения о сотруднике, подсчитывая средний
оклад всех запрошенных сотрудников.
I. Исходные данные, результаты и промежуточные величины
Исходные данные. База сотрудников находится в текстовом файле. Прежде всего
надо решить, хранить ли в оперативной памяти одновременно всю информацию
из файла или можно обойтись буфером на одну строку. Если бы сведения
о сотруднике запрашивались однократно, можно было бы остановиться на
втором варианте, но поскольку поиск по базе будет выполняться более одного
раза, всю информацию желательно хранить в оперативной памяти, поскольку
многократное чтение из файла крайне нерационально.
Максимальное количество строк файла по условию задачи ограничено, поэтому
можно выделить для их хранения массив из 100 элементов. Каждый элемент
массива будет содержать сведения об одном сотруднике. Поскольку эти сведения
разнородные, удобно организовать их в виде структуры.
Для решения этой конкретной задачи запись о сотруднике может быть просто
строкой символов, из которой при необходимости выделяется подстрока с
окладом, преобразуемая затем в число, но для общности и удобства дальнейшей
модификации программы следует использовать структуру.
В программу по условию требуется также вводить фамилии искомых сотрудников. Для хранения фамилии опишем строку символов той же длины, что и в
базе.
311
9 Лабораторная работа № 9
Результаты. В результате работы программы требуется вывести на экран
требуемые элементы исходного массива. Поскольку эти результаты представляют собой выборку из исходных данных, дополнительная память для них не
отводится. Кроме того, необходимо подсчитать средний оклад для найденных
сотрудников. Для этого необходима переменная вещественного типа.
Промежуточные величины. Для поиска среднего оклада необходимо подсчитать количество сотрудников, для которых выводились сведения. Заведем для
этого переменную целого типа. Для описания формата входного файла будем
использовать именованные константы.
II. Алгоритм решения задачи очевиден:
1. Ввести из файла в массив сведения о сотрудниках.
2. Организовать Цикл вывода сведений о сотруднике:
- ввести с клавиатуры фамилию;
- выполнить поиск сотрудника в массиве;
- увеличить суммарный оклад и счетчик количества сотрудников;
- вывести сведения о сотруднике или сообщение об их отсутствии;
3. Вывести средний оклад.
Необходимо решить, каким образом будет производиться выход из цикла вывода
сведений о сотрудниках. Для простоты условимся, что для выхода из цикла
вместо фамилии следует ввести слово end.
III. Программа и тестовые примеры
Листинг 9.1
#include <iostream>
#include <fstream>
#include <string.h>
#include <stdlib.h>
using namespace std;
int main()
{
const int l_name=30,l_year=5,l_pay=10,l_buf=l_name+l_pay; //1
struct Man
//2
{
int birth_year;
char name[l_name + 1];
float pay;
};
312
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
const int l_dbase = 100;
Man dbase [l_dbase];
char buf[l_buf + 1];
char name[l_name + 1];
//3
//4
//5
ifstream fin("dbase.txt", ios::in);
if (!fin)
{
cout << "Ошибка открытия файла";
return 1;
}
int i = 0;
while(fin.getline(buf,l_buf))
//7
{
if (i >= l_dbase)
{
cout << "Слишком длинный файл";
return 1;
}
strncpy(dbase[i].name,buf,l_name);
dbase[i].name[l_name] = ’\0’;
dbase[i].birth_year = atoi(&buf[l_name]);
dbase[i].pay = atof(&buf[l_name + l_year]);
i++;
}
int n_record = i,n_man = 0;
float mean_pay = 0;
//8
while(true)
//9
cout <<"Введите фамилию или слово end: ";cin >> name;
//DemToChar(name, name);
if(strcmp(name, "end") == 0) break;
bool not_found = true;
for (i = 0; i < n_record; i++)
//10
//11
//12
//13
{
{
if (strstr(dbase[i].name, name))
if (dbase[i].name[strlen(name)] == ’␣’)
{
strcpy(name, dbase[i].name);
// CharToOem(name.name);
313
//14
//15
//16
9 Лабораторная работа № 9
cout << name << dbase[i].birth_year << ’␣’
<< dbase[i].pay << endl;
n_man++; mean_pay += dbase[i].pay;
not_found = false;
}
}
if (not_found) cout<<"Такого сотрудника нет!"<<endl;
}
if (n_man > 0)
cout << "␣:␣"
<< mean_pay / n_man << endl;
return 0;
//17
}
В операторе 1 заданы именованные константы, в которых хранится формат
входного файла, то есть длина каждого из полей записи (строки файла). Такой
подход позволяет при необходимости легко вносить в программу изменения.
Длина буфера, в который будет считываться каждая строка файла, вычисляется как сумма длин указанных полей. В операторе 2 определяется структура
Man для хранения сведений об одном сотруднике. Длина поля, в котором будет
находиться фамилия, задана с учетом завершающего нуль-символа. В операторе
3 определяется массив структур dbase для хранения всей базы. Его размерность также задается именованной константой. В операторах 4 и 5 задаются
промежуточные переменные: буфер buf для ввода строки из файла и строка
name для фамилии запрашиваемого сотрудника. В операторе 6 выполняется открытие файла dbase.txt для чтения. Предполагается, что этот файл находится
в том же каталоге, что и текст программы, иначе следует указать полный путь.
Входной файл следует создать в любом текстовом редакторе до первого запуска
программы в соответствии с форматом, заданным в условии задачи. Файл для
целей тестирования должен состоять из нескольких строк, причем необходимо
предусмотреть случай, когда одна фамилия является частью другой (например,
Иванов и Ивановский). Не забудьте проверить, выдается ли диагностическое
сообщение, если файл не найден.
Цикл 7 выполняет построчное считывание из файла в строку buf и заполнение
очередного элемента массива dbase. Счетчик 1 хранит индекс первого свободного элемента массива. Для формирования полей структуры используются
функции копирования строк strncpy, преобразования из строки в целое число
atoi и преобразования из строки в вещественное число atof. Обратите внимание на то, что завершающий нуль-символ в поле фамилии заносится «вручную»,
поскольку функция strncpy делает это только в случае, если строка-источник
314
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
короче строки-приемника. В функцию atoi передается адрес начала подстроки,
в которой находится год рождения. При каждом проходе цикла выполняется
проверка, не превышает ли считанное количество строк размерность массива.
При тестировании программы в этот цикл следует добавить контрольный вывод на экран считанной строки, а также сформированных полей структуры.
Для проверки выдачи диагностического сообщения следует временно задать
константу l_base равной, а затем меньшей фактического количества строк в
файле.
При заполнении массива из файла обязательно контролируйте выход за границы
массива и при необходимости выдавайте предупреждающее сообщение.
Кстати, можно записать эту проверку и так:
while(fin.getline(buf,l_buf)&& i < l_dbase)
{
i++;
}
if (i >= l_dbase)
{
cout << "Слишком длинный файл!";
return 1;
}
В операторе 8 определяются две переменные: n_record для хранения фактического количества записей о сотрудниках и n_man - для подсчета сотрудников, о
которых будут выдаваться сведения. Следует также не забыть обнулить переменную mean_pay, в которой в следующем цикле будет накапливаться сумма
окладов.
Цикл поиска сотрудников по фамилии организован как бесконечный (оператор
9) с принудительным выходом (оператор 11). Некоторые специа- листы считают,
что такой способ является плохим стилем, и для выхода из цикла следует
определить переменную-флаг, но нам кажется иначе.
В операторе 12 определяется переменная-флаг not_found для того, чтобы
после окончания цикла поиска было известно, завершился ли он успешно. Имя
переменной следует выбирать таким образом, чтобы по нему было ясно, какое
значение является истинным:
if (not_found) cout << "Такого сотрудника нет!" << endl;
315
9 Лабораторная работа № 9
В операторе 13 организуется цикл просмотра массива структур (просматриваются только заполненные при вводе элементы). Проверка совпадения фамилии
сотрудника производится в два этапа. В операторе 14 с помощью функции strstr
поиска подстроки определяется, содержится ли в поле базы name искомая последовательность букв, а в операторе 15 проверяется, есть ли непосредственно
после фамилии пробел (если пробела нет, то искомая фамилия является частью
другой, и эта строка нам не подходит). Такая простая проверка возможна из-за
условия задачи, по которому фамилия должна начинаться с первой позиции
каждой строки.
Для подобных программ в инструкции для пользователя должно быть четко
указано, при помощи каких текстовых редакторов можно произвести первоначальное заполнение файла базы данных.
Алгоритм программы составлен в предположении, что фамилия начинается
с первой позиции записи в базе данных. Измените программу так, чтобы это
ограничение можно было снять. Для этого придется проверить, стоит ли перед
фамилией пробел в том случае, если она не начинается с первой позиции.
Проверка переменной n man в операторе 17 необходима для того, чтобы в случае,
если пользователь не введет ни одной фамилии, совпадающей с фамилией в
базе, не выполнялось деление на 0.
Крупным недостатком нашей программы является то, что вводить фамилию
сотрудника требуется именно в том регистре, в котором она присутствует в
базе. Для преодоления этого недостатка необходимо перед сравнением фамилий
переводить все символы в один регистр. Для символов латинского алфавита в
библиотеке есть функции tolower (с) и toupper (с), переводящие переданный
им символ с в нижний и верхний регистр соответственно, аналогичные функции
для символов русского алфавита придется написать самостоятельно.
Если в базе есть несколько сотрудников с одной и той же фамилией, программа
выдаст сведения обо всех.
Теперь рассмотрим вариант записи этой же программы с помощью библиотечных функций ввода-вывода:
Листинг 9.2
#include <stdio.h>
#include <string.h>
int main()
{
const int l_name = 30;
//1
struct Man
{
316
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int birth_year;
char name[l_name + 1];
float pay;
};
const int l_dbase = 100;
Man dbase[l_dbase];
char name[l_name + 1];
FILE *fin = fopen("dbase.txt", "r");
if (fin == NULL)
{
puts("Ошибка открытия файла!\n");
return 1;
}
int i = 0;
while(!feof(fin))
{
fgets(dbase[i].name,l_name,fin);
fscanf(fin, "%d␣%f\n", &dbase[i].birth_year, &dbase[i].pay);
//2
i++;
if (i > l_dbase)
{
puts("Слишком длинный файл!\n");
return 1;
}
}
int n_record = i, n_man = 0;
float mean_pay = 0;
while(true)
{
puts("Введите фамилию или нажмите Enter для окончания: ");
fgets(name,sizeof(name),stdin);
if(strlen(name) == 0)break;
//3
//OemToChar(name, name);
//4
bool not_found = true;
for (i = 0; i < n_record; i++)
317
9 Лабораторная работа № 9
{
if (strstr(dbase[i].name, name))
if (dbase[i].name[strlen(name)] == ’␣’)
{
strcpy(name,dbase[i].name);
// CharToOem(name.name);
//5
printf("%30s%5i%10.2f\n",name,dbase[i].birth_year,dbase[i].pay);//6
n_man++; mean_pay += dbase[i].pay;
not_found = false;
}
}
if (not_found) puts("Такого сотрудника нет!\n");
}
if(n_man > 0) printf("Средний оклад: %10.2f\n",mean_pay/n_man);
return 0;
}
Из всех именованных констант осталась одна, задающая длину поля фамилии
(l_name, оператор 1). Все остальные константы определять нет смысла, потому
что ввод осуществляется не через буферную переменную, а непосредственно в
поля структуры с помощью функции чтения строки fgets и форматного ввода
fscanf (оператор 2). Эта функция сама выполняет действия по преобразованию подстроки в число, которые мы явным образом задавали в предыдущей
программе.
Мы упростили выход из цикла ввода запросов, теперь для завершения цикла достаточно нажать клавишу Enter (оператор 3). Для вывода сведений о
сотруднике мы использовали функцию printf (оператор 6).
Пример 9.2. Сортировка массива структур
Написать программу, которая упорядочивает описанный в предыдущей задаче
файл по году рождения сотрудников.
Изменим предыдущую программу таким образом, чтобы она вместо поиска
упорядочивала массив, а затем записывала его в файл с тем же именем, что исходный.
Для сортировки применим метод выбора. При всей своей простоте он достаточно эффективен. Основная идея этого метода: из массива выбирается наименьший
318
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
элемент и меняется местами с первым элементом, затем рассматриваются элементы,
начиная со второго, наименьший из них меняется местами со вторым элементом,
и так далее. Для упорядочивания требуется количество просмотров, на единицу
меньшее, чем количество элементов в массиве (при последнем проходе цикла при
необходимости меняются местами предпоследний и последний элементы массива).
Листинг 9.3
#include <stdlib.h>
#include <string.h>
#include <fstream>
int main()
{
const int l_name = 30,l_year = 5, l_pay = 10, l
buf = l_name + l_pay;
struct Man
{
int birth_year;
char name[l_name + 1];
float pay;
};
const int l_dbase = 100;
Man dbase[l_dbase];
char buf[l_buf + 1];
ifstream fin("dbase.txt", ios::in);
if (!fin)
{
cout << "Ошибка открытия файла!";
return 1;
}
int i = 0;
while(fin.getline(buf.l_buf))
{
strncpy(dbase[i].name.buf.l_name);
dbase[i].name[l_name] = ’\0’;
dbase[i].birth_year = atoi(&buf[l_name]);
dbase[i].pay = atof(&buf[l_name + l_year]);
i++;
319
9 Лабораторная работа № 9
if(i > l_dbase)
{
cout <<"Слишком длинный файл!"<<endl;
return 1;
}
}
int n record = i;
fin.close();
ofstream fout("dbase.txt");
if(!fout){cout <<"Ошибка открытия файла!" << endl;
return 1;
for (i = 0; i < n record - l; i++)
{
// Принимаем за наименьший первый из рассматриваемых элементов
int imin = i;
// поиск номера минимального элемента из неупорядоченных
for(int j = i + 1; j < n record; j++)
if (dbase[j].birth_year < dbase[imin].birth_year) imin = j;
// обмен двух элементов массива структур
Man a = dbase[i];
dbase[i] = dbase[imin];
dbase[imin] = a;
}
for(i = 0; i < n record; i++)
{
fout << dbase[i].name << dbase[i].birth_year <<’␣’<< dbase[i].pay <<
endl;
}
fout.close();
cout << "Сортировка завершена!" << endl;
return 0;
}
Элементами массива в данном примере являются структуры. Для структур
320
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
одного типа определена операция присваивания, поэтому обмен двух Элементов
массива структур выглядит точно так же, как для основных типов данных.
Для того чтобы записать результаты в файл с тем же именем, файл, открытый
для чтения, закрывается, а затем открывается файл с тем же именем для записи
(говоря более строго, создается объект выходного потока ostream с именем fout).
При этом старый файл на диске уничтожается и создается новый, пустой файл, в
который и производится запись массива.
Пример 9.3. Структуры и бинарные файлы
Написать две программы. Первая считывает информацию из файла, формат
которого описан в примере 8.1, и записывает ее в бинарный файл. Количество записей
в файле не ограничено. Вторая программа по номеру записи корректирует оклад
сотрудника в этом файле.
Бинарные файлы, то есть файлы, в которых информация хранится во внутренней форме представления, применяются для последующего использования программными средствами. Преимущество бинарных файлов состоит в том, что, во-первых,
при чтении/записи не тратится время на преобразование данных из символьной формы представления во внутреннюю и обратно, а во-вторых, при этом не происходит
потери точности вещественных чисел. Кроме того, при работе с бинарными файлами
широко применяется прямой доступ к информации путем установки текущей позиции указателя. Это дает возможность быстрого получения и изменения отдельных
данных файла. Например, в данной задаче мы будем изменять оклад отдельных
сотрудников, не затрагивая другие записи базы.
Бинарный файл открывается в двоичном режиме, а чтение/запись в него
выполняются с помощью функций библиотеки fread и fwrite.
Хранить в памяти весь входной файл нет необходимости, вполне достаточно
одной переменной структурного типа, в которой будет содержаться в каждый момент
времени запись об одном сотруднике.
Листинг 9.4
//
#include <stdio.h>
#include <string.h>
int main()
{
const int l_name = 30;
struct
{
char name[l_name + 1];
int birth_year;
321
9 Лабораторная работа № 9
float pay;
} man;
FILE *fin = fopen("dbase.txt", "r");
if ((fin) == NULL)
{
puts("Ошибка открытия вх. файла!\n");
return 1;
}
FILE *fout = fopen("dbase.bin", "wb");
if ((fout) == NULL)
{
puts("Ошибка открытия вых. файла!\n");
return 1;
}
while (!feof(fin))
{
fgets(man.name,l_name,fin);
fscanf("%s%5i%10.2f\n", man.name, man.birth_year, man.pay);
//отладочная печать
fwrite(&man, sizeof(man), l, fout);
}
fclose(fout);
printf("Бинарный файл написан\n");
return 0;
}
Для формирования записей в бинарном файле здесь применяется функция
fwrite:
size_t fwrite(const void *p, size_t size, size_t n, FILE *f)
Она записывает n элементов длиной size байт из буфера, заданного указателем
р, в поток f. Возвращает число записанных элементов.
Для чтения из бинарного файла во второй программе будем применять функцию
fread:
322
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
size_t fread(void *p, size_t size, size_t n, FILE *f);
Она считывает n элементов длиной size байт в буфер, заданный указателем р,
из потока f. Возвращает число считанных элементов, которое может быть меньше,
чем запрошенное.
Листинг 9.5
// Корректировка бинарного файла
#include <stdio.h>
#include <string.h>
int main()
{
const int l_name = 30;
struct
{
char name[l_name + 1];
int birth year;
float pay;
} man;
FILE *fout = fopen("dbase.bin", "wb");
if ((fout)== NULL)
//1
{
puts("Ошибка открытия файла!\n");
return 1;
}
fseek(fout.0.SEEK END);
int n record = ftell(fout)/sizeof(man);
//2
int num;
while(true)
{
//3
puts("Введите номер записи или -1: ");
scanf("%i".&num);
if(num < 0||num >= n record)break;
fseek(fout. num * sizeof(man). SEEK SET);
fread(&man. Sizeof(man). 1. fout);
printf("%s%5i&10.2f\n", man.name, man.birth_year, man.pay);
323
9 Лабораторная работа № 9
puts("␣␣:");
scanf("%f". &man.pay);
fseek(fout. num*sizeof(man).SEEK SET);
fwrite(&man. sizeof(man). 1. fout);
printf("%s%5i&10.2f\n", man.name, man.birth_year, man.pay);
}
fclose(fout);
printf("Корректировка завершена\n");
return 0;
}
В операторе 1 открывается сформированный в предыдущей задаче бинарный
файл. Обратите внимание на режим открытия: г обозначает возможность чтения
и записи, b двоичный режим. Чтобы проконтролировать введенный номер записи,
в переменную n record заносится длина файла в записях (оператор 2). До этого
указатель текущей позиции файла устанавливается на его конец с помощью функции
f seek, следовательно, в результате вызова tel1 будет получен размер файла в байтах.
В цикле корректировки оклада (оператор 3) текущая позиция в файле устанавливается дважды, поскольку после первого чтения она смещается на размер
считанной записи. Выход из цикла выполняется, если будет задан неверный номер
записи: меньше нуля или больше, чем их количество (при написании программы мы
приняли, что первая запись в файле имеет номер 0).
Пример 9.4. Структуры в динамической памяти
Вывести на экран содержимое бинарного файла, сформированного в предыдущей задаче, упорядочив фамилии сотрудников по алфавиту.
Листинг 9.6
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
const int l_name = 30;
struct Man
{
char name[l_name + 1];
int birth_year;
float pay;
};
324
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int compare(const void *man1, const void *man2);
//1
int main()
{
FILE *fbin = fopen("dbase.bin", "rb");
if ((fbin) == NULL)
{
puts("Ошибка открытия файла!\n");
return 1;
}
fseek(fbin.0.SEEK_END);
int n_record = ftell(fbin)/sizeof(Man);
Man *man=new Man[n_record];
//2
fseek(fout, num*sizeof(man), SEEK SET);
//3
fread(man, Sizeof(Man), n_record, fbin);
//4
fclose(fbin);
qsort(man, n_record, sizeof(Man), compare);
//5
for (int i = 0; i < n_record; i++)
printf("%s%5i&10.2f\n", man[i].name, man[i].birt_hyear,man.pay);
return 0;
}
int compare(const void *man1, const void *man2)
{
return stromp(((Man*) man1) -> name, ((Man*) man2) -> name);
}
Рассмотрим моменты, которые отличают эту программу от предыдущих. Вопервых, это чтение из бинарного файла. После открытия файла мы, как и в предыдущей программе, заносим в переменную n record его размер в записях, а затем
выделяем в динамической памяти место под весь массив структур (оператор 2).
Функция fread позволяет считать весь файл за одно обращение (оператор 4), после
чего файл уже больше не требуется, поэтому лучше его сразу же закрыть.
Для сортировки мы в образовательных целях и для разнообразия использовали
стандартную функцию qsort (оператор 5). Ее прототип находится в заголовочном
файле <stdlib.h>. Функция может выполнять сортировку мас- сивов любых размеров
и типов. У нее четыре параметра:
1. указатель на начало области, в которой размещается упорядочивае- мая ин-
325
9 Лабораторная работа № 9
формация;
2. количество сортируемых элементов;
3. размер каждого элемента в байтах;
4. имя функции, которая выполняет сравнение двух элементов.
Раз функция qsort универсальна, мы должны дать ей информацию, как сравнивать сортируемые элементы и какие выводы делать из сравнения. Значит, мы
должны сами написать функцию, которая сравнивает два любых элемента, и передать ее в qsort. Имя функции может быть любым. Мы назвали ее compare. Оператор
1 представляет собой заголовок (прототип) функции, он необходим компилятору для
проверки правильности ее вызова.
Для правильной работы qsort требуется, чтобы наша функция имела два параметра - указатели на сравниваемые элементы. Они должны иметь тип void. Функция
должна возвращать значение, меньшее нуля, если первый элемент меньше второго,
равное нулю, если они равны, и большее нуля, если первый элемент больше. При
этом массив будет упорядочен по возрастанию. Если мы хотим упорядочить массив
по убыванию, нужно изменить возвращаемые значения так: если первый элемент
меньше второго, возвращать значение, большее нуля, а если больше - меньшее.
Внутри функции надо привести указатели на void к типу указателя на структуру Man. Для этого мы использовали операцию приведения типа в стиле С (Man
*). Более грамотно применять для этого операцию reinterpret_cast, введенную в
стандарт C++ относительно недавно. Старые компиляторы могут ее не поддерживать. Функция compare с использованием reinterpret_cast выглядит вот таким
устрашающим образом:
int compare(const void *man1. const void *man2)
{
return stromp((reinterpret cast < const Man* > (man1)) -> name,
(reinterpret cast < const Man* > (man2)) -> name);
}
Чтобы описание структуры было известно в функции compare, мы перенесли
описание структуры, а вместе с ней и описание необходимой ей константы lname в
глобальную область.
Для упорядочивания массива по другому полю надо изменить функцию сравнения. Вот, например, как она выглядит при сортировке по возрастанию года рождения:
int compare(const void *man1, const void *man2)
{
int p;
if(((Man*)man1) -> birth year < ((Man*)man2)-> birth year) p =- 1;
else if(((Man*)man1) -> birth year <((Man*)man2)-> birth year) p = 0;
326
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
else p = 1;
return p;
}
Можно записать то же самое более компактно с помощью тернарной условной
операции. Для разнообразия приведем функцию для сортировки по убыванию оклада:
int compare(const void *man1, const void *man2)
{
return ((Man*)man1) -> pay == ((Man*)man2) -> pay ? -1;
((Man*)man1) -> pay == ((Man*)man2) -> pay ? 0; 1;
}
327
9 Лабораторная работа № 9
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
328
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
9.2 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Составить программу с использованием структур. Номер варианта определяется
по номеру студента по списку преподавателя.
Вариант
1. Описать структуру с именем STUDENT, содержащую следующие поля: фамилия и инициалы; номер группы; успеваемость (массив из пяти элементов).
Написать программу, выполняющую следующие действия: ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи
должны быть упорядочены по возрастанию номера группы; вывод на дисплей фамилий и номеров групп для всех студентов, включенных в массив,
если средний балл студента больше 4.0; если таких студентов нет, вывести
соответствующее сообщение.
2. Описать структуру с именем STUDENT, содержащую следующие поля: фамилия и инициалы; номер группы; успеваемость (массив из пяти элементов).
Написать программу, выполняющую следующие действия: ввод с клавиатуры данных в массив, состоящий из десяти структур типа STUDENT; записи
должны быть упорядочены по возрастанию среднего балла; вывод на дисплей
фамилий и номеров групп для всех студентов, имеющих оценки 4 и 5; если
таких студентов нет, вывести соответствующее сообщение.
3. Описать структуру с именем STUDENT, содержащую следующие поля: фамилия и инициалы; номер группы; успеваемость (массив из пяти элементов).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из десяти структур типа STUDENT; записи должны быть упорядочены по алфавиту; вывод на дисплей фамилий и номеров групп
для всех студентов, имеющих хотя бы одну оценку 2; если таких студентов нет,
вывести соответствующее сообщение.
4. Описать структуру с именем AEROFLOT, содержащую следующие поля: название пункта назначения рейса; номер рейса; тип самолета.
329
9 Лабораторная работа № 9
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из семи элементов типа AEROFLOT; записи
должны быть упорядочены по возрастанию номера рейса; вывод на экран
номеров рейсов и типов самолетов, вылетающих в пункт назначения, название
которого совпало с названием, введенным с клавиатуры; если таких рейсов нет,
выдать на дисплей соответствующее сообщение.
5. Описать структуру с именем AEROFLOT, содержащую следующие поля: название пункта назначения рейса; номер рейса; тип самолета.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из семи элементов типа AEROFLOT; записи должны быть размещены в алфавитном порядке по названиям пунктов назначения;
вывод на экран пунктов назначения и номеров рейсов, обслуживаемых самолетом, тип которого введен с клавиатуры; если таких рейсов нет, выдать на
дисплей соответствующее сообщение.
6. Описать структуру с именем TRAIN, содержащую следующие поля: название
пункта назначения; номер поезда; время отправления.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа TRAIN; записи должны
быть размещены в алфавитном порядке по названиям пунктов назначения;
вывод на экран информации о поездах, отправляющихся после введенного с клавиатуры времени; если таких поездов нет, выдать на дисплей соответствующее
сообщение.
7. Описать структуру с именем TRAIN, содержащую следующие поля: название
пункта назначения; номер поезда; время отправления.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из шести элементов типа TRAIN; записи должны
быть упорядочены по времени отправления поезда; вывод на экран информации
о поездах, направляющихся в пункт, название которого введено с клавиатуры;
если таких поездов нет, выдать на дисплей соответствующее сообщение.
8. Описать структуру с именем TRAIN, содержащую следующие поля: название
пункта назначения; номер поезда; время отправления.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа TRAIN; записи должны
быть упорядочены по номерам поездов; вывод на экран информации о поезде,
номер которого введен с клавиатуры; если таких поездов нет, выдать на дисплей
соответствующее сообщение.
9. Описать структуру с именем MARSH, содержащую следующие поля: название
начального пункта маршрута; название конечного пункта маршрута; номер
маршрута.
330
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа MARSH; записи должны
быть упорядочены по номерам маршрутов; вывод на экран информации о
маршруте, номер которого введен с клавиатуры; если таких маршрутов нет,
выдать на дисплей соответствующее сообщение.
10. Описать структуру с именем MARSH, содержащую следующие поля: название
начального пункта маршрута; название конечного пункта маршрута; номер
маршрута.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа MARSH; записи должны
быть упорядочены по номерам маршрутов; вывод на экран информации о
маршрутах, которые начинаются или оканчиваются в пункте, название которого введено с клавиатуры; если таких маршрутов нет, выдать на дисплей
соответствующее сообщение.
11. Описать структуру с именем NOTE, содержащую следующие поля: фамилия,
имя; номер телефона; дата рождения (массив из трех чисел).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа NOTE; записи должны
быть упорядочены по датам рождения; вывод на экран информа- ции о человеке,
номер телефона которого введен с клавиатуры; если такого нет, выдать на
дисплей соответствующее сообщение.
12. Описать структуру с именем NOTE, содержащую следующие поля: фамилия,
имя; номер телефона; дата рождения (массив из трех чисел).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа NOTE; записи должны
быть размещены по алфавиту; вывод на экран информации о людях, чьи дни
рождения приходятся на месяц, значение которого введено с клавиатуры; если
таких нет, выдать на дисплей соответствующее сообщение.
13. Описать структуру с именем NOTE, содержащую следующие поля: фамилия,
имя; номер телефона; дата рождения (массив из трех чисел).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа NOTE; записи должны
быть упорядочены по трем первым цифрам номера телефона; вывод на экран
информации о человеке, чья фамилия введена с клавиатуры; если такого нет,
выдать на дисплей соответствующее сообщение.
14. Описать структуру с именем ZNAK, содержащую следующие поля: фамилия,
имя; знак Зодиака; дата рождения (массив из трех чисел).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа ZNAK; записи должны
331
9 Лабораторная работа № 9
быть упорядочены по датам рождения; вывод на экран информации о человеке, чья фамилия введена с клавиатуры; если такого нет, выдать на дисплей
соответствующее сообщение.
15. Описать структуру с именем ZNAK, содержащую следующие поля: фамилия,
имя; знак Зодиака; дата рождения (массив из трех чисел).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа ZNAK; записи должны
быть упорядочены по датам рождения; вывод на экран информации о людях,
родившихся под знаком, название которого введено с клавиатуры; если таких
нет, выдать на дисплей соответствующее сообщение.
16. Описать структуру с именем ZNAK, содержащую следующие поля: фамилия,
имя; знак Зодиака; дата рождения (массив из трех чисел).
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа ZNAK; записи должны
быть упорядочены по знакам Зодиака; вывод на экран информации о людях,
родившихся в месяц, значение которого введено с клавиатуры; если таких нет,
выдать на дисплей соответствующее сообщение.
17. Описать структуру с именем PRICE, содержащую следующие поля: название
товара; название магазина, в котором продается товар; стоимость товара в руб.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа PRICE; записи должны
быть размещены в алфавитном порядке по названиям товаров; вывод на экран
информации о товаре, название которого введено с клавиатуры; если таких
товаров нет, выдать на дисплей соответствующее сообщение
18. Описать структуру с именем PRICE, содержащую следующие поля: название
товара; название магазина, в котором продается товар; стоимость товара в руб.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа PRICE; записи должны
быть размещены в алфавитном порядке по названиям магазинов; вывод на экран
информации о товарах, продающихся в магазине, название которого введено
с клавиатуры; если такого магазина нет, выдать на дисплей соответствующее
сообщение.
19. Описать структуру с именем ORDER, содержащую следующие поля: расчетный
счет плательщика; расчетный счет получателя; перечисляемая сумма в руб.
Написать программу, выполняющую следующие действия: ввод с клавиатуры
данных в массив, состоящий из восьми элементов типа ORDER; записи должны
быть размещены в алфавитном порядке по расчетным счетам плательщиков;
вывод на экран информации о сумме, снятой с расчетного счета плательщика,
332
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
введенного с клавиатуры; если такого расчетного счета нет, выдать на дисплей
соответствующее сообщение.
333
9 Лабораторная работа № 9
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Для чего применяют структуры?
2. Что представляют собой поля структуры?
3. Варианты ввода-вывода структур.
4. Какие функции могут использоваться для формирования полей структуры?
5. Что такое бинарные файлы? Для чего они применяются и каковы их основные
преимущества?
6. Назовите параметры функции qsort. В каком заголовочном файле находится
ее прототип?
334
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
9.3 Пример выполнения лабораторной работы № 9:
9.3.1 Индивидуальное задание № 1:
1.1. Постановка задачи:
Составить программу с использованием двумерных локальных массивов для
решения задачи. Размерности локальных массивов задавать именованными константами, значения элементов массива — в списке инициализации.
Задача: описать структуру с именем WORKER, содержащую следующие поля:
фамилия и инициалы работника; G - название занимаемой должности; год поступления на работу.
Написать программу, выполняющую следующие действия:
- ввод с клавиатуры данных в массив, состоящий из десяти структур типа
WORKER;
- записи должны быть размещены по алфавиту;
- вывод на дисплей фамилий работников, чей стаж работы в организации превышает значение, введенное с клавиатуры;
- если таких работников нет, вывести на дисплей соответствующее сообщение.
1.2. Листинг программы:
// Лабораторная работа № 9
// Индивидуальное задание № 1
#include <iostream>
#include <string.h>
using namespace std;
struct WORKER
{
string fio;
string post;
int iyear;
};
int main()
{
cout <<
<<
<<
<<
"Лабораторная работа № 9\n"
"\nНемо А.А., 1-18\n"
"\nВариант № 6\n"
"\n\nИндивидуальное задание № 1:\n"
335
9 Лабораторная работа № 9
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
<<
"\nОписать структуру с именем WORKER,"
"содержащую следующие поля:\n"
"\nфамилия и инициалы работника;\n"
"\nназвание занимаемой должности;\n"
"\nгод поступления на работу.\n"
"\n\nНаписать программу, выполняющую следующие действия:\n"
"\nввод с клавиатуры данных в массив, состоящий из десяти"
"структур типа WORKER;\n"
"\nзаписи должны быть размещены по алфавиту;\n"
"\nвывод на дисплей фамилий работников, чей стаж работы"
"в организации\n"
"\nпревышает значение, введенное с клавиатуры;\n"
"\nесли таких работников нет, вывести на дисплей"
"соответствующее сообщение.\n"
"\n\nРабота программы:\n";
time_t t = time(NULL);
tm* timePtr = localtime(&t);
string temp_str;
int const n = 10;
int i, j, stag, current_year = timePtr->tm_year+1900, kol = 0;
char temp[255];
WORKER *works = new WORKER[n];
for (i = 0; i < n; i++)
{
cout << "\n␣␣" << i + 1 << "-го сотрудника:\n"
<< "Введите ФИО: ";
cin >> temp;
works[i].fio = temp;
cout << "Введите должность: ";
cin >> temp;
works[i].post = temp;
cout << "Введитие год поступления на работу: ";
cin >> works[i].iyear;
}
for (i = 0; i < n; i++)
for (j = 0; j < n-1; j++)
{
if(strcmp((const char*)works[j].fio.c_str(),(const char*)works[j + 1].
fio.c_str()) > 0)
{
temp_str = works[j].fio;
336
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
works[j].fio = works[j + 1].fio;
works[j+1].fio = temp_str;
}
}
cout << "\n\nСортировка записей по алфавиту: \n ";
for (i = 0; i < n; i++)
{
cout
cout
cout
cout
<<
<<
<<
<<
’\n’ << i + 1 << "-ый сотрудник:\n";
"ФИО: " << works[i].fio.c_str() << ’\n’;
"Должность: " << works[i].post.c_str() << ’\n’;
"год поступления на работу: " << works[i].iyear << ’\n’;
}
cout << "\n\nВведите стаж для отбора: ";
cin >> stag;
cout << "\nСотрудники, удовлетворяющие отбору:\n";
for (i = 0; i < n; i++)
{
if (current_year-works[i].iyear >= stag)
{
kol++;
if (kol!=0)
cout << "\nФИО: " << works[i].fio.c_str() << ’\n’;
cout << "Должность: " << works[i].post.c_str() << ’\n’;
cout << "год поступления на работу: " << works[i].iyear << ’\n’;
}
}
if (kol==0)
cout << "\n\nНе найдены сотрудники, удовлетворяющие отбору!\n";
return 0;
}
337
Лабораторная работа № 10
Классы в языке C++
Цель работы и содержание:
закрепление знаний о классах, составление программ с классами.
Ход работы
10.1 Структура программы на
объектно-ориентированном языке.
Структура программы на объектно-ориентированном языке состоит из трех
пунктов:
1. В основе лежит базовый класс (класс – это абстрактный тип данных ) – он
самый простой;
2. Классы могут быть независимыми;
3. Строится иерархия наследования (рис.10.1), связь классов, порождающиеся
классы является более сложными.
Базовый класс является простейшим из всех классов. Базовых классов может
быть несколько и их можно добавлять в процессе эксплуатации. Сложность класса
увеличивается с номером уровня. Внизу иерархии стоят самые сложные функции.
Свойства:
- Статика (обычно не меняется);
- Динамика (меняется).
Пример: Дата – абстракция
• Статика: число, месяц, год.
• Динамика: помнить следующую дату, помнить предыдущую дату, вычисление
промежутков между датами.
338
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Рис. 10.1: Иерархия наследования
10.2 Понятие объекта.
Понятие объекта в ООП во многом приближено к привычному определению
понятия объекта в реальном мире. Рассмотрим физические объекты, которые нас
окружают. Про любой из физических объектов можно сказать, что он:
- имеет какое-то состояние (или находится в каком-то состоянии). К примеру,
про собаку можно сказать, что она имеет имя, окраску, возраст, голодна она
или нет и т.д.
- имеет определенное поведение. Т.е., та же собака может вилять хвостом, есть,
лаять, прыгать и т.д.
Объект в ООП состоит из следующих трех частей:
- имя объекта;
- состояние (переменные состояния);
- методы (операции).
Объект ООП - это совокупность переменных состояния и связанных с ними
методов (операций). Эти методы определяют как объект взаимодействует с окружающим миром.
Возможность управлять состояниями объекта посредством вызова методов в
итоге и определять поведение объекта. Эту совокупность методов часто называют
интерфейсом объекта.
Синтаксис декларации объектов аналогичен базовому типу.
Обычно, если объекты соответствуют конкретным сущностям реального мира,
то классы являются некими абстракциями, выступающими в роли понятий. На
339
10 Лабораторная работа № 10
данном этапе можно воспринимать класс как шаблон объекта. Для формирования
какого-либо реального объекта необходимо иметь шаблон, на основании которого
и строится создаваемый объект. При рассмотрении основ ООП часто смешивается
понятие объекта и класса. Дело в том, что класс - это некоторое абстрактное понятие.
Для проведения аналогий или приведения примеров оно не очень подходит. На много
проще приводить примеры, основываясь на объектах из реального мира, а не на
абстрактных понятиях. Поэтому, говоря, к примеру, про наследование мы прежде
всего имеем ввиду наследование классов (шаблонов), а не объектов, хотя часто и
применяем слово объект. Скажем так: объект - это физическая реализация класса
(шаблона).
10.3 Понятие класса.
Понятие класс (class) относится ко всем объектам, которые ведут себя одинаково. Например, все окружности имеют вполне определенную форму, они обладают
такими атрибутами, как местоположение, цвет, диаметр. Объект – это конкретный
экземпляр данного класса. Например, Земля имеет размер, цвет и местоположение,
отличные от аналогичных параметров для Луны или Солнца. Связь между классом
и объектами в сущности такая же, как между типом и переменными этого типа.
Класс (class) - это группа данных и методов (функций) для работы с этими
данными. Это шаблон. Объекты с одинаковыми свойствами, то есть с одинаковыми
наборами переменных состояния и методов, образуют класс.
Каждый класс объектов может реагировать на строго определенные сообщения.
Так происходит потому, что каждый класс обладает набором функций, которые
связаны с объектами класса. Функции являются частью этого класса объектов –
его членами. На рисунке показан объект, содержащий функции-члены. Программа
посылает этому объекту сообщения (messages), которые вызывают функции-члены
(member functions) данного объекта. Затем эти функции-члены обрабатывают объект.
340
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Эти функции называются функциями-членами, поскольку принадлежат классу, то есть являются его членами. Функции-члены программируются так же, как
обычные функции, однако объявляются в классе и могут использоваться только с
объектами этого класса.
Примерная структура класса (не привязанная к какому-либо языку ООП):
class имя_класса
{
private:
. . . . . . .
public:
. . . . . . .
protected:
. . . . . . .
}
Класс должен иметь уникальное имя. Если он наследован из другого, то надо
указать имя родительского(их) класса(ов). Обычно у класса бывают три раздела:
private, public, protected. Указание на начало раздела private часто опускается
и, если не объявлено начало ни одного из других разделов описания класса, считается,
что данные относятся к разделу private.
Методы в классе могут быть объявлены как дружественные (friend) или
виртуальные (virtual). Иногда встречается объявление перегружаемых (overload)
функций. Каждое из этих понятий более подробно мы рассмотрим отдельно.
Private (частный) раздел описания класса обычно находится вначале описания
класса и содержит данные, доступ к которым закрыт из внешнего мира. Это и есть та
самая "строго охраняемая"зона класса, доступ к которой можно получить только из
методов самого класса. Она скрыта от внешнего мира глухой непробиваемой стеной
и доступ к данным раздела private обеспечивается только с помощью, специально
описанных в других разделах, методов. Скрытые в этом разделе данные также не
доступны для всех производных классов.
341
10 Лабораторная работа № 10
Если бы все данные, описанные в базовом (родительском) классе, были доступны для производных классов, то можно было бы просто создать суперкласс, а затем
из производных классов получать свободный доступ ко всем данным родительского
класса. В то же время это перечеркнуло бы все наши старания по скрытию и защите данных. По этой причине, производные (наследуемые) классы автоматически
не получают доступ к данным родительского класса (раздел private). Но бывают
такие случаи, когда необходимо автоматически наследовать некоторые данные из
родительского класса так, чтобы они вели себя так, как будто они описаны в производном классе . Именно для этих целей и существует раздел protected (защищенный)
описания класса.
Protected (защищенный) - раздел описания класса содержит данные и методы,
доступ к которым закрыт из внешней среды, но они напрямую доступны производным
классам.
Таким образом, раздел protected используется для описания данных и методов,
которые будут доступны только из производных классов. А в производных классах
эти данные и методы воспринимаются, как если бы они были описаны в самом
производном классе.
Название раздела public для англо-язычной публики говорит само за себя.
Переводится как публичный, я бы сказал, открытый раздел. Методы описанные в
разделе public доступны в пределах области видимости объекта и для производных
классов. Таким образом, можно получить свободный доступ к методам, описанным
в разделе public, из любого места программы (объект должен быть виден) и из
любого производного класса. Методы, входящие в этот раздел, образуют интерфейс
класса, с помощью которого и осуществляется взаимодействие экземпляра класса с
внешним миром. Это единственный раздел, доступ к которому из внешней среды
никак не ограничен.
Пример простейшего класса данных:
class date
{
private:
int day,year;
}
public:
int input (int,char,int);
int output (int, char*, int);
int sum1 (int,char*, int);
int sum2 (int, char*, int);
int min1 (int, Char*,int);
int minn (int,char*, int);
int koi (int, char*,int,int,char*,int,int)
При указании базового (родительского) класса в описании класса в С++ тре-
342
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
буется указать ключевое слово public. Указание этого ключевого слова позволит
получить свободный доступ ко всем методам класса, как если бы они были описаны в
самом производном классе. В противном же случае, мы не сможем получить доступ
к методам родительского класса.
Пример описания наследования классов на С++:
class A
{
. . . . .
}
class B: public A
{
. . . . .
}
Классы предоставляют программисту возможность моделировать объекты,
которые имеют атрибуты (представленные как данные элементы) и варианты поведения или операции (представленные как функции элементы). Типы, содержащие
данные-элементы и функции-элементы, обычно определяются в C++ с помощью
ключевого слова class.
Пример 10.1. Использование абстрактного типа данных
Time с помощью класса Time
class Time
{
public:
Time();
void setTime(int, int, int);
void printMilitary();
void printStandard();
private:
int hour;
int minute;
int second;
//0-23
//0-59
//0-59
};
343
10 Лабораторная работа № 10
Листинг 10.1
// Класс Time
#include <iostream>
// Определение абстрактного типа данных (АТД) Time
class Time
{
public:
Time();
void setTime(int, int, int); // установка часов, минут и секунд
void printMilitary(); // печать времени в военном формате
void printStandard(); // печать времени в стандартном формате
private:
int hour;
int minute;
int second;
//0-23
//0-59
//0-59
};
/*
* Конструктор Time присваивает нулевые начальные значения каждому
* элементу данных. Обеспечивает согласованное начальное
* состояние всех объектов Time
*/
Time :: Time() {hour = minute = second = 0;}
/*
* Задание нового значения Time в виде военного времени.
* Проверка правильности значений данных.
* Обнуление неверных значений.
*/
void Time :: setTime(int h, int m, int s)
{
hour = (h >= 0 && h < 24) ? h : 0;
minute = (m >= 0 && m < 60) ? m : 0;
second = (s >= 0 && s < 60) ? s : 0;
}
// Печать времени в военном формате
344
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
void Time::printMilitary()
{
cout <<
<<
<<
<<
}
(hour < 10 ? "0":"␣")
hour << ":" << (minute< 10 ? "0":"␣")
minute << ":" << (second < 10 ? "0" : "␣")
second;
// Печать времени в стандартном формате
void Time::printStandard()
{
cout << ((hour == 0 || hour == 12) ? 12 : hour % 12)<< ":"
<<(minute < 10 ? "0" :"")<<minute <<":"
<<(second < 10 ? "0" : "") << second
<< (hour<12 ? "AM" : "␣PM");
}
// Формирование проверки простого класса Time
int main()
{
Time t; // определение экземпляра объекта t класса Time
cout << "Начальное значение военного времени равно ";
t.printMilitary();
cout << endl << "Начальное значение стандартного времени равно ";
t.printStandard();
t.setTime(13,27,6);
cout << endl << endl << "Военное время после setTime равно ";
t.printMilitary();
cout<<endl<<"Стандартное время после setTime равно ";
t.printStandard();
t.setTime(99,99,99);// попытка установить неправильные значения
cout << endl << endl <<"После попытки неправильной установки: "
<< endl << "Военное время: ";
t.printMilitary();
cout << endl << "Стандартное время: ";
t.printStandard();
cout << endl;
return 0;
}
345
10 Лабораторная работа № 10
10.4 Инкапсуляция.
Инкапсуляция - это механизм, который объединяет данные и методы, манипулирующие этими данными, и защищает и то и другое от внешнего вмешательства
или неправильного использования. Когда методы и данные объединяются таким
способом, создается объект.
Применяя инкапсуляцию, мы, защищаем данные, принадлежащие объекту,
от возможных ошибок, которые могут возникнуть при прямом доступе к этим
данным. Кроме того, применение этого принципа очень часто помогает локализовать
возможные ошибки в коде программы. А это на много упрощает процесс поиска и
исправления этих ошибок. Можно сказать, что инкапсуляция подразумевает под
собой скрытие данных (data hiding), что позволяет защитить эти данные.
Переменные состояния объекта скрыты от внешнего мира. Изменение состояния
объекта (его переменных) возможно ТОЛЬКО с помощью его методов (операций).
Этот принцип позволяет защитить переменные состояния объекта от неправильного их использования. Это существенно ограничивает возможность введения
объекта в недопустимое состояние и/или несанкционированное разрушение этого
объекта.
Хорошим примером применения принципа инкапсуляции являются команды
доступа к файлам. Обычно доступ к данным на диске можно осуществить только
через специальные функции. Вы не имеете прямой доступ к данным, размещенным
на диске. Таким образом, данные, размещенные на диске, можно рассматривать
скрытыми от прямого Вашего вмешательства.
Доступ к ним можно получить с помощью специальных функций, которые
по своей роли схожи с методами объектов. При этом, хотелось бы отметить два
момента, которые важны при применении этого подхода. Во-первых, Вы можете
получить все данные, которые Вам нужны за счет законченного интерфейса доступа
к данным. И, во-вторых, Вы не можете получить доступ к тем данным, которые
Вам не нужны. Это предотвращает случайную порчу данных, которая возможна при
прямом обращении к файловой системе. Кроме того, это предотвращает получение
неверных данных, т.к. специальные функции обычно используют последовательный
доступ к данным.
Применение этого метода ведет к снижению эффективности доступа к элементам объекта. Это обусловлено необходимостью вызова методов для изменения
внутренних элементов(переменных) объекта. Однако, при современном уровне развития вычислительной техники, эти потери в эффективности не играют существенной
роли.
Абстрактный тип данных - это группа тесно связанных между собой данных и
методов (функций), которые могут осуществлять операции над этими данными.
Поскольку подразумевается, что эта структура защищена от внешнего влияния,
то она считается инкапсулированной структурой. Важным же отличием от других
аналогичных структур является то, что данные заключенные в этой структуре, тесно
связанны и активно взаимодействуют между собой внутри структуры. Подобные
структуры имеют слабые связи с внешним миром посредством ограниченного интер-
346
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
фейса. Суть подобных структур довольно проста: данные имеют тесные взаимосвязи
внутри структуры, но слабые связи с внешним миром посредством ограниченного
числа методов. Таким образом, структуры подобного рода имеют достаточно простой,
но эффективный интерфейс, что позволяет их легко интегрировать в программах.
10.5 Наследование (Inheritance).
Наследование - это процесс, посредством которого один объект может наследовать свойства другого объекта и добавлять к ним черты, характерные только для
него.
Смысл и универсальность наследования заключается в том, что не надо каждый
раз заново (с нуля) описывать новый объект, а можно указать родителя (базовый
класс) и описать отличительные особенности нового класса. В результате, новый
объект будет обладать всеми свойствами родительского класса плюс своими собственными отличительными особенностями.
В описаниях языков ООП принято класс, из которого наследуют называть родительским классом (parent class) или основой класса (base class). Класс, который
получаем в результате наследования называется порожденным классом (derived
or child class). Родительский класс всегда считается более общим и развернутым.
Порожденный же класс всегда более строгий и конкретный, что делает его более
удобным в применении при конкретной реализации.
Новый, или производный класс может быть определен на основе уже имеющегося, или базового. При этом новый класс сохраняет все свойства старого: данные
объекта базового класса включаются в данные объекта производного, а методы
базового класса могут быть вызваны для объекта производного класса, причем
они будут выполняться над данными включенного в него объекта базового класса.
Иначе говоря, новый класс наследует как данные старого класса, так и методы их
обработки.
Наследование нужно, для того чтобы расширить уже созданные абстрактные
классы новыми свойствами или действиями. Есть несколько правил наследования
классов:
1. Создаётся иерархия классов, где классы стоящие ниже по иерархии могут иметь
доступ к переменным и функциям выше стоящих классов.
2. Классы стоящие ниже по иерархии- производные классы, относительно классов,
которые стоят выше них (4,5 - производные относительно 2, а 8,9 - производные
относительно 7).
3. Классы , которые состоят выше по иерархиям являются базовыми для ниже
стоящих классов (1 - базовый для 2 и 3).
4. Понятие базового и производного класса не предполагают относительность
уровня иерархи, т.е. то множество классов, которое стоят на один уровень выше
и являются базовыми.
347
10 Лабораторная работа № 10
5. Каждый производный класс имеет множество непосредственных родителей,
т.е. то множество классов, которые стоят на один уровень выше и являются
базовыми.
6. Соответственно: если родитель один- простое наследование, в другом же случаемножественное наследование.
7. Начало иерархии компьютера - это класс, один или более, которые называются
протоклассом плюс корень дерева. Обычно бывает 2 или 3 протокласса на практике. Обычно протокол являются пустыми или состоят из пустых виртуальных
функций. (Виртуальной называется функция, сущность которой определяется
во время выполнения программы.)
8. Классы стоящие ниже по иерархии имеют дополнительные свойства и функции
относительно вышестоящих классов.
Концепция наследования позволяет создавать новые классы, которые используют переменные и функции уже существующих его класса, но не содержит их в
своём теле.
Когда один класс наследуется другим, используется следующая форма записи:
class имя_производного_класса: спецификатор_доступа имя_базового_класса
{
//...................
}
Здесь спецификатор_доступа – это одно из трех ключевых слов: public,
private или protected. Спецификатор доступа определяет, как элементы базового
класса наследуются производным классом. Если спецификатором доступа наследуемого базового класса является ключевое слово public, то все открытые члены
348
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
базового класса остаются открытыми и в производном. Если спецификатором доступа наследуемого базового класса является ключевое слово private, то все открытыте
члены базового класса в производном классе становятся закрытыми. В обоих случаях
все закрытые члены базового класса в производном классе остаются закрытыми и
недоступными. Важно понимать, что если спецификатором доступа является ключевое слово private, то хотя открытые члены базового класса становятся закрытыми в
производном, они остаются доступными для функций – членов производного класса.
Технически сецификатор доступа необязателен.
Пример 10.2. Реализация принципа наследования
классов
Написать программу, которая реализует принцип наследования классов. При
этом она создает объект одного класса, но для полной реализации поставленной
задачи берет часть функций представленных в базовом классе.
Листинг 10.2
#include <iostream>
class Subtraction
{
public:
int funct_Sub(int a, int b);
};
class Sum:public Subtraction
{
public:
int funct_Sum(int a, int b);
};
int Subtraction::funct_Sub(int a, int b)
{
return a - b;
}
int Sum::funct_Sum(int a, int b)
{
return a + b;
}
int main()
349
10 Лабораторная работа № 10
{
Sum s;
int a = 5;
int b = 3;
cout << "Raznost␣" << s.funct_Sub(a,b) << endl;
cout << "Summa␣" << s.funct_Sum(a,b) << endl;
return 0;
}
10.6 Полиморфизм.
Полиморфизм - это свойство, которое позволяет одно и тоже имя использовать
для решения нескольких технически разных задач.
В общем смысле, концепцией полиморфизма является идея "один интерфейс,
множество методов". Это означает, что можно создать общий интерфейс для группы
близких по смыслу действий.
Преимуществом полиморфизма является то, что он помогает снижать сложность программ, разрешая использование одного интерфейса для едино- го класса
действий. Выбор конкретного действия, в зависимости от ситуации, возлагается на
компилятор.
Применительно к ООП, целью полиморфизма является использование одного
имени для задания общих для класса действий. На практике это означает способность
объектов выбирать внутреннюю процедуру (метод) исходя из типа данных, принятых
в сообщении.
Компилятор при наличии нескольких функций последовательно проверяет
шаблоны функций с одним и тем же именем пока не найдет подходящий.
Одна из разновидностей полиморфизма в языке C++ - перегрузка функций.
Программирование с классами предоставляет еще две возможности: перегрузку операций и использование так называемых виртуальных методов. Перегрузка операций
позволяет применять для собственных классов те же операции, которые используются для встроенных типов C++. Виртуальные методы обеспечивают возможность
выбрать на этапе выполнения нужный метод среди одноименных методов базового и
производного классов.
Операторы объектно-ориентированного
программирования, связанные с применением классов.
1. Оператор доступа (.)
Синтаксис:
переменная типа класс.член класса;
350
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Доступ по этому оператору извне возможен только к открытому классу public.
Под "извне" понимается внешняя функция для класса
2. Оператор видимости (::)
Назначение оператора – определить к какому классу относиться конкретная
функция.
Синтаксис:
Тип имя_класса :: имя_функции (список параметров с указанием типа)
{
тело функции
}
Оператор видимости трансформирует имя_функции в имя_класса +
имя_функции.
3. Операция стрелка → доступа к членам класса
Используется, если объект объявлен как указатель на класс.
y *obj;
obj input( ); эквивалентно -> (*obj).input ( );
Синтаксис оператора — "стрелка":
адрес_объекта -> член_класса;
при объявлении объекта: имя_класса*имя_объекта.
4. Указатель this
Используется только в функциях членах класса. Указатель возвращает объект
(адрес объекта), для которого функция применяется.
10.7 Конструкторы.
Допустим, имеется объект класса Clock. При объявлении этого объекта, он
автоматически инициализуется. Это означает, что при создании нового объекта
класса Clock переменной timestarted присваивается текущее системное время. Кто
(или вернее что) это делает?
Для этого нужно определить специальную функцию, которая будет специально
вызываться при создании каждого объекта. В языке С++ это можно сделать при
помощи специальной функции, которая называется конструктором (constructor).
Конструктор похож на любую другую функцию-член, за исключением следующего:
1. Имя конструктора совпадает с именем класса. Например, конструктором класса
Clock является функция Clock().
351
10 Лабораторная работа № 10
2. При создании нового объекта конструктор вызывается автоматически. Например, если создать два объекта mine и yours класса Clock, то конструктор
Clock() будет вызван дважды- один раз при создании объекта mine и другой
при создании объекта yours.
3. Конструктор нельзя вызвать из программы напрямую. Например, нельзя написать инструкцию mine.Clock(); Конструктор вызывается только однажды –
при создании объекта.
4. У конструктора нет возвращаемого типа. Возможно существование нескольких
конструкторов с разными списками аргументов.
Простейшие правила проектирования класса:
1) Переменные класса находятся в разделе private.
2) Для каждой переменной класса в классе должна быть функция установки.
3) Функции установки обычно являются открытыми.
4) Для каждой закрытой переменной класса в классе должна быть функция
доступа.
5) Функция доступа (обычно) расположена в открытой части класса.
Встроенными функциями (inline) называются функции класса, описанные
внутри класса, то есть тело функции находится внутри класса. Встроенными могут
быть функции, которые не содержат сложных операций if, вложенных в цепи.
Простейший класс:
class my:
{
public
int x,y;
publi:
inline
int funk1(void)
{
retun(x+y);
}
int funk2(void)
{
retun(x*y);
}
void setx(int var)
352
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
x = var;
}
void sety(int var)
{
y = var;
}
int retx(void)
{
return x;
}
int rety(void)
{
return y;
}
}
Функция узнается компилятором по двойным фигурным скобкам. Это объясняет их необходимость.
Описание конструктора составляет альтернативу использованию нескольких
функций (перегруженных), который по заданному double создает complex.
Например:
class complex
{
// ...
complex(double r)
{
re = r;
im = 0;
}
};
Конструктор, требующий только один параметр, необязательно вызывать явно:
complex z1 = complex(23);
complex z2 = 23;
353
10 Лабораторная работа № 10
И z1, и z2 будут инициализированы вызовом complex(23).
Конструктор - это предписание, как создавать значение данного типа. Когда
требуется значение типа, и когда такое значение может быть создано конструктором, тогда, если такое значение дается для присваивания, вызывается конструктор.
Например, класс complex можно было бы описать так:
class complex
{
double re, im;
public:
complex(double r, double i = 0)
{
re = r;
im = i;
}
friend complex operator+(complex, complex);
friend complex operator*(complex, complex);
};
и действия, в которые будут входить переменные complex и целые константы, стали
бы допустимы. Целая константа будет интерпретироваться как complex с нулевой
мнимой частью. Например, a = b*2 означает:
a = operator*(b,complex(double(2), double(0)))
Определенное пользователем преобразование типа применяется неявно только
тогда, когда оно является единственным.
Объект, сконструированный с помощью явного или неявного вызова конструктора, является автоматическим и будет уничтожен при первой возможности, обычно
сразу же после оператора, в котором он был создан.
Операции преобразования.
Использование конструктора для задания преобразования типа является удобным, но имеет следствия, которые могут оказаться нежелательными:
1. Не может быть неявного преобразования из определенного пользователем типа
в основной тип (поскольку основные типы не являются классами);
2. Невозможно задать преобразование из нового типа в старый, не изменяя описание старого;
354
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
3. Невозможно иметь конструктор с одним параметром, не имея при этом преобразования.
Последнее не является серьезной проблемой, а с первыми двумя можно справиться, определив для исходного типа операцию преобразования. Функция член
X::operator T(), где T - имя типа, определяет преобразование из X в T.
Например, можно определить тип tiny (крошечный), который может иметь
значение только в диапазоне 0...63, но все равно может свободно сочетаться в
целыми в арифметических операциях:
class tiny
{
char v;
int assign(int i)
{
return v = (i&~63)? (error("ошибка диапазона"),0): i;
}
public:
tiny(int i)
{
assign(i);
}
tiny(tiny& i)
{
v = t.v;
}
int operator=(tiny& i)
{
return v = t.v;
}
int operator=(int i)
{
return assign(i);
}
operator int()
{
return v;
}
}
Диапазон значения проверяется всегда, когда tiny инициализируется int, и
всегда, когда ему присваивается int. Одно tiny может присваиваться другому без
проверки диапазона. Чтобы разрешить выполнять над переменными tiny обычные
355
10 Лабораторная работа № 10
целые операции, определяется tiny::operator int(), неявное преобразование из int
в tiny. Всегда, когда в том месте, где требуется int, появляется tiny, используется
соответствующее ему int. Например:
int main()
{
tiny c1 = 2;
tiny c2 = 62;
tiny c3 = c2 - c1;
tiny c4 = c3;
int i = c1 + c2;
c1 = c2 + 2 * c1;
c2 = c1 -i;
c3 = c2;
}
//
//
//
//
//
//
c3 = 60
нет проверки диапазона
i = 64
ошибка диапазона: c1 =
ошибка диапазона: c2 =
нет проверки диапазона
(необязательна)
0 (а не 66)
0
(необязательна)
Тип вектор из tiny может оказаться более полезным, поскольку он экономит пространство. Чтобы сделать этот тип более удобным в обращении, можно
использовать операцию индексирования.
Другое применение определяемых операций преобразования - это типы, которые
предоставляют нестандартные представления чисел (арифметика по основанию 100,
арифметика с фиксированной точкой, двоично-десятичное представление и т.п.). При
этом обычно переопределяются такие операции, как + и *.
Функции преобразования оказываются особенно полезными для работы со
структурами данных, когда чтение (реализованное посредством операции преобразования) тривиально, в то время как присваивание и инициализация заметно более
сложны.
Типы istream и ostream опираются на функцию преобразования, чтобы сделать
возможными такие операторы, как while (cin » x) cout « x выше возвращает
istream&. Это значение неявно преобразуется к значению, которое указывает состояние cin, а уже это значение может проверяться оператором while. Однако определять
преобразование из оного типа в другой так, что при этом теряется информация,
обычно не стоит.
Неоднозначности.
Присваивание объекту (или инициализация объекта) класса X является допустимым, если или присваиваемое значение является X, или существует единственное
преобразование присваиваемого значения в тип X.
В некоторых случаях значение нужного типа может сконструироваться с помощью нескольких применений конструкторов или операций преобразования. Это
должно делаться явно; допустим только один уровень неявных преобразований,
356
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
определенных пользователем. Иногда значение нужного типа может быть сконструировано более чем одним способом. Такие случаи являются недопустимыми.
Например:
class x
{
/* ... */ x(int); x(char*);
};
class y
{
/* ... */ y(int);
};
class z
{
/* ... */ z(x);
};
overload f;
x f(x);
y f(y);
z g(z);
f(1);
// недопустимо: неоднозначность f(x(1)) или f(y(1))
f(x(1));
f(y(1));
g("asdf");
// недопустимо: g(z(x("asdf"))) не пробуется
g(z("asdf"));
Определенные пользователем преобразования рассматриваются только в том
случае, если без них вызов разрешить нельзя. Например:
class x
{
/* ... */ x(int);
}
overload h(double), h(x);
h(1);
Вызов мог бы быть проинтерпретирован или как h(double(1)), или как h(x(1)),
и был бы недопустим по правилу единственности. Но первая интерпретация использует только стандартное преобразование. Правила преобразования не являются
357
10 Лабораторная работа № 10
ни самыми простыми для реализации и документации, ни наиболее общими из
тех, которые можно было бы разработать. Возьмем требование единственности
преобразования. Более общий подход разрешил бы компилятору применять любое
преобразование, которое он сможет найти; таким образом, не нужно было бы рассматривать все возможные преобразования перед тем, как объявить выражение
допустимым. К сожалению, это означало бы, что смысл программы зависит от того,
какое преобразование было найдено. В результате смысл программы неким образом
зависел бы от порядка описания преобразования.
Поскольку они часто находятся в разных исходных файлах (написанных разными людьми), смысл программы будет зависеть от порядка компоновки этих частей
вместе. Есть другой вариант - запретить все неявные преобразования. Нет ничего
проще, но такое правило приведет либо к неэлегантным пользовательским интерфейсам, либо к бурному росту перегруженных функций, как это было в предыдущем
разделе с complex.
Самый общий подход учитывал бы всю имеющуюся информацию о типах и
рассматривал бы все возможные преобразования. Например, если использовать
предыдущее описание, то можно было бы обработать aa=f(1), так как тип aa определяет единственность толкования. Если aa является x, то единственное, дающее
в результате x, который требуется присваиванием, - это f(x(1)), а если aa - это
y, то вместо этого будет использоваться f(y(1)). Самый общий подход справился бы и с g("asdf"), поскольку единственной интерпретацией этого может быть
g(z(x("asdf"))). Сложность этого подхода в том, что он требует расширенного анализа всего выражения для того, чтобы определить интерпретацию каждой операции
и вызова функции. Это приведет к замедлению компиляции, а также к вызывающим
удивление интерпретациям и сообщениям об ошибках, если компилятор рассмотрит
преобразования, определенные в библиотеках и т.п.
Константы.
Константы классового типа определить невозможно в том смысле, в каком
1.2 и 12e3 являются константой типа double. Вместо них, однако, часто можно
использовать константы основных типов, если их реализация обеспечивается с помощью функций членов. Общий аппарат для этого дают конструкторы, получающие
один параметр. Когда конструкторы просты и подставляются inline, имеет смысл
рассмотреть в качестве константы вызов конструктора. Если, например, в есть
описание класса comlpex, то выражение zz1*3+zz2*comlpex(1,2) даст два вызова
функций, а не пять. К двум вызовам функций приведут две операции *, а операция
+ и конструктор, к которому обращаются для создания comlpex(3) и comlpex(1,2),
будут расширены inline.
358
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Большие Объекты.
При каждом применении для comlpex бинарных операций, описанных выше,
в функцию, которая реализует операцию, как параметр передается копия каждого
операнда. Расходы на копирование каждого double заметны, но с ними вполне можно
примириться. К сожалению, не все классы имеют небольшое и удобное представление.
Чтобы избежать ненужного копирования, можно описать функции таким образом,
чтобы они получали ссылочные параметры. Например:
class matrix
{
double m[4][4];
public:
matrix();
friend matrix operator+(matrix&, matrix&);
friend matrix operator*(matrix&, matrix&);
};
Ссылки позволяют использовать выражения, содержащие обычные арифметические операции над большими объектами, без ненужного копирования. Указатели
применять нельзя, потому что невозможно для применения к указателю смысл
операции переопределить невозможно. Операцию плюс можно определить так:
matrix operator+(matrix&, matrix&);
{
matrix sum;
for (int i=0; i<4; i++)
for (int j=0; j<4; j++)
sum.m[i][j] = arg1.m[i][j] + arg2.m[i][j];
return sum;
}
Эта operator+() обращается к операндам + через ссылки, но возвращает
значение объекта. Возврат ссылки может оказаться более эффективным:
class matrix
{
// ...
friend matrix& operator+(matrix&, matrix&);
friend matrix& operator*(matrix&, matrix&);
};
Это является допустимым, но приводит к сложности с выделением памяти.
Поскольку ссылка на результат будет передаваться из функции как ссылка на возвращаемое значение, оно не может быть автоматической переменной. Поскольку часто
359
10 Лабораторная работа № 10
операция используется в выражении больше одного раза, результат не может быть и
статической переменной. Как правило, его размещают в свободной памяти. Часто
копирование возвращаемого значения оказывается дешевле (по времени выполнения,
объему кода и объему данных) и проще программируется.
Присваивание и Инициализация.
Рассмотрим очень простой класс строк string:
struct string
{
char* p;
int size;
// размер вектора, на который указывает p
string(int sz)
{
p = new char[size=sz];
}
~string()
{
delete p;
}
};
Строка - это структура данных, состоящая из вектора символов и длины этого
вектора. Вектор создается конструктором и уничтожается деструктором.
Однако это может привести к неприятностям. Например:
void f()
{
string s1(10);
string s2(20);
s1 = s2;
}
будет размещать два вектора символов, а присваивание s1 = s2 будет портить
указатель на один из них и дублировать другой. На выходе из f() для s1 и s2
будет вызываться деструктор и уничтожать один и тот же вектор с непредсказуемо
разрушительными последствиями. Решение этой проблемы состоит в том, чтобы
соответствующим образом определить присваивание объектов типа string:
struct string
{
360
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
char* p;
int size;
// размер вектора, на который указывает p
string(int sz)
{
p = new char[size = sz];
}
~string()
{
delete p;
}
void operator = (string&)
};
void string::operator = (string& a)
{
if (this == &a) return;
// остерегаться s = s;
delete p;
p = new char[size=a.size];
strcpy(p,a.p);
}
Это определение string гарантирует, и что предыдущий пример будет работать
как предполагалось. Однако небольшое изменение f() приведет к появлению той же
проблемы в новом облике:
void f()
{
string s1(10);
s2 = s1;
}
Теперь создается только одна строка, а уничтожается две. К неинициализированному объекту определенная пользователем операция присваивания не применяется.
Беглый взгляд на string::operator = () объясняет, почему было неразумно так делать: указатель p будет содержать неопределенное и совершенно случайное значение.
Часто операция присваивания полагается на то, что ее аргументы инициализированы.
Для такой инициализации, как здесь, это не так по определению. Следовательно,
361
10 Лабораторная работа № 10
нужно определить похожую, но другую, функцию, чтобы обрабатывать инициализацию:
struct string
{
char* p;
int size;
// размер вектора, на который указывает p
string(int sz)
{
p = new char[size = sz];
}
~string()
{
delete p;
}
void operator = (string&)
string(string&);
};
void string::string(string& a)
{
p = new char[size = a.size];
strcpy(p,a.p);
}
Для типа X инициализацию тем же типом X обрабатывает конструктор X(X&).
Присваивание и инициализация – разные действия. Это особенно существенно
при описании деструктора. Если класс X имеет конструктор, выполняющий нетривиальную работу вроде освобождения памяти, то скорее всего потребуется полный
комплект функций, чтобы полностью избежать побитового копирования объектов:
class X
{
X(something);
X(&X);
operator=(X&);
~X();
//
//
//
//
//
...
конструктор: создает объект
конструктор: копирует в инициализации
присваивание: чистит и копирует
деструктор: чистит
}
362
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Есть еще два случая, когда объект копируется: как параметр функции и как
возвращаемое значение. Когда передается параметр, инициализируется неинициализированная до этого переменная - формальный параметр. Семантика идентична
семантике инициализации. То же самое происходит при возврате из функции, хотя
это менее очевидно. В обоих случаях будет применен X(X&), если он определен:
string g(string arg)
{
return arg;
}
int main()
{
string s = "asdf";
s = g(s);
}
Ясно, что после вызова g() значение s обязано быть "asdf". Копирование
значения s в параметр arg сложности не представляет: для этого надо взывать
string(string&). Для взятия копии этого значения из g() требуется еще один вызов
string(string&); на этот раз инициализируемой является временная переменная,
которая затем присваивается s. Такие переменные, естественно, уничтожаются как
положено с помощью string:: string() при первой возможности.
Индексирование.
Чтобы задать смысл индексов для объектов класса используется функция
operator[]. Второй параметр (индекс) функции operator[] может быть любого
типа. Это позволяет определять ассоциативные массивы и т.п.
Пример 10.2. Реализация принципа индексирования
классов
Написать программу для подсчета числа вхождений слов в файл с применением
ассоциативного массива и использованием функции. Здесь определяется надлежащий
тип ассоциативного массива:
struct pair
{
char* name;
363
10 Лабораторная работа № 10
int val;
};
class assoc
{
pair* vec;
int max;
int free;
public:
assoc(int);
int& operator[](char*);
void print_all();
}
В assoc хранится вектор пар pair длины max. Индекс первого неиспользованного элемента вектора находится в free.
Конструктор выглядит так:
assoc :: assoc(int s)
{
max = (s<16) ? s : 16;
free = 0;
vec = new pair[max];
}
При реализации применяется простой и неэффективный метод поиска, однако
при переполнении assoc увеличивается:
#include <iostream>
int assoc :: operator[](char* p)
/*
работа с множеством пар "pair": поиск p,
возврат ссылки на целую часть его "pair"
делает новую "pair", если p не встречалось
*/
{
register pair* pp;
for (pp = &vec[free-1]; vec <= pp; pp--)
if (strcmp(p,pp->name)==0) return pp->val;
if (free == max) {
// переполнение: вектор увеличивается
364
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
pair* nvec = new pair[max*2];
for (int i = 0; iname=new char[strlen(p)+1];i++);strcpy(pp->name,p);
pp->val = 0;
// начальное значение: 0
return pp->val;
}
Поскольку представление assoc скрыто, нужен способ его печати. Здесь воспользуемся простой функцией печати:
vouid assoc::print_all()
{
for (int i = 0; i>buf) vec[buf]++;
vec.print_all();
}
Вызов функции.
Вызов функции, то есть запись выражение (список_выражений), можно проинтерпретировать как бинарную операцию, и операцию вызова можно перегружать так
же, как и другие операции. Список параметров функции operator() вычисляется
и проверяется в соответствие с обычными правилами передачи параметров. Перегружающая функция может оказаться полезной главным образом для определения
типов с единственной операцией и для типов, у которых одна операция настолько
преобладает, что другие в большинстве ситуаций можно не принимать во внимание.
Для типа ассоциативного массива assoc мы не определили итератор. Это можно
сделать, определив класс assoc_iterator, работа которого состоит в том, чтобы в
определенном порядке поставлять элементы из assoc. Итератору нужен доступ к
данным, которые хранятся в assoc, поэтому он сделан другом:
class assoc
{
friend class assoc_iterator;
pair* vec;
int max;
int free;
public:
assoc(int);
int& operator[](char*);
365
10 Лабораторная работа № 10
};
Итератор определяется как
class assoc_iterator
{
assoc* cs; // текущий массив assoc
int i;
// текущий индекс
public:
assoc_iterator(assoc& s)
{
cs = &s; i = 0;
}
pair* operator()()
{
return (ifree)? &cs->vec[i++] : 0;
}
};
Надо инициализировать assoc_iterator для массива assoc, после чего он будет
возвращать указатель на новую pair из этого массива всякий раз, когда его будут
активизировать операцией (). По достижении конца массива он возвращает 0:
int main()
// считает вхождения каждого слова во вводе
{
const MAX = 256; // больше самого большого слова
char buf[MAX];
assoc vec(512);
while (cin>>buf) vec[buf]++;
assoc_iterator next(vec);
pair* p;
while ( p = next() )
cout << p->name << ":␣" << p->val << "\n";
}
Итераторный тип вроде этого имеет преимущество перед набором функций,
которые выполняют ту же работу: у него есть собственные закрытые данные для
хранения хода итерации. К тому же обычно существенно, чтобы одновременно могли
работать много итераторов этого типа.
366
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Конечно, такое применение объектов для представления итераторов никак
особенно с перегрузкой операций не связано. Многие любят использовать итераторы с
такими операциями, как first(), next() и last() (первый, следующий и последний).
Пример 10.3. Класс Строка
Написать программу с использованием класса string, в котором производится
учет ссылок на строку с целью минимизировать копирование. В качестве констант
применяются стандартные символьные строки C++.
Листинг 10.4
#include <iostream>
#include <string>
class string
{
struct srep
{
char* s;
int
n;
};
srep *p;
public:
string(char *);
string();
string(string &);
string& operator=(char *);
string& operator=(string &);
~string();
// указатель на данные
// счетчик ссылок
// string x = "abc"
// string x;
// string x = string ...
char& operator[](int i);
friend ostream& operator<<(ostream&, string&);
friend istream& operator>>(istream&, string&);
friend int operator==(string& x, char* s)
{
return strcmp(x.p->s, s) == 0;
}
friend int operator==(string& x, string& y)
{
367
10 Лабораторная работа № 10
return strcmp(x.p->s, y.p->s) == 0;
}
friend int operator!=(string& x, char* s)
{
return strcmp(x.p->s, s) != 0;
}
friend int operator!=(string& x, string& y)
{
return strcmp(x.p->s, y.p->s) != 0;
}
};
Конструкторы и деструкторы просты (как обычно):
string::string()
{
p = new srep;
p->s = 0;
p->n = 1;
}
string::string(char* s)
{
p = new srep;
p->s = new char[ strlen(s)+1 ];
strcpy(p->s, s);
p->n = 1;
}
string::string(string& x)
{
x.p->n++;
p = x.p;
}
string::~string()
{
if (--p->n == 0)
{
delete p->s;
delete p;
368
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
}
}
Как обычно, операции присваивания очень похожи на конструкторы. Они
должны обрабатывать очистку своего первого (левого) операнда:
string& string::operator=(char* s)
{
if (p->n > 1)
{
// разъединить себя
p-n--;
p = new srep;
}
else if (p->n == 1)
delete p->s;
p->s = new char[ strlen(s)+1 ];
strcpy(p->s, s);
p->n = 1;
return *this;
}
Благоразумно обеспечить, чтобы присваивание объекта самому себе работало
правильно:
string& string::operator=(string& x)
{
x.p->n++;
if (--p->n == 0)
{
delete p->s;
delete p;
}
p = x.p;
return *this;
}
Операция вывода задумана так, чтобы продемонстрировать применение учета
ссылок. Она повторяет каждую вводимую строку (с помощью операции «, которая
определяется позднее):
ostream& operator<<(ostream& s, string& x)
369
10 Лабораторная работа № 10
{
return s << x.p->s << "␣[" << x.p->n << "]\n";
}
Операция ввода использует стандартную функцию ввода символьной строки:
istream& operator>>(istream& s, string& x)
{
char buf[256];
s >> buf;
x = buf;
cout << "echo:␣" << x << "\n";
return s;
}
Для доступа к отдельным символам предоставлена операция индексирования.
Осуществляется проверка индекса:
void error(char* p)
{
cerr << p << "\n";
exit(1);
}
char& string::operator[](int i)
{
if (i<0 || strlen(p->s) s[i];
}
Головная программа просто немного опробует действия над строками. Она
читает слова со ввода в строки, а потом эти строки печатает. Она продолжает это
делать до тех пор, пока не распознает строку done, которая завершает сохранение
слов в строках, или не встретит конец файла. После этого она печатает строки в
обратном порядке и завершается.
int main()
{
string x[100];
int n;
cout << "отсюда начнем\n";
for (n = 0; cin>>x[n]; n++)
370
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
{
string y;
if (n==100) error("слишком много строк");
cout << (y = x[n]);
if (y=="done") break;
}
cout << "отсюда мы пройдем обратно\n";
for (int i=n-1; 0<=i; i--) cout << x[i];
}
Друзья и Члены.
Теперь, наконец, можно обсудить, в каких случаях для доступа к закрытой
части определяемого пользователем типа использовать члены, а в каких - друзей.
Некоторые операции должны быть членами: конструкторы, деструкторы и виртуальные функции, но обычно это зависит от выбора.
Рассмотрим простой класс X:
class X
{
// ...
X(int);
int m();
friend int f(X&);
};
Внешне не видно никаких причин делать f(X&) другом дополнительно к члену
X::m() (или наоборот), чтобы реализовать действия над классом X.
Однако член X::m() можно вызывать только для "настоящего объекта в то
время как друг f() может вызываться для объекта, созданного с помощью неявного
преобразования типа. Например:
void g()
{
1.m(); // ошибка
f(1); // f(x(1));
}
371
10 Лабораторная работа № 10
Поэтому операция, изменяющее состояние объекта, должно быть членом, а
не другом. Для определяемых пользователем типов операции, требующие в случае
фундаментальных типов операнд lvalue (=, *=, ++ и т.д.), наиболее естественно
определяются как члены. И наоборот, если нужно иметь неявное преобразование
для всех операндов операции, то реализующая ее функция должна быть другом, а
не членом. Это часто имеет место для функций, которые реализуют операции, не
требующие при применении к фундаментальным типам lvalue в качестве операндов
(+, -, || и т.д.). Если никакие преобразования типа не определены, то оказывается,
что нет никаких существенных оснований в пользу члена, если есть друг, который
получает ссылочный параметр, и наоборот. В некоторых случаях программист
может предпочитать один синтаксис вызова другому. Например, оказывается, что
большинство предпочитает для обращения матрицы m запись m.inv().
Конечно, если inv() действительно обращает матрицу m, а не просто возвращает
новую матрицу, обратную m, ей следует быть другом.
При прочих равных условиях выбирайте, чтобы функция была членом: никто
не знает, вдруг когда-нибудь кто-то определит операцию преобразования.
Невозможно предсказать, потребуют ли будущие изменения изменить статус
объекта. Синтаксис вызова функции члена ясно указывает пользователю, что объект
можно изменить; ссылочный параметр является далеко не столь очевидным. Кроме
того, выражения в члене могут быть заметно короче выражений в друге. В функции
друге надо использовать явный параметр, тогда как в члене можно использовать
неявный this. Если только не применяется перегрузка, имена членов обычно короче
имен друзей.
Композиция классов.
Включение нескольких объектов других классов в данный класс с тем, чтобы
данный класс мог брать нужные сведения из других классов называется композицией.
Пример 10.4. Реализация принципа композиции классов
Написать программу для вывода на экран определенных значений.
Листинг 10.5
#include <iostream>
class One
{
public:
One(int = 1);
void print();
// конструктор по умолчанию
372
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
private:
int a;
};
class Two
{
public:
Two(int = 1);
void print();
// конструктор по умолчанию
private:
int a;
};
class OnePlusTwo_Three
{
public:
OnePlusTwo_Three(int=1, int=1); // конструктор по умолчанию
void print();
private:
One o;
Two t;
};
One::One(int a1)
{
a = a1;
}
void One::print()
{
cout << a << endl;
}
void Two::print()
{
cout << a << endl;
}
Two::Two(int a2)
{
a = a2;
}
OnePlusTwo_Three::OnePlusTwo_Three(int a1, int a3) : o(a1),t(a3)
{
}
void OnePlusTwo_Three::print()
373
10 Лабораторная работа № 10
{
o.print();
t.print();
}
int main()
{
OnePlusTwo_Three opt(6,8);
opt.print();
return 0;
}
В этой программе определено три класса: One, Two, OnePlusTwo_Three.
Композиция классов в этом примере реализована в том, что мы включили под
директивой private в классе OnePlusTwo_Three, два объекта классов: Two t, One
o. А также посмотрев на определение конструктора класса OnePlusTwo_Three мы
видим, что он содержит параметры, помогающие определить конструкторы классов
One и Two.
Использование дружественных функций и указателя
this.
Дружественные функции определяются вне области действия этого класса, но
имеют право доступа к закрытым элементам private данного класса. Функция или
класс в целом могут быть объявлены другом (friend) другого класса Дружественные
функции используются для повышения производительности.
Чтобы объявить функцию как друга (friend) класса, перед ее прототипом в
описании класса ставится ключевое слово friend. Чтобы объявить класс ClassTwo
как друга класса ClassOne, запишите объявление в форме friend ClassTwo в определении класса ClassOne.
Дружественность требует разрешения, то есть чтобы класс B стал другом класса
A, класс A должен объявить, что класс B - его друг. Таким образом дружественность
не обладает ни свойством симметричности, ни свойством транзитивности, то есть
если класс A друг класса B , а класс B - друг класса C, то от сюда не следует, что
класс B друг класса A, что класс C друг класса B, или что класс A - друг класса C.
Ниже приведенная программа демонстрирует объявление и использование дружественной функции setX для установки закрытого элемента данных x класса count.
Заметим, что объявление friend появляется первым (по соглашению) в объявлении
класса, даже раньше объявления закрытых функций элментов.
374
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Пример 10.5. Реализация дружественности классов
Написать программу, выводящую на экран целое значение.
Листинг 10.6
#include <iostream>
class One
{
friend class Two;
public:
One(int = 1);
private:
int a;
};
class Two
{
public:
int ret_value(One o1, int v);
};
One::One(int a1)
{
a = a1;
}
int Two::ret_value(One o1, int v)
{
o1.a = v;
return v;
}
int main()
{
Two t;
One o1;
int v, r;
cout << "Enter␣the␣number␣what␣you␣want␣to␣see␣later!␣" << endl;
cin >> v;
r = t.ret_value(o1,v);
375
10 Лабораторная работа № 10
cout << endl;
cout << r << endl;
return 0;
}
В этой программе класс Two является другом для класса One. Поэтому, даже
если мы определяем объект класса One внутри функции описываемой в классе Two,
то мы все равно имеем право на доступ к закрытым членам класса One. По этой
причине, иногда говорят, что дружественность нарушает объектно-ориентированный
подход. Когда функция элемент ссылается на другой элемент какого-то объекта
данного класса, имеется ввиду соответствующий объект. Это происходит благодаря
тому, что каждый объект сопровождается указателем на самого себя - называемым
указателем this - это неявный аргумент во всех ссылках на элементы внутри этого
объекта. Указатель this можно использовать также и явно. Каждый объект может
определить свой собственный адрес с помощью ключевого слова this.
Указатель this неявно используется для ссылки как на данные элементы так
и на функции - элементы объекта. Тип указателя this зависит от типа объекта и от
того, объявлена ли функция элемент, в которой используется this, как const. Например, в не константной функции-элементе класса Employee указатель this имеет
тип Employee *const (константный указатель на объект Employee). В константной функции-элементе класса Employee указатель this имеет тип const Employee
*const (константный указатель на объект Employee, который тоже константный).
Пример 10.6. Использование указателя this
#include <iostream>
class Test
{
public:
Test (int = 0);
void print() const;
private:
int x;
};
Test::Test(int a) {x = a;} // конструктор
void Test :: print() const
{
cout <<"x␣=␣"<< x << endl <<"this->x␣=␣" <<
this -> x<<endl <<"(*this).x="<<(*this).x<<endl;
376
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
}
int main()
{
Test a(12);
a.print();
return 0;
}
Перегрузка операций.
Любая операция, определенная в C++, может быть перегружена для созданного класса. Это делается с помощью функций специального вида, называемых
функциями-операциями (операторными функциями). Общий вид такой функции:
возвращаемый_тип operator # (список параметров)
{
тело функции
}
где вместо знака ] ставится знак перегружаемой операции.
Функция-операция может быть реализована либо как функция класса, либо как
внешняя (обычно дружественная) функция. В первом случае количество параметров у
функции-операции на единицу меньше, так как первым операндом при этом считается
сам объект, вызвавший данную операцию. Например, два варианта перегрузки
операции сложения для класса Point: первый вариант - в форме метода класса:
class Point
{
double x, y;
public:
//...
Point operator +(Point&);
};
Point Point :: operator +(Point& p)
{
return Point(x+p.x, y+p.y) ;
}
377
10 Лабораторная работа № 10
Второй вариант - в форме внешней глобальной функции, причем функция, как
правило, объявляется дружественной классу, чтобы иметь доступ к его закрытым
элементам:
class Point
{
double x, y;
public:
//. . .
friend Point operator +(Point&, Point&);
};
Point operator +(Point& p1, Points p2)
{
return Point (p1.x+p2.x, p1.y+p2.y) ;
}
Независимо от формы реализации операции «+» можно теперь написать:
Point p1(0,2), p2(-1,5);
Point = p1 + p2;
Встретив выражение p1 + р2, компилятор в случае первой формы перегрузки
вызовет метод p1.operator + (p2), а в случае второй формы перегрузки - глобальную функцию operator + (p1, р2). Результатом выполнения данных операторов
будет точка рЗ с координатами х = -1, у = 7.
Итак, если операция может перегружаться как внешней функцией, так и
функцией класса, следует использовать перегрузку в форме метода класса, если
нет каких-либо причин, препятствующих этому. Например, если первый аргумент
(левый операнд) относится к одному из базовых типов (к примеру, int), то перегрузка
операции возможна только в форме внешней функции.
Перегрузка операций инкремента (декремента).
Операция инкремента (декремента) имеет две формы: префиксную и постфиксную. Для первой формы сначала изменяется состояние объекта в соответствии с
данной операцией, а затем он (объект) используется в том или ином выражении.
Для второй формы объект используется в том состоянии, которое у него было до
начала операции, а потом уже его состояние изменяется.
Чтобы компилятор смог различить эти две формы операции инкремента (декремента), для них используются разные сигнатуры, например:
378
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Point& operator ++( ); // префиксный инкремент
Point operator ++(int); // постфиксный инкремент
Реализация данных операций на примере класса Point:
Point& Point::operator ++()
{
x++;
y++;
return *this;
}
Point Point::operator ++(int)
{
Point old = *this;
X++;
y++;
return old;
}
В префиксной операции осуществляется возврат результата по ссылке. Это
предотвращает вызов конструктора копирования для создания возвращаемого значения и последующего вызова деструктора. В постфиксной операции инкремента
возврат по ссылке не подходит, поскольку необходимо вернуть первоначальное состояние объекта, сохраненное в локальной переменной old. Таким образом, префиксный
инкремент является более эффективной операцией, чем постфиксный инкремент.
Использование префиксного инкремента (декремента) для параметра цикла for дает
более эффективный программный код.
Перегрузка операции присваивания.
Если не определить эту операцию в некотором классе, то компилятор создаст
операцию присваивания по умолчанию, которая выполняет поэлементное копирование объекта. В этом случае возможно появление тех же проблем, которые возникают
при использовании конструктора копирования по умолчанию. Поэтому если в классе
требуется определить конструктор копирования, то его верной спутницей должна
быть перегруженная операция присваивания, и наоборот.
Операция присваивания может быть определена только в форме метода класса
и она, в отличие от всех остальных операций, не наследуется. Например, для класса
Man перегрузку операции присваивания можно определить следующим образом:
379
10 Лабораторная работа № 10
// Man.h (интерфейс класса)
class Man
{
public:
// . . .
// операция присваивания
Man& operator =(const Man&);
private:
char* pName;
// II ...
};
// n. (реализация класса) // . . .
Man& Man::operator =(const Man& man)
{
// проверка на самоприсваивание
if (this == &man) return *this;
// уничтожить предыдущее значение
delete [] pName;
pName = new char[strlen(man.pName) + 1];
strcpy(pName. man.pName);
birth_year = man.birth_year;
pay = man.pay;
return *this;
}
Моменты реализации операции присваивания:
- убедитесь, что не выполняется присваивание вида х = х. Если левая и правая
части ссылаются на один и тот же объект, то делать ничего не надо. Если не
перехватить этот особый случай, то следующий шаг уничтожит значение, на
которое указывает pName, еще до того, как оно будет скопировано;
- удалите предыдущие значения полей в динамически выделенной памяти;
- выделите память под новые значения полей;
- скопируйте в нее новые значения всех полей;
- возвратите значение объекта, на которое указывает this (то есть *this).
380
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Статические элементы класса.
До сих пор одноименные поля разных объектов одного и того же класса были
уникальными. Но что делать, если необходимо создать переменную, значение которой
будет общим для всех объектов конкретного класса? Если воспользоваться глобальной
переменной, то это нарушит принцип инкапсуляции данных. Модификатор static как
раз и позволяет объявить поле в классе, которое будет общим для всех экземпляров
класса. Кроме объявления статического поля в классе, необходимо также дать его
определение в глобальной области видимости программы, например:
class Coo
{
static int count; // объявление в классе
// остальной код
};
int Coo::count = 1; // определение и инициализация
// int Coo::count; // по умолчанию инициализируется нулем
Аналогично статическим полям могут быть объявлены и статические методы
класса (с модификатором static). Они могут обращаться непосредственно только к
статическим полям и вызывать только другие статические методы класса, потому
что им не передается скрытый указатель this. Статические методы не могут быть
константными (const) и виртуальными (virtual).
Обращение к статическим методам производится так же, как к статическим
полям - либо через имя класса, либо, если хотя бы один объект класса уже создан,
через имя объекта.
Пример 10.7. Класс треугольников
Для некоторого множества заданных координатами своих вершин треугольников найти треугольник максимальной площади (если максимальную площадь имеют
несколько треугольников, то найти первый из них). Предусмотреть возможность
перемещения треугольников и проверки включения одного треугольника в другой.
Для реализации этой задачи составить описание класса треугольников на плоскости.
Предусмотреть возможность объявления в клиентской программе (main) экземпляра
треугольника с заданными координатами вершин. Предусмотреть наличие в классе
методов, обеспечивающих:
1) перемещение треугольников на плоскости;
2) определение отношения > для пары заданных треугольников (мера сравнения площадь треугольников);
381
10 Лабораторная работа № 10
3) определение отношения включения типа: «Треугольник 1 входит в (не
входит в) Треугольник 2».
Программа должна содержать меню, позволяющее осуществить проверку всех
методов класса. Применим гибридный подход: разработку главного клиента main( )
проведем по технологии функциональной декомпозиции, а функции-серверы, вызываемые из main(), будут использовать объекты.
Начнем с выявления основных понятий/классов. Первый очевидный класс
Triangle необходим для представления треугольников (через три точки, задающие
его вершины). Точку на плоскости представим с помощью пары вещественных чисел,
задающих координаты точки по осям х и у.
Таким образом, с понятием точки связывается как минимум пара атрибутов.
В принципе, этого уже достаточно, чтобы подумать о создании класса Point. Если
же представить, что можно делать с объектом типа точки - например, перемещать
ее на плоскости или определять ее вхождение в заданную фигуру, - то становится
ясным, что такой класс Point будет полезен.
Итак, объектно-ориентированная декомпозиция дала нам два класса: Triangle
и Point.
Если класс В является «частным случаем» класса А, то говорят, что В is а А
(например, класс треугольников есть частный вид класса многоугольников: Triangle
is a Polygon).
Если класс А содержит в себе объект класса В, то говорят, что A has а В
(например, класс треугольников может содержать в себе объекты класса точек:
Triangle has a Point).
Порядок перечисления вершин особо важен, так как в дальнейшем, решая
подзадачу определения отношения включения одного треугольника в другой, мы
будем рассматривать стороны треугольника как векторы. Условимся, что вершины
треугольника перечисляются в направлении по часовой стрелке.
Займемся теперь основным клиентом - main(). Здесь мы применяем функциональную декомпозицию, или технологию нисходящего проектирования. В соответствии с данной технологией основной алгоритм представляется как последовательность нескольких подзадач. Каждой подзадаче соответствует вызываемая
серверная функция. На начальном этапе проектирования тела этих функций могут
быть заполнены «заглушками» - отладочной печатью. Если при этом в какой-то
серверной функции окажется слабое сцепление, то она в свою очередь разбивается
на несколько подзадач. То же самое происходит и с классами, используемыми в
программе: по мере реализации подзадач они пополняются необходимыми для этого
методами. Такая технология облегчает отладку и поиск ошибок, сокращая общее
время разработки программы.
На первом этапе мы напишем код для начального представления классов Point
и Triangle, достаточный для того, чтобы создать несколько объектов типа Triangle
и реализовать первый пункт меню - вывод всех объектов на экран.
382
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Этап 1
Point.h
#ifndef POINT_H
#define POINT_H
class Point
{
public:
// Конструктор
Point(double _x = 0, double _y = 0): x(_x), y(_y) {}
// Другие методы
void Show() const;
public:
double x, y;
};
#endif /* POINT_H */
Point.cpp
#include <iostream>
#include "Point.h"
using namespace std;
void Point::Show() const
{
cout << "␣(" << x << "␣,␣" << y <<")" ;
}
Triangle.h
#ifndef TRIANGLE_H
#define TRIANGLE_H
#include "Point.h"
class Triangle
383
10 Лабораторная работа № 10
{
public:
Triangle(Point, Point, Point, const char*); // конструктор
Triangle(const char*); // конструктор пустого (нулевого) треугольника
~Triangle( ); // деструктор
Point Get_v1() const
{
return v1;
} // Получить значение v1
Point Get_v2() const
{
return v2;
} // Получить значение v2
Point Get_v3() const
{
return v3;
} // Получить значение v3
char* GetName( ) const
{
return name;
} // Получить имя объекта
void Show() const;
// Показать объект
void ShowSideAndArea() const; // Показать стороны и площадь объекта
public:
static int count;
private:
char* objID;
char* name;
Point v1, v2, v3;
double a;
double b;
double c;
// кол-во созданных объектов
//
//
//
//
//
//
идентификатор объекта
наименование треугольника
вершины
сторона, соединяющая v1 и v2
сторона, соединяющая v2 и v3
сторона, соединяющая v1 и v3
};
#endif /* TRIANGLE_H */
384
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Triangle.cpp
// Triangle.cpp
// Реализация класса Triangle
#include <math.h>
#include <iostream>
#include <iomanip>
#include <cstring>
#include "Triangle.h"
using namespace std;
// Конструктор
Triangle::Triangle(Point _v1,Point _v2,Point _v3,const char* ident):
v1(_v1),v2(_v2),v3(_v3)
{
char buf[16];
objID = new char[strlen(ident) + 1];
strcpy(objID, ident);
count++;
sprintf(buf, "Треугольник %d", count);
name = new char[strlen(buf) + 1];
strcpy(name, buf);
a = sqrt((v1.x-v2.x)*(v1.x-v2.x)+(v1.y-v2.y)*(v1.y-v2.y));
b = sqrt((v2.x-v3.x)*(v2.x-v3.x)+(v2.y-v3.y)*(v2.y-v3.y));
c = sqrt((v1.x-v3.x)*(v1.x-v3.x)+(v1.y-v3.y)*(v1.y-v3.y));
cout << "Constructor_1␣for:␣" << objID
<< "␣(" << name << ")"
<< endl; // отладочный вывод
}
// Конструктор пустого (нулевого) треугольника
Triangle::Triangle(const char* ident)
{
char buf[16];
objID = new char[strlen(ident) +1];
strcpy(objID, ident);
count++;
sprintf(buf, "Треугольник %d", count);
name = new char[strlen(buf) +1];
strcpy(name, buf);
a = b = c = 0;
cout << "Constructor_2␣for:␣" << objID
<< "␣(" << name << ")"
<< endl; // отладочный вывод
}
385
10 Лабораторная работа № 10
// Деструктор
Triangle::~Triangle()
{
cout << "Destructor␣for:␣" << objID << endl;
delete [] objID;
delete [] name;
}
// Показать объект
void Triangle::Show() const
{
cout << name << "␣:␣" ;
v1.Show();
v2.Show();
v3.Show();
cout << endl;
}
// Показать стороны и площадь объекта
void Triangle::ShowSideAndArea() const
{
double p = (a + b + c) / 2;
double s = sqrt(p * (p - a) * (p - b) * (p - c));
cout << "␣--␣" << endl;
cout << name << ":";
cout.precision(4);
cout << "␣a=␣" << setw(5) << a;
cout << ",␣b=␣" << setw(5) << b;
cout << ",␣c=␣" << setw(5) << c;
cout << ":\ts=␣" << s << endl;
}
Main.cpp
////////////////////////////////
//
Проект Task1_2
//
////////////////////////////////
// Main.cpp
#include <iostream>
#include "Triangle.h"
using namespace std;
386
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
int Menu();
int GetNumber(int, int);
void ExitBack();
void Show(Triangle* [], int);
void Move(Triangle* [], int);
void FindMax(Triangle* [], int);
void IsIncluded(Triangle* [], int);
// Инициализация глобальных переменных
int Triangle::count =0;
// ---- главная функция ---int main()
{
// Определения точек
Point p1(0, 0);
Point p2(0.5,1);
Point p3(1, 0);
Point p4(0,4.5);
Point p5(2, 1);
Point p6(2, 0);
Point p7(2, 2);
Point p8(3, 0);
// Определения треугольников
Triangle triaA(p1, p2, p3, "triaA");
Triangle triaB(p1, p4, p8, "triaB");
Triangle triaC(p1, p5, p6, "triaC");
Triangle triaD(p1, p7, p8, "triaD");
// Определение массива указателей на треугольники
Triangle* pTria[] = { &triaA, &triaB, &triaC, &triaD };
int n = sizeof(pTria)/sizeof(pTria[0]);
// Главный цикл
bool done = false;
while(!done)
{
switch(Menu())
{
case 1: Show(pTria, n); break;
case 2: Move(pTria, n); break;
case 3: FindMax(pTria, n); break;
387
10 Лабораторная работа № 10
case 4: IsIncluded(pTria, n); break;
case 5: cout << "Конец работы." << endl;
done = true;
break;
}
}
return 0;
}
// II -вывод меню
int Menu()
{
cout << "\n=====␣Главное меню =====" << endl;
cout << "1␣-␣вывести все объекты\t 3 - найти максимальный" << endl;
cout << "2␣-␣переместить\t\t 4 - определить отношение включения"
<< endl;
cout << "\t\t␣5␣-␣выход" << endl;
return GetNumber(1, 5);
}
// ввод целого числа в заданном диапазоне
int GetNumber(int min, int max)
{
int number = min - 1;
while (true)
{
cin >> number;
if ((number >= min) && (number <= max) && (cin.peek() == ’\n’))
break;
else
{
cout << "Повторите ввод (ожидается число от "
<< min << "␣␣" << max << "):" << endl;
cin.clear();
while (cin.get() != ’\n’) {};
}
}
return number;
}
// возврат в функцию с основным меню
void ExitBack()
{
388
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
cout << "Нажмите Enter." << endl;
cin.get();
cin.get();
}
// вывод всех треугольников
void Show(Triangle* p_tria[], int k)
{
int i;
cout << "===␣Перечень треугольников ===" << endl;
for (i = 0; i < k; ++i) p_tria[i]->Show();
for (i = 0; i < k; ++i)
p_tria[i]->ShowSideAndArea();
ExitBack();
}
// II -- перемещение
void Move(Triangle* p_tria[], int k)
{
cout << "=======␣Перемещение ======" << endl;
// здесь будет код функции...
ExitBack();
}
// поиск максимального треугольника
void FindMax(Triangle* p_tria[], int k)
{
cout << "=␣Поиск максимального треугольника =" << endl;
// здесь будет код функции...
ExitBack();
}
// определение отношения включения
void IsIncluded(Triangle* p_tria[], int k)
{
cout << "=====␣Отношение включения =====" << endl;
// здесь будет код функции...
ExitBack();
}
// ---------------Task1_2
//////////////////////////////////////////////
389
10 Лабораторная работа № 10
Рекомендуем вам обратить внимание на следующие моменты в проекте Task1_2.
1. Класс Point (файлы Point.h, Point.cpp). Реализация класса Point пока что
содержит единственный метод Show(), назначение которого очевидно: показать
объект типа Point на экране. Здесь следует заметить, что при решении реальных задач в какой-либо графической оболочке метод Show() действительно
нарисовал бы нашу точку, да еще в цвете.
Но мы-то изучаем «чистый» C++, так что придется удовольствоваться текстовым
выводом на экран основных атрибутов точки - ее координат.
2. Класс Triangle (файлы Triangle.h, Triangle.cpp). Назначение большинства
полей и методов очевидно из их имен и комментариев.
Поле static int count играет роль глобального счетчика создаваемых объектов; мы сочли удобным в конструкторах генерировать имена треугольников
автоматически: «Треугольник 1», «Треугольник 2» и т. д., используя текущее
значение count (возможны и другие способы именования тре угольников).
Поле char* objID избыточно для решения нашей задачи - оно введено исключительно для целей отладки и обучения; вскоре вы увидите, что благодаря
отладочным операторам печати в конструкторах и деструкторе удобно наблюдать за созданием и уничтожением объектов.
Метод ShowSideAndArea() введен также только для целей отладки, - убедившись, что стороны треугольника и его площадь вычисляются правильно (с
помощью калькулятора), в дальнейшем этот метод можно удалить. Конструктор пустого (нулевого) треугольника предусмотрен для создания временных
объектов, которые могут модифицироваться с помощью присваивания.
Метод Show() - см. комментарий выше по поводу метода Show() в классе Point.
К сожалению, здесь нам тоже не удастся нарисовать треугольник на экране;
вместо этого печатаются координаты его вершин.
3. Основной модуль (файл Main.cpp). Инициализация глобальных переменных:
обратите внимание на оператор int Triangle::count = 0: - если вы забудете
это написать, компилятор очень сильно обидится. Функция main ():
- определения восьми точек p1,..., р8 выбраны произвольно, но так, чтобы
из них можно было составить треугольники;
- определения четырех треугольников сделаны тоже произвольно, впоследствии на них будут демонстрироваться основные методы класса; однако не
забывайте, что вершины в каждом треугольнике должны перечисляться
по часовой стрелке;
- далее определяются массив указателей Triangle* pTria[] с адресами
объявленных выше треугольников и его размер n; в таком виде удобно
передавать адрес pTria и величину n в вызываемые серверные функции;
390
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
- главный цикл функции довольно прозрачен и дополнительных пояснений
не требует.
Функция Menu () - после вывода на экран списка пунктов меню вызывается
функция GetNumber(), возвращающая номер пункта, введенный пользователем
с клавиатуры.
Функция Show() пpocто выводит на экран перечень всех треугольников. В завершение вызывается функция ExitBack(), которая обеспечивает заключительный
диалог с пользователем после обработки очередного пункта меню.
Остальные функции по обработке оставшихся пунктов меню выполнены в виде
заглушек, выводящих только наименование соответствующего пункта. Тестирование
и отладка первой версии программы После компиляции и запуска программы вы
должны увидеть на экране следующий текст:
Constructor_1
for: triaA Треугольник 1)
Constructor_1
for: triaB (Треугольник 2)
Constructor_1
for: triaC (Треугольник 3)
Constructor_1
for: triaD (Треугольник 4)
=============== Г л а в н о е м е н ю ===============
1 - вывести все объекты
2 - переместить
3 - найти максимальный
4 - определить отношение включения
5 - выход
Введите с клавиатуры цифру 12. Программа выведет:
1
======= Перечень треугольников ========
Треугольник 1: (0, 0) (0.5, 1) (1, 0)
Треугольник 2: (0, 0) (0, 4.5) (3, 0)
Треугольник 3: (0. 0) (2,
1) (2, 0)
Треугольник 4: (0. 0) (2,
2) (3, 0)
Треугольник 1: а=1.118, b=1.118, с=1: s=0 .5
Треугольник 2: а=4 .5 , b=5.408, с=3: s=6.75
Треугольник 3: а=2.236, b=1,
с=2: s=1
Треугольник 4: а=2.828, b=2.236, с=3: s=3
Нажмите Enter.
После ввода числовой информации всегда подразумевается нажатие клавиши
Enter. Выбор первого пункта меню проверен. Нажмите Enter. Программа выведет:
391
10 Лабораторная работа № 10
============ Главное меню =============
Теперь проверим выбор второго пункта меню. Введите с клавиатуры цифру 2.
На экране должно появиться:
2
============ Перемещение ===============
Нажмите Enter.
Выбор второго пункта проверен. Нажмите Enter. Программа выведет:
============ Главное меню =============
Теперь проверим ввод ошибочного символа. Введите с клавиатуры любой
буквенный символ, например w, и нажмите Enter. Программа должна выругаться:
Повторите ввод (ожидается число от 1 до 5 ) :
Проверяем завершение работы. Введите цифру 5. Программа выведет:
5
Конец работы.
Destructor for:
Destructor for:
Destructor for:
Destructor for:
triaD
triaC
triaB
triaA
Тестирование закончено. Обратите внимание на то, что деструкторы объектов
вызываются в порядке, обратном вызову конструкторов.
Продолжим разработку программы. На втором этапе мы добавим в классы
Point и Triangle методы, обеспечивающие перемещение треугольников, а в основной
модуль - реализацию функции Move(). Кроме этого, в классе Triangle мы удалим
метод ShowSideAndArea(), поскольку он был введен только для целей отладки и
свою роль уже выполнил.
Этап 2
Внесите следующие изменения в тексты модулей проекта.
1. Модуль Point.h: добавьте сразу после объявления метода Show() объявление
операции-функции «+=», которая позволит нам впоследствии реализовать
метод перемещения Move() в классе Triangle:
void operator +=(Point&);
2. Модуль Point.cpp. Добавьте код реализации данной функции:
392
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
void Point::operator +=(Point& p)
{
x += p.x;
y += p.y;
}
3. Модуль Triangle.h.
Удалите объявление метода ShowSideAndArea().
Добавьте объявление метода:
void Move(Point);
4. Модуль Triangle.cpp.
Удалите метод ShowSideAndArea().
Добавьте код метода Move():
// Переместить объект на величину (dp.x, dp.y)
void Triangle:: Move(Point dp)
{
v1 += dp;
v2 += dp;
v3 += dp;
}
5. Модуль Main.cpp.
В список прототипов функций в начале файла добавьте сигнатуру:
double GetDouble();
Добавьте в файл текст новой функции GetDouble() либо сразу после функции
Show(), либо в конец файла. Эта функция предназначена для ввода вещественного числа и вызывается из функции Move(). В ней предусмотрена защита от
ввода недопустимых (например, буквенных) символов аналогично тому, как
это решено в функции GetNumber():
393
10 Лабораторная работа № 10
// ввод вещественного числа
double GetDouble()
{
double value;
while (true)
{
cin >> value;
if(cin.peek() == ’\n’ ) break;
else
{
cout << "Повторите ввод (ожидается вещественное число):"
<< endl;
cin.clear();
while(cin.get() != ’\n’ ) { };
}
}
return value;
}
Замените заглушку функции Move() следующим кодом:
// перемещение
void Move(Triangle* p_tria[], int k)
{
cout << "=========␣Перемещение =========" << endl;
cout << "Введите номер треугольника (от 1 до" << k << "␣):␣";
int i = GetNumber(d, k) - 1;
p_tria[i]->Show();
Point dp;
cout << "Введите смещение по x: ";
dp.x = GetDouble();
cout << "Введите смещение по y: ";
dp.y = GetDouble();
p_tria[i]->Move(dp);
cout << "Новое положение треугольника:" << endl;
p_tria[i]->Show();
ExitBack();
}
Выполнив компиляцию проекта, проведите его тестирование аналогично тестированию на первом этапе. После выбора второго пункта меню и ввода данных,
394
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
задающих номер треугольника, величину сдвига по х и величину сдвига по у, вы
должны увидеть на экране примерно следующее:
2
============= Перемещение =============
Введите номер треугольника (от 1 до 4): 1
Треугольник 1: (0. 0) (0.5. 1) (1, 0)
Введите смещение по х: 2.5
Введите смещение по у: -7
Новое положение треугольника:
Треугольник 1: (2.5, -7) (3, -6) (3.5, -7)
Нажмите Enter.
Продолжим разработку программы.
Этап 3
На этом этапе мы добавим в класс Triangle метод, обеспечивающий сравнение
треугольников по их площади, а в основной модуль - реализацию функции FindMax().
Внесите следующие изменения в тексты модулей проекта:
1. Модуль Triangle.h: добавьте объявление функции-операции:
bool operator >(const Triangle&) const;
2. Модуль Triangle.cpp: добавьте код реализации функции-операции:
// Сравнить объект (по площади) с объектом tria
bool Triangle :: operator >(const Triangles tria) const
{
double p = (a + b + c) / 2;
double s = sqrt(p *(p - a)*(p - b)*(p - c )) ;
double p1 = ( triaa+triab+triac)/2;
double s1 = sqrt(p1*(p1-tria.a)*(p1-tria.b)*(p1-tria.c));
if (s > s1) return true;
else
return false;
}
3. Модуль Main.cpp: замените заглушку функции FindMax() следующим кодом:
// поиск максимального треугольника
void FindMaxCTriangle* p_tria[], int k)
{
395
10 Лабораторная работа № 10
cout << "=␣Поиск максимального треугольника =" << endl;
// Создаем объект triaMax, который по завершении
// поиска будет идентичен максимальному объекту.
// Инициализируем его значением 1-го объекта из массива объектов.
Triangle triaMax("triaMax"): triaMax = *p_tria[0];
//
for (int i = 1; i < 4: ++i)
if (*p_tria[i] > triaMax) triaMax = *p_tria[i];
cout << "Максимальный треугольник: "
<< triaMax.GetName() << endl;
ExitBack();
}
Откомпилируйте программу и запустите. Выберите третий пункт меню. На
экране должно появиться:
3
=== Поиск максимального треугольника ==
Constructor_2 for: triaMax (Треугольник 5)
Максимальный треугольник: Треугольник 2
Нажмите Enter.
Как видите, максимальный треугольник найден правильно. Нажмите Enter.
Появится текст:
Destructor for: triaB
============ Г л а в н о е м е н ю ==============
1 - вывести все объекты
3 - найти максимальный
2 - переместить
4 - определить отношение включения
5 - выход
Чтобы завершить работу, введите цифру 5 и нажмите Enter. На передний план
выскочит диалоговая панель с жирным белым крестом на красном кружочке и с
сообщением об ошибке:
Debug Assertion Failed!
Program: C:\.... a.out File: dbgdel.cpp
Line 47
Программа выведет на экран следующее:
396
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
5
Конец работы.
Destructor for: triaD
Destructor for: triaC
Destructor for: ННННННННННээээЭЭЭЭЭЭЭЭЭЭО
В предыдущем выводе нашей программы после того как функция FindMax()
выполнила основную работу и вывела на экран сообщение
Максимальный треугольник: Треугольник 2
программа пригласила пользователя нажать клавишу Enter. Это приглашение выводится функцией ExitBack(). А вот после нажатия клавиши Enter на экране появился
текст:
Destructor for: triaB
после которого опять было выведено главное меню.
Значит, деструктор для объекта triaB был вызван в момент возврата из функции FindMax(). Внутри функции объявлен объект triaMax, и мы даже видели работу
его конструктора:
Constructor_2 for:
triaMax (Треугольник 5)
А где же вызов деструктора, который по идее должен произойти в момент
возврата из функции FindMax()? Объект triaMax после своего объявления неоднократно модифицируется с помощью операции присваивания. Последняя такая
модификация происходит в цикле, причем объекту triaMax присваивается значение
объекта triaB. А теперь давайте вспомним, что если мы не перегрузили операцию
присваивания для некоторого класса, то компилятор сделает это за нас, но в такой
«операции присваивания по умолчанию» будут поэлементно копироваться все поля
объекта. При наличии же полей типа указателей возможны проблемы, что мы и
получили.
В поля objID и name объекта triaMax были скопированы значения одноименных
полей объекта triaB. В момент выхода из функции FindMax() деструктор объекта
освободил память, на которую указывали эти поля. А при выходе из основной
функции main() деструктор объекта triaB попытался еще раз освободить эту же
память. Это делать нельзя, потому что этого делать нельзя никогда.
Нужно добавить в класс Triangle перегрузку операции присваивания, а заодно
и конструктор копирования.
Внесите следующие изменения в тексты модулей проекта:
1. Модуль Triangle.h.
Добавьте объявление конструктора копирования:
397
10 Лабораторная работа № 10
Triangle(const Triangle&); // конструктор копирования
Добавьте объявление операции присваивания:
Triangle& operator =(const Triangle&);
2. Модуль Triangle.cpp.
Добавьте реализацию конструктора копирования:
// Конструктор копирования
Triangle::Triangle(const Triangle& tria):
vl(tria.v1), v2 (tria.v2), v3(tria.v3)
{
cout << "Copy␣constructor␣for:␣"
<< tria.objID << endl ; // отладочный вывод
objID = new char[strlen(tria.objID) + strlen(копия)) + 1];
strcpy(objID, tria.objID): strcat(objID, "(копия)");
name = new char[strlen(tria.name) + 1 ] :
strcpy(name,tria.name);
a = tria.a; b = tria.b; = tria.;
}
Добавьте реализацию операции присваивания:
// Присвоить значение объекта tria
Triangle& Triangle :: operator=(const Triangle& tria)
{
cout << "Assign␣operator:␣" << objID << "␣=␣"
<< tria.objID << endl; // отладочный вывод
if (&tria == this) return *this;
delete [] name;
name = new char[strlen(tria.name) + 1];
strcpy(name, tria.name);
a = tria.a;
b = tria.b;
c = tria.c;
398
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
return *this;
}
И в конструкторе копирования, и в операторе присваивания перед копированием содержимого полей, на которые указывают поля типа char*, для них выделяется
новая память. Обратите внимание, что в конструкторе копирования после переписи
поля objID мы добавляем в конец этой строки текст «(копия)». А в операции присваивания это поле, идентифицирующее объект, вообще не затрагивается и остается
в своем первоначальном значении. Все это полезно для целей отладки.
Откомпилируйте и запустите программу. Выберите третий пункт меню. На
экране должен появиться текст:
3
=== Поиск максимального треугольника ==
Constructor_2 for: triaMax (Треугольник 5)
Assign operator: triaMax = triaA
Assign operator: triaMax = triaB
Максимальный треугольник: Треугольник 2
Нажмите Enter.
Обратите внимание на отладочный вывод операции присваивания. Продолжим
тестирование. Нажмите Enter. Программа выведет:
Destructor for: triaMax
============== Г л а в н о е м е н ю ===============
1 - вывести все объекты
3 - найти максимальный
2 - переместить
4 - определить отношение включения
5 - выход
Обратите внимание на то, что был вызван деструктор для объекта triaMax, а
не triaB. Продолжим тестирование. Введите цифру 5. Программа выведет:
5
Конец работы.
Destructor for:
Destructor for:
Destructor for:
Destructor for:
triaD
triaC
triaB
triaA
Осталось теперь решить самую сложную подзадачу - определение отношения
включения одного треугольника в другой.
399
10 Лабораторная работа № 10
Этап 4
Из многочисленных подходов к решению этой подзадачи наш выбор остановился на алгоритме, в основе которого лежит определение относительного положения
точки и вектора на плоскости. Вектор - это направленный отрезок прямой линии,
начинающийся в точке beg_p и заканчивающийся в точке end_p. При графическом
изображении конец вектора украшают стрелкой. Теперь призовите ваше пространственное воображение или вооружитесь карандашом и бумагой, чтобы проверить
следующее утверждение. Вектор (beg_p, end_p) делит плоскость на пять непересекающихся областей:
1) все точки слева от воображаемой бесконечной прямой1, на которой лежит наш
вектор (область LEFT),
2) все точки справа от воображаемой бесконечной прямой, на которой лежит наш
вектор (область RIGHT),
3) все точки на воображаемом продолжении прямой назад от точки beg_p в
бесконечность (область BEHIND),
4) все точки на воображаемом продолжении прямой вперед от точки end_p в
бесконечность (область AHEAD),
5) все точки, принадлежащие самому вектору (область BETWEEN).
Для выяснения относительного положения точки, заданной некоторым объектом класса Point, добавим в класс Point перечисляемый тип:
enum ORIENT { LEFT, RIGHT, AHEAD, BEHIND, BETWEEN }:
а также метод C1assify(beg_p, end_p), возвращающий значение типа ORIENT для
данной точки относительно вектора (beg_p, end_p).
Обладая этим мощным методом, совсем нетрудно определить, находится ли
точка внутри некоторого треугольника. Мы договорились перед началом решения
задачи, что треугольники будут задаваться перечислением их вершин в порядке
изображения их на плоскости по часовой стрелке. То есть каждая пара вершин
образует вектор, и эти векторы следуют один за другим по часовой стрелке. При
этом условии некоторая точка находится внутри треугольника тогда и только тогда,
когда ее ориентация относительно каждой вектора стороны треугольника имеет одно
из двух значений: либо RIGHT, либо BETWEEN. Эту подзадачу будет решать метод
InTriangle() в классе Point.
Изложим по порядку, какие изменения нужно внести в тексты модулей.
1. Модуль Point.h.
Добавьте перед объявлением класса Point объявление нового типа ORIENT, а
также упреждающее объявление типа Triangle, чтобы имя типа Triangle было
400
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
известно компилятору в данной единице трансляции, так как оно используется
в сигнатуре метода InTriangle():
enum ORIENT
{
LEFT, RIGHT, AHEAD, BEHIND, BETWEEN
};
class Triangle;
Добавьте внутри класса Point объявления функций:
Point operator +(Point&);
Point operator -(Point&);
double Length() const;
//
//
//
ORIENT Classify(Point&, Point&) const; //
//
//
//
bool InTriangle(Triangle&) const;
//
//
//
определяет длину вектора
точки в полярной системе
координат
определяет положение
точки относительно
вектора, заданного
двумя точками
определяет, находится ли
точка внутри
треугольника
Функция-операция «-» и метод Length() будут использованы при реализации
метода Classify(), а функция-операция «+» добавлена для симметрии. Метод
Classify(), в свою очередь, вызывается из метода InTriangle().
2. Модуль Point.cpp.
Добавьте после директивы
<math.h>.
]include <iostream>
директиву
]include
Она необходима для использования функции sqrt(x) из математической библиотеки C++ в алгоритме метода Length().
Добавьте после
<Triangle.h>.
директивы
]include <Point.h>
директиву
]include
Последняя необходима в связи с использованием имени класса Triangle в
данной единице трансляции.
Добавьте реализацию функций-операций:
401
10 Лабораторная работа № 10
Point Point :: operator+(Point& p)
{
return Point(x+p.x, y+p.y) ;
}
Point Point :: operator-(Points p)
{
return Point(x-p.x,y-p.y) ;
}
Добавьте реализацию метода Length():
double Point :: Length() const
{
return sqrt(x*x + y*y);
}
Добавьте реализацию метода Classify():
ORIENT Point :: Classify(Point& beg_p, Points end_p) const
{
Point p0 = *this; Point a = end_p - beg_p;
Point b = p0 - beg_p;
double sa = a . x * b .y - b.x * a.y;
if (sa > 0.0) return LEFT;
if (sa < 0.0) return RIGHT;
if ((a.x * b.x < 0.0) || (a.y * b.y < 0.0)) return BEHIND;
if (a.Length() < b.Length()) return AHEAD;
return BETWEEN;
}
Аргументы передаются в функцию по ссылке - это позволяет избежать вызова
конструктора копирования.
Добавьте реализацию метода InTriangle():
bool Point :: InTriangle(Triangle& tria) const
{
402
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
ORIENT or1 = Classify(tria.Get_v1(),tria.Get_v2());
ORIENT or2 = Classify(tria.Get_v2(),tria.Get_v3());
ORIENT = Classify(tria.Get_v3(),tria.Get_v1());
if((or1 == RIGHT || or1 == BETWEEN)
&& (or2 == RIGHT || or2 == BETWEEN)
&& (r == RIGHT || r == BETWEEN)) return true;
else return false;
}
3. Модуль Triangle.h: добавьте в классе Triangle объявление дружественной
функции (все равно, в каком месте):
// Определить, входит ли один треугольник во второй
friend bool TriaInTna(Triangle, Triangle);
4. Модуль Triangle.cpp: добавьте в конец файла реализацию внешней дружественной функции:
// Определить, входит ли треугольник tria1 в треугольник tria2
bool TriaInTria(Triangle tria1, Triangle tria2)
{
Point v1 = tria1.Get_v1();
Point v2 = tria1.Get_v2();
Point v3 = tria1.Get_v3();
return (v1.InTriangle(tria2) && v2.InTriangle(tria2)
&& v3.InTriangle(tria2));
return true;
}
Результат, возвращаемый функцией, основан на проверке вхождения каждой
вершины первого треугольника (tria1) во второй треугольник (tria2).
5. Модуль Main.cpp: замените заглушку функции IsIncluded() следующим кодом:
// определение отношения включения
void IsIncluded(Triangle* p_tria[], int k)
{
403
10 Лабораторная работа № 10
cout << "====␣Отношение включения ====" << endl;
cout << "Введите номер 1-го треугольника"
<< "(от 1 до " << k << "):␣";
int i1 = GetNumber(1, k) - 1;
cout << "Введите номер 2-го треугольника"
<< "(от 1 до " << k << "):␣" ;
int i2 = GetNumber(1, k) - 1;
if (TriaInTria(*p_tria[il], *p_tria[i2]))
cout << p_tria[i1]->GetName() << "␣-␣входит в - "
<< p_tria[i2]->GetName() << endl;
else
cout << p_tria[i1]->GetName() << "␣-␣не входит в - "
<< p_tria[i2]->GetName() << endl;
ExitBack();
}
Модификация проекта завершена. Откомпилируйте и запустите программу.
Выберите четвертый пункт меню. Следуя указаниям программы, введите номера
сравниваемых треугольников, например 1 и 2. Вы должны получить следующий
результат:
4
======== Отношение включения ==========
Введите номер 1-го треугольника (от 1 до 4 ):
1
Введите номер 2-го треугольника (от 1 до 4 ):
2
Copy constructor for: triaB
Copy constructor for: triaA
Destructor for: triaA(копия)
Destructor for: triaB(копия)
Треугольник 1 - входит в - Треугольник 2
Нажмите Enter.
404
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Аппаратура и материалы.
Для выполнения лабораторной работы необходим персональный компьютер c
необходимым программным обеспечением - операционная система MS Windows или
GNU/Linux и компилятор от C++, среда разработки приложений Geany.
Указания по технике безопасности.
Техника безопасности при выполнении лабораторной работы совпадает с общепринятой для пользователей персональных компьютеров, самостоятельно не производить ремонт персонального компьютера, установку и удаление программного
обеспечения; в случае неисправности персонального компьютера сообщить об этом
обслуживающему персоналу лаборатории (оператору, администратору); соблюдать
правила техники безопасности при работе с электрооборудованием; не касаться
электрических розеток металлическими предметами; рабочее место пользователя
персонального компьютера должно содержаться в чистоте; не разрешается возле
персонального компьютера принимать пищу, напитки.
405
10 Лабораторная работа № 10
10.8 Методика и порядок выполнения работы.
Перед выполнением лабораторной работы каждый студент получает индивидуальное задание. Защита лабораторной работы происходит только после его
выполнения (индивидуального задания). При защите лабораторной работы студент
отвечает на контрольные вопросы, приведенные в конце, и поясняет выполненное
индивидуальное задание. Ход защиты лабораторной работы контролируется преподавателем.
Порядок выполнения работы:
1. Проработать примеры, приведенные в лабораторной работе.
2. Реализовать класс Time.
3. Реализовать класс треугольников.
4. Решить задачу на двумерные массивы согласно своему варианту лабораторной
работы № 6 с использованием классов.
5. Реализовать класс многочленов и смоделировать операции над ними, принимая
во внимание то, что многочлен – это объект, имеющий свойства: коэффициенты
и степени.
406
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
Содержание отчета и его форма.
Отчет по лабораторной работе должен состоять из:
1. Названия лабораторной работы.
2. Цели и содержания лабораторной работы.
3. Ответов на контрольные вопросы лабораторной работы.
4. Формулировки индивидуальных заданий и порядка их выполнения.
Отчет о выполнении лабораторной работы в письменном виде сдается преподавателю.
Вопросы для защиты работы
1. Какова структура программы на объектно-ориентированном языке?
2. Что такое объект ООП?
3. Что такое класс?
4. На какие разделы может делиться класс?
5. Назовите и охарактеризуйте три основных принципа в использовании классов.
6. Что такое конструктор и для чего он используется?
7. Как происходит индексирование объектов класса?
8. Как происходит вызов функции?
9. Что такое композиция классов?
10. Для чего и каким образом используются дружественные функции?
11. Для чего требуется указатель this?
12. Для чего используется модификатор static?
407
Литература
1. Воронкин Р.А., Банько М.А. Лабораторный практикум по основам программирования на языке высокого уровня C++, Ставрополь, 2007.
2. Романов Е.Л. Практикум по программированию на C++, Санкт-Петербург
«БХВ-Петербург» 2004
3. Стучилин В.В. Программирование на языках высокого уровня. Учебное
пособие. –М: МГГУ 2005
4. Давыдов В.Г. Программирование и основы алгоритмизации. –М: Высшая
школа 2010
5. Куренкова Т.В., Светозарова Г.И. Основы алгоритмизации и объектноориентированного программирования. Учеб. пособие. Изд.Дом МИСиС
2011
6. Андреева О.В. Информатика и программирование: Раздел: Основы алгоритмизации и программирования. Лаб. практикум. Изд.Дом МИСиС
2014
7. Светозарова Г.И., Андреева О.В, Крынецкая Г.С. и др. Информационные
технологии. Изд.Дом МИСиС 2009
8. Вернер М. Мир программирования. Основы кодирования. Учебное пособие. –М: Техносфера 2006
9. Хаггарти Р. Дискретная математика для программистов. Учебное пособие.
–М: Техносфера 2005
10. Камаев В.А. Технологии программирования. Учебник для ВУЗов. –М: Высшая школа 2006
11. Майк МакГрат. Программирование на С для начинающих. Издательство
«Эксмо»; Москва 2016
12. Бен Клеменс. Язык С в XXI веке. O’RELLY 2015
408
Нишонов М.М., Темербекова Б.М. Филиал НИТУ "МИСиС" в г.Алмалык
13. Ulla Kirch-Prinz, Peter Prinz. A Complete Guide to Programming in C++.
Massachusetts 2002
409
Скачать