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

В.И.

КОВШОВ

КОНСТРУИРОВАНИЕ ПРОГРАММНОГО

ОБЕСПЕЧЕНИЯ

Лабораторный практикум

Самара
Самарский государственный технический университет
2022
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ

ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ


ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ
ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ
«САМАРСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»

К а ф е д р а «Вычислительная техника»

В.И. КОВШОВ

КОНСТРУИРОВАНИЕ ПРОГРАММНОГО

ОБЕСПЕЧЕНИЯ

Лабораторный практикум

Самара
Самарский государственный технический университет
2022

1
Печатается по решению редакционно-издательского совета СамГТУ

УДК 004.438
ББК 32.973.26-018.2
К 56

Ковшов В.И.
К 56 Конструирование программного обеспечения: лабораторный прак-
тикум / В.И. Ковшов. – Самара: Самар. гос. техн. ун-т, 2022. – 58 с.

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


входящих в состав ФГОС 3++. Лабораторные работы позволяют студентам на
практике освоить основные технологии разработки приложений в среде MS
Visual Studio – структурное и объектно ориентированное программирование на
языке C++. В каждой работе коротко изложен теоретический материал, приве-
дены примеры программ; определен порядок выполнения работы, содержание
отчета. Контрольные вопросы помогают закрепить изучаемый материал.
Предназначен для бакалавров, обучающихся по направлению 09.03.01
«Информатика и вычислительная техника», профиль «Вычислительные маши-
ны, комплексы, системы и сети» и направления 09.03.04 «Программная инже-
нерия», профиль «Программная инженерия».

Р е ц е н з е н т канд. техн. наук Д.Ю. К о к о р е в

УДК 681.324
К 56

© В.И. Ковшов, 2022


© Самарский государственный
технический университет, 2022

2
Лабораторная работа № 1
ПРОСТЫЕ ПРОГРАММЫ С ЦИКЛАМИ
И ОПЕРАТОРАМИ КОНСОЛЬНОГО ВВОДА/ВЫВОДА

Цель работы – знакомство с работой в среде MS Visual Studio и


программированием на языке С++.

ЗАДАНИЕ

1. Освоить основные операции в среде MS Visual Studio, выполняе-


мые при разработке программ на языке С++.
2. Разработать консольную программу, которая реализует ввод точек
экспериментально полученной зависимости y = f(x), вывод их в виде
таблицы, вычисляет и выводит в виде таблицы сумму, среднее ариф-
метическое, минимальное и максимальное значения по X и по Y.
3. Разработать консольную программу, которая для полинома, задан-
ного в таблице 1, выводит таблицу значений по X и Y аналогично
п. 2. Диапазон и шаг изменения Х вводить с клавиатуры.
4. Разработать консольную программу, которая на заданном отрезке
выполняет поиск одного из корней полинома методом деления от-
резка пополам. Границы отрезка вводить с клавиатуры.
Таблица 1
Варианты задания
Бригада № Полином Корни
1 X + 7.5X + 3X – 3.5 = 0
3 2
-7, -1, 0.5
2 X3 + 3X2 – 6X – 8 = 0 -4, -1, 2
3 X3 – 5X2 + 2X + 8 = 0 -1, 2, 4
4 X3 – 8X2 + 11X + 20 = 0 -1, 4, 5
5 X3 + 2X2 – 5X – 6 = 0 -3, -1, 2
6 X3 – 4.5X2 + 6.5X – 3 = 0 1, 1.5, 2
7 X3 – 4X2 + X + 6 = 0 -1, 2, 3
8 X3 – 1.5X2 – X + 1.5 = 0 -1, 2, 1.5
9 X3 – 1.5X2 – 2.5X + 3 = 0 -1.5, 1, 2
10 X3 – 3.5X2 + 0.5X + 5 = 0 -1, 2, 2.5
11 X3 + 2.5X2 – X – 2.5 = 0 -2.5, -1, 1
12 X3 – 4X2 – 20X + 48 = 0 -4, 2, 6

3
ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

РАБОТА В СРЕДЕ MS VISUAL STUDIO

Профессиональный разработчик ПО вынужден большую часть


жизни проводить за компьютером, бесконечно выполняя цепочку од-
них и тех же действий, которые составляют суть процесса отладки при-
ложения. Студия разработчика Microsoft Visual Studio .NET предостав-
ляет для этой цели целый набор достаточно удобных встроенных ин-
струментов. Возможности студии позволяют разрабатывать проекты
различных типов, организовывать зависимые проекты, объединять про-
екты разных типов. Вы можете создавать и отлаживать как структурно,
так и объектно ориентированные программы внутри интегрированной
среды разработки Integrated Development Environment (IDE).
Создаваемые вами файлы должны быть включены в Visual C++
Project, который необходимо поместить в решение (Solution). Так назы-
вается хранилище проектов или область памяти (папка на диске), где
расположены ваши проекты и их конфигурации. Solution может содер-
жать несколько разных проектов, в том числе и проекты различных ти-
пов (например, Visual C++ и Visual J++). Запустив оболочку, вы увиди-
те окно редактора текста, а справа от него – окна, которые представля-
ют собой инструменты для просмотра элементов проекта:
− ClassView – демонстрирует классы, их данные и методы;
− Solution Explorer – показывает все файлы, включенные в
проекты решения;
− Properties – показывает свойства выбранного в окне Solution
Explorer или ClassView элемента.
Документация выделена в отдельное приложение, называемое
MSDN Library (библиотека разработчика). Для получения справки по
какому-либо элементу программы в редакторе кода установите на
нем курсор и нажмите кнопку F1.
При создании нового проекта (по команде меню File > New > Pro-
jects) по умолчанию создается один проект, но в двух конфигурациях:

4
− Debug – версия проекта, в которой выключается оптимизация
кода и включается в него отладочная информация;
− Release – версия, в которой делается все наоборот
(выключается отладочная информация и включается оптимизация
кода).
Обычно при создании нового проекта он автоматически помеща-
ется во вновь созданное решение в конфигурации Debug. Демонстра-
ция файлов проекта в отдельной папке проекта не означает, что фай-
лы физически расположены в одной папке. Оболочка может исполь-
зовать ссылки на файлы из других директорий, но графически отоб-
ражать файлы так, как будто они находятся в папке проекта. В про-
цессе работы с проектом вы можете безболезненно удалять папки
Debug или Release. Они автоматически восстановятся при последую-
щей компиляции исходных файлов.
Visual Studio предлагает выбор из различных типов проектов
приложений. Их все можно увидеть, выбрав пункт меню File > New >
Projects. Для создания консольного приложения выполните действия
в следующей последовательности.
1. Выберите в меню команду File > New > Projects.
2. В появившемся окне диалога выберите тип проекта Win32 Con-
sole Project.
3. В поле Name задайте имя проекта: First.
4. Выберите месторасположение папки с проектом (в поле Location:).
5. Нажмите кнопку More, введите имя решения Solution (Lab1) и
сделайте отметку в окне Create directory for Solution.
6. Подтвердите выбор, нажав ОК.
7. Нажмите кнопку Finish.
Фокус переходит в окно редактора текста, где можно ввести код
программы. Набранную программу можно сразу компилировать и за-
пускать на выполнение.
Компиляция, компоновка и запуск приложения в Microsoft Visual
Studio совершаются с помощью команд, выбираемых из меню Build
или Debug или с помощью комбинаций клавиш (shortcuts). Самый

5
быстрый способ запуска программы – с помощью команды Debug >
Start или F5. Отдав команду, вы должны увидеть сообщение (message
box) о том, что не существует файла First.exe или First.obj во вновь
созданной папке Debug (эта конфигурация проекта выбрана по умол-
чанию). Согласитесь с предложением создать эти файлы.
Выполнение Debug-версии проекта в пошаговом режиме осу-
ществляется с помощью следующих клавиш:
− F10 – без захода в вызываемые функции (Step Over);
− F11 – с заходом в вызываемые функции (Step Into).
Поставьте фокус в окно редактора и нажмите клавишу F10. При-
ложение запускается в режиме пошаговой отладки и появляется но-
вое окно Locals для просмотра текущего содержимого переменных
программы.
Массивы, объекты классов и структуры можно «раскрывать» (ex-
pand). Переход в окно выполняемого приложения выполняется обыч-
ным способом – нажатием комбинации клавиш Alt+Tab. Каждое
нажатие клавиши F10 приводит к выполнению одной строки про-
граммы. Указатель в окне редактора опережает события и устанавли-
вается на ту строку, которую еще предстоит выполнить. Нажмите
клавишу F10 еще раз – указатель перемещается на первую строку с
выполнимыми операторами. Посмотрите в окно Locals. Здесь высве-
чены текущие значения переменных
Останавливать отладку следует командой Stop Debugging (Shift+F5).
Клавиша F11 служит для пошагового выполнения с заходом в
вызываемые функции. Она позволяет пройтись по многим системным
функциям, и ее можно рассматривать как некоего гида по коду, на-
писанному разработчиками системы. Без сомнения, таким образом
можно многое увидеть, многое понять и многому научиться. Гид про-
сто необходим, когда вы хотите попасть внутрь своей функции, вызов
которой находится в текущей строке отладчика.
Ошибки, обнаруженные на этапах компиляции и компоновки,
выводятся в окно Output. Мгновенный переход в файл с текстом про-
граммы (к строке с ошибкой) происходит при двойном щелчке на

6
строке с сообщением об ошибке. Это справедливо для ошибок, обна-
руженных на этапе компиляции. Многие ошибки на этапе компонов-
ки (linking) могут быть исправлены путем изменения настроек про-
екта (Project > Properties...).

Пример
Ввод точек экспериментально полученной зависимости у = f(x),
то есть дискретных значений аргумента x[i] и соответствующих им
значений функции y[i].

#include <stdio.h> // Подключаемые файлы заголовков


#include <conio.h>
int in (int min, int max) //Внешняя функция
{ //======== Ввод целого в диапазоне (min, max) ==//
int n;
bool bad=true;
while (bad) // Пока есть ошибки
{
printf ("\n Enter an integer %d..%d:",min,max);
scanf ("%d",&n); // Ввод целого по адресу n
fflush (stdin); // Очистка буфера
bad = n<min || max<n; // Ошибка
if (bad) {
printf("\nOut of range %d..%d ",min,max);
if (!getch()) getch(); //Ждем реакцию
}
}
return n;
}
void main ()
{
float x[10],y[10]; // Массивы вещественных
int i,n;
puts ("\n Number of points:");
n = in(2,10); //Вызов функции безопасного ввода
puts ("\n Points Coordinates :");
for (i=0; i<n; i++)
{

7
printf ("\nx[%d]= ",i+1);
scanf ("%f",&x[i]);
printf ("y[%d]= ",i+1);
scanf ("%f",&y[i]);
}
puts ("\n You've entered:");
for (i=0; i<n; i++)
printf("\nx[%d]=%5.2f\ty[%d]=%6.3f",i+1,x[i],i+1,y[i]);
puts( " \n\n");
}

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Запустите MS Visual Studio, внимательно прочтите раздел «Работа


