О.В. АРИПОВА, А.Н. ГУЩИН, О.А. ПАЛЕХОВА

ПРОГРАММИРОВАНИЕ
НА  ЯЗЫКЕ  ВЫСОКОГО  УРОВНЯ

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

УДК 004.438 (075.8)

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

Предназначен для использования в лабораторных работах по учебным курсам «Основы программирования», «Программирование на языке высокого уровня», «Учебный практикум по вычислительной технике» и «Информатика» для студентов, изучающих языки программирования С и С++.

© Авторы, 2014

© БГТУ, 2014

 

СОДЕРЖАНИЕ

 
Предисловие
Лабораторная работа № 1. Файлы
Лабораторная работа № 2. Графический режим ввода-вывода в языках С и С++  с использованием SDL
Лабораторная работа № 3. Классы: основные понятия и определения
Лабораторная работа № 4. Линейные структуры данных
Лабораторная работа № 5. Шаблоны
Лабораторная работа № 6. Наследование
Приложение 1.Примеры обработки текстовых и бинарных файлов137
Приложение 2.Начало работы с SDL 1.2.15, SDL_draw 1.2.13 и SDL_ttf 2.0.11 в интегрированной среде разработки Dev-Cpp 4.9.9.2 под управлением Windows XP
Приложение 3. Пример программы имитации движения объекта
Приложение 4. Пример вывода на экран графика функции с использованием библиотеки
Приложение 5. Линейный односвязный список
Приложение 6. Очередь в циклическом массиве
Библиографический список

Предисловие

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

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

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

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

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

Лабораторная работа № 1
Файлы

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

Теоретические сведения

Файлы и потоки

Наиболее общее определение понятия файл в вычислительной технике и информатике можно дать следующим образом: файл – это поименованная совокупность данных, хранящаяся на внешнем носителе информации (внешнем запоминающем устройстве, не доступном, в отличие от основной памяти, для непосредственной обработки программой). Оно указывает на главное свойство файлов – сохранение своего состояния, пока не происходит их явная обработка той или иной программой, столь долго, сколько позволяют физические принципы, на которых организовано конкретное внешнее запоминающее устройство. Разумеется, это относится только к нормальным режимам работы устройства, сохранение состояния файла в аварийных режимах гарантировать невозможно. Приведенное определение включает в себя и более частные, например, файл – поименованная последовательность данных на внешнем носителе. Более того, данное определение можно применить и к таким часто рассматриваемым в вычислительной технике в качестве файлов объектам, как логические устройства посимвольного ввода и посимвольного вывода, если допустить, что это файлы, с которыми одновременно работают две программы: собственно программа и операционная система.

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

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

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

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

Файловый указатель представляет собой указатель на структуру типа FILE, определенную в заголовочном файле <stdio.h> с помощью оператора typedef, содержащую информацию о файле: местонахождение файла с точки зрения операционной системы, текущую позицию в файле, характер операций, разрешенных при открытии файла, наличие ошибок и состояние признака конца файла, – и сведения о буферизации. Для файлового указателя также встречаются названия указатель на файловый поток, файловый поток,указатель на поток или просто поток. Это связано с одним из способов интерпретации внешних файлов как потока символов и показывает общность работы с файлами и со стандартными средствами ввода-вывода.

Объявление файлового указателя может быть как глобальным, так и локальным, внутри тела функции main() или какой-либо другой функции:

#include <stdio.h>
FILE *fp1;
int func1(double x);
int main(int argc, char *argv[])
{
FILE *fp2;
… /* Текст функции main() пропущен */
}
int func1(double x)
{
FILE *fp3;
… /* Текст функции func1() пропущен */
}

В приведенном примере объявляются три файловых указателя, соответственно fp1 в глобальной области видимости и fp2 и fp3 в локальных областях видимости функций main() и func1().

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

FILE *fopen(const char *filename, const char *mode);

Первый аргумент функции fopen() – символьная строка, содержащая имя файла. Это внешнее имя файла, записанное по правилам конкретной операционной системы, от следования этим правилам зависит возможность нахождения данного файла. При этом необходимо учитывать, что при задании данного имени как строкового литерала в двойных кавычках, происходит обычная трансляция последовательностей управляющих символов, начинающихся с обратной косой черты, и если она используется в имени файла (например, для разделения имен каталогов в полном имени файла в операционных системах MS-DOS и  Windows), то ее необходимо представить в виде последовательности \\ (две косых черты подряд без каких либо иных символов между ними), такая последовательность будет рассматриваться компилятором как одна косая черта собственно в строковом литерале.

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

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

• ”w” (от”write”) – режим открытия для записи; данная (открывающая) программа, запущенная некоторым конкретным пользователем, должна иметь право вести запись в данный файл (определяется операционной системой). Отсутствие права на запись в данный файл является ошибкой. Если открываемый файл существует, то все его содержимое стирается, а его длина становится равной 0. Если открываемый файл не существует, то предпринимается попытка его создания, для чего данная (открывающая) программа, запущенная некоторым конкретным пользователем, должна иметь право создания файла с указанным именем (определяется операционной системой – например, может требоваться отдельный допуск для записи в указанный каталог или существовать отдельный допуск для создания файлов вообще). Отсутствие права на создание файла с указанным именем является ошибкой.

• ”a” (от ”append”)> – режим открытия для добавления в конец файла; данная (открывающая) программа, запущенная некоторым конкретным пользователем, должна иметь право на запись в данный файл (определяется операционной системой). Отсутствие права на запись в данный файл является ошибкой. Если открываемый файл существует, то все его содержимое сохраняется, добавление новых данных производится сразу после существующих, размер файла увеличится на количество записанных байт. Если открываемый файл не существует, то предпринимается попытка его создания. Отсутствие права на создание файла с указанным именем является ошибкой.

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

”r+” – режим открытия для модифицирования (чтения и записи) существующего файла.

”w+” – режим открытия для модифицирования (чтения и записи). Если открываемый файл существует, то все его содержимое стирается, а его длина становится равной 0. Если открываемый файл не существует, то предпринимается попытка его создания.

”a+” – режим открытия для модифицирования (чтения и записи). Если открываемый файл существует, то добавление после открытия начинается в конец файла (после существующих данных). Если открываемый файл не существует, то предпринимается попытка его создания.

В некоторых системах (например, MS-DOS и совместимых с ними, ОС семейства Microsoft Windows) делается различие между текстовыми и двоичными файлами, также называемыми бинарными файлами от английского binary – двоичный. Для текстовых файлов производится преобразование некоторых символов при записи в файл в другие символы или последовательности символов и обратное преобразование при чтении из файла (фактически в файл записывается байт или последовательность байт, соответствующая кодам символов). Для двоичных файлов никаких преобразований не производится, каждый символ однозначно записывается в файл своим кодом и именно записанный в данную позицию в файле байт будет впоследствии из нее прочитан. В других системах (например, FreeBSD, OpenBSD и ОС семейства  Linux), такого различия нет, поскольку и для текстовых файлов никаких преобразований не производится. Для систем, делающих различие между текстовыми и двоичными файлами, при работе с последними необходимо добавить в конец строки режима букву ”b” (от ”binary”). Тогда возможные строки режима для них будут выглядеть как "rb", "wb", "ab", ”r+b”, ”w+b”, ”a+b”. . Согласно стандарту языка С, для указания открытия файла как текстового специального модификатора в строке режима не предусмотрено. Однако многие компиляторы допускают указания в строке режима сразу после одного (”r”, ”w”, ”a”) или двух (”r+”, ”w+”, ”a+”) символов, специфицирующих разрешенные операции для работы с файлом, произвольной строки, и интерпретируют ее следующим образом: если она начинается с символа ”b” – то файл рассматривается как двоичный, иначе как текстовый. Поэтому, для таких компиляторов часто встречающиеся в литературе строки режима открытия файла как текстового ”rt”, ”wt”, ”at”, ”r+t”, ”w+t”, ”a+t” не являются ошибкой и приводят к ожидаемому результату, однако в стандарте языка C89 подобное поведение не определено и использования таких строк следует, по возможности, избегать.

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

fp1 = fopen(name, mode);
if(fp1 != NULL)
{
  /* Работаем с успешно открытым файлом*/
}

Программа на языке С при запуске на выполнение автоматически открывает три файла и создает для них файловые указатели. Это стандартный поток ввода, стандартный поток вывода и стандартный поток ошибок, предоставляемые операционной системой. Соответствующие им файловые указатели (объекты типа FILE*) объявлены в заголовочном файле <stdio.h> и называются stdin, stdout и stderr. Следует обратить внимание, что файловые указатели stdin, stdout и stderr являются константами, а не переменными, поэтому им нельзя присваивать какие-либо значения.

Для разрыва связи, установленной функцией fopen() между внешним файлом и файловым указателем, служит функция fclose():

int fclose(FILE *fp);

Функция fclose() выполняет запись буферизованных, но еще не записанных физически во внешний файл данных, уничтожает в памяти (но не в самом внешнем файле!) буферизованные входные данные, еще не прочитанные программой, освобождает все автоматически выделенные буферы, после чего закрывает файловый указатель и его можно повторно использовать для других файлов. Функция fclose() возвращает значение EOF (это константа типа int, описанная в заголовочном файле <stdio.h>, используется рядом функций для указания достижения конца файла или как признак ошибки) в случае ошибки и нуль в противном случае. Для гарантии корректного закрытия файлов и сохранения данных, находящихся в буфере, но еще не записанных на внешнее запоминающее устройство, следует принудительно вызывать функцию fclose() для всех открытых и еще не закрытых файлов при завершении работы программы. Во время работы программы использование функции fclose() рекомендуется для закрытия более не используемых файлов, чтобы не превысить максимальное число открытых одной программой файлов, определяемое операционной системой. Следует обратить внимание, что значение самого файлового указателя при закрытии файла не изменяется, но использовать его для доступа к файлам нельзя (структура, на которую он указывает, более не содержит корректных данных для доступа к внешнему файлу).

Функция fclose() может использоваться и для разрыва связи файловых указателей stdin, stdout и stderr с соответствующими файлами, по умолчанию связанными с вводом с клавиатуры и выводом на экран. После разрыва данных связей, файловые указатели stdin, stdout и stderr могут быть переназначены на другие внешние файлы с помощью функции freopen():

 
FILE *freopen (const char *filename, 
               const char *mode, FILE *stream);

Функция freopen() открывает файл в заданном режиме (параметры filename и mode соответственно) и ассоциирует с ним поток stream. Параметр stream должен быть действительным указателем на структуру типа FILE, соответствующую либо открытому файлу, либо ранее успешно открытому и впоследствии закрытому файлу (поскольку функция freopen(), в отличие от fopen(), сама память не распределяет). Возвращает указатель на поток (значение fstream) или NUL

Для записи всех буферизованных, но еще не записанных данных в поток вывода (файл, открытый для записи, добавления или модификации) может быть применена функция fflush():

int fflush(FILE *stream)

В случае ошибки функция возвращает EOF, в противном случае – нуль. Результат применения к потоку ввода не определен. Вызов fflush(NULL) выполняет указанные операции для всех потоков вывода.

Для более детального управления буферизацией служат функции setbuf() и setvbuf(), описание которых приводится, например, в [1] и в специальной литературе

Функции для чтения из файла и записи в файл

Посимвольное чтение из файла – fgetc() и getc() (функция getc() эквивалентна fgetc(), но если она определена как макрос, то может обращаться к потоку stream несколько раз):

int fgetc(FILE *stream);int
getc(FILE *stream);

Обе функции возвращают следующий символ из потока stream в виде unsig nedchar (преобразованным к int) или EOF в случае достижения конца файла или обнаружения ошибки.

Посимвольная запись в файл – fputc() и putc() (функция putc эквивалентна fputc(), но если она определена как макрос, то может обращаться к потоку stream несколько раз):

int fputc(int c, FILE *stream);

int putc(int c, FILE *stream);

Обе функции записывают символ c (преобразованный в unsigned char) в поток stream. Возвращают записанный символ или EOF, если обнаружена ошибка.

Для посимвольной работы со стандартными потоками ввода и вывода существуют функции getchar() и putchar():

int getchar(void); /* вызов getchar(с) эквивалентен getc(stdin) */

int putchar(int c); /* вызов putchar(c) эквивалентен putc(c, stdout) */

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

int ungetc(int c, FILE *stream);

Символ EOF возвращать в поток нельзя! Функция ungetc() возвращает отправленный назад в поток символ или EOF при обнаружении ошибки.

Для чтения из файла строки предназначена функция fgets():

int *fgets(char *s, int n, FILE *stream);

Она считывает из потока stream не более n–1 следующих подряд символов в массив s, прекращая ввод, если встретится символ конца строки, который включается в массив. После последнего прочитанного символа в массив записывается символ '\0'. Функция возвращает s при успешном выполнении, NULL в случае достижения конца файла или при обнаружении ошибки.

Для записи строки в файл предназначена функция fputs():

int fputs(const char *s, FILE *stream);

Она записывает в поток stream строку s, которая может как содержать, так и не содержать символ '\n' (строка записывается до символа '\0', не включая его). Функция возвращает неотрицательное целое число при успешном выполнении или EOF при обнаружении ошибки.

Для построчной работы со стандартными потоками ввода и вывода существуют функции puts() и gets():

int puts(const char *s);

char *gets(char *);

Функция puts() записывает в stdout строку s и сразу после неё символ конца строки, возвращает неотрицательное число при нормальном завершении и EOF в случае ошибки. Функция gets() считывает следующую строку из потока ввода в массив s, заменяя символ конца строки на '\0'. Возвращает s при нормальном завершении или NULL при достижении конца файла или обнаружении ошибки. Обратите внимание, что функция gets() не проверяет размер буфера по указателю s и возможно переполнение буфера, то есть функция является небезопасной.

Для форматированного ввода данных из файла и записи в файл используются функции fscanf() и fprintf(), отличающиеся от функций scanf() и printf() добавлением первого обязательного аргумента – файлового указателя:

int fscanf(FILE *stream, const char *format, ...);

int fprintf(FILE *stream, const char *format, ...);

Соответственно, все требования к строке формата и к соответствию последующих аргументов спецификаторам формата у них идентичны. Функция fprintf() возвращает количество записанных в поток символов или отрицательное число в случае ошибки. Функция fscanf() возвращает EOF, если до преобразования формата ей встречается конец файла или появилась ошибка, иначе функция fscanf() возвращает количество введенных и помещенных по назначению данных.

Функции прямого ввода-вывода fread() и fwrite():

size_t fread (void *ptr, size_t size, size_t nobj, FILE *stream);

size_t fwrite (void *ptr, size_t size, size_t nobj, FILE *stream);

Функция fwrite() записывает nobj объектов размера size из массива ptr в поток stream и возвращает количество фактически записанных объектов, которое в случае ошибки будет меньше nobj. Функция fread() считывает в массив ptr из потока stream не больше nobj объектов размеров size и возвращает количество считанных объектов, которое может оказаться меньше запрашиваемого nobj. Никаких преобразований данных при использовании этих функций не осуществляется. Простые примеры обработки файлов с использованием рассмотренных функций приведены в приложении 1.

Для определения успешного завершения или ошибки при чтении можно использовать функции feof() и ferror():

int feof(FILE *stream);

int ferror(FILE *stream);

Функция feof() возвращает значение, отличное от нуля, если для потока stream установлен признак конца файла (достигнут конец файла). К сожалению, она не всегда работает правильно. Функция ferror() возвращает значение, отличное от нуля, если для потока stream установлен (включен) признак ошибки. Дополнительную информацию о последней ошибке может дать целочисленное выражение errno, согласно стандарту C89 объявленное в <errno.h>.

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

int clearerr(FILE *stream);

int perror(const char *s);

Первая функция сбрасывает индикаторы ошибок потока и конца файла (если они установлены) для потока stream. Вторая функция выводит в поток stderr зависящее от реализации сообщение об ошибке, соответствующее текущему значению errno. Вызов perror(s) эквивалентен вызову fprintf (stderr, “%s: %s\n”, s, “зависящее от errno сообщение об ошибке”).

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

Функция принудительного перемещения текущей позиции в файле fseek() объявлена следующим образом:

int fseek(FILE *stream, long offset, int origin);

Эта функция перемещает указатель позиции на заданный символ в потоке stream, причем последующая запись или чтение будут производиться с указанной позиции. Позиция задается смещением на offset символов относительно точки отсчета, заданной параметром origin: если он равен SEEK_SET, то смещение задается относительно начала файла; если равен SEEK_CUR – относительно текущей позиции в файле; если SEEK_END – относительно конца файла. В случае ошибки функция возвращает значение, отличное от нуля.

Узнать текущую позицию в потоке можно с помощью функции ftell():

long ftell(FILE *stream);

В случае ошибки вместо текущей позиции в потоке она возвращает значение –1L.

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

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

Функция «перемотки» – сброса признаков ошибок и перемещения текущей позиции на начало файла – rewind():

void rewind(FILE *stream);

Вызов функции rewind(fp) эквивалентен следующему фрагменту программы:

fseek (fp, 0L, SEEK_SET);

clearerr(fp);

Функции записи текущей позиции в потоке stream и восстановления в нем ранее сохраненной позиции fgetpos() и fsetpos():

