37477

Изучить среду Microsoft Visual Studio 6.0. Изучить структуру программы на языке C++

Лабораторная работа

Информатика, кибернетика и программирование

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

Русский

2013-09-24

1.17 MB

7 чел.

Лабораторная работа №1.

Цель работы: изучить среду Microsoft Visual Studio 6.0. Изучить структуру программы на языке C++. Научиться писать простейшие программы с использованием простых и условных операторов с применением арифметических типов данных.

Теоретическая часть.

Краткие исторические сведения о языке C++.

Язык C++ является развитием языка C, который тесно связан с операционной системой UNIX, а точнее предназначался для ее написания. В языке C++ добавлено много новых возможностей, которых не было в C, и, прежде всего, эти расширения касаются ООП (объектно-ориентированного программирования). Первоначально «прообраз» C++, который назывался «C с классами» использовался с 1980 г. Разработчиком языка C++ и создателем первого транслятора является Берн Страус труп. Название С++ (си плюс плюс), было придумано Риком Маскити летом 1983 г. Это название отражает эволюционный характер изменений языка С. Обозначение ++ относится к операции наращивания С.

В язык С++  были добавлены новые возможности: виртуальные функции, перегрузка функций и операторов, ссылки, константы, пользовательский контроль над управлением свободной памятью, улучшенная проверка типов и новый стиль комментариев (//). Его первый коммерческий выпуск состоялся в октябре 1985 года. В 1985 году вышло также первое издание «Языка программирования С++», обеспечивающее первое описание этого языка, что было чрезвычайно важно из-за отсутствия официального стандарта. В 1989 году состоялся выход C++ версии 2.0. Его новые возможности включали множественное наследование, абстрактные классы, статические функции-члены, функции-константы и защищённые члены.

В 1990 году вышло «Комментированное справочное руководство по C++», положенное впоследствии в основу стандарта. Последние обновления включали шаблоны, исключения, пространства имён, новые способы приведения типов и булевский тип.

Стандартная библиотека C++ также развивалась вместе с ним. Первым добавлением к стандартной библиотеке C++ стали потоки ввода/вывода, обеспечивающие средства для замены традиционных функций C printf и scanf. Позднее самым значительным развитием стандартной библиотеки стало включение в неё Стандартной библиотеки шаблонов.

После многих лет работы совместный комитет ANSI-ISO стандартизировал
C++ в 1998 году (ISO/IEC 14882:1998 — Язык программирования C++). В течение нескольких лет после официального выхода стандарта комитет обрабатывал сообщения об ошибках и в итоге выпустил исправленную версию стандарта С++ в 2003 году.

Краткое описание среды Microsoft Visual Studio 6.0 и возможностей компилятора.

Как среда Microsoft Visual Studio 6.0, так и компилятор, являются WIN32 приложениями, работающими под управлением операционной системы (ОС) Microsoft Windows 9x или NT-подобных (Windows NT/2000/XP и т.д.). Соответственно, в отличие от среды Borland C++ 3.1, в NT-подобных ОС эта среда НЕ запускается в процессе ntvdm, может использовать всю виртуальную (оперативную) память, предоставляемую ОС, а также позволяет разрабатывать WIN32 приложения, использовать в разрабатываемых приложениях инструкции процессоров 80386/80486/Pentium и соответствующих математических сопроцессоров.

WIN32 приложения бывают как консольными (не имеют визуального интерфейса, запускаются и работают в консоли), так и не требующими консоли для своей работы. Последние, зачастую, имеют GUI (Graphical User Interface), хотя могут его и не иметь. Они также несколько отличаются от консольных WIN32 приложений своей структурой, например, функций main заменена на функцию WinMain.

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

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

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

На рис. 1 представлен вид Microsoft Visual Studio 6.0 сразу после запуска, пока не открыто еще ни одного проекта.

Рис. 1. Вид Microsoft Visual Studio 6.0 сразу после запуска

Для создания нового проекта Win32 консольного приложения необходимо нажать сочетание клавиш Ctrl+N или выбрать пункт меню File\New… После этого на экране появится диалоговое окно, представленное на рис. 2, в котором нужно в правой часть окна выбрать строку Win32 Console Application, в окне Project Name ввести имя проекта (будет создана папка с таким именем), а в окне Location  выбрать путь, по которому будет находиться созданная для нового проекта папка (выбор пути осуществляется при нажатии на кнопку с надписью «…»). После вышеописанных действий необходимо нажать на кнопку Ok, которая станет доступна.

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

Рис. 2. Диалоговое окно создания нового проекта

в Microsoft Visual Studio 6.0

Добавление в проект файлов можно осуществить двумя способами:

  1.  Если необходимо добавить новый (пустой) файл, то для этого необходимо снова нажать Ctrl+N или выбрать пункт меню File\New… В результате этого появится диалоговое окно, представленное на рис. 3 (название проекта в примере 3).

Рис. 3. Диалоговое окно добавления новых (пустых) файлов

в проект в среде Microsoft Visual Studio 6.0

Для добавления файлов заголовков нужно выбрать в левой части диалогового окна пункт C/C++ Header File, для добавления .cpp файлов, содержащих код программы  C++ Source File. После выбора типа файла нужно в правой части окна ввести имя файла и нажать на кнопку Ok, которая станет доступной. После этого новый файл будет помещен в папку, в которой хранятся все файлы проекта.

  1.  Если необходимо добавить в проект существующий файл, то сначала нужно скопировать его в папку с проектом, а затем выбрать пункт меню Project\Add To Project\Files… После этого в появившемся диалоговом окне выбрать файл(ы), которые нужно добавить в проект.

Для открытия существующего проекта необходимо выбрать пункт меню File\Open Workspace… и в появившемся диалоговом окне выбрать главный файл проекта (с расширением .dsw). Для сохранения текущего проекта необходимо выбрать пункт меню File\Save Workspace. Для того, чтобы закрыть текущий проект пункт меню File\Close Workspace. Для того, чтобы сохранить все файлы проекта пункт меню File\Save All.

Пример внешнего вида среды Microsoft Visual Studio 6.0 с открытым проектом (Пример 3 в этой лабораторной работе) приведен на рис. 4.

Рис. 4. Пример внешнего вида среды Microsoft Visual Studio 6.0

с открытым проектом

В левой части окна (вкладка FileView) схематически представлены все файлы проекта, которые разбиты на 3 типа (3 вида папок): исходные файлы (Source Files), файлы заголовков (Header Files) и файлы ресурсов (Resource Files), применяемые при создании приложений с графическим интерфейсом. В правой части окна содержимое выбранного файла проекта. В нижней части окна отображаются сообщения компилятора и линковщика, в том числе сообщения об ошибках и предупреждения. Для локализации места ошибки (предупреждения) необходимо дважды щелкнуть мышкой на соответствующем сообщении (следует отметить, что не всегда строка, в которой компилятор обнаружил ошибку, действительно содержит причину ее возникновения). Предупреждения (warnings) не являются по своей сути ошибками, которые не позволяют создать исполнимый (.exe) файл программы, но на них следует обращать внимание и, желательно, добиваться их отсутствия. Возникновение предупреждений в процессе компиляции программы может в дальнейшем привести к ошибкам в логике их работы (например, использование переменных, начальные значения которых не определены). Строка под главным меню содержит панель инструментов, полезным свойством которой является выпадающий список функций программы , с помощью которого можно быстро перейти к коду нужной функции.

Для компиляции проекта необходимо нажать сочетание клавиш Ctrl+F7 или выбрать пункт меню Build\Compile <имя_файла> либо нажать на панели инструментов (справа) кнопку . В результате успешной компиляции будет создан .obj файл, который содержит исполнимый код программы, но не содержит исполнимый код подпрограмм, описание переменных и т.п. из подключенных библиотек (.lib-файлы или другие .obj-файлы). Для получения исполнимого файла необходимо выполнить процесс линковки, в результате которого в исполнимый код программы будет добавлен нужный исполнимый кол из подключаемых библиотек. Для этого необходимо нажать F7 либо выбрать пункт меню Build\Build <имя_файла> либо на панели инструментов нажать кнопку . Иногда возникает необходимость воспользоваться пунктом меню Build\Rebuild All в результате чего будут перекомпилированы все файлы проекта после чего произойдет линковка  исполнимого файла. Для запуска приложения (включает компиляцию изменившихся файлов проекта и линковку исполнимого файла (при необходимости)) следует нажать сочетание клавиш Ctrl+F5 либо выбрать пункт меню Build\Execute <имя_файла>, либо на панели инструментов нажать кнопку .

Следует обратить внимание, что когда проект создается, то по умолчанию используется Debug-конфигурация настроек компиляции и линковки, что приводит к отключению оптимизации исполнимого кода и добавлению в исполнимый код информации для отладчика (Debuger), что позволяет выполнять пошаговую отладку программы для выявления логических ошибок. При создании исполнимого файла финальной версии приложения предпочтительнее выбрать Release-конфигурацию, в результате чего не добавляется информации для отладчика в исполнимый код, что уменьшает его размер. Также эта конфигурация содержит настройки направленные на оптимизацию исполнимого кода, что повышает быстродействие программы (иногда достаточно существенно). Для выбора активной конфигурации необходимо выбрать пункт меню Build\Set Active Configuration… и в появившемся диалоговом окне изменить активную конфигурацию. Для более детальной настройки параметров компиляции и линковки необходимо нажать сочетание клавиш Alt+F7 либо выбрать пункт меню Project\Settings… и появившемся диалоговом окне выполнить «тонкую» настройку параметров компилятора и линковщика для активной конфигурации.

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

Для осуществления пошаговой отладки среда Microsoft Visual Studio 6.0 предоставляет следующие возможности:

  1.  Установить/убрать точку останова (breakpoint). Breakpoint устанавливает для определенной строки программы и приводит к тому, что программа выполняется в обычном режиме до этой строки, а начиная с нее переходит в режим пошаговой отладки. Для того, что установить/убрать breakpoint, необходимо установить курсор в нужную строку и нажать F9 либо на панели инструментов нажать кнопку .
  2.  Выполнить программу до курсора. Аналогично точкам останова, программа выполняется до выбранной строки в обычном режиме, а, начиная с нее, переходит в режим пошаговой отладки. Для использования этой возможности необходимо установить курсор в необходимую строку и нажать сочетание клавиш Ctrl+F10 либо выбрать пункт меню Build\Start Debug\Run to Cursor.
  3.  Пошаговое выполнение программы, включая функции. Для этого нужно нажать F11 либо выбрать пункт меню Build\Start Debug\Step Into.
  4.  Пошаговое выполнение программы, не включая функции (выполняются в обычном режиме). Для этого в процессе отладки нужно нажать F10 либо выбрать пункт меню Debug\Step Over. Можно комбинировать с предыдущим режимом.
  5.  Выход из отладки функции в вызывающую программу. Для этого в процессе отладки нужно нажать сочетание клавиш Shift + F11 либо выбрать пункт меню Debug\Step Out.
  6.  Выход из режима пошаговой отладки. Для этого в процессе отладки нужно нажать сочетание клавиш Shift + F5 либо выбрать пункт меню Debug\Stop Debugging.

Пример внешнего вида среды Microsoft Visual Studio 6.0 в процессе пошаговой отладки приложения приведен на рис. 5.

Рис. 5. Внешний вид среды Microsoft Visual Studio 6.0 в процессе

пошаговой отладки приложения

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

Краткие сведения о синтаксисе языка C++.

Структура программы на языке C++.

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

/*

ПОДКЛЮЧЕНИЕ ФАЙЛОВ ЗАГОЛОВКОВ

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

Подключение файлов заголовков производится с помощью директивы include.

Например:

#include <stdio.h>

или

#include ”stdio.h”

При необходимости можно указать полный путь к файлу.

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

*/

//---------------------------------------------------------------------------------------

/*

ОПИСАНИЕ ТИПОВ ДАННЫХ, ДОСТУПНЫХ ВСЕМ ПОСЛЕДУЮЩИМ ФУНКЦИЯМ

Описание типов данных при помощи ключевого слова typedef или ключевых слов class, struct, union (при описании классов).

Например, можно описать такой пользовательский тип данных при помощи ключевого слова typedef:

typedef unsigned short int UINT;

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

*/

/*

ОПИСАНИЕ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ

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

тип_данных имя_пер1, имя_пер2, …, имя_перN;

Например,

int a, b, sum, ar[10];

В данном примере описано 3 переменных типа int и массив из 10 элементов типа int. Следует отметить, что при описании переменных также можно задавать их начальные значения, например:

char ch =’a’, ch1 = 32;

В данном примере переменной ch присвоен код символа a, а переменной ch1 – значение 32.

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

const  int c_a = 0;

Следует отметить, что в этом случае начальное значение (меняться не может) ОБЯЗАТЕЛЬНО необходимо указывать. Если после ключевого слова const не указать тип данных, то подразумевается тип данных int.

Константное значение также можно описать с помощью директивы #define (она также может называться макроопределением), например:

#define C_A 100

Таким образом, был описан идентификатор, значение которого компилятор в коде программы всегда будет заменять на 100. В качестве значения может быть использовано выражение. Следует обратить внимание, что сфера применения директивы #define значительно шире, чем простое описание констант, как это делалось в языке Паскаль. Например, можно сделать такие определения:

#define begin {

#define Begin {

#define BEGIN {

#define end }

#define End }

#define END }

Теперь вместо операторных скобок { } можно применять привычные для языка Паскаль операторные скобки Begin End в разных вариантах написания (в языке C++ большие и маленькие буквы различаются).

При описании переменных также можно указать один из 4-ех классов памяти: auto, static, register и extern. Если ничего не указать, то по умолчанию выбирается auto. Их назначение описано в табл. 1.

Табл. 1. Назначение классов памяти

в языке C++.

Название

Назначение

auto

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

static

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

register

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

extern

Ссылка на переменную, объявленную в другом модуле (.h или .cpp файле) или в этом же модуле, но ранее.

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

*/

//---------------------------------------------------------------------------------------

/*

ОПИСАНИЕ ПРОТОТИПОВ (ЗАГОЛОВКОВ) ФУНКЦИЙ

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

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

*/

inline Тип_возвр_значения  Имя_функц1(Необяз_список_аргументов);

или

Тип_возвр_значения inline Имя_функц1(Необяз_список_аргументов);

или

Тип_возвр_значения Имя_функц1(Необяз_список_аргументов);

или

Тип_возвр_значения Имя_функц2(Необяз_список_аргументов)

{

/*

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

*/

}

//---------------------------------------------------------------------------------------

/*

ОПИСАНИЕ ФУНКЦИИ main

*/

int main()

{

/*

Пример многострочного комментария. Здесь должен располагаться код основной программы

*/

// Пример однострочного комментария.

 return 0;

}

//---------------------------------------------------------------------------------------

/*

ОПИСАНИЕ РЕАЛИЗАЦИЙ ФУНКЦИЙ, ПРОТОТИПЫ КОТОРЫХ БЫЛИ ОПИСАНЫ РАНЕЕ.

*/

Следует отметить, что:

  1.  В языке C++ различаются большие и маленькие буквы.
  2.  Из всех приведенных разделов обязательным является только описание функции main (заголовка и тела), причем рекомендуется, чтобы функция возвращала значение цело численного типа int (0 в случае удачного завершения, не 0 в противном случае). Это производится с помощью оператора return, за которым через пробел идет возвращаемое функцией значение. Это значение может быть задано непосредственно константой или с помощью переменной и должно совпадать с типом возвращаемого значения, описанным в заголовке функции. После выполнения оператора return происходит немедленное прекращение выполнения функции. Если после оператора  return не указать значение, то оно будет неопределенным (это делается в том случае если нужно преждевременно прервать выполнение функции, которая имеет тип возвращаемого значения void  пустое множество значение).

Если функция main будет описана с типом возвращаемого значения void, то использование ключевого слова return не обязательно (как и в случае любой другой функции). Однако, тип возвращаемого значения void для функции main при использовании некоторых компиляторов (не Microsoft или Borland) приводит к генерации предупреждения или ошибки. Их поведение таково, потому что в случае «пакетного» запуска программ операционная система не сможет «понять» удачно завершилась программа или нет, т.к. за это отвечает значение, возвращаемое функцией main, а в случае использования типа данных void оно отсутствует.

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

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

где argc  содержит количество аргументов командной строки, а массив строк argv  собственно аргументы.

  1.  Имя любого идентификатора в языке C++ (имена переменных, макроопределений,  типов данных, функций, меток) должно начинаться с большой или маленькой буквы латинского алфавита или знака подчеркивания (_) и может содержать цифры в качестве не первого символа. Количество воспринимаемых символов в имени идентификатора в стандарте языка не ограничено, но может ограничиваться в конкретных компиляторах. Например, в некоторых компиляторах это 31 символ. В Microsoft Visual Studio различаются первые 2048 имени идентификатора, а также допускается использование в любой позиции имени символа $.
  2.  Порядок следования описаний функций, типов данных, переменных, макроопределений, подключение файлов заголовков жестко не регламентирован. Однако, следует отметить, что перед использованием любого объекта (макроопределения, переменной, типа данных, функции) он должен быть предварительно описан.
  3.  В языке C++ допускается (хотя и не рекомендуется) использование меток (синтаксис их использования подобен Паскалю) и, соответственно, безусловных переходов по ним с помощью оператора goto, но, в отличие от языка Паскаль, нет специальной секции описания меток.  
  4.  В языке C++ константные значения бывают числовые (целочисленные и вещественные), строковые и символьные.

Примеры целочисленных константных значений:

Десятичная система: -10, 20.

Восьмеричная система: 020, -0100. Таким образом, восьмеричные значения начинаются префиксом 0.

Шестнадцатеричная система: -0x20, 0xFFFF. Таким образом, шестнадцатеричные значения начинаются префиксом 0x.

Следует отметить, что по умолчанию целочисленные константные значения считаются значениями типа int. Если необходимо интерпретировать их, как значения типа long (длинное целое), то в конце необходимо дописать букву l или L, например: 2000000000l. Это важно в случаях, когда тип данных int 16-битный.

Примеры вещественных константных значений:

В формате с фиксированной точкой, например: 5.256, -678.2768.

В формате с экспонентой, например: 1.5E-2 (эквивалентно 0.015), -2E2 (эквивалентно -200).

Примеры строковых константных значений (литералов):

Строковые константные значения записываются в ”” (двойных кавычках), например: ”This is an example”. В конце строковой константы компилятор автоматически размещает символ с ASCII кодом 0, который на языке C++ означает конец строки. В случае использования символов ”, \ и некоторых других необходимо применять так называемые Escape-последовательности (управляющие последовательности), которые начинаются символом \. Перечень допустимых Escape-последовательностей приведен в табл. 2.

Табл. 2. Перечень допустимых Escape-

последовательностей языка C++

Обозначение

Назначение

\\

Обратный слеш.

\

Двойная кавычка.

\’

Одинарная кавычка.

\a

Управляющий символ звукового сигнала (воспроизводится через спикер). Его ASCII код 7.

\b

Управляющий символ, генерируемый при нажатии клавиши Backspace. Приводит к возврату курсора на одну позицию влево, но при этом не стирается символ слева от курсора. Его ASCII код 8.

\f

Управляющий символ, используемый при печати на принтере для перехода к новой странице. Его ASCII код 12.

\n

Управляющий символ, соответствующий новой строке (переходу на новую строку). Его ASCII код 13.

\r

Управляющий символ, соответствующий возврату каретки при переходе на новую строку. Его ASCII код 10.

\t

Управляющий символ, соответствующий горизонтальной табуляции (нажатие на клавишу Tab). Его ASCII код 9.

\v

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

\ooo

Символ, ASCII код которого задан восьмеричным значением.

\xhh

Символ, ASCII код которого задан шестнадцатеричным значением. Также можно задавать символы при использовании Unicode (2-байтные коды символов).

Примеры символьных константных значений:

Символьные константные значения записываются в ’’ (одинарных кавычках или апострофах), например: ’f’. Количество символов в такой константе не может превышать одного символа и при ее задании также можно использовать Escape-последовательности.

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

Арифметические типы данных языка C++.

Рассмотрим арифметические типы данных языка C++, которые представлены в табл. 3:

Табл. 3. Перечень арифметических типов

данных языка C++

Характеристики типов

 

 

Название типа в языке C++

Класс

Байт

Дес. знаков

Диапазон

Целые без знака

1

2-3 

0,,28-1

unsigned char

unsigned __int8

2

 4-5

0,,216-1

unsigned short int 

unsigned __int16

4

 9-10

0,,232-1

unsigned int

unsigned long

unsigned __int32

8

 19-20

0,,264-1

unsigned long long unsigned __int64

Целые знаковые

1

 2-3

-27…27-1

char

__int8

2

 4-5

-215…215-1

short int

__int16 

4

 9-10

-231…231-1

int 

long int

__int32

8

 19-20

-263…263-1

long long

__int64

Действительные

4

 7-8

3.4е-38,,,3,4е38

float

8

 15-16

1,7е-308,,,1,7е308

double

long double

Следует отметить, что:

  1.  Размер типа данных int зависит от размеров машинного слова, от используемой платформы и компилятора, поэтому в некоторых случаях может быть 2 байта (Например, в Borland C++ 3.1).
  2.  Следует обратить внимание, что при использовании типов short int, unsigned int, long int ключевое слово int использовать необязательно.
  3.  В некоторых компиляторах тип данных long double не реализован.
  4.  При использовании целочисленных типов данных для указания конкретного типа могут быть использованы следующие ключевые слова:
    •  unsigned  означает беззнаковый целочисленный тип.
    •  signed  означает знаковый целочисленный тип. Используется по умолчанию, если не указать ни unsigned, ни signed.
    •  short  означает короткое целое.
    •  long  означает длинное целое (или вещественное).
  5.  Тип данных long long в среде Microsoft Visual Studio 6.0 использовать невозможно, хотя, например, в среде Microsoft Visual Studio 2005 применение такого типа допустимо.
  6.  Типы данных __int8, __int16, __int32, __int64 (знаковые и беззнаковые) характерны только для компиляторов Microsoft и стандартом ANSI не предусмотрены.
  7.  Для целочисленных знаковых типов данных старший бит является знаковым: если он равен 1, то число считается отрицательным (представляется в дополнительном коде при записи в двоичной системе счисления), а если он равен 0, то положительным.
  8.  Существует неявное и явное преобразование типов. Неявное работает в ряде случаев автоматически (будет рассмотрено ниже). Явное предполагает конструкцию вида: новый_тип_данных(значение) или (новый_тип_данных)(значение), которая может использоваться, например, в правой части оператора присваивания, в операциях сравнения, и возможно для совместимых типов данных:
    •  Знаковый целочисленный тип данных можно преобразовать к беззнаковому целочисленному типу данных и наоборот. При этом старший бит числа будет интерпретироваться либо как знаковый (следует учитывать использование дополнительного кода для представления целых отрицательных чисел), либо как часть числа.
    •  В случае преобразования к типу данных с меньшей размерности будет потеряна старшая (наиболее значимая часть значения). Например, при приведении значения 4-байтного целочисленного типа данных 65537 к беззнаковому целочисленному типу данных получаем 1 из-за потери 2-ух старших байт.
    •  В случае приведения вещественного значения к целочисленному типу данных всегда происходит усечение (отбрасывание) дробной части, а также может произойти потеря точности в целой части числа в зависимости от используемого при приведении целочисленного типа данных.
    •  Приведение целочисленного значения к вещественному типу данных может привести к потере точности в целой части, если размерность мантиссы вещественного значения меньше размерности целой части значения.

Краткий перечень операций и функций языка C++.

Рассмотрим арифметические операции (операторы) языка C++ (табл. 4):

Табл. 4. Перечень арифметических операций

языка C++

Назначение

Обозначение

Кол-во операндов

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

Целых

Действительных

Сложение

+

2

Да

Да

Обозначение положительного числа

+ или отсутствие знака

1

Да

Да

Вычитание

-

2

Да

Да

Обозначение отрицательного числа

-

1

Да

Да

Умножение

*

2

Да

Да

Деление

/

2

Да

Да

Взятие остатка от деления

%

2

Да

Нет

Увеличение на 1

++

1

Да

Да

Уменьшение на 1

--

1

Да

Да

Следует отметить, что:

  1.  Тип данных результата деления (/) будет зависеть от типа данных операндов: если хотя бы один из операндов вещественного типа, то результат также будет вещественным, в противном случае результат будет целочисленным (при помещении результата в вещественную переменную в этом случае дробная часть будет утеряна).
  2.  Операторы инкремента (++) и декремента (--) могут быть в префиксном (++имя_перем; или --имя_перем;) и постфиксном (имя_перем++; или имя_перем--;) виде. Различия заключаются в том, что при использовании префиксной формы сначала происходит изменение значения переменной, а потом его дальнейшее использование; при использовании постфиксной формы сначала используется значение переменной, а потом происходит изменение его значения. Это различие существенно в сложных выражениях.
  3.  При выполнении арифметических операций производятся следующие неявные преобразования типов:
    •  Операнды типа float преобразуются к типу double.
    •  Если один операнд long double, то второй преобразуется к этому же типу.
    •  Если один операнд double, то второй также преобразуется к типу double.
    •  Любые операнды типа char и short преобразуются к типу int.
    •  Любые операнды unsigned char или unsigned short преобразуются к типу unsigned int.
    •  Если один операнд типа unsigned long или long, то второй также преобразуется к типу unsigned long или long.
    •  Если один операнд типа unsigned int, то второй операнд преобразуется к этому же типу.

Рассмотрим операции (операторы) сравнения языка C++ (табл. 5):

Табл. 5. Перечень операций сравнения

языка C++

Назначение

Обозначение

Кол-во операндов

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

Целых

Действительных

Равно

==

2

Да

Да

Не Равно

!=

2

Да

Да

Меньше

<

2

Да

Да

Меньше либо равно

<=

2

Да

Да

Больше

>

2

Да

Да

Больше либо равно

>=

2

Да

Да

Рассмотрим побитовые и логические операции (операторы) языка C++ (табл. 6):

Табл. 6. Перечень побитовых операций

языка C++

Назначение

Обозначение

Кол-во операндов

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

Целых

Действительных

Двоичное дополнение

~

1

Да

Нет

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

<<

2

Да

Нет

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

>>

2

Да

Нет

Побитовое «И»

&

2

Да

Нет

Побитовое «ИЛИ»

|

2

Да

Нет

Побитовое исключающее «ИЛИ»

^

2

Да

Нет

В языке  C++ существует 3 логических операции, которые применяются для построения логических выражений: &&(логическое «и»), ||(логическое или) и !(отрицание). Причем && и || – бинарные операции (имеют 2 аргумента), а ! – унарная (1 аргумент).

Результатом выполнения логической операции будет одно из двух константных значений (хотя правильно называть это ключевыми словами, что, в последствии, и будем делать): false (ложь, числовое значение 0) и true (истина, числовое значение НЕ 0). Эти ключевые слова могут также использоваться как 2 возможных значения переменных логического типа данных bool. Это встроенный тип данных, размер которого 1 байт.

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

Табл. 7. Таблицы истиности логических

операций языка C++.

Значения аргументов

Значение результата выполнения логической операции

Значения аргумента

Значение результата выполнения логической операции

1-ый

2-ой

&& или &

|| или |

^

!

0

0

0

0

0

0

1

0

1

0

1

1

1

0

1

0

0

1

1

1

1

1

1

0

Операции << (Сдвиг на указанное количество двоичных разрядов влево) и >> (Сдвиг на указанное количество двоичных разрядов вправо) заменяют собой умножение и деление нацело, соответственно, на число, представляющее собой степень двойки. Например: 10 << 2 == 40, а 10 >> 2 == 2. Для того, чтобы понять принцип их работы необходимо записать число 10 в двоичной системе счисления и произвести сдвиг этого числа на указанное число двоичных разрядов влево или вправо (Табл. 8).

Табл. 8. Принцип работы операций сдвига

Число

Операция

Кол-во двоичн. разр.

Результат

10 =

<<

2

40 =

10 =

>>

2

2 =

Следует обратить внимание на тот факт, что:

  1.  При работе с переменными знаковых типов данных операция >> будет расширять знаковый бит, например -1 >> 2 == -1.
  2.  Операции >> и << выполняются существенно быстрее деления или умножения даже на современных процессорах.

Применение операции двоичного дополнения (~) приводит к тому, что в двоичной записи числа значение каждого бита инвертируется. Например, ~10 == 65525 (Если интерпретировать 10 как 2-ухбайтное целое число).

В языке C++ существует еще две очень полезные унарные операции (операторов), которые приведены в табл. 9.

Табл. 9.  Унарные операции взятия адреса и

получения размера в языке C++

Название

Обозна

чение

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

Описание

Взятие адреса

&

Переменные любых типов данных

В результате применения формируется номер ячейки памяти, в которой хранится значение переменной. Пример: &a

где a  имя переменной.

Вычисление размера объекта в байтах

sizeof

Применим для типов данных и переменных.

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

1. К типу данных void.

2. К именам функций.

3. К битовым полям.

4. К внешним массивам.

Пример 1: sizeof (int) 

Результат = 4.

Пример 2: sizeof a

Результат зависит от типа данных переменной a.

Среди математических функций хотелось бы выделить следующие:

  1.  abs – имеет один аргумент числового типа, возвращает значение по модулю.
  2.  sqrt – имеет один аргумент действительного типа (double), возвращает корень квадратный из значения аргумента тоже действительного типа (double).

В качестве аргументов и результата любой математической функции языка C++, которые описаны с использованием типа данных double, можно использовать переменные ЛЮБОГО действительного типа данных.

Следует отметить, что для использования вышеперечисленных математических функций необходимо подключить файл заголовков math.h, т.е. программа должна содержать строку #include <math.h>. При использовании некоторых компиляторов при осуществлении линковки необходимо убедиться в том, что библиотека math.lib присутствует в списке библиотек, используемых линковщиком.

Краткие сведения об операторах языка C++.

Язык C++ содержит такие группы операторов: простые и структурированные. Структурированные операторы в свою очередь бывают следующих видов: условный оператор (if), операторы цикла (цикл for, цикл с предусловием – while, цикл с постусловием – dowhile), оператор множественного ветвления (switch).

Также следует обратить внимание на использование символа ;(точка с запятой). Практически каждый оператор на языке C++ заканчивается точкой с запятой. Однако есть ряд исключений, когда точка с запятой никогда не ставится:

  1.  После ключевых слов: do, while, for, if, switch, case, default, class, struct, union, typedef, template, enum, после названий типов данных.
  2.  После заголовка функции, если за ним сразу следует ее тело.
  3.  После { ставить НЕ ОБЯЗАТЕЛЬНО (как правило, этого не делают).
  4.  После } ставить НЕ ОБЯЗАТЕЛЬНО (как правило, этого не делают). Единственное исключение точку с запятой ВСЕГДА нужно ставить в конце описания типов данных class, struct и union.

Рассмотрим некоторые операторы языка C++.

  1.  Пустой оператор (;). Не выполняет никаких действий.
  2.  Оператор присваивания (=). Этот простой оператор тому объекту (переменной), который стоит в левой части, присваивает то выражение, которое стоит в правой части. Например:

A = 2 * B + C + D / 4 – E % 2;

В этом примере переменной A присваивается выражение, которое включает ряд математических действий, причем B, C, D, E могут быть переменными и константами, в то время как A константой быть НЕ МОЖЕТ.

Существуют составные операторы присваивания, объединяющие оператор присваивания с каким-либо арифметическим или побитовым оператором. Их перечень приведен в табл. 10.

Табл. 10. Перечень составных операторов

присваивания языка C++

Назначение

Обозначение

Кол-во операндов

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

Целых

Действительных

Значение переменной слева сложить со значением справа и результат записать в переменную слева

+=

2

Да

Да

Из значения переменной слева вычесть значение справа и результат записать в переменную слева

-=

2

Да

Да

Значение переменной слева умножить на значение справа и результат записать в переменную слева

*=

2

Да

Да

Значение переменной слева разделить на значение справа и результат записать в переменную слева

/=

2

Да

Да

Найти остаток от деления значения переменной слева на значение справа и результат записать в переменную слева

%=

2

Да

Нет

Найти результат побитового «И» значения переменной слева и значения справа и результат записать в переменную слева

&=

2

Да

Нет

Найти результат побитового «ИЛИ» значения переменной слева и значения справа и результат записать в переменную слева

|=

2

Да

Нет

Найти результат побитового исключающего «ИЛИ» значения переменной слева и значения справа и результат записать в переменную слева

^=

2

Да

Нет

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

<<=

2

Да

Нет

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

>>=

2

Да

Нет

  1.  { и } являются операторными скобками, т.е. с { начинается какая-то группа операторов, которая рассматривается как некоторый единый блок, а } эта группа операторов завершается.
  2.  Условный оператор (if). Это структурированной оператор, с помощью которого можно осуществлять ветвления в программе на основании выполнения или невыполнения какого-то условия. В общем виде этот оператор выглядит следующим образом:

if (условие)

 Действие1;

else

 Действие2;

Если условие истинно, т.е. результат его проверки будет не 0, то выполнится действие1, иначе выполнится действие2, причем под действием подразумевается некоторый оператор языка C++. Брать простое условие в скобки обязательно. Условие состоит, как правило, из некоторого количества операций сравнения, которые объединяются с помощью логических операторов. В случае необходимости указания нескольких действий нужно использовать операторные скобки, например:

if (условие)

{

 Блок_действий1

     }

else

{

 Блок_действий2;

}

Условный оператор может также иметь сокращенный вид:

if (условие)

          Действие1;

if (условие)

{

 Блок_действий1

     }

т.е. возможность невыполнения условия не рассматривается.

Рассмотрим пример применения условного оператора:

Пример 1.

if ((A >= 3) && (B <= 2))

{

    C = A + B;

    A += 2;

}

else

    B += 2;

В рассмотренном примере условие является сложным, т.е. состоит из 2-ух подусловий, которые объединяются логической связкой «и». Таким образом, блок действий в ветке then выполнится, если одновременно будут истинны подусловия A >= 3 и B <= 2. Если хотя бы одно из них будет ложно, то выполнится действие, находящее в веточке else.

  1.  Кроме оператора if в языке C++ существует похожий на него тернарный (3 операнда) условный оператор ?:, который в общем виде выглядит следующим образом:

выражение1 ? выражение2 : выражение3;

Этот оператор работает следующим образом:

  1.  Сначала вычисляется значение выражения1, которое неявно приводится к типу данных bool.
  2.  Если значение выражения1 true, то результатом выполнения оператора есть выражение2, в противном случае  выражение3.

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

Пример 2.

a = b > 0 ? a + 3 : --b;

В результате выполнения условного оператора в примере 2 переменной a в зависимости от значения переменной b будет присвоено либо значение a + 3, либо уменьшенное на 1 значение переменной b.

Следует обратить внимание на одну очень распространенную ошибку при использовании операции сравнения на равенство (Пример 3).

Пример 3.

if (A = 3)

    A += 2;

В этом примере операция сравнения на равенство ошибочно заменена на операцию присваивания. Однако, в результате такой замены компилятор не сгенерирует сообщение об ошибке. Переменной A будет присвоено значение 3 и результат этой операции будет истинным (значение переменной не ноль). Следовательно, значение переменной A будет увеличено на 2. Но при выполнении «сравнения» первоначальное значение переменной A БЫЛО ПОТЕРЯНО, а операция сравнения вообще не выполнялась! Если бы условный оператор в примере 3 был записан следующим образом (пример 4):

Пример 4.

if ( 3 = A)

    A += 2;

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

Пример 5.

if ( 3 == A)

    A += 2;

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

Последовательность выполнения операторов на языке C++.

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

Табл. 11.  Таблица приоритетов

операции в языке C++

Приори

тет

Обозначение операций

Кол-во аргументов

Назначение операций

1

::

2

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

2

++; --

1

Префиксный инкремент и декремент

2

[]; (); .; ->;

2

Обращение к элементу массива (указание количества элементов); вызов функции; обращение к полю класса; обращение к полю класса через указатель.

3

sizeof; ++; --; ~; !; -; +; &; *; new; delete;

1

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

3

()

2

Приведение к типу данных.

4

.*; ->*

2

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

5

*; /; %

2

Умножение; деление; взятие остатка от деления.

6

+; -

2

Сложение; вычитание.

7

<<; >>

2

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

8

<; >; <=; >=

2

Сравнения

9

==; !=

2

Сравнения на равенство (неравенство)

10

&

2

Побитовое «И»

11

^

2

Побитовое исключающее «ИЛИ»

12

|

2

Побитовое «ИЛИ»

13

&&

2

Логическое «И»

14

||

2

Логическое «ИЛИ»

15

?:

3

Тернарный оператор, эквивалентный операторы if.

16

=; *=; %=; +=; -=; <<=; >>=; &=; |=; ^=

2

Простой оператор присваивания; составные операторы присваивания.

17

throw

1

Возбуждение исключительной ситуации.

18

,

2

Последовательное вычисление выражений.

Следует отметить, что:

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

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

Пример 6.

#include <stdio.h>

int main()

{

float a, b;

printf("Enter the coefficient a of linear equation a*x + b = 0: ");

/*

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

*/

 scanf("%f", &a);

/*

Функция, которая считывает с клавиатуры значения переменных. Следует обратить внимание, что при использовании форматов ввода для вещественных чисел (f, e, E, g, G) второй (третий и т.д.) аргумент должен быть указателем на переменную (элемент массива и т.п.) типа float. Тип double использовать в данном случае нельзя.

*/

printf("Enter the coefficient b of linear equation a*x + b = 0: ");

scanf("%f", &b);

if (a != 0)

 printf("The root of equation = %6.4f\n", -b / a);

else

 if (b != 0)

  printf("This linear equation has not decision: a = 0 and b <> 0\n");

 else

  printf("This linear equation has infinite number of decisions\n");

 return 0;

}

Функции ввода / вывода при работе с консолью.

Для вывода данных на экран (вообще говоря для вывода в стандартный выходной поток stdout, вывод в который, как правило, соответствует выводу на экран) в консольных приложениях, написанных на языке C++, может применяться функция printf. Синтаксис ее вызова в общем виде выглядит следующим образом:

printf(”Строка форматного вывода”, необязательный_перечень_аргументов);

При этом:

  1.  Строка форматного вывода  представляет собой обычную строковую константу, которая может содержать специальные спецификаторы форматного вывода.
  2.  Необязательный_перечень_аргументов представляет собой список выражений, разделенный запятыми, который используется в случае, если Строка форматного вывода содержит спецификаторы форматного вывода. Количество аргументов равно количеству спецификаторы форматного вывода.
  3.  Спецификатор форматного вывода в общем виде выглядит следующим образом:

%[флаги][ширина поля вывода][.точность][{h | l | ll | I32 | I64}]тип_выводимого_значения

В этой записи:

  •  квадратные скобки означают необязательность того, что в них находится.
  •  фигурные скобки означают, что должно быть указан один из аргументов, которые в них находятся.
  •  флаги могут быть одним из следующих символов:
    1.  -  выравнивание по левому краю поля вывода.
    2.  +  в поле вывода для знаковых типов (для поля вывода) будет помещаться знак.
    3.  пробел  в поле вывода для знаковых типов (для поля вывода) будет помещаться пробел, если значение положительно. Не может быть использовании совместно с +.
    4.  #  совместно с типами выводимых значений o, x и X приводит к тому, что перед любым ненулевым значением будет выводиться префикс 0, 0x или 0X; совместно с типами выводимых значений e, E, f, a, A приводит к тому, что выводимое значение всегда содержит десятичную точку; совместно с типами выводимых значений g, G приводит к тому, что выводимое значение всегда содержит десятичную точку и предотвращается усечение незначащих нулей.
    5.  0  используется совместно с шириной поля вывода для заполнения 0 левой или правой части поля вывода (в зависимости от использования флага -) до достижения минимального количества символов, которое задается значением шириной поля вывода. Если указывается также признак точности, но при этом используется целочисленный тип_выводимого_значения.
  •  ширина поля вывода  неотрицательное целое десятичное число, которое задает минимальную длину поля вывода. Если количество выводимых символов больше чем заданная длина, то поле вывода автоматически расширяется.
  •  точность  неотрицательное целое десятичное число, указываемое после символа точка (.), которое задает выводимое количество цифр (для целочисленных типов выводимых значений), количество цифр после десятичной точки (для вещественных типов выводимых значений), причем в этом случае используется округление, или количество выводимых символов. Если задать точность = 0, то для целочисленных значений будет получен 0, для вещественных отсутствие дробной части, а для строковых отсутствие символов. Если в качестве точности задан символ *, то значение точности должно быть указано неотрицательным целым десятичным значением, которое задается в списке аргументов непосредственно перед выводимым значением, например:

printf("%5.*f\n", 2, 15.67567);

приведет к выводу на экран значения 15.68.

  •  префикс типа выводимого значения  перечень префиксов и их назначение представлены в табл. 12.

Табл. 12. Перечень префиксов для типа

выводимого значения в функции printf

Префикс

Назначение

l

Выводимое значение интерпретируется, как значение типов long int, unsigned long int, long double в зависимости от используемого типа выводимого значения. Для символьных и строковых значений этот префикс означает использование юникодовых (2-байтных) символов. Для long double также можно использовать префикс L.

ll

Выводимое значение интерпретируется, как значение типов long long, unsigned long long в зависимости от используемого типа выводимого значения. В среде Microsoft Visual Studio 6.0 использование такого префикса невозможно.

h

Выводимое значение интерпретируется, как значение типов short int, unsigned short int в зависимости от используемого типа выводимого значения. Для символьных и строковых значений этот префикс означает использование 1-байтных символов.

I32

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

I64

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

  •  тип выводимого значения  перечень типов выводимых значений и их назначение представлены в табл. 13.

Табл. 13. Перечень типов

выводимого значения в функции printf

Тип выводимого значения

Назначение

i, d

Знаковое десятичное целочисленное значение

u

Беззнаковое десятичное целочисленное значение

o

Беззнаковое восьмеричное целочисленное значение

x

Беззнаковое шестнадцатеричное целочисленное значение, цифры a, b, c, d, e, f обозначаются маленькими латинскими буквами.

X

Беззнаковое шестнадцатеричное целочисленное значение, цифры A, B, C, D, E, F обозначаются большими латинскими буквами.

f

Вещественное значение в формате с фиксированной точкой.

e

Вещественное значение в экспоненциальном формате. Символ e выводится маленькой буквой.  

E

Вещественное значение в экспоненциальном формате. Символ E выводится большой буквой.  

g

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

G

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

a

Знаковое шестнадцатеричное вещественное значение в экспоненциальном формате, цифры a, b, c, d, e, f обозначаются маленькими латинскими буквами. В среде Microsoft Visual Studio 6.0 использование такого типа выводимого значения невозможно.

A

Знаковое шестнадцатеричное вещественное значение в экспоненциальном формате, цифры A, B, C, D, E, F обозначаются большими латинскими буквами. В среде Microsoft Visual Studio 6.0 использование такого типа выводимого значения невозможно.

с

Целочисленнное значение интерпретируется как 1-байтный код символа.

С

Целочисленнное значение интерпретируется как 2-байтный код символа. Этот тип выводимого значения характерен только для компиляторов Microsoft.

s

Предназначен для вывода строки 1-байтных символов. Если указатель на строку равен NULL, то на экран выведется (null).

S

Предназначен для вывода строки 2-байтных символов. Если указатель на строку равен NULL, то на экран выведется (null). Этот тип выводимого значения характерен только для компиляторов Microsoft.

p

Предназначен для вывода адреса аргумента в шестнадцатеричном формате. Этот тип выводимого значения характерен только для компиляторов Microsoft.

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

Функция printf возвращает либо количество выведенных символов, либо целое отрицательное число в случае ошибки.

Для ввода данных с клавиатуры (вообще говоря для ввода со стандартного входного потока stdin, ввод из которого, как правило, соответствует вводу с клавиатуры) в консольных приложениях, написанных на языке C++, может применяться функция scanf. Синтаксис ее вызова во многом схож с синтаксисом вызова функции printf и в общем виде выглядит следующим образом:

scanf(”Строка форматного ввода”, обязательный_перечень_адресов);

При этом:

  1.  Строка форматного ввода  представляет собой обычную строковую константу, которая должна содержать специальные спецификаторы форматного ввода. Кроме этого эта строка может содержать другие символы, например, пробел, \t, \n, которые показывают, что эти символы будут использоваться как разделители между значениями и не могут входить в считываемое значение.
    1.  Обязательный_перечень_адресов представляет собой список адресов переменных, с которыми будут связаны прочитанные значения. Для получения адреса переменной (что является обязательным) используется унарная операция &. Количество адресов равно количеству спецификаторы форматного ввода.
      1.  Спецификатор форматного ввода во многом похож на спецификатор форматного вывода и в общем виде выглядит следующим образом:

%[*][ширина поля ввода][{h | l | ll | I64 | L}]тип_вводимого_значения

В этой записи:

  •  квадратные скобки означают необязательность того, что в них находится.
  •  фигурные скобки означают, что должно быть указан один из аргументов, которые в них находятся.
  •  * означает, что следующее прочитанное значение будет считано, интерпретировано, но связано НЕ БУДЕТ.
  •  ширина поля вывода  неотрицательное целое десятичное число, которое задает минимальную длину поля вывода. Если количество выводимых символов больше чем заданная длина, то поле вывода автоматически расширяется.
  •  префикс типа вводимого значения  совпадает с аналогичными значениями, описанными в табл. 12.
  •  тип выводимого значения  совпадают с аналогичными значениями, описанными в табл. 13 с некоторым исключениями:
    1.  Типы g, G, f, e, E приводят к тому, что будет прочитано значение типа float, которое должно быть связано с переменной такого типа. Такими образом, соответствующим аргументом в списке адресов должен быть адрес переменной (элемента массива и т.п.) типа float, в противном случае связывание значения не произойдет.
    2.  Типы a, A, p для функции scanf не применяются.

Функция scanf возвращает либо количество прочитанных и связанных значений, либо EOF (константа, соответствующая значению -1), что является признаком конца файла (строки) или ошибки.

Следует отметить, что в языке C++ для ввода/вывода с использованием стандартных потоков обычно применяются экземпляры классов istream и ostream cin и cout, соответственно, и перегруженные операторы >> и <<, для чего нужно выполнить подключение iostream.h (т.е. написать строку #include <iostream.h>). Например,

cin >> a >> b;

cout << a << endl << b << endl;

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

При выполнении лабораторных работ необходимо использовать вышеописанные механизмы ввода/вывода с помощью функций scanf / printf.

Задание

Написать программу для решения квадратного уравнения. Корни квадратного уравнения вида могут вычисляться по следующей формуле:

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

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

На примере этой программы необходимо продемонстрировать возможности по пошаговой отладке в среде Microsoft Visual Studio 6.0 независимо от выбранного уровня сложности.


Лабораторная работа №2.

Цель работы: Изучить операторы цикла языка C++. Научиться применять одиночные и вложенные циклы при написании программ.

Теоретическая часть.

Операторы цикла языка C++.

В языке C++ существует 3 оператора цикла: цикл for, цикл с предусловием - while и цикл с постусловием - dowhile. Рассмотрим синтаксис этих циклов.

Общий вид цикла for:

for (инициализация;

       условие_продолжения_работы;         

 действия_выполняемые_на_каждом_шагу)

//1-ая строка называется заголовко цикла

       действие; //Тело цикла

for (инициализация;

       условие_продолжения_работы;         

 действия_выполняемые_на_каждом_шагу)

//1-ая строка называется заголовком цикла

{

        Блок_действий; //Тело цикла

}

Заголовок цикла for состоит из трех частей:

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

for (int i = 0, j = 1; i <= j + 10; i++)

// Тело цикла

В данном примере в разделе инициализации описаны 2 переменные типа int и им присвоены начальные значения. Таким образом, в разделе инициализации допустимо описание переменных только одного выбранного пользователем типа. Следует обратить внимание на тот факт, что в ANSI стандарте языка C++ обе описанных в данном примере будут недоступны при выходе из цикла. Однако в Microsoft Visual Studio при настройках компилятора по умолчанию (опция /Ze) этого НЕ ПРОИСХОДИТ, и переменные остаются доступными при выходе из цикла.

  1.  Условие продолжения работы. Условие, которое проверяется перед началом каждой итерации цикла. Если оно истинно (true), то цикл продолжает свое выполнение, в противном случае цикл завершается.
  2.  Действия, выполняемые на каждом шагу. Это действия, выполняемые в конце каждой итерации цикла перед очередной проверкой истинности условия продолжения работы цикла. Обычно, среди этих действий помещают изменение счетчика цикла.

Самый простой цикл for выглядит следующим образом:

for (; ;)

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

Общий вид цикла while:

while (условие_продолж_работы)

//1-ая строка называется заголовком цикла

       действие; //Тело цикла

while (условие_продолж_работы)

//1-ая строка называется заголовком цикла

{

        Блок_действий; //Тело цикла

}

Цикл while работает до тех пор, пока условие выполняется, т.е. является истинным. Условие является набором каких-то операций сравнения, которые могут быть объединены логическими связками (см. описание условия для условного оператора if в л.р. №1). ВАЖНЫМ моментом является присваивание начального значение счетчику цикла перед началом работы цикла и изменение значения счетчика цикла в теле цикла, т.к. автоматическое изменение, в отличие от цикла for не происходит. Если при первой проверке условие не выполняется, т.е. является ложным, то цикл не выполнится ни одного раза.

Общий вид цикла dowhile:

do

{

 Блок_действий; //Тело цикла}

}while (условие_продолж_работы);

Цикл dowhile во многом подобен циклу while, но в то же время имеет следующие отличия:

  1.  В цикле dowhile, КАК ПРАВИЛО, ВСЕГДА используются операторные скобки независимо от количества выполняемых операторов в теле цикла (хотя допустимо в случае, когда тело цикла состоит из одного оператора их не использовать).
  2.  Цикл dowhile является циклом с постусловием, т.е. в независимости от истинности или ложности условия продолжения работы цикл ВСЕГДА сработает 1 раз.

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

Пример 1.

#include <stdio.h>

int main()

{

        int N;

        printf("Enter the high limit of sequence: ");

        scanf("%i", &N);

        for (int i = 1; i <= N; i++)

int i = 1;

           printf("%3i ^ 2 = %i\n", i, i * i);

while (i <= N)

do

                 

{

{       

      printf("%3i ^ 2 = %i\n", i, i * i);

      i++;

}

}while (i <= N);

         return 0;

}

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

Задание

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая для заданного натурального числа n (вводится с клавиатуры после соответствующего запроса) найдет список всех простых чисел от 2 до n. Вывод списка простых чисел осуществляется на экран. Необходимо провести оптимизацию алгоритма решения задачи с целью повышения быстродействия написанной программы. Сделать отчет, в котором будет замерено время работы оптимизированной и неоптимизированной версий программ для n >= 500000.
  2.  Для получения 80% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая для заданного натурального числа n (вводится с клавиатуры после соответствующего запроса) найдет список всех простых чисел от 2 до n. Вывод списка простых чисел осуществляется на экран.
  3.  Для получения 65% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая для заданного натурального числа n(вводится с клавиатуры после соответствующего запроса) определит является ли оно простым.

Простые числа – это числа, которые делятся только на 1 и на само себя.

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

Рекомендации по выполнению лабораторной работы.

Оптимизация алгоритма решаемой задачи производится за счет:

  •  уменьшения количества перебираемых чисел (на основании того, что данное число НЕ МОЖЕТ БЫТЬ простым);
  •  уменьшения количества делителей, используемых для определения того простое ли число;
  •  отсутствия деления проверяемого числа на 1 и на само себя.
  •  остановки процесса проверки, если для проверяемого числа найден хотя бы один делитель, отличный от 1 и от самого числа.


Лабораторная работа №3.

Цель работы: Изучить тип данных массив и основные приемы для работы с переменными этого типа. Изучить синтаксис написание подпрограмм. Научиться применять тип данных массив и подпрограммы при написании программ.

Теоретическая часть.

Краткие сведения о типе данных массив и работе с ним.

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

typedef тип_дан_элемента Имя_типа [кол-во элементов массива];

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

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

typedef int CubeType[3][3][3];

Таким образом был описан тип данных 3-ехмерного массива с базовым типом данных int (тип данных элемента), в котором в каждом измерении 3 элемента, следовательно, итоговое количество элементов в массивах такого типа будет равно 3*3*3 = 27 и переменные такого типа будут занимать 27 * 4 (размер типа int) = 108 байт.

Следует отметить, что в среде Microsoft Visual Studio 6.0 единственное ограничение при описании переменных их суммарный объем (глобальных или локальных для функции) не может быть более 4Гб.

Примеры описания массивов:

Пример 1.

typedef int ArType[12];

ArType A1, A2;

char  B[5], C[5];

В примере 1 описан тип данных ArType и переменные этого типа A1 и A2. Этот массив имеет 12 элементов, а каждая из переменных A1 и A2 занимает, соответственно, 12*4 = 48 байт. Также описаны переменные B и C, которые являются массивами символов (char) из 5 элементов. Таким образом, описание массивов допускается сразу при описании переменных (хотя для повышения читабельности программы и упрощения ее модификации рекомендуется описывать пользовательские типы).

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

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

Пример 2.

#define N 5

typedef int ArType[N];

ArType A1 = {1, 2, 3, 4, 5}, A2 = {11, 12, 13};

float F2[3] = {1.0, 2.234, 5.321}, F3;

char c1[5] = {’a’, ’b’, ’c’, ’d’, ’e’}, c2[5] = ”abcd”;

В примере 2 для массива F2 заданы начальные значения элементов, а для массива F3  нет, что также допустимо. Следует обратить внимание, что в массиве A2 заданы значения только для первых 3-ех элементов. Остальные элементы получат значение 0 соответствующего типа данных. При работе с массивами символов следует обратить внимание, что их инициализация может проходить как по общим правилам (массив c1  в примере 2), так и с помощью строковой константы (массив c2  в примере 2). При этом в случае необходимости использовать массив символов как строку нужно обеспечить, чтобы среди символов массива был символ с ASCII кодом 0 (признак окончания строки). В противном случае стандартные функции по работе со строками не смогут корректно определить конец строки.

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

Рассмотрим простой пример работы с массивами: пусть требуется написать программу, которая выводит на экран сумму всех нечетных элементов массива. Элементы массива вводятся с клавиатуры.

Пример 3.

#include <stdio.h>

int main()

{

#define N 5

typedef unsigned char basetype;

typedef basetype ArNum[N];

ArNum Numbers;

printf ("Enter %i integer number >=0 and <= 255\n", N);

for (int i = 0, Sum = 0; i < N; i++)

{

 printf("Enter element number %i: ", i + 1);

 scanf("%hu", &Numbers[i]);

 if (Numbers[i] & 1)

  Sum += Numbers[i];

}

printf("The sum of even elements of array is %u\n", Sum);

return 0;

}

 Следует обратить внимание на то, что:

  1.  Для получения доступа к определенному элементу массива необходимо указать переменную типа массив, после которой в квадратных скобках указывается выражение, при вычислении которого получается нужный индекс. Индексом также может быть значение переменной, целочисленная константа или выражение.
  2.  В результате вычисления значения выражения Numbers[i] & 1 производится побитовая операция «и» над значением текущего элемента массива и 1. В результате выполнения этой операции все биты, кроме последнего, всегда будут равны 0. Значение последнего бита будет 1 для нечетного числа (т.к. единица в самом младшем бите двоичного представления числа означает, что при его разложении на сумму степеней двойки в ней будет присутствовать слагаемое , что однозначно показывает на нечетность числа), и 0 – для четного.

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

  1.  void *memmove(void *dest, const void *src, size_t count). Эта функция копирует из источника (src) в приемник (dest) count байт (тип данных size_t эквивалентен unsigned int), причем src и dest являются бестиповыми указателями, поэтому в качестве них можно использовать адреса любые массивов и классов (class, struct, union). Если при копировании произойдет частичное наложение областей памяти источника и приемника, то эта функция гарантировано обеспечивает считывание данных из этой области перед записью в нее новой информации. Функция возвращает указатель на dest. Для использования этой функции необходимо подключить файл string.h. Например, для массивов B и C из примера 1 можно с помощью следующего вызова этой функции выполнить копирование 4-ех элементов (со 1-ого по 4-ый, т.к. нумерация начинается от нуля) из массива C в массив B (на место элементов 03):

memmove(&B[1], C, 4);

еще один пример:

memmove(B + 1, B, 4);

копирование 4-ех элементов (со 1-ого по 4-ый) из массива B в массив B (на место элементов 03):

  1.  void *memcpy(void *dest, const void *src, size_t count). Работает аналогично memmove. Отличия: не гарантирует правильной работы в случае частичного наложения областей памяти источника и приемника; функцию также можно использовать при подключении memory.h.
  2.  void *memset(void *dest, int c, size_t count). Эта функция заполняет в приемнике (dest), который является, как и для предыдущих функций, бестиповым указателем, count байт значением c. Функция возвращает указатель на dest. Для использования этой функции необходимо подключить файл string.h или memory.h. Например, можно заполнить символом ‘0’ все элементы массива B из примера 1 следующим вызовом этой функции:

memset(B, ’0’, 5);

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

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

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

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

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

inline тип_возвр_значения <имя функции>(Type1 Var1, Type2 Var2,  …, TypeN VarN);

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

Список параметров является необязательной частью описания заголовка функции и заключается в круглые скобки. При описании типа данных аргументов можно задавать класс памяти register, а также использовать ключевое слово const. Даже, если у функции нет аргументов, круглые скобки все же НЕОБХОДИМО ИСПОЛЬЗОВАТЬ. В этом случае также рекомендуется в скобках написать ключевое слово void. Ключевое слово inline может находиться до или после типа возвращаемого значения и означает, что тело функции будет вставлено в точку вызова, что характерно для маленьких простых функций и призвано служить повышению производительности за счет некоторого увеличения исполнимого кода программы.

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

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

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

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

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

#include <stdio.h>

inline int sum(int a, int b)

{

return a + b;

}

//-----------------------------------

bool div(int a, int b, int &res, int &rem)

{

if (b != 0)

{

 res = a / b;

 rem = a % b;

 return true;

}

else

 return false;

}

//-----------------------------------

int main()

{

int a = 5, b = 10, res, rem;

 

printf("%i + %i = %i\n%i + %i = %i\n", a, b, sum(a, b), 10, -10, sum(10, -10));

if (div(b, a, res, rem))

 printf("%i / %i = %i + %i\n", b, a, res, rem);

 return 0;

}

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

bool div(int a, int b, int *res, int *rem)

{

if (b != 0)

{

 *res = a / b;

 *rem = a % b;

 

 return true;

}

else

 return false;

}

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

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

if (div(b, a, &res, &rem))

 printf("%i / %i = %i + %i\n", b, a, res, rem);

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

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

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

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

#include <stdio.h>

inline sum(int a, int b)

{

return a + b;

}

//--------------------------------

inline unsigned sum(unsigned a, unsigned b)

{

return a + b;

}

//--------------------------------

inline double sum(double a, double b)

{

return a + b;

}

//--------------------------------

inline __int64 sum(__int64 a, __int64 b)

{

return a + b;

}

//--------------------------------

inline unsigned __int64 sum(unsigned __int64 a, unsigned __int64 b)

{

 return a + b;

}

//--------------------------------

main()

{

printf("%i + %i = %i\n", 10, 5, sum(10, 5));

printf("%u + %u = %u\n", 4000000000, 20, sum(4000000000, 20));

printf("%6.4f + %6.4f = %f6.4\n", 10.234, 5.545, sum(10.234, 5.545));

printf("%I64i + %I64i = %I64i\n", __int64(40000000000), __int64(20), sum(__int64(40000000000), __int64(20)));

printf("%I64u + %I64u = %I64u\n", unsigned __int64(8000000000000000000), unsigned __int64(20), sum(unsigned __int64(8000000000000000000), unsigned __int64(20)));

 return 0;

}

Следует отметить, что:

  1.  Если тип возвращаемого значения функции int, то при описании заголовка его можно не указывать.
  2.  В этом примере все 5 функций суммирования имеют одинаковые имена, но различные типы аргументов. Компилятор допускает такую ситуацию, которая называется перегрузка (overloading) функций. Для того, чтобы компилятор смог отличить одну функцию от другой, необходимо выполнение хотя бы одно из следующих условий:
    •  количество аргументов у функций было различным;
    •  типы аргументов (хотя бы у одного для каждой функции) отличались друг от друга;
    •  присутствие или отсутствие у функции признака переменного числа аргументов (запятая в конце перечня аргументов или запятая и …).
  3.  Т.к. для целочисленных типов данных существует неявное приведение к типу int, то первая функция подойдет в большинстве случаев.
  4.  В случаях, когда компилятор неспособен автоматически определить тип выражения (использование в примере констант типа __int64 и unsigned __int64) необходимо применить явное приведение типа.
  5.  Для констант типа __int64 и unsigned __int64 всегда необходимо выполнять явное приведение типа, т.к. целочисленная константа по умолчанию считается типа int или unsigned int.

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

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

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

#include <stdio.h>

#define sum_macro(a, b)\

(a) + (b)

//------------------------------

template <class T> inline T sum(T a, T b)

{

return a + b;

}

//------------------------------

main()

{

printf("%i + %i = %i\n", 10, 5, sum_macro(10, 5));

printf("%i + %i = %i\n\n", 10, 5, sum(10, 5));

printf("%u + %u = %u\n", 4000000000, 20, sum(unsigned(4000000000), unsigned(20)));

printf("%u + %u = %u\n\n", 4000000000, 20, sum_macro(4000000000, 20));

printf("%6.4f + %6.4f = %f6.4\n", 10.234, 5.545, sum(10.234, 5.545));

printf("%6.4f + %6.4f = %f6.4\n\n", 10.234, 5.545, sum_macro(10.234, 5.545));

printf("%I64i + %I64i = %I64i\n", __int64(40000000000), __int64(20), sum(__int64(40000000000), __int64(20)));

printf("%I64i + %I64i = %I64i\n\n", __int64(40000000000), __int64(20), sum_macro(__int64(40000000000), 20));

printf("%I64u + %I64u = %I64u\n", unsigned __int64(8000000000000000000), unsigned __int64(20), sum(unsigned __int64(8000000000000000000), unsigned __int64(20)));

printf("%I64u + %I64u = %I64u\n\n", unsigned __int64(8000000000000000000), unsigned __int64(20), sum_macro(unsigned __int64(8000000000000000000), 20));

 return 0;

}

Следует отметить, что:

  1.  При использовании макроопределения не происходит проверка типов данных формальных и фактических параметров, соответственно могут быть ситуации, когда произойдет ошибка из-за невозможности выполнить выбранное действие над переданным значением из-за несовместимости типов данных (например, взятие остатка от деления для вещественных значений, что невозможно).
  2.  Символ \ в конце первой строки макроопределения означает перенос на другую строку.
  3.  Тело макроопределения будет подставлено в точку вызова после подстановки в него аргументов макроопределения.
  4.  При передаче выражения в качестве аргумента оно не будет вычислено, его вычисление будет происходить после подстановки макроопределения в точку вызова. Поэтому, во избежание неправильного вычисления выражений, рекомендуется в теле макроопределения его аргументы ВСЕГДА брать в скобки.
  5.  Функция-шаблон по своему поведению во многом похожа на обычную функцию: происходит проверка совместимости типов формальных и фактических параметров; подстановка тела функции в точку вызова происходит только при наличии в описании ключевого слова inline; при подстановке выражений в качестве фактических параметров происходит их вычисление.
  6.  Генерация исполнимого кода функции-шаблона происходит ТОЛЬКО после вызова этой функции в коде программы.
  7.  При описании функции-шаблона сначала используется ключевое слово template, после которого в <> записывается перечень абстрактных типов данных в виде: class имя_типа1, …, class имя_типаN (в примере class T). При этом вместо ключевого слова class можно использовать ключевое слово typename. Под абстрактными типами данных подразумеваются типы аргументов (локальных переменных), которые будут использованы при генерации кода функции-шаблона. Эти абстрактные типы, как правило, автоматически определяются компилятором на основании типов данных аргументов.
  8.  Количество различных вариантов исполнимого кода, генерируемого компилятором для функции-шаблона, зависит от количества вызовов этой функции с аргументами различных типов данных.

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

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

float func_sum_and_out(float a, float b, int precision = 4)

{

float res = a + b;

printf(”%7.*f, precision, res);

return res;

}

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

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

Задание

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

#define NLen 77

typedef char NumberType[NLen];

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

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

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

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

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

Например:

int c;

// Код функции

c = _getch();

// Проверки введенного символа.

putchar(c); // Если введен допустимый символ

  1.  **Для получения 250% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая выполнит деление, умножение, сложение и вычитание (выбор выполняемого действия осуществляет пользователь) над двумя числами в вышеуказанном формате, причем они могут быть разной длины. При написании программы использовать классы (class или struct) для хранения числа: хранится не только массив чисел, а и текущая длина числа. При этом для описанного типа данных перегрузить операции присваивания и сравнения.
  2.  *Для получения 150% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая выполнит умножение, сложение и вычитание (выбор выполняемого действия осуществляет пользователь) над двумя числами в вышеуказанном формате, причем они могут быть разной длины.
  3.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая выполнит сложение и вычитание (выбор выполняемого действия осуществляет пользователь) над двумя числами в вышеуказанном формате, причем они могут быть разной длины.
  4.  Для получения 80% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая выполнит сложение или вычитание (по Вашему выбору) над двумя числами в вышеуказанном формате, причем они могут быть разной длины.
  5.  Для получения 65% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая выполнит сложение или вычитание (по Вашему выбору) над двумя числами в вышеуказанном формате, причем предполагается, что они одинаковой длины (70 символов) и первое число больше второго.

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

Рекомендации по выполнению лабораторной работы.

Рекомендуемый алгоритм работы программы:

  1.  Посимвольно ввести 1-ое число (рекомендуется написание функции). Ввод числа состоит из следующих шагов:
    1.  выдается приглашение пользователю для ввода числа.
    2.  пока не достигнуто максимальное число цифр в числе или не нажата клавиша Enter (клавиша генерирует 2 символа с кодами #13#10) осуществляется ввод очередной цифры числа. Цифры вводятся в виде соответствующих символов.
    3.  производится выравнивание числа по правому краю. Для этого можно использовать функцию memmove.
  2.  Посимвольно ввести 2-ое число. Осуществляется аналогично вводу 1-ого числа.
  3.  Выбор действия (сложение или вычитание), если в задании реализуются оба действия.
  4.  Сложение (рекомендуется написание функции). Производится посимвольно справа налево. Причем при сложении каждых двух цифр необходимо учитывать разряд переноса от предыдущего сложения и формировать разряд переноса для следующего сложения. Т.к. каждая цифра представлена в виде символа, а этот тип данных не поддерживает арифметических операций, то ее необходимо перевести в числовое представление. Для этого можно вычесть из нее код символа ‘0’.
  5.  Вычитание (рекомендуется написание функции). Аналогично сложению, производится справа налево. При вычитании каждой пары цифр необходимо учитывать разряд заёма от предыдущего вычитания и формировать разряд заёма для следующего вычитания. Очевидно, что при вычитании двух цифр вычитание кода символа ‘0’ производить не нужно.
  6.  Вывести результат выполнения действия на экран (рекомендуется написание функции). Начиная с первого значащего символа (или знака числа) выводятся все цифры числа на экран.


Лабораторная работа №4.

Цель работы: изучить работу со строками, тип данных структура (struct) и основные приемы для работы с ним. Научиться применять полученные теоретические знания при написании программ.

Теоретическая часть.

Краткие сведения о работе со строками.

Как уже говорилось в лабораторной работе №1, строка на языке C++ представляет собой последовательность символов, которая должна заканчиваться символом с ASCII кодом 0 (’\0’). Для стандартных функций, предназначенных для работы со строками, этот символ является ЕДИНТСВЕННЫМ признаком, на основании которого определяется конец строки. Такой подход для определения конца строки называется NULL terminated strings (строки, оканчивающиеся нулем).

Для хранения символов строки можно использовать статические или динамические одномерные массивы символов. Работа со статическими массивами (память под переменные типа массив выделяется в процессе компиляции программы) была рассмотрена в лабораторной работе №3.

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

Пример описания переменной-указателя для работы с одномерным динамическим массивом:

char *st;

При описании переменных, как уже говорилось в лабораторной работе №3, символ * означает, что описывается типизированный указатель, т.е. указатель на значение определенного типа, в данном случае  char. Сам по себе указатель это беззнаковое целочисленное 32-битное значение. При описании указателя областью действия знака * (собственно, признак указателя) является ОДНА СЛЕДУЮЩАЯ ПЕРЕМЕННАЯ.

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

int n;

//…..

scanf(”%i”, &n);

//…..

st = new char [n];

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

Следует обратить внимание на тот факт, что обращаться к элементам динамического массива можно ТОЛЬКО ПОСЛЕ УДАЧНОГО ВЫДЕЛЕНИЯ ПАМЯТИ по требованию программы. Обращение к области памяти, которая не принадлежит Вашей программе (в том числе и адрес NULL), немедленно приводит к ошибке и работа программы прекращается.

Обращаться к элементам динамического массива можно также как и к элементам статического, например:

printf(”%c\n”, st[3]);

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

Другим способом обращения к элементу является операция разименования (*), например:

printf(”%c\n”, *(st + 3));

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

После работы с динамическим массивом перед завершением программы нужно ОБЯЗАТЕЛЬНО освободить память, выделенную под него. Для этого можно воспользоваться оператором delete. Для рассмотренного выше массива это можно сделать следующим образом:

delete []st;

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

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

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

Существует ряд стандартных функций, разработанных специально для работы со строками. Для их использования необходимо подключить файл string.h. Также следует отметить, что все рассмотренные ниже функции не могут корректно обработать ситуацию, когда указатель на строку = NULL (произойдет ошибка выполнения программы). Рассмотрим эти функции, на примере работы с переменной st, предположив, что под нее выделено 15 байт и в нее считана с клавиатуры строка ”1234567” (сначала приводится описание прототипа, потом пример использования функции, в случае необходимости):

  •  size_t strlen(const char *st), возвращает длину строки в символах (байтах), таким образом, strlen(st) в нашем случае вернет значение 7 (символ с ASCII кодом 0 не считается), причем st может быть переменной или константой;
  •  char *strcat(char *strDest, const char *strSrc) дописывает содержимое строки, на которую указывает strSrc к строке, на которую указывает strDest. Через свое имя возвращает указатель strDest. Например: в результате выполнения такого вызова strcat(st, ”890”) будет получено значение ”1234567890”. 1-ый аргумент  strcat должен быть ТОЛЬКО переменной, 2-ой  может быть переменной или константой;
  •  char *strncat(char *strDest, const char *strSrc, size_t count) аналогично предыдущей функции, но дописывает только count символов из 2-ой строки в 1-ую. Следует отметить, что при использовании этой функции нужно самостоятельно добавлять символ с кодом 0 в конце строки, на которую указывает 1-ый аргумент, в случае, когда символ с кодом 0 не был скопирован из строки-источника;
  •  char *strstr(const char *str, const char *strSearch) осуществляет поиск начала подстроки, на которую указывает strSearch, в строке, на которую указывает str и, если strSearch будет найдена в строке str, то возвращает указатель на 1-ый символ строки strSearch в строке str, в противном случае будет возвращен NULL. Если strSearch указывает на пустую строку (обозначается ””), то возвращается str. Например: strstr(st, ”234”) вернет значение указатель на символ 2 в строке str, а strstr (st, ”235”) вернет NULL. Аргументы strstr могут быть переменными или константами;
  •  char *strchr(const char *str, int c) осуществляет поиск символа c в строке (слева направо), на которую указывает str и, если c будет найден в строке str, то возвращает указатель на 1-ый такой символ в строке str, в противном случае будет возвращен NULL. Например: strchr(st, ’2’) вернет значение указатель на символ 2 в строке str, а strstr (st, ’0’) вернет NULL. Аргументы strchr могут быть переменными или константами;
  •  char *strrchr(const char *str, int c) практически полностью аналогична strchr, за исключением того, что поиск производится справа налево;
  •  int strcmp(const char *string1, const char *string2)  осуществляет по символьное сравнение двух строк (с учетом регистра буквенных символов). Если 1-ая строка < 2-ой, то возвращается значение < 0, если равны 0, если больше значение больше 0. Например, strcmp(st, ”1234567”) вернет 0, а strcmp(st, ”912”)  значение < 0. Аргументы strcmp могут быть переменными или константами;
  •  int strncmp(const char *string1, const char *string2, size_t count) аналогична предыдущей функции, но сравнивает только 1-ых count символов;
  •  int stricmp(const char *string1, const char *string2) аналогична функции strcmp, но сравнение происходит без учета регистра буквенных символов;
  •  int strnicmp(const char *string1, const char *string2) аналогична функции strncmp, но сравнение происходит без учета регистра буквенных символов;
  •  char *strcpy(char *strDest, const char *strSrc) копирует содержимое строки, на которую указывает strSrc, в строку, на которую указывает strDest. 1-ый аргумент  strcpy должен быть ТОЛЬКО переменной, 2-ой  может быть переменной или константой;
  •  char *strncpy(char *strDest, const char *strSrc, size_t count) аналогична предыдущей, но копирует только указанное количество символов count. Следует отметить, что при использовании этой функции нужно самостоятельно добавлять символ с кодом 0 в конце строки, на которую указывает 1-ый аргумент, в случае, когда символ с кодом 0 не был скопирован из строки-источника;
  •  char *_strdup(const char *strSrc) делает копию строки своего аргумента и возвращает указатель на эту копию. Аргумент может быть переменной или константой;
  •  char *_strrev(char *str) переставляет символы в строке в обратном порядке и возвращает указатель на строку str. Например, в результате выполнения _strrev(st) st будет указывать на строку, содержащую ”7654321”. Аргумент может быть ТОЛЬКО переменной;
  •  char *_strset(char *str, int c) устанавливает все символы строки равными символу c. Пример: в результате выполнения _strset(st, ’2’) st будет указывать на строку ”2222222”. 1-ый аргумент  _strset должен быть ТОЛЬКО переменной, 2-ой  может быть переменной или константой;
  •  char *_strnset(char *str, int c, size_t count ) аналогична предыдущей, но изменяет на символ c только указанное количество символов count;
  •  char *_strlwr(char *str) переводит все буквенные символы в нижний регистр. Через свое имя возвращает str. Аргумент может быть ТОЛЬКО переменной;
  •  char *_strupr(char *str) переводит все буквенные символы в верхний регистр. Через свое имя возвращает str. Аргумент может быть ТОЛЬКО переменной;
  •  Для использования следующих функций необходимо подключить stdio.h.
  •  int puts(const char *str) выводит на в стандартный выходной поток stdout содержимое строки, на которую указывает str. По окончании вывода происходит перевод курсора на новую строку. Возвращает EOF в случае неудачного окончания вывода. Аргумент может быть переменной или константой;
  •  int sprintf(char *buffer, const char *format, необязательный_перечень_аргументов) аналогична printf, но форматный вывод производится в строку (1-ый аргумент функции).

Удаление и вставку части строки можно реализовать с использованием функции memmove.

Следует отметить, что в C++ для работы со строками можно также использовать тип данных string. Однако, в отличие от языка Паскаль, тип данных string является шаблонным классом из STL (Standard Template Library) с перегруженными операторами (о перегрузке операторов будет сказано ниже). Рассмотрим пример использования этого типа данных:

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

#include "stdafx.h"

// Необходимо для компиляции проекта в среде Microsoft Visual Studio 2005

#include <string>

#include <iostream>

// Необходимо указывать без расширения h

using namespace std;

// Использование пространства имен std

main()

{

string str1, str2, str3;

cout << "Enter string 1: ";

getline(cin, str1);

/*

Следует отметить, что для ввода данных используется функция getline (функция scanf неприменима) из пространства имен std. Эта функция 1-ым апгументом получает имя входного потока, 2-ым имя переменной типа string, а 3-им, необязательным символ, после которого символы прекращают сохраняться во внутренний буфер (по умолчанию это ‘\n’), причем ввод прекращается по нажатию клавиши Enter или по достижению максимально возможной длины буфера.

Также можно осуществить ввод данных в переменную str1 с помощью входного потока cin следующим образом:

cin >> str1;

Аналогично можно осуществить ввод данных и для переменных str2 и str3.

*/

cout << "Enter string 2: ";

getline(cin, str2);

cout << "Enter string 3: ";

getline(cin, str3);

/*

Метод find класса string предназначен для поиска 1-ого аргумента во внутреннем  буфере экземпляра класса, для которого он вызван, начиная с позиции, указанной 2-ым аргументом. Он возвращает номер символа, с которого начинается подстрока str2 в строке str1 или значение < 0, если поиск был неудачен.

Метод length класса string предназначен для получения длины строки.

*/

if (str2.length() > 0)

 for (int pos = 0; (pos = str1.find(str2, pos)) >= 0; pos += str3.length())

 {

/*

Метод erase класса string предназначен для удаления во внутреннем  буфере экземпляра класса, для которого он вызван, начиная с позиции, указанной 1-ым аргументом, количества символов, указанного 2-ым аргументом.

*/

  str1.erase(pos, str2.length());

/*

Метод insert класса string предназначен для вставки 2-ого аргумента во внутренний  буфер экземпляра класса, для которого он вызван, начиная с позиции, указанной 1-ым аргументом.

*/

  str1.insert(pos, str3);

 }

cout << str1 << endl;

return 0;

}

Следует отметить, что данный пример не будет работать в среде Microsoft Visual Studio 6.0 ввиду неправильной работы функции getline (2-ая строка не будет считываться). Если же использовать ввод данных в переменные типа string через входной поток cin, не используя функцию getline, то пример 1 будет работоспособен и в среде Microsoft Visual Studio 6.0.

Краткие сведения о типе данных структура (struct) и работе с ним.

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

struct имя_типа_данных

{

 тип_данных1 Имя_поля1;

тип_данных2 Имя_поля2, Имя_поля3;

 …

тип_данныхM Имя_поляN;

описание_конструктора;

описание_деструктора;

описание_перегруженных_операторов;

} имя_перем1, …, имя_перемK;

После описания пользовательского типа данных структура можно сразу же описывать переменные этого типа (экземпляры этого класса), как показано выше. Если этого не сделать, то после символа } ОБЯЗАТЕЛЬНО НУЖНО ПОСТАВИТЬ ТОЧКУ С ЗАПЯТОЙ.

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

Конструктор и деструктор это специальные функции, которые относятся к типу данных структура (являются его методами) и имеют строго определенные имена: имя конструктора совпадает с именем типа данных, а имя деструктора ~имя_типа_данных. Их написание необязательно (всегда генерируются автоматически, хотя при этом не выполняют никаких видимых пользователем действий). Конструктор и деструктор всегда вызываются автоматически: конструктор при создании экземпляра описанного типа (описании статической переменной или выделении памяти с помощью оператора new при работе с динамическими переменными), а деструктор при уничтожении экземпляра класса (для статической переменной при выходе за пределы области видимости: для локальных переменных это, как правило, функция, в которой они описаны, а для глобальных переменных или переменных с классом памяти static  при окончании работы программы; для динамических переменных при вызове оператора delete). Конструктор может иметь аргументы, в этом случае при описании (выделении памяти) для них нужно подставить фактические параметры. В конструкторе обычно прописывают действия, необходимые при создании экземпляра класса, например, начальную инициализацию полей структуры. В деструкторе прописывают действия, необходимые при уничтожении экземпляра класса, например, освобождение динамически выделенной памяти.

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

Рассмотрим пример описания типа данных структура и переменной этого типа, а также работы с ней:

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

#include <stdio.h>

#include <math.h>

inline double VLen (double Y, double X)

{

return sqrt(Y * Y + X * X);

}

struct Point

{

double y, x;

Point()

{

 y = x = 0;

}

Point& operator = (Point &Val)

{

 if (this != &Val)

 {

  y = Val.y;

  x = Val.x;

 }

 return *this;

 }

//----------------------------------

Point& operator = (double Val)

{

 y = x = Val;

 return *this;

}

//----------------------------------

 bool operator > (Point  &Val)

{

 //Сравниваем длин векторов с началом в точке (0;0)

 return VLen(y, x) > VLen(Val.y, Val.x);

 }

 

//----------------------------------

 bool operator < (Point  &Val)

{

 //Сравниваем длин векторов с началом в точке (0;0)

 return VLen(y, x) < VLen(Val.y, Val.x);

 }

 

//----------------------------------

 bool operator >= (Point  &Val)

{

 //Сравниваем длин векторов с началом в точке (0;0)

 return VLen(y, x) >= VLen(Val.y, Val.x);

 }

//----------------------------------

 bool operator <= (Point  &Val)

{

 //Сравниваем длин векторов с началом в точке (0;0)

 return VLen(y, x) <= VLen(Val.y, Val.x);

 }

//----------------------------------

 bool operator == (Point  &Val)

{

 //Сравниваем длин векторов с началом в точке (0;0)

 return VLen(y, x) == VLen(Val.y, Val.x);

 }

//----------------------------------

 bool operator != (Point  &Val)

{

 //Сравниваем длин векторов с началом в точке (0;0)

 return VLen(y, x) != VLen(Val.y, Val.x);

}

};

//----------------------------------

main()

{

Point p1, p2;

p1 = 5.5;

p2 = p1;

p2.y -= 2;

if (p1 != p2)

 puts("The points have different distances from the point (0;0)");

return 0;

}

Следует отметить, что:

  1.  Конструктор (Point) в данном примере выполняет начальную инициализацию полей y и x нулями и аргументов не имеет.
  2.  При перегрузке операторов важно аргумент класса Point (это же касается любого аргумента типа class, struct, union) передавать по ссылке, иначе при выходе из перегруженного оператора произойдет вызов деструктора для переданного в качестве фактического параметра экземпляра класса, что, как правило, приводит к невозможности его дальнейшего использования.
  3.  Все перегруженные операторы сравнения построены на сравнении расстояний от выбранной точки до начала координат (хотя возможны и другие интерпретации сравнения), что и является их результатом. Тип возвращаемого значения у них, соответственно, bool.
  4.  При перегрузке оператора присваивания типом возвращаемого значения является ссылка на класс Point. При этом рекомендуется выполнять проверку экземпляра класса, который стоит слева от операции присваивания (ее перегруженный оператор присваивания и вызывается) на совпадение с экземпляром класса, который стоит справа от оператора присваивания (в этом случае нечего присваивать). Для этого происходит проверка на совпадение адреса, который хранится в указателе this (указатель на экземпляр класса, перегруженный оператор которого был вызван) с адресом экземпляра класса, который передан в качестве аргумента.
  5.  Перегруженный оператор присваивания должен возвращать результат разименования указателя this.
  6.  Как показано в примере для перегруженного оператора присваивания, тип данных операнда правой части (для бинарных операций) не обязательно должен совпадать с типом данных операнда левой части. Таким образом, в примере описано два разных оператора присваивания: первый для случая, когда оба операнда имеют тип Point, а второй когда 1-ый операнд типа Point, а второй типа double.
  7.  После перегрузки операторов их использование для переменных типа Point, ничем не отличается от использования аналогичных операторов, например, для арифметических типов данных.
  8.  Для доступа к полям и методам структуры необходимо указать переменную типа структура, поставить точку и написать имя поля или метода (как показано в примере 1). Если дан указатель на переменную типа структура, то необходимо вместо оператора . использовать оператор -> для доступа к полям и методам структуры.
  9.  Каждая переменная типа Point в рассмотренном примере будет занимать 8 + 8 = 16 байт.

Задание

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

Массив, хранящий информацию о студентах, задается следующим образом:

typedef char group_type[7];

struct StudInfo

{

            char *Surname, *Name, *Patroname;       {Ф. И. О}

            group_type group;                             {учебная группа}

            char gender;                            {пол}

            unsigned short int BY;                              {год рождения}

            float AM;                                 {средний балл}

 };

В функции main необходимо описать переменную:

StudInfo *SG;

Для описанного типа данных написать:

  1.  Конструктор, который инициализирует нулями поля (для указателей  NULL).
  2.  Деструктор, который освобождает память, выделенную под поля Surname, Name, Patroname.
  3.  Перегруженный оператор присваивания (в случае необходимости). При его написании необходимо для каждого указателя на строку выполнить следующие действия:
    •  определить сколько памяти необходимо выделить для нового строкового значения, используя функцию strlen;
    •  освободить память, выделенную под изменяемое поле структуры;
    •  выделить полученный на первом шаге объем памяти + 1 байт (для символа с кодом 0);
    •  использовать соответствующую функцию для копирования строкового значения.
  4.  Необходимые для работы операторы сравнения (если нужно). Критерий сравнения выбрать самостоятельно.

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

  •  1, 2 – специальность
  •  3, 4 – год поступления
  •  5, 6 – подгруппа

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

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

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая при вычислении номера курса будет учитывать год и месяц (вводятся пользователем с клавиатуры после соответствующего запроса), т.к. учебный год не совпадает с календарным. Также необходимо предусмотреть возможность перехода между столетиями, при вычислении номера курса.
  2.  Для получения 85% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая при вычислении номера курса будет учитывать год и месяц (вводятся пользователем с клавиатуры после соответствующего запроса), т.к. учебный год не совпадает с календарным.
  3.  Для получения 65% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая при вычислении номера курса будет учитывать только текущий год (вводится пользователем с клавиатуры после соответствующего запроса).

Вывод таблицы производится на экран с применением форматирования.

При решении задачи необходимо применить процедурно-ориентированное программирование (использовать функции).


Лабораторная работа №5.

Цель работы: познакомиться с понятием рекурсия, а также научиться писать рекурсивные функции на языке C++. Научиться применять рекурсию при написании программ.

Теоретическая часть.

Краткие сведения об использовании рекурсии.

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

  1.  Использование рекурсии целесообразно в том случае, когда алгоритм решаемой задачи предполагает аналогичные действия, как для целой последовательности, так и для ее части или в случае, когда сложно (неудобно) сформировать условие остановки для цикла обработки данных.
  2.  Т.к. при каждом рекурсивном вызове подпрограммы все локальные переменные и аргументы сохраняются в стеке, то использование рекурсии оправданно, когда при решении задачи удобно использовать такую «бесплатную» (не требующую специальной программной реализации) структуру данных, как стек.
  3.  Однако сохранение в стеке всех аргументов является также и недостатком применения рекурсии, т.к. размер стека ограничен (для среды Microsoft Visual Studio его размер по умолчанию составляет 1Мб, но его можно изменить). Соответственно, при большом количестве рекурсивных вызовов и значительном объеме сохраняемой в стеке информации легко получить переполнение стека (stack overflow), что приводит к аварийному останову выполняемой программы.
  4.  Передача информации через стек также требует дополнительных действий (скрытых от пользователя), что приводит к некоторому снижению производительности. Поэтому при использовании рекурсии нужно сделать приблизительную оценку того, насколько ее применение повысит эффективность программы, учитывая ее достоинства и недостатки.

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

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

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

Заголовок_рекурсивной_функции

{

 Блок_действий_до_рекурсивного_вызова;

 if (условие)

  Рекурсивный_вызов;

Блок_действий_после_рекурсивного_вызова;

}

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

Рекурсивная подпрограмма работает следующим образом: выполняются действия до рекурсивного вызова (Блок_действий_до_рекурсивного_вызова), после чего проверяется условие продолжения рекурсии. Если оно истинно, то происходит очередной рекурсивный вызов с использованием полученных в процессе выполнения Блок_действий_до_рекурсивного_вызова новых значений аргументов функции и снова выполняется Блок_действий_до_рекурсивного_вызова и т.д. Если же условие продолжения рекурсии становится ложным, то рекурсивный вызов функции не происходит и выполняется Блок_действий_после_рекурсивного_вызова, после чего выполнение функции завершается и происходит возврат в точку вызова (переход к выполнению следующего после вызова функции оператора). Т.к. вызов был рекурсивный то после окончания выполнения функции N-ого вызова, происходит возврат на 1-ый оператор Блок_действий_после_рекурсивного_вызова N-1-ого вызова. При этом восстанавливаются все значения аргументов функции и значения локальных переменных, которые были на N-1-ом вызове путем чтения информации из стека. Этот процесс возврата из рекурсивных вызовов происходит до тех пор, пока не произойдет возврат из первоначального вызова рекурсивной функции, причем при каждом возврате из рекурсивного вызова будут восстанавливать те значения аргументов и локальных переменных, которые имели место на момент осуществления рекурсивного вызова.

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

Пример 1.

#include <stdio.h>

typedef int val_type;

//-----------------------------------

/*

Не рекурсивная подпрограмма, которая для нахождения суммы использует цикл с for

*/

val_type sum_nonrec(val_type st_val, val_type end_val, val_type step)

{

for (val_type sum = 0; st_val <= end_val; sum += st_val, st_val += step);

return sum;

}

//-----------------------------------

/*

Рекурсивная подпрограмма, которая осуществляет суммирование чисел в прямом порядке, т.е. от st_val до end_val. Для проверки условия останова рекурсивных вызовов используется тернарный оператор ? :.

*/

val_type sum_rec1(val_type st_val, val_type end_val, val_type step)

{

 return st_val <= end_val ? st_val + sum_rec1(st_val + step, end_val, step) : 0;

}

//-----------------------------------

/*

Рекурсивная подпрограмма, которая осуществляет суммирование чисел в обратном порядке, т.е. от st_val до end_val. Несмотря на ее схожесть с предыдущей рекурсивной функцией перестановка слагаемых существенно изменяет процесс суммирования. Для проверки условия останова рекурсивных вызовов используется тернарный оператор ? :.

*/

val_type sum_rec2(val_type st_val, val_type end_val, val_type step)

{

 return st_val <= end_val ? sum_rec2(st_val + step, end_val, step) + st_val : 0;

}

//-----------------------------------

main()

{

val_type st_val, end_val, step;

printf("Enter the first number of sequence: ");

scanf("%i", &st_val);

printf("Enter the last number of sequence: ");

scanf("%i", &end_val);

printf("Enter the step: ");

scanf("%i", &step);

printf("Sum of numbers from %i to %i with step %i calculated by function \n\tsum_nonrec = %i\n\tsum_rec1 = %i\n\tsum_rec2 = %i\n",

  st_val, end_val, step, sum_nonrec(st_val, end_val, step), sum_rec1(st_val, end_val, step), sum_rec2(st_val, end_val, step));

 return 0;

}

Задание

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

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая будет осуществлять перевод числа в двоичную, восьмеричную и шестнадцатеричную систему счисления.

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

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

  1.  Для получения 85% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая будет осуществлять перевод числа в шестнадцатеричную систему счисления.

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

  •  исходное число в десятичной системе счисления (в виде числа).

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

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

  •  исходное число в десятичной системе счисления (в виде числа).

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

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

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

Рекомендации по выполнению задания.

Для получения символа, представляющего очередную цифру числа можно для двоичной или восьмеричной системы к текущей цифре прибавить код символа 0. Для шестнадцатеричной системы такой подход применим только для первых 10 цифр. Для получения символьного представления цифр, представленных буквами латинского алфавита, кроме символа 0 необходимо добавить число 7 (’A’ - ’9’ - 1).


Лабораторная работа №6.

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

Теоретическая часть.

Краткие сведения о работе с файлами.

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

Имя файла является наряду с путем (совокупностью каталогов (папок), пройдя через которые можно достигнуть интересующего файла, причем для DOS длина полного пути не должна превышать 80 символов) являются важнейшими параметрами, описывающими файл. Имя файла – это некоторый набор символов (до 8 – для DOS, до 255 – для Windows 9x/NT/2000/XP). Некоторые имена файлов являются особыми, т.к. на самом деле соответствуют определенным устройствам, например:

con – при вводе данных соответствует клавиатуре, при выводе – дисплею.

nul – несуществующее устройство. Запись в файл с таким именем  эквивалентна записи в никуда, т.е. отсутствия записи данных.

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

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

При использовании функций, требующих для своей работы указателя на структуру FILE, необходимо подключить stdio.h, а при использовании функций, требующих для своей работы дескриптора файла  io.h и fcntl.h, sys/stat.h (в двух последних файлах заголовков описаны константы).

Рассмотрим общую схему работы с файлами:

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

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

Таблица 1. Функции для работы с файлами

на языке С++

№ шага

Прототип функции

Назначение функции

1

FILE *fopen (

    const char* filename,

    const char* mode

);

FILE *_fdopen (

    int fd,

    const char* mode

);

Получает указатель на структуру FILE, если файл уже открыт и имеется его дескриптор (fd). Работа аналогична fopen.

int  _fileno(

   FILE *stream

);

Получает дескриптор уже открытого потока (файла). Если аргумент NULL, то функция вернет -1. Если аргумент не указывает на открытый файл, то поведение функции не определено.

FILE *freopen (

    const char* filename,

    const char* mode

    FILE *stream

);

Связывает указатель на структуру FILE (3-ий аргумент) с новым файлом. Файл, связанный с 3-им аргументом, будет закрыт. Работа аналогична fopen.

int _open (

    const char* filename,

    int flag [,

    int pmode]

);

int _creat (

    const char* filename,

    int pmode

);

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

Открывает файл. 1-ый аргумент задает имя файла (можно указывать путь), а 2-ой аргумент режим открытия файла. Рассмотрим некоторые допустимые значения 2-ого аргумента:

r открывает файл для чтения. Файл должен существовать.

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

a  открывает для записи в конец файла (маркер EOF (символ с кодом 26) остается до тех пор, пока не будет произведена запись). Если файл не существует, то он создается.

r+”  открывает файл для чтения и для записи. Файл должен существовать.

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

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

Следует отметить, что

  1.  В режимах r+”, ”w+” и a+” при переключении между чтением и записью требуется вызвать одну из следующих функций: fflush, fsetpos, fseek, rewind для выполнения позиционирования.
  2.  В режимах aи a+” запись производится ТОЛЬКО в конец файла, даже если переместить указатель та текущую позицию в начало.

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

t  файл открывается в текстовом режиме. В результате перевод строки интерпретируется как один символ, CTRL+Z интерпретируется как признак конца файла.

b  файл открывается в бинарном режиме. Никаких специальных интерпретаций символов не происходит. Несовместим с t.

S  используется оптимизация для последовательного считывания данных из файла.

R  используется оптимизация для случайного считывания данных из файла.

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

D  признак временного файла. Автоматически удаляется, когда с ним прекращается работа (он закрывается).

Следует отметить, что режимы S”, ”R”, ”T”, ”Dмогут применяться только при использовании компиляторов Microsoft.

Через свое имя функция возвращает указатель на структуру FILE, которая соответствует открытому файлу. Если при открытии файла возникли ошибки, то возвращается NULL.

Открывает файл, возвращая его дескриптор (-1 в случае ошибки). 1-ый аргумент имя файла (можно указывать путь). 2-ой аргумент задает режим открытия файла с помощью целочисленной константы (возможны объединения основных и дополнительных режимов с помощью операции |), для использования которых необходимо подключить файл fcntl.h. Рассмотрим некоторые допустимые значения 2-ого аргумента:

_O_RDONLY  эквивалентен режиму r функции fopen. Несовместим с режимами _O_RDWR, _O_WRONLY.

_O_WRONLY  эквивалентен режиму w функции fopen. Несовместим с режимами _O_RDWR, _O_RDONLY. Обычно используется в такой комбинации: _O_WRONLY | _O_CREAT | _O_TRUNC.

_O_RDWR  эквивалентен режиму r+” или w+” функции fopen. Несовместим с режимами _O_WRONLY, _O_RDONLY. Обычно используется в такой комбинации: _O_RDWR | _O_CREAT | _O_TRUNC.

_O_APPEND  в комбинации _O_WRONLY | _O_CREAT | _O_APPEND эквивалентен режиму a функции fopen.

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

  1.  _S_IREAD  только чтение.
  2.  _S_IWRITE  только запись (хотя на самом деле чтение тоже допустимо).

_O_TRUNC  открывает файл и «усекает» его длину до 0 байт. Совместно с _O_CREAT позволяет создавать новые файлы.

Вспомогательные режимы:

_O_TEXT  эквивалентен режиму t функции fopen.

_O_BINARY  эквивалентен режиму ”b” функции fopen.

_O_SEQUENTIAL  эквивалентен режиму ”S” функции fopen.

_O_RANDOM  эквивалентен режиму ”R” функции fopen.

Комбинации режимов:

_O_CREAT |  _O_SHORT_LIVED  эквивалентен режиму wT функции fopen.

_O_CREAT | _O_TEMPORARY  эквивалентен режиму wD функции fopen.

_O_CREAT | _O_EXCL  возвращает ошибку, если файл существует.

_O_RDWR | _O_CREAT | _O_APPEND  эквивалентен режиму ”a+” функции fopen.

2

Проверка успешности открытия файла.

Производится путем сравнения указателя на структуру FILE со значением NULL или дескриптора файла со значением -1. Если не равно, то файл открыт успешно.

2 a)

Функции для потокового ввода / вывода:

int fscanf (

  FILE *stream,

  const char *format,

  arguments

);

int fprintf (

  FILE *stream,

  const char *format,

  arguments

);

size_t fread (

   void *buffer,

   size_t size,

   size_t count,

   FILE *stream

);

size_t fwrite (

   void *buffer,

   size_t size,

   size_t count,

   FILE *stream

);

int fgetc (

  FILE *stream

);

char *fgets (

  char *str,

  int n,

  FILE *stream

);

int fputc (

  int c,

  FILE *stream

);

int fputs (

  const char *str,

  FILE *stream

);

int feof (

  FILE *stream

);

int fflush (

  FILE *stream

);

int _flushall ()

int setvbuf (

  FILE *stream,

  char *buffer,

  int mode,

  size_t size

);

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

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

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

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

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

Считывает символ из потока (1-ый аргумент). Возвращает ASCII код считанного символа или EOF в случае ошибки или достижения конца файла.

Считывает строку из потока (3-ий аргумент) в буфер (1-ый аргумент), память под который должна быть выделена до вызова функции, при этом длина строки (с учетом конечного символа с кодом 0) не должна превышать n (2-ой аргумент). Считывание символов строки завершается, когда достигнут конец файла или строки (символы перевода курсора на новую строку при этом помещаются в буфер) или прочитано n-1 символов.

Записывает символ (1-ый аргумент) в поток (2-ой аргумент). Возвращает ASCII код записанного символа или EOF в случае ошибки.

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

Проверяет достижение конца потока (1-ый аргумент). Если конец потока не достигнут возвращает не 0, в противном случае 0.

Если в связанный с потоком файл (1-ый аргумент) производилась запись, то сбрасывает содержимое буфера на диск. Если из связанного с потоком файла производилось чтение, то очищает буфер. Применяется только к буферизированным потокам. В случае удачного завершения возвращает 0, в противном случае  EOF. fflush (NULL) сбрасывает на диск буфера всех связанных с потоками файлов, в которые производилась запись.

Подобна функции fflush, но при этом срабатывает для всех открытых потоков.

Предназначена для изменения размеров (4-ый аргумент) буфера (указатель на него 2-ой аргумент) для указанного потока (1-ый аргумент). 3-ий аргумент задает включение (значение _IOFBF) или выключение (значение _IONBF, при этом 2-ой и 4-ый аргументы игнорируются). Размер буфера может быть от 2 до 2147483647 байт.

Функции для ввода / вывода с использованием дескриптора файла.

int _read (

   int fd,

   void *buffer,

   unsigned int count,

);

int _write (

   int fd,

   const void *buffer,

   unsigned int count,

);

int _eof(

  int fd

);

Для их использования необходимо подключить файл io.h.

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

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

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

Навигационные функции при работе с потоками.

void rewind (

  FILE *stream

);

int fgetpos (

  FILE *stream,

  fpos_t *pos

);

int fsetpos (

  FILE *stream,

  const fpos_t *pos

);

int ftell (

  FILE *stream

);

 

int fseek (

  FILE *stream,

  long offset,

  int origin

);

Позволяют получать / изменять указатель на текущее положение в потоке.

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

Для файла, связанного с потоком (1-ый аргумент) возвращает целочисленное значение, соответствующее позиции указателя на текущее положение в потоке (2-ой аргумент). Если функция выполнена успешно, то она возвращает 0, в противном случае не 0. Используется в паре с fsetpos.

Для файла, связанного с потоком (1-ый аргумент) изменяет позицию указателя на текущее положение в потоке на значение 2-ого аргумента. Если функция выполнена успешно, то она возвращает 0, в противном случае не 0.

Для файла, связанного с потоком (1-ый аргумент) возвращает целочисленное значение, соответствующее позиции указателя на текущее положение в потоке. В случае возникновения ошибки возвращает -1. Следует отметить, что функция ftell может неверно определить указатель на текущее положение в потоке, если файл открыт в текстовом режиме из-за интерпретации 2-ух символов с кодами 13 и 10 как одного. Для файлов, открытых в режиме добавления данных в конец, функция вернет позицию, для последней операции ввода/вывода, при этом для чтения и для записи эти позиции могут быть разными. Используется в паре с fseek.

Для файла, связанного с потоком (1-ый аргумент) изменяет позицию указателя на текущее положение в потоке на значение 2-ого аргумента. 3-ий аргумент задает начало отсчета: SEEK_CUR  от текущего положения; SEEK_END  от конца файла; SEEK_SET  от начала файла. Если функция выполнена успешно, то она возвращает 0, в противном случае не 0.

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

long _tell (

  int handle

);

long _lseek (

  int fd,

  long offset,

  int origin

);

//------------------------------

int _chsize (

  int fd,

  long offset

);      

int _filelength (

  int fd

);

Позволяют получать / изменять указатель на текущее положение в файле.

Работает аналогично ftell, но файл задается своим дескриптором.

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

//--------------------------------------------------------------------------

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

Изменяет размер файла, указанного своим дескриптором (1-ый аргумент) на размер, указанный 2-ым аргументом. Если функция выполнена успешно, то возвращает значение 0, иначе возвращает -1.

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

2 b)