в MS Visual Studio» и создайте проект консольного приложения
для платформы Win32.
2. Наберите текст консольной программы из примера. Скомпилируй-
те программу. Если в ней есть ошибки, то исправьте их. Запустите
программу и проверьте ее работу с различными исходными дан-
ными. Выполните приложение в режиме отладки.
3. Измените программу, добавив в нее вычисление и вывод в виде
таблицы суммы, среднего арифметического, минимального и мак-
симального значений по X и по Y.
4. Добавьте в Solution новый проект, который для полинома, заданно-
го в таблице 1, выводит таблицу значений по X и Y.
5. Добавьте в Solution новый проект, который выполняет на заданном
отрезке поиск одного из корней полинома методом деления отрез-
ка пополам.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на брига-


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

КОНТРОЛЬНЫЕ ВОПРОСЫ

8
1. Как выполняется отладка приложений в среде MS Visual Studio?
2. Как определить место в программе, где обнаружена ошибка компиляции?
3. Как работает функция printf()?
4. Какую роль в программе играет функция int in(int min, int max)?
5. Какие операторы циклов существуют в С++, каково их назначение, принцип
работы?
6. Как называется главная функция в программах на С++ и какова ее роль?
7. Как работают функции ввода, использованные в примере?

Лабораторная работа № 2
РАБОТА С ТЕКСТОВЫМИ ФАЙЛАМИ,
СТРУКТУРАМИ ДАННЫХ И МЕНЮ

Цель работы – освоение на практике основных приемов струк-


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

ЗАДАНИЕ

1. Разработать методом структурного программирования кон-


сольное приложение, выполняющее следующие функции:
1.1. ввод с клавиатуры наборов данных в соответствии с вари-
антом задания;
1.2. сохранение данных в текстовом файле с разделителями;
1.3. считывание и отображение содержимого файла на экране в
табличном виде.
2. Хранение данных в оперативной памяти организовать в виде од-
номерного статического массива. Элемент массива – структура данных.
3. Хранение данных в текстовом файле организовать по следую-
щему принципу:
3.1. одна строка файла содержит одну строку таблицы;
3.2. значения элементов в строке разделены символом “|”.
4. Выбор режимов функционирования организовать с помощью меню.

9
Таблица 2
Варианты задания
Бригада Объекты и их атрибуты

1 Комплектующие для ПК: тип, модель, цена.
2 Географические объекты: тип, название, размер
(длина/высота/площадь), ед. измерения (м, кв.м).
3 Сотрудники: Ф.И.О., возраст, должность.
4 Канцелярские товары: тип, фирма-производитель, цена.
5 Графические фигуры: тип, длина контура, площадь.
6 Микросхемы: тип, технология, серия.
7 Бытовые приборы: тип, марка, потребляемая мощность.
8 Компьютерные программы: категория, название, объем дистрибутива.
9 Радиоэлектронные элементы: тип, марка, количество в наличии.
10 Автомобили: марка, изготовитель, мощность двигателя.
11 Книги: автор, название, объем.
12 Небесные тела: тип, название, расстояние от Земли.

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

СТРУКТУРНОЕ ПРОГРАММИРОВАНИЕ
И ФУНКЦИОНАЛЬНАЯ ДЕКОМПОЗИЦИЯ СИСТЕМЫ

В самом общем случае программа – это последовательность опера-


ций над структурами данных, которые реализуют алгоритм решения за-
дачи. Для большинства задач этот алгоритм достаточно объемен и сло-
жен. Для уменьшения сложности программ, облегчения их понимания и
сопровождения используются приемы структурного программирования.
Структурная программа должна удовлетворять следующим ос-
новным требованиям:
1. Текст программы представляет собой композицию трех ос-
новных элементов: последовательное соединение (следование), ус-
ловное предложение (развилка) и повторение (цикл).
2. Употребления GO TO избегают всюду, где это возможно.
Наихудшим применением GO TO считается переход на оператора,
расположенного выше (раньше) в тексте программы.
3. Текст программы напечатан с правильными сдвигами, так что
разрывы в последовательности выполнения легко прослеживаются.

10
4. Текст программы физически разбит на части, чтобы облегчить
чтение. Выполняемые предложения каждого модуля должны уме-
щаться на одной странице печатающего устройства.
5. Программа представляет собой простое и ясное решение за-
дачи.
6. Текст программы сопровождается комментариями, поясняю-
щими назначение переменных и шаги алгоритма.
При структурном подходе главным является принцип разработки
ПО сверху вниз. Все начинается со структурного анализа будущего
комплекса ПО и декомпозиции алгоритма на осмысленные части
(блоки), которые должны быть максимально автономными, обозри-
мыми (то есть достаточно короткими) и обычно не реализованными в
виде программ. Структурированная программа состоит из блок-
программы и блоков. Первая не должна содержать вычислений, она
содержит обращения к блокам, которые выполняют роль отдельных
строительных кирпичиков сложного комплекса. Начинают писать
структурированную программу с блок-программы, которую пытают-
ся отладить, даже не имея всех составляющих ее блоков. Блоки заме-
няют временными программными заглушками. Если какой-то блок
оказывается плохо обозримым или появляются трудности в его реа-
лизации, то с ним пытаются поступить так же, как и со всей програм-
мой, то есть разбить на отдельные более мелкие части. Каждый блок
оформляется в виде отдельной функции языка.
В языке С++ все функции глобальные. Объявление функции име-
ет вид:
return_type func_name(arg_typel argl, arg_type2 arg2, ...);
Для объявления функции необходимо указать тип возвращаемого
значения, который может быть любым: как базовым (int, char* и т. д.),
так и определенным программистом (struct fruit и т. п.). Если функция
не возвращает никакого значения, то следует указать тип void. Затем
следует имя функции, которое может быть произвольным, но лучше,
если оно несет информацию о ее назначении. После имени в круглых
скобках перечисляются все ее аргументы с указанием типа каждого.

11
Аргументы могут и отсутствовать. Определение функции содержит код
ее реализации (тело функции), заключенный в фигурные скобки. Объ-
явление и определение функции могут быть отделены друг от друга.
В C++ передача параметров осуществляется по значению. При
этом внутри функции заводится копия переданного параметра. Копии
присваивается значение, равное значению фактического параметра.
Если функция в процессе своей работы изменит значение параметра
(его копии), то вызывающая процедура никак не сможет это обнару-
жить. Если необходимо, чтобы функция возвращала измененные зна-
чения параметров, то в функцию передается адрес переменной (ука-
затель). Переменная типа указатель объявляется при помощи символа
* перед именем переменной:
long factorial(int nNumber) {…}
int swap(float *a, float *b) {…}

ОРГАНИЗАЦИЯ МЕНЮ В КОНСОЛЬНОМ ПРИЛОЖЕНИИ

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


void Proc1() { …. }
void Proc2() { …. }
void Proc3() { …. }
void main()
{
unsigned c=l;
while (с)
{ // Цикл проверки
printf ("\n\tSelect a function (0 – to quit)\n"
"\n 0. Quit"
"\n 1. Call proc 1"
"\n 2. Call proc 2"
"\n 3. Call proc 3 \n\n\t");
с = getch() -'0'; // Реакция пользователя
switch(c)
{
case 1: Proc1(); break;
case 2: Proc2(); break;
case 3: Proc3(); break;

12
case 0: break;
default: с = 0;
}
}
}
Выражение getch()-'0' использовано для анализа символа, введен-
ного пользователем. При вычитании кода символа '0', равного 48, код
введенного символа преобразуется в целое число, соответствующее
количеству символов в кодовой таблице между нулем и введенным
символом. Таким образом, если была введена цифра, то выражение
дает число, равное этой цифре. Если введена не цифра, то переменная
unsigned c содержит некоторое недопустимое число, которое игнори-
руется, и цикл запроса ввода повторяется.

СТРУКТУРЫ ДАННЫХ

Структуры служат для объединения в одной переменной эле-


ментов разных типов. Например, нижеследующая структура Man
объединяет элементы типов int и char:
struct Man // Структура как тип данных для учета людей
{
int Age; // Возраст
char Name [50]; // Имя
};

Идентификатор Man определяет новый тип данных, используе-


мый в программе. Чтобы иметь возможность работы с реальной
структурой, надо создать представителя типа, то есть определить пе-
ременную типа Man:
Man m;
Добраться до элемента структуры можно с помощью операции
<точка>. Например, оператор m.Age=49; присваивает элементу Age
структуры m типа Man значение 49. Если к полю структуры обраща-
ются через указатель на структуру, то вместо символа «точка» необ-
ходимо использовать другую операцию выбора (->) – два символа
(«минус» и «больше»), образующие стрелку – указание на объект.

13
Следующий пример показывает различные варианты обращения к
полю Age структуры Man:
Man m, *p=&m, d[10], *pm[10];
m.Age=20;
р->Аge=20;
d[2].Age=31;
pm[0]->Age=23;

ОПЕРАЦИИ С ФАЙЛАМИ

В заголовке stdio.h даны прототипы функций для работы с фай-


лами. Там же определена специальная структура типа FILE, поля ко-
торой содержат системную информацию о файле, например указатель
на буфер обмена, размер буфера, флаги статуса файла и т. д. Пользо-
ватель должен описать переменную типа FILE* и использовать ее при
вызове функций, оперирующих с файлами.
Функция fopen() пытается найти и открыть файл с именем, зада-
ваемым первым параметром, и связать поток ввода-вывода с указате-
лем fp. Второй параметр определяет режим доступа. Он представляет
собой константную строку текста, символы которой могут принимать
следующие значения:
• r – открыть существующий файл только для чтения;
• r+ – открыть существующий файл для обновления (чтения и
записи);
• w – создать для записи. Если файл с указанным именем уже су-
ществует, он будет разрушен;
• w+ – создать новый файл для обновления. Если файл уже суще-
ствует, то он разрушается;
• а – открыть для записи в конец файла или создать для записи,
если файл не существует;
• а+ – открыть (или создать) для чтения и записи, но в конец файла.
Чтобы указать компилятору, что данный файл открывается (со-
здается) для чтения (записи) в текстовом формате (а не в двоичном),
следует добавить символ t, например "rt", "w+t" и т. д.