int fgetpos(FILE *stream, fpos_t *ptr);

int fsetpos(FILE *stream, const fpos_t *ptr);

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

Для переименования или удаления файлов средствами операционной системы без их открытия предусмотрены стандартные функции rename() и remove() соответственно:

int rename(const char *oldname, const char *newname);

int remove(const char *filename);

Параметры функций очевидны: oldname и newname – старое и новое имя переименовываемого файла, filename – имя удаляемого файла. Обе функции в случае ошибки возвращают значение, отличное от нуля.

На практике также могут представлять интерес функции для работы с временными файлами tmpfile и tmpname(), подробное описание которых также можно найти в [1] и в специальной литературе.

Контрольные вопросы

1.     Что такое файл с точки зрения информатики и вычислительной техники?

2.     Что такое файл с точки зрения языка программирования С?

3.     Какие виды файлов Вы знаете?

4.     В чем отличие текстового файла от бинарного и от чего зависит наличие этого различия?

5.     Что такое поток?

6.     Что такое признак окончания файла? Для каких файлов он существует?

7.     Как инициализировать поток?

8.     Что такое структурный тип?

9.     Какая функция отвечает за открытие потока? Какие у нее параметры?

10.                 Какие режимы открытия файла Вы знаете? В чем их отличия друг от друга?

11.                 Какие ошибки могут возникнуть при открытии файла?

12.                 В каком случае указатель на поток принимает значение NULL?

13.                 Какая функция отвечает за закрытие файла? Какие у нее параметры?

14.                 Зачем закрывать файл?

15.                 Сколько раз можно открыть файл в программе?

16.                 Какие функции используются для ввода-вывода данных при работе с текстовыми файлами?

17.                 Какие функции используются для ввода-вывода данных при работе с бинарными файлами?

18.                 В чем отличие функции fprintf() от функции fread(), а функции fscanf() от fwrite()?

19.                 Как определить текущую позицию указателя на поток?

20.                 Как переместить указатель на поток из текущей позиции в заданную?

21.                 За что отвечает константа EOF? Как и где ее можно использовать?

22.                 Какие основные функции для работы с файлами Вы знаете?

23.                 С помощью каких функций можно осуществлять побайтовую обработку файлов?

24.                 Для чего используются функции rename() и remove()? Нужно ли при работе с ними объявлять файловый указатель?

25.                 Можно ли удалить или переименовать открытый файл?

Постановка задачи

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

Задания могут быть выполнены на трех уровнях сложности.

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

2)       Средний. Имена входных файлов должны передаваться программе при ее запуске (через параметры функции main()). Исходный файл к первой задаче может содержать как латинские, так и русские буквы, на одной строке может находиться несколько фраз, возможно продолжение фразы на следующей строке. Фразы отделяются друг от друга точками, а слова – пробелами и знаками препинания. Последняя фраза в файле может быть без точки в конце. Вывод содержимого файла записей осуществлять в табличном виде с графлением визуально подходящими символами.

3)       Высокий. Имена входных файлов должны передаваться программе при ее запуске (через параметры функции main()). Если параметры пользователем при запуске программы не заданы, имена файлов вводятся с клавиатуры. Исходный файл к первой задаче может содержать как латинские, так и русские буквы, фразы могут быть любой длины, соответственно, одна фраза может располагаться на нескольких строках. Фразы отделяются друг от друга точками, а слова – пробелами и знаками препинания. Последняя фраза в файле может быть без точки в конце. Вывод содержимого файла записей осуществлять постранично в табличном виде с графлением визуально подходящими символами, предусмотреть возможность «листания» страниц как в прямом, так и в обратном направлении.

Варианты заданий

Вариант № 1

1.     Дан файл, содержащий некоторый текст. Удалить из этого файла все фразы, содержащие слово «мама».

2.     В текстовом файле хранится целочисленная матрица. Заменить в ней все числа, кратные 7, наибольшим значением матрицы. Полученный файл вывести на экран.

3.     Компоненты бинарного файла – вещественные числа. Изменить знак у каждого третьего числа на противоположный.

4.     Дан файл, содержащий сведения об ассортименте игрушек в магазине. Структура записи: название игрушки, цена, количество, возрастные границы, например от 2 до 5. Вывести названия игрушек, которые подходят детям определенного возраста и стоят не больше определенной суммы. Получить сведения о самом дорогом конструкторе.

Вариант № 2

1.     Дан файл, содержащий некоторый текст. Удалить из файла все фразы, заканчивающиеся и начинающиеся на одну и ту же букву.

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

3.     Компоненты бинарного файла – вещественные числа. Поменять местами первый компонент файла с минимальным, а последний – с максимальным.

4.     В файле содержатся сведения о спортсменах: фамилия, пол, вид спорта, год рождения, рост. Найти самого высокого спортсмена, занимающегося плаванием, среди мужчин. Вывести сведения о спортсменках, выступающих в юниорском разряде (14-17 лет).

Вариант № 3

1.     Дан файл, содержащий некоторый текст. В каждой фразе найти самое длинное слово и записать его в другой текстовый файл.

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

3.     Бинарный файл содержит вещественные числа. Удалить отрицательные, в конец файла записать количество удалений.

4.     Имеется файл, содержащий сведения о наличии билетов на рейсы некоторой авиакомпании. Структура записи: номер рейса, пункт назначения, время вылета, цена билета, количество свободных мест в салоне. Произвести корректировку данных в файле при продаже билетов, исходные данные – номер рейса и количество проданных билетов. Получить сведения о наличии мест, цене билета и времени вылета для определенного рейса.

Вариант № 4

1.     Дан файл, содержащий некоторый текст. Удалить из этого файла лишние пробелы, оставив по одному между словами. Если слова разделяются знаком препинания без пробела, добавить пробел после знака препинания. Для высокого уровня сложности дополнительно требуется учет следующих правил. Однократная встреча символа «-» («HYPHEN-MINUS», «минус», код символа равен 45), окруженного слева и справа буквами (или с одной стороны соседствующего с буквой, а с другой – с цифрой) трактуется как дефис и не требует вставки пробелов. При наличии пробелов между дефисом и символами справа или слева – они удаляются. Последовательность из двух подряд идущих символов «--» трактуется как тире и отделяется от прочих символов слева и справа одним пробелом.

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

3.     Компоненты бинарного файла – вещественные числа. Поставить последнее число из этого файла между 10-м и 11-м компонентами. Если в файле меньше одиннадцати чисел, то никаких изменений производить не требуется.

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

Вариант № 5

1.     Дан файл, содержащий некоторый текст. Удалить из этого файла фразы, содержащие слова с двумя буквами «О».

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

3.     Компоненты бинарного файла – целые числа. Добавить после каждого положительного числа его квадрат, нули удалить.

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

Вариант № 6

1.     Дан файл, содержащий некоторый текст. Отредактировать файл: после каждой фразы в скобках указать число слов в ней.

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

3.     Компоненты бинарного файла – целые числа. Удалить из этого файла максимальное и минимальное число.

4.     Дан файл, содержащий сведения о поступивших в продажу автомобилях. Записи содержат следующие поля: марка автомобиля, страна-производитель, год выпуска, объем двигателя, расход бензина на 100 км, цена, количество экземпляров. Скорректировать данные об определенном автомобиле при изменении на него цены. Вывести марку автомобиля с определенным объемом двигателя и наименьшим расходом бензина.

Вариант № 7

1.     Дан файл, содержащий некоторый текст. Переписать в новый текстовый файл фразы, состоящие из наибольшего количества слов.

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

3.     Компоненты бинарного файла – целые числа. Удалить из него все нули. Добавить в начало файла количество отрицательных компонентов, а в конец – количество положительных.

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

Вариант № 8

1.     Дан файл, содержащий некоторый текст. Оставить в этом файле только те фразы, все слова в которых содержат букву «а».

2.     В текстовом файле хранится квадратная целочисленная матрица. «Разорвать» эту матрицу по главной диагонали, записав в другой файл сначала элементы, находящиеся над диагональю, потом в одну строку диагональные элементы, и затем элементы, находящиеся под диагональю. Форма треугольников должна сохраниться.

3.     Компоненты бинарного файла – целые числа. Добавить в начало файла значение -1, а в конец файла – значение, на 1 больше максимального в этом файле.

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

Вариант № 9

1.     Дан файл, содержащий некоторый текст. Оставить в этом файле только те фразы, в которых встречается не меньше 4 различных гласных букв.

2.     В текстовом файле хранится вещественная матрица. Добавить в эту матрицу столбцы, содержащие суммы элементов каждой строки, их максимумы и минимумы.

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

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

Вариант № 10

1.     Дан файл, содержащий некоторый текст. Оставить в этом файле только те слова, которые содержат хотя бы одну букву «а» и не содержат букв «е».

2.     В текстовом файле хранится вещественная матрица. Заменить в ней все отрицательные числа нулями.

3.     Компоненты бинарного файла – беззнаковые целые числа. Удалить из этого файла все числа, являющиеся степенью числа 2.

4.     В файле хранятся сведения о личной библиотеке: фамилия автора, название, издательство, год издания, тематика книги. Вывести названия книг определенного автора, изданных после 2000 года. Определить долю книг в библиотеке по теме «Программирование» от общего количества экземпляров.

Вариант № 11

1.     Дан файл, содержащий некоторый текст. Оставить в этом файле только те фразы, которые содержат не менее трех слов.

2.     В текстовом файле в табличном виде хранится информация о количестве и ценах товаров на складе. Добавить в таблицу графу с общими суммами по каждому виду товара.

3.     Компоненты бинарного файла – целые числа. Удалить из этого файла все положительные числа, кратные 3, добавив в конец файла их количество.

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

Вариант № 12

1.     Дан файл, содержащий некоторый текст. Удалить из него все фразы, в которых есть слова, содержащие заглавные буквы (начальную заглавную букву в предложении не учитывать).

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

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

4.     Имеется файл, содержащий сведения об экспортируемых товарах: наименование товара, страна-импортер и объем поставляемой партии в штуках. Вывести страны, в которые экспортируется определенный товар и общий объем его экспорта.

Вариант № 13

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

2.     В текстовом файле хранится целочисленная матрица. Преобразовать ее в вещественную и записать в другой файл с точностью до второго знака после точки.

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

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

Вариант № 14

1.     Дан файл, содержащий некоторый текст. Оставить в этом файле только те фразы, в которых имеются слова, записанные прописными буквами.

2.     Дан текстовый файл, содержащий целые числа. Увеличить значения четных чисел этого файла вдвое, остальные оставить без изменения.

3.     Компоненты бинарного файла – вещественные числа. Поменять местами первый и последний отрицательные компоненты. В конец файла добавить количество отрицательных компонент.

4.     В файле хранятся сведения о вкладчиках банка: номер счета, паспортные данные, категория вклада, текущая сумма вклада, дата последней операции. Зафиксировать (произвести изменения) операции приема и выдачи любой суммы. Вывести наибольшую сумму вклада в категории «срочный».

Вариант № 15

1.     Дан файл, содержащий некоторый текст. Проверить, все ли фразы начинаются с прописной буквы. Если нет – исправить.

2.     В текстовом файле хранится таблица синусов и косинусов различных углов. У таблицы есть шапка вида «   x    sin x    cos x ». Добавить в этот файл колонки с тангенсами и котангенсами этих углов. Если значение тангенса или котангенса не определено, в соответствующей графе поставить прочерк.

3.     Компоненты бинарного файла – вещественные числа. Удалить из этого файла каждое пятое число.

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

Вариант № 16

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

2.     В текстовом файле хранится таблица с результатами сдачи сессии студентами одной группы. У таблицы есть шапка следующего вида: Фамилия, № зачетки, математика, физика, химия, черчение. Добавить в таблицу графу со средним баллом студента за сессию.

3.     Компоненты бинарного файла – целые числа. Поменять местами первый компонент с последним, второй – с предпоследним и т.д.

4.     В файле хранятся сведения об архитектурных памятниках: название, местоположение, тип постройки, архитектор, год постройки. Вывести сведения о сооружениях определенного типа, например, «собор», построенных до 18 века. Найти самый старый архитектурный памятник.

Вариант № 17

1.     Дан файл, содержащий некоторый текст. В новый файл записать самую длинную фразу и фразу с наибольшим количеством слов.

2.     В текстовом файле хранятся вещественные числа. Удалить из этого файла каждое пятое число.

3.     Компоненты бинарного файла – вещественные числа. Нормировать компоненты файла вычитанием среднего арифметического всех чисел из каждого числа.

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

Вариант № 18

1.     Дан файл, содержащий некоторый текст. В новый текстовый файл записать статистику по этому файлу: количество строк, фраз, слов, знаков без пробелов, знаков с пробелами,– с пояснениями, что означает каждое число.

2.     В текстовом файле хранится вещественная матрица. Изменить матрицу: округлить все значения до первого знака после запятой.

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

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

Вариант № 19

1.     Дан файл, содержащий некоторый текст. Переписать его в новый файл по две фразы на строку. Если число фраз нечетное, то в последней строке останется одна фраза.

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

3.     Компоненты бинарного файла – вещественные числа. Удалить из файла те числа, которые меньше среднего арифметического всех чисел файла.

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

Вариант № 20

1.     Дан файл, содержащий некоторый текст. Удалить из него фразы, содержащие слова, состоящие только из гласных букв.

2.     Компоненты текстового файла – вещественные числа. Поменять местами первый и последний отрицательные компоненты. В конец файла добавить среднее арифметическое отрицательных компонент.

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

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

Лабораторная работа № 2
Графический режим ввода-вывода в языках С и С++
с использованием SDL

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

Теоретические сведения

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

В модели потокового посимвольного ввода-вывода все, что выводится на экран, рассматривается как один поток вывода, а все, что пользователь вводит с клавиатуры, – как один поток ввода. При вводе информации пользователь сначала подготавливает данные с помощью предоставляемого ему однострочного текстового редактора и лишь по готовности подтверждает ввод нажатием клавиши «Enter». Программа в это время приостанавливает выполнение до получения данных от пользователя. Достоинством данной модели является ее простота и возможность реализации ограниченными аппаратными средствами. Именно такая модель ввода и вывода информации принята в стандарте C89 языка программирования C. Она реализуется функциями для работы с файловыми потоками stdin и stdout, объявленными в заголовочном файле <stdio.h>. Недостатками данной модели является ограниченность предоставляемых программисту выразительных возможностей при выводе информации, а также невозможность непосредственного реагирования на нажатие пользователем тех или иных клавиш до ввода им подготовленных данных для программы. Кроме того, данная модель не предполагает использования никаких других устройств ввода информации, кроме клавиатуры.

Особенностью вывода информации в модели псевдографического пользовательского интерфейса является рассмотрение текстового дисплея как прямоугольной матрицы знакомест. Каждое знакоместо непосредственно адресуется своими координатами по горизонтали и вертикали и обладает собственными атрибутами. При вводе информации возможно как использование потокового ввода, аналогичного предыдущей модели, так и непосредственный опрос устройств ввода, таких как клавиатура, мышь или джойстик. Непосредственная поддержка данной модели в стандарте C89 языка С отсутствует, для ее использования в программах необходимо пользоваться либо средствами конкретной операционной системы, либо использовать сторонние переносимые между операционными системами библиотеки, например ncurses.

Модель графического пользовательского интерфейса предполагает предоставление программе некоторой области вывода (в однозадачных операционных системах или при явном указании – соответствующей всему физическому дисплею). В пределах данной области имеется возможность как программно устанавливать атрибуты каждого минимального физического элемента изображения – пикселя (pixel – от PICture ELement), так и получать текущие значения его атрибутов. Вывод на экран текста в графическом режиме осуществляется с помощью предопределенных или пользовательских шрифтов. Средства ввода в графическом режиме, как правило, предполагают возможность для программиста самостоятельно определять, требуется ли отображение вводимых пользователем символов на экране или же интересует только нажатие конкретных клавиш. Аналогично модели псевдографического пользовательского интерфейса, как правило, предоставляется и возможность ожидания нажатия клавиш, и возможность проверки без ожидания, нажата ли хоть какая-то клавиша в данный момент с последующим определением, какая именно клавиша нажата. В качестве расширения второго способа часто применяется подход с назначением обработчиков событий – специальных подпрограмм, вызываемых в случае нажатия клавиш, перемещения мыши и т. п.

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

Цветовая модель RGB

Наиболее распространенный в настоящее время способ построения изображений на мониторах персональных компьютеров – индивидуальное управление яркостью и цветом каждого пикселя. Особенности восприятия цветов человеком таковы, что луч света некоторого цвета вызывает у подавляющего большинства людей такие же ощущения, как и собранные в один луч, например с помощью призмы, лучи света трех основных цветов с подобранным соотношением яркостей между ними. Такими основными цветами являются красный, зеленый и синий. По английским названиям этих цветов, соответственно red, green и blue, модель синтеза цвета светящейся точки из трех световых лучей основных цветов получила название RGB-модели или сокращенно RGB. Поскольку при этом происходит создание цветоощущения у человека за счет объединения воздействия трех различных цветов, то такая модель называется аддитивной (от английского add – сложение).