Закрытие файла.

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

int flcose (

  FILE *stream

);

int _fcloseall ()

При работе с файлами через их дескрипторы.

int _close (

  int fd

);

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

Закрывает поток, а, соответственно, и файл, связанный с потоком. Если функция выполнена успешно, то возвращает значение 0, иначе  EOF.

Закрывает все открытые потоки (кроме stdin, stdout, stderr), а, соответственно, и файлы, связанные с потоками. Если функция выполнена успешно, то возвращает значение 0, иначе  EOF.

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

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

#include <stdio.h>

#include <io.h>

#include <fcntl.h>

#include <string.h>

#include <sys/stat.h>

#include <stdlib.h>

typedef unsigned int UINT;

#define MAX_NAME 255

/*

Эта функция генерирует очередное имя файла путем добавления к базовому имени

номера обрабатываемого фрагмента.

new_name - указатель на новое имя с учетом расширения.

base_name - часть имени исходного файла, которое не включает расширение.

ext - расширение

num_part - указатель на буфер для преобразования числа в строку.

format_str - указатель на форматную строку для функции sprintf.

cur_number - номер текущего фрагмента.

Функция возвращает указатель на полученное имя.

*/

inline char *get_cur_name(char *new_name, char *base_name, char *ext, char *num_part, char *format_str, int cur_number)