14
Режим обработки чисел (binary mode) отмечается символом b,
например: а+b, wb и т. д.
В случае успеха функция fopen возвращает указатель на вновь откры-
тый поток. Если попытка оказалась неудачной, она возвращает NULL.
Библиотека ввода-вывода stdio.h имеет много других функций
для работы с файлами, например:
− void rewind (FILE* stream); – переустанавливает указатель
файла на начало потока;
− int fseek (FILE *stream,long offset,int origin); – перемещает
указатель в заданную позицию;
− int fclose (FILE *stream); – закрывает файл;
− char *fgets (char *string, int n, FILE *stream ); – чтение строки
текста из потока, где
String – место размещения данных; N – максимальное количество
символов, которые должны быть считаны; stream – указатель на
структуру FILE;
− int fgetc (FILE *stream ); – чтение символа из потока;
− int fprintf (FILE *stream, const char *format [, argument ]...); –
форматированный вывод в текстовый файл. Аналогично printf, но
первый параметр – указатель на файловую структуру;
− int fputc (int c, FILE *stream ); – Вывод символа с в тексто-
вый файл
FILE *stream;
char line[100];

if ((stream = fopen( "fgets.c", "r" )) != NULL)


{
while (feof( stream ) = = 0)
{
if (fgets( line, 100, stream ) = = NULL)
printf("fgets error\n" );
else
printf("%s", line);
}
fclose(stream);
}

15
МЕТОДИКА ВЫПОЛНЕНИЯ

1. Запустите MS Visual Studio и создайте проект консольного прило-


жения для платформы Win32.
2. Наберите текст примера программы из раздела «Организация меню в
консольном приложении». Имена функций Proc1(), Proc2(), Proc3()
измените так, чтобы они отражали смысл операций приложения.
Функции должны представлять собой «программные заглушки», т.е.
содержать только оператор вывода сообщения о вызове функции.
Запустите программу и проверьте правильность ее работы.
3. Объявите глобальный тип данных – структуру, соответствующую
вашему объекту. В функции main объявите массив структур. Про-
думайте интерфейс функций и добавьте в их объявление нужное
количество параметров. Используйте указатели при передаче зна-
чений параметров в функции. Запустите программу и проверьте
правильность ее работы.
4. Используя метод пошаговой детализации, разработайте функцию
ввода данных с клавиатуры, затем функцию вывода элементов мас-
сива на экран в табличном виде. Отладьте работу этих двух функций.
5. Разработайте и отладьте функцию сохранения данных в файле.
6. Разработайте и отладьте функцию загрузки данных из текстового
файла в массив. Операцию разбора строки из файла реализуйте в
виде отдельной функции.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на брига-


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

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Каким требованиям должна удовлетворять структурная программа?


2. Как работает оператор switch?
3. Что такое «программная заглушка»?
4. Что представляют собой функции в языке C++?

16
5. Расскажите о типе данных struct (структура) языка C++.
6. Расскажите о функциях ввода/вывода в текстовый файл.
7. Что представляют собой строки символов в языке C++ и каковы основные
операции со строками?
8. Расскажите об алгоритме выбора функции в меню консольного приложения.

Лабораторная работа № 3
РАЗРАБОТКА И СПЕЦИФИКАЦИЯ ФУНКЦИЙ
И МОДУЛЕЙ ПРОГРАММЫ

Цель работы – освоение на практике основных приемов процедур-


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

ЗАДАНИЕ

1. Используя методы процедурного, структурного и модульного


программирования, разработать на языке С++ 2-ю версию консольно-
го приложения для хранения и отображения сведений об объектах
некоторого вида (варианты указаны в работе № 2).
2. Обеспечить выполнение следующих операций:
2.1. Сохранение данных из массива в бинарном файле на диске.
2.2. Загрузка данных из двоичного файла в массив и отображение
на экране.
2.3. Поиск и редактирование записей в массиве (в виде заглушки).
2.4. Сортировка данных в массиве по любому элементу структуры
(в виде заглушки).
2.5. Идентификаторы входного и выходного файлов передавать в
программу в виде параметров. Предусмотреть возможность ввода иден-
тификаторов с клавиатуры, если они отсутствуют в командной строке.
3. Код программы из работы № 2 преобразовать во вспомога-
тельный модуль и включить в новую программу.

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

МОДУЛЬНАЯ СТРУКТУРА ПРОГРАММ

Единицей компиляции в языке С++ является файл или модуль.


Исходный файл не обязательно должен содержать выполняемые опе-

17
раторы. Традиционно в программах на языке С++ используются два
типа файлов: файлы реализации, имеющие расширение .сpp, и заго-
ловочные файлы (файлы интерфейса), имеющие расширение .h. В за-
головочных файлах обычно располагаются именованные константы,
макроопределения и объявления, а в файлах реализации – определе-
ния переменных и функций. Заголовочные файлы подключаются по-
средством директивы препроцессора #include.
Обычный способ доступа к внешним определениям в файле состо-
ит в создании отдельного файла заголовка, содержащего внешние объ-
явления и поддерживающего необходимые для корректного использо-
вания определения типов. Объявления заголовочного файла включают-
ся в любой файл, использующий внешние функции и объекты.
Разделение большой программы на модули (файлы) может облег-
чить процесс создания, понимания и сопровождения программ. При
этом связанные функции и структуры могут группироваться для об-
легчения их чтения и понимания, а локальные модификации могут
иметь ограниченный эффект. Кроме того, код, разделенный на фай-
лы, легче перерабатывать для повторного использования в другой
программе. Это обычный способ повторного использования кода,
например в библиотеках.
После того как проведены функциональная и модульная компо-
зиции, т. е. определена структура программы, можно переходить к
следующему этапу – написанию кода.
Любые переменные, описанные в файле (модуле) вне какой-
либо функции и не имеющие спецификатора класса памяти (auto,
register, extern или static), по умолчанию относятся к глобальному
классу памяти и называются глобальными или внешними. Для гло-
бальных переменных память отводится только один раз и сохраня-
ется за ними до окончания выполнения программы. Если не указано
никакое инициализирующее значение, то таким переменным по
умолчанию присваивается нулевое значение. Это положение отно-
сится и к структурным типам переменных.
Область видимости таких переменных простирается от точки опи-
сания до конца файла. Если внутри блока определена автоматическая

18
переменная, имя которой совпадает с именем глобальной переменной,
то внутри блока глобальная переменная становится невидимой.
Доступ к глобальным переменным возможен и из других модулей
(файлов); для этого следует задать спецификатор памяти extern. Если
описание extern применяется к переменной, расположенной внутри
функции, то его действие распространяется только на данную функ-
цию; если же оно расположено вне какой-либо функции, то его дей-
ствие распространяется до конца модуля.
При использовании спецификатора класса памяти extern пере-
менная только объявляется и память под нее не выделяется. Следует
иметь в виду, что переменная должна быть определена в каком-
нибудь одном модуле – в нескольких модулях переменную с одним и
тем же именем определять нельзя.
Все функции по умолчанию считаются внешними или глобальны-
ми. К любой функции из любого модуля возможно обращение, если
для нее не указан спецификатор static. Другими словами, функция
определяется один раз, но может быть объявлена много раз с помо-
щью спецификатора extern.
Каждый модуль должен предваряться заголовком, который как
минимум содержит:
− название модуля;
− краткое описание его назначения;
− краткое описание входных и выходных параметров с
указанием единиц измерения;
− список используемых (вызываемых) модулей;
− краткое описание алгоритма (метода) и/или ограничений;
− Ф.И.О. автора программы;
− идентифицирующую информацию (номер версии и/или дату
последней корректировки).
Стиль оформления текстов модулей определяет использование
отступов, пропусков строк и комментариев, облегчающих понимание
программы. Как правило, пропуски строк и комментарии используют
для визуального разделения частей модуля, что позволяет прояснить
структуру программы: обычно дополнительный отступ обозначает
вложение операторов языка.

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

ФУНКЦИИ ДЛЯ РАБОТЫ С БИНАРНЫМ ФАЙЛОМ

Чтение неформатированных данных из потока:

size_t fread(void *buffer, size_t size, size_t count, FILE *stream );


Параметры:
Buffer – место для размещения данных.
Size – размер единицы данных в байтах.
Count – макс. кол-во единиц, которые должны быть прочитаны.
Stream – указатель на структуру FILE.
Возвращает количество действительно прочитанных данных.

Запись неформатированных данных в файл:

size_t fwrite(const void *buffer, size_t size, size_t count, FILE


*stream );
Параметры:
Buffer – указатель на место размещения записываемых данных.
Size – размер единицы данных в байтах.
Count – макс. кол-во единиц, которые должны быть записаны.
Stream – указатель на структуру FILE.
Возвращает количество фактически записанных данных.

ПАРАМЕТРЫ КОМАНДНОЙ СТРОКИ

С помощью параметров командной строки в программу можно


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

20
лов. В главную функцию программы main система способна передать
два параметра int argc и char* argv[].
Первый представляет собой целое число, которое соответствует
количеству параметров в командной строке программы. Второй явля-
ется массивом указателей на строки текста, которые пользователь за-
дал в командной строке.
Если запуск программы осуществлен с помощью кнопки Start на
панели задач Windows (Task bar) и при этом выбрана команда Run, то
в окне Open можно, кроме имени программы, указать дополнитель-
ные строки, адреса которых и попадут в массив argv. Предположим,
что строка запуска программы выглядит так:
"С:\Му Projects\Console\Debug\Console.exe" "..\Svet.c"
Обе строки должны быть разделены пробелами. При этом:
− argc будет равен 2, то есть учтена строка с именем запускаемо-
го файла;
− argv[0] содержит адрес первой строки (то есть строки с полным
именем запускаемого модуля);
− argv[l] содержит адрес второй строки (то есть строки с именем
файла – параметра).
Пример программы:

int main(int argc, char* argv[])


{
char fnIn[80];
if (argc>1 )
strcpy(fnIn,argv[1]);
}
В результате массив fnIn будет содержать значение 1-го параметра.

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Запустите MS Visual Studio и откройте проект консольного при-


ложения из работы № 2.
2. Добавьте к проекту дополнительные заголовочный файл и файл
реализации.

21
3. Перенесите из основного модуля в дополнительный заголовочный
объявление структуры данных и всех функций, кроме функции
main. Подключите заголовочный файл к файлу "stdafx.h". В допол-
нительный файл реализации перенесите из главного модуля опре-
деления функций и подключите файл "stdafx.h". Скомпилируйте
проект. Сделайте выводы.
4. Добавьте объявления и определения новых функций для реализа-
ции операций, указанных в п. 2 задания. Определения новых
функций должны представлять собой программные заглушки. В
главное меню программы добавьте вызовы функций. Запустите
программу и проверьте правильность ее работы.
5. Используя метод пошаговой детализации, разработайте функцию
сохранения данных в бинарном файле, затем функцию загрузки
данных из бинарного файла. Если нужно, оптимизируйте структу-
ру программы, оформив вывод таблицы на экран в виде отдельной
функции. Отладьте работу функций ввода/вывода данных в двоич-
ный файл совместно.
6. Добавьте в основной модуль функцию обработки параметров ко-
мандной строки.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на брига-


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

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Что такое заголовочный файл в программе C++?