При применении данного способа управления цветом пикселей в мониторах, для упрощения конструкции (чтобы не строить для каждого пикселя оптическую систему микроскопических размеров для сведения трех лучей света), используется другая особенность зрения человека – ограниченная способность к различению близко расположенных объектов. Поэтому три монохромных источника света красного, зеленого и синего цвета, имеющие размеры в доли миллиметра и расположенные вплотную друг к другу, будут вызывать у подавляющего большинства людей те же ощущения, что и рассмотренный выше точечный источник света, полученный оптическим сложением трех соответствующих лучей. Данные источники света основных цветов, относящиеся к одному управляемому пикселю, получили название субпикселей. Таким образом, на аппаратном уровне управление яркостью и цветом одиночного пикселя монитора сводится к управлению яркостью свечения трех отдельных субпикселей основных цветов данного пикселя. При этом, если все субпиксели выключены, то такое состояние считают черным цветом пикселя, если все субпиксели включены на одинаковую максимальную яркость, то такое состояние считают белым цветом пикселя. Степень их соответствия собственно белому и черному цветам определяется конструкцией, качеством изготовления и настройками монитора. Если все субпиксели имеют некоторое одинаковое значение яркости, промежуточное между максимальной и выключенным состоянием, то пиксель воспринимается как имеющий оттенок серого цвета. Если два из трех субпикселей выключены, то пиксель имеет один из основных цветов и яркость, определяемую третьим включенным субпикселем. Все остальные цвета, которые сможет увидеть человек на экране монитора, соответствуют оставшимся комбинациям яркостей субпикселей. Степени яркости субпикселей называют компонентами цвета, а сами используемые основные цвета – цветовыми каналами. Часто эти понятия отождествляют.

Аппаратная (на уровне непосредственно монитора) глубина цвета пикселя для цветного изображения равна утроенной глубине цвета субпикселей. Например, если для каждого субпикселя можно установить 256 различных состояний яркости (обычно 0 – выключен, 255 – максимальная яркость), то есть глубина цвета в каждом канале составляет 8 бит на пиксель, то глубина цвета собственно пикселя будет составлять 24 бита на пиксель. Это позволяет задавать для каждого пикселя более 16 миллионов различных состояний. Практически все современные мониторы и их контролеры (видеокарты, видеоадаптеры) позволяют работать с глубиной цвета 24 бита на пиксель на максимально возможном для данного монитора и видеокарты разрешении. Для повышения скорости обработки изображений также используется и является сейчас самым распространенным режим работы с глубиной цвета 32 бита на пиксель. Фактически, это тот же режим работы с 24 битами на пиксель, где для представления одного пикселя вместо 3 байт используются 4 байта, составляющие одно машинное слово на компьютерах с 32 разрядной архитектурой. При обработке на 64 разрядных компьютерах, 4 байта составляют ровно половину машинного слова, что также позволяет проводить пересылку данных об одном пикселе за одну операцию, или пересылать данные сразу о двух пикселях.

Создание программ с графическим пользовательским интерфейсом
на языках С и C++ с использованием библиотек семейства SDL

Simple DirectMedia Layer (сокращенно SDL) – это свободная кроссплатформенная библиотека, реализующая единый интерфейс к графической подсистеме, устройствам ввода и звуковой подсистеме, официально поддерживающая операционные системы Linux, Microsoft Windows и Mac OS X как на уровне исходных текстов, так и скомпонованных библиотек, а платформы iOS и Android – на уровне исходных текстов.

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

Далее будут рассматриваться только основные возможности SDL для работы с изображениями на двумерных плоскостях, а также базовые возможности библиотек SDL_ttf для вывода текста и SDL_draw для вывода базовых графических примитивов, таких как линии, эллипсы и прямоугольники. Работа с ними будет рассматриваться на примере версий библиотек SDL 1.2.15, SDL_draw 1.2.13 и SDL_ttf 2.0.11.

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

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

#include "SDL.h"

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

При использовании среды разработки DevCpp 4.9.9.2 следует также обратить внимание, что использование библиотеки SDL приводит к переопределению стандартного потока вывода stdout и стандартного потока ошибок stderr на одноименные файлы stdout и stderr в каталоге с исполняемым файлом программы. Также обращаем внимание на необходимость определения функции main() в соответствии с сигнатурой, используемой в библиотеке SDL, а именно следующим образом:

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

{

/* тело функции main*/

}

Непосредственно перед использованием функций библиотеки SDL необходимо инициализировать соответствующие подсистемы библиотеки с помощью функции SDL_Init(), объявленной следующим образом:

int SDLCALL SDL_Init(Uint32 flags);

Для работы с библиотекой SDL важным является понимание причин использования в ней вместо встроенных типов данных языка C специально объявленных типов, например тип Uint32 аргумента flags – целое число без знака, представленное 32 битами (4 байта). Для переносимости между платформами и различными компиляторами он объявлен как самостоятельный тип с помощью директив препроцессора, обеспечивающих условную трансляцию. С помощью параметра flags указывается, какие именно подсистемы библиотеки требуется инициализировать и какие использовать глобальные режимы работы. Для этого объявлен ряд флагов, которые можно комбинировать с помощью операции побитового ИЛИ. Для инициализации подсистемы работы с дисплеем служит флаг SDL_INIT_VIDEO, для работы с таймером – SDL_INIT_TIMER, а для работы с подсистемой обработки звука – SDL_INIT_AUDIO. Для инициализации всех подсистем вместо комбинации соответствующих флагов используется специальный флаг SDL_INIT_EVERYTHING.

Результат, возвращаемый функцией SDL_Init(), указывает на успешность инициализации, если равен 0, или на ошибку инициализации в противном случае. При ошибке можно получить описание ошибки (также на английском языке) с помощью функции SDL_GetError(), объявленной следующим образом:

char * SDL_GetError(void);

Такое объявление позволяет использовать ее для формирования сообщений о произошедших ошибках, например, для вывода в поток stderr с помощью функции fprintf():

/* объявление переменных */

if (SDL_Init(SDL_INIT_VIDEO)) /* инициализация SDL */

{ /* При ошибке формируем сообщение и выходим */

  fprintf(stderr,"Ошибка в SDL_Init: %s\n",SDL_GetError());

  return 1;

}

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

atexit(SDL_Quit);

Поверхности. Отображение графической информации в библиотеке SDL основано на понятии «поверхность». Поверхность логически представляет собой прямоугольную матрицу пикселей, на которой можно рисовать, изменяя состояние пикселей. Набор возможных состояний пикселя определяется его форматом. Все пиксели одной поверхности имеют одинаковый формат, поэтому его также можно считать форматом поверхности. Возможно и более сложное использование поверхностей, например для формирования изображения наложением изображений разных поверхностей. В программе каждая поверхность представляется указателем на структуру SDL_Surface. Данный указатель возвращают функции, создающие поверхности как объекты программы, и он впоследствии используется для указания используемой поверхности при всех операциях с ней. Основное окно программы, возможно являющееся полноэкранным окном, также является поверхностью, однако создающейся специальной функцией установки видеорежима SDL_SetVideoMode(), объявленной следующим образом:

SDL_Surface * SDL_SetVideoMode

    (int width, int height, int bpp, Uint32 flags);

Она возвращает либо корректный указатель на структуру SDL_Surface, соответствующую поверхности окна программы (или всего экрана), либо NULL в случае ошибки. Это позволяет контролировать возникновение ошибок при установке видеорежима в простейшем случае опять же с помощью функции SDL_GetError():

SDL_Surface *screen; /* указатель на поверхность */

/* После инициализации собственно SDL и установки atexit(SDL_Quit): */

screen=SDL_SetVideoMode(800,600,32,SDL_ANYFORMAT);

if (!screen)

{

  fprintf(stderr,"SDL mode failed: %s\n",SDL_GetError());

  return 1;

}

Первый параметр функции SDL_SetVideoMode() задает ширину окна или разрешение по горизонтали для полноэкранного режима, второй – высоту окна или разрешение по вертикали. Третий параметр задает глубину цвета – количество бит для представления цвета одного пикселя. Если в качестве значения третьего параметра указать 0, то будет выбрана оптимальная глубина цвета для указанного разрешения. Четвертый параметр предназначен для указания специальных флагов задания видеорежима (часть из них применима и к другим поверхностям для рисования), в частности, для указания на полноэкранный видеорежим, возможность изменения размера окна пользователем (по умолчанию отключено), определение расположения поверхности в системной памяти или видеопамяти, возможности асинхронного обновления. В простых случаях достаточно указать флаг SDL_ANYFORMAT, заставляющий инициализировать поверхность даже в том случае, если запрошенная глубина цвета не поддерживается: практически все современные видеоадаптеры для персональных компьютеров поддерживают разрешение до 1920 на 1080 пикселей с глубиной цвета 32 бита на пиксель.

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

Функции SDL_GetVideoInfo(), SDL_VideoModeOK(), SDL_ListModes() библиотеки SDL позволяют проверить поддерживаемые аппаратной частью конкретного компьютера видеорежимы, получить список видеорежимов или получить параметры для оптимального видеорежима.

Непосредственное рисование на поверхности путем изменения состояния пикселей является достаточно трудоемким. Оно требует вычисления представления каждой компоненты цвета для соответствующей данной поверхности глубины цвета и вычисления положения битовых последовательностей, отвечающих за соответствующие компоненты конкретного пикселя для их изменения. Примеры написания таких функций можно найти в [2]. Для упрощения программного построения изображений могут использоваться дополнительные библиотеки или самостоятельное написание функций, строящих попиксельно необходимые графические примитивы.

Графические примитивы библиотеки SDL_draw

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

Библиотека SDL_draw содержит функции, содержащие следующие графические примитивы: отдельные точки, линии, окружности, эллипсы, прямоугольники и прямоугольники со скругленными углами, причем каждая фигура может изображаться и контурами, и быть полностью закрашенной. Для использования функций библиотеки в программе необходимо в ее текст добавить подключение заголовочного файла SDL_draw.h:

#include "SDL_draw.h"

Первым и последним параметрами всех функций, независимо от вида отображаемого графического примитива, являются поверхность, на которой должно производиться рисование (super – указатель на структуру SDL_Surface) и цвет, которым производится рисование (color – целое четырехбайтовое число без знака). Параметр цвета может быть сформированным из отдельных цветовых компонент (R, G, B) с помощью функции библиотеки SDL SDL_MapRGB() или задаваться соответствующей шестнадцатеричной константой. Например, белый цвет можно получить, вызвав функцию

SDL_MapRGB(screen -> format , 255, 255, 255),

где screen – поверхность рисования.

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

Система координат в окне или на экране: ось X слева направо, ось Y сверху вниз, верхний левый угол имеет координаты (0,0), нижний правый – на единицу меньше ширины и высоты окна (разрешения экрана).

Функции библиотеки SDL_draw:

1.       Отображение точки заданного цвета

void (*Draw_Pixel)(SDL_Surface *super,

                   Sint16 x, Sint16 y, Uint32 color);

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

2.       Рисование произвольной прямой линии

void (*Draw_Line)(SDL_Surface *super,

                  Sint16 x1, Sint16 y1,

                  Sint16 x2, Sint16 y2,

                  Uint32 color);

3.       Рисование горизонтальной линии

void (*Draw_HLine)(SDL_Surface *super,

                      Sint16 x0, Sint16 y0, Sint16 x1,

                      Uint32 color);

4.       Рисование вертикальной линии

void (*Draw_VLine)(SDL_Surface *super,

                      Sint16 x0, Sint16 y0, Sint16 y1,

                      Uint32 color);

Параметры x1, y1 и x2, y2 функции Draw_Line() задают координаты начала и конца отображаемой линии в целочисленных координатах поверхности (пикселях). У функций Draw_HLine() и Draw_VLine(), отображающих горизонтальную и вертикальную линии, на один параметр меньше, поскольку координата x или y обоих концов линии у них постоянна.

5.       Рисование окружности

void (*Draw_Circle)(SDL_Surface *super,

                    Sint16 x0, Sint16 y0, Uint16 r,

                    Uint32 color);

6.       Рисование круга, закрашенного одним цветом со своим контуром

void (*Draw_FillCircle)(SDL_Surface *super,

                        Sint16 x0, Sint16 y0, Uint16 r,

                        Uint32 color);

Параметры x0, y0> задают центр, а r – радиус фигуры.

7.       Рисование контура прямоугольника

void (*Draw_Rect)(SDL_Surface *super,

                  Sint16 x, Sint16 y, Uint16 w, Uint16 h,

                  Uint32 color);

8.       Макрос для единообразного с Draw_Rect() рисования закрашенного прямоугольника

Draw_FillRect (SUPER, X, Y, W, H, COLOR)

Параметры x, y задают координаты верхнего левого угла, а w, h – соответственно ширину и высоту фигуры.

9.       Рисование контура эллипса с полуосями, параллельными осям координат

void (*Draw_Ellipse)(SDL_Surface *super,

                        Sint16 x0, Sint16 y0,

                        Uint16 Xradius, Uint16 Yradius,

                        Uint32 color);

10.  Рисование эллипса, закрашенного одним цветом со своим контуром

void (*Draw_FillEllipse)(SDL_Surface *super,

                        Sint16 x0, Sint16 y0,

                        Uint16 Xradius, Uint16 Yradius,

                        Uint32 color);

Параметры x0, y0> задают центр, а Xradius и Yradius – длины соответствующих полуосей фигуры.

11.  Рисование контура прямоугольника со скругленными углами

void (*Draw_Round)(SDL_Surface *super,

                   Sint16 x0,Sint16 y0, Uint16 w,Uint16 h,

                   Uint16 corner, Uint32 color);

12.  Рисование закрашенного одним цветом  со своим контуром прямоугольника со скругленными углами

void (*Draw_FillRound)(SDL_Surface *super,

                       Sint16 x0,Sint16 y0,

                       Uint16 w,Uint16 h,

                       Uint16 corner, Uint32 color);

Параметры x, y задают координаты верхнего левого угла, w и h – соответственно ширину и высоту фигуры, corner – радиус дуг, заменяющих углы прямоугольника.

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

Блокировка поверхности предназначена для предотвращения попыток одновременного изменения данных о состоянии пикселей или чтения данных о состоянии одновременно с изменением разными процессами (собственно программой и операционной системой или непосредственно видеоадаптером, если эти данные расположены в его памяти). Блокировку поверхности необходимо выполнять явно, если производится непосредственный (без использования функций библиотеки SDL или сторонних библиотек, обеспечивающих блокировку при необходимости) доступ к данным о состоянии пикселей как по записи, так и по чтению. Поверхность следует блокировать на как можно более короткое время, а затем обязательно разблокировать. Между блокировкой и разблокировкой не следует вызывать какие-либо функции библиотеки SDL или обращаться к функциям операционной системы, поскольку им может потребоваться работа с областью памяти, занятой данными о состоянии пикселей – и они могут начать ожидать разблокировки этой же поверхности и программа «зависнет». Общая последовательность использования функций блокировки для некоторой поверхности screen при работе с SDL следующая:

1)       проверить необходимость блокировки поверхности функцией SDL_MUSTLOCK(screen) (возвращает ненулевое значение при необходимости блокировки);

1)       заблокировать поверхность при необходимости функцией SDL_LockSurface(screen) (возвращает 0 при успешной блокировке и –1, если поверхность не заблокирована);

2)       построить изображение на поверхности при успешной блокировке или отсутствии необходимости в ней;

3)       если производилась блокировка, то снять блокировку функцией SDL_UnlockSurface(screen);

4)       обновить отображение поверхности на экране функцией SDL_Flip(screen).

Рисование сложных фигур

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

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

/* Определяем структуру для задания координат точек: */

struct MyPoint {

  Sint16 x, y;

};

typedef struct MyPoint Point;

/* surf – поверхность для рисования, формат пикселей которой совпадает с форматом пикселей поверхности окна программы;

points – одномерный массив структур, описывающих координаты вершин многоугольника;

n – число вершин (элементов массива points);

colorцвет линий многоугольника */

void Draw_Polyline (SDL_Surface *surf, Point points[], Uint32 n,

                    Uint32 color)

{

  int i;

  for(i = 1; i < n; i++)

     Draw_Line (surf, points[i-1].x, points[i-1].y,

                points[i].x, points[i].y, color);

}

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

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

void Draw_Right_Polygon (SDL_Surface *surf, 
                         Uint16 x0, Uint16 y0, Uint16 r, 
                         Uint32 n, double phi, Uint32 color)
{
  Point *points = NULL;
  if(n > 2)
  {
    n++; /*число точек для построения ломаной на 1 больше числа отрезков*/
    points = (Point*) malloc (n * sizeof (Point));
    if (points)
    {
      double omega = M_PI*2/n; /*вычисление угла поворота радиуса*/
      int i;
      for (i = 0; i < n-1; i++)
      { /*вычисление координат вершин*/
        points[i].x=floor (x0 + r * cos (omega * i + phi));
        points[i].y=floor (y0 – r * sin (omega * i + phi));
      }
      points[n-1] = points[0]; /*замыкаем многоугольник*/
      Draw_Polyline(surf, points, n, color);
      free(points);        
    }   
  }       
}

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

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

Пример функции рисования дуги окружности:

void Draw_Arc(SDL_Surface *surf, Uint16 x0, Uint16 y0, Uint16 r, 
              double phi1, double phi2, Uint32 color)