{

sprintf(num_part, format_str, cur_number);

return strcat(strcat(strcpy(new_name, base_name), num_part), ext);

}

//-------------------------------------------------

/*

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

quantity - количество фрагментов

*/

int get_max_num_digits(UINT quantity)

{

for (int num = 0; quantity > 0; quantity /= 10, num++);

 return num;

}

//-------------------------------------------------

/*

Эта функция получает фрагменты имени исходного файла.

name - указатель на имя исходного файла.

base_name - двойной указатель на часть имени файла (не включая расширение).

ext - двойной указатель на расширение исходного файла.

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

При работе с двойными указателями для записи/получения адреса используется одиночное разименование, для записи значения по полученному адресу - двойное разименование

*/

void get_name_parts(char *name, char **base_name, char **ext)

{

char *pos = strrchr(name, '.');

int len;

if (pos != NULL)

{

 *base_name = new char [len = pos - name + 1];

 strncpy(*base_name, name, len - 1);

// Ручное добавление символа с кодом 0 в конец строки

 (*base_name)[len - 1] = 0;

 *ext = new char [strlen(name) - len + 2];

 strcpy(*ext, name + len - 1);

}

else

{

 *base_name = new char [strlen(name) + 1];

 *ext = new char;

 **ext = 0;

 strcpy(*base_name, name);

 }

}