2. Перечислите достоинства модульного программирования.
3. В чем разница между объявлением и определением переменной?
4. Как определяется область видимости переменной?
5. Что необходимо указывать в спецификации модуля?

22
6. Расскажите о функциях ввода/вывода в бинарный файл.
7. Как в программах на С++ используются параметры командной строки?
8. Как параметры командной строки передаются в программу на C++?

Лабораторная работа № 4
РАЗРАБОТКА И СПЕЦИФИКАЦИЯ СТРУКТУР ДАННЫХ,
ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ И ДИНАМИЧЕСКИХ
МАССИВОВ СТРУКТУР

Цель работы – освоение на практике методов разработки и спе-


цификации данных и основных приемов работы в С++ с указателями
и динамическими структурами данных.

ЗАДАНИЕ

1. Разработать на языке С++ 3-ю версию консольного приложе-


ния для хранения и отображения сведений об объектах некоторого
вида (варианты указаны в работе № 2).
2. Изменить способ хранения данных в памяти. Хранение данных
в оперативной памяти организовать в виде динамического массива
структур.
3. Разработать функции, включенные во 2-ю версию в виде за-
глушек:
3.1. Поиск и редактирование записей в массиве.
3.2. Сортировка данных в массиве по любому элементу структуры.
4. Сравнить варианты реализации во 2 и 3 версиях программы,
сделать вывод.

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

УКАЗАТЕЛИ

Указатель – это адрес памяти. После инициализации он содержит ад-


рес какой-либо переменной. Значение указателя показывает, где в памяти
хранится объект, а не что хранится по адресу. Переменная типа указатель
объявляется при помощи символа * перед именем переменной.

23
В выражениях операция * используется для разадресации, то есть
выборки содержимого по адресу. Существует обратная операция & взя-
тия адреса. Присвоение указателю адреса какой-либо другой перемен-
ной позволяет получить ее значение путем разадресации указателя (*р).

double v,*p;
*p=10.0;
v = *p + 2;
p=&v;
После выполнения оператора указатель р содержит адрес пере-
менной v, который компилятор определил для хранения значений
этой локальной переменной.
Переменная может быть создана в любой точке программы с по-
мощью операции new и затем при необходимости уничтожена с по-
мощью операции delete. Например, оператор
р = new int;
присваивает переменной р адрес начала области памяти длиной в
один элемент типа int. Память выделяется динамически из специаль-
ной области центрального пула памяти, которая называется heap.
Элемент занимает sizeof(int) байт. Операция sizeof(arg) возвращает
размер в байтах своего аргумента arg, который может быть либо ти-
пом, либо выражением.
Операция delete освобождает ранее занятую память, возвращая ее в
heap. Сам указатель при этом продолжает указывать на тот же участок
памяти, но он больше ею не управляет. При освобождении памяти нет
необходимости явно указывать ее размер, так как эту информацию со-
храняет система при реализации операции new. Если программист за-
бывает освободить память, то это остается незамеченным в небольших
программах, однако часто приводит к отказам в достаточно серьезных
разработках и является плохим стилем программирования.
В языке C++ имеется возможность инициализировать указатель пу-
тем запроса памяти прямо в момент объявления переменной. Например,

float *р = new float[n]; // Инициализация указателя р

24
Эту же операцию можно было бы выполнить в два этапа.

float *р; // Объявление указателя р


р = new float [n]; // Присвоение указателю р
Важно понять, что в сокращенной записи адрес вновь выделенно-
го участка памяти будет присвоен указателю р, а не содержимому по
адресу (*р), как это может показаться при рассмотрении примера.
Указатели, объявленные вне функций или объявленные с описа-
телем static, автоматически инициализируются нулями, как и все гло-
бальные переменные в C++. Указатели, объявленные внутри функции
(автоматические переменные), не инициализированы вовсе. Исполь-
зование такого указателя до присвоения ему осмысленного значения
является серьезной ошибкой. Нулевой адрес (NULL или 0) служит
признаком того, что указатель еще не был инициализирован про-
граммистом. NULL – это символическая константа, определенная в
stdio.h для нулевого указателя.
При объявлении указателя задается тип переменных, на которые
он может указывать, так как с указателями связана адресная арифме-
тика, правила которой различны для разных типов указателей. Так,
если к указателю на тип int прибавить единицу, то его значение изме-
нится на 4 байта или sizeof(int). При увеличении указателя на массив
объектов какой-либо структуры (например, Man) указатель сдвинется
в памяти ровно на один объект структуры.

УКАЗАТЕЛИ И СТАТИЧЕСКИЕ МАССИВЫ

Имя массива в языке С фактически является указателем на пер-


вый его элемент, который соответствует нулевому значению индекса
(или индексов в случае многомерных массивов). Если объявлен мас-
сив float a[16], то справедливо равенство а = = &а[0]. Переменные ти-
па «указатель» могут быть использованы и часто используются для
доступа к элементам массива. Имеют смысл следующие присвоения:

float a[16],*p: // Объявление массива и указателя


for (int i=0; i<16; i++)

25
a[i]=float(i*i); //Заполнение массива
p = &а[6]; //p указывает на а[6]
*р = 3.14f; // Равносильно а[6] = 3.14
p++; // Теперь р указывает на а[7]. Произошел сдвиг на 4 байта.
а[1] = *(р+3); // Равносильно а[1] = а[10]. В а[1] попадает 100.
а[2] = ++*р; //Равносильно а[2] = ++а[7]. В а[2] попадает 50.
а[3] = *++р; //Равносильно а[3] = а[8]. В а[3] попадает 64.

Адресная арифметика, например р+3 или ++р, осуществляется в


единицах объявленного базового типа данных float. Если в конкрет-
ной вычислительной системе число типа float занимает 4 байта, то ре-
зультатом операции р+3 будет адрес, отстоящий от р на 12 байтов.
Значение ++*р вычисляется так: сначала выбирается (*р) – содержи-
мое по адресу р, то есть а[7], так как в данный момент указатель со-
держит &а[7]. Затем выполняется приращение (increment) a[7], то
есть увеличение а[7] на единицу. При вычислении *++р, наоборот,
сначала производится изменение указателя (++р), потом выборка со-
держимого по адресу, на который он указывает.
Рассмотрим теперь двухмерный массив int a[2][3];. Встретив опи-
сание такого типа, компилятор отводит в памяти место для линейного
размещения массива в виде последовательности ячеек.
Двухмерный массив рассматривается как массив массивов. Эле-
ментами главного массива из двух элементов являются одномерные
массивы, каждый из трех элементов типа int*. В языке имеют смысл
такие объекты:
− а[0][0] – первый элемент массива типа int;
− а[0] – адрес первого элемента массива типа int*;
− а – адрес первой строки массива типа int**.
Выражение а+1 означает адрес второй строки массива, то есть
адрес, сдвинутый на один элемент массива массивов, а таким элемен-
том является строка двухмерного массива.
Выражение а+1 подразумевает сдвиг от а на размер одной строки
(а не на размер числа типа int). Адресная арифметика всегда осу-

26
ществляется в единицах базового типа данных. Теперь такой едини-
цей является строка двухмерного массива или массив целых из трех
элементов. Имеют место следующие равенства:
а= =&а[0] а[0]= =&а[0][0] **а= = а[0][0].
Так как а является адресом адреса, понадобилась двойная ра-
задресация (**а) для того, чтобы добраться до первого элемента мас-
сива.

УКАЗАТЕЛИ И ДИНАМИЧЕСКИЕ МАССИВЫ

При решении на компьютере серьезных задач важно экономно


расходовать имеющуюся память и освобождать ее по мере возможно-
сти. Принцип организации динамического двухмерного массива
очень похож на принцип организации статического двухмерного мас-
сива. Отличие состоит в том, что теперь для адресов а, а[0], а[1],...,
а[n-1] должно быть отведено реальное физическое пространство па-
мяти, в то время как для статического двухмерного массива выраже-
ния вида а, а[0], а[1],..., а[n-1] были лишь возможными конструкция-
ми для ссылок на реально существующие элементы массива, но сами
эти указатели не существовали как объекты в памяти компьютера.
Алгоритм выделения памяти таков:
− Определяем переменную а как адрес массива адресов: float **a;.
− Захватываем память из области heap для массива из n
указателей на тип float и присваиваем адрес начала этой памяти
указателю а. Оператор, выполняющий это действие, выглядит так:
а = new float* [n];.
− В цикле пробегаем по массиву адресов а[], присваивая
каждому указателю а[i] адрес вновь захватываемой памяти под
массив из n чисел типа float (a[i]=new float [m];).
Если был размещен массив переменных, то следует освобождать
память оператором
delete [] р;.
Здесь квадратные скобки указывают компилятору на то, что
освобождать следует то количество ячеек, которое было захвачено

27
последней операцией new в применении к указателю р. Явно указы-
вать это число не нужно.

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Разработайте модель организации данных и представьте ее в виде


рисунка. Изобразите на схеме выполнение операции перестановки
элементов при сортировке массива. Сделайте то же самое для ста-
тического массива. Сравните способы организации данных.
2. Запустите MS Visual Studio и откройте проект консольного при-
ложения из работы № 3.
3. Измените определение переменной, предназначенной для хране-
ния данных в памяти программы.
4. Сделайте необходимые изменения в объявлениях и определениях
функций обработки данных. Учтите, что для доступа к элементам
структуры через указатель используется операция ->. Отладьте
программу. В процессе отладки используйте заглушки и коммен-
тарии для постепенного включения исправлений в программу.
5. Методом пошаговой детализации разработайте функции поиска, ре-
дактирования и сортировки записей. Используйте при этом разрабо-
танную модель организации данных и схему выполнения операций.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на брига-


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

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Что такое указатель?


2. Каковы основные операции с указателями?
3. Что такое адресная арифметика?
4. Как используются указатели в статических массивах?

28
5. Какой тип данных имеет идентификатор трехмерного статического массива
целых чисел?
6. Что представляет собой двумерный динамический массив?
7. Как создаются и уничтожаются динамические массивы?
8. Как создать динамический массив с переменным количеством элементов в
каждой строке?

Лабораторная работа № 5
ИСПОЛЬЗОВАНИЕ ОБЪЕКТНО ОРИЕНТИРОВАННОГО
ПРОГРАММИРОВАНИЯ В РАЗРАБОТКЕ ПРИЛОЖЕНИЙ

Цель работы – освоение на практике средств объектно ориенти-


рованного программирования в языке С++ и методов обработки ди-
намического массива объектов класса.

ЗАДАНИЕ

1. Разработать на языке С++ 4-ю версию консольного приложе-