/*surf – поверхность для рисования;
x0, y0, r – координаты центра и радиус окружности, от которой взята дуга; 
phi1, phi2 – углы начала и конца дуги в радианах, считая от оси OX против часовой стрелки;
color – цвет линии */
{
  Point *points = NULL;
  double delta = M_PI / 180; /* 1 градус в пересчете на радианы*/
  int n = (phi2 - phi1) / delta; /* центральный угол в градусах, он же –
количество отрезков аппроксимации*/
  if (n)
    n++; /* число точек на 1 больше числа отрезков*/
  if(n > 2)
  {
    points = (Point*) malloc (n * sizeof (Point));
    if (points)
    {   
      int i;
      for (i = 0; i < n; i++)
      {
        points[i].x = floor (x0 + r * cos (delta * i + phi1));
        points[i].y = floor (y0 – r * sin (delta * i + phi1));
      }  
      Draw_Polyline(surf, points, n, color);
      free(points);        
    }   
  }             
}

Если соединить отрезками концы дуги с точкой (x0, y0), то получится сектор.

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

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

void Draw_BoldLine1 (SDL_Surface *surf, Sint16 x1, Sint16 y1,
                     Sint16 x2, Sint16 y2, Uint16 bold,
                     Uint32 color)                 
/* Параметры совпадают c параметрами функции Draw_Line() из SDL_draw, дополнительный параметр bold – толщина линии в пикселях.*/
{
  Sint16 dx = x2 - x1, dy = y2 - y1;
 /* Рисуем основную линию */ 
  Draw_Line (surf, x1, y1, x2, y2, color);              
  if (bold >1 && !(dx == 0 && dy == 0))
  {
    double r = bold/2; /* радиус "кисти" для рисования */
    double phi; /* направляющий угол */
    double ri; /* для рисования параллельных линий */

    if (dy == 0) /*если горизонталь*/
      phi = dx > 0 ? 0 : M_PI;
    else
      if (dx == 0) /*если вертикаль*/
        phi = dy < 0 ? M_PI_2 : M_PI + M_PI_2;
      else
        phi = acos (dx / sqrt (dx * dx + dy * dy));
        if (dy > 0)
          phi = 2*M_PI - phi;  
    for (ri = 0; ri < r; ri+=0.5)
    { /* рисуем линии, параллельные исходной */
      Sint16 px1, py1, px2, py2;
      px1 = floor (x1 + ri * cos (phi + M_PI_2));     
      py1 = floor (y1 – ri * sin (phi + M_PI_2));     
      px2 = floor (x2 + ri * cos (phi + M_PI_2));     
      py2 = floor (y2 – ri * sin (phi + M_PI_2));     
      Draw_Line (surf, px1, py1, px2, py2, color); 
      px1 = floor (x1 + ri * cos (phi - M_PI_2));     
      py1 = floor (y1 – ri * sin (phi - M_PI_2));     
      px2 = floor (x2 + ri * cos (phi - M_PI_2));     
      py2 = floor (y2 – ri * sin (phi - M_PI_2));     
      Draw_Line (surf, px1, py1, px2, py2, color); 
    } 
  }   
}

Данную функцию можно использовать и для рисования закрашенных прямоугольников, стороны которых не параллельны границам окна.

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

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

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

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

void Draw_Ring (SDL_Surface *surf, Sint16 x0, Sint16 y0,
                Uint16 rout, Uint16 rin,
                Uint32 color, Uint32 bgcolor)
/* surf -- поверхность для рисования;
   x0, y0 – координаты центра окружности;
   rout, rin – внешний и внутренний радиусы;
   color – цвет кольца;
   bgcolor – цвет фона (внутри кольца) */
{
  Draw_FillCircle (surf, x0, y0, rout, color);       
  Draw_FillCircle (surf, x0, y0, rin, bgcolor);       
}

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

Предопределенных функций заливки цветом произвольных областей в библиотеке SDL_draw и собственно в библиотеке SDL нет, их придется писать самостоятельно. Принципы построения таких функций можно найти в [2] и в специальной литературе.

Имитация движения при выводе на дисплей

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

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

Самый простейший способ – рисование на мониторе всего изображения (кадра), задержка для его демонстрации, стирание и рисование следующего кадра с новым относительным расположением объектов.

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

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

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

SDL_Surface *scr; /* Основная поверхность отображения */
/* Цикл отображения ролика примерно на 1 минуту, всего
  60 секунд * 25 кадров в секунду = 1500 кадров */
int framecnt = 0; /* номер кадра */
Uint32 before_next_frame = 40; /* задержка между перерисовкой кадров 
40 миллисекунд */ 
/* Первый кадр – без задержек: */
Draw_Background(surf); /* Рисование фона */
Draw_MovedObject(surf, framecnt++); /* Первый кадр */
while(framecnt < 1500)
{
  SDL_Flip(scr); /* Отображение кадра на экране */
  SDL_Delay(before_next_frame); /* Задержка */
  Draw_Background(surf); /* Рисование фона – стирание изображения */
  Draw_MovedObject(surf, framecnt++); /* Следующий кадр */
}

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

1)       восстановление фонового изображения на месте предыдущего изображения движущегося объекта;

2)       рисование движущегося объекта в новом положении.

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

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

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

Пример программы перемещения изображения на сложном фоне приведен в приложении 3.

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

Вывод текста с помощью библиотеки SDL_ttf

Непосредственно в составе библиотеки SDL отсутствуют средства вывода на графические поверхности текстовой информации, отличные от программного попиксельного отображения каждого символа. Поэтому разработчиками библиотеки SDL создано расширение – библиотека SDL_ttf, использование версии 2.0.11 которой и будет рассмотрено далее.

Библиотека SDL_ttf является интерфейсом-надстройкой над кроссплатформенной библиотекой FreeType 2.0, обеспечивающей в операционных системах семейств Linux, Mac OS X и Windows единообразное построение изображений символов по их двухбайтовым обозначениям согласно UNICODE, используя векторные шрифты формата TrueType (.ttf), а также некоторые шрифты формата .fon. При этом библиотека FreeType непосредственно не отвечает за перенесение построенных изображений на устройство отображения. Библиотека SDL_ttf обеспечивает формирование на основе текстовой строки специально создаваемой временной поверхности, содержащей изображение данного текста, выполненное указанным шрифтом с указанными параметрами. Собственно перенос на устройство отображения сводится к наложению созданной поверхности на непосредственно отображаемую поверхность. Также библиотека SDL_ttf содержит средства по преобразованию кодировок символов, определению размеров области, которую будет занимать конкретный текст при его отображении с указанными параметрами, средства выбора шрифтов и задания параметров и т.п.

Для возможности использования в программе функций SDL_ttf необходимо подключить заголовочный файл SDL_ttf.h:

#include "SDL_ttf.h"

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

int SDLCALL TTF_Init(void);

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

void TTF_Quit(void);

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

TTF_Font * TTF_OpenFont(const char *file, int ptsize);

Первым параметром передается имя файла со шрифтом (либо полное имя, либо относительно текущего каталога). Независимо от наличия пути к файлу в первом аргументе, собственно имя файла не может как-либо сокращаться, например, если расширение имени файла в данной операционной системе считается отдельным компонентом, а не частью имени после последней точки, оно также должно быть явно указано. Вторым параметром – требуемый размер шрифта в пунктах (как в текстовых редакторах). Функция возвращает указатель на динамически размещаемую структуру типа TTF_Font (такой указатель при работе с библиотекой SDL_ttf часто также называют шрифтом по аналогии с указателями на файловый поток), содержащую информацию, необходимую для отображения символов данным шрифтом данного размера. В случае невозможности создания такой структуры (не найден файл шрифта, в нем отсутствует описание для запрашиваемого размера символов, закончилась память или при иных ошибках) функция возвращает NULL. Для завершения работы с данным шрифтом необходимо освободить память, выделенную под структуру TTF_font и связанные с ней другие структуры данных, для чего вызвать функцию TTF_CloseFont(), передав ей в качестве аргумента указатель на структуру TTF_Font. Объявление функции TTF_CloseFont() выглядит так:

void TTF_CloseFont(TTF_Font *font);

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

SDL_Surface * TTF_RenderUTF8_Solid(TTF_Font *font,

                           const char *text, SDL_Color fg);

Первый параметр – указатель на ранее успешно открытый шрифт требуемого размера, третий параметр – структура SDL_Color, в компонентах r, g и b которой описан требуемый цвет шрифта (значениями красной, зеленой и синей составляющей цвета). Второй параметр задает собственно отображаемый текст в виде си-строки, закодированный согласно UTF8. Функция возвращает указатель на созданную новую поверхность минимально необходимого размера, содержащую изображение текста, или NULL в случае ошибки.

В файле SDL_ttf.h объявлены и другие функции графического вывода текста, их описание можно посмотреть в [2].

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

int SDL_BlitSurface (SDL_Surface *src, SDL_Rect *srcrect,

                     SDL_Surface *dst, SDL_Rect *dstrect);

Первый параметр – указатель на накладываемую поверхность, второй – указатель на структуру SDL_Rect, в которой компонентами x и y задан верхний левый угол, а компонентами w и h – ширина и высота накладываемого фрагмента данной поверхности. При этом если компонентами второго параметра задается область, выходящая за пределы накладываемой поверхности, внутри функции используются значения, откорректированные для предотвращения обращения за пределы ее области памяти. Однако после успешного выполнения функции в структуре, на которую указывал второй параметр, остаются исходные значения. Если второй параметр равен NULL – накладывается вся поверхность. Третий параметр – целевая поверхность, на которую производится наложение. Если указатель хотя бы на одну из поверхностей будет равен NULL, функция завершится с ошибкой без выполнения каких-либо действий. Четвертым параметром задается указатель на структуру SDL_Rect, в которой компонентами x и y задан верхний левый угол области целевой поверхности, на которую должен быть наложен выбранный вторым параметром фрагмент накладываемой поверхности. Ширина и высота данной области определяются автоматически внутри функции таким образом, чтобы при наложении использовалась такая часть выбранного фрагмента накладываемой поверхности, которая не превосходит указанный фрагмент целевой поверхности. Поэтому значения компонент w и h четвертого параметра можно не задавать. Если четвертый параметр равен NULL, то наложение происходит, начиная от верхнего левого угла целевой поверхности, при необходимости используя всю ее площадь.  При успешном выполнении функция возвращает значение 0, в случае ошибки – значение –1. При успешном выполнении и отличном от NULL значении четвертого параметра, в него записываются параметры фактически использованного фрагмента целевой поверхности.

Для повторяющегося текста можно один раз создать содержащую его поверхность, а затем многократно ее отображать в разных точках одной поверхности и даже на разных целевых поверхностях. Однако это требует обязательного освобождения получаемых поверхностей, как только исчезает необходимость в их отдельном сохранении для предотвращения утечки ресурсов. Для этого служит функция SDL_FreeSurface() основной библиотеки SDL, объявленная следующим образом:

void SDL_FreeSurface(SDL_Surface *surface);

Таким образом, процесс вывода традиционного текста «Привет, Мир!» с отступом от верхнего левого угла окна на 200 пикселей по горизонтали и 100 пикселей по вертикали неким шрифтом типа TrueType, расположенным в файле 1.ttf в каталоге с исполняемым файлом программы, ярко-зеленого цвета, размером 14 пунктов, будет выглядеть следующим образом (при условии, что среда разработки и компилятор поддерживают исходные тексты в кодировке UTF8 и именно в ней был набран текст программы):

if(screen) /* screen – поверхность, соответствующая окну программы */

{

  SDL_Color text_color;

  SDL_Rect dest;

  SDL_Surface *text_surface = NULL;

  TTF_Font * text_font = TTF_OpenFont("1.ttf", 14);

  if(text_font){

    text_color.r = 0;

    text_color.g = 255;

    text_color.b = 0;

    dest.x = 200;

    dest.y = 100;

    text_surface = TTF_RenderUTF8_Solid(text_font,

      "Привет, Мир!", text_color);

    if(text_surface){

      SDL_BlitSurface(text_surface, NULL, screen, &dest);

      SDL_FreeSurface(text_surface);

    }

    TTF_CloseFont(fnt);

  }

}

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

Обработка событий средствами библиотеки SDL

Библиотека SDL предоставляет ряд механизмов по обработке событий, возникающих «извне» программы, таких как нажатие клавиш клавиатуры, перемещение мыши или нажатие клавиш на ней, изменение размера окна программы или его закрытие средствами операционной системы и т.д.

Активизация всех способов обработки событий в программе происходит во время инициализации графической подсистемы SDL при наличии в аргументе функции SDL_Init() флага SDL_INIT_VIDEO.

Рассмотрим основной способ обработки событий с использованием внутренней очереди библиотеки SDL. Для получения событий из очереди в порядке их поступления служат функции SDL_PollEvent() и SDL_WaitEvent(), объявленные следующим образом:

int SDL_PollEvent(SDL_Event *event);

int SDL_WaitEvent(SDL_Event *event);

Функция SDL_PollEvent() вызывает функцию принудительного обновления очереди событий, затем проверяет, имеется ли в очереди хотя бы одно событие любого типа, ожидающее обработки. Если очередь пуста, функция SDL_PollEvent() возвращает 0. Если очередь не пуста и параметр event не равен NULL, то очередное (первое) событие извлекается из очереди и сохраняется в объединении SDL_Event, на которое указывает параметр event, при этом функция SDL_PollEvent() возвращает 1. Если очередь не пуста и параметр event равен NULL, то событие остается в очереди без изменений, а функция SDL_PollEvent() также возвращает 1.

Функция SDL_WaitEvent() отличается тем, что при отсутствии событий в очереди она не возвращает управление вызвавшей функции, а ожидает наступления хотя бы одного события. При появлении в очереди события функция завершает работу и возвращает 1. При обнаружении ошибок в процессе работы с очередью событий функция возвращает 0. Параметр event обрабатывается аналогично функции SDL_PollEvent().

Для сохранения каждого полученного сообщения в общем виде и последующего анализа используется объединение типа SDL_Event, объявленное следующим образом:

typedef union SDL_Event {

        Uint8 type;

        SDL_ActiveEvent active;

        SDL_KeyboardEvent key;

        SDL_MouseMotionEvent motion;

        SDL_MouseButtonEvent button;

        SDL_JoyAxisEvent jaxis;

        SDL_JoyBallEvent jball;

        SDL_JoyHatEvent jhat;

        SDL_JoyButtonEvent jbutton;

        SDL_ResizeEvent resize;

        SDL_ExposeEvent expose;

        SDL_QuitEvent quit;

        SDL_UserEvent user;

        SDL_SysWMEvent syswm;

} SDL_Event;

Поле type представляет тип события, заданный целым числом без знака, представленным одним байтом. Для символического представления констант, описывающих разные типы событий, в SDL используется перечисление SDL_EventType. Рассмотрим основные типы событий, обозначая их здесь и далее соответствующими константами из данного перечисления (то есть, «событие SDL_KEYDOWN» – событие, при котором поле type объединения SDL_Event имеет значение, равное значению константы SDL_KEYDOWN):

·     SDL_ACTIVEEVENT – приложение (текущее) стало активным  или перестало быть активным;

·     SDL_KEYDOWN – нажата клавиша на клавиатуре;

·     SDL_KEYUP – отпущена клавиша на клавиатуре;

·     SDL_MOUSEMOTION – перемещена мышь;

·     SDL_MOUSEBUTTONDOWN – нажата клавиша мыши;

·     SDL_MOUSEBUTTONUP – отпущена клавиша мыши;

·     SDL_QUIT – запрос выхода из программы по действию пользователя (например, по нажатию мышью системной кнопки закрытия окна);

·     SDL_VIDEORESIZE – пользователь изменил размер окна и требуется изменение видеорежима;

·     SDL_VIDEOEXPOSE – необходимо перерисовать экран или окно.

Для каждого типа события или группы событий в объединении SDL_Event имеется отдельное поле – структура, содержащая тип события (это поле type, имеющее тот же тип и совпадающее в памяти с полем type самого объединения), а также, для некоторых типов событий, поля параметров, требующихся для обработки таких событий. Рассмотрим подробнее данные структуры для некоторых типов событий или их групп.

При обработке событий SDL_KEYDOWN и SDL_KEYUP используется структура SDL_KeyboardEvent следующего вида:

typedef struct SDL_KeyboardEvent {

        Uint8 type;

        Uint8 which;

        Uint8 state;

        SDL_keysym keysym;

} SDL_KeyboardEvent;

Поле type указывает на тип события – SDL_KEYDOWN или SDL_KEYUP. Поле which содержит индекс устройства клавиатуры, на которой была нажата или отпущена клавиша. Поле state указывает на состояние клавиши – она нажата (SDL_PRESSED) или отпущена (SDL_RELEASED). Поле keysym содержит информацию о конкретной нажатой клавише и, возможно, о преобразовании ее в конкретный символ UNICODE. Оно имеет тип SDL_keysym, объявленный в заголовочном файле SDL_keyboard.h так:

typedef struct SDL_keysym {

        Uint8 scancode;

        SDLKey sym;

        SDLMod mod;

        Uint16 unicode;

} SDL_keysym;