//-------------------------------------------------

/*

Эта функция разбивает исходный файл на фрагменты и записывает его в указанное

количество выходных файлов.

name - имя исходного файла.

quantity - количество фрагментов.

Функция возвращает 0, в случае удачного выполнения, -1 - в случае возникновения ошибки.

*/

int write_parts(char *name, UINT quantity)

{

int max_dig = get_max_num_digits(quantity), part_size, cnt_read;

char *base_name, *ext, *new_name = new char [strlen(name) + max_dig + 1];

char *format_str = new char [max_dig + 4], *num_part = new char [max_dig + 1], *buf;

 UINT cur_number = 0;

// Важным является использование режима _O_BINARY как для входного, так и

// для выходных файлов

int fd = _open(name, _O_RDONLY | _O_BINARY | _O_SEQUENTIAL), fd_out = -1;

if (fd != -1)

{

 get_name_parts(name, &base_name, &ext);

// Определяем размер каждого фрагмента, кроме последнего

 part_size = filelength(fd) / quantity;

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

// записи текущего фрагмента

// %% означает вывод символа %

 sprintf(format_str, "%%0%ii", max_dig);

// Выделяем память под буфер для ввода / вывода

 buf = new char [part_size];

 do

 {

// Создаем файл с сгенерированным именем для записи очередного фрагмента.

// Если создался

  if ((fd_out = _open(get_cur_name(new_name, base_name, ext, num_part, format_str, cur_number++), _O_WRONLY | _O_CREAT | O_TRUNC | _O_BINARY | _O_SEQUENTIAL, _S_IWRITE)) != -1)

/*   {

Читаем данные (необходимо 2-ой аргумент функции _read  привести к типу данных нетипизированный указатель (void *)) и определяем, сколько прочитано. Если не ошибка

*/

   if ((cnt_read = _read(fd, (void *)(buf), part_size)) > 0)

   {

// Записываем столько, сколько прочитали

    _write(fd_out, (void *)(buf), cnt_read);

// Закрываем очередной выходной файл

    _close(fd_out);

   }

  }

  else

   break;

// Пока количество прочитанных байт совпадает с размером фрагмента

 }while (cnt_read == part_size);

// Закрываем исходный файл

 _close(fd);

 delete []buf;

 delete []base_name;

 delete []ext;

}