ния для хранения и отображения сведений об объектах некоторого
вида (варианты указаны в работе № 2). Объекты в программе пред-
ставить экземплярами класса.
2. Для представления набора объектов в оперативной памяти ис-
пользовать динамический массив (массив указателей на объекты класса).
3. Обеспечить выполнение следующих операций:
3.1. Ввод сведений об объекте.
3.2. Вывод данных об объектах на экран в виде таблицы.
3.3. Вставка объектов в массив.
3.4. Удаление объектов из массива.
3.5. Сортировка объектов в массиве.
3.6. Поиск объекта в массиве.
3.7. Редактирование сведений об объекте.
3.8. Сохранение сведений об объектах в бинарном файле.
3.9. Загрузка данных из бинарного файла в массив.
4. Функции консольного и файлового ввода/вывода и редактиро-
вания реализовать в виде методов класса.

29
5. Переопределить для разработанного класса операторы << и >>
для консольного ввода/вывода.

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

ПОНЯТИЕ КЛАССА В C++

В ООП каждый объект принадлежит к определенному классу.


Класс – это основной инструмент, посредством которого осуществляет-
ся инкапсуляция и скрытие данных. Если описан класс, то определены
характеристики всех объектов, принадлежащих к этому классу. Функ-
ции, инкапсулированные в классе и предназначенные для выполнения
каких-либо операций с данными любого из объектов этого класса, при-
нято называть методами класса. В ООП наряду с вызовами обычных
внешних функций принято говорить о передаче сообщений объектам
классов. Сообщением считается имя того или иного метода.
В общем виде схема описания класса выглядит следующим образом.
class Class_Name {
private:
// Здесь расположены данные и методы, которые недоступны
извне обычным,
// прямым способом. Они доступны только в методах класса
Class_Name
protected:
// Здесь расположены данные и методы, недоступные прямо.
// Они доступны в методах класса и в методах производных классов
public:
// Здесь расположены данные и методы, доступные обычным,
// прямым способом из внешних процедур
};
При отсутствии описателей члены класса по умолчанию имеют
тип private.
Тела методов могут быть приведены как внутри каждого из раз-
делов класса, так и вне всего блока описаний. В первом случае за счет
увеличения объема памяти сокращается время выполнения. При раз-

30
работке реальных приложений модули описаний классов (definition
module) размещают в отдельных файлах с расширением h. Они ком-
пилируются отдельно и именно в них задаются все правила игры с
объектами классов. Тела методов размещают вне блока описания
класса в этом же файле или в другом файле (с расширением срр).
Принадлежность метода к классу обозначается в заголовке функции c
помощью операции :: .
сlass MyClass{
…. // Объявления переменных и методов
float func();
};
float MyClass::func() {
…. // операторы метода
return x;
}
КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ

Среди всех методов любого класса выделяются два (конструк-


тор и деструктор), которые определяют, каким образом объекты
класса создаются, инициализируются, копируются и разрушаются.
Конструктор выполняет особую функцию – он служит для автома-
тического создания и инициализации объекта класса в точке его опреде-
ления. Конструктор носит то же имя, что и сам класс, в котором он
определен. Конструктор ничего не возвращает в точку вызова, и при
объявлении он не должен иметь описатель типа возвращаемого значе-
ния.
Деструктор используется для освобождения памяти, занимаемой
членами класса. Особенно важную роль играет деструктор в случае ди-
намического выделения памяти из области heap. Как известно, такую
память следует явно освобождать операцией delete. Если тело деструк-
тора создано, то он сделает это автоматически, как только закончит ра-
боту функция, в которой определен объект данного класса. Он неявно
вызывается в случае выхода программы из области действия объекта.
Деструктор носит имя класса, в котором он определен, но перед его

31
именем должен стоять символ ~. Например, описание А::~А(){}; задает
деструктор класса А, а А::А(){}; определяет его конструктор.
Один и тот же класс может иметь несколько конструкторов, если
каждый из них имеет свой собственный список аргументов. При
определении объекта будет запущен тот конструктор, с которым сов-
падает заданный список аргументов. В качестве аргумента конструк-
тора нельзя использовать объект того же класса. Вместо этого можно
использовать ссылку на объект.
Конструктор по умолчанию не имеет аргументов и используется
при создании неинициализированного объекта:
class MyClass{
public:
MyClass() { } // Конструктор по умолчанию
MyClass(int Arg) { … } // Конструктор с параметрами
….. // Остальные данные и методы
};
Без такого конструктора объекты не могли бы быть определены
без задания инициализирующих значений. Если конструктор по
умолчанию не объявлен явно, то компилятор автоматически назнача-
ет его. Он вызывается при таких определениях, как
MyClass clObj;
Аргументом конструктора может быть и сам объект класса. Та-
кой конструктор будет иметь следующий прототип:

MyClass::MyClass(MyClass &);
Такие конструкторы запускаются при копировании данных "ста-
рого" объекта во вновь создаваемый:

MyClass clObjl(7);
MyClass clObj2 = clObj1;
Если для класса конструктор копирования не определен, то ком-
пилятор сделает это сам.
В отличие от конструкторов, деструктор класса не имеет аргу-
ментов и не может быть перегружен. Деструкторы вызываются стро-

32
го в обратной последовательности вызова соответствующих кон-
структоров. Они вызываются автоматически при выходе объекта из
блока, в котором были определены. Единственным исключением из
этого общего правила является случай, когда объект создается дина-
мически из "кучи" путем вызова оператора new. В этом случае для
корректного удаления объекта необходимо явно выполнить оператор
delete, который и вызовет необходимый деструктор.

ПЕРЕОПРЕДЕЛЕНИЕ ОПЕРАЦИЙ

С помощью ключевого слова operator программист для объектов


класса может переопределить смысл любой из более чем 40 встроен-
ных операций языка (*, /, +, -, && и т.д.). Существуют два способа
переопределения бинарных операций: public-методом с одним аргу-
ментом и friend-функцией с двумя аргументами. Унарную операцию
можно переопределить либо public-методом класса без параметров,
либо friend-функцией с одним параметром.
Рассмотрим выражение х+у, где х, у являются объектами какого-
либо класса. Предположим, что в этом классе одним из двух возможных
способов переопределена бинарная операция +. Тогда выражение х+у
может быть интерпретировано компилятором также двумя способами:
х.operator + (у); //либо как
operator + (х,у);
Первая интерпретация означает, что объекту х посылается сооб-
щение operator+(у). То есть вызывается функция (член класса) с име-
нем operator+(), при этом в качестве единственного параметра пере-
дается объект у. Вторая означает вызов внешней функции с именем
operator+(), которой передаются два параметра х и у.
Функция, переопределяющая операцию, не может изменить ко-
личество аргументов операции. Например, унарная операция не мо-
жет стать бинарной и наоборот. Невозможно также изменить суще-
ствующий приоритет при нормальном использовании операции.
Переопределяя операции, мы как бы наделяем язык свойствами,
которыми он раньше не обладал. Например, переопределив операции

33
+,-,*,/ в классе комплексных чисел, мы как бы наделяем язык C++ спо-
собностью оперировать комплексными числами по обычным правилам.
Оператор cout<<i; интерпретируется как cout.operator<<(i);. Так
как тип возвращаемого значения операции – ostream&, возможна це-
почка последовательного вывода. Оператор cout<<i<<j; интерпрети-
руется как (cout.operator<<(i)).operator<<(j);.
Для ввода и вывода переменных нестандартных типов, например
объектов класса, операции << и >> следует переопределить в этом
классе с помощью friend-функций.

class Vector {
double x1, x2; // Координаты вектора
public:
// Три конструктора
Vector (double c1, double c2) // С параметрами
{x1=c1; x2=c2;}
Vector() // По умолчанию
{x1=x2= 0.; }
Vector(const Vector& v) // Копирования
{x1=v.x1; x2=v.x2;}
//= = = = = = Переопределение операций =====//
friend ostream& operator<<(ostream&, Vector& v);
friend istream& operator>>(istream&, Vector& v);
};
Pеализуем тела friend-функций вне блока описания класса.

ostream& operator<<(ostream& os, Vector& v)


{return os<<"\n Vector: x1="<<v.x1<<" x2="<<v.x2;}
istream& operator>>(istream& is, Vector& v)
{
double d1, d2;
cout << "\n x1= "; is >> d1;
cout << " x2= "; is >> d2;
v=Vector (d1, d2); // Создание объекта
return is; // Входной поток
}
Первый формальный параметр (&os) функции operator<< – ссыл-
ка на объект класса ostream. С ним связан стандартный поток cout.

34
Второй параметр является ссылкой на объект класса Vector. Функция
возвращает адрес выходного потока. Аналогично для переопределен-
ной операции ввода. Теперь можно вводить и выводить объекты
класса Vector:
Vector х, у;
//Переопределенные операции ввода-вывода
cout << "\n\n Enter vector:"; cin >> x;
cout << "\n Enter one more:"; cin >> y;
cout << "\n\n You have entered: " << x <<y;

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Запустите MS Visual Studio и откройте проект консольного прило-


жения из работы № 4.
2. Измените объявление структуры на объявление класса.
3. Сделайте необходимые изменения в объявлениях и определениях
функций обработки данных. Отладьте программу. В процессе от-
ладки используйте заглушки и комментарии для постепенного
включения исправлений в программу.
4. Преобразуйте функции консольного и файлового ввода/вывода объ-
ектов класса в методы этого класса. Тела методов определите вне
блока описания класса в отдельном модуле. Отладьте программу.
5. Замените в программе все функции консольного ввода/вывода из
библиотек stdio.h и conio.h на операции библиотеки классов пото-
кового ввода/вывода.
6. Переопределите для разработанного класса операторы << и >> для
консольного ввода/вывода.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на бри-


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

35
КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Как в С++ объявляется класс?


2. С какой целью и как в классе выделяются элементы с различным уровнем
доступности?
3. Как объявляются и какие функции выполняют конструкторы класса?
4. Как действует деструктор класса?
5. Что такое friend-конструкции?
6. Назовите основные элементы библиотеки классов потокового ввода-вывода C++.
7. Объясните механизм переопределения операций в классах.
8. В чем достоинства переопределения операций?

Лабораторная работа № 6
ИСПОЛЬЗОВАНИЕ НАСЛЕДОВАНИЯ,
ПОЛИМОРФИЗМА И АБСТРАКТНЫХ КЛАССОВ

Цель работы – освоение на практике механизмов наследования,


полиморфизма и абстрактных классов в языке С++ для обработки ди-
намических массивов объектов различных классов.

ЗАДАНИЕ

1. Разработать на языке С++ 5-ю версию консольного приложения