Поле scancode содержит зависящий от аппаратуры скан-код клавиши и, как правило, не должно использоваться. Если данная платформа не поддерживает скан-коды, его значение равно 0. Поле sym содержит значение виртуального кода клавиши, одинаковое на различных платформах для одинаковых клавиш независимо от фактических скан-кодов или способов ввода. Тип SDLKey – это объявленное в заголовочном файле SDL_keysym.h перечисление, содержащие символьные константы для используемых в библиотеке SDL виртуальных кодов клавиш (или кратко виртуальных клавиш).  Значения части из них совпадают с кодами символов или строчных букв в кодировке ASCII (в диапазоне от 8 до 127), однако общее число различимых клавиш составляет более 300, включая клавиши «международных клавиатур» или специфические функциональные клавиши некоторых типов компьютеров. Поле mod содержит состояние модификаторов на момент нажатия клавиши. Тип SDLMod – это объявленное также в заголовочном файле SDL_keysym.h перечисление, содержащее символьные константы для битовых масок состояния клавиш модификаторов. Значение данного поля является объединением (операцией побитового ИЛИ) значений для всех одновременно установленных модификаторов.

Поле unicode, если оно отлично от 0, содержит результат преобразования нажатия клавиши в конкретный двухбайтовый символ UNICODE, но поскольку подобные преобразования являются достаточно ресурсоемкими, то по умолчанию они отключены.

При обработке события SDL_VIDEORESIZE используется структура SDL_ResizeEvent следующего вида:

typedef struct SDL_ResizeEvent {

     Uint8 type;  

     int w;       

     int h;       

} SDL_ResizeEvent;

Поле type указывает на тип события и должно быть равно SDL_VIDEORESIZE. Поле w указывает новую ширину окна, поле h – новую высоту окна. При обнаружении такого события программа обязана установить новый видеорежим (размер окна) с соответствующей шириной и высотой. Событие возникает только в том случае, если при задании видеорежима (создании графического окна) был установлен флаг «окно изменяемого размера». Если установить точно заданные размеры невозможно, необходимо установить ближайшие осмысленные для приложения размеры окна (например, для сохранения пропорций изображения).

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

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

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

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

В первом примере используется функция ожидания события SDL_WaitEvent(). В окне приложения некоторая функция draw_picture() строит статическое изображение. Выход из программы осуществляется при наступлении события SDL_QUIT или нажатии клавиши Esc. Также, при необходимости, производится перерисовка изображения при наступлении события SDL_VIDEOEXPOSE.

/* в соответствующем месте объявляем указатель на поверхность: */
SDL_Surface *screen;
SDL_Event event;
/* инициализация библиотеки и установка видеорежима*/
if (!screen) 
{
  fprintf(stderr,"SDL mode failed: %s\n",SDL_GetError()); 
  SDL_Quit();
  return 1; /* Выход с одним кодом ошибки */
}
draw_picture(screen); /* Ранее определенная функция  вывода некоторого
 изображения, различного при каждом вызове*/
SDL_Flip(screen); /* Принудительное обновление окна программы */
/* цикл ожидания событий */
while(SDL_WaitEvent(&event))
{
  if (event.type == SDL_QUIT || 
     (event.type == SDL_KEYDOWN &&
      event.key.keysym.sym == SDLK_ESCAPE))
  {
    SDL_Quit();
    return 0; /* пусть 0 – нормальное завершение*/
  }
  if (event.type == SDL_VIDEOEXPOSE)
  {
    draw_picture(screen);
    SDL_Flip(screen); /* Принудительное обновление окна программы */
  }
}
fprintf(stderr,"WaitEvent failed: %s\n",SDL_GetError());
SDL_Quit();
return 2; /* Выход с другим кодом ошибки */

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

SDL_Surface *screen; /* объявление указателя на поверхность: */
SDL_Event event;
SDL_Rect r; /* сам прямоугольник*/
SDL_Rect r_new; /* новое положение прямоугольника*/
Sint16 leftright = 1; /* слева направо = 1, справа налево =-1 */
Sint16 max_x, max_y;
int nextstep = 1; /* для цикла обработки сообщений */
/* инициализация библиотеки и установка видеорежима */
if (!screen) 
{
  fprintf(stderr,"SDL mode failed: %s\n",SDL_GetError()); 
  SDL_Quit();
  return 1; /* Выход с одним кодом ошибки */
}
/* В объявленных ранее переменных Sint16 max_x и Sint16 max_y записаны фактическая ширина и высота области, в которой перемещается квадратик после установки видеорежима – фрагмент опущен*/
/* Первоначальное рисование по центру экрана синего прямоугольника с шириной 40 и высотой 20 пикселей */
r.x = max_x / 2 - 20;
r.y = max_y / 2 - 10;
r.w = 40;
r.h = 20;
r_new = r;
SDL_FillRect(screen, &r, 0x000000FF); /* ярко-синий */
while(nextstep) /* цикл перерисовки и обработки событий */
{
  if (SDL_PollEvent(&event)) /*если наступило событие*/
  {
    if (event.type == SDL_QUIT ||
        (event.type == SDL_KEYDOWN &&
         event.key.keysym.sym == SDLK_ESCAPE))
      nextstep = 0; /* Выход */
    if (event.type == SDL_KEYDOWN &&
        event.key.keysym.sym == SDLK_DOWN)
    { /* Вниз, если есть куда опускаться*/
      r_new.y = (r.y + r.h) < max_y ? r.y +1 : r.y;
    }
    if (event.type == SDL_KEYDOWN &&
      event.key.keysym.sym == SDLK_UP)
    { /* Вверх, если есть куда подниматься*/
      r_new.y = r.y > 0 ? r.y -1 : r.y;
    }
  }
  /* расчет перемещения по горизонтали */
  r_new.x = r.x + 1 * leftright; /* 1 – скорость перемещения
(на сколько пикселей смещаться за один шаг цикла)*/
  if (r_new.x < 0 || r_new.x + r.w > max_x)
  { /* отскок от стенки */
    leftright = -leftright;
  }  
  /* собственно перерисовка: */
  SDL_LockSurface(screen); 
  SDL_FillRect(screen, &r, 0x00000000); /* стерли черным */
  r = r_new; /* используем новые координаты */
  SDL_FillRect(screen, &r, 0x000000FF); /* ярко-синий */
  SDL_UnlockSurface(screen); 
  SDL_UpdateRect(screen,0,0,max_x,max_y);
/* Задержка на опрос событий составляет около 10 мс или более, в зависимости от производительности компьютера. При необходимости возможна дополнительная задержка */
}
SDL_Quit();
return 0; /* Нормальное завершение */

Аналогичным образом могут обрабатываться и иные события.

Теоретические основы построения графиков функций на дискретных устройствах отображения информации.

С точки зрения математики график функции = f(x), где xÎR, yÎR, есть множество точек (xмат.гр., yмат.гр.) на плоскости (точек, принадлежащих множеству R2), таких, что их координаты удовлетворяют равенствам:

xмат.гр. = x,

yмат.гр. = f(xмат.гр.) = y = f(x)

При традиционном отображении графика функции на бумаге из-за невозможности нарисовать точку бесконечно малого размера происходит, во-первых, замена множества действительных чисел множеством рациональных (исходя из точности применяемых средств измерения), а во-вторых, представление каждой точки  множеством точек, которое можно приближенно считать кругом с некоторым конечным диаметром, соответствующим минимальной толщине линии, оставляемой используемыми средствами рисования (для самых распространенных в настоящее время – от 0,1 до 1 мм). При этом направление осей координат традиционно выбирается для оси OX слева направо, для оси OY снизу вверх, а расположение точки (0, 0) видом функции. Расположение точки (1, 1), то есть выбор единицы измерения или масштаба графика определяется в зависимости от имеющихся средств измерения и требуемой точности графика. Очевидно, что на некотором листе бумаги фиксированного размера A мм по горизонтали (вдоль оси OX) на B мм по вертикали (вдоль оси OY), при условии, что значению x = 1 соответствует горизонтальная черта длиной MX, а значению y = 1 соответствует вертикальная черта длиной MY мм, может быть полностью отображена лишь такая функция, область определения которой полностью находится внутри отрезка [xmin,xmax], а область значений – внутри отрезка [min, ymax], таких что |xmax×MX xmin×MX| ≤ А и |ymax×MY ymin×MY| ≤ B. При этом MX и MY играют роль, во-первых, масштабных коэффициентов, а во-вторых – коэффициентов преобразования единиц измерения из абстрактных математических «единиц» в конкретные единицы измерения длины, в данном случае – в миллиметры. Поэтому коэффициенты MX и MY имеют размерность «единица длины/единицу» или, если считать «математическую единицу» безразмерной – то просто «единица длины», в данном случае – миллиметры. Если область определения и область значений функций полностью находятся внутри некоторых отрезков конечной длины, то график такой функции путем выбора соответствующих коэффициентов MX и MY всегда можно разместить на листе заданного размера A на B. Для максимально полного использования листа при вышеприведенных параметрах функции коэффициенты будут вычисляться по формулам:MX = А/(|xmax – xmin|) и MY = B/(|ymax – ymin|). Разумеется, при этом возможно как искажение пропорций (различный масштаб по осям), так и просто существенное искажение вида функции – как правило, из-за слияния соседних точек, связанного с физической невозможностью нарисовать точку иначе как некоторое пятно вполне конечного размера. Если же область определения или область значений функции неограниченна, то физически отобразить возможно только некоторый интересующий нас фрагмент графика, обозначив тем или иным способом продолжение изменения аргумента или значения за пределами отображенной части. Как правило, для этого используют либо некоторое продолжение графика после крайних явно обозначенных точек, либо изображение асимптот.

При построении графика функции на экране компьютера или ином дискретном устройстве отображения дополнительно к вышеприведенном рассуждениям имеются и более жесткие ограничения. Во-первых, в силу логической дискретности устройства вывода (независимо от физической реализации), то есть наличия конечного числа адресуемых по горизонтали и вертикали независимых элементов изображения (пикселей, точек и т. п.), требуется отображение как множества аргументов, так и множества значений функции на конечное подмножество целых чисел (а чаще всего – целых неотрицательных чисел). Во-вторых, во многих случаях программные средства отображения (доступа к соответствующим аппаратным средствам компьютера) используют отличающиеся от математических направления осей координат. Например, рассмотренная в пособии библиотека SDL_draw, как и многие другие библиотеки отображения, предполагает, что верхний левый угол экрана (или окна, или конкретной поверхности отображения) имеет координаты (0, 0), а правый нижний (width–1, height–1), где width – число доступных пикселей по ширине, а height – по высоте. Таким образом, ось OX направлена традиционно слева направо, а ось OY – сверху вниз. Сочетание этих двух факторов приводит к необходимости использовать явное округление и преобразование типов, а также явное указание экранных координат (x0экр, y0экр) в пикселях для точки (0, 0) на математической плоскости при вычислении экранных координат каждой точки графика. Также меняется и единица измерения коэффициентов MX и MY на «пикселей на единицу» или просто «пикселей». Для обеспечения точности все округления следует производить только после того, как из математических координат получено экранное (в пикселях) смещение отображаемой точки относительно положения на экране начала математических координат. И уже это смещение использовать для вычисления собственно экранных координат отображаемой точки. При ранее приведенных ограничениях на область определения и область значения функции, значениях ширины экрана width пикселей и высоты height пикселей, с приведенным выше направлением экранных осей координат потребуется вычислить следующие коэффициенты и экранные координаты точки начала координат для того, чтобы график функции полностью поместился на экране:

MX = width/(|xmax – xmin|) пикселей, MY = height/(|ymaxymin|) пикселей,

x0экр = floor(– xmin×MX) пикселей, y0экр = floor(height + ymin×MY) пикселей.

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

С использованием рассчитанных коэффициентов экранные координаты (xэкр, yэкр) каждой точки (x, y) на графике функции можно будет вычислить следующим образом:

xэкр = x0экр + floor(x×MX) пикселей, yэкр = y0экр – floor(ymin×MY) пикселей.

Поскольку график отображается на дискретном устройстве вывода, то очевидно, что имеет смысл рассчитывать и отображать некоторое минимальное количество точек, достаточное для восприятия полученного изображения как графика. В качестве нижней границы числа точек можно рассмотреть width – «использование каждого пикселя». Тогда точки рассчитываются от xmin до xmax с шагом x = (|xmaxxmin|)/width = MX-1. Однако при этом часто для точек непрерывной функции, xэкр которых отличаются на 1, координаты yэкр отличаются на значительную величину, что на экране выглядит как разбросанные отдельные точки, не похожие на график. Одним из способов устранения данного недостатка является увеличение числа рассчитываемых и отображаемых точек (когда для нескольких близко расположенных рассчитанных точек совпадают значения xэкр, а координаты yэкр отличаются незначительно). Например, выбирается шаг x = (|xmaxxmin|)/(10×width) или x = (|xmaxxmin|)/(100×width) и т.п. Основной недостаток данного способа – существенное увеличение вычислительных затрат, а также пропорциональное увеличение числа относительно медленных в графическом режиме операций ввода-вывода по отображению большого числа соседних (часто совпадающих) точек. При этом некоторые функции, имеющие существенное изменение поведения (такие как экспонента, логарифм или гипербола) для обеспечения визуального восприятия могут требовать расчета до 100 и более точек между двумя соседними пикселями по горизонтали. Альтернативой построению графиков функций по точкам является построение графика приближенной кусочно-линейной функции, представляющего собой ломаную линию, состоящую из отрезков, соединяющих рассчитанные точки исходной функции. При этом построение самих отрезков между точками осуществляется имеющимися средствами библиотек рисования изображений. Следует заметить, что в силу дискретности устройств отображения даже при по-точечном рисовании графика также фактически получается график некоторой приближенной функции, зато производительность второго способа существенно выше. Как правило, между точками, экранные координаты которых отличаются по горизонтали на 1 пиксель, строится от 1 до 10 отрезков, поскольку дальнейшее увеличение их числа практически не будет влиять на восприятие графика.

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

Контрольные вопросы

1.       Что такое разрешающая способность экрана?

2.       Что такое RGB-модель?

3.       Что такое поверхность?

4.       Сколько поверхностей можно одновременно использовать в программе?

5.       Как получить координаты всех четырех углов экрана?

6.       Как вычислить координату центра экрана?

7.       Как нарисовать точку?

8.       Как задать цвет рисования изображения?

9.       Какие функции предназначены для вывода текста в графическом режиме?

10.  Как в графическом режиме вводить данные с клавиатуры?

11.  Что такое графический примитив? Какие функции предназначены для изображения графических примитивов?

12.  Как можно изобразить многоугольник?

13.  Как можно изобразить кривую?

14.  Как построить сложное изображение с помощью примитивов?

15.  Какими способами можно получить закрашенную картинку?

16.  Как изобразить движение простых объектов?

17.  Как изобразить движение более сложных объектов?

18.  Как отследить нажатие клавиши на клавиатуре?

19.  Как вычислить масштаб построения графика функции? Обязаны ли быть одинаковыми коэффициенты масштабирования по осям абсцисс и ординат?

20.  Чем будет отличаться график, построенный с помощью функции Draw_Pixel() от графика, построенного с помощью Draw_Line()?

Постановка задачи

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

Варианты заданий

Вариант № 1

1.Построить график функции  при -2p£f£-p.

2.Написать программу движения окружности в прямоугольнике. Движение происходит под некоторым углом с «отражением от стенки».

Вариант № 2

1.       Построить график функции  при .

2.       Написать программу движения окружности в равнобедренном прямоугольном треугольнике, катеты которого параллельны границам экрана. Движение происходит под некоторым углом с «отражением от стенки».

Вариант № 3

1.       Построить график функции  при -15£x£10.

2.       Написать программу увеличения и уменьшения снежинки при достижении некоторого минимального или максимального размеров соответственно.

Вариант № 4

1.       Построить график функции  при хÎ[-5, 3].

 

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

Вариант № 5

1.                Построить график функции  при .

2.       Написать программу движения снежинки по спирали Архимеда. Начальное положение снежинки на спирали и направление движения выбирается случайно.

Вариант № 6

1.       Построить график функции  при -0,5£x£1.

2.       Написать программу, изображающую на экране полет бабочки. Движение должно быть хаотичным.

Вариант № 7

1.       Построить график функции , .

2.       Изобразить на экране, как футболист забивает мяч в ворота.

Вариант № 8

1.       Построить график функции  при .

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

Вариант № 9

1.       Построить график функции  при xÎ[-3, 10].

 

2.       Написать программу увеличения и уменьшения трех предметов (окружность, квадрат и заполненный прямоугольник) по нажатию на клавиши 1, 2, 3, 4, 5 и 6 соответственно.

Вариант № 10

1.       Построить график функции  при .

2.       Изобразить на экране катящийся велосипед. Количество спиц в каждом колесе взять равным 12.

Вариант № 11

1.       Построить график функции  при xÎ[0, 2p].

2.       Изобразить на экране человечка, который делает два упражнения: руками, при нажатии клавиши с буквой <Р> и ногами при нажатии клавиши с буквой <Н>.

Вариант № 12

1.       Построить график функции  при .