delete []new_name;

delete []format_str;

delete []num_part;

return fd_out < 0 ? -1 : 0;

}

//----------------------------------------------------------------

main(int argc, char *argv[])

{

char *name;

UINT quantity;

// Если есть хотя бы 1 аргумент командной строки

if (argc >= 1)

{

// 0-ой элемент массива argv хранит путь к исполнимому файлу программы

 name = new char [strlen(argv[1]) + 1];

// Считываем его как имя файла

 strcpy(name, argv[1]);

}

// Иначе вводим с клавиатуры

 else

{

 name = new char [MAX_NAME + 1];

 printf("Enter file name: ");

 scanf("%s", name);

 }

  

// Если 2-ой аргумент командной строки

if (argc >= 2)

{

// Переводим его в числовой вид и интерпретируем как количество фрагментов

// Для перевода в целочисленный вид используется функция atoi, прототип

// которой описан в файле stdlib.h

 quantity = atoi(argv[2]);

}

// Если 2-ого аргумента командной строки нет или

// перевод в число был неудачен, то считываем с клавиатуры

 if (argc < 2 || quantity == 0)

{

 printf("Enter file name: ");

 scanf("%u", &quantity);

}

if (write_parts(name, quantity) == -1)

 puts("An error was found! Check filename and try once more!");

else

 puts("Operation was successfully completed");

delete []name;

 return 0;

}

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

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <io.h>