для хранения и отображения сведений об объектах некоторого ви-
да в соответствии с вариантом задания, указанным в таблице.
2. Графа «Предметная область» представляет родительский класс, в
котором определены общие атрибуты. Графа «Список типов объ-
ектов» представляет список дочерних классов. Для каждого такого
класса необходимо индивидуально определить любые 3 атрибута
по своему выбору. В случае затруднений уточнить список атрибу-
тов у преподавателя.
3. Для представления набора объектов в оперативной памяти использо-
вать динамический массив (массив указателей на объекты класса).
4. При обработке элементов массива использовать полиморфизм
позднего связывания.
5. Поиск и сортировку производить только по общим атрибутам.

36
6. В классах запретить непосредственный доступ к полям данных.
Доступ к атрибутам объектов осуществлять только с помощью
методов классов.

Таблица 3
Варианты задания
Бри- Предметная Список типов объектов Общие атрибуты
гада область

1 Комплектую- Процессор, память, НЖМД, Модель, цена
щие для ПК монитор
2 Географические Страна, река, озеро, гора Название, континент
объекты
3 Сотрудники Руководитель верхнего уров- Ф.И.О., возраст,
ня, менеджер, штатный должность
исполнитель, совместитель
4 Канцелярские Карандаш, скоросшиватель, Фирма-
товары тетрадь, органайзер производитель, цена
5 Графические Линия, треугольник, Название,
фигуры прямоугольник, окружность длина контура
6 Микросхемы Регистр, счетчик, дешифратор, Технология, серия
мультиплексор
7 Бытовые Телевизор, холодильник, сти- Марка, цена
приборы ральная машина, СВЧ-печь
8 Компьютерные Игра, ОС, СУБД, среда про- Название, объем
программы граммирования дистрибутива
9 Радио- Конденсатор, резистор, Марка, количество
электронные транзистор, диод в наличии
элементы
10 Автомобили Легковой, грузовой, тягач, Марка, изготовитель,
автобус мощность двигателя
11 Книги Учебник, справочник, Автор, название,
худ. литература, альбом объем
12 Небесные тела Планета, комета, звезда, Название
галактика

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

НАСЛЕДОВАНИЕ ДАННЫХ И МЕТОДОВ

Производный (Derived или Child) класс является основой для реа-


лизации механизма наследования свойств базового (Base или Parent)

37
класса, а также средством подстройки класса к нуждам пользователя.
Подкласс (или производный класс) наследует все данные и методы ба-
зового класса. В дополнение к ним он может приобрести новые данные
и методы, не содержащиеся в базовом классе, но необходимые для кон-
кретных целей, преследуемых при создании подкласса. Каждый объект
производного класса содержит свои собственные копии данных, уна-
следованных от базового класса. Любой класс может стать базовым, ес-
ли создать новый класс, объявив его производным от первого.

class BaseClass
{ // Компоненты базового класса
};
class SubClass: public BaseClass
{ // Компоненты производного класса
};
Имя базового класса указывается непосредственно после имени
производного, до открывающей фигурной скобки. В данном случае
производный класс SubClass наследует все компоненты базового
BaseClass, а также содержит любые, определенные непосредственно в
нем самом, компоненты. В языке существует возможность управлять
атрибутами доступа унаследованных данных и методов. Ключевое
слово перед именем базового класса определяет тип наследования.
По умолчанию установлен тип наследования доступа private.
В производном классе можно изменять доступность отдельных
компонентов базового класса.
class BaseClass
{
public: // Общедоступные компоненты базового класса
int nPublBase;
void funcBase ();
protected: // Защищенные компоненты базового класса
int nProtBase;
private: // Частные компоненты базового класса
int nPrivBase;
};

38
class Subclass: BaseClass // private-наследование по умолчанию
{
public: // Общедоступные компоненты производного класса
BaseClass::nPublBase; // Конкретно объявляем как public
int nPublSub;
void funcSub();
} clSub;.
Из любого места программы становится возможным доступ к
этому компоненту базового класса:
clSub.nPublBase = 7;.
ПОЛИМОРФИЗМ И ВИРТУАЛЬНЫЕ ФУНКЦИИ

Метод родительского класса может быть переопределен в дочерних


классах. В результате одно и то же по смыслу действие может иметь
множество конкретных воплощений, форм реализации, что и означает
полиморфизм. Полиморфизм позволяет пользователю иерархической
структуры классов посылать сообщения общего характера объектам
разных классов, не заботясь о деталях интерпретации конкретного дей-
ствия внутри класса. Полиморфизм сильно облегчает задачу пользова-
ния библиотекой классов. Пользователь должен понимать лишь общий
смысл или эффект воздействия метода на любой объект в иерархии
классов, а не особенности его реализации для конкретного класса.
Главным моментом в понимании сути полиморфизма является
момент времени, в который система определяет детали реализации
метода для объекта. Если система решает, как выполнить действие во
время компиляции, то этот процесс называется ранним связыванием;
если решение принимается динамически во время исполнения про-
граммы, то это называется поздним связыванием. Под связыванием
понимают связывание сути послания (имени метода) с конкретными
кодами его реализации для данного объекта.
Раннее связывание реализуется путем переопределения в дочер-
нем классе обычных методов родительского класса. Если метод или
операция переопределены в двух или более классах, то компилятор

39
может связать послание объекту с конкретными кодами заранее, так
как он знает класс объекта, которому послано данное сообщение.
Раннее связывание дает преимущество в скорости, поскольку компи-
лятор имеет возможность оптимизировать код до его выполнения.
class Parent
{
public:
void print(){…}
} *pp,pobj;
class Derived: Parent
{
public:
void print(){…}
} *pd,dobj;

pobj.print (); // Будут вызваны разные print()


dobj.print(); // Полиморфизм раннего связывания

Компилятор заранее решает, к каким классам должны принадле-


жать функции print в последних двух операторах. Очевидно, что для
объекта pobj класса Parent будет вызван метод своего же класса, то
есть Parent::print(). Аналогично для производного класса справедлив
вызов Derived::print().
В языке C++ указатель на базовый класс может содержать адрес
объекта производного класса.
рp=&pobj;
pp->print(); // Вызов метода Parent::print()
рd=&dobj;
pp=pd; // Будут вызваны разные print()
pp->print(); // Вызов метода Parent::print()
pd->print(); // Вызов метода Derived::print()

Указатели рр и pd содержат адрес объекта dobj производного


класса. Оператор pd->print(); вызовет метод Derived::print(), так как pd
является указателем на класс Derived и содержит в данный момент
адрес объекта dobj того же класса. В случае оператора pp->print(); по-
лиморфизм раннего связывания диктует следующее правило. Так как

40
рр объявлен указателем на класс Parent, оператор pp->print(); вызовет
Parent::print(). Тот факт, что рр в данный момент времени содержит
адрес объекта производного класса, не влияет на решение, принятое
на этапе компиляции.
Позднее связывание реализуется с помощью виртуальных функ-
ций. Оно дает преимущество в гибкости и высоте абстракции задачи.
Виртуальная функция определяется в базовом классе с помощью спе-
цификатора virtual перед ее объявлением. Изменим объявление мето-
да print в предыдущем примере.

class Parent
{
public:
virtual void print(){…}
} *pp,pobj;
class Derived: Parent
{
public:
void print(){…}
} *pd,dobj;
pobj.print (); // Вызов метода Parent::print()
dobj.print(); // Вызов метода Derived::print()
рp=&pobj;
pp->print(); // Вызов метода Parent::print()
рd=&dobj;
pp=pd;
pp->print(); // Вызов метода Derived::print()
pd->print(); // Вызов метода Derived::print()
В этих условиях изменится функционирование только одного опе-
ратора pp->print. В игру вступает полиморфизм позднего связывания, и
выбор метода print производится на этапе исполнения программы в за-
висимости от того, на объект какого класса ссылается в данный момент
времени указатель Parent *pp. Если он ссылается на объект производно-
го класса, то будет вызван Derived::print(), если же указатель ссылается
на объект базового класса, то будет вызван Parent::print(). Такая гиб-
кость функционирования виртуальных функций является мощным

41
средством, позволяющим выбрать нужный метод при получении указа-
телем (на текущий объект одного из производных классов иерархии)
сообщения общего характера вида print(), input() и т. д.
Все методы классов могут быть виртуальными, за исключением
конструктора. Деструктор класса, однако, может быть объявлен вир-
туальным. Это позволяет вызвать требуемый деструктор с помощью
указателя на базовый класс. Если указатель в данный момент ссыла-
ется на объект одного из производных классов, то будет вызван де-
структор соответствующего класса. Деструктор класса, производного
от класса с virtual-деструктором, сам является virtual-деструктором
(так же, как и любая другая виртуальная функция).
При определении в производных классах виртуальных функций
количество и типы параметров у всех функций в разных классах
иерархии должны быть одинаковы. Только в этом случае они счита-
ются виртуальными.
Необходимо различать три варианта переопределения функций,
для которых в английском языке используются разные специальные
термины. Говорят, что переопределенная в производном классе вир-
туальная функция (overrides) преобладает (над) или заменяет одно-
именную функцию базового класса. Если в производном классе пере-
определена обычная функция, то говорят, что она скрывает (hides)
функцию из базового класса с таким же именем. Если в классе опре-
делены две функции с одинаковым именем, но разным набором па-
раметров, то говорят, что они совмещены или что вторая является пе-
регруженной (overloaded) версией первой функции.
В случае скрытия (hiding) количество и типы аргументов не вли-
яют на сам факт скрытия. Совмещение (overloading) функций с одним
именем, но различием в аргументах возможно только в рамках одного
класса и при одинаковом типе возвращаемого значения. Если объяв-
ленная виртуальной в базовом классе функция имеет в производном
классе другой тип или набор параметров, то она считается спрятан-

42
ной (hiden) и виртуальный механизм игнорируется. Если в произ-
водном классе она имеет другой тип возвращаемого значения, то это
рассматривается как ошибка.

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Запустите MS Visual Studio и откройте проект консольного при-


ложения из работы № 5.
2. Измените объявление класса так, чтобы он стал абстрактным базо-
вым классом для задачи.
3. Объявите нужное количество дочерних классов.
4. Определите методы дочерних классов.
5. Сделайте необходимые изменения в объявлениях и определениях
функций обработки данных. Используйте полиморфизм позднего
связывания. Отладьте программу. В процессе отладки используйте
заглушки и комментарии для постепенного включения исправле-
ний в программу.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на брига-


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

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Как в классе указывается родительский класс?


2. Что такое тип наследования и как он влияет на доступность элементов клас-
са?
3. Что такое полиморфизм раннего связывания?
4. Что такое полиморфизм позднего связывания?
5. Какие возможны варианты переопределения функций в классах?
6. Что такое абстрактный класс?
7. Какая функция называется чисто виртуальной?
8. В чем достоинства и недостатки использования в программах виртуальных
функций?