2.       Изобразить на экране движение корабля по морю с помощью клавиш управления курсором: при нажатии на клавишу <à> - начинается движение вправо, при нажатии на клавишу <ß> начинается движение влево, при нажатии на клавишу <â > корабль останавливается. Начало движения сопровождается подъемом флага на мачте с одновременным спуском флага на корме, окончание  – спуском флага на мачте и одновременным подъемом флага на корме. Флаг на корме и на мачте один и тот же.

Вариант № 13

1.       Построить график функции  при .

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

Вариант № 14

1.       Построить график функции  при .

2.       Вверху экрана нарисовать яблоки разного размера и цвета. Внизу экрана бежит ежик. При нажатии клавиши <ENTER> яблоки начинают падать. Если одно из них попадает на ежика, он останавливается.

Вариант № 15

1.       Построить график функции  при .

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

Вариант № 16

1.       Построить график функции  при .

2.       Написать программу движения разноцветных снежинок (не менее 10) слева направо. Начальное положение снежинок, их цвет и скорость движения задаются случайными числами.

Вариант № 17

1.       Построить график функции y=lg|x2-3| при .

2.       Написать программу, изображающую на экране деревянную доску, в которую молотком забивается гвоздь (вид сбоку). Удары по гвоздю осуществляются при нажатии клавиши <ENTER>.

Вариант № 18

1.       Построить график функции  при xÎ[-1, 5].

2.       Перемещением шарика нарисовать n-конечную звезду, по возвращении в исходную точку шарик изменяет цвет. Значение n, не превышающее 12, вводится с клавиатуры.

Вариант № 19

1.       Построить график функции при xÎ[-7, 0].

2.       Изобразить салют. Ракета взлетает и рассыпается яркими искрами.

Вариант № 20

1.       Построить график функции  при xÎ[5, 15].

2.       Изобразить на экране движение и разгрузку самосвала.

Лабораторная работа № 3
Классы: основные понятия и определения

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

Теоретические сведения

Данный раздел содержит краткий обзор сведений, необходимых для начала работы в стиле объектно-ориентированного программирования. Поэтому после ознакомления с ним рекомендуется обратиться к учебникам [3] или [4].

Классы и объекты

Класс – это производный структурированный тип, введенный программистом на основе существующих типов. Механизм классов позволяет создавать типы в соответствии с принципами абстракции данных, т.е. класс задает некоторую структурированную совокупность типизированных данных и позволяет определить набор операций над этими данными.

Простейшим образом класс можно определить с помощью конструкции:

сlass имя_класса
{
список_компонентов;
};

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

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

Данные, содержащиеся внутри класса, называются полями класса, данными-членами или компонентными данными. Принадлежащие классу функции называются методами класса, функциями-членами или компонентными функциями.

Переменная пользовательского типа, описанного классом, называется объектом или экземпляром класса. Для объявления объекта класса используется конструкция:

имя_класса имя_объекта;

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

имя_объекта.имя_поля /* для поля данных */

имя_объекта.имя_метода() /*для обращения к функции-члену класса */

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

указатель_на_объект_класса -> имя_элемента

указатель_на_объект_класса -> обращение_к_функции().

Спецификаторы доступа

Для управления видимостью компонент в определении класса можно использовать спецификаторы доступа. Определены следующие спецификаторы доступа:

1)       private – собственный, частный, недоступны для внешних обращений;

2)       protected – защищенный, используется при построении иерархии классов;

3)       public – общедоступный.

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

Компонентные данные

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

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

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

или protected, то обратиться к нему «извне» невозможно. Для обращения к открытым (public) полям используется уточненное имя: имя_объекта.имя_поля или указатель_на_объект->имя_поля.

Компонентные функции

Компонентная функция может быть определена как в теле класса, так и вне его. В последнем случае в описание класса необходимо поместить ее прототип.

При определении компонентной функции вне класса программист должен сообщить компилятору, к какому именно классу она относится. Для этого используется бинарная операция «::», называемая операцией глобального разрешения или операцией расширения области видимости. Формат ее использования:

тип имя_класса :: имя_функции ( список_формальных_параметров )

{

тело_функции;

}

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

Пример класса:

class point
{
      int x, y; /* поля данных; по умолчанию private */
   public:
      void set_x (int a) { x=a; } /* установка значения поля x */
      void set_y (int a) { y=a; } /* установка значения поля y */
      int get_x(); /* прототип функции получения значения поля x */
      int get_y(); /* прототип функции получения значения поля y */
};
int point::get_x() /*внешнее определение функций */
{ return x; }
int point::get_y()
{ return y; }

Конструкторы и деструктор

В классе всегда явно или неявно присутствуют специальные методы, которые называются конструктором и деструктором. Конструктор выполняется автоматически в момент создания объекта (при определении переменной-объекта или выделении памяти под объект с помощью оператора new), деструктор – при его уничтожении (завершении времени жизни переменной или освобождении памяти с помощью оператора delete).

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

имя_класса (список_формальных_параметров)
{
операторы тела конструктора;
}

При отсутствии параметров скобки остаются пустыми.

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

Для вышеобъявленного класса point конструктор с параметрами можно объявить так:

point (int a=0, int b=0) /* по умолчанию значения параметров равны 0 */

{

      x = a;

      y = b;

}

При объявлении объектов данного класса:

point A, B(7,6);

поля объекта А получат значения по умолчанию (нули), а поля объекта В станут равными значениям параметров (x=7, y=6).

Конструктор копирования – это специальный вид конструктора, получающий в качестве единственного параметра ссылку на объект этого же класса. Общий вид:  T :: T(T&) {…}, где Т – имя класса.

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

•          при описании нового объекта с инициализацией другим объектом;

•          при передаче объекта в функцию по значению;

•          при возврате объекта из функции.

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

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

~имя_класса ()

{

операторы_тела_деструктора;

}

Название деструктора всегда начинается со специального символа «тильда», за которым без пробелов или других разделительных знаков помещается имя класса. У деструктора не может быть параметров (даже типа void), и он не имеет возвращаемого значения. Вызов деструктора выполняется неявно, автоматически, как только объект класса уничтожается.

Перегрузка операций

Перегрузка операций дает возможность использовать стандартные знаки операций для выполнения действий над объектами создаваемого класса, например, описывая класс «Длинное целое» логично знаком «+» обозначить операцию сложения. Приоритет операций при перегрузке не изменяется.

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

int operator== (point &p)

{

   return x==p.x && y==p.y;

}

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

Указатель this

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

имя_класса * const this = адрес_обрабатываемого_объекта.

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

Указатель this удобно использовать в тех случаях, когда функция должна вернуть значение объекта, для которого она вызвана. Например, операция увеличения полей объекта класса point в m раз:

point operator*= (unsigned m)
      {
          x *= m;
          y *= m;
          return *this;
      }

Дружественные функции

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

Например, для вывода значений полей объекта рассматриваемого класса point можно перегрузить оператор сдвига в потоке вывода ostream:

/* объявление функции в теле класса point*/
friend ostream &operator << (ostream &stream, point &p); 
/* определение функции вне всех классов */
ostream &operator << (ostream &stream, point &p)
{
    stream << "(" << p.x << ", " << p.y <<")";
    return stream;
}

Контрольные вопросы

1.       Что такое структура и структурный тип?

2.       Как осуществляется доступ к элементам структур?

3.       Что такое класс? Что такое объект?

4.       Что такое инкапсуляция?

5.       Что такое абстрактный тип данных?

6.       Какие потоки отвечают за ввод-вывод в языке С++?

7.       Что такое методы и поля класса?

8.       Какой объем памяти выделяется под объект?

9.       Как осуществляется доступ к методам и полям объекта?

10.  Что такое конструктор? Что он возвращает?

11.  Сколько конструкторов может быть у класса?

12.  Что такое конструктор копирования?

13.  Чем конструктор копирования отличается от операции присваивания?

14.  Что такое деструктор? Что он возвращает?

15.  Сколько деструкторов может быть у класса?

16.  Как вызвать конструктор? Как вызвать деструктор?

17.  Какие методы создаются для любого класса автоматически?

18.  Можно ли описать класс, не содержащий полей или методов?

19.  Какие спецификаторы доступа к полям и методам класса Вы знаете?

20.  Внутреннее и внешнее описания компонентных функций.

21.  Что такое указатель this? Для чего он используется и когда?

22.  Какие операции можно и нельзя перегружать?

23.  Чем перегрузка операции отличается от других методов класса?

24.  Каковы особенности перегрузки унарных и бинарных операций?

25.  Что такое дружественная функция? Как и когда используются дружественные функции?

Постановка задачи

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

Варианты заданий

Вариант 1.                        

Класс «Комплексное число». Поля класса: действительная и мнимая части.  Методы: вычисление модуля, перегрузка операций сложения «+» и умножения «*» двух комплексных чисел и комплексного числа с действительным, уменьшения значения на величину другого комплексного числа «-=».

Вариант 2.                        

Класс «Комплексное число». Поля класса: действительная и мнимая части.  Методы: возведение комплексного числа в целую степень, перегрузка операций сравнения («==», «!=»)  двух комплексных чисел и увеличения значения «+=» на величину другого комплексного или вещественного числа.

Вариант 3.                        

Класс «Комплексное число». Поля класса: действительная и мнимая части.  Методы: перегрузка операций деления «/» комплексных чисел,  отношения («>=», «<=») для сравнения двух комплексных чисел по модулю, получения сопряженного комплексного числа «!».

Вариант 4.                        

Класс «Рациональное число» (РЧ). Поля: числитель, знаменатель. Методы: перегрузка операции приведения типа к double, операций сложения «+» двух РЧ и РЧ с целым числом, расширенной операции присваивания «+=» с целочисленным операндом и РЧ.

Вариант 5.                        

Класс «Рациональное число» (РЧ). Поля: числитель, знаменатель. Методы: перегрузка операции приведения типа к float, операций умножения «*» двух РЧ и РЧ с целым числом, сравнения на равенство «==» двух РЧ и РЧ с целым числом.

Вариант 6.                        

Класс «Рациональное число» (РЧ). Поля: числитель, знаменатель. Методы: перегрузка операции приведения типа к float, операций деления «/» РЧ на РЧ и на целое число, сравнения («>») двух РЧ и РЧ с целым числом.

Вариант 7.                        

Класс «Рациональное число» (РЧ). Поля: числитель, знаменатель. Методы: вычисление модуля, перегрузка операции приведения типа к double, операций вычитания «-» из РЧ целого числа и РЧ, операции получения обратного РЧ «!».

Вариант 8.                        

Класс «Треугольник». Поля: длины сторон. Методы: вычисление площади, радиусов вписанной и описанной окружностей, определение типа (остроугольный, прямоугольный, тупоугольный), перегрузка операции сравнения на равенство «==».

Вариант 9.                        

Класс «Треугольник». Поля: координаты вершин. Методы: вычисление периметра, площади, наименьшего угла, перегрузка операции сравнения на равенство «==» двух треугольников и расширенной операции присваивания «*=» с числовым правым операндом для обозначения операции масштабирования треугольника.

Вариант 10.                   

Класс «Треугольник». Поля: длины двух сторон и величина угла между ними. Методы: вычисление периметра, площади, высоты, проведенной из заданного угла, определение типа (остроугольный, прямоугольный, тупоугольный), перегрузка операции сравнения на равенство «==» двух треугольников.

Вариант 11.                   

Класс «Треугольник». Поля: длины сторон. Методы: вычисление всех углов, медианы, проведенной к большей стороне, радиуса вписанной окружности, перегрузка операции «^» для обозначения операции определения подобия двух треугольников.

Вариант 12.                   

Класс «Треугольник». Поля: длины двух сторон и величина угла между ними. Методы: вычисление длин всех сторон, высоты, проведенной из большего угла, перегрузка операций «==» как сравнение на равенство и «^» как определение подобия треугольников.

Вариант 13.                   

Класс «Треугольник». Поля: длина одной из сторон и величины прилежащих к ней углов. Методы: вычисление периметра, медианы, проведенной к заданной стороне, радиуса описанной окружности, перегрузка операции сравнения на равенство «==» двух треугольников.

Вариант 14.                   

Класс «Треугольник». Поля: длина одной из сторон и величины прилежащих к ней углов. Методы: вычисление длин всех сторон, биссектрис всех углов, перегрузка операции «==» в качестве сравнения на равенство площадей двух треугольников.

Вариант 15.                   

Класс «Треугольник». Поля: координаты вершин. Методы: вычисление длин всех сторон, медианы, проведенной к наименьшей стороне, перегрузка операций «+» как вычисление суммы площадей и «^» как определение подобия двух треугольников.

Вариант 16.                   

Класс «Многочлен одной переменной». Поля: степень многочлена, массив коэффициентов. Методы: вычисление значения, дифференцирование, перегрузка операций сложения «+» и сравнения на равенство «==» двух многочленов.

Вариант 17.                   

Класс «Связанный вектор». Поля: координаты начала и конца вектора. Методы: вычисление длины вектора, угла между двумя векторами, перегрузка операции «||» как определение коллинеарности двух векторов, операции сложения «+» двух векторов и расширенной операции присваивания «+=».

Вариант 18.                   

Класс «Связанный вектор». Поля: координаты начала и конца вектора. Методы: вычисление длины вектора, угла между двумя векторами, перегрузка унарной операции «-» для изменения направления вектора и операции «*» с числовым правым операндом для умножения вектора на число и с правым операндом-вектором для вычисления скалярного произведения двух векторов.

Вариант 19.                   

Класс «Рациональное число» (РЧ). Поля: числитель, знаменатель. Методы: возведение РЧ в целую степень, перегрузка расширенной операции присваивания «-=» с РЧ и целым числом, сравнения («<») двух РЧ и РЧ с целым числом.

Вариант 20.                   

Класс «Рациональное число» (РЧ). Поля: числитель, знаменатель. Методы: перегрузка операции «^» как возведение вещественного числа в степень, заданную РЧ, операций сравнения («<=», «>=») двух РЧ и РЧ с целым числом.

Лабораторная работа № 4
Линейные структуры данных

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

Теоретические сведения

Абстрактные типы данных и классы

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

Комбинация семантической и синтаксической характеристик данных называют типом данных. Существует две группы типов данных – скалярные и структурированные.

Данные скалярных типов рассматриваются как целые простые неделимые объекты: int, float, double, char и т.д.

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

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

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

АТД имя_типа

Данные

Описание структуры данных

Операции

Инициализатор

Начальные значения:

Данные, используемые для инициализации объекта

Процесс:

Инициализация объекта

Операция1

Вход:

Входные данные

Предусловия:

Состояние системы перед выполнением операции

Процесс:

Действия, выполняемые с данными

Выход:

Данные, возвращаемые клиенту

Постусловия:

Состояние системы после выполнения операций

Операция2

Операцияn

Конец АТД

Для описания АТД на языке С++ лучше всего использовать классы.

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

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

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

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

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

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

2)  поля связок, в котором содержатся один или несколько указателей, связывающих данный элемент с другими элементами структуры.

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

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

Вместе с тем связное представление не лишено и недостатков:

•     работа с указателями требует, как правило, более высокой квалификации от программиста;

•     на поля связок расходуется дополнительная память;

•     доступ к элементам связной структуры менее эффективен по времени в сравнении с вектором.

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

Список

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

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


Структура однонаправленного линейного списка содержит (рисунок 1): поле INFO – информационное поле, содержащее данные; NEXT – указатель на следующий элемент списка; BEGIN – указатель начала списка или «голова» списка; END – указатель окончания списка или «хвост» списка. В поле указателя последнего элемента списка находится специальный признак NULL, свидетельствующий о конце списка. Направление связей элементов в списке изображаются с помощью стрелок.

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

Ниже рассматриваются некоторые простые операции над линейными списками. К таким операциям относят: создание и уничтожение списка, включение и исключение элемента из списка, поиск элемента в списке.

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

Для включения элемента в список необходимо проделать следующие действия, показанные на рисунке 2:

new_p->next = tek_p->next; /* присоединяем «хвост» списка к новому элементу */

tek_p->next = new_p; /* присоединяем к «голове» списка новый элемент */


Для исключения элемента из списка необходимо проделать следующие действия, показанные на рисунке 3:

del_p = tek_p->next; /* запоминаем адрес удаляемого элемента */

tek_p->next = del_p->next; /* перенастраиваем связи элементов */

delete del_p;  /* удаляем элемент */

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


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

 

Структура двунаправленного линейного списка содержит (рисунок 4): поле INFO – информационное поле, содержащее данные, NEXT – указатель на следующий элемент списка, поле PREV – указатель на предыдущий элемент списка, BEGIN – указатель начала списка или «голова» списка, END – указатель окончания списка или «хвост» списка. В крайних элементах соответствующие указатели содержат специальный признак NULL. Наличие двух указателей в каждом элементе усложняет список и приводит к дополнительным затратам памяти, но в то же время обеспечивает более эффективное выполнение некоторых операций над списком.

Разновидностью рассмотренных видов линейных списков является кольцевой список, который может быть организован на основе как односвязного, так и двухсвязного списков. При этом в односвязном списке указатель последнего элемента должен указывать на первый элемент (рисунок 5).

 


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

Стек

Стек – это динамически изменяемый упорядоченный набор элементов, включение и исключение элементов из которого выполняются с одного и того же конца, называемого вершиной стека. Функционирование стека происходит по принципу LIFO (Last In First Out – последним пришел – первым исключается).

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

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

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