typedef unsigned int UINT;

#define MAX_NAME 255

/*

Эта функция генерирует очередное имя файла путем добавления к базовому имени

номера обрабатываемого фрагмента.

new_name - указатель на новое имя с учетом расширения.

base_name - часть имени исходного файла, которое не включает расширение.

ext – расширение.

num_part - указатель на буфер для преобразования числа в строку.

format_str - указатель на форматную строку для функции sprintf.

cur_number - номер текущего фрагмента.

Функция возвращает указатель на полученное имя.

*/

inline char *get_cur_name(char *new_name, char *base_name, char *ext, char *num_part, char *format_str, int cur_number)

{

sprintf(num_part, format_str, cur_number);

return strcat(strcat(strcpy(new_name, base_name), num_part), ext);

}

//-------------------------------------------------

/*

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

quantity - количество фрагментов.

*/

int get_max_num_digits(UINT quantity)

{

for (int num = 0; quantity > 0; quantity /= 10, num++);

 return num;

}

//-------------------------------------------------

/*

Эта функция получает фрагменты имени исходного файла одной из частей, исключая номер части.

name - указатель на имя исходного файла.

base_name - двойной указатель на часть имени файла (не включая расширение).

ext - двойной указатель на расширение исходного файла.

max_dig - максимальное количество символов, которое занимает номер части.

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

При работе с двойными указателями для записи/получения адреса используется одиночное разименование, для записи значения по полученному адресу - двойное разименование

*/