43
Лабораторная работа № 7
СЛОЖНЫЕ СТРУКТУРЫ ИЗ ОБЪЕКТОВ КЛАССОВ

Цель работы – изучение организации различных структур данных


и разработка методов манипулирования данными.

ЗАДАНИЕ

1. Разработать на языке С++ 6-ю версию консольного приложения


для хранения и отображения сведений об объектах в виде структу-
ры в соответствии с вариантом задания, указанным в таблице.
2. Обеспечить выполнение следующих операций:
2.1. Хранение сведений об объектах в файле, организация вво-
да/вывода элементов структуры.
2.2. Поиск объекта в структуре.
2.3. Редактирование сведений об объекте.
2.4. Вставка объектов в структуру.
2.5. Удаление объектов из структуры.
2.6. Сортировка объектов в структуре и вывод отчета на экран.
3. Все функции реализовать как методы класса.
4. Операции файлового ввода/вывода реализовать с помощью биб-
лиотеки потоковых классов.
Таблица 4
Варианты задания
Бригада № Вид структуры
1 Линейный однонаправленный список
2 Циклический однонаправленный список
3 Коллекция
4 Циклический двунаправленный список
5 Бинарное дерево
6 Линейный двунаправленный список
7 Очередь FIFO
8 Стек
9 Циклический двунаправленный список
10 Бинарное дерево
11 Очередь FIFO
12 Коллекция

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

44
ВЛОЖЕННОСТЬ КЛАССОВ

Поле данных класса может иметь любой тип, в том числе быть
объектом какого-либо класса или указателем на объект класса. Это
обстоятельство используется при разработке сложных структур дан-
ных, состоящих из различных классов. В целях экономии памяти бо-
лее предпочтительным является вариант работы с динамически со-
здаваемыми объектами, что предполагает использование указателей.
Существует специально зарезервированное ключевое слово this,
определяемое как указатель на объект, которому было послано обра-
батываемое сообщение. В каком-то конкретном методе указатель this
содержит адрес объекта, который инициировал этот метод. Это спра-
ведливо только для методов класса, не имеющих описатель static. При
любом вызове метода класса указатель this передается как скрытый
(hidden) параметр, то есть передается неявно. Он является локальной
переменной внутри метода и неявно используется при обращении к
данным и методам класса. Иногда его используют явно.
Рассмотрим эти возможности на примере инкапсуляции в классе
Node такой распространенной структуры данных, как узел линейного
односвязного списка. Класс Node имеет в качестве элемента данных
указатель next на объект своего собственного класса.