При реализации стека на основе связного списка и включение, и исключение элементов всегда производятся с начала списка. Дескриптор списка будет содержать один указатель – top, который всегда указывает на последний записанный в стек элемент. В исходном состоянии (при пустом стеке) значение указателя top равно NULL. При включении элемента для него выделяется память, а при исключении – освобождается. Перед включением элемента проверяется доступный объем памяти, и если он не позволяет выделить память для нового элемента, стек считается заполненным. При очистке стека последовательно уничтожаются его элементы.

Очередь

Очередью называется такой последовательный список с переменной длиной, в котором включение элементов выполняется только с одной стороны списка (хвоста очереди), а исключение – с другой стороны (из головы очереди). Часто для обозначения очереди используется аббревиатура FIFO (First In First Out – первым пришел – первым исключается).

Операции над очередью те же, что и над стеком: основные – включение и исключение, вспомогательные – неразрушающее чтение, очистка, проверка на пустоту, проверка на полноту.

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

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

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

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

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

Дек

Дек (англ. dequedouble ended queue, т.е. очередь с двумя концами) – это такой последовательный список, в котором как включение, так и исключение элементов может осуществляться с любого из двух концов списка. Однако, применительно к деку целесообразно говорить не о начале и конце, а о левом и правом концах списка. Операции над деком: включение элемента справа, включение элемента слева, исключение элемента справа, исключение элемента слева.

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

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

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

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

Контрольные вопросы

1.       Что такое абстрактный тип данных?

2.       Что такое список?

3.       Что такое связанный список?

4.       Какие виды списков Вы знаете?

5.       Какие методы применимы к спискам?

6.       Какие методы реализации списков Вы знаете?

7.       Что такое стек?

8.       Какие операции применимы к стекам?

9.       Каков механизм заполнения стека? Что такое «дно» стека?

10.  Что такое очередь?

11.  Какие операции применимы к очередям?

12.  Каков механизм заполнения очереди?

13.  Что такое дек? Какие операции применимы к декам?

14.  В чем различие между конкатенацией двух стеков и конкатенацией двух очередей?

15.  Что такое дескриптор списка?

16.  Как и когда используется нулевой указатель NULL?

17.  Какая структура данных описывается аббревиатурой LIFO?

18.  Какая структура данных описывается аббревиатурой FIFO?

19.  Что такое «структура данных» и «структура хранения»?

20.  От чего зависит выбор структуры хранения для реализации структуры данных?

Постановка задачи

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

Задания могут быть выполнены на трех уровнях сложности.

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

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

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

Варианты реализаций

Реализация 1.       Стек в массиве. Заполнение стека должно производиться с начала массива. Методы класса: добавление элемента в стек, удаление элемента из стека, получение значения с вершины стека, проверка заполнения стека, проверка пустоты стека.

Реализация 2.       Разработайте класс, реализующий стек с помощью указателей. Методы класса: добавление элемента в стек, удаление элемента из стека, получение значения с вершины стека, проверка заполнения стека, проверка пустоты стека, очистка стека.

Реализация 3.       Разработайте класс, реализующий очередь в «циклическом» массиве. Поля класса: массив, индексы первого и последнего элементов в очереди. Методы класса: добавление элемента в очередь, удаление элемента из очереди, получение значения из очереди, проверка заполнения очереди, проверка пустоты очереди.

Реализация 4.       Разработайте класс, реализующий очередь в «циклическом» массиве. Поля класса: массив, индекс первого элемента в очереди, количество элементов в очереди. Методы класса: добавление элемента в очередь, удаление элемента из очереди, получение значения из очереди, проверка заполнения очереди, проверка пустоты очереди.

Реализация 5.       Разработайте класс, реализующий очередь с помощью указателей. Методы класса: добавление элемента в очередь, удаление элемента из очереди, получение значения из очереди, проверка заполнения очереди, проверка пустоты очереди.

Варианты заданий

Вариант 1.                        

1. Поля данных: название игрушки, цена, количество, возрастные границы, например от 2 до 5. Вывести названия игрушек, которые подходят детям определенного возраста и стоят не больше определенной суммы. Получить сведения о самом дорогом конструкторе.

2. Напишите программу, которая считывает символьную строку, содержащую три набора скобок: круглые (), угловые <> и квадратные [],- и определяет, правильно ли расставлены в этой строке скобки. Указание: в стек заносить тип скобки (реализация 1).

Вариант 2.                        

1. Поля данных: фамилия, пол, вид спорта, год рождения, рост. Найти самого высокого спортсмена, занимающегося плаванием, среди мужчин. Вывести сведения о спортсменках, выступающих в юниорском разряде (14-17 лет).

2. Напишите программу, которая считывает символьную строку, содержащую три набора скобок: круглые (), угловые <> и квадратные [],- и определяет, правильно ли расставлены в этой строке скобки. Указание: в стек заносить тип скобки (реализация 2).

Вариант 3.                        

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

2. Многочлены вида , где  можно представить в виде очереди, где каждый элемент имеет два поля: одно – для коэффициента ci, второе – для показателя степени ei. Напишите программу сложения двух многочленов, представленных таким способом (реализация 4).

Вариант 4.

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

2.Фирма по хранению и сбыту товаров получает грузы по различным ценам, причем товары, полученные позднее, продаются в первую очередь. Напишите программу, считывающую записи о торговых операциях двух типов: операции по закупке и операции по продаже с 20%-ной надбавкой. Результаты о каждой из операций выводить на экран. Если на складе отсутствует требуемое в заказе число товара, то продайте все имеющееся, а затем напечатайте сообщение об отсутствии остальной части изделий на складе (реализация 1).

Вариант 5.                        

1. Поля данных: название животного, природная зона, затраты на корм за один день. Вывести количество животных определенной природной зоны, находящихся в зоопарке, и определить, сколько денег тратится на содержание определенного животного в месяц.

2.Фирма по хранению и сбыту товаров получает грузы по различным ценам, причем товары, полученные позднее, продаются в первую очередь. Напишите программу, считывающую записи о торговых операциях двух типов: операции по закупке и операции по продаже с 20%-ной надбавкой. Результаты о каждой из операций выводить на экран. Если на складе отсутствует требуемое в заказе число товара, то продайте все имеющееся, а затем напечатайте сообщение об отсутствии остальной части изделий на складе (реализация 2).

Вариант 6.                        

1. Поля данных: марка автомобиля, страна-производитель, год выпуска, объем двигателя, расход бензина на 100 км, цена, количество экземпляров. Скорректировать данные об определенном автомобиле при изменении на него цены. Вывести марку автомобиля с определенным объемом двигателя и наименьшим расходом бензина.

2.Фирма по хранению и сбыту товаров получает грузы по различным ценам, причем товары, полученные позднее, продаются в первую очередь. Напишите программу, считывающую записи о торговых операциях двух типов: операции по закупке и операции по продаже с 20%-ной надбавкой. Результаты о каждой из операций выводить на экран. Если на складе отсутствует требуемое в заказе число товара, то продайте все имеющееся, а затем напечатайте сообщение об отсутствии остальной части изделий на складе (реализация 4).

Вариант 7.                        

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

2.Фирма по хранению и сбыту товаров получает грузы по различным ценам, причем товары, полученные позднее, продаются в первую очередь. Напишите программу, считывающую записи о торговых операциях двух типов: операции по закупке и операции по продаже с 20%-ной надбавкой. Результаты о каждой из операций выводить на экран. Если на складе отсутствует требуемое в заказе число товара, то продайте все имеющееся, а затем напечатайте сообщение об отсутствии остальной части изделий на складе (реализация 5).

Вариант 8.                        

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

2.Многочлены вида , где  можно представить в виде очереди, где каждый элемент имеет два поля: одно – для коэффициента ci, второе – для показателя степени ei. Для описанного представления многочленов напишите программу их дифференцирования (реализация 5).

Вариант 9.                        

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

2.Многочлены вида , где  можно представить в виде очереди, где каждый элемент имеет два поля: одно – для коэффициента ci, второе – для показателя степени ei. Для описанного представления многочленов напишите программу их дифференцирования (реализация 4).

Вариант 10.                   

1. Поля данных: фамилия автора, название, издательство, год издания, тематика книги. Вывести названия книг определенного автора, изданных после 2000 года. Определить долю книг в библиотеке по теме «Программирование» от общего количества экземпляров.

2.Многочлены вида , где  можно представить в виде очереди, где каждый элемент имеет два поля: одно – для коэффициента ci, второе – для показателя степени ei. Напишите программу сложения многочленов, представленных описанным образом (реализация 5).

Вариант 11.                   

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

2.Напишите программу, которая считывает строку текста, помещая каждый непустой символ и в очередь, и в стек, и проверяет, является ли данная строка палиндромом (реализации 1 и 3).

Вариант 12.                   

1. Поля данных: наименование товара, страна-импортер и объем поставляемой партии в штуках. Вывести страны, в которые экспортируется определенный товар и общий объем его экспорта.

2.Многочлены вида , где  можно представить в виде очереди, где каждый элемент имеет два поля: одно – для коэффициента ci, второе – для показателя степени ei. Для описанного представления многочленов написать программу вычисления значения р(х) при заданном х (реализация 3).

Вариант 13.                   

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

2.Многочлены вида , где  можно представить в виде очереди, где каждый элемент имеет два поля: одно – для коэффициента ci, второе – для показателя степени ei. Напишите программу сложения и умножения многочленов, представленных описанным образом (реализация 4).

Вариант 14.                   

1. Поля данных: номер счета, паспортные данные, категория вклада, текущая сумма вклада, дата последней операции. Зафиксировать (произвести изменения) операции приема и выдачи любой суммы. Вывести наибольшую сумму вклада в категории «срочный».

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

Вариант 15.                   

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

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

Вариант 16.                   

1. Поля данных: название, местоположение, тип постройки, архитектор, год постройки. Вывести сведения о сооружениях определенного типа, например, «собор», построенных до 18 века. Найти самый старый архитектурный памятник.

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

Вариант 17.                   

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

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

Вариант 18.                   

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

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

Вариант 19.                   

1. Поля данных: название спектакля, название театра, дата, количество билетов, цена. Произвести корректировку данных при продаже билетов, исходные данные – название спектакля, название театра, дата и количество проданных билетов. Вывести названия спектаклей, на которые есть билеты на указанную дату.

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

Вариант 20.                   

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

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

Лабораторная работа № 5
Шаблоны

Цель работы – научиться создавать шаблоны функций и шаблоны классов для работы с любыми типами данных без переписывания кода программы.

Теоретические сведения

Шаблоны позволяют использовать одни и те же функции или классы для обработки данных разных типов. Концепция шаблонов может быть использована в двух видах: по отношению к функциям и по отношению к классам.

Шаблон функции

Шаблон семейства функций (или просто шаблон функции) определяет потенциально неограниченное множество родственных функций. Он имеет следующий вид:

template <список_параметров_шаблона>

определение функции {}

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

template  /* шаблон функции с одним параметром */
T summa (T a, T b) /* параметры функции одного типа, заданного параметром
шаблона, тот же тип будет и у результата */
{
    return a+b;
}

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

cout << summa (3, 5) <<endl;
cout << summa (3.1, 5.) << endl;

будет сгенерировано две шаблонные функции: одна для вычисления суммы целых чисел типа int, другая для вычисления суммы двух вещественных значений типа double. При попытке вызова функции с разнотипными параметрами

cout << summa (3, 5.) << endl; /* первый параметр int, второй double */
произойдет ошибка компиляции, потому что тип параметра шаблона при таком вызове определить невозможно.

Количество параметров шаблона может быть любым, они перечисляются через запятую в угловых скобках после служебного слова template. Например, требуется найти индекс первого минимального элемента в одномерном массиве. Понятно, что тип элементов массива может быть любым, значит, он будет определять параметр шаблона. Но количество элементов в массиве тоже не обязательно будет типа int, оно может быть задано и значением типа char, и значением типа unsigned long. Следовательно, нужен еще один параметр шаблона:

template  /* Tdata – тип элементов массива,
Tnumber – тип индекса */
Tnumber pos_min (Tdata mas[], Tnumber n)
{
    Tnumber i, p = 0;
    for (i=1; i<n; i++)
       if (mas[i] < mas[p]) p = i;
    return p;
}

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

Шаблон класса

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

Шаблон семейства классов (просто шаблон класса или параметризованный класс) определяется аналогично шаблону функции:

template <список_параметров_шаблона>

определение_класса{};

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

template  class Array
{
    private:
      Data *array;
      int size;
    public:
      Array(int s = 10)
      {
        size = s;
        array = new Data [s];
      }
     ~Array()
     {
        if (array)
           delete [] array;
     }
     Data& operator[] (int index)
     {
        return array[index];
     }
};

Создав параметризованный класс, можно создать конкретную реализацию этого класса, используя общую форму:

имя_класса <перечисление_параметров_шаблона> имя_объекта;

Например, выражение

Array <int> A(100);

создаст объект А как массив из 100 целых чисел типа int, а выражение

Array <double> B;

создаст объект В как массив из 10 вещественных чисел типа double. Создание объекта типа Array означает не только резервирование памяти для данных, но и генерацию набора методов, оперирующих данными указанного параметром типа. Имя типа переменной-объекта состоит из имени класса и параметра шаблона: переменная А имеет тип Array<int>, а переменная В – Array<double>.

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

template <список_параметров_шаблона>
тип_результата имя_класса <перечисление_параметров_шаблона> ::
имя_функции(список_формальных_параметров)

{

тело_функции;

}

Например, рассмотренный выше шаблон класса Array может быть переписан так:

template  class Array /* объявление шаблона класса */
{
    private:
        Data *array;
        int size;
    public:
        Array(int s = 0);
        ~Array();
        Data& operator[] (int index);
};
/* внешнее определение методов */
template  
Array :: Array(int s)
{
    size = s;
    array = new Data [s];
}
    template  
    Array :: ~Array()
{
    if (array)
        delete [] array;
}
template  
Data& Array :: operator[] (int index)
{
    return array[index];
}

Контрольные вопросы

1.       Зачем нужны шаблоны?

2.       Что такое шаблон функции?

3.       Как используются шаблоны функций?

4.       Что такое шаблонная функция?

5.       Когда генерируется код шаблонной функции?

6.       Что такое параметр шаблона? Чем он отличается от параметра функции?

7.       Как количество параметров шаблона влияет на количество экземпляров шаблонной функции?

8.       Как определить, можно ли создать шаблонную функцию с каким-то типом данных?

9.       Что такое шаблон класса?

10.  Как используются шаблоны классов?

11.  Что такое шаблонный класс?

12.  Чем шаблоны классов отличаются от шаблонов функций?

13.  Как определяются методы вне спецификации шаблона класса?

Постановка задачи

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

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

1)       Низкий. Указанный АТД можно реализовать любым удобным способом.

2)       Средний. Заданный АТД реализовать с помощью указанной структуры хранения.

3)       Повышенный. Создать требуемый АТД с помощью двух структур хранения: векторной и списковой,– реализацию оформить в виде шаблонов классов с единым интерфейсом.

Варианты заданий

Вариант 1.        

Типы аргументов int и char.

1.       Сортировка элементов одномерного массива по неубыванию.

2.       АТД Дек. Структура хранения – связанный список.

Вариант 2.        

Типы аргументов int и float.

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

2.       АТД Очередь. Структура хранения – циклический массив.

Вариант 3.        

Типы аргументов float и char.

1.       Сортировка элементов одномерного массива по невозрастанию.

2.       АТД Стек. Структура хранения – связанный список.

Вариант 4.        

Типы аргументов int и double.

1.       Вычисление суммы положительных элементов массива.

2.       АТД Дек с ограниченным входом. Структура хранения – связанный список.

Вариант 5.        

Типы аргументов double и char.

1.       Поиск наименьшего неотрицательного элемента массива.

2.       АТД Дек с ограниченным выходом. Структура хранения – циклический массив.

Вариант 6.        

Типы аргументов int и char.

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

2.       АТД Стек. Структура хранения – связанный список.

Вариант 7.        

Типы аргументов int и float.

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

2.       АТД Дек с ограниченным входом. Структура хранения векторная.

Вариант 8.        

Типы аргументов float

и char.

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

2.       АТД Стек. Структура хранения векторная.

Вариант 9.        

Типы аргументов int и double.

1.       Поиск максимального по модулю элемента массива.

2.       АТД Дек. Структура хранения – связанный список.

Вариант 10.   

Типы аргументов double и char.

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

2.       АТД Стек. Структура хранения – вектор.

Вариант 11.   

Типы аргументов int и char.

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

2.       АТД Дек с ограниченным выходом. Структура хранения – связанный список.

Вариант 12.   

Типы аргументов float и unsigned char.

1.       Нормирование элементов массива вычитанием из каждого из них минимального элемента этого массива.

2.       АТД Стек. Структура хранения – связанный список.

Вариант 13.   

Типы аргументов unsigned int и float.

1.       Перестановка элементов массива в обратном порядке.

2.       АТД Дек с ограниченным входом. Структура хранения – связанный список.

Вариант 14.   

Типы аргументов unsigned int и unsigned char.

1.       Перестановка элементов первой половины массива в обратном порядке. При нечетном количестве элементов центральный элемент массива не перемещать.