void get_name_parts(char *name, char **base_name, char **ext, int max_dig)

{

char *pos = strrchr(name, '.');

 int len;

 if (pos != NULL)

{

 *base_name = new char [len = pos - name + 1 - max_dig];

 strncpy(*base_name, name, len - 1);

// Ручное добавление символа с кодом 0 в конец строки

 (*base_name)[len - 1] = 0;

 *ext = new char [strlen(name) - len + 2 - max_dig];

 strcpy(*ext, name + len + max_dig - 1);

}

else

{

 *base_name = new char [strlen(name) + 1 - max_dig];

 *ext = new char;

 **ext = 0;

 len = strlen(name) - max_dig - 1;

 strncpy(*base_name, name, len);

 (*base_name)[len] = 0;

}

}

//-------------------------------------------------

/*

Эта функция собирает фрагменты файла в указанный выходной файл.

name - имя фрагмента исходного файла.

quantity - количество фрагментов.

name_out - имя выходного файла.

Функция возвращает 0, в случае удачного выполнения, -1 - в случае возникновения ошибки.

*/

int read_parts(char *name, UINT quantity, char *name_out)

{

int max_dig = get_max_num_digits(quantity), part_size, cnt_read;

char *base_name, *ext, *new_name = new char [strlen(name) + max_dig + 1];

char *format_str = new char [max_dig + 4], *num_part = new char [max_dig + 1], *buf = NULL;

 UINT cur_number = 0;

// Важным является использование режима _O_BINARY как для входных, так и

// для выходного файлов

FILE *f_out = fopen(name_out, "wbS"), *f = NULL;

if (f_out != NULL)

{

 get_name_parts(name, &base_name, &ext, max_dig);

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

// текущего фрагмента.

// %% означает вывод символа %

 sprintf(format_str, "%%0%ii", max_dig);

 do

 {

// Открываем файл с сгенерированным именем для чтения очередного фрагмента.

// Если открылся

  if ((f = fopen(get_cur_name(new_name, base_name, ext, num_part, format_str, cur_number++), "rbS")) != NULL)

  {

   if (buf == NULL)

   {

    part_size = filelength(fileno(f));

    buf = new char [part_size];

   }

/*   {

Читаем данные (необходимо 2-ой аргумент функции _read  привести к типу данных нетипизированный указатель (void *)) и определяем, сколько прочитано. Если не ошибка

*/

   if ((cnt_read = fread((void *)(buf), 1, part_size, f)) > 0)

   {

// Записываем столько, сколько прочитали

    fwrite((void *)(buf), 1, cnt_read, f_out);

// Закрываем очередной входной файл

    fclose(f);

   }

  }

  else

   break;

// Пока количество прочитанных байт совпадает с размером фрагмента

 }while (cnt_read == part_size);

// Закрываем исходный файл

 fclose(f_out);

 delete []buf;

 delete []base_name;

 delete []ext;

}

delete []new_name;

delete []format_str;

delete []num_part;

 return f == NULL ? -1 : 0;

}

//----------------------------------------------------------------

main(int argc, char *argv[])

{

char *name, *name_out;

UINT quantity;

// Если есть хотя бы 1 аргумент командной строки

if (argc >= 1)

{

// 0-ой элемент массива argv хранит путь к исполнимому файлу программы

 name = new char [strlen(argv[1]) + 1];

// Считываем его как имя файла

 strcpy(name, argv[1]);

}

// Иначе вводим с клавиатуры

else

{

 name = new char [MAX_NAME + 1];

 printf("Enter file name of some parts of source file: ");

 scanf("%s", name);

}

  

// Если 2-ой аргумент командной строки

if (argc >= 2)

{

// Переводим его в числовой вид и интерпретируем как количество фрагментов

// Для перевода в целочисленный вид используется функция atoi, прототип

// которой описан в файле stdlib.h

 quantity = atoi(argv[2]);

}

// Если 2-ого аргумента командной строки нет или

// перевод в число был неудачен, то считываем с клавиатуры

 if (argc < 2 || quantity == 0)

{

 printf("Enter file name: ");

 scanf("%u", &quantity);

}

if (argc >= 3)

 {

// 0-ой элемент массива argv хранит путь к исполнимому файлу программы

 name_out = new char [strlen(argv[3]) + 1];

// Считываем его как имя файла

 strcpy(name_out, argv[3]);

}

// Иначе вводим с клавиатуры

else

{

 name_out = new char [MAX_NAME + 1];

 printf("Enter output file name: ");

 scanf("%s", name_out);

}

if (read_parts(name, quantity, name_out) == -1)

 puts("An error was found! Check filename and try once more!");

else

 puts("Operation was successfully completed");

delete []name;

delete []name_out;

 return 0;

}

Для того, чтобы задать аргументы командной строки при запуске из среды Microsoft Visual Studio 6.0, необходимо выбрать пункт меню Project\Settings (или нажать сочетание клавиш Alt + F7), в открывшемся диалоговом окне выбрать вкладку Debug и на этой вкладке в окне редактирования Program arguments ввести через пробел необходимое количество аргументов командной строки.

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

Рассмотрим некоторые функции, предназначенные для работы с неоткрытыми файлами (для их использования необходимо подключить stdio.h или io.h):

  1.  int remove (const char *path) – выполняет удаление файла, указанного своим именем (1-ый аргумент). Если файл удален успешно, то функция возвращает 0, иначе -1.
  2.  int rename (const char *oldname, const char *newname) – переименовывает файл или каталог, заданный своим именем (1-ый аргумент), изменяя его имя на 2-ой аргумент. Если переименование выполнено успешно, то функция возвращает 0, иначе не 0.

Задание

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая осуществляет шифрование / дешифрование файла, используя при этом блочную работу с файлом и пароль длиной от 1 до 30 символов.
  2.  Для получения 75% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая осуществляет шифрование / дешифрование файла, используя при этом пароль длиной от 1 до 30 символов (Можно использовать посимвольный обмен данными с файлами).
  3.  Для получения 65% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, осуществляет шифрование / дешифрование файла, используя при этом пароль длиной 1 символ (Можно использовать посимвольный обмен данными с файлами).

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

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

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

  •  выходной файл (зашифрованный или расшифрованный).

Алгоритм шифрования / дешифрования следующий:

Над каждым символом исходного файла проводится побитовая операция ^ (побитовое исключающее «ИЛИ») с текущим символом пароля, т.е.

cout = cin ^ p;

где cout – текущий символ выходного файла; сin – текущий символ исходного файла; p – текущий символ пароля.

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

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

Программа должна быть написана с применением процедурно-ориентированного подхода (использовать пользовательские функции).


Лабораторная работа №7.

Цель работы: познакомиться с понятием «каталог» («папка»). Изучить функции для поиска всех файлов с указанными характеристиками. Научиться применять полученные знания при написании программ.

Задание 

Написать программу, которая для заданного каталога (папки) вычисляет ее суммарную длину всех объектов, находящихся в ней, и выводит эту информацию в файл с именем f_size.txt (обычный текстовый файл). Предполагается, что для хранения суммарной длины всех объектов в указанной папке достаточно переменной типа данных unsigned __int64.

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу,  в качестве объектов, длины которых суммируются, должны поддерживаться как файлы, так и папки, причем уровень вложенности папок одной в другую не ограничивается.
  2.  Для получения 80% от числа баллов, отводимых на лабораторную работу, в качестве объектов, длины которых суммируются, должны поддерживаться как файлы, так и папки, причем предполагается, что вложенные папки не могут в свою очередь содержать вложенные каталоги (уровень вложения папок = 1).
  3.  Для получения 65% от числа баллов, отводимых на лабораторную работу,  в качестве объектов, длины которых суммируются, должны поддерживаться только файлы.

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

  1.  Вывести приглашение пользователю для ввода пути и имени выбранной папки.
  2.  Ввести имя папки с указанием полного пути к ней.
  3.  Запомнить текущий каталог.
  4.  Изменить текущий каталог на указанный.
  5.  Для текущего каталога выполнить следующие действия:
    1.  С помощью функций _findfirst и _findnext (их синтаксис изучить самостоятельно, используя справочную систему MSDN) найти имя очередного файла.
    2.  Если найденный файл не является каталогом (проверяется с помощью атрибутов файла, которые можно получить с помощью поля attrib структуры _finddata_t, указателем на которую является 2-ой аргумент функций _findfirst и _findnext), то к текущему значению суммы добавить длину найденного файла.
    3.  Если найденный файл является каталогом, то перейти в этот каталог и осуществить поиск файлов и каталогов в нем.
    4.  Когда функция findnext перестает возвращать имена новых найденных файлов, то программа должна создать в текущем каталоге файл с именем f_size.txt и записать в него суммарный размер всех файлов и каталогов, находящихся в нем, а затем осуществить переход в родительский каталог (если текущий каталог отличен от выбранного пользователем). Родительский каталог обозначается ...  
  6.  Когда найдена суммарная длина всех файлов и папок в указанном каталоге, программа должна перейти в каталог, путь к которому был запомнен в пункте 3.
  7.  В конце поиска необходимо вызвать функцию _findclose для освобождения ресурсов, используемых при поиске.

Рекомендации по выполнению лабораторной работы.

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

Для изменения текущего каталога рекомендуется использовать функцию int _сhdir(const char *dirname), где dirname – имя нового текущего каталога (может содержать указание полного пути). Эта функция возвращает 0 в случае успешного завершения и -1 в случае возникновения ошибки. Для ее использования необходимо подключить direct.h.