class Node {
char *data; // Информационная часть узла
Node *next; // Указатель на следующий узел
publiс:
Node (char *s) // Конструктор
{
data = strcpy(new char[strlen(s)+l], s);
next = NULL;
}
void GetData()
{
puts (this->data);
}
void SetNextTo (Node& n)

45
{
n.next = this;
}
Node* GetNext()
{
return next;
}
~Node ()
{
puts("\nDeleting data");
delete data;
}
};
void main ()
{ // Использование класса Node
Node n1(“First"), n2("Second");
n1.GetData();
n2.GetData();
n2.SetNextTo(n1);
n1.GetNext()->GetData();
}
Конструктор класса Node дважды вызывается в функции main при
объявлении объектов n1 и n2. Каждый раз он запрашивает память из
области heap и размещает там строку символов (содержимое узла спис-
ка), которая передастся конструктору в качестве параметра. Метод
класса GetData выводит в поток stdout (стандартный выходной поток)
поле data того объекта, которому передается сообщение. Здесь мы спе-
циально вставили выбор с помощью указателя (this->), чтобы проиллю-
стрировать то, что обычно делает компилятор. Его можно оставить, а
можно и убрать. Любое обращение к data в любом методе класса Node
компилятор расширяет до this->data. Оператор программы n1.GetData();
выведет строку "First", а оператор n2.GetData(); – строку "Second".
Внутри конструктора мы обращаемся к переменной data, не указывая
явно, какому объекту принадлежит это поле данных. Компилятор C++
сам расширяет это обращение до this->data. Метод SetNextTo получает
в качестве параметра ссылку на объект класса Node. Поскольку этот

46
метод принадлежит к тому же классу, что и параметр, внутренний ком-
понент next объекта n прямо доступен в данном методе. Ему присваи-
вается адрес объекта, который инициировал метод (n.next=this;). Адрес
содержится в переменной this. Так как в нашем случае объекту n2 по-
слано сообщение SetNextTo(n1), указатель this содержит адрес объекта
n2, и он присваивается полю next объекта nl. Получается, что объект n1
имеет в качестве следующего узла списка объект n2, или можно ска-
зать, что n2 зацеплен за n1.
Метод GetNext позволяет получить адрес следующего элемента
списка, то есть поле next объекта, которому послано сообщение. В
последней строке функции main он используется для того, чтобы до-
быть объект, стоящий в списке за объектом n1. Выражение
n1.GetNext() имеет результатом адрес объекта n2, которому посыла-
ется сообщение GetData. Следовательно, результатом всего выраже-
ния будет вывод поля data объекта n2, то есть строка "Second".
СЛОЖНЫЕ СТРУКТУРЫ ДАННЫХ

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


размещение новых элементов и удаление существующих производится
только с одного его конца, называемого вершиной стека. Его еще назы-
вают структурой типа LIFO (Last In First Out, первым вошел – послед-
ним вышел). Максимальное число элементов, которые можно размес-
тить в стеке, не должно ограничиваться программным окружением: по
мере вталкивания в стек новых элементов и выталкивания старых па-
мять под него должна динамически запрашиваться и освобождаться.
Состояние стека рассматривается только по отношению к его вершине,
а не ко всему содержимому. Операции, выполняемые над стеком, име-
ют специальные названия. При добавлении элемента в стек мы гово-
рим, что элемент вталкивается в стек (push). Для стека s и элемента
sItem определена операция push(s, sItem), по которой в стек s добавля-
ется элемент sItem. Аналогичным образом определяется операция вы-
талкивания из стека – pop(s), по которой из стека s "верхний" элемент
удаляется и возвращается в качестве значения функции. Следовательно,
операция присваивания sItem = pop(s); удалит элемент из стека и при-

47
своит его значение переменной sItem. На применение операции вытал-
кивания из стека существует единственное ограничение: она не может
применяться к пустому стеку, т. е. к такому, который не содержит ни
одного элемента. Помимо этих двух основных операций, часто бывает
необходимо прочитать элемент в вершине стека, не извлекая его отту-
да. Для этих целей используют операцию peek(s).
Очередью называется упорядоченный набор элементов, которые
могут удаляться с одного ее конца (называемого началом очереди) и
помещаться в другой конец этого набора (называемого концом очере-
ди). Ее также называют структурой данных, организованной по прин-
ципу FIFO (First In First Out, первый вошел – первым вышел). Как и для
стека, максимальное число элементов в очереди не должно лимитиро-
ваться используемым программным обеспечением: память должна за-
прашиваться и освобождаться динамически по мере того, как в очередь
добавляются новые и из нее удаляются находящиеся там элементы.
Рассмотренные структуры – стек и очередь – являются специаль-
ными разновидностями более общей структуры данных – обобщен-
ного связанного списка. Приведенные ниже функции позволяют ра-
ботать со списком:
− insert – добавить новый элемент в список, сохраняя
установленный порядок следования.
− destroy – разрушить список.
− display – вывести все элементы списка.
− remove – удалить элемент списка.
Существует множество способов конкретной реализации списков.
В линейном однонаправленном списке поле next содержит адрес сле-
дующего элемента списка. Последний элемент в поле next хранит зна-
чение NULL. В линейном двунаправленном списке, кроме поля next,
имеется также поле prev, которое содержит адрес предыдущего элемен-
та списка. Поле prev первого элемента списка содержит NULL. В цик-
лическом однонаправленном списке поле next последнего элемента
содержит указатель назад на первый элемент. Такой список не имеет
первого и последнего элементов. Однако в некоторых случаях удобно

48
использовать внешний указатель на последний элемент, что автомати-
чески делает первым следующий за ним элемент. Альтернативный ва-
риант предполагает использование указателя на первый элемент. В
циклическом двунаправленном списке поле prev первого элемента
содержит указатель на его последний элемент, а поле next последнего
элемента – указатель на первый элемент.
Коллекция – это упорядоченный набор объектов, построенный на
базе динамического массива и записи, что позволяет, с одной стороны,
перенумеровать все элементы семейства, а с другой – иметь прямой до-
ступ к объектам – элементам семейства – по значению определенного
поля, называемого ключом. Ключ – это строковое выражение, которое
может быть использовано вместо индекса для доступа к элементу семей-
ства. Класс Collection имеет одно свойство Count и три метода – Add,
Item и Remove. Свойство Count возвращает количество элементов семей-
ства.
Метод Add (элемент [, ключ][, до] [, после]) добавляет объект в се-
мейство. Его обязательным аргументом является элемент, он указывает
добавляемый в семейство элемент. Параметр ключ задает ключ, по кото-
рому возможно будет произвести поиск этого элемента. Параметры до и
после указывают на то, перед каким или после какого элемента добавля-
ется новый. По умолчанию элемент добавляется в конец семейства.
Метод Remove (ключ) удаляет элемент из семейства. Параметр ключ
– это ключ или индекс, указывающие на удаляемый элемент. Заметьте,
что при удалении элемента из семейства не остается "дыр": индексы пе-
ренумеровываются, значение свойства Count уменьшается на единицу.
Метод Item (ключ) возвращает значение элемента семейства с
ключом ключ. Как в случае с методом Remove, параметр ключ может
быть как ключом, так и индексом.
Бинарное дерево – это конечное множество элементов, которое
либо пусто, либо содержит один элемент, называемый корнем дерева, а
остальные элементы множества делятся на два непересекающихся под-
множества, каждое из которых само является бинарным деревом. Эти
подмножества называются левым и правым поддеревьями исходного

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

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Разработайте модель организации данных и представьте ее в виде


схемы. Изобразите на схеме выполнение операций добавления,
удаления и перестановки элементов при сортировке.
2. Запустите MS Visual Studio и создайте новый проект консольного
приложения для платформы Win32.
3. Скопируйте и подключите к проекту модули объявления и опреде-
ления классов из работы № 6. Используя определение абстрактно-
го класса из работы № 6, создайте класс для представления задан-
ной структуры объектов (см. таблицу).
4. Определите методы класса для выполнения операций добавления,
удаления, редактирования, отображения, поиска и сортировки.
5. Переопределите методы классов, выполняющие операции с би-
нарным файлом, заменив библиотеку stdio.h библиотекой классов
потокового ввода/вывода.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на бригаду.


В отчет включить модель данных и схемы выполнения операций пунк-
тов 2.4, 2.5 и 2.6 задания, листинги модулей, результаты тестовых про-
гонов программы. Листинги должны содержать спецификации модулей
и функций, иметь структурированный вид и комментарии.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Что означает зарезервированное ключевое слово this в C++?


2. Какой общий принцип используется при создании сложных структур дан-
ных из объектов классов?
3. Назовите основные операции со стеком.
4. Как устроены различные виды списков?

50
5. Что такое бинарное дерево?
6. В чем состоит отличие линейного списка от циклического?
7. В чем разница между однонаправленными и двунаправленными списками?
8. С помощью каких классов и методов библиотеки классов потокового вво-
да/вывода организуется работа с текстовыми файлами?

51
Лабораторная работа № 8
РАЗРАБОТКА WINDOWS-ИНТЕРФЕЙСА ПРИЛОЖЕНИЯ

Цель работы – знакомство на практике с приемами программиро-


вания на С++, применяемыми при разработке Windows-приложений с
графическим интерфейсом пользователя и событийным управлением.

ЗАДАНИЕ

1. Разработать на языке С++ 7-ю версию приложения с графиче-


ским Windows-интерфейсом пользователя. Приложение должно со-
держать одно диалоговое окно с главным меню.
2. Для вывода таблицы данных использовать список (LISTBOX).
3. Поля текущей строки таблицы отображать под таблицей от-
дельно с помощью элементов управления типа «редактор» (EDIT).
Для подписей полей использовать статический элемент – метку
(STATIC). Использовать эти поля также для ввода и редактирования
данных в таблице.
4. Для запуска функций приложения использовать кнопки (BUT-
TON) и пункты меню.
5. Для вывода сообщений пользователю использовать всплыва-
ющие окна.

ТЕОРЕТИЧЕСКИЙ МАТЕРИАЛ

Графический интерфейс пользователя в Windows формируется из


окон, блоков диалога и различных элементов управления.
Графические пользовательские интерфейсы поддерживаются
операционными системами Windows, Apple Macintosh, OS/2 и т. д. В
рамках указанных операционных систем для таких интерфейсов раз-
работаны наборы стандартных компонентов взаимодействия с поль-
зователем. Эти наборы не идентичны, как и основные приемы работы
с интерфейсами различных операционных систем.
Пользовательские интерфейсы большинства современных про-
грамм строятся по технологии WIMP: W – Windows (окна), I – Icons

52
(пиктограммы), М – Mouse (мышь), Р – Pop-up (всплывающие или
выпадающие меню). Основными элементами графических интерфей-
сов, таким образом, являются: окна, пиктограммы, компоненты вво-
да-вывода и мышь, которую используют в качестве указующего
устройства и устройства прямого манипулирования объектами на
экране.
Окна. Окно – обычно прямоугольная, ограниченная рамкой об-
ласть физического экрана. Окно может менять размеры и местополо-
жение в пределах экрана. Все окна можно разделить на 5 категорий:
− основные окна (окна приложений);
− дочерние или подчиненные окна;
− окна диалога;
− информационные окна;
− окна меню.
Окно приложения Windows обычно содержит: рамку, ограни-
чивающую рабочую область окна, строку заголовка с кнопкой си-
стемного меню и кнопками выбора представления окна и выхода,
строку меню, пиктографическое меню (панель инструментов), гори-
зонтальные и вертикальные полосы прокрутки и строку состояния.
Дочернее окно Windows используют в многодокументных про-
граммных интерфейсах (MDI), предполагающих, что программное обес-
печение должно работать с несколькими документами одновременно. В
отличие от окна приложения дочернее окно не содержит меню. В строке
заголовка – специальное имя, идентифицирующее связанный с ним до-
кумент или файл. Пиктограммы всех дочерних окон одинаковы.
Диалоговое окно используют для просмотра и задания различных
режимов работы, необходимых параметров или другой информации.
Оно может содержать:
− строку заголовка с кнопкой системного меню;
− компоненты, обеспечивающие пользователю возможность
ввода или выбора ответа;
− вспомогательные компоненты, обеспечивающие подсказку,
например поле предварительного просмотра или кнопка вызова справки.

53
Как правило, размер диалогового окна не изменяется, но его
можно перемещать по экрану.
Информационные окна бывают двух типов: окна сообщений и
окна помощи. Окна сообщений, кроме заголовка с кнопкой системно-
го меню, обычно содержат текст сообщения и одну или несколько
кнопок реакции пользователя, например кнопки Yes и No или кнопки
Yes, No и Cancel.
Окно помощи имеет более сложную структуру: оно может содер-
жать меню, полосы прокрутки и информационную область, т. е. по
структуре оно аналогично окну приложения, но отличается от него
тем, что имеет узко специальное назначение, обеспечивая навигацию
по справочной информации.
Окна меню Windows можно использовать как открывающиеся
панели иерархического меню или как отдельные контекстные меню.
Каждой строке окна меню может соответствовать:
− команда;
− меню следующего уровня, что обозначается стрелкой;
− окно диалога, что обозначается тремя точками.
Кроме того, в некоторых строках добавляется указание клавиш
быстрого вызова.
Пиктограммы. Пиктограмма представляет собой небольшое ок-
но с графическим изображением, отражающим содержимое буфера, с
которым она связана. Различают:
− программные пиктограммы;
− пиктограммы дочерних окон;
− пиктограммы панели инструментов;
− пиктограммы объектов.
Программными пиктограммами, которые связаны с соответ-
ствующей программой, управляет операционная система. Так, можно
«свернуть» окно приложения в пиктограмму на панели задач Win-
dows или «развернуть» его обратно «на рабочий стол».
Аналогично многодокументная программная система управляет
пиктограммами дочерних окон, обеспечивающими доступ к различным
документам, одновременно обрабатываемым программной системой.

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

55
Компоненты ввода-вывода. Как уже упоминалось, в окнах при-
ложения могут размещаться специальные компоненты, используемые
для ввода-вывода информации. Интерфейс практически любого совре-
менного программного обеспечения включает несколько меню: основ-
ное или «ниспадающее» иерархическое меню, пиктографические меню
(панели инструментов) и контекстные меню для разных ситуаций. Лю-
бое из указанных меню представляет собой компонент ввода-вывода,
реализующий диалог с пользователем, используя табличную форму.
Иерархические меню используют, чтобы организовать выполня-
емые программным обеспечением операции, если их число превыша-
ет 5-8 (6 в соответствии с рекомендациями фирмы IBM), и обеспечить
пользователю их обзор. Панели инструментов и контекстные меню
применяют для обеспечения быстрого доступа к часто используемым
командам, тем самым пользователю предоставляется возможность
свободной навигации.
Кроме меню в интерфейсе используют и другие компоненты вво-
да-вывода, которые можно разделить на три группы в соответствии с
тем, какую форму диалога они реализуют: фразовую, табличную или
смешанную. Директивная форма диалога обычно предполагает ввод
комбинаций клавиш или перемещение пиктограмм, а потому не тре-
бует использования компонентов ввода-вывода.

МЕТОДИКА ВЫПОЛНЕНИЯ

1. Запустите MS Visual Studio и создайте проект Windows-приложения.


2. Разработайте графический интерфейс пользователя и экранную
форму с учетом требований задания.
3. Скопируйте и подключите к проекту модули объявления и опреде-
ления классов из работы № 8.
4. Определите обработчиков событий при нажатии кнопок и выборе
пунктов меню для операций добавления, удаления, редактирова-
ния, отображения, поиска и сортировки.

56
5. Переопределите методы классов, выполняющие операции кон-
сольного ввода/вывода, с учетом использования элементов графи-
ческого интерфейса пользователя.

СОДЕРЖАНИЕ ОТЧЕТА

Отчет готовится в рукописном или печатном виде один на брига-


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

КОНТРОЛЬНЫЕ ВОПРОСЫ

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


интерфейс пользователя Windows-приложения.
2. Объясните устройство основных компонентов Windows-интерфейса пользо-
вателя: окно, блок диалога, элемент управления, кнопка, редактор, список,
комбинированный список, полоса прокрутки, метка.
3. Объясните устройство дополнительных элементов управления: изменяемый
список, просмотр списка, строка состояния, подсказка, просмотр деревьев,
вкладка, окно свойств, индикатор.
4. Что такое ресурсы приложения?
5. Объясните суть понятия интерфейс программирования приложения (API).
6. Что такое оконная процедура? Как работают программы, управляемые со-
бытиями?
7. В чем заключается визуальное программирование, каковы его достоинства и
недостатки?
8. Объясните назначение функции WinMain.

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

1. Павловская Т.А. С/С++. Программирование на языке высокого уровня. – СПб.:


Питер, 2006. – 461 с.: ил.
2. Павловская Т.А., Щупак Ю.А. С/С++. Структурное программирование:
практикум. – СПб.: Питер, 2005. – 239 с.: ил.
3. Павловская Т.А., Щупак Ю.А. С/С++. Объектно ориентированное
программирование: практикум. – СПб.: Питер, 2005. – 265 с.: ил.
4. Орлов С.А. Технология разработки программного обеспечения: учебник для ву-
зов. – 3-е изд. – СПб.: Питер, 2004. – 527 с.: ил.

57
СОДЕРЖАНИЕ

Лабораторная работа № 1.
Простые программы с циклами и операторами консольного ввода/вывода ........ 3
Лабораторная работа № 2.
Работа с текстовыми файлами, структурами данных и меню ................................ 9
Лабораторная работа № 3.
Разработка и спецификация функций и модулей программы .............................. 17
Лабораторная работа № 4.
Разработка и спецификация структур данных, использование указателей
и динамических массивов структур ........................................................................ 23
Лабораторная работа № 5.
Использование объектно ориентированного программирования
в разработке приложений ......................................................................................... 29
Лабораторная работа № 6.
Использование наследования, полиморфизма и абстрактных классов ............... 36
Лабораторная работа № 7.
Сложные структуры из объектов классов............................................................... 44
Лабораторная работа № 8.
Разработка windows-интерфейса приложения ....................................................... 52
Библиографический список ...................................................................................... 57

58
Учебное издание

Конструирование программного обеспечения

КОВШОВ Владимир Илларионович

Редактор Т.Г. Трубина


Компьютерная верстка И.О. Миняева
Выпускающий редактор Е.В. Абрамова

Подписано в печать 05.06.13.


Формат 60х84 1/16. Бумага офсетная.
Усл. п. л.3,34. Уч..-изд. л. 3,31.
Тираж 50 экз. Рег. № 126/13.

Государственное образовательное учреждение


высшего профессионального образования
«Самарский государственный технический университет»
443100, г. Самара, ул. Молодогвардейская, 244. Главный корпус

Отпечатано в типографии
Самарского государственного технического университета
443100, г. Самара, ул. Молодогвардейская, 244. Корпус № 8

59

Вам также может понравиться