2.       АТД Очередь. Структура хранения – связанный список.

Вариант 15.   

Типы аргументов long и unsigned char.

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

2.       АТД Очередь. Структура хранения – циклический массив.

Вариант 16.   

Типы аргументов int и double.

1.       Перестановка элементов в массиве следующим образом: сначала элементы с нечетными значениями в том же порядке следования, затем с четными в обратном порядке.

2.       АТД Дек с ограниченным выходом. Структура хранения – связанный список.

Вариант 17.   

Типы аргументов double и char.

1.       Поиск минимального положительного элемента массива среди элементов с нечетными индексами.

2.       АТД Очередь. Структура хранения векторная.

Вариант 18.   

Типы аргументов unsigned int и long.

1.       Перестановка элементов второй половины массива в обратном порядке. При нечетном количестве элементов центральный элемент массива не перемещать.

2.       АТД Очередь. Структура хранения – связанный список.

Вариант 19.   

Типы аргументов int и unsigned long.

1.       Поиск минимального нечетного элемента массива.

2.       АТД Дек. Структура хранения – вектор.

Вариант 20.   

Типы аргументов float и char.

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

2.       АТД Стек. Структура хранения – связанный список.

Лабораторная работа № 6
Наследование

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

Теоретические сведения

Наследование классов

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

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

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

class A
{ 
   public:
     A(); //конструктор класса А
     ~A(); //деструктор класса А
     MethodA(); //функция-член класса А
};
class B: public A //все компоненты класса А наследуются классом В
{
   public:
     B(); //собственный конструктор класса В
     ~B(); //собственный деструктор класса В
     …  //собственные методы класса В
}; 

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

Возможность использования объектами производного класса компонентов базового класса определяется правами доступа: наследник получает доступ только к компонентам, объявленным со спецификаторами protected и public.

Помимо простого наследования, существует еще и множественное наследование. Подробнее об особенностях наследования и правах доступа можно прочитать в [3], [4] и других учебниках по С++.

Виртуальные функции

В базовом и производном классах могут присутствовать методы с одинаковыми именами. В этом случае собственный метод производного класса «перекрывает» видимость метода базового класса, и при использовании объекта производного класса будет вызван метод именно этого класса. Однако, когда используется несколько объектов классов, производных от одного базового, в этом случае придется писать последовательность однотипных вызовов.

Для упрощения подобных ситуаций существует возможность обращаться к объектам производных классов через указатели на базовый класс. Чтобы для каждого объекта вызывался соответствующий метод, используется механизм виртуальных функций. Классы, включающие в себя виртуальные функции, называются полиморфными, а сама возможность работать с группой разнородных объектов одинаковым образом, не задумываясь о различиях в реализации, называется полиморфизмом. сущности, для полиморфизма как такового виртуальные функции и не нужны. Зато они нужны для полиморфизма динамического (времени выполнения) в противовес статическому (времени компиляции),  примерами которого являются перегрузка функций и использование шаблонов функций.)

Виртуальная функция – это функция класса, которая может быть переопределена в классах-наследниках так, что конкретная ее реализация для вызова будет определяться во время исполнения. Для объявления функции виртуальной в заголовок функции в базовом классе нужно добавить служебное слово virtual:

virtual тип_результата имя_функции (спецификация_параметров);

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

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

virtual тип_результата имя_функции (спецификация_параметров) = 0;

В этой записи конструкция « = 0» называется чистым спецификатором. Такая функция ничего не делает и недоступна для вызовов. Ее назначение – служить основой для подменяющих ее функций в производных классах.

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

Статические члены класса

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

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

<p>Статический элемент данных разделяется всеми представителями данного класса. То есть существует только один экземпляр переменной независимо от числа созданных представителей. Память под статический элемент выделяется, даже если не существует никаких представителей класса. Определение статических полей класса происходит не так, как для обычных полей. Обычные поля объявляются (компилятору сообщается имя и тип поля) и определяются (компилятор выделяет память для хранения поля) при помощи одного оператора. Для статических полей эти два действия выполняются двумя разными операторами: объявление поля находится внутри определения класса, а определение, как правило, располагается вне класса и зачастую представляет собой определение глобальной переменной. Например, подсчитаем число созданных представителей класса:

class A
{
private:
   static int iCount; // Объявление статического элемента данных
public:
   A() {iCount++;};
   int GetCount() {return iCount;};
};
int A::iCount = 0; //Определение статического элемента данных
int main()
{
  A a1, a2, a3; //создаем три объекта 
  cout << “Число объектов ” << a1.GetCount() << endl;
  cout << “Число объектов ” << a2.GetCount() << endl;
  cout << “Число объектов ” << a3.GetCount() << endl;
  return 0;
}

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

Число объектов 3
Число объектов 3
Число объектов 3

Помимо статических полей, класс может иметь и статические функции. О том, что это такое и как с ними работать, можно прочитать в [3], [4] и других учебниках по С++.

Контрольные вопросы

1.       Что такое наследование?

2.       Что такое базовый класс?

3.       Что такое производный класс?

4.       Каков порядок выполнения конструкторов и деструкторов при наследовании?

5.       Что такое простое наследование?

6.       Что такое множественное наследование?

7.       Как влияют на наследование права доступа?

8.       Что такое виртуальные функции?

9.       Что такое полиморфизм?

10.  Что такое раннее и позднее связывание?

11.  Что такое чистая виртуальная функция?

12.  Что такое абстрактный класс?

13.  Что такое статические поля данных?

14.  Что можно присвоить указателю на базовый класс?

15.  Что можно присвоить указателю на производный класс?

16.  Как обращаться к методам базового класса из производного класса?

17.  Как применять методы базового класса к объектам производных классов?

18.  Верно ли утверждение, что абстрактный класс служит для описания абстрактного типа данных?

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

20.  Может ли производный класс быть абстрактным?

Постановка задачи

1. Описать три класса: базовый класс «Строка» и производные от него класс «Строка-идентификатор» и класс, заданный индивидуальным вариантом. Обязательные для всех классов методы: конструктор без параметров, конструктор, принимающий в качестве параметра Си-строку, конструктор копирования, деструктор, перегрузка операции присваивания «=». Во всех методах всех классов предусмотреть печать сообщения, содержащего имя метода. Для конструкторов копирования каждого класса дополнительно предусмотреть диагностическую печать количества его вызовов, рекомендуется использовать статические члены класса.

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

Строки класса «Строка-идентификатор» строятся по правилам записи идентификаторов в Си, и могут включать в себя только те символы, которые могут входить в состав Си-идентификаторов. Если исходные данные противоречат правилам записи идентификатора, то создается пустая «Строка-идентификатор».

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

2. Написать тестовую программу, которая должна:

·     динамически выделить память под массив указателей на базовый класс (4-6 шт.);

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

·     для созданных экземпляров производных классов выполнить проверку всех разработанных методов с выводом исходных данных и результатов на дисплей.

Режим диалога должен обеспечиваться с помощью иерархического меню. Основные пункты:

1.     «Инициализация». Подпункты:

1.1  «Число элементов». Задает число элементов в массиве указателей на базовый класс. После ввода числа элементов пользоваться этим пунктом меню запрещается.

1.2  «Начальное значение». С помощью этого пункта меню можно задать номер элемента, его тип и начальное значение. Задавать начальные значения и работать с другими пунктами меню запрещается до тех пор, пока не будет задано число элементов. Допускается задать новое начальное значение несколько раз.

2.     «Тестирование». Подпункты:

2.1      «Строка»

2.2      «Строка-идентификатор»

2.3      Класс, соответствующий варианту задания

2.4      «Задать операнды»

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

3.     Выход

Варианты заданий

Вариант 1.   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, перегрузка операции сложения «+» для конкатенации строк.

Производный от «Строка» класс «Битовая строка».

Строки данного класса могут содержать только символы '0' или '1'. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Битовая строка» принимает нулевое значение. Содержимое данных строк рассматривается как двоичное число. Отрицательные числа хранятся в дополнительном коде.

Обязательные методы: изменение знака на противоположный (перевод в дополнительный код), переопределение операций проверки на равенство «==» и сложения «+» для получения арифметической суммы строк. Длина строки результата равна длине большей из строк, в случае необходимости, более короткая битовая строка расширяется влево знаковым разрядом.

Вариант 2.   

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

Производный от «Строка» класс «Десятичная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате long, перегрузка операций вычитания «-» и умножения «*» для получения разности и произведения двух десятичных чисел.

Вариант 3.   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, переопределение операций сложения «+» для конкатенации строк и индексации «[]».

Производный от «Строка» класс «Восьмеричная строка».

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

Обязательные методы: определение, можно ли представить данное число в формате char, переопределение операций сложения «+» и вычитания «-» для получения арифметических суммы и разности строк.

Вариант 4.   

Дополнительные методы для класса «Строка-идентификатор»: поиск первого вхождения символа в строку, переопределение операции проверки на равенство «==».

Производный от «Строка» класс «Комплексное число».

Строки данного класса состоят из двух полей, разделенных символом i. Первое поле задает значение реальной части числа, а второе – мнимой. Каждое из полей может содержать только символы десятичных цифр и символы «-»и «+», задающие знак числа, которые могут находиться только в первой позиции числа, при отсутствии символа «+» число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Комплексное число» принимает нулевое значение. Примеры строк: 33i12, -7i100, +5i-21.

Обязательные методы: переопределение операций проверки на равенство «==» и умножения «*» для вычисления произведения двух комплексных чисел.

Вариант 5.   

Дополнительные методы для класса «Строка-идентификатор»: поиск последнего вхождения символа в строку, переопределение операций сравнения «>» и «<».

Производный от «Строка» класс «Десятичная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате int, перегрузка операций вычитания «-» и сравнения «>» для получения арифметической разности и сравнения чисел.

Вариант 6.   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, переопределение операции сравнения «<».

Производный от «Строка» класс «Комплексное число».

Строки данного класса состоят из двух полей, разделенных символом i. Первое поле задает значение реальной части числа, а второе – мнимой. Каждое из полей может содержать только символы десятичных цифр и символы «-»и «+», задающие знак числа, которые могут находиться только в первой позиции числа, при отсутствии символа «+» число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Комплексное число» принимает нулевое значение. Примеры строк: 33i12, -7i100, +5i-21.

Обязательные методы: переопределение операций деления «/» двух комплексных чисел и отрицания «!» для получения сопряженного комплексного числа.

Вариант 7.   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в нижний регистр, переопределение операции проверки на равенство «==».

Производный от «Строка» класс «Шестнадцатеричная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате long, перегрузка операций сложения «+» и проверки на меньше «<» двух шестнадцатеричных чисел.

Вариант 8.   

Дополнительные методы для класса «Строка-идентификатор»: поиск первого вхождения символа в строку, переопределение операции сложения «+» для конкатенации строк.

Производный от «Строка» класс «Комплексное число».

Строки данного класса состоят из двух полей, разделенных символом i. Первое поле задает значение реальной части числа, а второе – мнимой. Каждое из полей может содержать только символы десятичных цифр и символы «-»и «+», задающие знак числа, которые могут находиться только в первой позиции числа, при отсутствии символа «+» число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Комплексное число» принимает нулевое значение. Примеры строк: 33i12, -7i100, +5i-21.

Обязательные методы: переопределение операций сложения «+» и умножения «*» двух комплексных чисел.

Вариант 9.   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, переопределение операции проверки на меньше «<».

Производный от «Строка» класс «Десятичная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате char, перегрузка операций вычитания «-» и сравнения «<» для получения арифметической разности и сравнения чисел.

Вариант 10.                   

Дополнительные методы для класса «Строка-идентификатор»: поиск последнего вхождения символа в строку, переопределение операции проверка на больше «>».

Производный от «Строка» класс «Битовая строка».

Строки данного класса могут содержать только символы '0' или '1'. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Битовая строка» принимает нулевое значение. Содержимое данных строк рассматривается как двоичное число. Отрицательные числа хранятся в дополнительном коде.

Обязательные методы: получение информации о знаке числа, переопределение операций вычитания «-» (длина строки результата равна длине большей из строк, в случае необходимости, более короткая битовая строка расширяется влево знаковым разрядом) и проверки на больше «>» двух двоичных чисел.

Вариант 11.                   

Дополнительные методы для класса «Строка-идентификатор»: поиск первого вхождения символа в строку, переопределение операции проверки на равенство «==».

Производный от «Строка» класс «Восьмеричная строка».

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

Обязательные методы: определение, можно ли представить данное число в формате int, переопределение операций сложения «+» и сравнения на равенство «==» двух восьмеричных чисел.

Вариант 12.                   

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

Производный от «Строка» класс «Комплексное число».

Строки данного класса состоят из двух полей, разделенных символом i. Первое поле задает значение реальной части числа, а второе – мнимой. Каждое из полей может содержать только символы десятичных цифр и символы «-»и «+», задающие знак числа, которые могут находиться только в первой позиции числа, при отсутствии символа «+» число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Комплексное число» принимает нулевое значение. Примеры строк: 33i12, -7i100, +5i-21.

Обязательные методы: переопределение операций вычитания «-» и деления «/» двух комплексных чисел.

Вариант 13.                   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, переопределение операции сложение «+» для конкатенации строк.

Производный от «Строка» класс «Шестнадцатеричная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате int, перегрузка операции сложения «+» двух шестнадцатеричных чисел.

Вариант 14.                   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, переопределение операции сложение «+» для конкатенации строк.

Производный от «Строка» класс «Десятичная строка».

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

Обязательные методы: получение информации о знаке числа, перегрузка операции сложения «+» чисел.

Вариант 15.                   

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

Производный от «Строка» класс «Битовая строка».

Строки данного класса могут содержать только символы '0' или '1'. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Битовая строка» принимает нулевое значение. Содержимое данных строк рассматривается как двоичное число. Отрицательные числа хранятся в дополнительном коде.

Обязательные методы: инвертирование числа, переопределение операции вычитания «-» (длина строки результата равна длине большей из строк, в случае необходимости, более короткая битовая строка расширяется влево знаковым разрядом).

Вариант 16.                   

Дополнительные методы для класса «Строка-идентификатор»: перевод всех символов строки (кроме цифр) в верхний регистр, перегрузка операции сложения «+» для конкатенации строк.

Производный от «Строка» класс «Шестнадцатеричная строка».

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

Обязательные методы: перевод всех буквенных символов строки в верхний регистр, перегрузка операции сложения «+» двух шестнадцатеричных чисел.

Вариант 17.                   

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

Производный от «Строка» класс «Десятичная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате unsigned int, перегрузка операций вычитания «-» для получения разности двух десятичных чисел.

Вариант 18.                   

Дополнительные методы для класса «Строка-идентификатор»: поиск первого вхождения символа в строку, переопределение операций сложения «+» для конкатенации строк и индексации «[]».

Производный от «Строка» класс «Восьмеричная строка».

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

Обязательные методы: определение, можно ли представить данное число в формате unsigned long, переопределение операции сложения «+» двух восьмеричных чисел.

Вариант 19.                   

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

Производный от «Строка» класс «Комплексное число».

Строки данного класса состоят из двух полей, разделенных символом i. Первое поле задает значение реальной части числа, а второе – мнимой. Каждое из полей может содержать только символы десятичных цифр и символы «-» и «+», задающие знак числа, которые могут находиться только в первой позиции числа, при отсутствии символа «+» число считается положительным. Если в составе инициализирующей строки будут встречены любые символы, отличные от допустимых, «Комплексное число» принимает нулевое значение. Примеры строк: 33i12, -7i100, +5i-21.

Обязательные методы: переопределение операций сложения «+» и вычитания «-» двух комплексных чисел.

Вариант 20.                   

Дополнительные методы для класса «Строка-идентификатор»: поиск последнего вхождения символа в строку, переопределение операций сравнения «>=» и «<=».

Производный от «Строка» класс «Десятичная строка».

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

Обязательные методы: определение, можно ли представить данное число  в формате unsigned char, перегрузка операций сравнения «>=» и «<=» двух десятичных чисел.

 

Библиографический список

1.         Керниган, Брайен У. Язык программирования C: Пер. с англ./ Б.У. Керниган, Д.М. Ритчи – 2-е изд. – М.: Издательский дом «Вильямс», 2013. 304 с.

2.         Гущин, А.Н. SDL: учебное пособие / Балт. гос. техн. ун-т. СПб., 2014. - 142 с.

3.         Страуструп, Б. Язык программирования C++ / Б. Страуструп ; пер. с англ. под ред. Н. Н. Мартынова. - Спец. изд. - Москва : Бином, 2011. - 1135 с. 

4.         Лафоре, Р. Объектно-ориентированное программирование в C++: Пер. с англ./ Р. Лафоре; Пер. А. Кузнецов, Пер. М. Назаров, Пер. В. Шрага. - 4-е изд. - СПб.: Питер, 2003. - 923 с.

5.         Подбельский, В.В. Язык Си ++: учебное пособие/ В.В. Подбельский. - 5-е изд. - М.: Финансы и статистика, 2001. - 560 с.

6.         ISO International Standard ISO/IEC 14882:2011(E) – Programming Language C++. URL: isocpp.org/std/the-standard (дата обращения: 14.04.2014)