Для получения имени текущего каталога рекомендуется использовать функцию char *_getcwd(char *buffer, int maxlen), где buffer – указатель на буфер, в который будет скопировано имя текущего каталога на текущем диске (может быть NULL. В этом случае память будет выделена автоматически), maxlen  максимальное количество символов в пути к текущему каталогу. Функция возвращает buffer в случае удачного завершения или NULL в случае возникновения ошибки. Для ее использования необходимо подключить direct.h.


Лабораторная работа №8.

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

Теоретическая часть.

Краткие сведения об использовании указателей на функции.

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

Рассмотрим применение указателей на функцию на примере.

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

#include <stdio.h>

typedef double val_type;

/*

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

Допустимо и такое описание

typedef val_type (func)(val_type);

*/

typedef val_type (*func)(val_type);

//---------------------------------------------------

// Функция, реализующая математическую  функцию y = 1 / x.

val_type math_func(double val)

{

return = 1 / val;

}

//---------------------------------------------------

/*

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

*/

void print_vals(val_type start_x, val_type end_x, val_type step, func f)

{

for( ; start_x <= end_x; start_x += step)

 printf("%6.4f -> %6.4f\n", start_x, f(start_x));

}

//---------------------------------------------------

main()

{

float start_x, end_x, step;

printf("Enter the start value of x: ");

scanf("%f", &start_x);

printf("Enter the end value of x: ");

scanf("%f", &end_x);

printf("Enter the step of x: ");

scanf("%f", &step);

print_vals(start_x, end_x, step, math_func);

 return 0;

}

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

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

Краткие сведения об обработке исключительных ситуаций.

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

Для обработки исключительных ситуаций в C++ может применяться конструкция:

try

{

 // Код, который может привести к возникновению исключительной ситуации,

           // Еще называется защищенная секция

}

catch (тип_исключения имя_перем)

{

 // Действия при исключениях определенного типа

}

//….

catch ()

{

 // Действия при исключениях всех остальных типов

}

При этом исключение (определенного типа) можно сгенерировать искусственно с помощью оператора throw <выражение>, где значение выражения и определяет тип исключения.

Если в защищенной секции не произойдет ничего, что могло бы возбудить исключительную ситуацию (или просто исключение), то все секции catch пропускаются и программа продолжает свое выполнение в обычном режиме. Если же в защищенной секции исключение произошло, то происходит поиск первой по порядку секции catch, тип обрабатываемого исключения которой совпадает с возникшим исключением. Следует отметить, что если значение, которое соответствует исключительной ситуации, при ее обработке неважно, важен только сам факт возникновения исключения определенного типа, то имя переменной после типа исключения можно опускать. Секция catch, тип исключения которой … , перехватывает исключения всех типов, поэтому ВСЕГДА должна помещаться последней. Если нет подходящей для данного исключения секции catch, то вызывается функция terminate(). Следует отметить, что хотя бы одна секция catch должна быть ОБЯЗАТЕЛЬНО описана.

Пример 2. Демонстрация возможностей обработки исключительных ситуаций.

#include <stdio.h>

main()

{

int choise;

try

{

 printf("Enter 1 - to generate int exception\nEnter 2 - to generate char exception\nEnter 3 - to generate float exception\n");

 printf("Your choise: ");

 scanf("%i", &choise);

/*

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

*/

 switch (choise)

 {

  case 1 : throw 1;

     break;

  case 2 : throw "String";

     break;

  default: throw 2.0;

 }

}

catch (int i)

{

 printf("Integer exception = %i\n", i);

}

catch (char *str)

{

 printf("String exception = %s\n", str);

}

catch (...)

{

 printf("Undefined exception\n");

 }

return 0;

}

Следует отметить, что для перехвата исключений типа деления целочисленного значения на ноль следует использовать секцию catch(…).

Существует возможность вместо стандартной функции terminate() подставить собственную. Для этих целей применяется функция, прототип которой описывается следующим образом: terminate_handler set_terminate(terminate_handler _Pnew), где _Pnew  это указатель на функцию, которая будет вызваться вместо стандартной функции terminate(). Через свое имя функция возвращает указатель на предыдущую функцию terminate(). Для использования функции set_terminate необходимо подключить файл exception.

Пример 3. Использование собственной функции вместо стандартной функции terminate() при обработке исключительных ситуаций.

#include <stdio.h>

#include <process.h>

#include <exception>

void my_terminate()

{

puts("This is my terminate function");

// Функция abort() прекращает работу текущего процесса. Для ее использования

// необходимо подключить process.h

abort();

}

main()

{

terminate_handler oldfunc = set_terminate(my_terminate);

try

{

 throw 2.0;

}

catch (int)

{

// Не перехватывает исключение вещественного типа

 puts("This section do not catch exception divide by zero");

}

puts("You do not see this message");

 return 0;

}

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

#include <stdio.h>

typedef double val_type;

typedef val_type (*func)(val_type);

//---------------------------------------------------

val_type math_func(double val)

{

val_type res = 1 / val;

// Если делим на 0, то возбуждаем целочисленное исключение

 if (val == 0)

 throw(1);

return res;

}

//---------------------------------------------------

void print_vals(val_type start_x, val_type end_x, val_type step, func f)

{

for( ; start_x <= end_x; start_x += step)

 try

 {

// Защищенная секция, в которой вызывается функция, которая может вызвать

// исключение

  printf("%6.4f -> %6.4f\n", start_x, f(start_x));

 }

 catch (int)

 {

// Перехватываются только целочисленные исключения

  printf("%6.4f -> %s\n", start_x, "error");

 }

}

//---------------------------------------------------

main()

{

float start_x, end_x, step;

printf("Enter the start value of x: ");

scanf("%f", &start_x);

printf("Enter the end value of x: ");

scanf("%f", &end_x);

printf("Enter the step of x: ");

scanf("%f", &step);

print_vals(start_x, end_x, step, math_func);

return 0;

}

Задание

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

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо при написании функции вывода, предусмотреть вывод в поток, который может быть как stdout, так и потоком, с которым связан файл. Имя файла вводится с клавиатуры после соответствующего запроса и является аргументом функции вывода, где этот поток и открывается (в случае неудачного открытия выдать соответствующее предупреждение и выйти из программы). Для выбранной математической функции и функции вывода реализовать обработку исключительных ситуаций.
  2.  Для получения 75% от числа баллов, отводимых на лабораторную работу, необходимо для выбранной математической функции и функции вывода реализовать обработку исключительных ситуаций.

Перечень математических функций (функция выбирается путем нахождения остатка от деления номера по журналу на 4 и прибавления к полученному числу 1):

  1.  tg(x).
  2.  .
  3.  .
  4.  .


Лабораторная работа №9.

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

Теоретическая часть.

Краткое описание алгоритмов сортировки.

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

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

Предположим, что имеется следующее описание на языке C++:

typedef <тип элемента> ElemType;

ElemType *Ar;

И в эту переменную записан указатель на выделенную с помощью оператора new память под n элементов типа ElemType, где n некоторое натуральное число.

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

Пусть необходимо обменять i и j элементы массива Ar. Для этого следует выполнить такие действия:

tmp = Ar[i];

Ar[i] = Ar[j];

Ar[j] = tmp;

где tmp – вспомогательная переменная, тип которой совпадает с типом элемента массива.

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

Простые алгоритмы сортировки.

Эти алгоритмы отличаются следующими особенностями:

  1.  В среднем невысокой эффективностью для работы с массивами, когда нет априорных данных об отсортированности элементов.
  2.  Наличием 2-ух циклов: внешнего и внутреннего (предположим, что внешний цикл имеет счетчик i, а внутренний – j).
  3.  Простотой алгоритма.
  4.  Неплохой эффективностью при работе со спискообразными структурами, т.е. со структурами данных, которые характеризуются последовательным доступом к элементам.

К простым алгоритмам сортировки относятся алгоритмы пузырька, минимумов (максимумов) и вставок.  

Рассмотрим эти 3 алгоритма.

Алгоритм пузырька.

Суть этого алгоритма состоит в следующем: на каждом шаге внешнего цикла выбирается очередной элемент неотсортированной части массива (от 0-ого до n – 2-ого). Внутренний цикл осуществляет поочередное сравнение текущего и следующего элемента. Если текущий элемент больше следующего, то происходит обмен элементов местами. Внутренний цикл перебирает элементы от 0-ого до ni – 1 ого.

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

Алгоритм минимумов (максимумов).

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

Внешний цикл перебирает элементы от 0-ого до n – 2 – ого. Внутренний цикл перебирает элементы от i +1–ого до n - 1-ого  - сортировка минимумами или от 0-ого до ni – 2–ого – сортировка максимумами.

Алгоритм минимумов – максимумов.

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

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

Алгоритм вставки.

Один из самых эффективных среди простых алгоритмов сортировки.

Суть алгоритма заключается в следующем: массив делится на отсортированную и неотсортированную части (на 1-ом шаге 0-ой элемент – отсортированная часть; остальной массив – неотсортированная часть).

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

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

После конца внутреннего цикла осуществляется вставка элемента на полученную позицию.

При реализации алгоритма вставки соответствующая подпрограмма должна иметь ТОЛЬКО 2 цикла, т.е. внутренний цикл должен объединять 2 операции: поиск места вставки и сдвиг элементов на 1 позицию вправо.

Алгоритм сортировки Шелла.

Этот алгоритм намного эффективнее простых алгоритмов сортировки, но в среднем немного уступает сортировке Хоара.

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

Далее массив делится на 4 части и происходит описанное выше сравнение элементов 1-ой и 2-ой, затем 2-ой и 3-ей и 3-ей и 4-ой частей. Потом массив делится на 8 частей и т.д., до тех пор, пока количество элементов в каждой части массива больше 1.

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

В конце этой сортировки мы получаем ЧАСТИЧНО УПОРЯДОЧЕННЫЙ массив, который необходимо окончательно отсортировать методом вставки. Смысл сортировки Шелла заключается в предварительном упорядочении массива, в результате чего сдвиги элементов в сортировке вставками будут запускаться гораздо реже, что приводит к повышению эффективности этой сортировки.

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

,

где s – номер элемента последовательности длин для частей массива. Последовательность формируется, пока Len[s] < N / 3. 

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

Алгоритм сортировки Хоара (QuickSort).

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

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

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

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

Краткое описание алгоритмов поиска.

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

Рассмотрим 2 часто используемых алгоритма поиска: поиск с помощью последовательного перебора элементов массива (последовательный поиск) и бинарный поиск.

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

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

Если необходимо найти все указанные элементы в массиве, то при обнаружении 1-ого подходящего элемента поиск продолжается, пока не просмотрим все элементы массива.

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

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

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

Задание

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

  1.  Для получения 100% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая реализует:

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

  •  пузырька (с оптимизацией для определения упорядоченности массива);
  •  минимумов;
  •  вставки;
  •  минимумов и максимумов одновременно;
  •  Шелла (2 варианта: с использованием деления массива на части, длины которых кратны степеням двойки и применением последовательности Седжвика);
  •  quick sort (Хоара).

B) Поиск:

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

Необходимо представить отчет, который будет содержать:

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

  1.  Для получения 80% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая реализует:

А) Сортировку методами:

  •  пузырька (с оптимизацией для определения упорядоченности массива);
  •  минимумов;
  •  вставки;
  •  минимумов и максимумов одновременно;
  •  Шелла;
  •  quick sort (Хоара).

B) Поиск:

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

Необходимо представить отчет, который будет содержать:

  •  количество элементов массива (не менее 100000);
  •  время работы каждого из алгоритмов сортировки для массива указанной длины;
  •  выводы (оценка эффективности реализованных алгоритмов сортировки);
  •  обоснование выбора рекурсивной или не рекурсивной реализации бинарного поиска.

  1.  Для получения 65% от числа баллов, отводимых на лабораторную работу, необходимо написать программу, которая реализует:

А) Сортировку методами:

  •  пузырька (с оптимизацией для определения упорядоченности массива);
  •  минимумов;
  •  вставки;
  •  минимумов и максимумов одновременно;

B) Поиск:

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

Необходимо представить отчет, который будет содержать:

  •  количество элементов массива (не менее 100000);
  •  время работы каждого из алгоритмов сортировки для массива указанной длины;
  •  выводы (оценка эффективности реализованных алгоритмов сортировки);

Сортировка производится по возрастанию.

Прототипы всех функций по сортировке и поиску должны быть описаны в файлах sort.h и search.h, а исполнимый код этих функций должен находиться в .obj файлах (получаются путем компиляции .cpp файлов, причем в данном случае .cpp файлы не должны содержать функцию main) sort.obj и search.obj, которые подключаются к итоговому проекту на этапе линковки. Для этого необходимо выбрать пункт меню Project\Settings или нажать сочетание клавиш Alt+F7 и в открывшемся диалоговом окне выбрать вкладку Link. На этой вкладке необходимо в окне Object/library modules дописать эти два .obj файла.

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

#ifndef имя_файла_заголовков_INCLUDE

#define имя_файла_заголовков_INCLUDE

// содержимое файла заголовков

#endif

PAGE  16


 

А также другие работы, которые могут Вас заинтересовать

48413. Регіональна економіка 328.3 KB
  Міжгалузеві господарські комплекси та регіональні особливості їх розвитку і розміщення Паливноенергетичний комплекс: регіональні особливості розвитку і розміщення 75 ЛЕКЦІЯ 6. Міжгалузеві господарські комплекси та регіональні особливості їх розвитку і розміщення Хімічний комплекс: регіональні особливості розвитку і розміщення 93 ЛЕКЦІЯ 7. Економіка регіонів України: стан та перспективи розвитку 128 ЛЕКЦІЯ 9.030509 ОА 1 2 4 144 52 20 32 – 90 36 – екзамен – – Мета: формування знань щодо теоретичних і практичних засад територіальної...
48414. Базові поняття Системного програмування та СПЗ 924.9 KB
  Системні програмні засоби виконують такі завдання як передача даних з пам’яті довільного доступу на диск або відтворення тексту на дисплеї. Через ці обмеження часто використовуються моніторинг та реєстрація даних; операційні системи мають бути забезпечені дуже якісними підсистемами реєстрації даних. Базові відомості Поняття операційної системи напряму пов'язане з такими поняттями як: Файл іменований впорядкований набір даних на пристрої зберігання інформації; операційна система забезпечує організацію файлів в файлові системи. Файлова...
48415. Державна мова — мова професійного спілкування 652.5 KB
  Правильно використовувати різні мовні засоби відповідно до комунікативних намірів; влучно висловлювати думки для успішного розв’язання проблем і завдань у професійній діяльності; сприймати, відтворювати, редагувати тексти офіційно-ділового й наукового стилів; скорочувати та створювати наукові тексти професійного спрямування, складати план, конспект, реферат тощо, робити необхідні нотатки, виписки відповідно до поставленої мети
48416. Лінійне програмування 112.29 KB
  При дослідженні різноманітних економічних процесів і явищ виникають задачі знаходження таких управлінських рішень які б давали змогу оптимізувати хід процесу явищ. До задач лінійного програмування належать ті задачі в яких функція мети лінійно залежить від керованих параметрів а також співвідношення між керованими і некерованими параметрами мають лінійний вигляд. Обмеження на сировину і її витрати на виготовлення 1 плити кожного виду а також прибуток від реалізації 1 плити задані в таблиці: Тип сировини Витрати на 1 плиту Запаси сировини...
48417. Лекції з історії світової та вітчизняної культури 4.76 MB
  ЗМІСТ ПЕРЕДМОВА ФІЛОСОФСЬКО-ТЕОРЕТИЧНІ ОСНОВИ ІСТОРІЇ КУЛЬТУРИ Поняття культури ПОНЯТТЯ СВГГОВОЇТА НАЦІОНАЛЬНОЇ КУЛЬТУРИ КУЛЬТУРА І СУЧАСНА ЦИВІЛІЗАЦІЯ Регіональна типологія світової культури
48418. Кримінально-процесуальне право України 837.42 KB
  Для захисту особи, суспільства і держави від кримінальних правопорушень, забезпечення того, щоб кожний, хто вчинив кримінальне правопорушення, був притягнутий до відповідальності в міру своєї вини, жоден невинуватий не був обвинувачений або засуджений, необхідно встановити фактичні обставини кримінального правопорушення, винуватість особи у його вчиненні та інші обставини
48419. Лекції з курсу програмування 233.39 KB
  Мови програмування Pscl Bsic Сі. 3 Алгоритмічною мовою або мовою програмування. Алгоритмічною мовою Мовою програмування Turbo Bsic алг Площа progrm squre; дійсн а b S...
48420. Слідчі дії та кримінальне судочинство 207.37 KB
  Підстави проведення негласних слідчих розшукових дій. Засоби що використовуються під час проведення негласних розшукових дій Лекція 3. Негласні слідчі розшукові дії законодавець визначив як різновид слідчих розшукових дій відомості про факт та методи проведення яких не підлягають розголошенню за винятком випадків передбачених Кримінальним процесуальним кодексом України ч. В зв’язку з цим авторським колективом Національної академії внутрішніх справ підготовлено курс лекцій за актуальними питаннями організації та тактики проведення...
48421. ТЕОРІЯ ТЕКСТУ 191.71 KB
  ТЕОРІЯ ТЕКСТУ Ознаки тексту. Функції тексту. Функції журналістського тексту.